[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250512012748.79749-9-damien.riegel@silabs.com>
Date: Sun, 11 May 2025 21:27:41 -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 08/15] net: cpc: add support for connecting endpoints
Endpoints must be connected before transmitting. This is achieved using
a three-way handshake in which each peer advertises the maximum payload
size it can receive. Once again, this constraint comes from the remote
microcontroller, which can have tight limit on the payload size it can
handle.
Signed-off-by: Damien Riégel <damien.riegel@...abs.com>
---
drivers/net/cpc/cpc.h | 12 +++++
drivers/net/cpc/endpoint.c | 75 ++++++++++++++++++++++++++++
drivers/net/cpc/interface.c | 29 +++++++++++
drivers/net/cpc/interface.h | 3 ++
drivers/net/cpc/protocol.c | 97 ++++++++++++++++++++++++++++++++++++-
drivers/net/cpc/protocol.h | 4 ++
6 files changed, 219 insertions(+), 1 deletion(-)
diff --git a/drivers/net/cpc/cpc.h b/drivers/net/cpc/cpc.h
index 94284e2d59d..d316fce4ad7 100644
--- a/drivers/net/cpc/cpc.h
+++ b/drivers/net/cpc/cpc.h
@@ -18,6 +18,11 @@ struct cpc_endpoint;
extern const struct bus_type cpc_bus;
+/* CPC endpoint flags */
+enum {
+ CPC_ENDPOINT_UP, /* Connection is established with remote counterpart. */
+};
+
/**
* struct cpc_endpoint_tcb - endpoint's transmission control block
* @lock: synchronize tcb access
@@ -37,6 +42,7 @@ struct cpc_endpoint_tcb {
u8 send_una;
u8 ack;
u8 seq;
+ u16 mtu;
};
/** struct cpc_endpoint_ops - Endpoint's callbacks.
@@ -54,6 +60,8 @@ struct cpc_endpoint_ops {
* @intf: Pointer to CPC device this endpoint belongs to.
* @list_node: list_head member for linking in a CPC device.
* @tcb: Transmission control block.
+ * @conn: Completion structure for connection.
+ * @flags: Endpoint state flags.
* @pending_ack_queue: Contain frames pending on an acknowledge.
* @holding_queue: Contains frames that were not pushed to the transport layer
* due to having insufficient space in the transmit window.
@@ -72,6 +80,8 @@ struct cpc_endpoint {
struct cpc_endpoint_ops *ops;
struct cpc_endpoint_tcb tcb;
+ struct completion conn;
+ unsigned long flags;
struct sk_buff_head pending_ack_queue;
struct sk_buff_head holding_queue;
@@ -83,6 +93,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_connect(struct cpc_endpoint *ep);
+void cpc_endpoint_disconnect(struct cpc_endpoint *ep);
int cpc_endpoint_write(struct cpc_endpoint *ep, struct sk_buff *skb);
void cpc_endpoint_set_ops(struct cpc_endpoint *ep, struct cpc_endpoint_ops *ops);
diff --git a/drivers/net/cpc/endpoint.c b/drivers/net/cpc/endpoint.c
index db925cc078d..e6b2793d842 100644
--- a/drivers/net/cpc/endpoint.c
+++ b/drivers/net/cpc/endpoint.c
@@ -35,6 +35,7 @@ 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;
@@ -72,6 +73,7 @@ struct cpc_endpoint *cpc_endpoint_alloc(struct cpc_interface *intf, u8 id)
mutex_init(&ep->tcb.lock);
cpc_endpoint_tcb_reset(ep);
+ init_completion(&ep->conn);
skb_queue_head_init(&ep->pending_ack_queue);
skb_queue_head_init(&ep->holding_queue);
@@ -197,10 +199,77 @@ void cpc_endpoint_unregister(struct cpc_endpoint *ep)
*/
void cpc_endpoint_set_ops(struct cpc_endpoint *ep, struct cpc_endpoint_ops *ops)
{
+ if (test_bit(CPC_ENDPOINT_UP, &ep->flags))
+ return;
+
if (ep)
ep->ops = ops;
}
+/**
+ * cpc_endpoint_connect - Connect to the remote endpoint.
+ * @ep: Endpoint handle.
+ *
+ * @return: 0 on success, otherwise a negative error code.
+ */
+int cpc_endpoint_connect(struct cpc_endpoint *ep)
+{
+ unsigned long timeout = msecs_to_jiffies(2000);
+ int err;
+
+ if (!ep->ops || !ep->ops->rx)
+ return -EINVAL;
+
+ if (test_bit(CPC_ENDPOINT_UP, &ep->flags))
+ return 0;
+
+ cpc_interface_add_rx_endpoint(ep);
+
+ mutex_lock(&ep->tcb.lock);
+ skb_queue_purge(&ep->pending_ack_queue);
+ skb_queue_purge(&ep->holding_queue);
+ cpc_endpoint_tcb_reset(ep);
+ mutex_unlock(&ep->tcb.lock);
+
+ err = cpc_protocol_send_syn(ep);
+ if (err)
+ goto remove_from_ep_list;
+
+ timeout = wait_for_completion_timeout(&ep->conn, timeout);
+ if (timeout == 0) {
+ err = -ETIMEDOUT;
+ mutex_lock(&ep->tcb.lock);
+ skb_queue_purge(&ep->pending_ack_queue);
+ mutex_unlock(&ep->tcb.lock);
+
+ goto remove_from_ep_list;
+ }
+
+ return 0;
+
+remove_from_ep_list:
+ cpc_interface_remove_rx_endpoint(ep);
+
+ return err;
+}
+
+/**
+ * cpc_endpoint_disconnect - Disconnect endpoint from remote.
+ * @ep: Endpoint handle.
+ *
+ * Close the connection with the remote device. When that function returns, no more packets will be
+ * received from the remote.
+ *
+ * Context: Must be called from process context, endpoint's interface lock is held.
+ */
+void cpc_endpoint_disconnect(struct cpc_endpoint *ep)
+{
+ if (!test_and_clear_bit(CPC_ENDPOINT_UP, &ep->flags))
+ return;
+
+ cpc_interface_remove_rx_endpoint(ep);
+}
+
/**
* cpc_endpoint_write - Write a DATA frame.
* @ep: Endpoint handle.
@@ -215,6 +284,11 @@ int cpc_endpoint_write(struct cpc_endpoint *ep, struct sk_buff *skb)
mutex_lock(&ep->tcb.lock);
+ if (skb->len > ep->tcb.mtu) {
+ err = -EINVAL;
+ goto out;
+ }
+
if (ep->intf->ops->csum)
ep->intf->ops->csum(skb);
@@ -227,6 +301,7 @@ int cpc_endpoint_write(struct cpc_endpoint *ep, struct sk_buff *skb)
err = __cpc_protocol_write(ep, &hdr, skb);
+out:
mutex_unlock(&ep->tcb.lock);
return err;
diff --git a/drivers/net/cpc/interface.c b/drivers/net/cpc/interface.c
index edc6b387e50..d6b04588a61 100644
--- a/drivers/net/cpc/interface.c
+++ b/drivers/net/cpc/interface.c
@@ -36,6 +36,9 @@ static void cpc_interface_rx_work(struct work_struct *work)
case CPC_FRAME_TYPE_DATA:
cpc_protocol_on_data(ep, skb);
break;
+ case CPC_FRAME_TYPE_SYN:
+ cpc_protocol_on_syn(ep, skb);
+ break;
default:
kfree_skb(skb);
}
@@ -203,6 +206,32 @@ struct cpc_endpoint *cpc_interface_get_endpoint(struct cpc_interface *intf, u8 e
return ep;
}
+/**
+ * cpc_interface_add_rx_endpoint() - Set an endpoint as being available for receiving frames.
+ * @ep: Endpoint.
+ */
+void cpc_interface_add_rx_endpoint(struct cpc_endpoint *ep)
+{
+ struct cpc_interface *intf = ep->intf;
+
+ mutex_lock(&intf->lock);
+ list_add_tail(&ep->list_node, &intf->eps);
+ mutex_unlock(&intf->lock);
+}
+
+/**
+ * cpc_interface_remove_rx_endpoint() - Unet an endpoint as being available for receiving frames.
+ * @ep: Endpoint.
+ */
+void cpc_interface_remove_rx_endpoint(struct cpc_endpoint *ep)
+{
+ struct cpc_interface *intf = ep->intf;
+
+ mutex_lock(&intf->lock);
+ list_del(&ep->list_node);
+ mutex_unlock(&intf->lock);
+}
+
/**
* cpc_interface_receive_frame - queue a received frame for processing
* @intf: pointer to the CPC device
diff --git a/drivers/net/cpc/interface.h b/drivers/net/cpc/interface.h
index a45227a50a7..f7f46053fad 100644
--- a/drivers/net/cpc/interface.h
+++ b/drivers/net/cpc/interface.h
@@ -68,6 +68,9 @@ 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_add_rx_endpoint(struct cpc_endpoint *ep);
+void cpc_interface_remove_rx_endpoint(struct cpc_endpoint *ep);
+
void cpc_interface_receive_frame(struct cpc_interface *intf, struct sk_buff *skb);
void cpc_interface_send_frame(struct cpc_interface *intf, struct sk_buff *skb);
struct sk_buff *cpc_interface_dequeue(struct cpc_interface *intf);
diff --git a/drivers/net/cpc/protocol.c b/drivers/net/cpc/protocol.c
index 92e3b0a9cdf..db7ac0dc066 100644
--- a/drivers/net/cpc/protocol.c
+++ b/drivers/net/cpc/protocol.c
@@ -11,6 +11,36 @@
#include "interface.h"
#include "protocol.h"
+int cpc_protocol_send_syn(struct cpc_endpoint *ep)
+{
+ struct cpc_header hdr;
+ struct sk_buff *skb;
+ int err;
+
+ skb = cpc_skb_alloc(0, GFP_KERNEL);
+ if (!skb)
+ return -ENOMEM;
+
+ memset(&hdr, 0, sizeof(hdr));
+
+ mutex_lock(&ep->tcb.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);
+
+ err = __cpc_protocol_write(ep, &hdr, skb);
+
+ mutex_unlock(&ep->tcb.lock);
+
+ if (err)
+ kfree_skb(skb);
+
+ return err;
+}
+
static void __cpc_protocol_send_ack(struct cpc_endpoint *ep)
{
struct cpc_header hdr;
@@ -116,6 +146,42 @@ static void __cpc_protocol_receive_ack(struct cpc_endpoint *ep, u8 recv_wnd, u8
__cpc_protocol_process_pending_tx_frames(ep);
}
+static bool __cpc_protocol_is_syn_ack_valid(struct cpc_endpoint *ep, struct sk_buff *skb)
+{
+ enum cpc_frame_type type;
+ struct sk_buff *syn_skb;
+ u8 syn_seq;
+ u8 ack;
+
+ /* Fetch the previously sent frame. */
+ syn_skb = skb_peek(&ep->pending_ack_queue);
+ if (!syn_skb) {
+ dev_warn(&ep->dev, "cannot validate syn-ack, no frame was sent\n");
+ return false;
+ }
+
+ cpc_header_get_type(syn_skb->data, &type);
+
+ /* Verify if this frame is SYN. */
+ if (type != CPC_FRAME_TYPE_SYN) {
+ dev_warn(&ep->dev, "cannot validate syn-ack, no syn frame was sent (%d)\n", type);
+ return false;
+ }
+
+ syn_seq = cpc_header_get_seq(syn_skb->data);
+ ack = cpc_header_get_ack(skb->data);
+
+ /* Validate received ACK with the SEQ used in the initial SYN. */
+ if (!cpc_header_is_syn_ack_valid(syn_seq, ack)) {
+ dev_warn(&ep->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 sk_buff *skb)
{
bool expected_seq;
@@ -149,7 +215,7 @@ void cpc_protocol_on_data(struct cpc_endpoint *ep, struct sk_buff *skb)
/* Strip header. */
skb_pull(skb, CPC_HEADER_SIZE);
- if (ep->ops && ep->ops->rx)
+ if (test_bit(CPC_ENDPOINT_UP, &ep->flags))
ep->ops->rx(ep, skb);
else
kfree_skb(skb);
@@ -158,6 +224,35 @@ void cpc_protocol_on_data(struct cpc_endpoint *ep, struct sk_buff *skb)
}
}
+void cpc_protocol_on_syn(struct cpc_endpoint *ep, struct sk_buff *skb)
+{
+ mutex_lock(&ep->tcb.lock);
+
+ if (!__cpc_protocol_is_syn_ack_valid(ep, skb))
+ goto out;
+
+ __cpc_protocol_receive_ack(ep,
+ cpc_header_get_recv_wnd(skb->data),
+ cpc_header_get_ack(skb->data));
+
+ /* On SYN-ACK, the remote's SEQ becomes our starting ACK. */
+ ep->tcb.ack = cpc_header_get_seq(skb->data);
+ ep->tcb.mtu = cpc_header_get_mtu(skb->data);
+ ep->tcb.ack++;
+
+ complete(&ep->conn);
+
+ __cpc_protocol_send_ack(ep);
+
+ set_bit(CPC_ENDPOINT_UP, &ep->flags);
+ complete(&ep->conn);
+
+out:
+ mutex_unlock(&ep->tcb.lock);
+
+ kfree_skb(skb);
+}
+
/**
* __cpc_protocol_write() - Write a frame.
* @ep: Endpoint handle.
diff --git a/drivers/net/cpc/protocol.h b/drivers/net/cpc/protocol.h
index 9a028e0e94b..e67f0f6d025 100644
--- a/drivers/net/cpc/protocol.h
+++ b/drivers/net/cpc/protocol.h
@@ -13,9 +13,13 @@
struct cpc_endpoint;
struct cpc_header;
+struct cpc_interface;
int __cpc_protocol_write(struct cpc_endpoint *ep, struct cpc_header *hdr, struct sk_buff *skb);
void cpc_protocol_on_data(struct cpc_endpoint *ep, struct sk_buff *skb);
+void cpc_protocol_on_syn(struct cpc_endpoint *ep, struct sk_buff *skb);
+
+int cpc_protocol_send_syn(struct cpc_endpoint *ep);
#endif
--
2.49.0
Powered by blists - more mailing lists