[PATCH v8 03/23] firmware: arm_scmi: Introduce protocol instance notifiers

From: Cristian Marussi

Date: Sat Aug 08 2026 - 15:08:29 EST


SCMI Protocol notifications are typically used by SCMI drivers to detect
and react to particular conditions: this was the assumption and the classic
usage scenario upon which the SCMI notification framework was built.

Some protocols like Telemetry, though, could be interested in enabling and
registering for such notifications: in such a scenario each initialized
protocol instance could want to register a common protocol notifier.

In order to reuse the existing SCMI Notifications machinery, allow
protocols themselves to register for their own notifications and provide
their own notifier callbacks.

Each protocol, at the end of its initialization phase, can now provide one
unique per-protocol-instance notifier block, which will be registered, by
the core SCMI stack, on the related notification chain: such notifier will
be automatically removed during the protocol de-initialization phase.

Signed-off-by: Cristian Marussi <cristian.marussi@xxxxxxx>
---
v7 --> v8
- Reworked logic around protocol notifier registration to address
possible races as pointed out by Sashiko
- Reviewed commit message
v5 -->v6
- move protocol notifier registration out of proto_init
v2-->v3
- split out unrelated changes on event sizing
v1-->v2
- Fixed multiline comment format
---
drivers/firmware/arm_scmi/common.h | 21 +++++++
drivers/firmware/arm_scmi/driver.c | 89 +++++++++++++++++++++------
drivers/firmware/arm_scmi/notify.c | 34 ++++++++++
drivers/firmware/arm_scmi/protocols.h | 10 +++
4 files changed, 135 insertions(+), 19 deletions(-)

diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index d9db4a4d17dc..e1220735a991 100644
--- a/drivers/firmware/arm_scmi/common.h
+++ b/drivers/firmware/arm_scmi/common.h
@@ -18,6 +18,7 @@
#include <linux/list.h>
#include <linux/module.h>
#include <linux/property.h>
+#include <linux/notifier.h>
#include <linux/refcount.h>
#include <linux/scmi_protocol.h>
#include <linux/spinlock.h>
@@ -685,8 +686,28 @@ static struct platform_driver __drv = { \
.probe = __tag##_probe, \
}

