[PATCH v8 13/23] firmware: arm-scmi: Add telemetry generic event support

From: Cristian Marussi

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


Add support to allow SCMI driver users to subscribe/unsubscribe to specific
Telemetry abstract (non protocol) events.

This enables SCMI drivers to subscribe to available events by registering
an eventfd context that the core SCMI stack will use as a signalling
mechanisms.

Signed-off-by: Cristian Marussi <cristian.marussi@xxxxxxx>
---
drivers/firmware/arm_scmi/telemetry.c | 100 ++++++++++++++++++++++++++
include/linux/scmi_protocol.h | 15 ++++
2 files changed, 115 insertions(+)

diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
index a74849410856..1de5c0db2f8a 100644
--- a/drivers/firmware/arm_scmi/telemetry.c
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -12,6 +12,7 @@
#include <linux/completion.h>
#include <linux/err.h>
#include <linux/delay.h>
+#include <linux/eventfd.h>
#include <linux/io.h>
#include <linux/limits.h>
#include <linux/minmax.h>
@@ -505,6 +506,11 @@ enum de_state {
ENA_MAX
};

+struct telemetry_event {
+ struct eventfd_ctx *ctx;
+ struct list_head item;
+};
+
struct telemetry_info {
bool streaming_mode;
unsigned int num_shmti;
@@ -529,6 +535,9 @@ struct telemetry_info {
struct list_head free_des;
struct list_head fcs_des;
struct scmi_telemetry_info info;
+ /* Mutex to protect access to @events */
+ struct mutex events_mtx;
+ struct list_head events[SCMI_TLM_EVT_MAX];
struct notifier_block telemetry_nb;
atomic_t rinfo_initializing;
struct completion rinfo_initdone;
@@ -548,6 +557,21 @@ static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
static inline void scmi_telemetry_uuid_link(struct telemetry_de *tde,
struct telemetry_uuid *uuid);

+static int scmi_telemetry_event_signal(struct telemetry_info *ti,
+ enum scmi_telemetry_event type)
+{
+ struct telemetry_event *evt;
+
+ if (type >= SCMI_TLM_EVT_MAX)
+ return -EINVAL;
+
+ guard(mutex)(&ti->events_mtx);
+ list_for_each_entry(evt, &ti->events[type], item)
+ eventfd_signal(evt->ctx);
+
+ return 0;
+}
+
static inline void
scmi_telemetry_de_state_update(struct telemetry_info *ti, enum de_state state,
bool *current_state, const bool next_state)
@@ -3097,6 +3121,54 @@ static int scmi_telemetry_reset(const struct scmi_protocol_handle *ph)
return ret;
}

+static int scmi_telemetry_event_subscribe(const struct scmi_protocol_handle *ph,
+ enum scmi_telemetry_event type,
+ struct eventfd_ctx *ctx)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct telemetry_event *evt;
+
+ if (type >= SCMI_TLM_EVT_MAX)
+ return -EINVAL;
+
+ evt = kzalloc_obj(*evt);
+ if (!evt)
+ return -ENOMEM;
+
+ evt->ctx = ctx;
+ guard(mutex)(&ti->events_mtx);
+ list_add(&evt->item, &ti->events[type]);
+
+ trace_scmi_tlm_access(0, "TLM_EVT_SUBS", 0, 0);
+
+ return 0;
+}
+
+static int scmi_telemetry_event_unsubscribe(const struct scmi_protocol_handle *ph,
+ enum scmi_telemetry_event type,
+ struct eventfd_ctx *ctx)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct telemetry_event *evt, *n;
+
+ if (type >= SCMI_TLM_EVT_MAX)
+ return -EINVAL;
+
+ guard(mutex)(&ti->events_mtx);
+ list_for_each_entry_safe(evt, n, &ti->events[type], item) {
+ if (evt->ctx == ctx) {
+ list_del(&evt->item);
+ kfree(evt);
+
+ trace_scmi_tlm_access(0, "TLM_EVT_UNSUBS", 0, 0);
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.info_get = scmi_telemetry_info_get,
.de_lookup = scmi_telemetry_de_lookup,
@@ -3108,6 +3180,8 @@ static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.des_bulk_read = scmi_telemetry_des_bulk_read,
.des_sample_get = scmi_telemetry_des_sample_get,
.reset = scmi_telemetry_reset,
+ .event_subscribe = scmi_telemetry_event_subscribe,
+ .event_unsubscribe = scmi_telemetry_event_unsubscribe,
};

static bool
@@ -3496,6 +3570,9 @@ static int scmi_telemetry_instance_init(struct telemetry_info *ti)
if (ret)
return ret;

+ for (int i = 0; i < SCMI_TLM_EVT_MAX; i++)
+ INIT_LIST_HEAD(&ti->events[i]);
+ mutex_init(&ti->events_mtx);
atomic_set(&ti->des_enabled[ENA_STATE], 0);
atomic_set(&ti->des_enabled[ENA_TSTAMP], 0);
/* Setup resources lazy initialization */
@@ -3577,10 +3654,33 @@ static int scmi_telemetry_protocol_init(const struct scmi_protocol_handle *ph)
return 0;
}

