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

Krishnamoorthi M <[email protected]> Tue, 4 Aug 2026 17:22:57 +0530
Newsgroups org.ozlabs.lists.openbmc,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
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