[RFC PATCH 2/4] espi: add slave device model and event notification

Krishnamoorthi M <[email protected]>
Newsgroups dev.linux.lists.chrome-platform,org.kernel.vger.linux-doc,org.kernel.vger.linux-kernel,org.kernel.vger.linux-spi,org.ozlabs.lists.linux-aspeed,org.ozlabs.lists.openbmc
Message-ID <[email protected]>
Add espi_new_device()/espi_remove_device() and a process-context
blocking-notifier event chain. Devices are named espi<bus>.<cs>
(unique across controllers) and matched via a modalias string, also
emitted in the MODALIAS uevent.

cs is range-checked against ctrl->max_targets when the controller
advertises a non-zero limit. Devices are added to ctrl->device_list
before device_register() so that any controller-side iteration sees
the device as soon as the uevent fires.

A per-controller blocking notifier chain delivers hardware events to
slave drivers from process context. Slave drivers subscribe with a
notifier_block; the val argument is the espi_event_type and the data
pointer is the full struct espi_event. A blocking notifier is used
rather than a raw notifier because slave driver callbacks may sleep,
for example to issue follow-up configuration commands over the bus.

Signed-off-by: Krishnamoorthi M <[email protected]>
---
 drivers/espi/Kconfig      |   3 +
 drivers/espi/Makefile     |   2 +-
 drivers/espi/espi-slave.c | 177 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 181 insertions(+), 1 deletion(-)
 create mode 100644 drivers/espi/espi-slave.c

diff --git a/drivers/espi/Kconfig b/drivers/espi/Kconfig
index 4411d8336e82..7e609004967e 100644
--- a/drivers/espi/Kconfig
+++ b/drivers/espi/Kconfig
@@ -16,4 +16,7 @@ menuconfig ESPI
 	  over a shared physical link: Peripheral (I/O and memory), Virtual
 	  Wire, OOB (Out-of-Band) messaging, and Flash Access.
 
+	  Includes slave device registration and a blocking-notifier event
+	  chain for delivering hardware events to slave drivers.
+
 	  If unsure, say N.
