[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250512012748.79749-3-damien.riegel@silabs.com>
Date: Sun, 11 May 2025 21:27:35 -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 02/15] net: cpc: add endpoint infrastructure
Network stacks using CPC are isolated from each other and their
communication channels are called endpoints. Within a CPC interface,
endpoints must have a unique Endpoint ID, which will be used to address
messages to that specific endpoint in a latter changeset.
Endpoints are part of an interface, this is represented in the device
model by endpoints being children of interface, and the interface
ensuring uniqueness of the endpoint ID when a new one is added.
Signed-off-by: Damien Riégel <damien.riegel@...abs.com>
---
drivers/net/cpc/Makefile | 2 +-
drivers/net/cpc/cpc.h | 101 ++++++++++++++++++++++
drivers/net/cpc/endpoint.c | 166 ++++++++++++++++++++++++++++++++++++
drivers/net/cpc/interface.c | 58 +++++++++++++
drivers/net/cpc/interface.h | 11 +++
5 files changed, 337 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/cpc/cpc.h
create mode 100644 drivers/net/cpc/endpoint.c
diff --git a/drivers/net/cpc/Makefile b/drivers/net/cpc/Makefile
index 1ce7415f305..673a40db424 100644
--- a/drivers/net/cpc/Makefile
+++ b/drivers/net/cpc/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
-cpc-y := interface.o main.o
+cpc-y := endpoint.o interface.o main.o
obj-$(CONFIG_CPC) += cpc.o
diff --git a/drivers/net/cpc/cpc.h b/drivers/net/cpc/cpc.h
new file mode 100644
index 00000000000..529319f4339
--- /dev/null
+++ b/drivers/net/cpc/cpc.h
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025, Silicon Laboratories, Inc.
+ */
+
+#ifndef __CPC_H
+#define __CPC_H
+
+#include <linux/device.h>
+#include <linux/types.h>
+
+#define CPC_ENDPOINT_NAME_MAX_LEN 128
+
+struct cpc_driver;
+struct cpc_interface;
+struct cpc_endpoint;
+
+/**
+ * struct cpc_endpoint - Representation of CPC endpointl
+ * @dev: Driver model representation of the device.
+ * @name: Endpoint name, used for matching with corresponding driver.
+ * @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.
+ *
+ * Each endpoint can send and receive data without consideration of the other endpoints sharing the
+ * same physical link.
+ */
+struct cpc_endpoint {
+ struct device dev;
+
+ char name[CPC_ENDPOINT_NAME_MAX_LEN];
+ u8 id;
+
+ struct cpc_interface *intf;
+ struct list_head list_node;
+};
+
+struct cpc_endpoint *cpc_endpoint_alloc(struct cpc_interface *intf, u8 id);
+int cpc_endpoint_register(struct cpc_endpoint *ep);
+struct cpc_endpoint *cpc_endpoint_new(struct cpc_interface *intf, u8 id, const char *ep_name);
+
+void cpc_endpoint_unregister(struct cpc_endpoint *ep);
+
+/**
+ * cpc_endpoint_from_dev() - Upcast from a device pointer.
+ * @dev: Reference to a device.
+ *
+ * Return: Reference to the cpc endpoint.
+ */
+static inline struct cpc_endpoint *cpc_endpoint_from_dev(const struct device *dev)
+{
+ return container_of(dev, struct cpc_endpoint, dev);
+}
+
+/**
+ * cpc_endpoint_get() - Get a reference to endpoint and return its pointer.
+ * @ep: Endpoint to get.
+ *
+ * Return: Endpoint pointer with its reference counter incremented, or %NULL.
+ */
+static inline struct cpc_endpoint *cpc_endpoint_get(struct cpc_endpoint *ep)
+{
+ if (!ep || !get_device(&ep->dev))
+ return NULL;
+ return ep;
+}
+
+/**
+ * cpc_endpoint_put() - Release reference to an endpoint.
+ * @ep: CPC endpoint, allocated by cpc_endpoint_alloc().
+ *
+ * Context: Process context.
+ */
+static inline void cpc_endpoint_put(struct cpc_endpoint *ep)
+{
+ if (ep)
+ put_device(&ep->dev);
+}
+
+/**
+ * cpc_endpoint_get_drvdata() - Get driver data associated with this endpoint.
+ * @ep: Endpoint.
+ *
+ * Return: Driver data, set by cpc_endpoint_set_drvdata().
+ */
+static inline void *cpc_endpoint_get_drvdata(struct cpc_endpoint *ep)
+{
+ return dev_get_drvdata(&ep->dev);
+}
+
+/**
+ * cpc_endpoint_set_drvdata() - Set driver data for this endpoint.
+ * @ep: Endpoint.
+ */
+static inline void cpc_endpoint_set_drvdata(struct cpc_endpoint *ep, void *data)
+{
+ dev_set_drvdata(&ep->dev, data);
+}
+
+#endif
diff --git a/drivers/net/cpc/endpoint.c b/drivers/net/cpc/endpoint.c
new file mode 100644
index 00000000000..5aef8d7e43c
--- /dev/null
+++ b/drivers/net/cpc/endpoint.c
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025, Silicon Laboratories, Inc.
+ */
+
+#include <linux/string.h>
+
+#include "cpc.h"
+#include "interface.h"
+
+/**
+ * cpc_ep_release() - Actual release of the CPC endpoint.
+ * @dev: Device embedded in struct cpc_endpoint.
+ *
+ * This function should not be called directly, users are expected to use cpc_endpoint_put().
+ */
+static void cpc_ep_release(struct device *dev)
+{
+ struct cpc_endpoint *ep = cpc_endpoint_from_dev(dev);
+
+ cpc_interface_put(ep->intf);
+ kfree(ep);
+}
+
+/**
+ * cpc_endpoint_alloc() - Allocate memory for new CPC endpoint.
+ * @intf: CPC interface owning this endpoint.
+ * @id: Endpoint ID.
+ *
+ * Context: Process context as allocations are done with @GFP_KERNEL flag
+ *
+ * Return: allocated CPC endpoint or %NULL.
+ */
+struct cpc_endpoint *cpc_endpoint_alloc(struct cpc_interface *intf, u8 id)
+{
+ struct cpc_endpoint *ep;
+
+ if (!cpc_interface_get(intf))
+ return NULL;
+
+ ep = kzalloc(sizeof(*ep), GFP_KERNEL);
+ if (!ep) {
+ cpc_interface_put(intf);
+ return NULL;
+ }
+
+ ep->intf = intf;
+ ep->id = id;
+
+ ep->dev.parent = &intf->dev;
+ ep->dev.release = cpc_ep_release;
+
+ device_initialize(&ep->dev);
+
+ return ep;
+}
+
+static int cpc_ep_check_unique_id(struct device *dev, void *data)
+{
+ struct cpc_endpoint *ep = cpc_endpoint_from_dev(dev);
+ struct cpc_endpoint *new_ep = data;
+
+ if (ep->id == new_ep->id)
+ return -EBUSY;
+
+ return 0;
+}
+
+static int __cpc_endpoint_register(struct cpc_endpoint *ep)
+{
+ size_t name_len;
+ int err;
+
+ name_len = strnlen(ep->name, sizeof(ep->name));
+ if (name_len == 0 || name_len == sizeof(ep->name))
+ return -EINVAL;
+
+ err = dev_set_name(&ep->dev, "%s.%d", dev_name(&ep->intf->dev), ep->id);
+ if (err) {
+ dev_err(&ep->dev, "failed to dev_set_name (%d)\n", err);
+ return err;
+ }
+
+ err = device_for_each_child(&ep->intf->dev, ep, cpc_ep_check_unique_id);
+ if (err)
+ return err;
+
+ err = device_add(&ep->dev);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+/**
+ * cpc_endpoint_register() - Register an endpoint.
+ * @ep: Endpoint to register.
+ *
+ * Companion function of cpc_endpoint_alloc(). This function adds the endpoint, making it usable by
+ * CPC drivers. As this ensures that endpoint ID is unique within a CPC interface and then adds the
+ * endpoint, the lock interface is held to prevent concurrent additions.
+ *
+ * Context: Lock "add_lock" of endpoint's interface.
+ *
+ * Return: 0 on success, negative errno otherwise.
+ */
+int cpc_endpoint_register(struct cpc_endpoint *ep)
+{
+ int err;
+
+ if (!ep || !ep->intf)
+ return -EINVAL;
+
+ mutex_lock(&ep->intf->add_lock);
+ err = __cpc_endpoint_register(ep);
+ mutex_unlock(&ep->intf->add_lock);
+
+ return err;
+}
+
+/**
+ * cpc_endpoint_new() - Convenience wrapper to allocate and register an endpoint.
+ * @intf: The interface the endpoint will be attached to.
+ * @id: ID of the endpoint to add.
+ * @ep_name: Name of the endpoint to add.
+ *
+ * Context: Process context, as allocation are done with GFP_KERNEL and interface's lock is
+ * acquired.
+ *
+ * Return: Newly added endpoint, or %NULL in case of error.
+ */
+struct cpc_endpoint *cpc_endpoint_new(struct cpc_interface *intf, u8 id, const char *ep_name)
+{
+ struct cpc_endpoint *ep;
+ int err;
+
+ ep = cpc_endpoint_alloc(intf, id);
+ if (!ep)
+ return NULL;
+
+ if (ep_name)
+ strscpy(ep->name, ep_name);
+
+ err = cpc_endpoint_register(ep);
+ if (err)
+ goto put_ep;
+
+ return ep;
+
+put_ep:
+ cpc_endpoint_put(ep);
+
+ return NULL;
+}
+
+/** cpc_endpoint_unregister() - Unregister an endpoint.
+ * @ep: Endpoint registered with cpc_endpoint_new() or cpc_endpoint_register().
+ *
+ * Unregister an endpoint, its resource will be freed when the last reference to this
+ * endpoint is dropped.
+ */
+void cpc_endpoint_unregister(struct cpc_endpoint *ep)
+{
+ device_del(&ep->dev);
+ put_device(&ep->dev);
+}
diff --git a/drivers/net/cpc/interface.c b/drivers/net/cpc/interface.c
index 4fdc78a0868..6b3fc16f212 100644
--- a/drivers/net/cpc/interface.c
+++ b/drivers/net/cpc/interface.c
@@ -5,6 +5,7 @@
#include <linux/module.h>
+#include "cpc.h"
#include "interface.h"
#define to_cpc_interface(d) container_of(d, struct cpc_interface, dev)
@@ -53,6 +54,10 @@ struct cpc_interface *cpc_interface_alloc(struct device *parent,
return NULL;
}
+ mutex_init(&intf->add_lock);
+ mutex_init(&intf->lock);
+ INIT_LIST_HEAD(&intf->eps);
+
intf->ops = ops;
intf->dev.parent = parent;
@@ -85,6 +90,12 @@ int cpc_interface_register(struct cpc_interface *intf)
return 0;
}
+static int cpc_intf_unregister_ep(struct device *dev, void *null)
+{
+ cpc_endpoint_unregister(cpc_endpoint_from_dev(dev));
+ return 0;
+}
+
/**
* cpc_interface_unregister() - Unregister a CPC interface.
* @intf: CPC device to unregister.
@@ -93,6 +104,53 @@ int cpc_interface_register(struct cpc_interface *intf)
*/
void cpc_interface_unregister(struct cpc_interface *intf)
{
+ /* Iterate in reverse order so that system endpoint is removed last. */
+ device_for_each_child_reverse(&intf->dev, NULL, cpc_intf_unregister_ep);
+
device_del(&intf->dev);
cpc_interface_put(intf);
}
+
+/**
+ * __cpc_interface_get_endpoint() - get endpoint registered in CPC device with this id without lock
+ * @intf: CPC device to probe
+ * @ep_id: endpoint ID that's being looked for
+ *
+ * Get an endpoint by its ID if present in a CPC device. Endpoint's ref count is incremented and
+ * should be decremented with cpc_endpoint_put() when done.
+ *
+ * Context: This function doesn't lock device's endpoint list, caller is responsible for that.
+ *
+ * Return: a struct cpc_endpoint pointer or NULL if not found.
+ */
+static struct cpc_endpoint *__cpc_interface_get_endpoint(struct cpc_interface *intf, u8 ep_id)
+{
+ struct cpc_endpoint *ep_it;
+
+ list_for_each_entry(ep_it, &intf->eps, list_node) {
+ if (ep_it->id == ep_id)
+ return cpc_endpoint_get(ep_it);
+ }
+
+ return NULL;
+}
+
+/**
+ * cpc_interface_get_endpoint() - get endpoint registered in CPC device with this id
+ * @intf: CPC device to probe
+ * @ep_id: endpoint ID that's being looked for
+ *
+ * Context: This function locks device's endpoint list.
+ *
+ * Return: a struct cpc_endpoint pointer or NULL if not found.
+ */
+struct cpc_endpoint *cpc_interface_get_endpoint(struct cpc_interface *intf, u8 ep_id)
+{
+ struct cpc_endpoint *ep;
+
+ mutex_lock(&intf->lock);
+ ep = __cpc_interface_get_endpoint(intf, ep_id);
+ mutex_unlock(&intf->lock);
+
+ return ep;
+}
diff --git a/drivers/net/cpc/interface.h b/drivers/net/cpc/interface.h
index 797f70119a8..d6b6d9ce5de 100644
--- a/drivers/net/cpc/interface.h
+++ b/drivers/net/cpc/interface.h
@@ -17,15 +17,24 @@ struct cpc_interface_ops;
/**
* struct cpc_interface - Representation of a CPC interface.
* @dev: Device structure for bookkeeping..
+ * @add_lock: Lock to serialize addition of new endpoints.
* @ops: Callbacks for this device.
* @index: Device index.
+ * @lock: Protect access to endpoint list.
+ * @eps: List of endpoints managed by this device.
*/
struct cpc_interface {
struct device dev;
+ /* Prevent concurrent addition of new devices */
+ struct mutex add_lock;
+
const struct cpc_interface_ops *ops;
int index;
+
+ struct mutex lock; /* Protect eps from concurrent access. */
+ struct list_head eps;
};
/**
@@ -47,6 +56,8 @@ struct cpc_interface *cpc_interface_alloc(struct device *parent,
int cpc_interface_register(struct cpc_interface *intf);
void cpc_interface_unregister(struct cpc_interface *intf);
+struct cpc_endpoint *cpc_interface_get_endpoint(struct cpc_interface *intf, u8 ep_id);
+
/**
* cpc_interface_get() - Get a reference to interface and return its pointer.
* @intf: Interface to get.
--
2.49.0
Powered by blists - more mailing lists