+static int scmi_telemetry_protocol_deinit(const struct scmi_protocol_handle *ph)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ //TODO maybe better on general cleanup
+
+ guard(mutex)(&ti->events_mtx);
+ /* Clear any residual events */
+ for (int type = 0; type < SCMI_TLM_EVT_MAX; type++) {
+ struct telemetry_event *evt, *n;
+
+ list_for_each_entry_safe(evt, n, &ti->events[type], item) {
+ dev_warn(ti->ph->dev,
+ "Found UN-SUBSCRIBED event type %d!\n", type);
+ list_del(&evt->item);
+ kfree(evt);
+ }
+ }
+
+ return 0;
+}
+
static const struct scmi_protocol scmi_telemetry = {
.id = SCMI_PROTOCOL_TELEMETRY,
.owner = THIS_MODULE,
.instance_init = &scmi_telemetry_protocol_init,
+ .instance_deinit = &scmi_telemetry_protocol_deinit,
.ops = &tlm_proto_ops,
.events = &tlm_protocol_events,
.supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index 2d571c78613b..82aacb7fdc94 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -11,6 +11,7 @@
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/device.h>
+#include <linux/eventfd.h>
#include <linux/notifier.h>
#include <linux/types.h>
#include <linux/uuid.h>
@@ -994,6 +995,10 @@ struct scmi_telemetry_de_sample {
unsigned long long val;
};

+enum scmi_telemetry_event {
+ SCMI_TLM_EVT_MAX
+};
+
/**
* struct scmi_telemetry_proto_ops - represents the various operations provided
* by SCMI Telemetry Protocol
@@ -1017,6 +1022,10 @@ struct scmi_telemetry_de_sample {
* This causes an immediate update platform-side of all the
* enabled DEs.
* @reset: reset configuration and telemetry data.
+ * @event_subscribe: subscribe to the specified event @type using the provided
+ * @eventfd_ctx.
+ * @event_unsubscribe: unsubscribe to the specified event @type the previously
+ * registered @eventfd_ctx.
*/
struct scmi_telemetry_proto_ops {
const struct scmi_telemetry_info __must_check *(*info_get)
@@ -1044,6 +1053,12 @@ struct scmi_telemetry_proto_ops {
int __must_check (*des_sample_get)(const struct scmi_protocol_handle *ph,
int grp_id, int *num_samples,
struct scmi_telemetry_de_sample *samples);
+ int (*event_subscribe)(const struct scmi_protocol_handle *ph,
+ enum scmi_telemetry_event type,
+ struct eventfd_ctx *ctx);
+ int (*event_unsubscribe)(const struct scmi_protocol_handle *ph,
+ enum scmi_telemetry_event type,
+ struct eventfd_ctx *ctx);
int (*reset)(const struct scmi_protocol_handle *ph);
};

--
2.54.0