+struct scmi_protocol_notifier {
+ u8 proto_id;
+ u8 evt_id;
+ const u32 *src_id;
+ struct notifier_block *nb;
+ bool registered;
+};
+
void scmi_notification_instance_data_set(const struct scmi_handle *handle,
void *priv);
void *scmi_notification_instance_data_get(const struct scmi_handle *handle);
+
+int scmi_protocol_notifier_register(const struct scmi_handle *handle,
+ struct scmi_protocol_notifier *pno);
+int scmi_protocol_notifier_unregister(const struct scmi_handle *handle,
+ struct scmi_protocol_notifier *pno);
+
+static inline bool
+scmi_protocol_notifier_registered(struct scmi_protocol_notifier *pno)
+{
+ return smp_load_acquire(&pno->registered);
+}
+
int scmi_inflight_count(const struct scmi_handle *handle);
#endif /* _SCMI_COMMON_H */
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index 3e0d975ec94c..3a405db9bb79 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -90,6 +90,8 @@ struct scmi_xfers_info {
* @gid: A reference for per-protocol devres management.
* @users: A refcount to track effective users of this protocol.
* @priv: Reference for optional protocol private data.
+ * @pno: A protocol instance notifier descriptor: active only when the
+ * included notifier block @nb is non-NULL.
* @version: Protocol version supported by the platform as detected at runtime.
* @negotiated_version: When the platform supports a newer protocol version,
* the agent will try to negotiate with the platform the
@@ -109,6 +111,7 @@ struct scmi_protocol_instance {
void *gid;
refcount_t users;
void *priv;
+ struct scmi_protocol_notifier pno;
unsigned int version;
unsigned int negotiated_version;
struct scmi_protocol_handle ph;
@@ -1655,6 +1658,24 @@ static void *scmi_get_protocol_priv(const struct scmi_protocol_handle *ph)
return pi->priv;
}

+static int
+scmi_register_instance_notifier(const struct scmi_protocol_handle *ph, u8 evt_id,
+ const u32 *src_id, struct notifier_block *nb)
+{
+ struct scmi_protocol_instance *pi = ph_to_pi(ph);
+
+ pi->pno.proto_id = pi->proto->id;
+ pi->pno.evt_id = evt_id;
+ pi->pno.src_id = src_id;
+ /*
+ * Ensure the descriptor fields are visibile when the notifier block is
+ * made available
+ */
+ smp_store_release(&pi->pno.nb, nb);
+
+ return 0;
+}
+
static const struct scmi_xfer_ops xfer_ops = {
.xfer_get_init = xfer_get_init,
.reset_rx_to_maxsz = reset_rx_to_maxsz,
@@ -2255,6 +2276,7 @@ scmi_alloc_init_protocol_instance(struct scmi_info *info,
pi->ph.hops = &helpers_ops;
pi->ph.set_priv = scmi_set_protocol_priv;
pi->ph.get_priv = scmi_get_protocol_priv;
+ pi->ph.instance_notifier_register = scmi_register_instance_notifier;
refcount_set(&pi->users, 1);

/*
@@ -2317,24 +2339,34 @@ static struct scmi_protocol_instance * __must_check
scmi_get_protocol_instance(const struct scmi_handle *handle, u8 protocol_id)
{
struct scmi_protocol_instance *pi;
+ struct notifier_block *proto_notifier_nb = NULL;
struct scmi_info *info = handle_to_scmi_info(handle);

- mutex_lock(&info->protocols_mtx);
- pi = idr_find(&info->protocols, protocol_id);
+ scoped_guard(mutex, &info->protocols_mtx) {
+ pi = idr_find(&info->protocols, protocol_id);
+ if (pi) {
+ refcount_inc(&pi->users);
+ } else {
+ const struct scmi_protocol *proto;

- if (pi) {
- refcount_inc(&pi->users);
- } else {
- const struct scmi_protocol *proto;
+ /* Fails if protocol not registered on bus */
+ proto = scmi_protocol_get(protocol_id, &info->version);
+ if (!proto)
+ return ERR_PTR(-EPROBE_DEFER);

- /* Fails if protocol not registered on bus */
- proto = scmi_protocol_get(protocol_id, &info->version);
- if (proto)
pi = scmi_alloc_init_protocol_instance(info, proto);
- else
- pi = ERR_PTR(-EPROBE_DEFER);
+ if (IS_ERR(pi))
+ return pi;
+
+ proto_notifier_nb = READ_ONCE(pi->pno.nb);
+ }
+ }
+
+ if (proto_notifier_nb) {
+ if (scmi_protocol_notifier_register(pi->handle, &pi->pno))
+ dev_warn(handle->dev,
+ "Failed to register protocol notifier\n");
}
- mutex_unlock(&info->protocols_mtx);

return pi;
}
@@ -2365,13 +2397,35 @@ int scmi_protocol_acquire(const struct scmi_handle *handle, u8 protocol_id)
void scmi_protocol_release(const struct scmi_handle *handle, u8 protocol_id)
{
struct scmi_info *info = handle_to_scmi_info(handle);
+ struct notifier_block *proto_notifier_nb = NULL;
struct scmi_protocol_instance *pi;

- mutex_lock(&info->protocols_mtx);
- pi = idr_find(&info->protocols, protocol_id);
- if (WARN_ON(!pi))
- goto out;
+ scoped_guard(mutex, &info->protocols_mtx) {
+ pi = idr_find(&info->protocols, protocol_id);
+ if (WARN_ON(!pi))
+ return;

+ /*
+ * If a protocol notifier was registered and this is the
+ * last istance releasing the protocol, mark the notifier
+ * for un-registration: note that the notifier itself counts
+ * as one user, as for any other regular notification, so if a
+ * protocol notifier is registered and there are only 2 users
+ * active we can derive that this is the last protocol
+ * instance de-registering.
+ */
+ if (scmi_protocol_notifier_registered(&pi->pno) &&
+ refcount_read(&pi->users) == 2)
+ proto_notifier_nb = READ_ONCE(pi->pno.nb);
+ }
+
+ if (proto_notifier_nb) {
+ if (scmi_protocol_notifier_unregister(pi->handle, &pi->pno))
+ dev_err(handle->dev,
+ "Failed to release protocol notifier\n");
+ }
+
+ guard(mutex)(&info->protocols_mtx);
if (refcount_dec_and_test(&pi->users)) {
void *gid = pi->gid;

@@ -2389,9 +2443,6 @@ void scmi_protocol_release(const struct scmi_handle *handle, u8 protocol_id)
dev_dbg(handle->dev, "De-Initialized protocol: 0x%X\n",
protocol_id);
}
-
-out:
- mutex_unlock(&info->protocols_mtx);
}