diff --git a/drivers/espi/Makefile b/drivers/espi/Makefile
index 72712fcb0ada..48c2a591132f 100644
--- a/drivers/espi/Makefile
+++ b/drivers/espi/Makefile
@@ -1,2 +1,2 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
-obj-$(CONFIG_ESPI)		+= espi-core.o
+obj-$(CONFIG_ESPI)		+= espi-core.o espi-slave.o
diff --git a/drivers/espi/espi-slave.c b/drivers/espi/espi-slave.c
new file mode 100644
index 000000000000..5d7f8c3c0d1f
--- /dev/null
+++ b/drivers/espi/espi-slave.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * eSPI slave-side device management and event notification
+ *
+ * Copyright (c) 2026, Advanced Micro Devices, Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/notifier.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/espi/espi.h>
+
+static void espi_device_release(struct device *dev)
+{
+	struct espi_device *edev = to_espi_device(dev);
+
+	fwnode_handle_put(dev_fwnode(dev));
+	kfree(edev);
+}
+
+static const struct device_type espi_device_type = {
+	.release = espi_device_release,
+};
+
+/**
+ * espi_new_device - instantiate a new eSPI slave device
+ * @ctrl: controller to which the device is attached
+ * @info: board-level description of the device
+ *
+ * Creates and registers a new &struct espi_device on @ctrl.  The device
+ * is named ``espi<bus>.<cs>`` and its modalias is set from
+ * @info->type.
+ *
+ * Return: pointer to the new device, or an ERR_PTR() on failure.
+ */
+struct espi_device *espi_new_device(struct espi_controller *ctrl,
+				    const struct espi_board_info *info)
+{
+	struct espi_device *edev;
+	int ret;
+
+	if (!ctrl || !info)
+		return ERR_PTR(-EINVAL);
+	if (ctrl->max_targets && info->cs >= ctrl->max_targets)
+		return ERR_PTR(-EINVAL);
+
+	edev = kzalloc_obj(*edev, GFP_KERNEL);
+	if (!edev)
+		return ERR_PTR(-ENOMEM);
+
+	edev->ctrl = ctrl;
+	edev->cs = info->cs;
+	edev->platform_data = info->platform_data;
+	strscpy(edev->modalias, info->type, sizeof(edev->modalias));
+	INIT_LIST_HEAD(&edev->list);
+
+	edev->dev.parent = &ctrl->dev;
+	edev->dev.bus = &espi_bus_type;
+	edev->dev.type = &espi_device_type;
+	device_set_node(&edev->dev, fwnode_handle_get(info->fwnode));
+
+	dev_set_name(&edev->dev, "espi%d.%u", ctrl->bus_num, info->cs);
+
+	/*
+	 * Add to the list before device_register() so the device is visible
+	 * to any controller-side iteration as soon as the uevent fires.
+	 */
+	mutex_lock(&ctrl->device_list_lock);
+	list_add_tail(&edev->list, &ctrl->device_list);
+	mutex_unlock(&ctrl->device_list_lock);
+
+	ret = device_register(&edev->dev);
+	if (ret) {
+		dev_err(&ctrl->dev, "failed to register device '%s': %d\n",
+			dev_name(&edev->dev), ret);
+		mutex_lock(&ctrl->device_list_lock);
+		list_del_init(&edev->list);
+		mutex_unlock(&ctrl->device_list_lock);
+		put_device(&edev->dev);
+		return ERR_PTR(ret);
+	}
+
+	return edev;
+}
+EXPORT_SYMBOL_GPL(espi_new_device);
+
+/**
+ * espi_remove_device - unregister and free an eSPI slave device
+ * @edev: device to remove
+ *
+ * Removes @edev from the controller's device list and unregisters it
+ * from the bus.  Must be called at most once per device.
+ */
+void espi_remove_device(struct espi_device *edev)
+{
+	struct espi_controller *ctrl;
+
+	if (!edev)
+		return;
+	ctrl = edev->ctrl;
+
+	mutex_lock(&ctrl->device_list_lock);
+	if (WARN_ON(list_empty(&edev->list))) {
+		mutex_unlock(&ctrl->device_list_lock);
+		return;
+	}
+	list_del_init(&edev->list);
+	mutex_unlock(&ctrl->device_list_lock);
+
+	device_unregister(&edev->dev);
+}
+EXPORT_SYMBOL_GPL(espi_remove_device);
+
+/**
+ * espi_register_notifier - subscribe to eSPI hardware events
+ * @ctrl: controller whose event chain to subscribe to
+ * @nb:   notifier block to register
+ *
+ * Notifier callbacks are invoked from process context (threaded IRQ or
+ * workqueue).  The @val argument passed to the callback is the
+ * &enum espi_event_type value; @data points to the &struct espi_event.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int espi_register_notifier(struct espi_controller *ctrl,
+			   struct notifier_block *nb)
+{
+	if (!ctrl || !nb)
+		return -EINVAL;
+	return blocking_notifier_chain_register(&ctrl->notifier_list, nb);
+}
+EXPORT_SYMBOL_GPL(espi_register_notifier);
+
+/**
+ * espi_unregister_notifier - unsubscribe from eSPI hardware events
+ * @ctrl: controller whose event chain to unsubscribe from
+ * @nb:   notifier block to unregister
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int espi_unregister_notifier(struct espi_controller *ctrl,
+			     struct notifier_block *nb)
+{
+	if (!ctrl || !nb)
+		return -EINVAL;
+	return blocking_notifier_chain_unregister(&ctrl->notifier_list, nb);
+}
+EXPORT_SYMBOL_GPL(espi_unregister_notifier);
+
+/**
+ * espi_notify_event - deliver a hardware event to all registered listeners
+ * @ctrl:  controller on which the event occurred
+ * @event: event descriptor; @event->ctrl is set by this function
+ *
+ * Must be called from process context (threaded IRQ or workqueue), never
+ * from hard-IRQ context and never with @ctrl->lock held.
+ *
+ * Return: a NOTIFY_* value, not an errno.  Callers that need to map this
+ * to an errno should use notifier_to_errno().
+ */
+int espi_notify_event(struct espi_controller *ctrl, struct espi_event *event)
+{
+	if (!ctrl || !event)
+		return notifier_from_errno(-EINVAL);
+	event->ctrl = ctrl;
+	return blocking_notifier_call_chain(&ctrl->notifier_list,
+					    (unsigned long)event->type, event);
+}
+EXPORT_SYMBOL_GPL(espi_notify_event);
+
+MODULE_AUTHOR("Krishnamoorthi M <[email protected]>");
+MODULE_DESCRIPTION("eSPI slave-side device management");
+MODULE_LICENSE("GPL");
-- 
2.34.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.