Hi,
This patchset brings support for Silicon Labs' CPC (Co-Processor Communication) protocol as transport layer for Greybus. This is introduced as a module that sits between Greybus and CPC Host Device Driver implementations, like SDIO or SPI, which are not part of this RFC. If there's no push back with this RFC, the final patchset ready for upstream will include the SDIO driver.
The goal of this module is to implement some of the features of Unipro that Greybus relies upon, like reliable transmission. CPC takes care of detecting transmission errors and retransmit frames if necessary. That feature is not part of the RFC to keep it concise, but it's planned for a future patchset. There's also a flow-control feature, to prevent from sending messages to cports that don't have anymore room.
In order to implement these features, a 4-byte header is prepended to Greybus messages, making the whole header 12 bytes (Greybus header itself being 8 bytes).
This RFC starts by implementing a shim layer that sits between physical bus drivers (like SDIO and SPI) and Greybus, and progressively add more elements to it to make it useful in its own right.
+----------------------------------------------------+ | Greybus | +----------------------------------------------------+ /|\ | |/ +----------------------------------------------------+ | CPC | +----------------------------------------------------+ /|\ /|\ /|\ | | | |/ |/ |/ +----------+ +---------+ +-----------+ | SDIO | | SPI | | Others | +----------+ +---------+ +-----------+
Changes in v2: - v1 included a new protocol for Bluetooth HCI, this has been dropped to focus on CPC itself - likewise, there was an SPI driver, it has been dropped of this RFC for the same reason - v1 introduced CPC in a big commit, this time it's been split in smaller commits to make review manageable
Damien Riégel (12): greybus: cpc: add minimal CPC Host Device infrastructure greybus: cpc: introduce CPC cport structure greybus: cpc: use socket buffers instead of gb_message in TX path greybus: cpc: pack cport ID in Greybus header greybus: cpc: switch RX path to socket buffers greybus: cpc: introduce CPC header structure greybus: cpc: account for CPC header size in RX and TX path greybus: cpc: add and validate sequence numbers greybus: cpc: acknowledge all incoming messages greybus: cpc: use holding queue instead of sending out immediately greybus: cpc: honour remote's RX window greybus: cpc: let host device drivers dequeue TX frames
MAINTAINERS | 6 + drivers/greybus/Kconfig | 2 + drivers/greybus/Makefile | 2 + drivers/greybus/cpc/Kconfig | 10 ++ drivers/greybus/cpc/Makefile | 6 + drivers/greybus/cpc/cpc.h | 76 +++++++++ drivers/greybus/cpc/cport.c | 107 ++++++++++++ drivers/greybus/cpc/header.c | 146 +++++++++++++++++ drivers/greybus/cpc/header.h | 54 +++++++ drivers/greybus/cpc/host.c | 287 +++++++++++++++++++++++++++++++++ drivers/greybus/cpc/host.h | 58 +++++++ drivers/greybus/cpc/protocol.c | 169 +++++++++++++++++++ 12 files changed, 923 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/cport.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/host.h create mode 100644 drivers/greybus/cpc/protocol.c
As the first step for adding CPC support with Greybus, add a very minimal module for CPC Host Devices. For now, this module only proxies calls to the Greybus Host Device API and does nothing useful, but further commits will use this base to add features.
Signed-off-by: Damien Riégel damien.riegel@silabs.com --- MAINTAINERS | 6 +++ drivers/greybus/Kconfig | 2 + drivers/greybus/Makefile | 2 + drivers/greybus/cpc/Kconfig | 10 +++++ drivers/greybus/cpc/Makefile | 6 +++ drivers/greybus/cpc/host.c | 85 ++++++++++++++++++++++++++++++++++++ drivers/greybus/cpc/host.h | 40 +++++++++++++++++ 7 files changed, 151 insertions(+) create mode 100644 drivers/greybus/cpc/Kconfig create mode 100644 drivers/greybus/cpc/Makefile create mode 100644 drivers/greybus/cpc/host.c create mode 100644 drivers/greybus/cpc/host.h
diff --git a/MAINTAINERS b/MAINTAINERS index f7af0a5cf1e..992c74b9f6c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10029,6 +10029,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@silabs.com +R: Silicon Labs Kernel Team linux-devel@silabs.com +S: Supported +F: drivers/greybus/cpc/* + GREYBUS SUBSYSTEM M: Johan Hovold johan@kernel.org M: Alex Elder elder@kernel.org diff --git a/drivers/greybus/Kconfig b/drivers/greybus/Kconfig index 797f32a9c5e..59dcfc126e5 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 7c179cc60e5..fc77e86bffb 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..ab96fedd0de --- /dev/null +++ b/drivers/greybus/cpc/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 + +config GREYBUS_CPC + tristate "Greybus CPC driver" + help + Select this option if you have a Silicon Labs 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..490982a0ff5 --- /dev/null +++ b/drivers/greybus/cpc/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 + +gb-cpc-y := host.o + +# CPC core +obj-$(CONFIG_GREYBUS_CPC) += gb-cpc.o diff --git a/drivers/greybus/cpc/host.c b/drivers/greybus/cpc/host.c new file mode 100644 index 00000000000..1eb6c87e25f --- /dev/null +++ b/drivers/greybus/cpc/host.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2025, Silicon Laboratories, Inc. + */ + +#include <linux/err.h> +#include <linux/greybus.h> +#include <linux/module.h> + +#include "host.h" + + +static struct cpc_host_device *gb_hd_to_cpc_hd(struct gb_host_device *hd) +{ + return (struct cpc_host_device *)&hd->hd_priv; +} + +static int cpc_gb_message_send(struct gb_host_device *gb_hd, u16 cport_id, + struct gb_message *message, gfp_t gfp_mask) +{ + struct cpc_host_device *cpc_hd = gb_hd_to_cpc_hd(gb_hd); + + return cpc_hd->driver->message_send(cpc_hd, cport_id, message, gfp_mask); +} + +static void cpc_gb_message_cancel(struct gb_message *message) +{ + /* Not implemented */ +} + +static struct gb_hd_driver cpc_gb_driver = { + .hd_priv_size = sizeof(struct cpc_host_device), + .message_send = cpc_gb_message_send, + .message_cancel = cpc_gb_message_cancel, +}; + +struct cpc_host_device *cpc_hd_create(struct cpc_hd_driver *driver, struct device *parent) +{ + struct cpc_host_device *cpc_hd; + struct gb_host_device *hd; + + if ((!driver->message_send) || (!driver->message_cancel)) { + dev_err(parent, "missing mandatory callbacks\n"); + return ERR_PTR(-EINVAL); + } + + hd = gb_hd_create(&cpc_gb_driver, parent, GB_CPC_MSG_SIZE_MAX, GB_CPC_NUM_CPORTS); + if (IS_ERR(hd)) + return (struct cpc_host_device *)hd; + + cpc_hd = gb_hd_to_cpc_hd(hd); + cpc_hd->gb_hd = hd; + cpc_hd->driver = driver; + + return cpc_hd; +} +EXPORT_SYMBOL_GPL(cpc_hd_create); + +int cpc_hd_add(struct cpc_host_device *cpc_hd) +{ + return gb_hd_add(cpc_hd->gb_hd); +} +EXPORT_SYMBOL_GPL(cpc_hd_add); + +void cpc_hd_put(struct cpc_host_device *cpc_hd) +{ + return gb_hd_put(cpc_hd->gb_hd); +} +EXPORT_SYMBOL_GPL(cpc_hd_put); + +void cpc_hd_del(struct cpc_host_device *cpc_hd) +{ + return gb_hd_del(cpc_hd->gb_hd); +} +EXPORT_SYMBOL_GPL(cpc_hd_del); + +void cpc_hd_rcvd(struct cpc_host_device *cpc_hd, u16 cport_id, u8 *data, size_t length) +{ + greybus_data_rcvd(cpc_hd->gb_hd, cport_id, data, length); +} +EXPORT_SYMBOL_GPL(cpc_hd_rcvd); + +MODULE_DESCRIPTION("Greybus over CPC"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Silicon Laboratories, Inc."); diff --git a/drivers/greybus/cpc/host.h b/drivers/greybus/cpc/host.h new file mode 100644 index 00000000000..fe07826b765 --- /dev/null +++ b/drivers/greybus/cpc/host.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2025, Silicon Laboratories, Inc. + */ + +#ifndef __CPC_HOST_H +#define __CPC_HOST_H + +#include <linux/device.h> +#include <linux/greybus.h> +#include <linux/types.h> + +#define GB_CPC_MSG_SIZE_MAX 2048 +#define GB_CPC_NUM_CPORTS 8 + +struct cpc_host_device; + +struct cpc_hd_driver { + int (*message_send)(struct cpc_host_device *hd, u16 dest_cport_id, + struct gb_message *message, gfp_t gfp_mask); + void (*message_cancel)(struct gb_message *message); +}; + +/** + * struct cpc_host_device - CPC host device. + * @gb_hd: pointer to Greybus Host Device this device belongs to. + * @driver: driver operations. + */ +struct cpc_host_device { + struct gb_host_device *gb_hd; + const struct cpc_hd_driver *driver; +}; + +struct cpc_host_device *cpc_hd_create(struct cpc_hd_driver *driver, struct device *parent); +int cpc_hd_add(struct cpc_host_device *cpc_hd); +void cpc_hd_put(struct cpc_host_device *cpc_hd); +void cpc_hd_del(struct cpc_host_device *cpc_hd); +void cpc_hd_rcvd(struct cpc_host_device *cpc_hd, u16 cport_id, u8 *data, size_t length); + +#endif
On Fri Nov 14, 2025 at 10:07 AM EST, Damien Riégel wrote:
# Greybus Host controller drivers obj-$(CONFIG_GREYBUS_BEAGLEPLAY) += gb-beagleplay.o
The above comment was moved without a proper commit to the patchset, making it unusable with `git am`. Reviewers should remove this modification.
Thanks,
On Fri Nov 14, 2025 at 10:07 AM EST, Damien Riégel wrote:
# Greybus Host controller drivers obj-$(CONFIG_GREYBUS_BEAGLEPLAY) += gb-beagleplay.o
The above comment was moved without a proper commit to the patchset, making it unusable with `git am`. Reviewers should remove this modification.
Thanks,
On Fri Nov 14, 2025 at 10:07 AM EST, Damien Riégel wrote:
# Greybus Host controller drivers obj-$(CONFIG_GREYBUS_BEAGLEPLAY) += gb-beagleplay.o
The above comment was moved without a proper commit to the patchset, making it unusable with `git am`. Reviewers should remove this modification.
Thanks,
A number of CPC features, like retransmission or remote's receive window, are cport-based. In order to prepare for these features, introduce a CPC CPort context structure.
The CPC Host Device module now implements cport_allocate and cport_release callbacks in order to allocate and release these structures when requested by Greybus module.
Signed-off-by: Damien Riégel damien.riegel@silabs.com --- drivers/greybus/cpc/Makefile | 2 +- drivers/greybus/cpc/cpc.h | 29 ++++++++++ drivers/greybus/cpc/cport.c | 37 ++++++++++++ drivers/greybus/cpc/host.c | 109 ++++++++++++++++++++++++++++++++++- drivers/greybus/cpc/host.h | 12 ++++ 5 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 drivers/greybus/cpc/cpc.h create mode 100644 drivers/greybus/cpc/cport.c
diff --git a/drivers/greybus/cpc/Makefile b/drivers/greybus/cpc/Makefile index 490982a0ff5..3d50f8c5473 100644 --- a/drivers/greybus/cpc/Makefile +++ b/drivers/greybus/cpc/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0
-gb-cpc-y := host.o +gb-cpc-y := cport.o host.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..85d02954307 --- /dev/null +++ b/drivers/greybus/cpc/cpc.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2025, Silicon Laboratories, Inc. + */ + +#ifndef __CPC_H +#define __CPC_H + +#include <linux/device.h> +#include <linux/greybus.h> +#include <linux/types.h> + +/** + * struct cpc_cport - CPC cport + * @id: cport ID + * @cpc_hd: pointer to the CPC host device this cport belongs to + */ +struct cpc_cport { + u16 id; + + struct cpc_host_device *cpc_hd; +}; + +struct cpc_cport *cpc_cport_alloc(u16 cport_id, gfp_t gfp_mask); +void cpc_cport_release(struct cpc_cport *cport); + +int cpc_cport_message_send(struct cpc_cport *cport, struct gb_message *message, gfp_t gfp_mask); + +#endif diff --git a/drivers/greybus/cpc/cport.c b/drivers/greybus/cpc/cport.c new file mode 100644 index 00000000000..88bdb2f8182 --- /dev/null +++ b/drivers/greybus/cpc/cport.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2025, Silicon Laboratories, Inc. + */ + +#include "cpc.h" +#include "host.h" + +/** + * cpc_cport_alloc() - Allocate and initialize CPC cport. + * @cport_id: cport ID. + * @gfp_mask: GFP mask for allocation. + * + * Return: Pointer to allocated and initialized cpc_cport, or NULL on failure. + */ +struct cpc_cport *cpc_cport_alloc(u16 cport_id, gfp_t gfp_mask) +{ + struct cpc_cport *cport; + + cport = kzalloc(sizeof(*cport), gfp_mask); + if (!cport) + return NULL; + + cport->id = cport_id; + + return cport; +} + +void cpc_cport_release(struct cpc_cport *cport) +{ + kfree(cport); +} + +int cpc_cport_message_send(struct cpc_cport *cport, struct gb_message *message, gfp_t gfp_mask) +{ + return cport->cpc_hd->driver->message_send(cport->cpc_hd, cport->id, message, gfp_mask); +} diff --git a/drivers/greybus/cpc/host.c b/drivers/greybus/cpc/host.c index 1eb6c87e25f..033ff7f0184 100644 --- a/drivers/greybus/cpc/host.c +++ b/drivers/greybus/cpc/host.c @@ -7,6 +7,7 @@ #include <linux/greybus.h> #include <linux/module.h>
+#include "cpc.h" #include "host.h"
@@ -15,12 +16,95 @@ static struct cpc_host_device *gb_hd_to_cpc_hd(struct gb_host_device *hd) return (struct cpc_host_device *)&hd->hd_priv; }
+static struct cpc_cport *cpc_hd_get_cport(struct cpc_host_device *cpc_hd, u16 cport_id) +{ + struct cpc_cport *cport; + + mutex_lock(&cpc_hd->lock); + for (int i = 0; i < ARRAY_SIZE(cpc_hd->cports); i++) { + cport = cpc_hd->cports[i]; + if (cport && cport->id == cport_id) + goto unlock; + } + + cport = NULL; + +unlock: + mutex_unlock(&cpc_hd->lock); + + return cport; +} + +static int cpc_hd_message_send(struct cpc_host_device *cpc_hd, u16 cport_id, + struct gb_message *message, gfp_t gfp_mask) +{ + struct cpc_cport *cport; + + cport = cpc_hd_get_cport(cpc_hd, cport_id); + if (!cport) { + dev_err(cpc_hd_dev(cpc_hd), "message_send: cport %u not found\n", cport_id); + return -EINVAL; + } + + return cpc_cport_message_send(cport, message, gfp_mask); +} + +static int cpc_hd_cport_allocate(struct cpc_host_device *cpc_hd, int cport_id, unsigned long flags) +{ + struct cpc_cport *cport; + int ret; + + mutex_lock(&cpc_hd->lock); + for (int i = 0; i < ARRAY_SIZE(cpc_hd->cports); i++) { + if (cpc_hd->cports[i] != NULL) + continue; + + if (cport_id < 0) + cport_id = i; + + cport = cpc_cport_alloc(cport_id, GFP_KERNEL); + if (!cport) { + ret = -ENOMEM; + goto unlock; + } + + cport->cpc_hd = cpc_hd; + + cpc_hd->cports[i] = cport; + ret = cport_id; + goto unlock; + } + + ret = -ENOSPC; +unlock: + mutex_unlock(&cpc_hd->lock); + + return ret; +} + +static void cpc_hd_cport_release(struct cpc_host_device *cpc_hd, u16 cport_id) +{ + struct cpc_cport *cport; + + mutex_lock(&cpc_hd->lock); + for (int i = 0; i < ARRAY_SIZE(cpc_hd->cports); i++) { + cport = cpc_hd->cports[i]; + + if (cport && cport->id == cport_id) { + cpc_cport_release(cport); + cpc_hd->cports[i] = NULL; + break; + } + } + mutex_unlock(&cpc_hd->lock); +} + static int cpc_gb_message_send(struct gb_host_device *gb_hd, u16 cport_id, struct gb_message *message, gfp_t gfp_mask) { struct cpc_host_device *cpc_hd = gb_hd_to_cpc_hd(gb_hd);
- return cpc_hd->driver->message_send(cpc_hd, cport_id, message, gfp_mask); + return cpc_hd_message_send(cpc_hd, cport_id, message, gfp_mask); }
static void cpc_gb_message_cancel(struct gb_message *message) @@ -28,12 +112,33 @@ static void cpc_gb_message_cancel(struct gb_message *message) /* Not implemented */ }
+static int cpc_gb_cport_allocate(struct gb_host_device *gb_hd, int cport_id, unsigned long flags) +{ + struct cpc_host_device *cpc_hd = gb_hd_to_cpc_hd(gb_hd); + + return cpc_hd_cport_allocate(cpc_hd, cport_id, flags); +} + +static void cpc_gb_cport_release(struct gb_host_device *gb_hd, u16 cport_id) +{ + struct cpc_host_device *cpc_hd = gb_hd_to_cpc_hd(gb_hd); + + return cpc_hd_cport_release(cpc_hd, cport_id); +} + static struct gb_hd_driver cpc_gb_driver = { .hd_priv_size = sizeof(struct cpc_host_device), .message_send = cpc_gb_message_send, .message_cancel = cpc_gb_message_cancel, + .cport_allocate = cpc_gb_cport_allocate, + .cport_release = cpc_gb_cport_release, };
+static void cpc_hd_init(struct cpc_host_device *cpc_hd) +{ + mutex_init(&cpc_hd->lock); +} + struct cpc_host_device *cpc_hd_create(struct cpc_hd_driver *driver, struct device *parent) { struct cpc_host_device *cpc_hd; @@ -52,6 +157,8 @@ struct cpc_host_device *cpc_hd_create(struct cpc_hd_driver *driver, struct devic cpc_hd->gb_hd = hd; cpc_hd->driver = driver;
+ cpc_hd_init(cpc_hd); + return cpc_hd; } EXPORT_SYMBOL_GPL(cpc_hd_create); diff --git a/drivers/greybus/cpc/host.h b/drivers/greybus/cpc/host.h index fe07826b765..1c168cdd2bf 100644 --- a/drivers/greybus/cpc/host.h +++ b/drivers/greybus/cpc/host.h @@ -8,11 +8,13 @@
#include <linux/device.h> #include <linux/greybus.h> +#include <linux/mutex.h> #include <linux/types.h>
#define GB_CPC_MSG_SIZE_MAX 2048 #define GB_CPC_NUM_CPORTS 8
+struct cpc_cport; struct cpc_host_device;
struct cpc_hd_driver { @@ -25,12 +27,22 @@ struct cpc_hd_driver { * struct cpc_host_device - CPC host device. * @gb_hd: pointer to Greybus Host Device this device belongs to. * @driver: driver operations. + * @lock: mutex to synchronize access to cport array. + * @cports: array of cport pointers allocated by Greybus core. */ struct cpc_host_device { struct gb_host_device *gb_hd; const struct cpc_hd_driver *driver; + + struct mutex lock; /* Synchronize access to cports */ + struct cpc_cport *cports[GB_CPC_NUM_CPORTS]; };
+static inline struct device *cpc_hd_dev(struct cpc_host_device *cpc_hd) +{ + return &cpc_hd->gb_hd->dev; +} + struct cpc_host_device *cpc_hd_create(struct cpc_hd_driver *driver, struct device *parent); int cpc_hd_add(struct cpc_host_device *cpc_hd); void cpc_hd_put(struct cpc_host_device *cpc_hd);
CPC comes with its own header, that is not yet implemented. Without skb, the CPC host device drivers have to get two pointers to get a full packet: one pointer to the CPC header and one pointer to the GB message. In order to make their implementations simpler, convert the GB message into an SKB.
Signed-off-by: Damien Riégel damien.riegel@silabs.com --- drivers/greybus/cpc/cpc.h | 11 +++++++++- drivers/greybus/cpc/cport.c | 11 ++++++++-- drivers/greybus/cpc/host.c | 41 ++++++++++++++++++++++++++++++++++--- drivers/greybus/cpc/host.h | 7 ++++--- 4 files changed, 61 insertions(+), 9 deletions(-)
diff --git a/drivers/greybus/cpc/cpc.h b/drivers/greybus/cpc/cpc.h index 85d02954307..7e032f6cf50 100644 --- a/drivers/greybus/cpc/cpc.h +++ b/drivers/greybus/cpc/cpc.h @@ -24,6 +24,15 @@ struct cpc_cport { struct cpc_cport *cpc_cport_alloc(u16 cport_id, gfp_t gfp_mask); void cpc_cport_release(struct cpc_cport *cport);
-int cpc_cport_message_send(struct cpc_cport *cport, struct gb_message *message, gfp_t gfp_mask); +int cpc_cport_transmit(struct cpc_cport *cport, struct sk_buff *skb); + +struct cpc_skb_cb { + struct cpc_cport *cport; + + /* Keep track of the GB message the skb originates from */ + struct gb_message *gb_message; +}; + +#define CPC_SKB_CB(__skb) ((struct cpc_skb_cb *)&((__skb)->cb[0]))
#endif diff --git a/drivers/greybus/cpc/cport.c b/drivers/greybus/cpc/cport.c index 88bdb2f8182..ed0b8e8b0d7 100644 --- a/drivers/greybus/cpc/cport.c +++ b/drivers/greybus/cpc/cport.c @@ -31,7 +31,14 @@ void cpc_cport_release(struct cpc_cport *cport) kfree(cport); }
-int cpc_cport_message_send(struct cpc_cport *cport, struct gb_message *message, gfp_t gfp_mask) +/** + * cpc_cport_transmit() - Transmit skb over cport. + * @cport: cport. + * @skb: skb to be transmitted. + */ +int cpc_cport_transmit(struct cpc_cport *cport, struct sk_buff *skb) { - return cport->cpc_hd->driver->message_send(cport->cpc_hd, cport->id, message, gfp_mask); + struct cpc_host_device *cpc_hd = cport->cpc_hd; + + return cpc_hd_send_skb(cpc_hd, skb); } diff --git a/drivers/greybus/cpc/host.c b/drivers/greybus/cpc/host.c index 033ff7f0184..2ca938c2b48 100644 --- a/drivers/greybus/cpc/host.c +++ b/drivers/greybus/cpc/host.c @@ -6,6 +6,7 @@ #include <linux/err.h> #include <linux/greybus.h> #include <linux/module.h> +#include <linux/skbuff.h>
#include "cpc.h" #include "host.h" @@ -39,6 +40,8 @@ static int cpc_hd_message_send(struct cpc_host_device *cpc_hd, u16 cport_id, struct gb_message *message, gfp_t gfp_mask) { struct cpc_cport *cport; + struct sk_buff *skb; + unsigned int size;
cport = cpc_hd_get_cport(cpc_hd, cport_id); if (!cport) { @@ -46,7 +49,18 @@ static int cpc_hd_message_send(struct cpc_host_device *cpc_hd, u16 cport_id, return -EINVAL; }
- return cpc_cport_message_send(cport, message, gfp_mask); + size = sizeof(*message->header) + message->payload_size; + skb = alloc_skb(size, gfp_mask); + if (!skb) + return -ENOMEM; + + /* Header and payload are already contiguous in Greybus message */ + skb_put_data(skb, message->buffer, sizeof(*message->header) + message->payload_size); + + CPC_SKB_CB(skb)->cport = cport; + CPC_SKB_CB(skb)->gb_message = message; + + return cpc_cport_transmit(cport, skb); }
static int cpc_hd_cport_allocate(struct cpc_host_device *cpc_hd, int cport_id, unsigned long flags) @@ -144,8 +158,8 @@ struct cpc_host_device *cpc_hd_create(struct cpc_hd_driver *driver, struct devic struct cpc_host_device *cpc_hd; struct gb_host_device *hd;
- if ((!driver->message_send) || (!driver->message_cancel)) { - dev_err(parent, "missing mandatory callbacks\n"); + if (!driver->transmit) { + dev_err(parent, "missing mandatory callback\n"); return ERR_PTR(-EINVAL); }
@@ -181,12 +195,33 @@ void cpc_hd_del(struct cpc_host_device *cpc_hd) } EXPORT_SYMBOL_GPL(cpc_hd_del);
+void cpc_hd_message_sent(struct sk_buff *skb, int status) +{ + struct cpc_host_device *cpc_hd = CPC_SKB_CB(skb)->cport->cpc_hd; + struct gb_host_device *hd = cpc_hd->gb_hd; + + greybus_message_sent(hd, CPC_SKB_CB(skb)->gb_message, status); +} +EXPORT_SYMBOL_GPL(cpc_hd_message_sent); + void cpc_hd_rcvd(struct cpc_host_device *cpc_hd, u16 cport_id, u8 *data, size_t length) { greybus_data_rcvd(cpc_hd->gb_hd, cport_id, data, length); } EXPORT_SYMBOL_GPL(cpc_hd_rcvd);
+/** + * cpc_hd_send_skb() - Queue a socket buffer for transmission. + * @cpc_hd: Host device to send SKB over. + * @skb: SKB to send. + */ +int cpc_hd_send_skb(struct cpc_host_device *cpc_hd, struct sk_buff *skb) +{ + const struct cpc_hd_driver *drv = cpc_hd->driver; + + return drv->transmit(cpc_hd, skb); +} + MODULE_DESCRIPTION("Greybus over CPC"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Silicon Laboratories, Inc."); diff --git a/drivers/greybus/cpc/host.h b/drivers/greybus/cpc/host.h index 1c168cdd2bf..104d61e3bc5 100644 --- a/drivers/greybus/cpc/host.h +++ b/drivers/greybus/cpc/host.h @@ -18,9 +18,7 @@ struct cpc_cport; struct cpc_host_device;
struct cpc_hd_driver { - int (*message_send)(struct cpc_host_device *hd, u16 dest_cport_id, - struct gb_message *message, gfp_t gfp_mask); - void (*message_cancel)(struct gb_message *message); + int (*transmit)(struct cpc_host_device *hd, struct sk_buff *skb); };
/** @@ -48,5 +46,8 @@ int cpc_hd_add(struct cpc_host_device *cpc_hd); void cpc_hd_put(struct cpc_host_device *cpc_hd); void cpc_hd_del(struct cpc_host_device *cpc_hd); void cpc_hd_rcvd(struct cpc_host_device *cpc_hd, u16 cport_id, u8 *data, size_t length); +void cpc_hd_message_sent(struct sk_buff *skb, int status); + +int cpc_hd_send_skb(struct cpc_host_device *cpc_hd, struct sk_buff *skb);
#endif
Take advantage of the padding bytes present in the Greybus header to store the CPort ID and minize overhead. This technique is already used by the es2 driver.
Signed-off-by: Damien Riégel damien.riegel@silabs.com --- drivers/greybus/cpc/cpc.h | 3 +++ drivers/greybus/cpc/cport.c | 29 +++++++++++++++++++++++++++++ drivers/greybus/cpc/host.c | 13 ++++++++++++- drivers/greybus/cpc/host.h | 2 +- 4 files changed, 45 insertions(+), 2 deletions(-)
diff --git a/drivers/greybus/cpc/cpc.h b/drivers/greybus/cpc/cpc.h index 7e032f6cf50..0f2d204d86d 100644 --- a/drivers/greybus/cpc/cpc.h +++ b/drivers/greybus/cpc/cpc.h @@ -24,6 +24,9 @@ struct cpc_cport { struct cpc_cport *cpc_cport_alloc(u16 cport_id, gfp_t gfp_mask); void cpc_cport_release(struct cpc_cport *cport);
+void cpc_cport_pack(struct gb_operation_msg_hdr *gb_hdr, u16 cport_id); +u16 cpc_cport_unpack(struct gb_operation_msg_hdr *gb_hdr); + int cpc_cport_transmit(struct cpc_cport *cport, struct sk_buff *skb);
struct cpc_skb_cb { diff --git a/drivers/greybus/cpc/cport.c b/drivers/greybus/cpc/cport.c index ed0b8e8b0d7..0fc4ff0c5bb 100644 --- a/drivers/greybus/cpc/cport.c +++ b/drivers/greybus/cpc/cport.c @@ -3,6 +3,9 @@ * Copyright (c) 2025, Silicon Laboratories, Inc. */
+#include <linux/unaligned.h> +#include <linux/skbuff.h> + #include "cpc.h" #include "host.h"
@@ -31,6 +34,27 @@ void cpc_cport_release(struct cpc_cport *cport) kfree(cport); }
+/** + * cpc_cport_pack() - Pack CPort ID into Greybus Operation Message header. + * @gb_hdr: Greybus operation message header. + * @cport_id: CPort ID to pack. + */ +void cpc_cport_pack(struct gb_operation_msg_hdr *gb_hdr, u16 cport_id) +{ + put_unaligned_le16(cport_id, gb_hdr->pad); +} + +/** + * cpc_cport_unpack() - Unpack CPort ID from Greybus Operation Message header. + * @gb_hdr: Greybus operation message header. + * + * Return: CPort ID packed in the header. + */ +u16 cpc_cport_unpack(struct gb_operation_msg_hdr *gb_hdr) +{ + return get_unaligned_le16(gb_hdr->pad); +} + /** * cpc_cport_transmit() - Transmit skb over cport. * @cport: cport. @@ -39,6 +63,11 @@ void cpc_cport_release(struct cpc_cport *cport) int cpc_cport_transmit(struct cpc_cport *cport, struct sk_buff *skb) { struct cpc_host_device *cpc_hd = cport->cpc_hd; + struct gb_operation_msg_hdr *gb_hdr; + + /* Inject cport ID in Greybus header */ + gb_hdr = (struct gb_operation_msg_hdr *)skb->data; + cpc_cport_pack(gb_hdr, cport->id);
return cpc_hd_send_skb(cpc_hd, skb); } diff --git a/drivers/greybus/cpc/host.c b/drivers/greybus/cpc/host.c index 2ca938c2b48..1d81c624dd6 100644 --- a/drivers/greybus/cpc/host.c +++ b/drivers/greybus/cpc/host.c @@ -204,8 +204,19 @@ void cpc_hd_message_sent(struct sk_buff *skb, int status) } EXPORT_SYMBOL_GPL(cpc_hd_message_sent);
-void cpc_hd_rcvd(struct cpc_host_device *cpc_hd, u16 cport_id, u8 *data, size_t length) +void cpc_hd_rcvd(struct cpc_host_device *cpc_hd, u8 *data, size_t length) { + struct gb_operation_msg_hdr *gb_hdr; + u16 cport_id; + + /* Prevent an out-of-bound access if called with non-sensical parameters. */ + if (!data || length < sizeof(*gb_hdr)) + return; + + /* Retrieve cport ID that was packed in Greybus header */ + gb_hdr = (struct gb_operation_msg_hdr *)data; + cport_id = cpc_cport_unpack(gb_hdr); + greybus_data_rcvd(cpc_hd->gb_hd, cport_id, data, length); } EXPORT_SYMBOL_GPL(cpc_hd_rcvd); diff --git a/drivers/greybus/cpc/host.h b/drivers/greybus/cpc/host.h index 104d61e3bc5..86d205fcb59 100644 --- a/drivers/greybus/cpc/host.h +++ b/drivers/greybus/cpc/host.h @@ -45,7 +45,7 @@ struct cpc_host_device *cpc_hd_create(struct cpc_hd_driver *driver, struct devic int cpc_hd_add(struct cpc_host_device *cpc_hd); void cpc_hd_put(struct cpc_host_device *cpc_hd); void cpc_hd_del(struct cpc_host_device *cpc_hd); -void cpc_hd_rcvd(struct cpc_host_device *cpc_hd, u16 cport_id, u8 *data, size_t length); +void cpc_hd_rcvd(struct cpc_host_device *cpc_hd, u8 *data, size_t length); void cpc_hd_message_sent(struct sk_buff *skb, int status);
int cpc_hd_send_skb(struct cpc_host_device *cpc_hd, struct sk_buff *skb);
For symmetry, also convert the RX path to use socket buffers instead of u8* buffers. The main difference is that CPC host device drivers were responsible for allocating and freeing the buffers. Now they are only responsible for allocating the skb and pass it to the upper layer, the CPC "core" module will take of releasing it when it's done with it.
Signed-off-by: Damien Riégel damien.riegel@silabs.com --- drivers/greybus/cpc/host.c | 13 ++++++++----- drivers/greybus/cpc/host.h | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/drivers/greybus/cpc/host.c b/drivers/greybus/cpc/host.c index 1d81c624dd6..d797845497a 100644 --- a/drivers/greybus/cpc/host.c +++ b/drivers/greybus/cpc/host.c @@ -204,20 +204,23 @@ void cpc_hd_message_sent(struct sk_buff *skb, int status) } EXPORT_SYMBOL_GPL(cpc_hd_message_sent);
-void cpc_hd_rcvd(struct cpc_host_device *cpc_hd, u8 *data, size_t length) +void cpc_hd_rcvd(struct cpc_host_device *cpc_hd, struct sk_buff *skb) { struct gb_operation_msg_hdr *gb_hdr; u16 cport_id;
/* Prevent an out-of-bound access if called with non-sensical parameters. */ - if (!data || length < sizeof(*gb_hdr)) - return; + if (skb->len < sizeof(*gb_hdr)) + goto free_skb;
/* Retrieve cport ID that was packed in Greybus header */ - gb_hdr = (struct gb_operation_msg_hdr *)data; + gb_hdr = (struct gb_operation_msg_hdr *)skb->data; cport_id = cpc_cport_unpack(gb_hdr);
- greybus_data_rcvd(cpc_hd->gb_hd, cport_id, data, length); + greybus_data_rcvd(cpc_hd->gb_hd, cport_id, skb->data, skb->len); + +free_skb: + kfree_skb(skb); } EXPORT_SYMBOL_GPL(cpc_hd_rcvd);
diff --git a/drivers/greybus/cpc/host.h b/drivers/greybus/cpc/host.h index 86d205fcb59..a3d6ddb648e 100644 --- a/drivers/greybus/cpc/host.h +++ b/drivers/greybus/cpc/host.h @@ -45,7 +45,7 @@ struct cpc_host_device *cpc_hd_create(struct cpc_hd_driver *driver, struct devic int cpc_hd_add(struct cpc_host_device *cpc_hd); void cpc_hd_put(struct cpc_host_device *cpc_hd); void cpc_hd_del(struct cpc_host_device *cpc_hd); -void cpc_hd_rcvd(struct cpc_host_device *cpc_hd, u8 *data, size_t length); +void cpc_hd_rcvd(struct cpc_host_device *cpc_hd, struct sk_buff *skb); void cpc_hd_message_sent(struct sk_buff *skb, int status);
int cpc_hd_send_skb(struct cpc_host_device *cpc_hd, struct sk_buff *skb);
CPC main features are reliable transmission and remote's receive window management. To implement these features, an additional header is needed. This header is prepended to all Greybus messages.
Reliable transmission: to make transmission reliable, messages are sequenced and acknowledged. That constitutes two bytes of the header, one for the sequence number, one for the acknowledgment number. If a message is not acked in a timely manner, a retransmission mechanism will attempt another transmission. That mechanism will be implemented in a future patch set.
Remote's receive window: the remote advertises the number of reception buffers that are available on this cport. The other peer must take care of not sending more messages than advertised by the remote. This is a sort of flow control. That accounts for one byte in the header.
The remaining byte carries some flags. For instance, there is a flag to indicate if it's a CPC message or a Greybus message.
Signed-off-by: Damien Riégel damien.riegel@silabs.com --- drivers/greybus/cpc/header.h | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 drivers/greybus/cpc/header.h
diff --git a/drivers/greybus/cpc/header.h b/drivers/greybus/cpc/header.h new file mode 100644 index 00000000000..84c47f105b3 --- /dev/null +++ b/drivers/greybus/cpc/header.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2025, Silicon Laboratories, Inc. + */ + +#ifndef __CPC_HEADER_H +#define __CPC_HEADER_H + +#include <linux/greybus.h> +#include <linux/types.h> + +#define CPC_HEADER_MAX_RX_WINDOW U8_MAX + +/** + * struct cpc header - Representation of CPC header. + * @ctrl_flags: contains the type of frame and other control flags. + * @recv_wnd: number of buffers that the cport can receive without blocking. + * @seq: sequence number. + * @ack: acknowledge number, indicate to the remote the next sequence number + * this peer expects to see. + * + * Each peer can confirm reception of frames by setting the acknowledgment number to the next frame + * it expects to see, i.e. setting the ack number to X effectively acknowledges frames with sequence + * number up to X-1. + * + * CPC is designed around the concept that each cport has its pool of reception buffers. The number + * of buffers in a pool is advertised to the remote via the @recv_wnd attribute. This acts as + * software flow-control, and a peer shall not send frames to a remote if the @recv_wnd is zero. + * + * The hight-bit (0x80) of the control byte indicates if the frame targets CPC or Greybus. If the + * bit is set, the frame should be interpreted as CPC control frames. For simplicity, control frames + * have the same encoding as Greybus frames. + */ +struct cpc_header { + __u8 ctrl_flags; + __u8 recv_wnd; + __u8 seq; + __u8 ack; +} __packed; + +#define CPC_HEADER_SIZE (sizeof(struct cpc_header)) + +#endif
Account that a CPC header is prepended to every frame in the RX and TX path. For now, nothing is done with that headroom but at least bytes are reserved for it.
Signed-off-by: Damien Riégel damien.riegel@silabs.com --- drivers/greybus/cpc/host.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/drivers/greybus/cpc/host.c b/drivers/greybus/cpc/host.c index d797845497a..1cce4f987e3 100644 --- a/drivers/greybus/cpc/host.c +++ b/drivers/greybus/cpc/host.c @@ -9,6 +9,7 @@ #include <linux/skbuff.h>
#include "cpc.h" +#include "header.h" #include "host.h"
@@ -49,11 +50,13 @@ static int cpc_hd_message_send(struct cpc_host_device *cpc_hd, u16 cport_id, return -EINVAL; }
- size = sizeof(*message->header) + message->payload_size; + size = sizeof(*message->header) + message->payload_size + CPC_HEADER_SIZE; skb = alloc_skb(size, gfp_mask); if (!skb) return -ENOMEM;
+ skb_reserve(skb, CPC_HEADER_SIZE); + /* Header and payload are already contiguous in Greybus message */ skb_put_data(skb, message->buffer, sizeof(*message->header) + message->payload_size);
@@ -210,9 +213,11 @@ void cpc_hd_rcvd(struct cpc_host_device *cpc_hd, struct sk_buff *skb) u16 cport_id;
/* Prevent an out-of-bound access if called with non-sensical parameters. */ - if (skb->len < sizeof(*gb_hdr)) + if (skb->len < (sizeof(*gb_hdr) + CPC_HEADER_SIZE)) goto free_skb;
+ skb_pull(skb, CPC_HEADER_SIZE); + /* Retrieve cport ID that was packed in Greybus header */ gb_hdr = (struct gb_operation_msg_hdr *)skb->data; cport_id = cpc_cport_unpack(gb_hdr);
The first step in making the CPC header actually do something is to add the sequence number to outgoing messages and validate that incoming frames are received in order.
At this stage, the driver doesn't send standalone acks, so if a message with Sequence X is received, the remote will not be acknowledged until a message targeting that CPort comes from the Greybus layer. Only then the driver will ack with acknowledgedment number of X + 1.
Signed-off-by: Damien Riégel damien.riegel@silabs.com --- drivers/greybus/cpc/Makefile | 2 +- drivers/greybus/cpc/cpc.h | 20 ++++++++++++++ drivers/greybus/cpc/cport.c | 25 +++++++++++++++++ drivers/greybus/cpc/header.c | 17 ++++++++++++ drivers/greybus/cpc/header.h | 2 ++ drivers/greybus/cpc/host.c | 13 ++++++--- drivers/greybus/cpc/protocol.c | 49 ++++++++++++++++++++++++++++++++++ 7 files changed, 123 insertions(+), 5 deletions(-) create mode 100644 drivers/greybus/cpc/header.c create mode 100644 drivers/greybus/cpc/protocol.c
diff --git a/drivers/greybus/cpc/Makefile b/drivers/greybus/cpc/Makefile index 3d50f8c5473..c4b530d27a3 100644 --- a/drivers/greybus/cpc/Makefile +++ b/drivers/greybus/cpc/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0
-gb-cpc-y := cport.o host.o +gb-cpc-y := cport.o header.o host.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 index 0f2d204d86d..db760cf8b32 100644 --- a/drivers/greybus/cpc/cpc.h +++ b/drivers/greybus/cpc/cpc.h @@ -8,17 +8,32 @@
#include <linux/device.h> #include <linux/greybus.h> +#include <linux/mutex.h> #include <linux/types.h>
+struct sk_buff; + /** * struct cpc_cport - CPC cport * @id: cport ID * @cpc_hd: pointer to the CPC host device this cport belongs to + * @lock: mutex to synchronize accesses to tcb and other attributes + * @tcb: Transmission Control Block */ struct cpc_cport { u16 id;
struct cpc_host_device *cpc_hd; + struct mutex lock; /* Synchronize access to state variables */ + + /* + * @ack: current acknowledge number + * @seq: current sequence number + */ + struct { + u8 ack; + u8 seq; + } tcb; };
struct cpc_cport *cpc_cport_alloc(u16 cport_id, gfp_t gfp_mask); @@ -34,8 +49,13 @@ struct cpc_skb_cb {
/* Keep track of the GB message the skb originates from */ struct gb_message *gb_message; + + u8 seq; };
#define CPC_SKB_CB(__skb) ((struct cpc_skb_cb *)&((__skb)->cb[0]))
+void cpc_protocol_prepare_header(struct sk_buff *skb, u8 ack); +void cpc_protocol_on_data(struct cpc_cport *cport, struct sk_buff *skb); + #endif diff --git a/drivers/greybus/cpc/cport.c b/drivers/greybus/cpc/cport.c index 0fc4ff0c5bb..2ee0b129996 100644 --- a/drivers/greybus/cpc/cport.c +++ b/drivers/greybus/cpc/cport.c @@ -9,6 +9,16 @@ #include "cpc.h" #include "host.h"
+/** + * cpc_cport_tcb_reset() - Reset cport's TCB to initial values. + * @cport: cport pointer + */ +static void cpc_cport_tcb_reset(struct cpc_cport *cport) +{ + cport->tcb.ack = 0; + cport->tcb.seq = 0; +} + /** * cpc_cport_alloc() - Allocate and initialize CPC cport. * @cport_id: cport ID. @@ -25,6 +35,9 @@ struct cpc_cport *cpc_cport_alloc(u16 cport_id, gfp_t gfp_mask) return NULL;
cport->id = cport_id; + cpc_cport_tcb_reset(cport); + + mutex_init(&cport->lock);
return cport; } @@ -64,10 +77,22 @@ int cpc_cport_transmit(struct cpc_cport *cport, struct sk_buff *skb) { struct cpc_host_device *cpc_hd = cport->cpc_hd; struct gb_operation_msg_hdr *gb_hdr; + u8 ack;
/* Inject cport ID in Greybus header */ gb_hdr = (struct gb_operation_msg_hdr *)skb->data; cpc_cport_pack(gb_hdr, cport->id);
+ mutex_lock(&cport->lock); + + CPC_SKB_CB(skb)->seq = cport->tcb.seq; + + cport->tcb.seq++; + ack = cport->tcb.ack; + + mutex_unlock(&cport->lock); + + cpc_protocol_prepare_header(skb, ack); + return cpc_hd_send_skb(cpc_hd, skb); } diff --git a/drivers/greybus/cpc/header.c b/drivers/greybus/cpc/header.c new file mode 100644 index 00000000000..62946d6077e --- /dev/null +++ b/drivers/greybus/cpc/header.c @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2025, Silicon Laboratories, Inc. + */ + +#include "header.h" + +/** + * 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; +} diff --git a/drivers/greybus/cpc/header.h b/drivers/greybus/cpc/header.h index 84c47f105b3..d46ad8e53fe 100644 --- a/drivers/greybus/cpc/header.h +++ b/drivers/greybus/cpc/header.h @@ -40,4 +40,6 @@ struct cpc_header {
#define CPC_HEADER_SIZE (sizeof(struct cpc_header))
+u8 cpc_header_get_seq(const struct cpc_header *hdr); + #endif diff --git a/drivers/greybus/cpc/host.c b/drivers/greybus/cpc/host.c index 1cce4f987e3..9173d5ab5a1 100644 --- a/drivers/greybus/cpc/host.c +++ b/drivers/greybus/cpc/host.c @@ -210,19 +210,24 @@ EXPORT_SYMBOL_GPL(cpc_hd_message_sent); void cpc_hd_rcvd(struct cpc_host_device *cpc_hd, struct sk_buff *skb) { struct gb_operation_msg_hdr *gb_hdr; + struct cpc_cport *cport; u16 cport_id;
/* Prevent an out-of-bound access if called with non-sensical parameters. */ if (skb->len < (sizeof(*gb_hdr) + CPC_HEADER_SIZE)) goto free_skb;
- skb_pull(skb, CPC_HEADER_SIZE); - /* Retrieve cport ID that was packed in Greybus header */ - gb_hdr = (struct gb_operation_msg_hdr *)skb->data; + gb_hdr = (struct gb_operation_msg_hdr *)(skb->data + CPC_HEADER_SIZE); cport_id = cpc_cport_unpack(gb_hdr);
- greybus_data_rcvd(cpc_hd->gb_hd, cport_id, skb->data, skb->len); + cport = cpc_hd_get_cport(cpc_hd, cport_id); + if (!cport) { + dev_warn(cpc_hd_dev(cpc_hd), "cport %u not allocated\n", cport_id); + goto free_skb; + } + + cpc_protocol_on_data(cport, skb);
free_skb: kfree_skb(skb); diff --git a/drivers/greybus/cpc/protocol.c b/drivers/greybus/cpc/protocol.c new file mode 100644 index 00000000000..5684557df64 --- /dev/null +++ b/drivers/greybus/cpc/protocol.c @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2025, Silicon Laboratories, Inc. + */ + +#include <linux/skbuff.h> + +#include "cpc.h" +#include "header.h" +#include "host.h" + + +void cpc_protocol_prepare_header(struct sk_buff *skb, u8 ack) +{ + struct cpc_header *hdr; + + skb_push(skb, sizeof(*hdr)); + + hdr = (struct cpc_header *)skb->data; + hdr->ack = ack; + hdr->recv_wnd = 0; + hdr->ctrl_flags = 0; + hdr->seq = CPC_SKB_CB(skb)->seq; +} + +void cpc_protocol_on_data(struct cpc_cport *cport, struct sk_buff *skb) +{ + struct cpc_header *cpc_hdr = (struct cpc_header *)skb->data; + u8 seq = cpc_header_get_seq(cpc_hdr); + bool expected_seq = false; + + mutex_lock(&cport->lock); + + expected_seq = seq == cport->tcb.ack; + if (expected_seq) + cport->tcb.ack++; + else + dev_warn(cpc_hd_dev(cport->cpc_hd), + "unexpected seq: %u, expected seq: %u\n", + seq, cport->tcb.ack); + + mutex_unlock(&cport->lock); + + if (expected_seq) { + skb_pull(skb, CPC_HEADER_SIZE); + + greybus_data_rcvd(cport->cpc_hd->gb_hd, cport->id, skb->data, skb->len); + } +}
Currently, CPC doesn't send messages on its own, it only prepends its header to outgoing messages. This can lead to messages not being acknowledged, for instance in the case of an SVC Ping
Host Device
SVC Ping (seq=X, ack=Y) SVC Ping Reply (seq=Y, ack=X+1)
The "Ping Reply" is never acknowledged at the CPC level, which can lead to retransmissions, or worst the device might think the link is broken and do something to recover.
To prevent that scenario, an ack mechanism is implemented in the most straightforward manner: send an ACK to all incoming messages. Here, two flags need to be added: - First, an ACK frame should not be passed to the Greybus layer, so a "CONTROL" flag is added. If this flag is set, it means it's a control messages and should stay at the CPC level. Currently there is only one type of control frame, the standalone ack. Control messages have the same format as Greybus operations. - Second, ack themselves should not be acked, so to determine if a message should be acked or not, a REQUEST_ACK flag is added.
Signed-off-by: Damien Riégel damien.riegel@silabs.com --- drivers/greybus/cpc/cpc.h | 3 ++ drivers/greybus/cpc/cport.c | 1 + drivers/greybus/cpc/header.c | 41 ++++++++++++++++++++++++ drivers/greybus/cpc/header.h | 3 ++ drivers/greybus/cpc/protocol.c | 57 ++++++++++++++++++++++++++++------ 5 files changed, 96 insertions(+), 9 deletions(-)
diff --git a/drivers/greybus/cpc/cpc.h b/drivers/greybus/cpc/cpc.h index db760cf8b32..bec2580e358 100644 --- a/drivers/greybus/cpc/cpc.h +++ b/drivers/greybus/cpc/cpc.h @@ -51,6 +51,9 @@ struct cpc_skb_cb { struct gb_message *gb_message;
u8 seq; + +#define CPC_SKB_FLAG_REQ_ACK (1 << 0) + u8 cpc_flags; };
#define CPC_SKB_CB(__skb) ((struct cpc_skb_cb *)&((__skb)->cb[0])) diff --git a/drivers/greybus/cpc/cport.c b/drivers/greybus/cpc/cport.c index 2ee0b129996..35a148abbed 100644 --- a/drivers/greybus/cpc/cport.c +++ b/drivers/greybus/cpc/cport.c @@ -86,6 +86,7 @@ int cpc_cport_transmit(struct cpc_cport *cport, struct sk_buff *skb) mutex_lock(&cport->lock);
CPC_SKB_CB(skb)->seq = cport->tcb.seq; + CPC_SKB_CB(skb)->cpc_flags = CPC_SKB_FLAG_REQ_ACK;
cport->tcb.seq++; ack = cport->tcb.ack; diff --git a/drivers/greybus/cpc/header.c b/drivers/greybus/cpc/header.c index 62946d6077e..973883c1d5c 100644 --- a/drivers/greybus/cpc/header.c +++ b/drivers/greybus/cpc/header.c @@ -3,8 +3,25 @@ * Copyright (c) 2025, Silicon Laboratories, Inc. */
+#include <linux/bitfield.h> +#include <linux/bits.h> + #include "header.h"
+#define CPC_HEADER_CONTROL_IS_CONTROL_MASK BIT(7) +#define CPC_HEADER_CONTROL_REQ_ACK_MASK BIT(6) + +/** + * cpc_header_is_control() - Identify if this is a control frame. + * @hdr: CPC header. + * + * Return: True if this is a control frame, false if this a Greybus frame. + */ +bool cpc_header_is_control(const struct cpc_header *hdr) +{ + return hdr->ctrl_flags & CPC_HEADER_CONTROL_IS_CONTROL_MASK; +} + /** * cpc_header_get_seq() - Get the sequence number. * @hdr: CPC header. @@ -15,3 +32,27 @@ u8 cpc_header_get_seq(const struct cpc_header *hdr) { return hdr->seq; } + +/** + * 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_HEADER_CONTROL_REQ_ACK_MASK, hdr->ctrl_flags); +} + +/** + * cpc_header_encode_ctrl_flags() - Encode parameters into the control byte. + * @control: True if CPC control frame, false if Greybus frame. + * @req_ack: Frame flag indicating a request to be acknowledged. + * + * Return: Encoded control byte. + */ +u8 cpc_header_encode_ctrl_flags(bool control, bool req_ack) +{ + return FIELD_PREP(CPC_HEADER_CONTROL_IS_CONTROL_MASK, control) | + FIELD_PREP(CPC_HEADER_CONTROL_REQ_ACK_MASK, req_ack); +} diff --git a/drivers/greybus/cpc/header.h b/drivers/greybus/cpc/header.h index d46ad8e53fe..5ac431b3c54 100644 --- a/drivers/greybus/cpc/header.h +++ b/drivers/greybus/cpc/header.h @@ -40,6 +40,9 @@ struct cpc_header {
#define CPC_HEADER_SIZE (sizeof(struct cpc_header))
+bool cpc_header_is_control(const struct cpc_header *hdr); u8 cpc_header_get_seq(const struct cpc_header *hdr); +bool cpc_header_get_req_ack(const struct cpc_header *hdr); +u8 cpc_header_encode_ctrl_flags(bool control, bool req_ack);
#endif diff --git a/drivers/greybus/cpc/protocol.c b/drivers/greybus/cpc/protocol.c index 5684557df64..63b4127fcea 100644 --- a/drivers/greybus/cpc/protocol.c +++ b/drivers/greybus/cpc/protocol.c @@ -10,6 +10,11 @@ #include "host.h"
+static bool cpc_skb_is_sequenced(struct sk_buff *skb) +{ + return CPC_SKB_CB(skb)->cpc_flags & CPC_SKB_FLAG_REQ_ACK; +} + void cpc_protocol_prepare_header(struct sk_buff *skb, u8 ack) { struct cpc_header *hdr; @@ -19,29 +24,63 @@ void cpc_protocol_prepare_header(struct sk_buff *skb, u8 ack) hdr = (struct cpc_header *)skb->data; hdr->ack = ack; hdr->recv_wnd = 0; - hdr->ctrl_flags = 0; hdr->seq = CPC_SKB_CB(skb)->seq; + hdr->ctrl_flags = cpc_header_encode_ctrl_flags(!CPC_SKB_CB(skb)->gb_message, + cpc_skb_is_sequenced(skb)); +} + +static void cpc_protocol_queue_ack(struct cpc_cport *cport, u8 ack) +{ + struct gb_operation_msg_hdr *gb_hdr; + struct sk_buff *skb; + + skb = alloc_skb(CPC_HEADER_SIZE + sizeof(*gb_hdr), GFP_KERNEL); + if (!skb) + return; + + skb_reserve(skb, CPC_HEADER_SIZE); + + gb_hdr = skb_put(skb, sizeof(*gb_hdr)); + memset(gb_hdr, 0, sizeof(*gb_hdr)); + + /* In the CPC Operation Header, only the size and cport_id matter for ACKs. */ + gb_hdr->size = sizeof(*gb_hdr); + cpc_cport_pack(gb_hdr, cport->id); + + cpc_protocol_prepare_header(skb, ack); + + cpc_hd_send_skb(cport->cpc_hd, skb); }
void cpc_protocol_on_data(struct cpc_cport *cport, struct sk_buff *skb) { struct cpc_header *cpc_hdr = (struct cpc_header *)skb->data; + bool require_ack = cpc_header_get_req_ack(cpc_hdr); u8 seq = cpc_header_get_seq(cpc_hdr); bool expected_seq = false; + u8 ack;
mutex_lock(&cport->lock);
- expected_seq = seq == cport->tcb.ack; - if (expected_seq) - cport->tcb.ack++; - else - dev_warn(cpc_hd_dev(cport->cpc_hd), - "unexpected seq: %u, expected seq: %u\n", - seq, cport->tcb.ack); + if (require_ack) { + expected_seq = seq == cport->tcb.ack; + if (expected_seq) + cport->tcb.ack++; + else + dev_warn(cpc_hd_dev(cport->cpc_hd), + "unexpected seq: %u, expected seq: %u\n", + seq, cport->tcb.ack); + } + + ack = cport->tcb.ack;
mutex_unlock(&cport->lock);
- if (expected_seq) { + /* Ack no matter if the sequence was valid or not, to resync with remote */ + if (require_ack) + cpc_protocol_queue_ack(cport, ack); + + if (expected_seq && !cpc_header_is_control(cpc_hdr)) { skb_pull(skb, CPC_HEADER_SIZE);
greybus_data_rcvd(cport->cpc_hd->gb_hd, cport->id, skb->data, skb->len);
As the first step to handle remote's RX window, store the skb in a sk_buff_head list, instead of sending a message immediately when pushed by Greybus.
skbs are still sent out straight away, but now there is a place to store away if the remote's RX window is too small.
Signed-off-by: Damien Riégel damien.riegel@silabs.com --- drivers/greybus/cpc/cpc.h | 9 ++++++--- drivers/greybus/cpc/cport.c | 21 ++++++++++++--------- drivers/greybus/cpc/host.c | 4 +++- drivers/greybus/cpc/protocol.c | 25 ++++++++++++++++++++++++- 4 files changed, 45 insertions(+), 14 deletions(-)
diff --git a/drivers/greybus/cpc/cpc.h b/drivers/greybus/cpc/cpc.h index bec2580e358..d5ad3b0846f 100644 --- a/drivers/greybus/cpc/cpc.h +++ b/drivers/greybus/cpc/cpc.h @@ -9,15 +9,16 @@ #include <linux/device.h> #include <linux/greybus.h> #include <linux/mutex.h> +#include <linux/skbuff.h> #include <linux/types.h>
-struct sk_buff;
/** * struct cpc_cport - CPC cport * @id: cport ID * @cpc_hd: pointer to the CPC host device this cport belongs to * @lock: mutex to synchronize accesses to tcb and other attributes + * @holding_queue: list of frames queued to be sent * @tcb: Transmission Control Block */ struct cpc_cport { @@ -26,6 +27,8 @@ struct cpc_cport { struct cpc_host_device *cpc_hd; struct mutex lock; /* Synchronize access to state variables */
+ struct sk_buff_head holding_queue; + /* * @ack: current acknowledge number * @seq: current sequence number @@ -42,7 +45,7 @@ void cpc_cport_release(struct cpc_cport *cport); void cpc_cport_pack(struct gb_operation_msg_hdr *gb_hdr, u16 cport_id); u16 cpc_cport_unpack(struct gb_operation_msg_hdr *gb_hdr);
-int cpc_cport_transmit(struct cpc_cport *cport, struct sk_buff *skb); +void cpc_cport_transmit(struct cpc_cport *cport, struct sk_buff *skb);
struct cpc_skb_cb { struct cpc_cport *cport; @@ -58,7 +61,7 @@ struct cpc_skb_cb {
#define CPC_SKB_CB(__skb) ((struct cpc_skb_cb *)&((__skb)->cb[0]))
-void cpc_protocol_prepare_header(struct sk_buff *skb, u8 ack); void cpc_protocol_on_data(struct cpc_cport *cport, struct sk_buff *skb); +void __cpc_protocol_write_head(struct cpc_cport *cport);
#endif diff --git a/drivers/greybus/cpc/cport.c b/drivers/greybus/cpc/cport.c index 35a148abbed..f850da7acfb 100644 --- a/drivers/greybus/cpc/cport.c +++ b/drivers/greybus/cpc/cport.c @@ -7,7 +7,6 @@ #include <linux/skbuff.h>
#include "cpc.h" -#include "host.h"
/** * cpc_cport_tcb_reset() - Reset cport's TCB to initial values. @@ -38,15 +37,23 @@ struct cpc_cport *cpc_cport_alloc(u16 cport_id, gfp_t gfp_mask) cpc_cport_tcb_reset(cport);
mutex_init(&cport->lock); + skb_queue_head_init(&cport->holding_queue);
return cport; }
void cpc_cport_release(struct cpc_cport *cport) { + skb_queue_purge(&cport->holding_queue); kfree(cport); }
+static void cpc_cport_queue_skb(struct cpc_cport *cport, struct sk_buff *skb) +{ + __skb_header_release(skb); + __skb_queue_tail(&cport->holding_queue, skb); +} + /** * cpc_cport_pack() - Pack CPort ID into Greybus Operation Message header. * @gb_hdr: Greybus operation message header. @@ -73,11 +80,9 @@ u16 cpc_cport_unpack(struct gb_operation_msg_hdr *gb_hdr) * @cport: cport. * @skb: skb to be transmitted. */ -int cpc_cport_transmit(struct cpc_cport *cport, struct sk_buff *skb) +void cpc_cport_transmit(struct cpc_cport *cport, struct sk_buff *skb) { - struct cpc_host_device *cpc_hd = cport->cpc_hd; struct gb_operation_msg_hdr *gb_hdr; - u8 ack;
/* Inject cport ID in Greybus header */ gb_hdr = (struct gb_operation_msg_hdr *)skb->data; @@ -89,11 +94,9 @@ int cpc_cport_transmit(struct cpc_cport *cport, struct sk_buff *skb) CPC_SKB_CB(skb)->cpc_flags = CPC_SKB_FLAG_REQ_ACK;
cport->tcb.seq++; - ack = cport->tcb.ack; + + cpc_cport_queue_skb(cport, skb); + __cpc_protocol_write_head(cport);
mutex_unlock(&cport->lock); - - cpc_protocol_prepare_header(skb, ack); - - return cpc_hd_send_skb(cpc_hd, skb); } diff --git a/drivers/greybus/cpc/host.c b/drivers/greybus/cpc/host.c index 9173d5ab5a1..27f02120266 100644 --- a/drivers/greybus/cpc/host.c +++ b/drivers/greybus/cpc/host.c @@ -63,7 +63,9 @@ static int cpc_hd_message_send(struct cpc_host_device *cpc_hd, u16 cport_id, CPC_SKB_CB(skb)->cport = cport; CPC_SKB_CB(skb)->gb_message = message;
- return cpc_cport_transmit(cport, skb); + cpc_cport_transmit(cport, skb); + + return 0; }
static int cpc_hd_cport_allocate(struct cpc_host_device *cpc_hd, int cport_id, unsigned long flags) diff --git a/drivers/greybus/cpc/protocol.c b/drivers/greybus/cpc/protocol.c index 63b4127fcea..272eafd79b0 100644 --- a/drivers/greybus/cpc/protocol.c +++ b/drivers/greybus/cpc/protocol.c @@ -15,7 +15,7 @@ static bool cpc_skb_is_sequenced(struct sk_buff *skb) return CPC_SKB_CB(skb)->cpc_flags & CPC_SKB_FLAG_REQ_ACK; }
-void cpc_protocol_prepare_header(struct sk_buff *skb, u8 ack) +static void cpc_protocol_prepare_header(struct sk_buff *skb, u8 ack) { struct cpc_header *hdr;
@@ -86,3 +86,26 @@ void cpc_protocol_on_data(struct cpc_cport *cport, struct sk_buff *skb) greybus_data_rcvd(cport->cpc_hd->gb_hd, cport->id, skb->data, skb->len); } } + +static void __cpc_protocol_write_skb(struct cpc_cport *cport, struct sk_buff *skb, u8 ack) +{ + cpc_protocol_prepare_header(skb, ack); + + cpc_hd_send_skb(cport->cpc_hd, skb); +} + +/* Write skbs at the head of holding queue */ +void __cpc_protocol_write_head(struct cpc_cport *cport) +{ + struct sk_buff *skb; + u8 ack; + + ack = cport->tcb.ack; + + /* For each SKB in the holding queue, clone it and pass it to lower layer */ + while ((skb = skb_peek(&cport->holding_queue))) { + skb_unlink(skb, &cport->holding_queue); + + __cpc_protocol_write_skb(cport, skb, ack); + } +}
On Fri Nov 14, 2025 at 10:07 AM EST, Damien Riégel wrote:
+/* Write skbs at the head of holding queue */ +void __cpc_protocol_write_head(struct cpc_cport *cport)
I have a nitpick with the name of this function. I find it misleading, as it suggests we are writing to the CPort's holding queue head. Instead, we are processing the queue to send skbs that fit in the remote's RX window (that feature actually comes in another commit).
I propose we rename this API to something along the lines of `__cpc_protocol_send_holding_queue` and remove the comment above it.
Thanks,
The RX window indicates how many reception buffers the peer has available for that cport. The other peer must not send more messages than that window, or the chances of those messages being lost is very high, leading to retransmissions and poor performance.
The RX window is associated with the ack number, and indicates the valid range of sequence number the other peer can use:
Ack RX window Valid Sequence Range
X 0 None X 1 X X 2 X -> X+1
So everytime an ack is received, the driver evaluates if the valid sequence range has changed and if so pops message from its holding queue.
As the skb is moved to another queue, it cannot be passed directly to the lower layer anymore, instead a clone is passed.
Signed-off-by: Damien Riégel damien.riegel@silabs.com --- drivers/greybus/cpc/cpc.h | 9 ++++ drivers/greybus/cpc/cport.c | 5 ++ drivers/greybus/cpc/header.c | 88 ++++++++++++++++++++++++++++++++++ drivers/greybus/cpc/header.h | 6 +++ drivers/greybus/cpc/host.c | 9 ---- drivers/greybus/cpc/host.h | 1 - drivers/greybus/cpc/protocol.c | 72 +++++++++++++++++++++++++--- 7 files changed, 173 insertions(+), 17 deletions(-)
diff --git a/drivers/greybus/cpc/cpc.h b/drivers/greybus/cpc/cpc.h index d5ad3b0846f..7c1b48f52ff 100644 --- a/drivers/greybus/cpc/cpc.h +++ b/drivers/greybus/cpc/cpc.h @@ -19,6 +19,7 @@ * @cpc_hd: pointer to the CPC host device this cport belongs to * @lock: mutex to synchronize accesses to tcb and other attributes * @holding_queue: list of frames queued to be sent + * @retx_queue: list of frames sent and waiting for acknowledgment * @tcb: Transmission Control Block */ struct cpc_cport { @@ -28,12 +29,20 @@ struct cpc_cport { struct mutex lock; /* Synchronize access to state variables */
struct sk_buff_head holding_queue; + struct sk_buff_head retx_queue;
/* + * @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 */ struct { + u8 send_wnd; + u8 send_nxt; + u8 send_una; u8 ack; u8 seq; } tcb; diff --git a/drivers/greybus/cpc/cport.c b/drivers/greybus/cpc/cport.c index f850da7acfb..2f37bd035b6 100644 --- a/drivers/greybus/cpc/cport.c +++ b/drivers/greybus/cpc/cport.c @@ -16,6 +16,9 @@ static void cpc_cport_tcb_reset(struct cpc_cport *cport) { cport->tcb.ack = 0; cport->tcb.seq = 0; + cport->tcb.send_nxt = 0; + cport->tcb.send_una = 0; + cport->tcb.send_wnd = 1; }
/** @@ -38,12 +41,14 @@ struct cpc_cport *cpc_cport_alloc(u16 cport_id, gfp_t gfp_mask)
mutex_init(&cport->lock); skb_queue_head_init(&cport->holding_queue); + skb_queue_head_init(&cport->retx_queue);
return cport; }
void cpc_cport_release(struct cpc_cport *cport) { + skb_queue_purge(&cport->retx_queue); skb_queue_purge(&cport->holding_queue); kfree(cport); } diff --git a/drivers/greybus/cpc/header.c b/drivers/greybus/cpc/header.c index 973883c1d5c..650fbaacc8c 100644 --- a/drivers/greybus/cpc/header.c +++ b/drivers/greybus/cpc/header.c @@ -22,6 +22,17 @@ bool cpc_header_is_control(const struct cpc_header *hdr) return hdr->ctrl_flags & CPC_HEADER_CONTROL_IS_CONTROL_MASK; }
+/** + * 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. @@ -33,6 +44,17 @@ 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. @@ -56,3 +78,69 @@ u8 cpc_header_encode_ctrl_flags(bool control, bool req_ack) return FIELD_PREP(CPC_HEADER_CONTROL_IS_CONTROL_MASK, control) | FIELD_PREP(CPC_HEADER_CONTROL_REQ_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_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 index 5ac431b3c54..79635077511 100644 --- a/drivers/greybus/cpc/header.h +++ b/drivers/greybus/cpc/header.h @@ -41,8 +41,14 @@ struct cpc_header { #define CPC_HEADER_SIZE (sizeof(struct cpc_header))
bool cpc_header_is_control(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); u8 cpc_header_encode_ctrl_flags(bool control, bool req_ack);
+u8 cpc_header_get_frames_acked_count(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 index 27f02120266..0f9aa394690 100644 --- a/drivers/greybus/cpc/host.c +++ b/drivers/greybus/cpc/host.c @@ -200,15 +200,6 @@ void cpc_hd_del(struct cpc_host_device *cpc_hd) } EXPORT_SYMBOL_GPL(cpc_hd_del);
-void cpc_hd_message_sent(struct sk_buff *skb, int status) -{ - struct cpc_host_device *cpc_hd = CPC_SKB_CB(skb)->cport->cpc_hd; - struct gb_host_device *hd = cpc_hd->gb_hd; - - greybus_message_sent(hd, CPC_SKB_CB(skb)->gb_message, status); -} -EXPORT_SYMBOL_GPL(cpc_hd_message_sent); - void cpc_hd_rcvd(struct cpc_host_device *cpc_hd, struct sk_buff *skb) { struct gb_operation_msg_hdr *gb_hdr; diff --git a/drivers/greybus/cpc/host.h b/drivers/greybus/cpc/host.h index a3d6ddb648e..07bb4eb5fb8 100644 --- a/drivers/greybus/cpc/host.h +++ b/drivers/greybus/cpc/host.h @@ -46,7 +46,6 @@ int cpc_hd_add(struct cpc_host_device *cpc_hd); void cpc_hd_put(struct cpc_host_device *cpc_hd); void cpc_hd_del(struct cpc_host_device *cpc_hd); void cpc_hd_rcvd(struct cpc_host_device *cpc_hd, struct sk_buff *skb); -void cpc_hd_message_sent(struct sk_buff *skb, int status);
int cpc_hd_send_skb(struct cpc_host_device *cpc_hd, struct sk_buff *skb);
diff --git a/drivers/greybus/cpc/protocol.c b/drivers/greybus/cpc/protocol.c index 272eafd79b0..13c5b921b50 100644 --- a/drivers/greybus/cpc/protocol.c +++ b/drivers/greybus/cpc/protocol.c @@ -15,7 +15,7 @@ static bool cpc_skb_is_sequenced(struct sk_buff *skb) return CPC_SKB_CB(skb)->cpc_flags & CPC_SKB_FLAG_REQ_ACK; }
-static void cpc_protocol_prepare_header(struct sk_buff *skb, u8 ack) +static void cpc_protocol_prepare_header(struct sk_buff *skb, u8 ack, u8 recv_window) { struct cpc_header *hdr;
@@ -23,7 +23,7 @@ static void cpc_protocol_prepare_header(struct sk_buff *skb, u8 ack)
hdr = (struct cpc_header *)skb->data; hdr->ack = ack; - hdr->recv_wnd = 0; + hdr->recv_wnd = recv_window; hdr->seq = CPC_SKB_CB(skb)->seq; hdr->ctrl_flags = cpc_header_encode_ctrl_flags(!CPC_SKB_CB(skb)->gb_message, cpc_skb_is_sequenced(skb)); @@ -47,11 +47,46 @@ static void cpc_protocol_queue_ack(struct cpc_cport *cport, u8 ack) gb_hdr->size = sizeof(*gb_hdr); cpc_cport_pack(gb_hdr, cport->id);
- cpc_protocol_prepare_header(skb, ack); + cpc_protocol_prepare_header(skb, ack, CPC_HEADER_MAX_RX_WINDOW);
cpc_hd_send_skb(cport->cpc_hd, skb); }
+static void __cpc_protocol_receive_ack(struct cpc_cport *cport, u8 recv_wnd, u8 ack) +{ + struct gb_host_device *gb_hd = cport->cpc_hd->gb_hd; + struct sk_buff *skb; + u8 acked_frames; + + cport->tcb.send_wnd = recv_wnd; + + skb = skb_peek(&cport->retx_queue); + if (!skb) + return; + + /* Return if no frame to ACK. */ + if (!cpc_header_number_in_range(cport->tcb.send_una, cport->tcb.send_nxt, ack)) + return; + + /* Calculate how many frames will be ACK'd. */ + acked_frames = cpc_header_get_frames_acked_count(CPC_SKB_CB(skb)->seq, ack); + + for (u8 i = 0; i < acked_frames; i++) { + skb = skb_dequeue(&cport->retx_queue); + if (!skb) { + dev_err_ratelimited(cpc_hd_dev(cport->cpc_hd), "pending ack queue shorter than expected"); + break; + } + + if (CPC_SKB_CB(skb)->gb_message) + greybus_message_sent(gb_hd, CPC_SKB_CB(skb)->gb_message, 0); + + kfree_skb(skb); + + cport->tcb.send_una++; + } +} + void cpc_protocol_on_data(struct cpc_cport *cport, struct sk_buff *skb) { struct cpc_header *cpc_hdr = (struct cpc_header *)skb->data; @@ -62,6 +97,10 @@ void cpc_protocol_on_data(struct cpc_cport *cport, struct sk_buff *skb)
mutex_lock(&cport->lock);
+ __cpc_protocol_receive_ack(cport, + cpc_header_get_recv_wnd(cpc_hdr), + cpc_header_get_ack(cpc_hdr)); + if (require_ack) { expected_seq = seq == cport->tcb.ack; if (expected_seq) @@ -74,6 +113,8 @@ void cpc_protocol_on_data(struct cpc_cport *cport, struct sk_buff *skb)
ack = cport->tcb.ack;
+ __cpc_protocol_write_head(cport); + mutex_unlock(&cport->lock);
/* Ack no matter if the sequence was valid or not, to resync with remote */ @@ -87,9 +128,10 @@ void cpc_protocol_on_data(struct cpc_cport *cport, struct sk_buff *skb) } }
-static void __cpc_protocol_write_skb(struct cpc_cport *cport, struct sk_buff *skb, u8 ack) +static void __cpc_protocol_write_skb(struct cpc_cport *cport, struct sk_buff *skb, + u8 ack, u8 recv_window) { - cpc_protocol_prepare_header(skb, ack); + cpc_protocol_prepare_header(skb, ack, recv_window);
cpc_hd_send_skb(cport->cpc_hd, skb); } @@ -98,14 +140,30 @@ static void __cpc_protocol_write_skb(struct cpc_cport *cport, struct sk_buff *sk void __cpc_protocol_write_head(struct cpc_cport *cport) { struct sk_buff *skb; - u8 ack; + u8 ack, send_una, send_wnd;
ack = cport->tcb.ack; + send_una = cport->tcb.send_una; + send_wnd = cport->tcb.send_wnd;
/* For each SKB in the holding queue, clone it and pass it to lower layer */ while ((skb = skb_peek(&cport->holding_queue))) { + struct sk_buff *out_skb; + + /* Skip this skb if it must be acked but the remote has no room for it. */ + if (!cpc_header_number_in_window(send_una, send_wnd, CPC_SKB_CB(skb)->seq)) + break; + + /* Clone and send out the skb */ + out_skb = skb_clone(skb, GFP_KERNEL); + if (!out_skb) + return; + skb_unlink(skb, &cport->holding_queue);
- __cpc_protocol_write_skb(cport, skb, ack); + __cpc_protocol_write_skb(cport, out_skb, ack, CPC_HEADER_MAX_RX_WINDOW); + + cport->tcb.send_nxt++; + skb_queue_tail(&cport->retx_queue, skb); } }
On Fri Nov 14, 2025 at 10:07 AM EST, Damien Riégel wrote:
+/**
- 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;
+}
There is no need to check whether `ack > seq` since the return value in downcasted to a `u8`. For example, if `ack=0` and `seq=254`, we can simply do `(u8)(0-254)=2`.
+static void __cpc_protocol_receive_ack(struct cpc_cport *cport, u8 recv_wnd, u8 ack) +{
- struct gb_host_device *gb_hd = cport->cpc_hd->gb_hd;
- struct sk_buff *skb;
- u8 acked_frames;
- cport->tcb.send_wnd = recv_wnd;
Little bit of a nitpick, but handling the RX window update in `__cpc_protocol_receive_ack` seems a bit misleading to me, in the sense that the ACK itself is not directly related to the window.
I would either rename the function to indicate that we are handling all the CPort's metadata received from the CPC header, or I would move the RX window update to the caller.
Thanks,
This lets the CPC host device drivers dequeue frames when it's convenient for them to do so, instead of forcing each to them to implement a queue to store pending skbs.
The callback is changed from `transmit` to `wake_tx` and let CPC core notify these drivers when there is something to transmit.
Signed-off-by: Damien Riégel damien.riegel@silabs.com --- drivers/greybus/cpc/host.c | 49 +++++++++++++++++++++++++++++++++++--- drivers/greybus/cpc/host.h | 10 ++++++-- 2 files changed, 54 insertions(+), 5 deletions(-)
diff --git a/drivers/greybus/cpc/host.c b/drivers/greybus/cpc/host.c index 0f9aa394690..7ae5bb0666f 100644 --- a/drivers/greybus/cpc/host.c +++ b/drivers/greybus/cpc/host.c @@ -156,6 +156,7 @@ static struct gb_hd_driver cpc_gb_driver = { static void cpc_hd_init(struct cpc_host_device *cpc_hd) { mutex_init(&cpc_hd->lock); + skb_queue_head_init(&cpc_hd->tx_queue); }
struct cpc_host_device *cpc_hd_create(struct cpc_hd_driver *driver, struct device *parent) @@ -163,7 +164,7 @@ struct cpc_host_device *cpc_hd_create(struct cpc_hd_driver *driver, struct devic struct cpc_host_device *cpc_hd; struct gb_host_device *hd;
- if (!driver->transmit) { + if (!driver->wake_tx) { dev_err(parent, "missing mandatory callback\n"); return ERR_PTR(-EINVAL); } @@ -232,13 +233,55 @@ EXPORT_SYMBOL_GPL(cpc_hd_rcvd); * @cpc_hd: Host device to send SKB over. * @skb: SKB to send. */ -int cpc_hd_send_skb(struct cpc_host_device *cpc_hd, struct sk_buff *skb) +void cpc_hd_send_skb(struct cpc_host_device *cpc_hd, struct sk_buff *skb) { const struct cpc_hd_driver *drv = cpc_hd->driver;
- return drv->transmit(cpc_hd, skb); + mutex_lock(&cpc_hd->lock); + skb_queue_tail(&cpc_hd->tx_queue, skb); + mutex_unlock(&cpc_hd->lock); + + drv->wake_tx(cpc_hd); }
+/** + * cpc_hd_tx_queue_empty() - Check if transmit queue is empty. + * @cpc_hd: CPC Host Device. + * + * 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 = skb_queue_empty(&cpc_hd->tx_queue); + mutex_unlock(&cpc_hd->lock); + + return empty; +} +EXPORT_SYMBOL_GPL(cpc_hd_tx_queue_empty); + +/** + * cpc_hd_dequeue() - Get the next SKB that was queued for transmission. + * @cpc_hd: CPC Host Device. + * + * Get an SKB that was previously queued by cpc_hd_send_skb(). + * + * Return: An SKB, or %NULL if queue was empty. + */ +struct sk_buff *cpc_hd_dequeue(struct cpc_host_device *cpc_hd) +{ + struct sk_buff *skb; + + mutex_lock(&cpc_hd->lock); + skb = skb_dequeue(&cpc_hd->tx_queue); + mutex_unlock(&cpc_hd->lock); + + return skb; +} +EXPORT_SYMBOL_GPL(cpc_hd_dequeue); + MODULE_DESCRIPTION("Greybus over CPC"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Silicon Laboratories, Inc."); diff --git a/drivers/greybus/cpc/host.h b/drivers/greybus/cpc/host.h index 07bb4eb5fb8..2c47e167ac1 100644 --- a/drivers/greybus/cpc/host.h +++ b/drivers/greybus/cpc/host.h @@ -9,6 +9,7 @@ #include <linux/device.h> #include <linux/greybus.h> #include <linux/mutex.h> +#include <linux/skbuff.h> #include <linux/types.h>
#define GB_CPC_MSG_SIZE_MAX 2048 @@ -18,7 +19,7 @@ struct cpc_cport; struct cpc_host_device;
struct cpc_hd_driver { - int (*transmit)(struct cpc_host_device *hd, struct sk_buff *skb); + int (*wake_tx)(struct cpc_host_device *cpc_hd); };
/** @@ -33,6 +34,8 @@ struct cpc_host_device { const struct cpc_hd_driver *driver;
struct mutex lock; /* Synchronize access to cports */ + struct sk_buff_head tx_queue; + struct cpc_cport *cports[GB_CPC_NUM_CPORTS]; };
@@ -47,6 +50,9 @@ void cpc_hd_put(struct cpc_host_device *cpc_hd); void cpc_hd_del(struct cpc_host_device *cpc_hd); void cpc_hd_rcvd(struct cpc_host_device *cpc_hd, struct sk_buff *skb);
-int cpc_hd_send_skb(struct cpc_host_device *cpc_hd, struct sk_buff *skb); +void cpc_hd_send_skb(struct cpc_host_device *cpc_hd, struct sk_buff *skb); + +bool cpc_hd_tx_queue_empty(struct cpc_host_device *cpc_hd); +struct sk_buff *cpc_hd_dequeue(struct cpc_host_device *cpc_hd);
#endif