void scmi_setup_protocol_implemented(const struct scmi_protocol_handle *ph,
diff --git a/drivers/firmware/arm_scmi/notify.c b/drivers/firmware/arm_scmi/notify.c
index 8995dd1b12aa..6687a7c3bfaf 100644
--- a/drivers/firmware/arm_scmi/notify.c
+++ b/drivers/firmware/arm_scmi/notify.c
@@ -1420,6 +1420,22 @@ static int scmi_notifier_register(const struct scmi_handle *handle,
return ret;
}

+int scmi_protocol_notifier_register(const struct scmi_handle *handle,
+ struct scmi_protocol_notifier *pno)
+{
+ int ret;
+
+ ret = scmi_notifier_register(handle, pno->proto_id, pno->evt_id,
+ pno->src_id, pno->nb);
+ if (ret)
+ return ret;
+
+ /* Make sure registration is visible */
+ smp_store_release(&pno->registered, true);
+
+ return 0;
+}
+
/**
* scmi_notifier_unregister() - Unregister a notifier_block for an event
* @handle: The handle identifying the platform instance against which the
@@ -1477,6 +1493,24 @@ static int scmi_notifier_unregister(const struct scmi_handle *handle,
return 0;
}

+int scmi_protocol_notifier_unregister(const struct scmi_handle *handle,
+ struct scmi_protocol_notifier *pno)
+{
+ int ret;
+
+ /* Make sure de-registration is visible BEFORE calling unregister */
+ smp_store_release(&pno->registered, false);
+ ret = scmi_notifier_unregister(handle, pno->proto_id, pno->evt_id,
+ pno->src_id, pno->nb);
+ if (WARN_ON(ret)) {
+ /* Make sure rollback is visible */
+ smp_store_release(&pno->registered, true);
+ return ret;
+ }
+
+ return 0;
+}
+
struct scmi_notifier_devres {
const struct scmi_handle *handle;
u8 proto_id;
diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h
index 15ad5162e37a..b7f29003a7dc 100644
--- a/drivers/firmware/arm_scmi/protocols.h
+++ b/drivers/firmware/arm_scmi/protocols.h
@@ -166,6 +166,13 @@ struct scmi_proto_helpers_ops;
* can be used by the protocol implementation to generate SCMI messages.
* @set_priv: A method to set protocol private data for this instance.
* @get_priv: A method to get protocol private data previously set.
+ * @instance_notifier_register: A method to register interest for specific
+ * notifications from within a protocol
+ * implementation unit: ONLY one instance notifier
+ * can be registered per-protocol.
+ * The related notifier block will be registered
+ * right after the protocol is initialized and
+ * automatically removed on protocol release.
*
* This structure represents a protocol initialized against specific SCMI
* instance and it will be used as follows:
@@ -185,6 +192,9 @@ struct scmi_protocol_handle {
const struct scmi_proto_helpers_ops *hops;
int (*set_priv)(const struct scmi_protocol_handle *ph, void *priv);
void *(*get_priv)(const struct scmi_protocol_handle *ph);
+ int (*instance_notifier_register)(const struct scmi_protocol_handle *ph,
+ u8 evt_id, const u32 *src_id,
+ struct notifier_block *nb);
};

/**
--
2.54.0