From: Krishnamoorthi M <krishnamoorthi.m@amd.com>
To: <linux-kernel@vger.kernel.org>
Cc: <gregkh@linuxfoundation.org>, <broonie@kernel.org>,
<linux-spi@vger.kernel.org>, <akshata.mukundshetty@amd.com>,
<bleung@chromium.org>, <groeck@chromium.org>,
<chrome-platform@lists.linux.dev>, <corbet@lwn.net>,
<linux-doc@vger.kernel.org>, <skhan@linuxfoundation.org>,
<andrew@codeconstruct.com.au>, <linux-aspeed@lists.ozlabs.org>,
<openbmc@lists.ozlabs.org>,
Krishnamoorthi M <krishnamoorthi.m@amd.com>
Subject: [RFC PATCH 2/4] espi: add slave device model and event notification
Date: Tue, 4 Aug 2026 17:22:57 +0530 [thread overview]
Message-ID: <20260804115259.4065638-3-krishnamoorthi.m@amd.com> (raw)
In-Reply-To: <20260804115259.4065638-1-krishnamoorthi.m@amd.com>
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 <krishnamoorthi.m@amd.com>
---
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 <krishnamoorthi.m@amd.com>");
+MODULE_DESCRIPTION("eSPI slave-side device management");
+MODULE_LICENSE("GPL");
--
2.34.1
next prev parent reply other threads:[~2026-08-04 11:54 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-04 11:52 [RFC PATCH 0/4] espi: introduce eSPI bus framework Krishnamoorthi M
2026-08-04 11:52 ` [RFC PATCH 1/4] espi: add core " Krishnamoorthi M
2026-08-06 13:31 ` Uwe Kleine-König
2026-08-04 11:52 ` Krishnamoorthi M [this message]
2026-08-04 11:52 ` [RFC PATCH 3/4] Documentation: espi: add subsystem overview and MAINTAINERS entry Krishnamoorthi M
2026-08-04 16:39 ` Randy Dunlap
2026-08-04 19:14 ` M, Krishnamoorthi
2026-08-04 11:52 ` [RFC PATCH 4/4] espi: amd: add AMD eSPI controller driver Krishnamoorthi M
2026-08-04 12:18 ` [RFC PATCH 0/4] espi: introduce eSPI bus framework Greg KH
2026-08-04 13:26 ` Greg KH
2026-08-05 0:42 ` Andrew Jeffery
2026-08-05 18:35 ` M, Krishnamoorthi
2026-08-05 10:14 ` M, Krishnamoorthi
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260804115259.4065638-3-krishnamoorthi.m@amd.com \
--to=krishnamoorthi.m@amd.com \
--cc=akshata.mukundshetty@amd.com \
--cc=andrew@codeconstruct.com.au \
--cc=bleung@chromium.org \
--cc=broonie@kernel.org \
--cc=chrome-platform@lists.linux.dev \
--cc=corbet@lwn.net \
--cc=gregkh@linuxfoundation.org \
--cc=groeck@chromium.org \
--cc=linux-aspeed@lists.ozlabs.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-spi@vger.kernel.org \
--cc=openbmc@lists.ozlabs.org \
--cc=skhan@linuxfoundation.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox