lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <20250705004036.3828-3-damien.riegel@silabs.com>
Date: Fri,  4 Jul 2025 20:40:32 -0400
From: Damien Riégel <damien.riegel@...abs.com>
To: greybus-dev@...ts.linaro.org
Cc: linux-kernel@...r.kernel.org, linux-devel@...abs.com,
        Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
        Alex Elder <elder@...nel.org>, Johan Hovold <johan@...nel.org>,
        Damien Riégel <damien.riegel@...abs.com>
Subject: [RFC 2/6] greybus: cpc: add core logic

This step adds the basic infrastructure in order to use CPC as backend
in Greybus. The goal of CPC is to add reliablity, by implementing error
detection and retransmission for links that don't have that capability
by default.

When Greybus establishes the connection between two CPorts, CPC will
create an endpoint for this connection. Greybus messages will then be
encapsulated in CPC frames, which basically are a custom header +
Greybus header + Greybus payload.

As this is still evolving and not the main point of the RFC, the whole
core is squashed in one big commit, but it will definitely be split into
more digestible commits as we refine it.

Signed-off-by: Damien Riégel <damien.riegel@...abs.com>
---
 MAINTAINERS                    |   6 +
 drivers/greybus/Kconfig        |   2 +
 drivers/greybus/Makefile       |   2 +
 drivers/greybus/cpc/Kconfig    |  12 ++
 drivers/greybus/cpc/Makefile   |   6 +
 drivers/greybus/cpc/cpc.h      | 135 ++++++++++++++++
 drivers/greybus/cpc/endpoint.c | 158 +++++++++++++++++++
 drivers/greybus/cpc/header.c   | 212 +++++++++++++++++++++++++
 drivers/greybus/cpc/header.h   |  81 ++++++++++
 drivers/greybus/cpc/host.c     | 113 ++++++++++++++
 drivers/greybus/cpc/protocol.c | 274 +++++++++++++++++++++++++++++++++
 11 files changed, 1001 insertions(+)
 create mode 100644 drivers/greybus/cpc/Kconfig
 create mode 100644 drivers/greybus/cpc/Makefile
 create mode 100644 drivers/greybus/cpc/cpc.h
 create mode 100644 drivers/greybus/cpc/endpoint.c
 create mode 100644 drivers/greybus/cpc/header.c
 create mode 100644 drivers/greybus/cpc/header.h
 create mode 100644 drivers/greybus/cpc/host.c
 create mode 100644 drivers/greybus/cpc/protocol.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 8256ec0ff8a..10385b5344b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10016,6 +10016,12 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/net/ti,cc1352p7.yaml
 F:	drivers/greybus/gb-beagleplay.c
 
