lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250512012748.79749-12-damien.riegel@silabs.com>
Date: Sun, 11 May 2025 21:27:44 -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 11/15] net: cpc: add system endpoint

The system endpoint is the main endpoint and is guaranteed to be present
in every CPC-enabled device. It is used to communicate device
capabilities.

One of the key element of the system endpoint is that it will send a
notification when an endpoint is opened on the microcontroller. When
such notification is received by the host, a new endpoint will be
registered.

As registering a new endpoint can be a long operation, as in the end it
calls device_add(), system's endpoint processing of RX frames is
dispatched in its own work function.

Signed-off-by: Damien Riégel <damien.riegel@...abs.com>
---
 drivers/net/cpc/Makefile |   2 +-
 drivers/net/cpc/main.c   |   8 +
 drivers/net/cpc/system.c | 432 +++++++++++++++++++++++++++++++++++++++
 drivers/net/cpc/system.h |  14 ++
 4 files changed, 455 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/cpc/system.c
 create mode 100644 drivers/net/cpc/system.h

diff --git a/drivers/net/cpc/Makefile b/drivers/net/cpc/Makefile
index 0e9c3f775dc..a61af84df90 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 protocol.o
+cpc-y := endpoint.o header.o interface.o main.o protocol.o system.o
 
 obj-$(CONFIG_CPC)	+= cpc.o
diff --git a/drivers/net/cpc/main.c b/drivers/net/cpc/main.c
index 8feb0613252..fc46a25f5dc 100644
--- a/drivers/net/cpc/main.c
+++ b/drivers/net/cpc/main.c
@@ -8,6 +8,7 @@
 
 #include "cpc.h"
 #include "header.h"
