[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250512012748.79749-6-damien.riegel@silabs.com>
Date: Sun, 11 May 2025 21:27:38 -0400
From: Damien Riégel <damien.riegel@...abs.com>
To: Andrew Lunn <andrew+netdev@...n.ch>,
"David S . Miller" <davem@...emloft.net>,
Eric Dumazet <edumazet@...gle.com>, Jakub Kicinski <kuba@...nel.org>,
Paolo Abeni <pabeni@...hat.com>, Rob Herring <robh@...nel.org>,
Krzysztof Kozlowski <krzk+dt@...nel.org>,
Conor Dooley <conor+dt@...nel.org>,
Silicon Labs Kernel Team <linux-devel@...abs.com>,
netdev@...r.kernel.org, devicetree@...r.kernel.org,
linux-kernel@...r.kernel.org
Subject: [RFC net-next 05/15] net: cpc: implement basic transmit path
Implement a very basic transmission path for endpoints. Transmitting
involves the following flow:
- user call cpc_endpoint_write() with the SKB they wish to send
- a CPC header is prepared and prepended to the frame
- SKB is pushed to endpoint's holding queue (*)
- SKB is dequeued from endpoint's holding queue and moved to
interface's transmit queue (*)
- interface is woken up by invoking its wake_tx() callback
- interface work task is now responsible for dequeueing and
transmitting that SKB when it's convenient for it to do so.
(*) Endpoint's holding queue currently serves no purpose but it will
when other protocol features are implemented.
Signed-off-by: Damien Riégel <damien.riegel@...abs.com>
---
drivers/net/cpc/Makefile | 2 +-
drivers/net/cpc/cpc.h | 15 +++++++++
drivers/net/cpc/endpoint.c | 33 +++++++++++++++++++
drivers/net/cpc/interface.c | 40 +++++++++++++++++++++++
drivers/net/cpc/interface.h | 7 +++++
drivers/net/cpc/main.c | 50 +++++++++++++++++++++++++++++
drivers/net/cpc/protocol.c | 63 +++++++++++++++++++++++++++++++++++++
drivers/net/cpc/protocol.h | 19 +++++++++++
8 files changed, 228 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/cpc/protocol.c
create mode 100644 drivers/net/cpc/protocol.h
diff --git a/drivers/net/cpc/Makefile b/drivers/net/cpc/Makefile
index 81c470012c1..0e9c3f775dc 100644
--- a/drivers/net/cpc/Makefile
+++ b/drivers/net/cpc/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
-cpc-y := endpoint.o header.o interface.o main.o
+cpc-y := endpoint.o header.o interface.o main.o protocol.o
obj-$(CONFIG_CPC) += cpc.o
diff --git a/drivers/net/cpc/cpc.h b/drivers/net/cpc/cpc.h
index cbd1b3d6a03..2f54e5b660e 100644
--- a/drivers/net/cpc/cpc.h
+++ b/drivers/net/cpc/cpc.h
@@ -7,6 +7,7 @@
#define __CPC_H
#include <linux/device.h>
+#include <linux/skbuff.h>
#include <linux/types.h>
#define CPC_ENDPOINT_NAME_MAX_LEN 128
@@ -24,6 +25,8 @@ extern const struct bus_type cpc_bus;
* @id: Endpoint id, uniquely identifies an endpoint within a CPC device.
* @intf: Pointer to CPC device this endpoint belongs to.
* @list_node: list_head member for linking in a CPC device.
+ * @holding_queue: Contains frames that were not pushed to the transport layer
+ * due to having insufficient space in the transmit window.
*
* Each endpoint can send and receive data without consideration of the other endpoints sharing the
* same physical link.
@@ -36,6 +39,8 @@ struct cpc_endpoint {
struct cpc_interface *intf;
struct list_head list_node;
+
+ struct sk_buff_head holding_queue;
};
struct cpc_endpoint *cpc_endpoint_alloc(struct cpc_interface *intf, u8 id);
@@ -44,6 +49,8 @@ struct cpc_endpoint *cpc_endpoint_new(struct cpc_interface *intf, u8 id, const c
void cpc_endpoint_unregister(struct cpc_endpoint *ep);
+int cpc_endpoint_write(struct cpc_endpoint *ep, struct sk_buff *skb);
+
/**
* cpc_endpoint_from_dev() - Upcast from a device pointer.
* @dev: Reference to a device.
@@ -137,4 +144,12 @@ static inline struct cpc_driver *cpc_driver_from_drv(const struct device_driver
return container_of(drv, struct cpc_driver, driver);
}
+/*---------------------------------------------------------------------------*/
+
+struct sk_buff *cpc_skb_alloc(size_t payload_len, gfp_t priority);
+void cpc_skb_set_ctx(struct sk_buff *skb,
+ void (*destructor)(struct sk_buff *skb),
+ void *ctx);
+void *cpc_skb_get_ctx(struct sk_buff *skb);
+
#endif
diff --git a/drivers/net/cpc/endpoint.c b/drivers/net/cpc/endpoint.c
index 98e49614320..4e98955be30 100644
--- a/drivers/net/cpc/endpoint.c
+++ b/drivers/net/cpc/endpoint.c
@@ -6,7 +6,9 @@
#include <linux/string.h>
#include "cpc.h"
+#include "header.h"
#include "interface.h"
+#include "protocol.h"
/**
* cpc_ep_release() - Actual release of the CPC endpoint.
@@ -18,6 +20,8 @@ static void cpc_ep_release(struct device *dev)
{
struct cpc_endpoint *ep = cpc_endpoint_from_dev(dev);
+ skb_queue_purge(&ep->holding_queue);
+
cpc_interface_put(ep->intf);
kfree(ep);
}
@@ -51,6 +55,8 @@ struct cpc_endpoint *cpc_endpoint_alloc(struct cpc_interface *intf, u8 id)
ep->dev.bus = &cpc_bus;
ep->dev.release = cpc_ep_release;
+ skb_queue_head_init(&ep->holding_queue);
+
device_initialize(&ep->dev);
return ep;
@@ -165,3 +171,30 @@ void cpc_endpoint_unregister(struct cpc_endpoint *ep)
device_del(&ep->dev);
put_device(&ep->dev);
}
+
+/**
+ * cpc_endpoint_write - Write a DATA frame.
+ * @ep: Endpoint handle.
+ * @skb: Frame to send.
+ *
+ * @return: 0 on success, otherwise a negative error code.
+ */
+int cpc_endpoint_write(struct cpc_endpoint *ep, struct sk_buff *skb)
+{
+ struct cpc_header hdr;
+ int err;
+
+ if (ep->intf->ops->csum)
+ ep->intf->ops->csum(skb);
+
+ 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 = 0;
+ hdr.dat.payload_len = skb->len;
+
+ err = __cpc_protocol_write(ep, &hdr, skb);
+
+ return err;
+}
diff --git a/drivers/net/cpc/interface.c b/drivers/net/cpc/interface.c
index 6b3fc16f212..1dd87deed59 100644
--- a/drivers/net/cpc/interface.c
+++ b/drivers/net/cpc/interface.c
@@ -58,6 +58,8 @@ struct cpc_interface *cpc_interface_alloc(struct device *parent,
mutex_init(&intf->lock);
INIT_LIST_HEAD(&intf->eps);
+ skb_queue_head_init(&intf->tx_queue);
+
intf->ops = ops;
intf->dev.parent = parent;
@@ -154,3 +156,41 @@ struct cpc_endpoint *cpc_interface_get_endpoint(struct cpc_interface *intf, u8 e
return ep;
}
+
+/**
+ * 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_interface_send_frame(struct cpc_interface *intf, struct sk_buff *skb)
+{
+ skb_queue_tail(&intf->tx_queue, skb);
+ intf->ops->wake_tx(intf);
+}
+
+/**
+ * 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 sk_buff *cpc_interface_dequeue(struct cpc_interface *intf)
+{
+ return skb_dequeue(&intf->tx_queue);
+}
+
+/**
+ * cpc_interface_tx_queue_empty() - Check if transmit queue is empty.
+ * @intf: Interface.
+ *
+ * Return: True if transmit queue is empty, false otherwise.
+ */
+bool cpc_interface_tx_queue_empty(struct cpc_interface *intf)
+{
+ return skb_queue_empty_lockless(&intf->tx_queue);
+}
diff --git a/drivers/net/cpc/interface.h b/drivers/net/cpc/interface.h
index d6b6d9ce5de..1b501b1f6dc 100644
--- a/drivers/net/cpc/interface.h
+++ b/drivers/net/cpc/interface.h
@@ -22,6 +22,7 @@ struct cpc_interface_ops;
* @index: Device index.
* @lock: Protect access to endpoint list.
* @eps: List of endpoints managed by this device.
+ * @tx_queue: Transmit queue to be consumed by the interface.
*/
struct cpc_interface {
struct device dev;
@@ -35,6 +36,8 @@ struct cpc_interface {
struct mutex lock; /* Protect eps from concurrent access. */
struct list_head eps;
+
+ struct sk_buff_head tx_queue;
};
/**
@@ -58,6 +61,10 @@ void cpc_interface_unregister(struct cpc_interface *intf);
struct cpc_endpoint *cpc_interface_get_endpoint(struct cpc_interface *intf, u8 ep_id);
+void cpc_interface_send_frame(struct cpc_interface *intf, struct sk_buff *skb);
+struct sk_buff *cpc_interface_dequeue(struct cpc_interface *intf);
+bool cpc_interface_tx_queue_empty(struct cpc_interface *intf);
+
/**
* cpc_interface_get() - Get a reference to interface and return its pointer.
* @intf: Interface to get.
diff --git a/drivers/net/cpc/main.c b/drivers/net/cpc/main.c
index dcbe6dcb651..8feb0613252 100644
--- a/drivers/net/cpc/main.c
+++ b/drivers/net/cpc/main.c
@@ -7,6 +7,56 @@
#include <linux/module.h>
#include "cpc.h"
+#include "header.h"
+
+/**
+ * cpc_skb_alloc() - Allocate an skb with a specific headroom for CPC headers.
+ * @payload_len: Length of the payload.
+ * @priority: GFP priority to use for memory allocation.
+ *
+ * Return: Pointer to the skb on success, otherwise NULL.
+ */
+struct sk_buff *cpc_skb_alloc(size_t payload_len, gfp_t priority)
+{
+ struct sk_buff *skb;
+
+ skb = alloc_skb(payload_len + CPC_HEADER_SIZE, priority);
+ if (skb)
+ skb_reserve(skb, CPC_HEADER_SIZE);
+
+ return skb;
+}
+
+/**
+ * cpc_skb_set_ctx() - Set the skb context.
+ * @skb: Frame.
+ * @destructor: Destructor callback.
+ * @ctx: Context pointer, might be NULL.
+ */
+void cpc_skb_set_ctx(struct sk_buff *skb,
+ void (*destructor)(struct sk_buff *skb),
+ void *ctx)
+{
+ skb->destructor = destructor;
+
+ if (ctx)
+ memcpy(&skb->cb[0], &ctx, sizeof(void *));
+}
+
+/**
+ * cpc_skb_get_ctx() - Get the skb context.
+ * @skb: Frame.
+ *
+ * Return: Context pointer.
+ */
+void *cpc_skb_get_ctx(struct sk_buff *skb)
+{
+ void *ctx;
+
+ memcpy(&ctx, &skb->cb[0], sizeof(void *));
+
+ return ctx;
+}
static int cpc_bus_match(struct device *dev, const struct device_driver *driver)
{
diff --git a/drivers/net/cpc/protocol.c b/drivers/net/cpc/protocol.c
new file mode 100644
index 00000000000..692d3e07939
--- /dev/null
+++ b/drivers/net/cpc/protocol.c
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025, Silicon Laboratories, Inc.
+ */
+
+#include <linux/mutex.h>
+#include <linux/skbuff.h>
+
+#include "cpc.h"
+#include "header.h"
+#include "interface.h"
+#include "protocol.h"
+
+static int __cpc_protocol_queue_tx_frame(struct cpc_endpoint *ep, struct sk_buff *skb)
+{
+ struct cpc_interface *intf = ep->intf;
+ struct sk_buff *cloned_skb;
+
+ cloned_skb = skb_clone(skb, GFP_KERNEL);
+ if (!cloned_skb)
+ return -ENOMEM;
+
+ cpc_interface_send_frame(intf, cloned_skb);
+
+ return 0;
+}
+
+static void __cpc_protocol_process_pending_tx_frames(struct cpc_endpoint *ep)
+{
+ struct sk_buff *skb;
+ int err;
+
+ while ((skb = skb_dequeue(&ep->holding_queue))) {
+ err = __cpc_protocol_queue_tx_frame(ep, skb);
+ if (err < 0) {
+ skb_queue_head(&ep->holding_queue, skb);
+ return;
+ }
+ }
+}
+
+/**
+ * __cpc_protocol_write() - Write a frame.
+ * @ep: Endpoint handle.
+ * @hdr: Header to write.
+ * @skb: Payload 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_header *hdr,
+ struct sk_buff *skb)
+{
+ memcpy(skb_push(skb, sizeof(*hdr)), hdr, sizeof(*hdr));
+
+ skb_queue_tail(&ep->holding_queue, skb);
+
+ __cpc_protocol_process_pending_tx_frames(ep);
+
+ return 0;
+}
diff --git a/drivers/net/cpc/protocol.h b/drivers/net/cpc/protocol.h
new file mode 100644
index 00000000000..b51f0191be4
--- /dev/null
+++ b/drivers/net/cpc/protocol.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025, Silicon Laboratories, Inc.
+ */
+
+#ifndef CPC_PROTOCOL_H
+#define CPC_PROTOCOL_H
+
+#include <linux/skbuff.h>
+#include <linux/timer.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+struct cpc_endpoint;
+struct cpc_header;
+
+int __cpc_protocol_write(struct cpc_endpoint *ep, struct cpc_header *hdr, struct sk_buff *skb);
+
+#endif
--
2.49.0
Powered by blists - more mailing lists