+GREYBUS CPC DRIVERS
+M:	Damien Riégel <damien.riegel@...abs.com>
+R:	Silicon Labs Kernel Team <linux-devel@...abs.com>
+S:	Supported
+F:	drivers/greybus/cpc/*
+
 GREYBUS SUBSYSTEM
 M:	Johan Hovold <johan@...nel.org>
 M:	Alex Elder <elder@...nel.org>
diff --git a/drivers/greybus/Kconfig b/drivers/greybus/Kconfig
index c3f056d28b0..565a0fdcb2c 100644
--- a/drivers/greybus/Kconfig
+++ b/drivers/greybus/Kconfig
@@ -30,6 +30,8 @@ config GREYBUS_BEAGLEPLAY
 	  To compile this code as a module, chose M here: the module
 	  will be called gb-beagleplay.ko
 
+source "drivers/greybus/cpc/Kconfig"
+
 config GREYBUS_ES2
 	tristate "Greybus ES3 USB host controller"
 	depends on USB
diff --git a/drivers/greybus/Makefile b/drivers/greybus/Makefile
index c3564ad151f..4ebf8907405 100644
--- a/drivers/greybus/Makefile
+++ b/drivers/greybus/Makefile
@@ -21,6 +21,8 @@ ccflags-y += -I$(src)
 # Greybus Host controller drivers
 obj-$(CONFIG_GREYBUS_BEAGLEPLAY)	+= gb-beagleplay.o
 
+obj-$(CONFIG_GREYBUS_CPC)	+= cpc/
+
 gb-es2-y := es2.o
 
 obj-$(CONFIG_GREYBUS_ES2)	+= gb-es2.o
diff --git a/drivers/greybus/cpc/Kconfig b/drivers/greybus/cpc/Kconfig
new file mode 100644
index 00000000000..1512f9324f8
--- /dev/null
+++ b/drivers/greybus/cpc/Kconfig
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config GREYBUS_CPC
+	tristate "Greybus CPC driver"
+	depends on SPI
+	select CRC_ITU_T
+	help
+	  Select this option if you have a Silicon Labs EFR32 device that acts
+	  as a Greybus SVC.
+
+	  To compile this code as a module, chose M here: the module will be
+	  called gb-cpc.ko
diff --git a/drivers/greybus/cpc/Makefile b/drivers/greybus/cpc/Makefile
new file mode 100644
index 00000000000..08ef7c6d24b
--- /dev/null
+++ b/drivers/greybus/cpc/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+
+gb-cpc-y := endpoint.o header.o host.o main.o protocol.o
+
+# CPC core
+obj-$(CONFIG_GREYBUS_CPC)	+= gb-cpc.o
diff --git a/drivers/greybus/cpc/cpc.h b/drivers/greybus/cpc/cpc.h
new file mode 100644
index 00000000000..4aece6da9f7
--- /dev/null
+++ b/drivers/greybus/cpc/cpc.h
@@ -0,0 +1,135 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025, Silicon Laboratories, Inc.
+ */
+
+#ifndef __CPC_H
+#define __CPC_H
+
+#include <linux/device.h>
+#include <linux/types.h>
+
+#include "header.h"
+
+#define GB_CPC_SPI_NUM_CPORTS			8
+
+struct cpc_endpoint;
+struct cpc_endpoint_tcb;
+struct cpc_frame;
+struct cpc_host_device;
+
+/**
+ * struct cpc_host_device - CPC host device
+ * @gb_hd: pointer to Greybus Host Device
+ * @lock: mutex to synchronize access to endpoint array
+ * @tx_queue: list of cpc_frame to send
+ * @endpoints: array of endpoint pointers
+ * @wake_tx: function called when a new packet must be transmitted
+ */
+struct cpc_host_device {
+	struct gb_host_device	*gb_hd;
+
+	struct mutex		lock;
+	struct list_head	tx_queue;
+
+	struct cpc_endpoint	*endpoints[GB_CPC_SPI_NUM_CPORTS];
+
+	int (*wake_tx)(struct cpc_host_device *cpc_hd);
+};
+
+struct cpc_endpoint *cpc_hd_get_endpoint(struct cpc_host_device *cpc_hd, u16 cport_id);
+void cpc_hd_send_frame(struct cpc_host_device *cpc_hd, struct cpc_frame *frame);
+void cpc_hd_rcvd(struct cpc_host_device *cpc_hd, struct cpc_header *hdr,
+		 u8 *data, size_t length);
+struct cpc_frame *cpc_hd_dequeue(struct cpc_host_device *cpc_hd);
+bool cpc_hd_tx_queue_empty(struct cpc_host_device *cpc_hd);
+
+/**
+ * struct cpc_endpoint_tcb - endpoint's transmission control block
+ * @send_wnd: send window, maximum number of frames that the remote can accept
+ *            TX frames should have a sequence in the range
+ *            [send_una; send_una + send_wnd].
+ * @send_nxt: send next, the next sequence number that will be used for transmission
+ * @send_una: send unacknowledged, the oldest unacknowledged sequence number
+ * @ack: current acknowledge number
+ * @seq: current sequence number
+ * @mtu: maximum transmission unit
+ */
+struct cpc_endpoint_tcb {
+	u8 send_wnd;
+	u8 send_nxt;
+	u8 send_una;
+	u8 ack;
+	u8 seq;
+	u16 mtu;
+};
+
+/**
+ * struct cpc_endpint - CPC endpoint
+ * @id: endpoint ID
+ * @cpc_hd: pointer to the CPC host device this endpoint belongs to
+ * @lock: synchronize access to other attributes
+ * @completion: (dis)connection completion
+ * @tcb: transmission control block
+ * @holding_queue: list of CPC frames queued to be sent
+ * @pending_ack_queue: list of CPC frames sent and waiting for acknowledgment
+ */
+struct cpc_endpoint {
+	u16			id;
+
+	struct cpc_host_device	*cpc_hd;
+
+	struct mutex		lock;		/* Synchronize access to all other attributes. */
+	struct completion	completion;
+	struct cpc_endpoint_tcb	tcb;
+	struct list_head	holding_queue;
+	struct list_head	pending_ack_queue;
+};
+
+struct cpc_endpoint *cpc_endpoint_alloc(u16 ep_id, gfp_t gfp_mask);
+void cpc_endpoint_release(struct cpc_endpoint *ep);
+int cpc_endpoint_frame_send(struct cpc_endpoint *ep, struct cpc_frame *frame);
+int cpc_endpoint_connect(struct cpc_endpoint *ep);
+int cpc_endpoint_disconnect(struct cpc_endpoint *ep);
+
+/**
+ * struct cpc_frame - CPC frame
+ * @header: CPC header
+ * @message: Greybus message to transmit
+ * @cancelled: indicate if Greybus message is cancelled and should not be sent
+ * @ep: endpoint this frame is sent over
+ * @links: list head in endpoint's queue
+ * @txq_links: list head in cpc host device's queue
+ */
+struct cpc_frame {
+	struct cpc_header	header;
+	struct gb_message	*message;
+
+	bool			cancelled;
+
+	struct cpc_endpoint	*ep;
+
+	struct list_head	links;		/* endpoint->holding_queue or
+						 * endpoint->pending_ack_queue.
+						 */
+	struct list_head	txq_links;	/* cpc_host_device->tx_queue. */
+
+};
+
+struct cpc_frame *cpc_frame_alloc(struct gb_message *message, gfp_t gfp_mask);
+void cpc_frame_free(struct cpc_frame *frame);
+void cpc_frame_sent(struct cpc_frame *frame, int status);
+
+int __cpc_protocol_write(struct cpc_endpoint *ep, struct cpc_frame *frame);
+
+void cpc_protocol_on_data(struct cpc_endpoint *ep, struct cpc_header *hdr, u8 *data, size_t length);
+void cpc_protocol_on_syn(struct cpc_endpoint *ep, struct cpc_header *hdr);
+void cpc_protocol_on_rst(struct cpc_endpoint *ep);
+
+void cpc_protocol_send_rst(struct cpc_host_device *cpc_hd, u8 ep_id);
+int cpc_protocol_send_syn(struct cpc_endpoint *ep);
+
+int cpc_spi_register_driver(void);
+void cpc_spi_unregister_driver(void);
+
+#endif
diff --git a/drivers/greybus/cpc/endpoint.c b/drivers/greybus/cpc/endpoint.c
new file mode 100644
index 00000000000..12710edebcf
--- /dev/null
+++ b/drivers/greybus/cpc/endpoint.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025, Silicon Laboratories, Inc.
+ */
+
+#include <linux/greybus.h>
+
+#include "cpc.h"
+
+/**
+ * cpc_endpoint_write - Write a DATA frame.
+ * @ep: Endpoint handle.
+ * @frame: Frame to send.
+ *
+ * @return: 0 on success, otherwise a negative error code.
+ */
+int cpc_endpoint_frame_send(struct cpc_endpoint *ep, struct cpc_frame *frame)
+{
+	struct cpc_header *hdr = &frame->header;
+	size_t cpc_payload_sz = 0;
+	int err;
+
+	if (frame->message) {
+		cpc_payload_sz += sizeof(struct gb_operation_msg_hdr);
+		cpc_payload_sz += frame->message->payload_size;
+	}
+
+	mutex_lock(&ep->lock);
+
+	if (cpc_payload_sz > ep->tcb.mtu) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	memset(hdr, 0, sizeof(*hdr));
+	hdr->ctrl = cpc_header_get_ctrl(CPC_FRAME_TYPE_DATA, true);
+	hdr->ep_id = ep->id;
+	hdr->recv_wnd = CPC_HEADER_MAX_RX_WINDOW;
+	hdr->seq = ep->tcb.seq;
+	hdr->dat.payload_len = cpc_payload_sz;
+
+	frame->ep = ep;
+
+	err = __cpc_protocol_write(ep, frame);
+
+out:
+	mutex_unlock(&ep->lock);
+
+	return err;
+}
+
+void cpc_frame_sent(struct cpc_frame *frame, int status)
+{
+	struct cpc_endpoint *ep = frame->ep;
+	struct gb_host_device *gb_hd = ep->cpc_hd->gb_hd;
+
+	/* There is no Greybus payload, this frame is purely CPC */
+	if (!frame->message)
+		return;
+
+	/*
+	 * Increase the send_nxt sequence, this is used as the upper bound of sequence number that
+	 * can be ACK'd by the remote. Only increase if sent successfully.
+	 */
+	if (!status) {
+		mutex_lock(&ep->lock);
+		ep->tcb.send_nxt++;
+		mutex_unlock(&ep->lock);
+	}
+
+	if (!frame->cancelled)
+		greybus_message_sent(gb_hd, frame->message, status);
+
+	kfree(frame);
+}
+
+/**
+ * cpc_endpoint_tcb_reset() - Reset endpoint's TCB to initial values.
+ * @ep: endpoint pointer
+ */
+static void cpc_endpoint_tcb_reset(struct cpc_endpoint *ep)
+{
+	ep->tcb.seq = ep->id;
+	ep->tcb.ack = 0;
+	ep->tcb.mtu = 0;
+	ep->tcb.send_nxt = ep->id;
+	ep->tcb.send_una = ep->id;
+	ep->tcb.send_wnd = 1;
+}
+
+/**
+ * cpc_endpoint_alloc() - Allocate and initialize CPC endpoint.
+ * @ep_id: Endpoint ID.
+ * @gfp_mask: GFP mask for allocation.
+ *
+ * Return: Pointer to allocated and initialized cpc_endpoint, or NULL on failure.
+ */
+struct cpc_endpoint *cpc_endpoint_alloc(u16 ep_id, gfp_t gfp_mask)
+{
+	struct cpc_endpoint *ep;
+
+	ep = kzalloc(sizeof(*ep), gfp_mask);
+	if (!ep)
+		return NULL;
+
+	ep->id = ep_id;
+	INIT_LIST_HEAD(&ep->holding_queue);
+	INIT_LIST_HEAD(&ep->pending_ack_queue);
+
+	mutex_init(&ep->lock);
+	cpc_endpoint_tcb_reset(ep);
+	init_completion(&ep->completion);
+
+	return ep;
+}
+
+void cpc_endpoint_release(struct cpc_endpoint *ep)
+{
+	kfree(ep);
+}
+
+int cpc_endpoint_connect(struct cpc_endpoint *ep)
+{
+	int ret;
+
+	ret = cpc_protocol_send_syn(ep);
+	if (ret)
+		return ret;
+
+	return wait_for_completion_interruptible(&ep->completion);
+}
+
+int cpc_endpoint_disconnect(struct cpc_endpoint *ep)
+{
+	cpc_protocol_send_rst(ep->cpc_hd, ep->id);
+
+	return 0;
+}
+
+struct cpc_frame *cpc_frame_alloc(struct gb_message *message, gfp_t gfp_mask)
+{
+	struct cpc_frame *frame;
+
+	frame = kzalloc(sizeof(*frame), gfp_mask);
+	if (!frame)
+		return NULL;
+
+	frame->message = message;
+	INIT_LIST_HEAD(&frame->links);
+	INIT_LIST_HEAD(&frame->txq_links);
+
+	return frame;
+}
+
+void cpc_frame_free(struct cpc_frame *frame)
+{
+	kfree(frame);
+}
diff --git a/drivers/greybus/cpc/header.c b/drivers/greybus/cpc/header.c
new file mode 100644
index 00000000000..4faa604b13a
--- /dev/null
+++ b/drivers/greybus/cpc/header.c
@@ -0,0 +1,212 @@
+// 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: CPC 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 struct cpc_header *hdr, enum cpc_frame_type *type)
+{
+	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: CPC header.
+ *
+ * Return: Endpoint id.
+ */
+u8 cpc_header_get_ep_id(const struct cpc_header *hdr)
+{
+	return hdr->ep_id;
+}
+
+/**
+ * cpc_header_get_recv_wnd() - Get the receive window.
+ * @hdr: CPC header.
+ *
+ * Return: Receive window.
+ */
+u8 cpc_header_get_recv_wnd(const struct cpc_header *hdr)
+{
+	return hdr->recv_wnd;
+}
+
+/**
+ * cpc_header_get_seq() - Get the sequence number.
+ * @hdr: CPC header.
+ *
+ * Return: Sequence number.
+ */
+u8 cpc_header_get_seq(const struct cpc_header *hdr)
+{
+	return hdr->seq;
+}
+
+/**
+ * cpc_header_get_ack() - Get the acknowledge number.
+ * @hdr: CPC header.
+ *
+ * Return: Acknowledge number.
+ */
+u8 cpc_header_get_ack(const struct cpc_header *hdr)
+{
+	return hdr->ack;
+}
+
+/**
+ * cpc_header_get_req_ack() - Get the request acknowledge frame flag.
+ * @hdr: CPC header.
+ *
+ * Return: Request acknowledge frame flag.
+ */
+bool cpc_header_get_req_ack(const struct cpc_header *hdr)
+{
+	return FIELD_GET(CPC_CONTROL_ACK_MASK, hdr->ctrl);
+}
+
+/**
+ * cpc_header_get_mtu() - Get the maximum transmission unit.
+ * @hdr: CPC header.
+ *
+ * Return: Maximum transmission unit.
+ *
+ * Must only be used over a SYN frame.
+ */
+u16 cpc_header_get_mtu(const struct cpc_header *hdr)
+{
+	return le16_to_cpu(hdr->syn.mtu);
+}
+
+/**
+ * cpc_header_get_payload_len() - Get the payload length.
+ * @hdr: CPC header.
+ *
+ * Return: Payload length.
+ *
+ * Must only be used over a DATA frame.
+ */
+u16 cpc_header_get_payload_len(const struct cpc_header *hdr)
+{
+	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.
+ *
+ * Return: Frames to be acknowledged.
+ */
+u8 cpc_header_get_frames_acked_count(u8 seq, u8 ack)
+{
+	u8 frames_acked_count;
+
+	/* 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);
+}
+
+/**
+ * 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/greybus/cpc/header.h b/drivers/greybus/cpc/header.h
new file mode 100644
index 00000000000..5d574fef422
--- /dev/null
+++ b/drivers/greybus/cpc/header.h
@@ -0,0 +1,81 @@
+/* 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	U8_MAX
+#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.
+ * @ep_id: Address of the endpoint the frame is destined to.
+ * @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.
+ * @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 {
+	u16 ep_id;
+	u8 ctrl;
+	u8 recv_wnd;
+	u8 seq;
+	u8 ack;
+	union {
+		u8 extension[2];
+		struct __packed {
+			__le16 mtu;
+		} syn;
+		struct __packed {
+			__le16 payload_len;
+		} dat;
+		struct __packed {
+			u8 reserved[2];
+		} rst;
+	};
+} __packed;
+
+bool cpc_header_get_type(const struct cpc_header *hdr, enum cpc_frame_type *type);
+u8 cpc_header_get_ep_id(const struct cpc_header *hdr);
+u8 cpc_header_get_recv_wnd(const struct cpc_header *hdr);
+u8 cpc_header_get_seq(const struct cpc_header *hdr);
+u8 cpc_header_get_ack(const struct cpc_header *hdr);
+bool cpc_header_get_req_ack(const struct cpc_header *hdr);
+u16 cpc_header_get_mtu(const struct cpc_header *hdr);
+u16 cpc_header_get_payload_len(const struct cpc_header *hdr);
+u8 cpc_header_get_ctrl(enum cpc_frame_type type, bool req_ack);
+
+u8 cpc_header_get_frames_acked_count(u8 seq, u8 ack);
+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
diff --git a/drivers/greybus/cpc/host.c b/drivers/greybus/cpc/host.c
new file mode 100644
index 00000000000..0805552d5ec
--- /dev/null
+++ b/drivers/greybus/cpc/host.c
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025, Silicon Laboratories, Inc.
+ */
+
+#include <linux/greybus.h>
+#include <linux/list.h>
+#include <linux/module.h>
+
+#include "cpc.h"
+#include "header.h"
+
+struct cpc_endpoint *cpc_hd_get_endpoint(struct cpc_host_device *cpc_hd, u16 cport_id)
+{
+	struct cpc_endpoint *ep;
+
+	for (int i = 0; i < ARRAY_SIZE(cpc_hd->endpoints); i++) {
+		ep = cpc_hd->endpoints[i];
+		if (ep && ep->id == cport_id)
+			return ep;
+	}
+
+	return NULL;
+}
+
+void cpc_hd_rcvd(struct cpc_host_device *cpc_hd, struct cpc_header *hdr,
+		 u8 *data, size_t length)
+{
+	enum cpc_frame_type type;
+	struct cpc_endpoint *ep;
+	u8 ep_id;
+
+	cpc_header_get_type(hdr, &type);
+	ep_id = cpc_header_get_ep_id(hdr);
+
+	ep = cpc_hd_get_endpoint(cpc_hd, ep_id);
+	if (!ep) {
+		if (type != CPC_FRAME_TYPE_RST) {
+			dev_dbg(&cpc_hd->gb_hd->dev, "ep%u not allocated (%d)\n", ep_id, type);
+			cpc_protocol_send_rst(cpc_hd, ep_id);
+		}
+		return;
+	}
+
+	switch (type) {
+	case CPC_FRAME_TYPE_DATA:
+		cpc_protocol_on_data(ep, hdr, data, length);
+		break;
+	case CPC_FRAME_TYPE_SYN:
+		cpc_protocol_on_syn(ep, hdr);
+		break;
+	case CPC_FRAME_TYPE_RST:
+		dev_dbg(&cpc_hd->gb_hd->dev, "reset\n");
+		cpc_protocol_on_rst(ep);
+		break;
+	}
+}
+
+
+/**
+ * cpc_interface_send_frame() - Queue a socket buffer for transmission.
+ * @intf: Interface to send SKB over.
+ * @ops: SKB to send.
+ *
+ * Queue SKB in interface's transmit queue and signal the interface. Interface is expected to use
+ * cpc_interface_dequeue() to get the next SKB to transmit.
+ */
+void cpc_hd_send_frame(struct cpc_host_device *cpc_hd, struct cpc_frame *frame)
+{
+	mutex_lock(&cpc_hd->lock);
+	list_add_tail(&frame->txq_links, &cpc_hd->tx_queue);
+	mutex_unlock(&cpc_hd->lock);
+
+	cpc_hd->wake_tx(cpc_hd);
+}
+
+/**
+ * cpc_interface_dequeue() - Get the next SKB that was queued for transmission.
+ * @intf: Interface.
+ *
+ * Get an SKB that was previously queued by cpc_interface_send_frame().
+ *
+ * Return: An SKB, or %NULL if queue was empty.
+ */
+struct cpc_frame *cpc_hd_dequeue(struct cpc_host_device *cpc_hd)
+{
+	struct cpc_frame *f;
+
+	mutex_lock(&cpc_hd->lock);
+	f = list_first_entry_or_null(&cpc_hd->tx_queue, struct cpc_frame, txq_links);
+	if (f)
+		list_del(&f->txq_links);
+	mutex_unlock(&cpc_hd->lock);
+
+	return f;
+}
+
+/**
+ * cpc_interface_tx_queue_empty() - Check if transmit queue is empty.
+ * @intf: Interface.
+ *
+ * Return: True if transmit queue is empty, false otherwise.
+ */
+bool cpc_hd_tx_queue_empty(struct cpc_host_device *cpc_hd)
+{
+	bool empty;
+
+	mutex_lock(&cpc_hd->lock);
+	empty = list_empty(&cpc_hd->tx_queue);
+	mutex_unlock(&cpc_hd->lock);
+
+	return empty;
+}
diff --git a/drivers/greybus/cpc/protocol.c b/drivers/greybus/cpc/protocol.c
new file mode 100644
index 00000000000..610e4b96edd
--- /dev/null
+++ b/drivers/greybus/cpc/protocol.c
@@ -0,0 +1,274 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025, Silicon Laboratories, Inc.
+ */
+
+#include <linux/greybus.h>
+#include <linux/mutex.h>
+#include <linux/skbuff.h>
+
+#include "cpc.h"
+#include "header.h"
+
+int cpc_protocol_send_syn(struct cpc_endpoint *ep)
+{
+	struct cpc_frame *frame;
+	struct cpc_header *hdr;
+
+	frame = cpc_frame_alloc(NULL, GFP_KERNEL);
+	if (!frame)
+		return -ENOMEM;
+
+	hdr = &frame->header;
+	memset(hdr, 0, sizeof(*hdr));
+
+	mutex_lock(&ep->lock);
+
+	hdr->ctrl = cpc_header_get_ctrl(CPC_FRAME_TYPE_SYN, true);
+	hdr->ep_id = ep->id;
+	hdr->recv_wnd = CPC_HEADER_MAX_RX_WINDOW;
+	hdr->seq = ep->tcb.seq;
+	hdr->syn.mtu = cpu_to_le16(U16_MAX);
+
+	cpc_hd_send_frame(ep->cpc_hd, frame);
+
+	mutex_unlock(&ep->lock);
+
+	return 0;
+}
+
+static void __cpc_protocol_send_ack(struct cpc_endpoint *ep)
+{
+	struct cpc_frame *frame;
+	struct cpc_header *hdr;
+
+	frame = cpc_frame_alloc(NULL, GFP_KERNEL);
+	if (!frame)
+		return;
+
+	hdr = &frame->header;
+
+	memset(hdr, 0, sizeof(*hdr));
+	hdr->ctrl = cpc_header_get_ctrl(CPC_FRAME_TYPE_DATA, false);
+	hdr->ep_id = ep->id;
+	hdr->recv_wnd = CPC_HEADER_MAX_RX_WINDOW;
+	hdr->ack = ep->tcb.ack;
+
+	cpc_hd_send_frame(ep->cpc_hd, frame);
+}
+
+/**
+ * cpc_protocol_send_rst - send a RST frame
+ * @cpc_hd: host device pointer
+ * @ep_id: endpoint id
+ */
+void cpc_protocol_send_rst(struct cpc_host_device *cpc_hd, u8 ep_id)
+{
+	struct cpc_frame *frame;
+	struct cpc_header *hdr;
+
+	frame = cpc_frame_alloc(NULL, GFP_KERNEL);
+	if (!frame)
+		return;
+
+	hdr = &frame->header;
+	memset(hdr, 0, sizeof(*hdr));
+	hdr->ctrl = cpc_header_get_ctrl(CPC_FRAME_TYPE_RST, false);
+	hdr->ep_id = ep_id;
+
+	cpc_hd_send_frame(cpc_hd, frame);
+}
+
+static int __cpc_protocol_queue_tx_frame(struct cpc_endpoint *ep, struct cpc_frame *frame)
+{
+	frame->header.ack = ep->tcb.ack;
+
+	list_add_tail(&frame->links, &ep->pending_ack_queue);
+
+	cpc_hd_send_frame(ep->cpc_hd, frame);
+
+	return 0;
+}
+
+static void __cpc_protocol_process_pending_tx_frames(struct cpc_endpoint *ep)
+{
+	struct cpc_frame *frame;
+	u8 window;
+	int err;
+
+	window = ep->tcb.send_wnd;
+
+	while ((frame = list_first_entry_or_null(&ep->holding_queue,
+						 struct cpc_frame,
+						 links))) {
+		if (!cpc_header_number_in_window(ep->tcb.send_una,
+						 window,
+						 cpc_header_get_seq(&frame->header)))
+			return;
+
+		list_del(&frame->links);
+
+		err = __cpc_protocol_queue_tx_frame(ep, frame);
+		if (err < 0) {
+			list_add(&frame->links, &ep->holding_queue);
+			return;
+		}
+	}
+}
+
+static void __cpc_protocol_receive_ack(struct cpc_endpoint *ep, u8 recv_wnd, u8 ack)
+{
+	struct cpc_frame *frame;
+	u8 acked_frames;
+
+	ep->tcb.send_wnd = recv_wnd;
+
+	frame = list_first_entry_or_null(&ep->pending_ack_queue, struct cpc_frame, links);
+	if (!frame)
+		goto out;
+
+	/* Return if no frame to ACK. */
+	if (!cpc_header_number_in_range(ep->tcb.send_una, ep->tcb.send_nxt, ack))
+		goto out;
+
+	/* Calculate how many frames will be ACK'd. */
+	acked_frames = cpc_header_get_frames_acked_count(cpc_header_get_seq(&frame->header), ack);
+
+	for (u8 i = 0; i < acked_frames; i++) {
+		frame = list_first_entry_or_null(&ep->pending_ack_queue, struct cpc_frame, links);
+		if (!frame) {
+			dev_err_ratelimited(&ep->cpc_hd->gb_hd->dev, "pending ack queue shorter than expected");
+			break;
+		}
+
+		list_del(&frame->links);
+		cpc_frame_free(frame);
+	}
+
+	ep->tcb.send_una += acked_frames;
+
+out:
+	__cpc_protocol_process_pending_tx_frames(ep);
+}
+
+static bool __cpc_protocol_is_syn_ack_valid(struct cpc_endpoint *ep, struct cpc_header *hdr)
+{
+	struct cpc_frame *syn_frame;
+	enum cpc_frame_type type;
+	u8 syn_seq;
+	u8 ack;
+
+	/* Fetch the previously sent frame. */
+	syn_frame = list_first_entry_or_null(&ep->pending_ack_queue, struct cpc_frame, links);
+	if (!syn_frame) {
+		dev_warn(&ep->cpc_hd->gb_hd->dev, "cannot validate syn-ack, no frame was sent\n");
+		return false;
+	}
+
+	cpc_header_get_type(&syn_frame->header, &type);
+
+	/* Verify if this frame is SYN. */
+	if (type != CPC_FRAME_TYPE_SYN) {
+		dev_warn(&ep->cpc_hd->gb_hd->dev,
+			 "cannot validate syn-ack, no syn frame was sent (%d)\n", type);
+		return false;
+	}
+
+	syn_seq = cpc_header_get_seq(&syn_frame->header);
+	ack = cpc_header_get_ack(hdr);
+
+	/* Validate received ACK with the SEQ used in the initial SYN. */
+	if (!cpc_header_is_syn_ack_valid(syn_seq, ack)) {
+		dev_warn(&ep->cpc_hd->gb_hd->dev,
+			 "syn-ack (%d) is not valid with previously sent syn-seq (%d)\n",
+			 ack, syn_seq);
+		return false;
+	}
+
+	return true;
+}
+
+void cpc_protocol_on_data(struct cpc_endpoint *ep, struct cpc_header *hdr,
+			  u8 *data, size_t length)
+{
+	bool expected_seq;
+
+	mutex_lock(&ep->lock);
+
+	__cpc_protocol_receive_ack(ep,
+				   cpc_header_get_recv_wnd(hdr),
+				   cpc_header_get_ack(hdr));
+
+	if (cpc_header_get_req_ack(hdr)) {
+		expected_seq = cpc_header_get_seq(hdr) == ep->tcb.ack;
+		if (expected_seq)
+			ep->tcb.ack++;
+
+		__cpc_protocol_send_ack(ep);
+
+		if (!expected_seq)
+			dev_warn(&ep->cpc_hd->gb_hd->dev,
+				 "unexpected seq: %u, expected seq: %u\n",
+				 cpc_header_get_seq(hdr), ep->tcb.ack);
+	}
+
+	mutex_unlock(&ep->lock);
+
+	if (data) {
+		if (expected_seq)
+			greybus_data_rcvd(ep->cpc_hd->gb_hd, ep->id, data, length);
+		else
+			kfree(data);
+	}
+}
+
+void cpc_protocol_on_syn(struct cpc_endpoint *ep, struct cpc_header *hdr)
+{
+	mutex_lock(&ep->lock);
+
+	if (!__cpc_protocol_is_syn_ack_valid(ep, hdr)) {
+		cpc_protocol_send_rst(ep->cpc_hd, ep->id);
+		goto out;
+	}
+
+	__cpc_protocol_receive_ack(ep,
+				   cpc_header_get_recv_wnd(hdr),
+				   cpc_header_get_ack(hdr));
+
+	/* On SYN-ACK, the remote's SEQ becomes our starting ACK. */
+	ep->tcb.ack = cpc_header_get_seq(hdr);
+	ep->tcb.mtu = cpc_header_get_mtu(hdr);
+	ep->tcb.ack++;
+
+	__cpc_protocol_send_ack(ep);
+
+	complete(&ep->completion);
+
+out:
+	mutex_unlock(&ep->lock);
+}
+
+void cpc_protocol_on_rst(struct cpc_endpoint *ep)
+{
+	// To be implemented when connection mechanism are restored
+}
+
+/**
+ * __cpc_protocol_write() - Write a frame.
+ * @ep: Endpoint handle.
+ * @frame: Frame to write.
+ *
+ * Context: Expect endpoint's lock to be held.
+ *
+ * Return: 0 on success, otherwise a negative error code.
+ */
+int __cpc_protocol_write(struct cpc_endpoint *ep, struct cpc_frame *frame)
+{
+	list_add_tail(&frame->links, &ep->holding_queue);
+
+	__cpc_protocol_process_pending_tx_frames(ep);
+
+	ep->tcb.seq++;
+
+	return 0;
+}
-- 
2.49.0


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