This commit prepares the addition of a CPC driver. CPC, standing for
Co-Processor Communication, enables users to have multiple stack
protocols over a shared physical link using multiple endpoints.
This patch adds the basic infrastructure for the new module, and
introduces a new structure `cpc_interface`. The goal of this structure
is to abstract a physical link like an SPI device, a SDIO function, or a
UART for instance.
Signed-off-by: Damien Riégel <damien.riegel@silabs.com>
---
MAINTAINERS | 6 +++
drivers/net/Kconfig | 2 +
drivers/net/Makefile | 1 +
drivers/net/cpc/Kconfig | 15 ++++++
drivers/net/cpc/Makefile | 5 ++
drivers/net/cpc/interface.c | 98 +++++++++++++++++++++++++++++++++++++
drivers/net/cpc/interface.h | 88 +++++++++++++++++++++++++++++++++
drivers/net/cpc/main.c | 21 ++++++++
8 files changed, 236 insertions(+)
create mode 100644 drivers/net/cpc/Kconfig
create mode 100644 drivers/net/cpc/Makefile
create mode 100644 drivers/net/cpc/interface.c
create mode 100644 drivers/net/cpc/interface.h
create mode 100644 drivers/net/cpc/main.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 00e94bec401..8256ec0ff8a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21731,6 +21731,12 @@ S: Maintained
F: drivers/input/touchscreen/silead.c
F: drivers/platform/x86/touchscreen_dmi.c
+SILICON LABS CPC DRIVERS
+M: Damien Riégel <damien.riegel@silabs.com>
+R: Silicon Labs Kernel Team <linux-devel@silabs.com>
+S: Supported
+F: drivers/net/cpc/*
+
SILICON LABS WIRELESS DRIVERS (for WFxxx series)
M: Jérôme Pouiller <jerome.pouiller@silabs.com>
S: Supported
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 1fd5acdc73c..d78ca2f4de5 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -508,6 +508,8 @@ source "drivers/atm/Kconfig"
source "drivers/net/caif/Kconfig"
+source "drivers/net/cpc/Kconfig"
+
source "drivers/net/dsa/Kconfig"
source "drivers/net/ethernet/Kconfig"
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 13743d0e83b..19878d11c62 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -49,6 +49,7 @@ obj-$(CONFIG_MHI_NET) += mhi_net.o
obj-$(CONFIG_ARCNET) += arcnet/
obj-$(CONFIG_CAIF) += caif/
obj-$(CONFIG_CAN) += can/
+obj-$(CONFIG_CPC) += cpc/
ifdef CONFIG_NET_DSA
obj-y += dsa/
endif
diff --git a/drivers/net/cpc/Kconfig b/drivers/net/cpc/Kconfig
new file mode 100644
index 00000000000..f31b6837b49
--- /dev/null
+++ b/drivers/net/cpc/Kconfig
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0
+
+menuconfig CPC
+ tristate "Silicon Labs Co-Processor Communication (CPC) Protocol"
+ depends on NET
+ help
+ Provide support for the CPC protocol to Silicon Labs EFR32 devices.
+
+ CPC provides a way to multiplex data channels over a shared physical
+ link. These data channels can carry Bluetooth, Wi-Fi, or any arbitrary
+ data. Depending on the part and the firmware, the set of available
+ channels may differ.
+
+ Say Y here to compile support for CPC into the kernel or say M to
+ compile as a module.
diff --git a/drivers/net/cpc/Makefile b/drivers/net/cpc/Makefile
new file mode 100644
index 00000000000..1ce7415f305
--- /dev/null
+++ b/drivers/net/cpc/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+
+cpc-y := interface.o main.o
+
+obj-$(CONFIG_CPC) += cpc.o
diff --git a/drivers/net/cpc/interface.c b/drivers/net/cpc/interface.c
new file mode 100644
index 00000000000..4fdc78a0868
--- /dev/null
+++ b/drivers/net/cpc/interface.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025, Silicon Laboratories, Inc.
+ */
+
+#include <linux/module.h>
+
+#include "interface.h"
+
+#define to_cpc_interface(d) container_of(d, struct cpc_interface, dev)
+
+static DEFINE_IDA(cpc_ida);
+
+/**
+ * cpc_intf_release() - Actual release of interface.
+ * @dev: Device embedded in struct cpc_interface
+ *
+ * This function should not be called directly, users are expected to use cpc_interface_put()
+ * instead. This function will be called when the last reference to the CPC device is released.
+ */
+static void cpc_intf_release(struct device *dev)
+{
+ struct cpc_interface *intf = to_cpc_interface(dev);
+
+ ida_free(&cpc_ida, intf->index);
+ kfree(intf);
+}
+
+/**
+ * cpc_interface_alloc() - Allocate memory for new CPC interface.
+ *
+ * @parent: Parent device.
+ * @ops: Callbacks for this device.
+ * @priv: Pointer to private structure associated with this device.
+ *
+ * Context: Process context as allocations are done with @GFP_KERNEL flag
+ *
+ * Return: allocated CPC interface or %NULL.
+ */
+struct cpc_interface *cpc_interface_alloc(struct device *parent,
+ const struct cpc_interface_ops *ops,
+ void *priv)
+{
+ struct cpc_interface *intf;
+
+ intf = kzalloc(sizeof(*intf), GFP_KERNEL);
+ if (!intf)
+ return NULL;
+
+ intf->index = ida_alloc(&cpc_ida, GFP_KERNEL);
+ if (intf->index < 0) {
+ kfree(intf);
+ return NULL;
+ }
+
+ intf->ops = ops;
+
+ intf->dev.parent = parent;
+ intf->dev.release = cpc_intf_release;
+
+ device_initialize(&intf->dev);
+
+ dev_set_name(&intf->dev, "cpc%d", intf->index);
+ dev_set_drvdata(&intf->dev, priv);
+
+ return intf;
+}
+
+/**
+ * cpc_interface_register() - Register CPC interface.
+ * @intf: CPC device to register.
+ *
+ * Context: Process context.
+ *
+ * Return: 0 if successful, otherwise a negative error code.
+ */
+int cpc_interface_register(struct cpc_interface *intf)
+{
+ int err;
+
+ err = device_add(&intf->dev);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+/**
+ * cpc_interface_unregister() - Unregister a CPC interface.
+ * @intf: CPC device to unregister.
+ *
+ * Context: Process context.
+ */
+void cpc_interface_unregister(struct cpc_interface *intf)
+{
+ device_del(&intf->dev);
+ cpc_interface_put(intf);
+}
diff --git a/drivers/net/cpc/interface.h b/drivers/net/cpc/interface.h
new file mode 100644
index 00000000000..797f70119a8
--- /dev/null
+++ b/drivers/net/cpc/interface.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025, Silicon Laboratories, Inc.
+ */
+
+#ifndef __CPC_INTERFACE_H
+#define __CPC_INTERFACE_H
+
+#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/skbuff.h>
+
+struct cpc_interface;
+struct cpc_interface_ops;
+
+/**
+ * struct cpc_interface - Representation of a CPC interface.
+ * @dev: Device structure for bookkeeping..
+ * @ops: Callbacks for this device.
+ * @index: Device index.
+ */
+struct cpc_interface {
+ struct device dev;
+
+ const struct cpc_interface_ops *ops;
+
+ int index;
+};
+
+/**
+ * struct cpc_interface_ops - Callbacks from CPC core to physical bus driver.
+ * @wake_tx: Called by CPC core to wake up the transmit task of that interface.
+ * @csum: Callback to calculate checksum over the payload.
+ *
+ * This structure contains various callbacks that the bus (SDIO, SPI) driver must implement.
+ */
+struct cpc_interface_ops {
+ int (*wake_tx)(struct cpc_interface *intf);
+ void (*csum)(struct sk_buff *skb);
+};
+
+struct cpc_interface *cpc_interface_alloc(struct device *parent,
+ const struct cpc_interface_ops *ops,
+ void *priv);
+
+int cpc_interface_register(struct cpc_interface *intf);
+void cpc_interface_unregister(struct cpc_interface *intf);
+
+/**
+ * cpc_interface_get() - Get a reference to interface and return its pointer.
+ * @intf: Interface to get.
+ *
+ * Return: Interface pointer with its reference counter incremented, or %NULL.
+ */
+static inline struct cpc_interface *cpc_interface_get(struct cpc_interface *intf)
+{
+ if (!intf || !get_device(&intf->dev))
+ return NULL;
+ return intf;
+}
+
+/**
+ * cpc_interface_put() - Release reference to an interface.
+ * @intf: CPC interface
+ *
+ * Context: Process context.
+ */
+static inline void cpc_interface_put(struct cpc_interface *intf)
+{
+ if (intf)
+ put_device(&intf->dev);
+}
+
+/**
+ * cpc_interface_get_priv() - Get driver data associated with this interface.
+ * @intf: Interface pointer.
+ *
+ * Return: Driver data, set at allocation via cpc_interface_alloc().
+ */
+static inline void *cpc_interface_get_priv(struct cpc_interface *intf)
+{
+ if (!intf)
+ return NULL;
+ return dev_get_drvdata(&intf->dev);
+}
+
+#endif
diff --git a/drivers/net/cpc/main.c b/drivers/net/cpc/main.c
new file mode 100644
index 00000000000..ba9ab1ccf63
--- /dev/null
+++ b/drivers/net/cpc/main.c
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025, Silicon Laboratories, Inc.
+ */
+
+#include <linux/module.h>
+
+static int __init cpc_init(void)
+{
+ return 0;
+}
+module_init(cpc_init);
+
+static void __exit cpc_exit(void)
+{
+}
+module_exit(cpc_exit);
+
+MODULE_DESCRIPTION("Silicon Labs CPC Protocol");
+MODULE_AUTHOR("Damien Riégel <damien.riegel@silabs.com>");
+MODULE_LICENSE("GPL");
--
2.49.0
> +/**
> + * cpc_interface_register() - Register CPC interface.
> + * @intf: CPC device to register.
> + *
> + * Context: Process context.
> + *
> + * Return: 0 if successful, otherwise a negative error code.
> + */
> +int cpc_interface_register(struct cpc_interface *intf)
> +{
> + int err;
> +
> + err = device_add(&intf->dev);
> + if (err)
> + return err;
> +
> + return 0;
I guess this will change in a later patch, but maybe not. This should
just be
return device_add(&intf->dev);
> +}
> +
> +/**
> + * cpc_interface_unregister() - Unregister a CPC interface.
> + * @intf: CPC device to unregister.
> + *
> + * Context: Process context.
> + */
> +void cpc_interface_unregister(struct cpc_interface *intf)
> +{
> + device_del(&intf->dev);
> + cpc_interface_put(intf);
> +}
It seems odd that unregister is not a mirror of register?
> +/**
> + * cpc_interface_get() - Get a reference to interface and return its pointer.
> + * @intf: Interface to get.
> + *
> + * Return: Interface pointer with its reference counter incremented, or %NULL.
> + */
> +static inline struct cpc_interface *cpc_interface_get(struct cpc_interface *intf)
> +{
> + if (!intf || !get_device(&intf->dev))
> + return NULL;
> + return intf;
> +}
What is the use case for passing in NULL?
> +
> +/**
> + * cpc_interface_put() - Release reference to an interface.
> + * @intf: CPC interface
> + *
> + * Context: Process context.
> + */
> +static inline void cpc_interface_put(struct cpc_interface *intf)
> +{
> + if (intf)
> + put_device(&intf->dev);
> +}
> +
> +/**
> + * cpc_interface_get_priv() - Get driver data associated with this interface.
> + * @intf: Interface pointer.
> + *
> + * Return: Driver data, set at allocation via cpc_interface_alloc().
> + */
> +static inline void *cpc_interface_get_priv(struct cpc_interface *intf)
> +{
> + if (!intf)
> + return NULL;
> + return dev_get_drvdata(&intf->dev);
> +}
What is the use case for passing in NULL?
To me, this is hiding bugs. It seems better to let the kernel opp so
you find out where you lost your intf.
Andrew
© 2016 - 2025 Red Hat, Inc.