+#include "system.h"
 
 /**
  * cpc_skb_alloc() - Allocate an skb with a specific headroom for CPC headers.
@@ -118,6 +119,12 @@ static int __init cpc_init(void)
 	int err;
 
 	err = bus_register(&cpc_bus);
+	if (err)
+		return err;
+
+	err = cpc_system_drv_register();
+	if (err)
+		bus_unregister(&cpc_bus);
 
 	return err;
 }
@@ -125,6 +132,7 @@ module_init(cpc_init);
 
 static void __exit cpc_exit(void)
 {
+	cpc_system_drv_unregister();
 	bus_unregister(&cpc_bus);
 }
 module_exit(cpc_exit);
diff --git a/drivers/net/cpc/system.c b/drivers/net/cpc/system.c
new file mode 100644
index 00000000000..1d5803093f8
--- /dev/null
+++ b/drivers/net/cpc/system.c
@@ -0,0 +1,432 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025, Silicon Laboratories, Inc.
+ */
+
+#include <linux/atomic.h>
+#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+
+#include "cpc.h"
+#include "interface.h"
+#include "protocol.h"
+#include "system.h"
+
+/**
+ * enum cpc_system_cmd_id - Describes all possible system command id's.
+ * @CPC_SYSTEM_CMD_NOOP: Used for testing purposes.
+ * @CPC_SYSTEM_CMD_PROP_NOTIFY: Used for notification purposes.
+ * @CPC_SYSTEM_CMD_PROP_VALUE_GET: Used to fetch a property.
+ * @CPC_SYSTEM_CMD_PROP_VALUE_SET: Used to set a property.
+ * @CPC_SYSTEM_CMD_PROP_VALUE_IS: Used for receiving asynchronous properties.
+ * @CPC_SYSTEM_CMD_INVALID: Used to indicate a system command was invalid.
+ */
+enum cpc_system_cmd_id {
+	CPC_SYSTEM_CMD_NOOP = 0x00,
+	CPC_SYSTEM_CMD_PROP_NOTIFY = 0x01,
+	CPC_SYSTEM_CMD_PROP_VALUE_GET = 0x02,
+	CPC_SYSTEM_CMD_PROP_VALUE_SET = 0x03,
+	CPC_SYSTEM_CMD_PROP_VALUE_IS = 0x06,
+	CPC_SYSTEM_CMD_INVALID = 0xFF,
+};
+
+/**
+ * enum cpc_system_property_id - Describes all possible system property identifiers.
+ * @CPC_SYSTEM_PROP_INVALID: Invalid property.
+ * @CPC_SYSTEM_PROP_PROTOCOL_VERSION: Protocol version property.
+ * @CPC_SYSTEM_PROP_DRV_CAPABILITIES: Driver capabilities property.
+ * @CPC_SYSTEM_PROP_CPC_VERSION: CPC version property.
+ * @CPC_SYSTEM_PROP_APP_VERSION: Application version property.
+ * @CPC_SYSTEM_PROP_RESET_REASON: Reset reason property.
+ * @CPC_SYSTEM_PROP_EP_OPEN_NOTIF: Endpoint open notification property.
+ */
+enum cpc_system_property_id {
+	CPC_SYSTEM_PROP_INVALID = 0x00,
+	CPC_SYSTEM_PROP_PROTOCOL_VERSION = 0x10,
+	CPC_SYSTEM_PROP_DRV_CAPABILITIES = 0x11,
+	CPC_SYSTEM_PROP_CPC_VERSION = 0x12,
+	CPC_SYSTEM_PROP_APP_VERSION = 0x13,
+	CPC_SYSTEM_PROP_RESET_REASON = 0x14,
+	CPC_SYSTEM_PROP_EP_OPEN_NOTIF = 0x800,
+};
+
+/**
+ * enum cpc_system_status - Describes all possible system statuses.
+ * @CPC_SYSTEM_STATUS_RESET_POWER_ON: Reset was due to a power reason.
+ * @CPC_SYSTEM_STATUS_RESET_EXTERNAL: Reset was due to an external reason.
+ * @CPC_SYSTEM_STATUS_RESET_SOFTWARE: Reset was due to a software reason.
+ * @CPC_SYSTEM_STATUS_RESET_FAULT: Reset was due to a fault.
+ * @CPC_SYSTEM_STATUS_RESET_CRASH: Reset was due to a crash.
+ * @CPC_SYSTEM_STATUS_RESET_ASSERT: Reset was due to an assert.
+ * @CPC_SYSTEM_STATUS_RESET_OTHER: Reset was due to some other reason.
+ * @CPC_SYSTEM_STATUS_RESET_UNKNOWN: Reset was due to an unknown reason.
+ * @CPC_SYSTEM_STATUS_RESET_WATCHDOG: Reset was due to a watchdog trigger.
+ */
+enum cpc_system_status {
+	CPC_SYSTEM_STATUS_RESET_POWER_ON = 112,
+	CPC_SYSTEM_STATUS_RESET_EXTERNAL = 113,
+	CPC_SYSTEM_STATUS_RESET_SOFTWARE = 114,
+	CPC_SYSTEM_STATUS_RESET_FAULT = 115,
+	CPC_SYSTEM_STATUS_RESET_CRASH = 116,
+	CPC_SYSTEM_STATUS_RESET_ASSERT = 117,
+	CPC_SYSTEM_STATUS_RESET_OTHER = 118,
+	CPC_SYSTEM_STATUS_RESET_UNKNOWN = 119,
+	CPC_SYSTEM_STATUS_RESET_WATCHDOG = 120,
+};
+
+/**
+ * struct cpc_system_cmd - Wire representation of the system command.
+ * @id: Identifier of the command.
+ * @seq: Command sequence number.
+ * @len: Length of the payload in bytes.
+ * @payload: Variable length payload.
+ */
+struct cpc_system_cmd {
+	u8 id;
+	u8 seq;
+	__le16 len;
+	u8 payload[];
+} __packed;
+
+/**
+ * struct cpc_system_cmd_property - Wire representation of the system
+ * command property.
+ * @id: Identifier of the property.
+ * @payload: Variable length property value.
+ */
+struct cpc_system_cmd_property {
+	__le32 id;
+	u8 payload[];
+} __packed;
+
+struct cpc_system_cmd_handle {
+	u8 seq;
+	struct list_head node;
+};
+
+struct cpc_system_cmd_ctx {
+	struct mutex lock;	/* Synchronize access to command list */
+	u8 next_seq;
+	struct list_head list;
+
+	struct work_struct init_work;
+	struct work_struct rx_work;
+
+	struct sk_buff_head rx_queue;
+
+	struct cpc_endpoint *ep;
+};
+
+static bool __cpc_system_cmd_pop_handle(struct cpc_system_cmd_ctx *cmd_ctx,
+					struct cpc_system_cmd_handle **cmd_handle)
+{
+	*cmd_handle = list_first_entry_or_null(&cmd_ctx->list, struct cpc_system_cmd_handle, node);
+
+	if (*cmd_handle)
+		list_del(&(*cmd_handle)->node);
+
+	return !!*cmd_handle;
+}
+
+static void cpc_system_cmd_init(struct cpc_system_cmd_ctx *cmd_ctx)
+{
+	mutex_init(&cmd_ctx->lock);
+	INIT_LIST_HEAD(&cmd_ctx->list);
+	cmd_ctx->next_seq = 0;
+}
+
+static void cpc_system_cmd_clear(struct cpc_system_cmd_ctx *cmd_ctx)
+{
+	struct cpc_system_cmd_handle *cmd_handle;
+
+	mutex_lock(&cmd_ctx->lock);
+	while (__cpc_system_cmd_pop_handle(cmd_ctx, &cmd_handle))
+		kfree(cmd_handle);
+
+	cmd_ctx->next_seq = 0;
+	mutex_unlock(&cmd_ctx->lock);
+}
+
+static bool cpc_system_cmd_find_handle(struct cpc_system_cmd_ctx *cmd_ctx,
+				       u8 cmd_seq,
+				       struct cpc_system_cmd_handle **cmd_handle)
+{
+	mutex_lock(&cmd_ctx->lock);
+
+	list_for_each_entry((*cmd_handle), &cmd_ctx->list, node) {
+		if ((*cmd_handle)->seq == cmd_seq) {
+			list_del(&(*cmd_handle)->node);
+			mutex_unlock(&cmd_ctx->lock);
+			return true;
+		}
+	}
+
+	mutex_unlock(&cmd_ctx->lock);
+
+	return false;
+}
+
+static void cpc_system_on_reset_reason(struct cpc_endpoint *ep, struct sk_buff *skb)
+{
+	u32 reset_reason;
+
+	if (skb->len != sizeof(reset_reason)) {
+		dev_err(&ep->dev, "reset reason has invalid length (%d)\n", skb->len);
+		return;
+	}
+
+	reset_reason = get_unaligned_le32(skb->data);
+
+	switch (reset_reason) {
+	case CPC_SYSTEM_STATUS_RESET_POWER_ON:
+	case CPC_SYSTEM_STATUS_RESET_EXTERNAL:
+	case CPC_SYSTEM_STATUS_RESET_SOFTWARE:
+	case CPC_SYSTEM_STATUS_RESET_FAULT:
+	case CPC_SYSTEM_STATUS_RESET_CRASH:
+	case CPC_SYSTEM_STATUS_RESET_ASSERT:
+	case CPC_SYSTEM_STATUS_RESET_OTHER:
+	case CPC_SYSTEM_STATUS_RESET_UNKNOWN:
+	case CPC_SYSTEM_STATUS_RESET_WATCHDOG:
+		dev_dbg(&ep->dev, "reset reason: %d\n", reset_reason);
+		break;
+	default:
+		dev_dbg(&ep->dev, "undefined reset reason: %d\n", reset_reason);
+		break;
+	}
+}
+
+static void cpc_system_prop_value_is(struct cpc_endpoint *ep, struct sk_buff *skb)
+{
+	u32 id;
+
+	if (skb->len < sizeof(id)) {
+		dev_warn(&ep->dev, "command property with invalid length (%d)\n", skb->len);
+		return;
+	}
+
+	id = get_unaligned_le32(skb_pull_data(skb, sizeof(id)));
+
+	switch (id) {
+	case CPC_SYSTEM_PROP_RESET_REASON:
+		cpc_system_on_reset_reason(ep, skb);
+		break;
+	default:
+		dev_dbg(&ep->dev, "unsupported command property identifier (%d)\n", id);
+		break;
+	}
+}
+
+static void cpc_system_on_prop_value_is(struct cpc_endpoint *ep, struct sk_buff *skb, u8 seq)
+{
+	struct cpc_system_cmd_ctx *cmd_ctx = cpc_endpoint_get_drvdata(ep);
+	struct cpc_system_cmd_handle *cmd_handle;
+
+	if (cpc_system_cmd_find_handle(cmd_ctx, seq, &cmd_handle)) {
+		cpc_system_prop_value_is(ep, skb);
+		kfree(cmd_handle);
+	} else {
+		dev_warn(&ep->dev, "unknown command sequence (%u)\n", seq);
+	}
+}
+
+static void cpc_system_on_ep_open_notification(struct cpc_endpoint *ep, struct sk_buff *skb)
+{
+	struct cpc_endpoint *new_ep;
+	u8 ep_id;
+
+	/*
+	 * Payload should contain at least the endpoint ID
+	 * and the null terminating byte of the string.
+	 */
+	if (skb->len < sizeof(ep_id) + 1) {
+		dev_warn(&ep->dev, "open with invalid length (%d)\n", skb->len);
+		return;
+	}
+
+	ep_id = *(u8 *)skb_pull_data(skb, sizeof(ep_id));
+
+	if (skb->data[skb->len - 1] != '\0') {
+		dev_warn(&ep->dev, "non nul-terminated endpoint name for ep%u\n", ep_id);
+		return;
+	}
+
+	if (skb->len > sizeof(ep->name)) {
+		dev_warn(&ep->dev, "oversized endpoint name (%d) for ep%u\n", skb->len, ep_id);
+		return;
+	}
+
+	dev_dbg(&ep->dev, "open notification for ep%u: '%s'\n", ep_id, skb->data);
+
+	new_ep = cpc_endpoint_alloc(ep->intf, ep_id);
+	if (!new_ep)
+		return;
+
+	memcpy(new_ep->name, skb->data, skb->len);
+
+	/* Register the new endpoint in the same interface that received the notification. */
+	cpc_endpoint_register(new_ep);
+}
+
+static void cpc_system_on_prop_notify(struct cpc_endpoint *ep, struct sk_buff *skb)
+{
+	u32 id;
+
+	if (skb->len < sizeof(id)) {
+		dev_warn(&ep->dev, "command property with invalid length (%d)\n", skb->len);
+		return;
+	}
+
+	id = get_unaligned_le32(skb_pull_data(skb, sizeof(id)));
+
+	switch (id) {
+	case CPC_SYSTEM_PROP_EP_OPEN_NOTIF:
+		cpc_system_on_ep_open_notification(ep, skb);
+		break;
+	default:
+		dev_dbg(&ep->dev, "unsupported command property identifier (%d)\n", id);
+		break;
+	}
+}
+
+static void cpc_system_on_data(struct cpc_endpoint *ep, struct sk_buff *skb)
+{
+	struct cpc_system_cmd *system_cmd;
+	u16 payload_len;
+	u8 seq;
+
+	if (skb->len < sizeof(*system_cmd)) {
+		dev_warn(&ep->dev, "command with invalid length (%d)\n", skb->len);
+		kfree_skb(skb);
+		return;
+	}
+
+	system_cmd = skb_pull_data(skb, sizeof(*system_cmd));
+	payload_len = le16_to_cpu(system_cmd->len);
+	seq = system_cmd->seq;
+
+	if (skb->len != payload_len) {
+		dev_warn(&ep->dev,
+			 "command payload length does not match (%d != %d)\n",
+			 skb->len, payload_len);
+		kfree_skb(skb);
+		return;
+	}
+
+	switch (system_cmd->id) {
+	case CPC_SYSTEM_CMD_PROP_VALUE_IS:
+		cpc_system_on_prop_value_is(ep, skb, seq);
+		break;
+	case CPC_SYSTEM_CMD_PROP_NOTIFY:
+		cpc_system_on_prop_notify(ep, skb);
+		break;
+	default:
+		dev_dbg(&ep->dev, "unsupported system command identifier (%d)\n", system_cmd->id);
+		break;
+	}
+
+	kfree_skb(skb);
+}
+
+static void cpc_system_rx_work(struct work_struct *work)
+{
+	struct cpc_system_cmd_ctx *cmd_ctx = container_of(work, struct cpc_system_cmd_ctx, rx_work);
+	struct sk_buff *skb;
+
+	while ((skb = skb_dequeue(&cmd_ctx->rx_queue)))
+		cpc_system_on_data(cmd_ctx->ep, skb);
+}
+
+static void cpc_system_rx(struct cpc_endpoint *ep, struct sk_buff *skb)
+{
+	struct cpc_system_cmd_ctx *cmd_ctx = cpc_endpoint_get_drvdata(ep);
+
+	skb_queue_tail(&cmd_ctx->rx_queue, skb);
+	schedule_work(&cmd_ctx->rx_work);
+}
+
+static void cpc_system_init_work(struct work_struct *work)
+{
+	struct cpc_system_cmd_ctx *cmd_ctx = container_of(work,
+							  struct cpc_system_cmd_ctx,
+							  init_work);
+
+	cpc_endpoint_connect(cmd_ctx->ep);
+}
+
+static struct cpc_endpoint_ops system_ops = {
+	.rx = cpc_system_rx,
+};
+
+static int cpc_system_probe(struct cpc_endpoint *ep)
+{
+	struct cpc_system_cmd_ctx *cmd_ctx;
+
+	cmd_ctx = kzalloc(sizeof(*cmd_ctx), GFP_KERNEL);
+	if (!cmd_ctx)
+		return -ENOMEM;
+
+	cpc_system_cmd_init(cmd_ctx);
+
+	INIT_WORK(&cmd_ctx->init_work, cpc_system_init_work);
+	INIT_WORK(&cmd_ctx->rx_work, cpc_system_rx_work);
+
+	skb_queue_head_init(&cmd_ctx->rx_queue);
+
+	cmd_ctx->ep = ep;
+
+	cpc_endpoint_set_drvdata(ep, cmd_ctx);
+	cpc_endpoint_set_ops(ep, &system_ops);
+
+	/* Defer connection of the system endpoint to not block in probe(). */
+	schedule_work(&cmd_ctx->init_work);
+
+	return 0;
+}
+
+static void cpc_system_remove(struct cpc_endpoint *ep)
+{
+	struct cpc_system_cmd_ctx *cmd_ctx = cpc_endpoint_get_drvdata(ep);
+
+	cpc_endpoint_disconnect(ep);
+
+	cpc_system_cmd_clear(cmd_ctx);
+
+	flush_work(&cmd_ctx->init_work);
+	flush_work(&cmd_ctx->rx_work);
+
+	skb_queue_purge(&cmd_ctx->rx_queue);
+
+	kfree(cmd_ctx);
+}
+
+static struct cpc_driver system_driver = {
+	.driver = {
+		.name = CPC_SYSTEM_ENDPOINT_NAME,
+	},
+	.probe = cpc_system_probe,
+	.remove = cpc_system_remove,
+};
+
+/**
+ * cpc_system_drv_register - Register the system endpoint driver.
+ *
+ * @return: 0 on success, otherwise a negative error code.
+ */
+int cpc_system_drv_register(void)
+{
+	return cpc_driver_register(&system_driver);
+}
+
+/**
+ * cpc_system_drv_unregister - Unregister the system endpoint driver.
+ */
+void cpc_system_drv_unregister(void)
+{
+	cpc_driver_unregister(&system_driver);
+}
diff --git a/drivers/net/cpc/system.h b/drivers/net/cpc/system.h
new file mode 100644
index 00000000000..31307da3547
--- /dev/null
+++ b/drivers/net/cpc/system.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025, Silicon Laboratories, Inc.
+ */
+
+#ifndef __CPC_SYSTEM_H
+#define __CPC_SYSTEM_H
+
+#define CPC_SYSTEM_ENDPOINT_NAME "system"
+
+int cpc_system_drv_register(void);
+void cpc_system_drv_unregister(void);
+
+#endif
-- 
2.49.0


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