CPC frames are composed of a header and optionally a payload. There are
three main frame types currently supported:
- DATA for transmitting payload or regular control frames (like ACKs,
that don't have payload associated with them)
- SYN for connection sequence
- RST for disconnection and errors
Add structure and functions to operate on this header. They will be
leveraged in a future commit where the protocol is actually implemented.
Signed-off-by: Damien Riégel <damien.riegel@silabs.com>
---
drivers/net/cpc/Makefile | 2 +-
drivers/net/cpc/header.c | 237 +++++++++++++++++++++++++++++++++++++++
drivers/net/cpc/header.h | 83 ++++++++++++++
3 files changed, 321 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/cpc/header.c
create mode 100644 drivers/net/cpc/header.h
diff --git a/drivers/net/cpc/Makefile b/drivers/net/cpc/Makefile
index 673a40db424..81c470012c1 100644
--- a/drivers/net/cpc/Makefile
+++ b/drivers/net/cpc/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
-cpc-y := endpoint.o interface.o main.o
+cpc-y := endpoint.o header.o interface.o main.o
obj-$(CONFIG_CPC) += cpc.o
diff --git a/drivers/net/cpc/header.c b/drivers/net/cpc/header.c
new file mode 100644
index 00000000000..9f6d637b5ae
--- /dev/null
+++ b/drivers/net/cpc/header.c
@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025, Silicon Laboratories, Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/string.h>
+
+#include "header.h"
+
+#define CPC_CONTROL_TYPE_MASK 0xC0
+#define CPC_CONTROL_ACK_MASK BIT(2)
+
+/**
+ * cpc_header_get_type() - Get the frame type.
+ * @hdr_raw: Raw header.
+ * @type: Reference to a frame type.
+ *
+ * Return: True if the type has been successfully decoded, otherwise false.
+ * On success, the output parameter type is assigned.
+ */
+bool cpc_header_get_type(const u8 hdr_raw[CPC_HEADER_SIZE], enum cpc_frame_type *type)
+{
+ const struct cpc_header *hdr = (struct cpc_header *)hdr_raw;
+
+ switch (FIELD_GET(CPC_CONTROL_TYPE_MASK, hdr->ctrl)) {
+ case CPC_FRAME_TYPE_DATA:
+ *type = CPC_FRAME_TYPE_DATA;
+ break;
+ case CPC_FRAME_TYPE_SYN:
+ *type = CPC_FRAME_TYPE_SYN;
+ break;
+ case CPC_FRAME_TYPE_RST:
+ *type = CPC_FRAME_TYPE_RST;
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * cpc_header_get_ep_id() - Get the endpoint id.
+ * @hdr_raw: Raw header.
+ *
+ * Return: Endpoint id.
+ */
+u8 cpc_header_get_ep_id(const u8 hdr_raw[CPC_HEADER_SIZE])
+{
+ const struct cpc_header *hdr = (struct cpc_header *)hdr_raw;
+
+ return hdr->ep_id;
+}
+
+/**
+ * cpc_header_get_recv_wnd() - Get the receive window.
+ * @hdr_raw: Raw header.
+ *
+ * Return: Receive window.
+ */
+u8 cpc_header_get_recv_wnd(const u8 hdr_raw[CPC_HEADER_SIZE])
+{
+ const struct cpc_header *hdr = (struct cpc_header *)hdr_raw;
+
+ return hdr->recv_wnd;
+}
+
+/**
+ * cpc_header_get_seq() - Get the sequence number.
+ * @hdr_raw: Raw header.
+ *
+ * Return: Sequence number.
+ */
+u8 cpc_header_get_seq(const u8 hdr_raw[CPC_HEADER_SIZE])
+{
+ const struct cpc_header *hdr = (struct cpc_header *)hdr_raw;
+
+ return hdr->seq;
+}
+
+/**
+ * cpc_header_get_ack() - Get the acknowledge number.
+ * @hdr_raw: Raw header.
+ *
+ * Return: Acknowledge number.
+ */
+u8 cpc_header_get_ack(const u8 hdr_raw[CPC_HEADER_SIZE])
+{
+ const struct cpc_header *hdr = (struct cpc_header *)hdr_raw;
+
+ return hdr->ack;
+}
+
+/**
+ * cpc_header_get_req_ack() - Get the request acknowledge frame flag.
+ * @hdr_raw: Raw header.
+ *
+ * Return: Request acknowledge frame flag.
+ */
+bool cpc_header_get_req_ack(const u8 hdr_raw[CPC_HEADER_SIZE])
+{
+ const struct cpc_header *hdr = (struct cpc_header *)hdr_raw;
+
+ return FIELD_GET(CPC_CONTROL_ACK_MASK, hdr->ctrl);
+}
+
+/**
+ * cpc_header_get_mtu() - Get the maximum transmission unit.
+ * @hdr_raw: Raw header.
+ *
+ * Return: Maximum transmission unit.
+ *
+ * Must only be used over a SYN frame.
+ */
+u16 cpc_header_get_mtu(const u8 hdr_raw[CPC_HEADER_SIZE])
+{
+ const struct cpc_header *hdr = (struct cpc_header *)hdr_raw;
+
+ return le16_to_cpu(hdr->syn.mtu);
+}
+
+/**
+ * cpc_header_get_payload_len() - Get the payload length.
+ * @hdr_raw: Raw header.
+ *
+ * Return: Payload length.
+ *
+ * Must only be used over a DATA frame.
+ */
+u16 cpc_header_get_payload_len(const u8 hdr_raw[CPC_HEADER_SIZE])
+{
+ const struct cpc_header *hdr = (struct cpc_header *)hdr_raw;
+
+ return le16_to_cpu(hdr->dat.payload_len);
+}
+
+/**
+ * cpc_header_get_ctrl() - Encode parameters into a control byte.
+ * @type: Frame type.
+ * @req_ack: Frame flag indicating a request to be acknowledged.
+ *
+ * Return: Encoded control byte.
+ */
+u8 cpc_header_get_ctrl(enum cpc_frame_type type, bool req_ack)
+{
+ return FIELD_PREP(CPC_CONTROL_TYPE_MASK, type) |
+ FIELD_PREP(CPC_CONTROL_ACK_MASK, req_ack);
+}
+
+/**
+ * cpc_header_get_frames_acked_count() - Get frames to be acknowledged.
+ * @seq: Current sequence number of the endpoint.
+ * @ack: Acknowledge number of the received frame.
+ * @ack_pending_count: Amount of frames pending on an acknowledge.
+ *
+ * Return: Frames to be acknowledged.
+ */
+u8 cpc_header_get_frames_acked_count(u8 seq, u8 ack, u8 ack_pending_count)
+{
+ u8 frames_acked_count;
+ u8 ack_range_min;
+ u8 ack_range_max;
+
+ ack_range_min = seq + 1;
+ ack_range_max = seq + ack_pending_count;
+
+ if (!cpc_header_number_in_range(ack_range_min, ack_range_max, ack))
+ return 0;
+
+ /* Find number of frames acknowledged with ACK number. */
+ if (ack > seq) {
+ frames_acked_count = ack - seq;
+ } else {
+ frames_acked_count = 256 - seq;
+ frames_acked_count += ack;
+ }
+
+ return frames_acked_count;
+}
+
+/**
+ * cpc_header_is_syn_ack_valid() - Check if the provided SYN-ACK valid or not.
+ * @seq: Current sequence number of the endpoint.
+ * @ack: Acknowledge number of the received SYN.
+ *
+ * Return: True if valid, otherwise false.
+ */
+bool cpc_header_is_syn_ack_valid(u8 seq, u8 ack)
+{
+ return !!cpc_header_get_frames_acked_count(seq, ack, 1);
+}
+
+/**
+ * cpc_header_number_in_window() - Test if a number is within a window.
+ * @start: Start of the window.
+ * @end: Window size.
+ * @n: Number to be tested.
+ *
+ * Given the start of the window and its size, test if the number is
+ * in the range [start; start + wnd).
+ *
+ * @return True if start <= n <= start + wnd - 1 (modulo 256), otherwise false.
+ */
+bool cpc_header_number_in_window(u8 start, u8 wnd, u8 n)
+{
+ u8 end;
+
+ if (wnd == 0)
+ return false;
+
+ end = start + wnd - 1;
+
+ return cpc_header_number_in_range(start, end, n);
+}
+
+/**
+ * cpc_header_number_in_range() - Test if a number is between start and end (included).
+ * @start: Lowest limit.
+ * @end: Highest limit inclusively.
+ * @n: Number to be tested.
+ *
+ * @return True if start <= n <= end (modulo 256), otherwise false.
+ */
+bool cpc_header_number_in_range(u8 start, u8 end, u8 n)
+{
+ if (end >= start) {
+ if (n < start || n > end)
+ return false;
+ } else {
+ if (n > end && n < start)
+ return false;
+ }
+
+ return true;
+}
diff --git a/drivers/net/cpc/header.h b/drivers/net/cpc/header.h
new file mode 100644
index 00000000000..c85bcaef345
--- /dev/null
+++ b/drivers/net/cpc/header.h
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025, Silicon Laboratories, Inc.
+ */
+
+#ifndef __CPC_HEADER_H
+#define __CPC_HEADER_H
+
+#include <linux/compiler_attributes.h>
+#include <linux/types.h>
+
+#define CPC_HEADER_MAX_RX_WINDOW 255
+#define CPC_HEADER_SIZE 8
+
+/**
+ * enum cpc_frame_type - Describes all possible frame types that can
+ * be received or sent.
+ * @CPC_FRAME_TYPE_DATA: Used to send and control application DATA frames.
+ * @CPC_FRAME_TYPE_SYN: Used to initiate an endpoint connection.
+ * @CPC_FRAME_TYPE_RST: Used to reset the endpoint connection and indicate
+ * that the endpoint is unavailable.
+ */
+enum cpc_frame_type {
+ CPC_FRAME_TYPE_DATA,
+ CPC_FRAME_TYPE_SYN,
+ CPC_FRAME_TYPE_RST,
+};
+
+/**
+ * struct cpc_header - Representation of the CPC header.
+ * @ctrl: Indicates the frame type [7..6] and frame flags [5..0].
+ * Currently only the request acknowledge flag is supported.
+ * This flag indicates if the frame should be acknowledged by
+ * the remote on reception.
+ * @ep_id: Address of the endpoint the frame is destined to.
+ * @recv_wnd: Indicates to the remote how many reception buffers are
+ * available so it can determine how many frames it can send.
+ * @seq: Identifies the frame with a number.
+ * @ack: Indicate the sequence number of the next expected frame from
+ * the remote. When paired with a fast re-transmit flag, it indicates
+ * the sequence number of the frame in error that should be
+ * re-transmitted.
+ * @syn.mtu: On a SYN frame, this represents the maximum transmission unit.
+ * @dat.payload_len: On a DATA frame, this indicates the payload length.
+ */
+struct cpc_header {
+ u8 ctrl;
+ u8 ep_id;
+ u8 recv_wnd;
+ u8 seq;
+ u8 ack;
+ union {
+ u8 extension[3];
+ struct __packed {
+ __le16 mtu;
+ u8 reserved;
+ } syn;
+ struct __packed {
+ __le16 payload_len;
+ u8 reserved;
+ } dat;
+ struct __packed {
+ u8 reserved[3];
+ } rst;
+ };
+} __packed;
+
+bool cpc_header_get_type(const u8 hdr_raw[CPC_HEADER_SIZE], enum cpc_frame_type *type);
+u8 cpc_header_get_ep_id(const u8 hdr_raw[CPC_HEADER_SIZE]);
+u8 cpc_header_get_recv_wnd(const u8 hdr_raw[CPC_HEADER_SIZE]);
+u8 cpc_header_get_seq(const u8 hdr_raw[CPC_HEADER_SIZE]);
+u8 cpc_header_get_ack(const u8 hdr_raw[CPC_HEADER_SIZE]);
+bool cpc_header_get_req_ack(const u8 hdr_raw[CPC_HEADER_SIZE]);
+u16 cpc_header_get_mtu(const u8 hdr_raw[CPC_HEADER_SIZE]);
+u16 cpc_header_get_payload_len(const u8 hdr_raw[CPC_HEADER_SIZE]);
+u8 cpc_header_get_ctrl(enum cpc_frame_type type, bool req_ack);
+
+u8 cpc_header_get_frames_acked_count(u8 seq, u8 ack, u8 ack_pending_count);
+bool cpc_header_is_syn_ack_valid(u8 seq, u8 ack);
+bool cpc_header_number_in_window(u8 start, u8 wnd, u8 n);
+bool cpc_header_number_in_range(u8 start, u8 end, u8 n);
+
+#endif
--
2.49.0
> +struct cpc_header {
> + u8 ctrl;
> + u8 ep_id;
> + u8 recv_wnd;
> + u8 seq;
> + u8 ack;
> + union {
> + u8 extension[3];
> + struct __packed {
> + __le16 mtu;
> + u8 reserved;
> + } syn;
> + struct __packed {
> + __le16 payload_len;
> + u8 reserved;
These two le16 are unaligned for no good reason. Put the reserved byte
first, then the u16. Once you have done that, you might be able to
throw away all the __packed because it is then all naturally aligned.
Andrew
© 2016 - 2025 Red Hat, Inc.