* [PATCH v5 00/23] Introduce SCMI Telemetry support
@ 2026-07-03 12:35 Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 01/23] firmware: arm_scmi: Add new SCMIv4.0 error codes definitions Cristian Marussi
` (23 more replies)
0 siblings, 24 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:35 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi
Hi all,
[TLDR Summary]
- This series introduces a new driver to support ARM SCMI Telemetry
- The previous (till v4) FS-based ABI driver is gone: no equivalent
FUSE support is planned, either, due to the implicit performance hit
- A new simple chardev driver exposing am IOCTLs ABI is proposed
- V5 focus is on the new UAPI (i.e. definitely missing to address some
previous V4 reviews...they will be taken care in v6)
The upcoming SCMI v4.0 specification [0] introduces a new SCMI protocol
dedicated to System Telemetry.
In a nutshell, the SCMI Telemetry protocol allows an agent to discover at
runtime the set of Telemetry Data Events (DEs) available on a specific
platform and provides the means to configure the set of DEs that a user is
interested into, while reading them back using the collection method that
is deeemed more suitable for the usecase at hand. (...amongst the various
possible collection methods allowed by SCMI specification)
Without delving into the gory details of the whole SCMI Telemetry protocol
let's just say that the SCMI platform/server firmware advertises a number
of Telemetry Data Events, each one identified by a 32bit unique ID, and an
SCMI agent/client, like Linux, can discover them and read back at will the
associated data value in a number of ways.
Data collection is mainly intended to happen on demand via shared memory
areas exposed by the platform firmware, discovered dynamically via SCMI
Telemetry and accessed by Linux on-demand, but some DE can also be reported
via SCMI Notifications asynchronous messages or via direct dedicated
FastChannels (another kind of SCMI memory based access): all of this
underlying mechanism is anyway hidden to the user since it is mediated by
the kernel driver which will return the proper data value when queried.
Anyway, the set of well-known architected DE IDs defined by the spec is
limited to a dozen IDs, which means that the vast majority of DE IDs are
customizable per-platform: as a consequence, though, the same ID, say
'0x1234', could represent completely different things on different systems.
Precise definitions and semantic of such custom Data Event IDs are out of
the scope of the SCMI Telemetry specification and of this implementation:
they are supposed to be provided using some kind of JSON-like description
file that will have to be consumed by a userspace tool which would be
finally in charge of making sense of the set of available DEs.
IOW, in turn, this means that even though the DEs enumerated via SCMI come
with some sort of topological and qualitative description provided by the
protocol (like unit of measurements, name, topology info etc), kernel-wise
we CANNOT be completely sure of "what is what" without being fed-back some
sort of information about the DEs by the afore mentioned userspace tool.
For these reasons, currently this series does NOT attempt to register any
of these DEs with any of the usual in-kernel subsystems (like HWMON, IIO,
PERF etc), simply because we cannot be sure which DE is suitable, or even
desirable, for a given subsystem. This also means there are NO in-kernel
users of these Telemetry data events as of now.
So, while we do not exclude, for the future, to feed/register some of the
discovered DEs to/with some of the above mentioned Kernel subsystems, as
of now we have ONLY modeled a custom userspace API to make SCMI Telemetry
available to userspace tools.
With V5 we drop the FS-based ABI approach, as it has been recently rejected
upstream, and we move to a pure chardev/IOCTL approach.
INTERFACES
For each discovered SCMI Instance a character device named tlm_<N> is
created under /dev/scmi/ subtree.
The IOCTL interface described at 'include/uapi/linux/scmi.h' is made
available to enumerate and configure Telemetry resources: Telemetry data
can be collected using a few different IOCTls, depending on the required
granularity.
Alternatively it is possible to obtain a list of the file descriptors
referencing directly the underlying SCMI Telemetry SHMTI memory areas and
implement in user space an SCMI Telemetry parser accessing directly the
SHMTI, while staying in compliance with the SCMI TDCF format.
NOTE THAT from v3 onwards the firmware interface level NOW supports ONLY
the latest SCMI v4.0 specification [0].
KNOWN ISSUES/IMPROVEMENTS/TODO
- still to look at Sashiko complaints
- a few V4-related review comments to address
- review SCMI Telemetry in-protocol notification handling
- maybe better to have a single unified TLM_CFG IOCTL with a variable
payload composed of TLV items ?
- what about RO static resource enumeration via mmaped areas as an
alternative to a bunch of enumeration IOCTLs that we have now ?
- maybe better to have one single mmap'able area where all the TDCFs
are collected at proper offsets, instead of one distinct mmap for each
TDCF ?
- add generation counter IOCTL support
- add Telemetry RESET IOCTL support
- stlm tool utility is crappy and minimal ... as of now just for testing
or development...serious rework needed to make it upstreamable
Based on V7.2-rc1, tested on an emulated setup.
This series is available also at [1].
If you still reading...any feedback welcome :P
Thanks,
Cristian
----
v4 --> v5
- rebased on v7.2-rc1
- dropped FileSystem based driver
- introduced a new simple chardev SCMI driver using Telemetry
- reworked/reviewed the v4 IOCTLs based UAPI
- UAPI: better struct alignment and comments
- UAI: Removed flexible array members
- UAPI: make SCMI Telemetry protocol stack completely independent from
uapi defs
- UAPI: new ioctls support to enable RAW mmap direct access to SCMI SHMTI
areas from userspace
- added new ABI Documentation
- added new SCMI core facility to lookup the current SCMI instance ID
v3 --> v4
- rebased on v7.1-rc7
- updatded doc to detail Concurrency model
- bail out on FW_BUG errors
- make all_des_enable/all_des_tstamp_enable entry readable
- refactored access to TDE values
- refactored common accessors for tlm_priv (FIX WARN on kfree)
- make all files by default world readable and user writable (if needed)
- added uid/god/umask mount options (and docs)
- added generation counter to aid spotting config changes (and docs)
- added DebugFS configurable support to debug/dump SHMTI areas (and docs)
- hide FS entries when NOT supported (like des_simple_sample_read)
- fixed output format of des/<NNN>/value to -> <TS> <VALUE>
- renamed top-dir by_components to by-components
- add a .remove method to SCMI System Telemetry Driver
- use kzalloc_obj
V2 --> V3
- rebased on v7.0-rc5
- ported the firmware interface to SCMI v4.0 BETA
- split the SCMI protocol layer in a lot of small patches
- completd filesystem and ABI documentation
- renamed components subtree to by_components
- fixed uninitialized var in scmi_telemetry_de_subdir_symlink
- renamd tstamp_exp to tstamp_rate
- swap logic in scmi_telemetry_initial_state_lookup
- use memcpy_from_le32 where required
- changed a dfew dev_err into Telemetry traces
- define and use new helper scmi_telemetry_de_unlink
- simplify a few assignments with ternary ops
- added a missing __mmust_check on the internal SCMI API
- reworked and clarified de_data_read returned errno:
ENODATA vs EINVAL vs ENODEV/ENOENT
- removed some risky/unneeded devres allocations
- various checkpatch fixes
- reworked and clarified usage of traces in Telemetry
- added the missing DT binding for protocol 0x1B
- split out unrelated change around notification from patch
adding support for protocol internal notifier
- more comments
V1 --> V2
- rebased on v6.19-rc3
- harden TDCF shared memory areas accesses by using proper accessors
- reworked protocol resources lifecycle to allow lazy enumeration
- using NEW FS mount API
- reworked FS inode allocation to use a std kmem_cache
- fixed a few IOCTLs support routine to support lazy enumeration
- added (RFC) a new FS lazy mount option to support lazily population of
some subtrees of the FS (des/ groups/ components/)
- reworked implementation of components/ alternative FS view to use
symlinks instead of hardlinks
- added a basic simple (RFC) testing tool to exercise UAPI ioctls interface
- hardened Telmetry protocol and driver to support partial out-of-spec FW
lacking some cmds (best effort)
- reworked probing races handling
- reviewed behaviour on unmount/unload
- added support for Boot_ON Telemetry by supporting SCMI Telemetry cmds:
+ DE_ENABLED_LIST
+ CONFIG_GET
- added FS and ABI docs
RFC --> V1
---
- moved from SysFS/chardev to a full fledged FS
- added support for SCMI Telemetry BLK timestamps
Thanks,
Cristian
[0]: https://developer.arm.com/documentation/den0056/f/?lang=en
[1]: https://git.kernel.org/pub/scm/linux/kernel/git/cris/linux.git/log/?h=scmi_telemetry_ng_V5
Cristian Marussi (23):
firmware: arm_scmi: Add new SCMIv4.0 error codes definitions
firmware: arm_scmi: Reduce the scope of protocols mutex
firmware: arm_scmi: Allow registration of unknown-size events/reports
firmware: arm_scmi: Allow protocols to register for notifications
dt-bindings: firmware: arm,scmi: Add support for telemetry protocol
include: trace: Add Telemetry trace events
firmware: arm_scmi: Add basic Telemetry support
firmware: arm_scmi: Add support to parse SHMTIs areas
firmware: arm_scmi: Add Telemetry configuration operations
firmware: arm_scmi: Add Telemetry DataEvent read capabilities
firmware: arm_scmi: Add support for Telemetry reset
firmware: arm_scmi: Add Telemetry notification support
firmware: arm_scmi: Add support for boot-on Telemetry
firmware: arm_scmi: Add Telemetry generation counter
firmware: arm_scmi: Add common per-protocol debugfs support
firmware: arm_scmi: Add Telemetry debugfs SHMTI dump support
firmware: arm_scmi: Add Telemetry debugfs ABI documentation
firmware: arm_scmi: Expose per-instance identifier
uapi: Add ARM SCMI Telemetry definitions
firmware: arm_scmi: Add System Telemetry driver
docs: ioctl-number: Add SCMI Ioctls
Documentation: Add SCMI System Telemetry documentation
[RFC] tools/scmi: Add SCMI Telemetry testing tool
Documentation/ABI/testing/debugfs-scmi | 22 +
.../bindings/firmware/arm,scmi.yaml | 8 +
Documentation/userspace-api/index.rst | 1 +
.../userspace-api/ioctl/ioctl-number.rst | 1 +
Documentation/userspace-api/stlm.rst | 143 +
MAINTAINERS | 1 +
drivers/firmware/arm_scmi/Kconfig | 24 +
drivers/firmware/arm_scmi/Makefile | 3 +-
drivers/firmware/arm_scmi/common.h | 10 +
drivers/firmware/arm_scmi/driver.c | 93 +-
drivers/firmware/arm_scmi/notify.c | 30 +-
drivers/firmware/arm_scmi/notify.h | 8 +-
drivers/firmware/arm_scmi/protocols.h | 13 +
.../firmware/arm_scmi/scmi_system_telemetry.c | 1233 ++++++
drivers/firmware/arm_scmi/telemetry.c | 3318 +++++++++++++++++
include/linux/scmi_protocol.h | 254 +-
include/trace/events/scmi.h | 48 +-
include/uapi/linux/scmi.h | 359 ++
tools/testing/scmi/Makefile | 25 +
tools/testing/scmi/stlm.c | 648 ++++
20 files changed, 6202 insertions(+), 40 deletions(-)
create mode 100644 Documentation/userspace-api/stlm.rst
create mode 100644 drivers/firmware/arm_scmi/scmi_system_telemetry.c
create mode 100644 drivers/firmware/arm_scmi/telemetry.c
create mode 100644 include/uapi/linux/scmi.h
create mode 100644 tools/testing/scmi/Makefile
create mode 100644 tools/testing/scmi/stlm.c
--
2.54.0
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v5 01/23] firmware: arm_scmi: Add new SCMIv4.0 error codes definitions
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
@ 2026-07-03 12:35 ` Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 02/23] firmware: arm_scmi: Reduce the scope of protocols mutex Cristian Marussi
` (22 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:35 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi
SCMIv4.0 introduces a couple of new possible protocol error codes: add
the needed definitions and mappings to Linux error values.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v4 --> v5
- map SCMI_ERR_IN_USE to EBUSY as natural
---
drivers/firmware/arm_scmi/common.h | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index be0ed89b3524..767f08be416c 100644
--- a/drivers/firmware/arm_scmi/common.h
+++ b/drivers/firmware/arm_scmi/common.h
@@ -46,6 +46,8 @@ enum scmi_error_codes {
SCMI_ERR_GENERIC = -8, /* Generic Error */
SCMI_ERR_HARDWARE = -9, /* Hardware Error */
SCMI_ERR_PROTOCOL = -10,/* Protocol Error */
+ SCMI_ERR_IN_USE = -11, /* In Use Error */
+ SCMI_ERR_PARTIAL = -12, /* Partial Error */
};
static const int scmi_linux_errmap[] = {
@@ -61,6 +63,8 @@ static const int scmi_linux_errmap[] = {
-EIO, /* SCMI_ERR_GENERIC */
-EREMOTEIO, /* SCMI_ERR_HARDWARE */
-EPROTO, /* SCMI_ERR_PROTOCOL */
+ -EBUSY, /* SCMI_ERR_IN_USE */
+ -EINVAL, /* SCMI_ERR_PARTIAL */
};
static inline int scmi_to_linux_errno(int errno)
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v5 02/23] firmware: arm_scmi: Reduce the scope of protocols mutex
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 01/23] firmware: arm_scmi: Add new SCMIv4.0 error codes definitions Cristian Marussi
@ 2026-07-03 12:35 ` Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 03/23] firmware: arm_scmi: Allow registration of unknown-size events/reports Cristian Marussi
` (21 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:35 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi
Currently the mutex dedicated to the protection of the list of registered
protocols is held during all the protocol initialization phase.
Such a wide locking region is not needed and causes problem when trying to
initialize notifications from within a protocol initialization routine.
Reduce the scope of the protocol mutex.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v1-->v2
- Fixed improper mixed usage of cleanup and goto constructs
---
drivers/firmware/arm_scmi/driver.c | 49 ++++++++++++++----------------
1 file changed, 23 insertions(+), 26 deletions(-)
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index 8e06e40d1a11..b616b73df957 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -2224,7 +2224,6 @@ static void scmi_protocol_version_initialize(struct device *dev,
* all resources management is handled via a dedicated per-protocol devres
* group.
*
- * Context: Assumes to be called with @protocols_mtx already acquired.
* Return: A reference to a freshly allocated and initialized protocol instance
* or ERR_PTR on failure. On failure the @proto reference is at first
* put using @scmi_protocol_put() before releasing all the devres group.
@@ -2270,8 +2269,10 @@ scmi_alloc_init_protocol_instance(struct scmi_info *info,
if (ret)
goto clean;
- ret = idr_alloc(&info->protocols, pi, proto->id, proto->id + 1,
- GFP_KERNEL);
+ /* Finally register the initialized protocol */
+ mutex_lock(&info->protocols_mtx);
+ ret = idr_alloc(&info->protocols, pi, proto->id, proto->id + 1, GFP_KERNEL);
+ mutex_unlock(&info->protocols_mtx);
if (ret != proto->id)
goto clean;
@@ -2318,27 +2319,25 @@ scmi_alloc_init_protocol_instance(struct scmi_info *info,
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 scmi_info *info = handle_to_scmi_info(handle);
+ const struct scmi_protocol *proto;
- mutex_lock(&info->protocols_mtx);
- pi = idr_find(&info->protocols, protocol_id);
-
- if (pi) {
- refcount_inc(&pi->users);
- } else {
- const struct scmi_protocol *proto;
+ scoped_guard(mutex, &info->protocols_mtx) {
+ struct scmi_protocol_instance *pi;
- /* 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);
+ pi = idr_find(&info->protocols, protocol_id);
+ if (pi) {
+ refcount_inc(&pi->users);
+ return pi;
+ }
}
- mutex_unlock(&info->protocols_mtx);
- return pi;
+ /* Fails if protocol not registered on bus */
+ proto = scmi_protocol_get(protocol_id, &info->version);
+ if (!proto)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ return scmi_alloc_init_protocol_instance(info, proto);
}
/**
@@ -2369,10 +2368,11 @@ void scmi_protocol_release(const struct scmi_handle *handle, u8 protocol_id)
struct scmi_info *info = handle_to_scmi_info(handle);
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 (refcount_dec_and_test(&pi->users)) {
void *gid = pi->gid;
@@ -2391,9 +2391,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,
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v5 03/23] firmware: arm_scmi: Allow registration of unknown-size events/reports
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 01/23] firmware: arm_scmi: Add new SCMIv4.0 error codes definitions Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 02/23] firmware: arm_scmi: Reduce the scope of protocols mutex Cristian Marussi
@ 2026-07-03 12:35 ` Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 04/23] firmware: arm_scmi: Allow protocols to register for notifications Cristian Marussi
` (20 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:35 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi
Allow protocols to register events with build-time unknown sizes: such
events can be declared zero-sized and let the core SCMI stack perform the
needed safe-net boundary checks based on the configured transport size.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2 --> v3
- split out of previous patch on protocol notifier
- use max() instead of max_t()
---
drivers/firmware/arm_scmi/notify.c | 24 +++++++++++++++++++-----
drivers/firmware/arm_scmi/notify.h | 8 ++++++--
2 files changed, 25 insertions(+), 7 deletions(-)
diff --git a/drivers/firmware/arm_scmi/notify.c b/drivers/firmware/arm_scmi/notify.c
index 40ec184eedae..3e4c97ab7b61 100644
--- a/drivers/firmware/arm_scmi/notify.c
+++ b/drivers/firmware/arm_scmi/notify.c
@@ -595,7 +595,13 @@ int scmi_notify(const struct scmi_handle *handle, u8 proto_id, u8 evt_id,
if (!r_evt)
return -EINVAL;
- if (len > r_evt->evt->max_payld_sz) {
+ /*
+ * Events with a zero max_payld_sz are sized to be of the maximum
+ * size allowed by the transport: no need to be size-checked here
+ * since the transport layer would have already dropped such
+ * over-sized messages.
+ */
+ if (r_evt->evt->max_payld_sz && len > r_evt->evt->max_payld_sz) {
dev_err(handle->dev, "discard badly sized message\n");
return -EINVAL;
}
@@ -754,7 +760,7 @@ int scmi_register_protocol_events(const struct scmi_handle *handle, u8 proto_id,
const struct scmi_protocol_handle *ph,
const struct scmi_protocol_events *ee)
{
- int i;
+ int i, max_msg_sz;
unsigned int num_sources;
size_t payld_sz = 0;
struct scmi_registered_events_desc *pd;
@@ -769,6 +775,8 @@ int scmi_register_protocol_events(const struct scmi_handle *handle, u8 proto_id,
if (!ni)
return -ENOMEM;
+ max_msg_sz = ph->hops->get_max_msg_size(ph);
+
/* num_sources cannot be <= 0 */
if (ee->num_sources) {
num_sources = ee->num_sources;
@@ -781,8 +789,13 @@ int scmi_register_protocol_events(const struct scmi_handle *handle, u8 proto_id,
}
evt = ee->evts;
- for (i = 0; i < ee->num_events; i++)
- payld_sz = max_t(size_t, payld_sz, evt[i].max_payld_sz);
+ for (i = 0; i < ee->num_events; i++) {
+ if (evt[i].max_payld_sz == 0) {
+ payld_sz = max_msg_sz;
+ break;
+ }
+ payld_sz = max(payld_sz, evt[i].max_payld_sz);
+ }
payld_sz += sizeof(struct scmi_event_header);
pd = scmi_allocate_registered_events_desc(ni, proto_id, ee->queue_sz,
@@ -811,7 +824,8 @@ int scmi_register_protocol_events(const struct scmi_handle *handle, u8 proto_id,
mutex_init(&r_evt->sources_mtx);
r_evt->report = devm_kzalloc(ni->handle->dev,
- evt->max_report_sz, GFP_KERNEL);
+ evt->max_report_sz ?: max_msg_sz,
+ GFP_KERNEL);
if (!r_evt->report)
return -ENOMEM;
diff --git a/drivers/firmware/arm_scmi/notify.h b/drivers/firmware/arm_scmi/notify.h
index 76758a736cf4..ecfa4b746487 100644
--- a/drivers/firmware/arm_scmi/notify.h
+++ b/drivers/firmware/arm_scmi/notify.h
@@ -18,8 +18,12 @@
/**
* struct scmi_event - Describes an event to be supported
* @id: Event ID
- * @max_payld_sz: Max possible size for the payload of a notification message
- * @max_report_sz: Max possible size for the report of a notification message
+ * @max_payld_sz: Max possible size for the payload of a notification message.
+ * Set to zero to use the maximum payload size allowed by the
+ * transport.
+ * @max_report_sz: Max possible size for the report of a notification message.
+ * Set to zero to use the maximum payload size allowed by the
+ * transport.
*
* Each SCMI protocol, during its initialization phase, can describe the events
* it wishes to support in a few struct scmi_event and pass them to the core
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v5 04/23] firmware: arm_scmi: Allow protocols to register for notifications
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
` (2 preceding siblings ...)
2026-07-03 12:35 ` [PATCH v5 03/23] firmware: arm_scmi: Allow registration of unknown-size events/reports Cristian Marussi
@ 2026-07-03 12:35 ` Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 05/23] dt-bindings: firmware: arm,scmi: Add support for telemetry protocol Cristian Marussi
` (19 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:35 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi
Allow protocols themselves to register for their own notifications and
provide their own notifier callbacks.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2-->v3
- split out unrelated changes on event sizing
v1-->v2
- Fixed multiline comment format
---
drivers/firmware/arm_scmi/common.h | 4 ++++
drivers/firmware/arm_scmi/driver.c | 12 ++++++++++++
drivers/firmware/arm_scmi/notify.c | 6 +++---
drivers/firmware/arm_scmi/protocols.h | 6 ++++++
4 files changed, 25 insertions(+), 3 deletions(-)
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index 767f08be416c..b9cd542302fb 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>
@@ -711,5 +712,8 @@ static struct platform_driver __drv = { \
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_notifier_register(const struct scmi_handle *handle, u8 proto_id,
+ u8 evt_id, const u32 *src_id,
+ struct notifier_block *nb);
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 b616b73df957..4d4bd2d4a858 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -1657,6 +1657,17 @@ 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)
+{
+ const struct scmi_protocol_instance *pi = ph_to_pi(ph);
+
+ return scmi_notifier_register(pi->handle, pi->proto->id,
+ evt_id, src_id, nb);
+}
+
static const struct scmi_xfer_ops xfer_ops = {
.xfer_get_init = xfer_get_init,
.reset_rx_to_maxsz = reset_rx_to_maxsz,
@@ -2256,6 +2267,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.notifier_register = scmi_register_instance_notifier;
refcount_set(&pi->users, 1);
/*
diff --git a/drivers/firmware/arm_scmi/notify.c b/drivers/firmware/arm_scmi/notify.c
index 3e4c97ab7b61..2a8efdf0bab8 100644
--- a/drivers/firmware/arm_scmi/notify.c
+++ b/drivers/firmware/arm_scmi/notify.c
@@ -1389,9 +1389,9 @@ static int scmi_event_handler_enable_events(struct scmi_event_handler *hndl)
*
* Return: 0 on Success
*/
-static int scmi_notifier_register(const struct scmi_handle *handle,
- u8 proto_id, u8 evt_id, const u32 *src_id,
- struct notifier_block *nb)
+int scmi_notifier_register(const struct scmi_handle *handle,
+ u8 proto_id, u8 evt_id, const u32 *src_id,
+ struct notifier_block *nb)
{
int ret = 0;
u32 evt_key;
diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h
index 15ad5162e37a..898d80c50f75 100644
--- a/drivers/firmware/arm_scmi/protocols.h
+++ b/drivers/firmware/arm_scmi/protocols.h
@@ -166,6 +166,9 @@ 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.
+ * @notifier_register: A method to register interest for notifications from
+ * within a protocol implementation unit: notifiers can
+ * be registered only for the same protocol.
*
* This structure represents a protocol initialized against specific SCMI
* instance and it will be used as follows:
@@ -185,6 +188,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 (*notifier_register)(const struct scmi_protocol_handle *ph,
+ u8 evt_id, const u32 *src_id,
+ struct notifier_block *nb);
};
/**
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v5 05/23] dt-bindings: firmware: arm,scmi: Add support for telemetry protocol
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
` (3 preceding siblings ...)
2026-07-03 12:35 ` [PATCH v5 04/23] firmware: arm_scmi: Allow protocols to register for notifications Cristian Marussi
@ 2026-07-03 12:35 ` Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 06/23] include: trace: Add Telemetry trace events Cristian Marussi
` (18 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:35 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi, Rob Herring (Arm),
Krzysztof Kozlowski, Conor Dooley, devicetree
Add new SCMI v4.0 Telemetry protocol bindings definitions.
Acked-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v3 --> v4
- changed protocol number to lowercase 1b
- fixed misplaced block for protocol 0x1b
Cc: Rob Herring <robh@kernel.org>
Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
Cc: Conor Dooley <conor+dt@kernel.org>
Cc: devicetree@vger.kernel.org
---
Documentation/devicetree/bindings/firmware/arm,scmi.yaml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/Documentation/devicetree/bindings/firmware/arm,scmi.yaml b/Documentation/devicetree/bindings/firmware/arm,scmi.yaml
index d06cca9273c4..be733f8d60a0 100644
--- a/Documentation/devicetree/bindings/firmware/arm,scmi.yaml
+++ b/Documentation/devicetree/bindings/firmware/arm,scmi.yaml
@@ -324,6 +324,14 @@ properties:
required:
- reg
+ protocol@1b:
+ $ref: '#/$defs/protocol-node'
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ const: 0x1b
+
unevaluatedProperties: false
$defs:
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v5 06/23] include: trace: Add Telemetry trace events
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
` (4 preceding siblings ...)
2026-07-03 12:35 ` [PATCH v5 05/23] dt-bindings: firmware: arm,scmi: Add support for telemetry protocol Cristian Marussi
@ 2026-07-03 12:35 ` Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 07/23] firmware: arm_scmi: Add basic Telemetry support Cristian Marussi
` (17 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:35 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi
Add custom traces to report Telemetry failed accesses and to report when DE
values are updated internally after a notification is processed.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
include/trace/events/scmi.h | 48 ++++++++++++++++++++++++++++++++++++-
1 file changed, 47 insertions(+), 1 deletion(-)
diff --git a/include/trace/events/scmi.h b/include/trace/events/scmi.h
index 703b7bb68e44..b70b26e467b8 100644
--- a/include/trace/events/scmi.h
+++ b/include/trace/events/scmi.h
@@ -7,7 +7,8 @@
#include <linux/tracepoint.h>
-#define TRACE_SCMI_MAX_TAG_LEN 6
+#define TRACE_SCMI_MAX_TAG_LEN 6
+#define TRACE_SCMI_TLM_MAX_TAG_LEN 16
TRACE_EVENT(scmi_fc_call,
TP_PROTO(u8 protocol_id, u8 msg_id, u32 res_id, u32 val1, u32 val2),
@@ -180,6 +181,51 @@ TRACE_EVENT(scmi_msg_dump,
__entry->tag, __entry->msg_id, __entry->seq, __entry->status,
__print_hex_str(__get_dynamic_array(cmd), __entry->len))
);
+
+TRACE_EVENT(scmi_tlm_access,
+ TP_PROTO(u64 de_id, unsigned char *tag, u64 startm, u64 endm),
+ TP_ARGS(de_id, tag, startm, endm),
+
+ TP_STRUCT__entry(
+ __field(u64, de_id)
+ __array(char, tag, TRACE_SCMI_TLM_MAX_TAG_LEN)
+ __field(u64, startm)
+ __field(u64, endm)
+ ),
+
+ TP_fast_assign(
+ __entry->de_id = de_id;
+ strscpy(__entry->tag, tag, TRACE_SCMI_TLM_MAX_TAG_LEN);
+ __entry->startm = startm;
+ __entry->endm = endm;
+ ),
+
+ TP_printk("de_id=0x%llX [%s] - startm=%016llX endm=%016llX",
+ __entry->de_id, __entry->tag, __entry->startm, __entry->endm)
+);
+
+TRACE_EVENT(scmi_tlm_collect,
+ TP_PROTO(u64 ts, u64 de_id, u64 value, unsigned char *tag),
+ TP_ARGS(ts, de_id, value, tag),
+
+ TP_STRUCT__entry(
+ __field(u64, ts)
+ __field(u64, de_id)
+ __field(u64, value)
+ __array(char, tag, TRACE_SCMI_TLM_MAX_TAG_LEN)
+ ),
+
+ TP_fast_assign(
+ __entry->ts = ts;
+ __entry->de_id = de_id;
+ __entry->value = value;
+ strscpy(__entry->tag, tag, TRACE_SCMI_TLM_MAX_TAG_LEN);
+ ),
+
+ TP_printk("ts=%llu de_id=0x%04llX value=%016llu [%s]",
+ __entry->ts, __entry->de_id, __entry->value, __entry->tag)
+);
+
#endif /* _TRACE_SCMI_H */
/* This part must be outside protection */
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v5 07/23] firmware: arm_scmi: Add basic Telemetry support
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
` (5 preceding siblings ...)
2026-07-03 12:35 ` [PATCH v5 06/23] include: trace: Add Telemetry trace events Cristian Marussi
@ 2026-07-03 12:35 ` Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 08/23] firmware: arm_scmi: Add support to parse SHMTIs areas Cristian Marussi
` (16 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:35 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi
Add SCMIv4.0 Telemetry basic support to enable initialization and resources
enumeration: add all the telemetry messages definitions and parsing logic
but only a few simple state gathering protocol operations.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v4 --> v5
- fix de->grp_id initialization for DEs not belonging to a group
- removed UAPI structures dependency
- expose discovered SHMTIs areas in scmi_protocol.h
- fixes sparse warnings
v3 -->v4
- bail-out on FW_BUG errors
- count timestamp capable DE to track enables
v2 --> v3
- split from monolithic Telemetry patch
- fix checkpatch macros complaints
- fix ACCESS_PRIVATE usage
- add a few comments on allocation/enumeration lifetime
- use interval.num_intervals
- removed needless cleanup handler usage
- simply return from scmi_telemetry_de_lookup()
- fixed composing_des name length to 08X
---
drivers/firmware/arm_scmi/Makefile | 2 +-
drivers/firmware/arm_scmi/driver.c | 2 +
drivers/firmware/arm_scmi/protocols.h | 1 +
drivers/firmware/arm_scmi/telemetry.c | 1394 +++++++++++++++++++++++++
include/linux/scmi_protocol.h | 181 +++-
5 files changed, 1578 insertions(+), 2 deletions(-)
create mode 100644 drivers/firmware/arm_scmi/telemetry.c
diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
index 780cd62b2f78..fe55b7aa0707 100644
--- a/drivers/firmware/arm_scmi/Makefile
+++ b/drivers/firmware/arm_scmi/Makefile
@@ -8,7 +8,7 @@ scmi-driver-$(CONFIG_ARM_SCMI_RAW_MODE_SUPPORT) += raw_mode.o
scmi-transport-$(CONFIG_ARM_SCMI_HAVE_SHMEM) = shmem.o
scmi-transport-$(CONFIG_ARM_SCMI_HAVE_MSG) += msg.o
scmi-protocols-y := base.o clock.o perf.o power.o reset.o sensors.o system.o voltage.o powercap.o
-scmi-protocols-y += pinctrl.o
+scmi-protocols-y += pinctrl.o telemetry.o
scmi-module-objs := $(scmi-driver-y) $(scmi-protocols-y) $(scmi-transport-y)
obj-$(CONFIG_ARM_SCMI_PROTOCOL) += transports/
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index 4d4bd2d4a858..f933b8b9e29c 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -3548,6 +3548,7 @@ static int __init scmi_driver_init(void)
scmi_system_register();
scmi_powercap_register();
scmi_pinctrl_register();
+ scmi_telemetry_register();
return platform_driver_register(&scmi_driver);
}
@@ -3566,6 +3567,7 @@ static void __exit scmi_driver_exit(void)
scmi_system_unregister();
scmi_powercap_unregister();
scmi_pinctrl_unregister();
+ scmi_telemetry_unregister();
platform_driver_unregister(&scmi_driver);
diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h
index 898d80c50f75..cc67e512fb6a 100644
--- a/drivers/firmware/arm_scmi/protocols.h
+++ b/drivers/firmware/arm_scmi/protocols.h
@@ -397,5 +397,6 @@ DECLARE_SCMI_REGISTER_UNREGISTER(sensors);
DECLARE_SCMI_REGISTER_UNREGISTER(voltage);
DECLARE_SCMI_REGISTER_UNREGISTER(system);
DECLARE_SCMI_REGISTER_UNREGISTER(powercap);
+DECLARE_SCMI_REGISTER_UNREGISTER(telemetry);
#endif /* _SCMI_PROTOCOLS_H */
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
new file mode 100644
index 000000000000..629a496d6d99
--- /dev/null
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -0,0 +1,1394 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * System Control and Management Interface (SCMI) Telemetry Protocol
+ *
+ * Copyright (C) 2026 ARM Ltd.
+ */
+
+#include <linux/atomic.h>
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/compiler_types.h>
+#include <linux/completion.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/refcount.h>
+#include <linux/slab.h>
+#include <linux/sprintf.h>
+#include <linux/string.h>
+#include <linux/xarray.h>
+
+#include "protocols.h"
+#include "notify.h"
+
+#include <trace/events/scmi.h>
+
+/* Updated only after ALL the mandatory features for that version are merged */
+#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x10000
+
+#define SCMI_TLM_TDCF_MAX_RETRIES 5
+
+enum scmi_telemetry_protocol_cmd {
+ TELEMETRY_LIST_SHMTI = 0x3,
+ TELEMETRY_DE_DESCRIPTION = 0x4,
+ TELEMETRY_LIST_UPDATE_INTERVALS = 0x5,
+ TELEMETRY_DE_CONFIGURE = 0x6,
+ TELEMETRY_DE_ENABLED_LIST = 0x7,
+ TELEMETRY_CONFIG_SET = 0x8,
+ TELEMETRY_READING_COMPLETE = TELEMETRY_CONFIG_SET,
+ TELEMETRY_CONFIG_GET = 0x9,
+ TELEMETRY_RESET = 0xA,
+};
+
+struct scmi_msg_resp_telemetry_protocol_attributes {
+ __le32 de_num;
+ __le32 groups_num;
+ __le32 de_implementation_rev_dword[SCMI_TLM_DE_IMPL_MAX_DWORDS];
+ __le32 attributes;
+#define SUPPORTS_SINGLE_READ(x) (le32_get_bits((x), BIT(31)))
+#define SUPPORTS_CONTINUOS_UPDATE(x) (le32_get_bits((x), BIT(30)))
+#define SUPPORTS_PER_GROUP_CONFIG(x) (le32_get_bits((x), BIT(18)))
+#define SUPPORTS_RESET(x) (le32_get_bits((x), BIT(17)))
+#define SUPPORTS_FC(x) (le32_get_bits((x), BIT(16)))
+ __le32 default_blk_ts_rate;
+};
+
+struct scmi_telemetry_update_notify_payld {
+ __le32 agent_id;
+ __le32 status;
+ __le32 num_dwords;
+ __le32 array[] __counted_by(num_dwords);
+};
+
+struct scmi_shmti_desc {
+ __le32 id;
+ __le32 addr_low;
+ __le32 addr_high;
+ __le32 length;
+ __le32 flags;
+};
+
+struct scmi_msg_resp_telemetry_shmti_list {
+ __le32 num_shmti;
+ struct scmi_shmti_desc desc[] __counted_by(num_shmti);
+};
+
+struct de_desc_fc {
+ __le32 addr_low;
+ __le32 addr_high;
+ __le32 size;
+};
+
+struct scmi_de_desc {
+ __le32 id;
+ __le32 grp_id;
+ __le32 data_sz;
+ __le32 attr_1;
+#define IS_NAME_SUPPORTED(d) (le32_get_bits((d)->attr_1, BIT(31)))
+#define IS_FC_SUPPORTED(d) (le32_get_bits((d)->attr_1, BIT(30)))
+#define GET_DE_TYPE(d) (le32_get_bits((d)->attr_1, GENMASK(29, 22)))
+#define IS_PERSISTENT(d) (le32_get_bits((d)->attr_1, BIT(21)))
+#define GET_DE_UNIT_EXP(d) \
+ ({ \
+ __u32 __signed_exp = \
+ le32_get_bits((d)->attr_1, GENMASK(20, 13)); \
+ \
+ sign_extend32(__signed_exp, 7); \
+ })
+#define GET_DE_UNIT(d) (le32_get_bits((d)->attr_1, GENMASK(12, 5)))
+#define TSTAMP_SUPPORT(d) (le32_get_bits((d)->attr_1, GENMASK(1, 0)))
+ __le32 attr_2;
+#define GET_DE_INSTA_ID(d) (le32_get_bits((d)->attr_2, GENMASK(31, 24)))
+#define GET_COMPO_INSTA_ID(d) (le32_get_bits((d)->attr_2, GENMASK(23, 8)))
+#define GET_COMPO_TYPE(d) (le32_get_bits((d)->attr_2, GENMASK(7, 0)))
+ __le32 reserved;
+};
+
+struct scmi_msg_resp_telemetry_de_description {
+ __le32 num_desc;
+ struct scmi_de_desc desc[] __counted_by(num_desc);
+};
+
+struct scmi_msg_telemetry_update_intervals {
+ __le32 index;
+ __le32 group_identifier;
+#define ALL_DES_NO_GROUP 0x0
+#define SPECIFIC_GROUP_DES 0x1
+#define ALL_DES_ANY_GROUP 0x2
+ __le32 flags;
+};
+
+struct scmi_msg_resp_telemetry_update_intervals {
+ __le32 flags;
+#define INTERVALS_DISCRETE(x) (le32_get_bits((x), BIT(12)) == 0)
+ __le32 intervals[];
+};
+
+struct scmi_msg_telemetry_de_enabled_list {
+ __le32 index;
+ __le32 flags;
+};
+
+struct scmi_enabled_de_desc {
+ __le32 id;
+ __le32 mode;
+};
+
+struct scmi_msg_resp_telemetry_de_enabled_list {
+ __le32 flags;
+ struct scmi_enabled_de_desc entry[];
+};
+
+struct scmi_msg_telemetry_de_configure {
+ __le32 id;
+ __le32 flags;
+#define DE_ENABLE_NO_TSTAMP BIT(0)
+#define DE_ENABLE_WTH_TSTAMP BIT(1)
+#define DE_DISABLE_ALL BIT(2)
+#define GROUP_SELECTOR BIT(3)
+#define EVENT_DE 0
+#define EVENT_GROUP 1
+#define DE_DISABLE_ONE 0x0
+};
+
+struct scmi_msg_resp_telemetry_de_configure {
+ __le32 shmti_id;
+#define IS_SHMTI_ID_VALID(x) ((x) != 0xFFFFFFFF)
+ __le32 shmti_de_offset;
+ __le32 blk_ts_offset;
+};
+
+struct scmi_msg_telemetry_config_set {
+ __le32 grp_id;
+ __le32 control;
+#define TELEMETRY_ENABLE (BIT(0))
+
+#define TELEMETRY_MODE_SET(x) (FIELD_PREP(GENMASK(4, 1), (x)))
+#define TLM_ONDEMAND (0)
+#define TLM_NOTIFS (1)
+#define TLM_SINGLE (2)
+#define TELEMETRY_MODE_ONDEMAND TELEMETRY_MODE_SET(TLM_ONDEMAND)
+#define TELEMETRY_MODE_NOTIFS TELEMETRY_MODE_SET(TLM_NOTIFS)
+#define TELEMETRY_MODE_SINGLE TELEMETRY_MODE_SET(TLM_SINGLE)
+
+#define TLM_ORPHANS (0)
+#define TLM_GROUP (1)
+#define TLM_ALL (2)
+#define TELEMETRY_SET_SELECTOR(x) (FIELD_PREP(GENMASK(8, 5), (x)))
+#define TELEMETRY_SET_SELECTOR_ORPHANS TELEMETRY_SET_SELECTOR(TLM_ORPHANS)
+#define TELEMETRY_SET_SELECTOR_GROUP TELEMETRY_SET_SELECTOR(TLM_GROUP)
+#define TELEMETRY_SET_SELECTOR_ALL TELEMETRY_SET_SELECTOR(TLM_ALL)
+ __le32 sampling_rate;
+};
+
+struct scmi_msg_resp_telemetry_reading_complete {
+ __le32 num_dwords;
+ __le32 dwords[] __counted_by(num_dwords);
+};
+
+struct scmi_msg_telemetry_config_get {
+ __le32 grp_id;
+ __le32 flags;
+#define TELEMETRY_GET_SELECTOR(x) (FIELD_PREP(GENMASK(3, 0), (x)))
+#define TELEMETRY_GET_SELECTOR_ORPHANS TELEMETRY_GET_SELECTOR(TLM_ORPHANS)
+#define TELEMETRY_GET_SELECTOR_GROUP TELEMETRY_GET_SELECTOR(TLM_GROUP)
+#define TELEMETRY_GET_SELECTOR_ALL TELEMETRY_GET_SELECTOR(TLM_ALL)
+};
+
+struct scmi_msg_resp_telemetry_config_get {
+ __le32 control;
+ __le32 sampling_rate;
+};
+
+/* TDCF */
+
+#define _I(__a) (ioread32((void __iomem *)(__a)))
+
+#define TO_CPU_64(h, l) ((((u64)(h)) << 32) | (l))
+
+/*
+ * Define the behaviour of a SHMTI scan defining what information will
+ * be gathered and which Telemetry items can be updated.
+ */
+enum scan_mode {
+ SCAN_LOOKUP, /* Update only value/tstamp */
+ SCAN_UPDATE, /* Update also location offset */
+ SCAN_DISCOVERY /* Update xa_des: allows for new DEs to be discovered */
+};
+
+struct fc_line {
+ __le32 data_low;
+ __le32 data_high;
+};
+
+struct fc_tsline {
+ __le32 data_low;
+ __le32 data_high;
+ __le32 ts_low;
+ __le32 ts_high;
+};
+
+struct line {
+ __le32 data_low;
+ __le32 data_high;
+};
+
+struct blk_tsline {
+ __le32 ts_low;
+ __le32 ts_high;
+};
+
+struct tsline {
+ __le32 data_low;
+ __le32 data_high;
+ __le32 ts_low;
+ __le32 ts_high;
+};
+
+struct uuid_line {
+ __le32 dwords[SCMI_TLM_DE_IMPL_MAX_DWORDS];
+};
+
+enum tdcf_line_types {
+ TDCF_DATA_LINE,
+ TDCF_BLK_TS_LINE,
+ TDCF_UUID_LINE,
+};
+
+struct payload {
+ __le32 meta;
+#define LINE_TYPE(x) (FIELD_GET(GENMASK(7, 4), _I(&((x)->meta))))
+#define IS_DATA_LINE(x) (LINE_TYPE(x) == TDCF_DATA_LINE)
+#define IS_BLK_TS_LINE(x) (LINE_TYPE(x) == TDCF_BLK_TS_LINE)
+#define IS_UUID_LINE(x) (LINE_TYPE(x) == TDCF_UUID_LINE)
+#define USE_BLK_TS(x) (_I(&((x)->meta)) & BIT(3))
+#define HAS_LINE_EXT(x) (_I(&((x)->meta)) & BIT(2))
+#define LINE_TS_VALID(x) (_I(&((x)->meta)) & BIT(1))
+#define DATA_INVALID(x) (_I(&((x)->meta)) & BIT(0))
+#define BLK_TS_INVALID(p) \
+({ \
+ typeof(p) _p = (p); \
+ bool invalid; \
+ \
+ invalid = LINE_TS_VALID(_p) || HAS_LINE_EXT(_p) || \
+ USE_BLK_TS(_p) || DATA_INVALID(_p); \
+ invalid; \
+})
+
+#define UUID_INVALID(p) \
+({ \
+ typeof(p) _p = (p); \
+ bool invalid; \
+ \
+ invalid = LINE_TS_VALID(_p) || USE_BLK_TS(_p) || \
+ DATA_INVALID(_p) || !HAS_LINE_EXT(_p); \
+ invalid; \
+})
+ __le32 id;
+ union {
+ struct line l;
+ struct tsline tsl;
+ struct blk_tsline blk_tsl;
+ struct uuid_line uuid_l;
+ };
+};
+
+#define PAYLD_ID(x) (_I(&(((struct payload __iomem *)(x))->id)))
+
+#define LINE_DATA_PAYLD_WORDS \
+ ((sizeof(u32) + sizeof(u32) + sizeof(struct line)) / sizeof(u32))
+#define EXT_LINE_DATA_PAYLD_WORDS \
+ ((sizeof(u32) + sizeof(u32) + sizeof(struct tsline)) / sizeof(u32))
+
+#define LINE_LENGTH_WORDS(x) \
+ (HAS_LINE_EXT((x)) ? EXT_LINE_DATA_PAYLD_WORDS : LINE_DATA_PAYLD_WORDS)
+
+#define LINE_LENGTH_QWORDS(x) ((LINE_LENGTH_WORDS(x)) / 2)
+
+struct prlg {
+ u32 sign_start;
+#define SIGNATURE_START 0x5442474E /* TBGN */
+ u32 match_start;
+ u32 num_qwords;
+ u32 hdr_meta_1;
+#define TDCF_REVISION_GET(x) (le32_get_bits((x)->hdr_meta_1, GENMASK(7, 0)))
+};
+
+struct eplg {
+ u32 match_end;
+ u32 sign_end;
+#define SIGNATURE_END 0x54454E44 /* TEND */
+};
+
+#define TDCF_EPLG_SZ (sizeof(struct eplg))
+
+struct tdcf {
+ struct prlg prlg;
+ unsigned char payld[];
+};
+
+#define QWORDS(_t) (_I(&(_t)->prlg.num_qwords))
+
+#define SHMTI_MIN_SIZE (sizeof(struct tdcf) + TDCF_EPLG_SZ)
+
+#define TDCF_START_SIGNATURE(x) (_I(&((x)->prlg.sign_start)))
+#define TDCF_START_SEQ_GET(x) (_I(&((x)->prlg.match_start)))
+#define IS_BAD_START_SEQ(s) ((s) & 0x1)
+
+#define TDCF_END_SEQ_GET(e) (_I(&((e)->match_end)))
+#define TDCF_END_SIGNATURE(e) (_I(&((e)->sign_end)))
+#define TDCF_BAD_END_SEQ GENMASK(31, 0)
+
+struct telemetry_shmti {
+ u32 flags;
+ void __iomem *base;
+ u32 last_magic;
+ struct scmi_telemetry_shmti_info info;
+};
+
+#define SHMTI_EPLG(s) \
+ ({ \
+ struct telemetry_shmti *_s = (s); \
+ struct eplg __iomem *_eplg; \
+ \
+ _eplg = _s->base + _s->info.len - TDCF_EPLG_SZ; \
+ (_eplg); \
+ })
+
+struct telemetry_line {
+ refcount_t users;
+ u32 last_magic;
+ struct payload __iomem *payld;
+ /* Protect line accesses */
+ struct mutex mtx;
+};
+
+struct telemetry_block_ts {
+ u64 last_ts;
+ u32 last_rate;
+ struct telemetry_line line;
+};
+
+#define to_blkts(l) container_of(l, struct telemetry_block_ts, line)
+
+struct telemetry_uuid {
+ u32 de_impl_version[SCMI_TLM_DE_IMPL_MAX_DWORDS];
+ struct telemetry_line line;
+};
+
+#define to_uuid(l) container_of(l, struct telemetry_uuid, line)
+
+enum timestamps {
+ TSTAMP_NONE,
+ TSTAMP_LINE,
+ TSTAMP_BLK
+};
+
+struct telemetry_de {
+ enum timestamps ts_type;
+ u32 ts_rate;
+ bool enumerated;
+ bool cached_msg;
+ void __iomem *base;
+ struct eplg __iomem *eplg;
+ u32 offset;
+ /* NOTE THAT DE data_sz is registered in scmi_telemetry_de */
+ u32 fc_size;
+ /* Protect last_val/ts/magic accesses */
+ struct mutex mtx;
+ u64 last_val;
+ u64 last_ts;
+ u32 last_magic;
+ struct list_head item;
+ struct telemetry_block_ts *bts;
+ struct telemetry_uuid *uuid;
+ struct scmi_telemetry_de de;
+};
+
+#define to_tde(d) container_of(d, struct telemetry_de, de)
+
+#define DE_ENABLED_WITH_TSTAMP 2
+
+struct telemetry_info {
+ bool streaming_mode;
+ unsigned int num_shmti;
+ unsigned int num_des_tstamp;
+ unsigned int default_blk_ts_rate;
+ const struct scmi_protocol_handle *ph;
+ struct telemetry_shmti *shmti;
+ struct telemetry_de *tdes;
+ struct scmi_telemetry_group *grps;
+ struct xarray xa_des;
+ /* Mutex to protect access to @free_des */
+ struct mutex free_mtx;
+ struct list_head free_des;
+ struct list_head fcs_des;
+ struct scmi_telemetry_info info;
+ atomic_t rinfo_initializing;
+ struct completion rinfo_initdone;
+ struct scmi_telemetry_res_info *rinfo;
+ struct scmi_telemetry_res_info *(*res_get)(struct telemetry_info *ti);
+};
+
+static struct scmi_telemetry_res_info *
+__scmi_telemetry_resources_get(struct telemetry_info *ti);
+
+static struct telemetry_de *
+scmi_telemetry_free_tde_get(struct telemetry_info *ti)
+{
+ struct telemetry_de *tde;
+
+ guard(mutex)(&ti->free_mtx);
+
+ tde = list_first_entry_or_null(&ti->free_des, struct telemetry_de, item);
+ if (!tde)
+ return tde;
+
+ list_del(&tde->item);
+
+ return tde;
+}
+
+static void scmi_telemetry_free_tde_put(struct telemetry_info *ti,
+ struct telemetry_de *tde)
+{
+ guard(mutex)(&ti->free_mtx);
+
+ list_add_tail(&tde->item, &ti->free_des);
+}
+
+static struct telemetry_de *scmi_telemetry_tde_lookup(struct telemetry_info *ti,
+ unsigned int de_id)
+{
+ struct scmi_telemetry_de *de;
+
+ de = xa_load(&ti->xa_des, de_id);
+ if (!de)
+ return NULL;
+
+ return to_tde(de);
+}
+
+static struct telemetry_de *scmi_telemetry_tde_get(struct telemetry_info *ti,
+ unsigned int de_id)
+{
+ static struct telemetry_de *tde;
+
+ /* Pick a new tde */
+ tde = scmi_telemetry_free_tde_get(ti);
+ if (!tde) {
+ dev_err(ti->ph->dev, "Cannot allocate DE for ID:0x%08X\n", de_id);
+ return ERR_PTR(-ENOSPC);
+ }
+
+ return tde;
+}
+
+static int scmi_telemetry_tde_register(struct telemetry_info *ti,
+ struct telemetry_de *tde)
+{
+ struct scmi_telemetry_res_info *rinfo = ti->rinfo;
+ int ret;
+
+ if (rinfo->num_des >= ti->info.base.num_des) {
+ ret = -ENOSPC;
+ goto err;
+ }
+
+ /* Store DE pointer by de_id ... */
+ ret = xa_insert(&ti->xa_des, tde->de.info->id, &tde->de, GFP_KERNEL);
+ if (ret)
+ goto err;
+
+ /* ... and in the general array */
+ rinfo->des[rinfo->num_des++] = &tde->de;
+
+ return 0;
+
+err:
+ dev_err(ti->ph->dev, "Cannot register DE for ID:0x%08X\n",
+ tde->de.info->id);
+
+ return ret;
+}
+
+struct scmi_tlm_de_priv {
+ struct telemetry_info *ti;
+ void *next;
+};
+
+static int
+scmi_telemetry_protocol_attributes_get(struct telemetry_info *ti)
+{
+ struct scmi_msg_resp_telemetry_protocol_attributes *resp;
+ const struct scmi_protocol_handle *ph = ti->ph;
+ struct scmi_xfer *t;
+ int ret;
+
+ ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0,
+ sizeof(*resp), &t);
+ if (ret)
+ return ret;
+
+ resp = t->rx.buf;
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret) {
+ __le32 attr = resp->attributes;
+
+ ti->info.base.num_des = le32_to_cpu(resp->de_num);
+ ti->info.base.num_groups = le32_to_cpu(resp->groups_num);
+ for (int i = 0; i < SCMI_TLM_DE_IMPL_MAX_DWORDS; i++)
+ ti->info.base.de_impl_version[i] =
+ le32_to_cpu(resp->de_implementation_rev_dword[i]);
+ ti->info.single_read_support = SUPPORTS_SINGLE_READ(attr);
+ ti->info.continuos_update_support = SUPPORTS_CONTINUOS_UPDATE(attr);
+ ti->info.per_group_config_support = SUPPORTS_PER_GROUP_CONFIG(attr);
+ ti->info.reset_support = SUPPORTS_RESET(attr);
+ ti->info.fc_support = SUPPORTS_FC(attr);
+ ti->num_shmti = le32_get_bits(attr, GENMASK(15, 0));
+ ti->default_blk_ts_rate = le32_to_cpu(resp->default_blk_ts_rate);
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static void iter_tlm_prepare_message(void *message,
+ unsigned int desc_index, const void *priv)
+{
+ put_unaligned_le32(desc_index, message);
+}
+
+static int iter_de_descr_update_state(struct scmi_iterator_state *st,
+ const void *response, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_de_description *r = response;
+ struct scmi_tlm_de_priv *p = priv;
+
+ st->num_returned = le32_get_bits(r->num_desc, GENMASK(15, 0));
+ st->num_remaining = le32_get_bits(r->num_desc, GENMASK(31, 16));
+
+ if (st->rx_len < (sizeof(*r) + sizeof(r->desc[0]) * st->num_returned))
+ return -EINVAL;
+
+ /* Initialized to first descriptor */
+ p->next = (void *)r->desc;
+
+ return 0;
+}
+
+static int scmi_telemetry_de_descriptor_parse(struct telemetry_info *ti,
+ struct telemetry_de *tde,
+ void **next)
+{
+ struct scmi_telemetry_res_info *rinfo = ti->rinfo;
+ const struct scmi_de_desc *desc = *next;
+ unsigned int grp_id;
+
+ tde->de.info->id = le32_to_cpu(desc->id);
+ grp_id = le32_to_cpu(desc->grp_id);
+ if (grp_id != SCMI_TLM_GRP_INVALID) {
+ /* Group descriptors are empty but allocated at this point */
+ if (grp_id >= ti->info.base.num_groups)
+ return -EINVAL;
+
+ /* Link to parent group */
+ tde->de.grp = &rinfo->grps[grp_id];
+ }
+
+ tde->de.info->grp_id = grp_id;
+ tde->de.info->data_sz = le32_to_cpu(desc->data_sz);
+ tde->de.info->type = GET_DE_TYPE(desc);
+ tde->de.info->unit = GET_DE_UNIT(desc);
+ tde->de.info->unit_exp = GET_DE_UNIT_EXP(desc);
+ tde->de.info->instance_id = GET_DE_INSTA_ID(desc);
+ tde->de.info->compo_instance_id = GET_COMPO_INSTA_ID(desc);
+ tde->de.info->compo_type = GET_COMPO_TYPE(desc);
+ tde->de.info->persistent = IS_PERSISTENT(desc);
+ tde->ts_type = TSTAMP_SUPPORT(desc);
+ tde->de.tstamp_support = !!tde->ts_type;
+ /* Count timestamped DEs */
+ ti->num_des_tstamp += !!tde->de.tstamp_support;
+ tde->de.fc_support = IS_FC_SUPPORTED(desc);
+ tde->de.name_support = IS_NAME_SUPPORTED(desc);
+ /* Update DE_DESCRIPTOR size for the next iteration */
+ *next += sizeof(*desc);
+
+ if (tde->ts_type == TSTAMP_LINE) {
+ u32 *line_ts_rate = *next;
+
+ tde->de.info->ts_rate = *line_ts_rate;
+
+ /* Variably sized depending on TS support */
+ *next += sizeof(*line_ts_rate);
+ } else if (tde->ts_type == TSTAMP_BLK) {
+ /* Setup default BLK TS value at first */
+ tde->de.info->ts_rate = ti->default_blk_ts_rate;
+ }
+
+ if (tde->de.fc_support) {
+ u32 size;
+ u64 phys_addr;
+ void __iomem *addr;
+ struct de_desc_fc *dfc;
+
+ dfc = *next;
+ phys_addr = le32_to_cpu(dfc->addr_low);
+ phys_addr |= (u64)le32_to_cpu(dfc->addr_high) << 32;
+
+ size = le32_to_cpu(dfc->size);
+ addr = devm_ioremap(ti->ph->dev, phys_addr, size);
+ if (!addr)
+ return -EADDRNOTAVAIL;
+
+ tde->base = addr;
+ tde->offset = 0;
+ tde->fc_size = size;
+
+ /* Add to FastChannels list */
+ list_add(&tde->item, &ti->fcs_des);
+
+ /* Variably sized depending on FC support */
+ *next += sizeof(*dfc);
+ }
+
+ if (tde->de.name_support) {
+ const char *de_name = *next;
+
+ strscpy(tde->de.info->name, de_name, SCMI_SHORT_NAME_MAX_SIZE);
+ /* Variably sized depending on name support */
+ *next += SCMI_SHORT_NAME_MAX_SIZE;
+ }
+
+ return 0;
+}
+
+static int iter_de_descr_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st,
+ void *priv)
+{
+ struct scmi_tlm_de_priv *p = priv;
+ struct telemetry_info *ti = p->ti;
+ const struct scmi_de_desc *desc = p->next;
+ struct telemetry_de *tde;
+ bool discovered = false;
+ unsigned int de_id;
+ int ret;
+
+ de_id = le32_to_cpu(desc->id);
+ /* Check if this DE has already been discovered by other means... */
+ tde = scmi_telemetry_tde_lookup(ti, de_id);
+ if (!tde) {
+ /* Create a new one */
+ tde = scmi_telemetry_tde_get(ti, de_id);
+ if (IS_ERR(tde))
+ return PTR_ERR(tde);
+
+ discovered = true;
+ } else if (tde->enumerated) {
+ /* Cannot be a duplicate of a DE already created by enumeration */
+ dev_err(ph->dev,
+ "Discovered INVALID DE with DUPLICATED ID:0x%08X\n",
+ de_id);
+ return -EINVAL;
+ }
+
+ ret = scmi_telemetry_de_descriptor_parse(ti, tde, &p->next);
+ if (ret)
+ goto err;
+
+ if (discovered) {
+ /* Register if it was not already ... */
+ ret = scmi_telemetry_tde_register(ti, tde);
+ if (ret)
+ goto err;
+
+ tde->enumerated = true;
+ }
+
+ /* Account for this DE in group num_de counter */
+ if (tde->de.grp)
+ tde->de.grp->info->num_des++;
+
+ return 0;
+
+err:
+ /* DE not enumerated at this point were created in this call */
+ if (!tde->enumerated)
+ scmi_telemetry_free_tde_put(ti, tde);
+
+ return ret;
+}
+
+static int
+scmi_telemetry_de_groups_init(struct device *dev, struct telemetry_info *ti)
+{
+ struct scmi_telemetry_res_info *rinfo = ti->rinfo;
+
+ /* Allocate all groups DEs IDs arrays at first ... */
+ for (int i = 0; i < ti->info.base.num_groups; i++) {
+ struct scmi_telemetry_group *grp = &rinfo->grps[i];
+ size_t des_str_sz;
+
+ unsigned int *des __free(kfree) = kcalloc(grp->info->num_des,
+ sizeof(unsigned int),
+ GFP_KERNEL);
+ if (!des)
+ return -ENOMEM;
+
+ /*
+ * Max size 32bit ID string in Hex: 0xCAFECAFE
+ * - 10 digits + ' '/'\n' = 11 bytes per number
+ * - terminating NUL character
+ */
+ des_str_sz = grp->info->num_des * 11 + 1;
+ char *des_str __free(kfree) = kzalloc(des_str_sz, GFP_KERNEL);
+ if (!des_str)
+ return -ENOMEM;
+
+ grp->des = no_free_ptr(des);
+ grp->des_str = no_free_ptr(des_str);
+ /* Reset group DE counter */
+ grp->info->num_des = 0;
+ }
+
+ /* Scan DEs and populate DE IDs arrays for all groups */
+ for (int i = 0; i < rinfo->num_des; i++) {
+ struct scmi_telemetry_group *grp = rinfo->des[i]->grp;
+
+ if (!grp)
+ continue;
+
+ /*
+ * Note that, at this point, num_des is guaranteed to be
+ * sane (in-bounds) by construction.
+ */
+ grp->des[grp->info->num_des++] = i;
+ }
+
+ /* Build composing DES string */
+ for (int i = 0; i < ti->info.base.num_groups; i++) {
+ struct scmi_telemetry_group *grp = &rinfo->grps[i];
+ size_t bufsize = grp->info->num_des * 11 + 1;
+ char *buf = grp->des_str;
+
+ for (int j = 0; j < grp->info->num_des; j++) {
+ char term = j != (grp->info->num_des - 1) ? ' ' : '\0';
+ int len;
+
+ len = scnprintf(buf, bufsize, "0x%08X%c",
+ rinfo->des[grp->des[j]]->info->id, term);
+
+ buf += len;
+ bufsize -= len;
+ }
+ }
+
+ rinfo->num_groups = ti->info.base.num_groups;
+
+ return 0;
+}
+
+static int scmi_telemetry_de_descriptors_get(struct telemetry_info *ti)
+{
+ const struct scmi_protocol_handle *ph = ti->ph;
+
+ struct scmi_iterator_ops ops = {
+ .prepare_message = iter_tlm_prepare_message,
+ .update_state = iter_de_descr_update_state,
+ .process_response = iter_de_descr_process_response,
+ };
+ struct scmi_tlm_de_priv tpriv = {
+ .ti = ti,
+ .next = NULL,
+ };
+ void *iter;
+ int ret;
+
+ if (!ti->info.base.num_des)
+ return 0;
+
+ iter = ph->hops->iter_response_init(ph, &ops, ti->info.base.num_des,
+ TELEMETRY_DE_DESCRIPTION,
+ sizeof(u32), &tpriv);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+
+ ret = ph->hops->iter_response_run(iter);
+ if (ret)
+ return ret;
+
+ return scmi_telemetry_de_groups_init(ph->dev, ti);
+}
+
+struct scmi_tlm_ivl_priv {
+ struct device *dev;
+ struct scmi_telemetry_intervals **intrvs;
+ unsigned int grp_id;
+ unsigned int flags;
+};
+
+static void iter_intervals_prepare_message(void *message,
+ unsigned int desc_index,
+ const void *priv)
+{
+ struct scmi_msg_telemetry_update_intervals *msg = message;
+ const struct scmi_tlm_ivl_priv *p = priv;
+
+ msg->index = cpu_to_le32(desc_index);
+ msg->group_identifier = cpu_to_le32(p->grp_id);
+ msg->flags = cpu_to_le32(FIELD_PREP(GENMASK(3, 0), p->flags));
+}
+
+static int iter_intervals_update_state(struct scmi_iterator_state *st,
+ const void *response, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_update_intervals *r = response;
+
+ st->num_returned = le32_get_bits(r->flags, GENMASK(11, 0));
+ st->num_remaining = le32_get_bits(r->flags, GENMASK(31, 16));
+
+ if (st->rx_len < (sizeof(*r) + sizeof(r->intervals[0]) * st->num_returned))
+ return -EINVAL;
+
+ /*
+ * total intervals is not declared previously anywhere so we
+ * assume it's returned+remaining on first call.
+ */
+ if (!st->max_resources) {
+ struct scmi_tlm_ivl_priv *p = priv;
+ struct scmi_telemetry_intervals *intrvs;
+ bool discrete;
+ int inum;
+
+ discrete = INTERVALS_DISCRETE(r->flags);
+ /* Check consistency on first call */
+ if (!discrete && (st->num_returned != 3 || st->num_remaining != 0))
+ return -EINVAL;
+
+ inum = st->num_returned + st->num_remaining;
+ intrvs = kzalloc(sizeof(*intrvs) + inum * sizeof(__u32), GFP_KERNEL);
+ if (!intrvs)
+ return -ENOMEM;
+
+ intrvs->num_intervals = inum;
+ intrvs->discrete = discrete;
+ st->max_resources = intrvs->num_intervals;
+
+ *p->intrvs = intrvs;
+ }
+
+ return 0;
+}
+
+static int
+iter_intervals_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_update_intervals *r = response;
+ struct scmi_tlm_ivl_priv *p = priv;
+ struct scmi_telemetry_intervals *intrvs = *p->intrvs;
+ unsigned int idx = st->loop_idx;
+
+ intrvs->update_intervals[st->desc_index + idx] =
+ le32_to_cpu(r->intervals[idx]);
+
+ return 0;
+}
+
+static int
+scmi_tlm_enumerate_update_intervals(struct telemetry_info *ti,
+ struct scmi_telemetry_intervals **intervals,
+ int grp_id, unsigned int flags)
+{
+ struct scmi_iterator_ops ops = {
+ .prepare_message = iter_intervals_prepare_message,
+ .update_state = iter_intervals_update_state,
+ .process_response = iter_intervals_process_response,
+ };
+ const struct scmi_protocol_handle *ph = ti->ph;
+ struct scmi_tlm_ivl_priv ipriv = {
+ .dev = ph->dev,
+ .grp_id = grp_id,
+ .intrvs = intervals,
+ .flags = flags,
+ };
+ void *iter;
+
+ iter = ph->hops->iter_response_init(ph, &ops, 0,
+ TELEMETRY_LIST_UPDATE_INTERVALS,
+ sizeof(struct scmi_msg_telemetry_update_intervals),
+ &ipriv);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+
+ return ph->hops->iter_response_run(iter);
+}
+
+static int
+scmi_telemetry_enumerate_groups_intervals(struct telemetry_info *ti)
+{
+ struct scmi_telemetry_res_info *rinfo = ti->rinfo;
+
+ if (!ti->info.per_group_config_support)
+ return 0;
+
+ for (int id = 0; id < rinfo->num_groups; id++) {
+ int ret;
+
+ ret = scmi_tlm_enumerate_update_intervals(ti,
+ &rinfo->grps[id].intervals,
+ id, SPECIFIC_GROUP_DES);
+ if (ret)
+ return ret;
+
+ rinfo->grps_store[id].num_intervals =
+ rinfo->grps[id].intervals->num_intervals;
+ }
+
+ return 0;
+}
+
+static void scmi_telemetry_intervals_free(void *interval)
+{
+ kfree(interval);
+}
+
+static int
+scmi_telemetry_enumerate_common_intervals(struct telemetry_info *ti)
+{
+ unsigned int flags;
+ int ret;
+
+ flags = !ti->info.per_group_config_support ?
+ ALL_DES_ANY_GROUP : ALL_DES_NO_GROUP;
+
+ ret = scmi_tlm_enumerate_update_intervals(ti, &ti->info.intervals,
+ SCMI_TLM_GRP_INVALID, flags);
+ if (ret)
+ return ret;
+
+ /* A copy for UAPI access... */
+ ti->info.base.num_intervals = ti->info.intervals->num_intervals;
+
+ /* Delegate freeing of allocated intervals to unbind time */
+ return devm_add_action_or_reset(ti->ph->dev,
+ scmi_telemetry_intervals_free,
+ ti->info.intervals);
+}
+
+static int iter_shmti_update_state(struct scmi_iterator_state *st,
+ const void *response, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_shmti_list *r = response;
+
+ st->num_returned = le32_get_bits(r->num_shmti, GENMASK(15, 0));
+ st->num_remaining = le32_get_bits(r->num_shmti, GENMASK(31, 16));
+
+ if (st->rx_len < (sizeof(*r) + sizeof(r->desc[0]) * st->num_returned))
+ return -EINVAL;
+
+ return 0;
+}
+
+static inline int
+scmi_telemetry_shmti_validate(struct device *dev, struct telemetry_shmti *shmti)
+{
+ struct tdcf __iomem *tdcf = shmti->base;
+ u32 sign_start, sign_end;
+
+ sign_start = TDCF_START_SIGNATURE(tdcf);
+ sign_end = TDCF_END_SIGNATURE(SHMTI_EPLG(shmti));
+
+ if (sign_start != SIGNATURE_START || sign_end != SIGNATURE_END) {
+ dev_err(dev,
+ "BAD signature for SHMTI ID:%u @phys:%pK - START:0x%04X END:0x%04X\n",
+ shmti->info.sid, shmti->base, sign_start, sign_end);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int iter_shmti_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st,
+ void *priv)
+{
+ const struct scmi_msg_resp_telemetry_shmti_list *r = response;
+ struct telemetry_info *ti = priv;
+ struct telemetry_shmti *shmti;
+ const struct scmi_shmti_desc *desc;
+ void __iomem *addr;
+ u64 phys_addr;
+ u32 len;
+
+ desc = &r->desc[st->loop_idx];
+ shmti = &ti->shmti[st->desc_index + st->loop_idx];
+
+ shmti->info.sid = le32_to_cpu(desc->id);
+ shmti->flags = le32_to_cpu(desc->flags);
+ phys_addr = le32_to_cpu(desc->addr_low);
+ phys_addr |= (u64)le32_to_cpu(desc->addr_high) << 32;
+
+ len = le32_to_cpu(desc->length);
+ if (len < SHMTI_MIN_SIZE) {
+ dev_err(ph->dev, "Invalid length for SHMTI ID:%u len:%u\n",
+ shmti->info.sid, len);
+ return -EINVAL;
+ }
+
+ addr = devm_ioremap(ph->dev, phys_addr, len);
+ if (!addr)
+ return -EADDRNOTAVAIL;
+
+ shmti->base = addr;
+ shmti->info.len = len;
+ shmti->info.phys = phys_addr;
+ shmti->info.offset = shmti->info.phys & ~PAGE_MASK;
+
+ return scmi_telemetry_shmti_validate(ph->dev, shmti);
+}
+
+static int scmi_telemetry_shmti_list(const struct scmi_protocol_handle *ph,
+ struct telemetry_info *ti)
+{
+ struct scmi_iterator_ops ops = {
+ .prepare_message = iter_tlm_prepare_message,
+ .update_state = iter_shmti_update_state,
+ .process_response = iter_shmti_process_response,
+ };
+ void *iter;
+
+ iter = ph->hops->iter_response_init(ph, &ops, ti->info.base.num_des,
+ TELEMETRY_LIST_SHMTI,
+ sizeof(u32), ti);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+
+ return ph->hops->iter_response_run(iter);
+}
+
+static int scmi_telemetry_enumerate_shmti(struct telemetry_info *ti)
+{
+ const struct scmi_protocol_handle *ph = ti->ph;
+ struct scmi_telemetry_shmti_info **shmtis;
+ int ret;
+
+ if (!ti->num_shmti)
+ return 0;
+
+ ti->shmti = devm_kcalloc(ph->dev, ti->num_shmti, sizeof(*ti->shmti),
+ GFP_KERNEL);
+ if (!ti->shmti)
+ return -ENOMEM;
+
+ ret = scmi_telemetry_shmti_list(ph, ti);
+ if (ret) {
+ dev_err(ph->dev, "Cannot get SHMTI list descriptors");
+ return ret;
+ }
+
+ /* Expose initialized SHMTIs references */
+ shmtis = devm_kcalloc(ph->dev, ti->num_shmti,
+ sizeof(*shmtis), GFP_KERNEL);
+ if (!shmtis)
+ return -ENOMEM;
+
+ for (int i = 0; i < ti->num_shmti; i++)
+ shmtis[i] = &ti->shmti[i].info;
+
+ ti->info.shmtis = shmtis;
+ ti->info.base.num_shmtis = ti->num_shmti;
+
+ return 0;
+}
+
+static const struct scmi_telemetry_info *
+scmi_telemetry_info_get(const struct scmi_protocol_handle *ph)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ return &ti->info;
+}
+
+static const struct scmi_telemetry_de *
+scmi_telemetry_de_lookup(const struct scmi_protocol_handle *ph, u32 id)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ ti->res_get(ti);
+ return xa_load(&ti->xa_des, id);
+}
+
+static const struct scmi_telemetry_res_info *
+scmi_telemetry_resources_get(const struct scmi_protocol_handle *ph)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ return ti->res_get(ti);
+}
+
+static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
+ .info_get = scmi_telemetry_info_get,
+ .de_lookup = scmi_telemetry_de_lookup,
+ .res_get = scmi_telemetry_resources_get,
+};
+
+/**
+ * scmi_telemetry_resources_alloc - Resources allocation
+ * @ti: A reference to the telemetry info descriptor for this instance
+ *
+ * This allocates and initializes dedicated resources for the maximum possible
+ * number of needed telemetry resources, based on information gathered from
+ * the initial enumeration: these allocations represent an upper bound on
+ * the number of discoverable telemetry resources and they will be later
+ * populated during late deferred further discovery phases.
+ *
+ * Return: 0 on Success, errno otherwise
+ */
+static int scmi_telemetry_resources_alloc(struct telemetry_info *ti)
+{
+ /* Array to hold pointers to discovered DEs */
+ struct scmi_telemetry_de **des __free(kfree) =
+ kcalloc(ti->info.base.num_des, sizeof(*des), GFP_KERNEL);
+ if (!des)
+ return -ENOMEM;
+
+ /* The allocated DE descriptors */
+ struct telemetry_de *tdes __free(kfree) =
+ kcalloc(ti->info.base.num_des, sizeof(*tdes), GFP_KERNEL);
+ if (!tdes)
+ return -ENOMEM;
+
+ /* Allocate a set of contiguous DE info descriptors. */
+ struct scmi_telemetry_de_info *dei_store __free(kfree) =
+ kcalloc(ti->info.base.num_des, sizeof(*dei_store), GFP_KERNEL);
+ if (!dei_store)
+ return -ENOMEM;
+
+ /* Array to hold descriptors of discovered GROUPs */
+ struct scmi_telemetry_group *grps __free(kfree) =
+ kcalloc(ti->info.base.num_groups, sizeof(*grps), GFP_KERNEL);
+ if (!grps)
+ return -ENOMEM;
+
+ /* Allocate a set of contiguous Group info descriptors. */
+ struct scmi_telemetry_grp_info *grps_store __free(kfree) =
+ kcalloc(ti->info.base.num_groups, sizeof(*grps_store), GFP_KERNEL);
+ if (!grps_store)
+ return -ENOMEM;
+
+ struct scmi_telemetry_res_info *rinfo __free(kfree) =
+ kzalloc(sizeof(*rinfo), GFP_KERNEL);
+ if (!rinfo)
+ return -ENOMEM;
+
+ mutex_init(&ti->free_mtx);
+ INIT_LIST_HEAD(&ti->free_des);
+ for (int i = 0; i < ti->info.base.num_des; i++) {
+ mutex_init(&tdes[i].mtx);
+ /* Bind contiguous DE info structures */
+ tdes[i].de.info = &dei_store[i];
+ list_add_tail(&tdes[i].item, &ti->free_des);
+ }
+
+ for (int i = 0; i < ti->info.base.num_groups; i++) {
+ grps_store[i].grp_id = i;
+ /* Bind contiguous Group info struct */
+ grps[i].info = &grps_store[i];
+ }
+
+ INIT_LIST_HEAD(&ti->fcs_des);
+
+ ti->tdes = no_free_ptr(tdes);
+
+ rinfo->des = no_free_ptr(des);
+ rinfo->dei_store = no_free_ptr(dei_store);
+ rinfo->grps = no_free_ptr(grps);
+ rinfo->grps_store = no_free_ptr(grps_store);
+
+ ti->rinfo = no_free_ptr(rinfo);
+
+ return 0;
+}
+
+static void scmi_telemetry_resources_free(void *arg)
+{
+ struct telemetry_info *ti = arg;
+ struct scmi_telemetry_res_info *rinfo = ti->rinfo;
+
+ kfree(ti->tdes);
+ kfree(rinfo->des);
+ kfree(rinfo->dei_store);
+ kfree(rinfo->grps);
+ kfree(rinfo->grps_store);
+
+ kfree(rinfo);
+
+ ti->rinfo = NULL;
+}
+
+static struct scmi_telemetry_res_info *
+__scmi_telemetry_resources_get(struct telemetry_info *ti)
+{
+ return ti->rinfo;
+}
+
+/**
+ * scmi_telemetry_resources_enumerate - Enumeration helper
+ * @ti: A reference to the telemetry info descriptor for this instance
+ *
+ * This helper is configured to be called once on the first enumeration
+ * attempt, when triggered by invoking ti->res_get() from somewhere else.
+ * Once run it substitues itself in ti->res_get() with the simple accessor
+ * __scmi_telemetry_resources_get, which returns a descriptor to the resources
+ * that were possibly discovered.
+ *
+ * Note that, while it attempts to fully enumerate Data Events and Groups, it
+ * does NOT fail when such enumerations fail, instead it simply gives up with
+ * the end result that only a partially populated, but consistent, resources
+ * descriptor will be returned; in such a case the incomplete descriptor will
+ * be marked as NOT fully_enumerated: this design enables the kernel to deal
+ * with badly implemented out-of-spec firmware support while keep on providing
+ * a minimal sane, albeit possibly incomplete, set of telemetry respources.
+ *
+ * Return: A reference to a fully or partially populated resources descriptor
+ */
+static struct scmi_telemetry_res_info *
+scmi_telemetry_resources_enumerate(struct telemetry_info *ti)
+{
+ struct scmi_telemetry_res_info *rinfo = ti->rinfo;
+ struct device *dev = ti->ph->dev;
+ int ret;
+
+ /*
+ * Ensure this init function can be called only once and
+ * handles properly concurrent calls.
+ */
+ if (atomic_cmpxchg(&ti->rinfo_initializing, 0, 1)) {
+ if (!completion_done(&ti->rinfo_initdone))
+ wait_for_completion(&ti->rinfo_initdone);
+ goto out;
+ }
+
+ ret = scmi_telemetry_de_descriptors_get(ti);
+ if (ret) {
+ dev_err(dev, FW_BUG "Cannot fully enumerate DEs resources. Degraded system.\n");
+ goto done;
+ }
+
+ ret = scmi_telemetry_enumerate_groups_intervals(ti);
+ if (ret) {
+ dev_err(dev, FW_BUG "Cannot fully enumerate group intervals. Degraded system.\n");
+ goto done;
+ }
+
+ /* If we got here, the enumeration was fully successful */
+ rinfo->fully_enumerated = true;
+done:
+ /* Disable initialization permanently */
+ smp_store_mb(ti->res_get, __scmi_telemetry_resources_get);
+ complete_all(&ti->rinfo_initdone);
+
+out:
+ return rinfo;
+}
+
+/**
+ * scmi_telemetry_instance_init - Instance initializer
+ * @ti: A reference to the telemetry info descriptor for this instance
+ *
+ * Note that this allocates and initialize all the resources possibly needed
+ * and then setups the @scmi_telemetry_resources_enumerate helper as the
+ * default method for the first call to ti->res_get(): this mechanism enables
+ * the possibility of optionally implementing deferred enumeration policies
+ * which optionally delay the discovery phase and related SCMI message exchanges
+ * to a later point in time.
+ *
+ * Return: 0 on Success, errno otherwise
+ */
+static int scmi_telemetry_instance_init(struct telemetry_info *ti)
+{
+ int ret;
+
+ /* Allocate and Initialize on first call... */
+ ret = scmi_telemetry_resources_alloc(ti);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(ti->ph->dev,
+ scmi_telemetry_resources_free, ti);
+ if (ret)
+ return ret;
+
+ xa_init(&ti->xa_des);
+ /* Setup resources lazy initialization */
+ atomic_set(&ti->rinfo_initializing, 0);
+ init_completion(&ti->rinfo_initdone);
+ /* Ensure the new res_get() operation is visible after this point */
+ smp_store_mb(ti->res_get, scmi_telemetry_resources_enumerate);
+
+ return 0;
+}
+
+static int scmi_telemetry_protocol_init(const struct scmi_protocol_handle *ph)
+{
+ struct device *dev = ph->dev;
+ struct telemetry_info *ti;
+ int ret;
+
+ dev_dbg(dev, "Telemetry Version %d.%d\n",
+ PROTOCOL_REV_MAJOR(ph->version), PROTOCOL_REV_MINOR(ph->version));
+
+ ti = devm_kzalloc(dev, sizeof(*ti), GFP_KERNEL);
+ if (!ti)
+ return -ENOMEM;
+
+ ti->ph = ph;
+
+ ret = scmi_telemetry_protocol_attributes_get(ti);
+ if (ret) {
+ dev_err(dev, FW_BUG "Cannot retrieve protocol attributes. Abort.\n");
+ return ret;
+ }
+
+ ret = scmi_telemetry_instance_init(ti);
+ if (ret) {
+ dev_err(dev, "Cannot initialize instance. Abort.\n");
+ return ret;
+ }
+
+ ret = scmi_telemetry_enumerate_common_intervals(ti);
+ if (ret) {
+ dev_err(dev, FW_BUG "Cannot enumerate update intervals. Abort.\n");
+ return ret;
+ }
+
+ ret = scmi_telemetry_enumerate_shmti(ti);
+ if (ret) {
+ dev_err(dev, FW_BUG "Cannot enumerate SHMTIs. Abort.\n");
+ return ret;
+ }
+
+ ti->info.base.version = ph->version;
+
+ return ph->set_priv(ph, ti);
+}
+
+static const struct scmi_protocol scmi_telemetry = {
+ .id = SCMI_PROTOCOL_TELEMETRY,
+ .owner = THIS_MODULE,
+ .instance_init = &scmi_telemetry_protocol_init,
+ .ops = &tlm_proto_ops,
+ .supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
+};
+
+DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(telemetry, scmi_telemetry)
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index 5ab73b1ab9aa..a03921e9a698 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -2,13 +2,14 @@
/*
* SCMI Message Protocol driver header
*
- * Copyright (C) 2018-2021 ARM Ltd.
+ * Copyright (C) 2018-2026 ARM Ltd.
*/
#ifndef _LINUX_SCMI_PROTOCOL_H
#define _LINUX_SCMI_PROTOCOL_H
#include <linux/bitfield.h>
+#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/notifier.h>
#include <linux/types.h>
@@ -824,6 +825,183 @@ struct scmi_pinctrl_proto_ops {
int (*pin_free)(const struct scmi_protocol_handle *ph, u32 pin);
};
+enum scmi_telemetry_de_type {
+ SCMI_TLM_DE_TYPE_USPECIFIED,
+ SCMI_TLM_DE_TYPE_ACCUMUL_IDLE_RESIDENCY,
+ SCMI_TLM_DE_TYPE_ACCUMUL_IDLE_COUNTS,
+ SCMI_TLM_DE_TYPE_ACCUMUL_OTHERS,
+ SCMI_TLM_DE_TYPE_INSTA_IDLE_STATE,
+ SCMI_TLM_DE_TYPE_INSTA_OTHERS,
+ SCMI_TLM_DE_TYPE_AVERAGE,
+ SCMI_TLM_DE_TYPE_STATUS,
+ SCMI_TLM_DE_TYPE_RESERVED_START,
+ SCMI_TLM_DE_TYPE_RESERVED_END = 0xef,
+ SCMI_TLM_DE_TYPE_OEM_START = 0xf0,
+ SCMI_TLM_DE_TYPE_OEM_END = 0xff,
+};
+
+enum scmi_telemetry_compo_type {
+ SCMI_TLM_COMPO_TYPE_USPECIFIED,
+ SCMI_TLM_COMPO_TYPE_CPU,
+ SCMI_TLM_COMPO_TYPE_CLUSTER,
+ SCMI_TLM_COMPO_TYPE_GPU,
+ SCMI_TLM_COMPO_TYPE_NPU,
+ SCMI_TLM_COMPO_TYPE_INTERCONNECT,
+ SCMI_TLM_COMPO_TYPE_MEM_CNTRL,
+ SCMI_TLM_COMPO_TYPE_L1_CACHE,
+ SCMI_TLM_COMPO_TYPE_L2_CACHE,
+ SCMI_TLM_COMPO_TYPE_L3_CACHE,
+ SCMI_TLM_COMPO_TYPE_LL_CACHE,
+ SCMI_TLM_COMPO_TYPE_SYS_CACHE,
+ SCMI_TLM_COMPO_TYPE_DISP_CNTRL,
+ SCMI_TLM_COMPO_TYPE_IPU,
+ SCMI_TLM_COMPO_TYPE_CHIPLET,
+ SCMI_TLM_COMPO_TYPE_PACKAGE,
+ SCMI_TLM_COMPO_TYPE_SOC,
+ SCMI_TLM_COMPO_TYPE_SYSTEM,
+ SCMI_TLM_COMPO_TYPE_SMCU,
+ SCMI_TLM_COMPO_TYPE_ACCEL,
+ SCMI_TLM_COMPO_TYPE_BATTERY,
+ SCMI_TLM_COMPO_TYPE_CHARGER,
+ SCMI_TLM_COMPO_TYPE_PMIC,
+ SCMI_TLM_COMPO_TYPE_BOARD,
+ SCMI_TLM_COMPO_TYPE_MEMORY,
+ SCMI_TLM_COMPO_TYPE_PERIPH,
+ SCMI_TLM_COMPO_TYPE_PERIPH_SUBC,
+ SCMI_TLM_COMPO_TYPE_LID,
+ SCMI_TLM_COMPO_TYPE_DISPLAY,
+ SCMI_TLM_COMPO_TYPE_RESERVED_START = 0x1d,
+ SCMI_TLM_COMPO_TYPE_RESERVED_END = 0xdf,
+ SCMI_TLM_COMPO_TYPE_OEM_START = 0xe0,
+ SCMI_TLM_COMPO_TYPE_OEM_END = 0xff,
+};
+
+#define SCMI_TLM_GET_UPDATE_INTERVAL_SECS(x) (FIELD_GET(GENMASK(20, 5), (x)))
+#define SCMI_TLM_GET_UPDATE_INTERVAL_EXP(x) (sign_extend32((x), 4))
+
+#define SCMI_TLM_GET_UPDATE_INTERVAL(x) (FIELD_GET(GENMASK(20, 0), (x)))
+#define SCMI_TLM_BUILD_UPDATE_INTERVAL(s, e) \
+ (FIELD_PREP(GENMASK(20, 5), (s)) | FIELD_PREP(GENMASK(4, 0), (e)))
+
+enum scmi_telemetry_collection {
+ SCMI_TLM_ONDEMAND,
+ SCMI_TLM_NOTIFICATION,
+ SCMI_TLM_SINGLE_READ,
+};
+
+#define SCMI_TLM_GRP_INVALID 0xFFFFFFFF
+#define SCMI_TLM_DE_IMPL_MAX_DWORDS 4
+
+struct scmi_telemetry_intervals {
+ unsigned int grp_id;
+ bool discrete;
+ unsigned int num_intervals;
+#define SCMI_TLM_UPDATE_INTVL_SEGMENT_LOW 0
+#define SCMI_TLM_UPDATE_INTVL_SEGMENT_HIGH 1
+#define SCMI_TLM_UPDATE_INTVL_SEGMENT_STEP 2
+ unsigned int update_intervals[] __counted_by(num_intervals);
+};
+
+struct scmi_telemetry_grp_info {
+ unsigned int grp_id;
+ unsigned int num_des;
+ unsigned int num_intervals;
+};
+
+struct scmi_telemetry_group {
+ bool enabled;
+ bool tstamp_enabled;
+ unsigned int *des;
+ char *des_str;
+ struct scmi_telemetry_grp_info *info;
+ unsigned int active_update_interval;
+ struct scmi_telemetry_intervals *intervals;
+ enum scmi_telemetry_collection current_mode;
+};
+
+struct scmi_telemetry_de_info {
+ unsigned int id;
+ unsigned int grp_id;
+ unsigned int data_sz;
+ unsigned int type;
+ unsigned int unit;
+ int unit_exp;
+ unsigned int ts_rate;
+ unsigned int instance_id;
+ unsigned int compo_instance_id;
+ unsigned int compo_type;
+ bool persistent;
+ char name[16];
+};
+
+struct scmi_telemetry_de {
+ bool tstamp_support;
+ bool fc_support;
+ bool name_support;
+ struct scmi_telemetry_de_info *info;
+ struct scmi_telemetry_group *grp;
+ bool enabled;
+ bool tstamp_enabled;
+};
+
+struct scmi_telemetry_res_info {
+ bool fully_enumerated;
+ unsigned int num_des;
+ struct scmi_telemetry_de **des;
+ struct scmi_telemetry_de_info *dei_store;
+ unsigned int num_groups;
+ struct scmi_telemetry_group *grps;
+ struct scmi_telemetry_grp_info *grps_store;
+};
+
+struct scmi_telemetry_base_info {
+ unsigned int version;
+ unsigned int de_impl_version[SCMI_TLM_DE_IMPL_MAX_DWORDS];
+ unsigned int num_des;
+ unsigned int num_groups;
+ unsigned int num_intervals;
+ unsigned int num_shmtis;
+};
+
+struct scmi_telemetry_shmti_info {
+ unsigned int sid;
+ unsigned int len;
+ unsigned long offset;
+ phys_addr_t phys;
+};
+
+struct scmi_telemetry_info {
+ bool single_read_support;
+ bool continuos_update_support;
+ bool per_group_config_support;
+ bool reset_support;
+ bool fc_support;
+ struct scmi_telemetry_base_info base;
+ unsigned int active_update_interval;
+ struct scmi_telemetry_intervals *intervals;
+ struct scmi_telemetry_shmti_info **shmtis;
+ bool enabled;
+ bool notif_enabled;
+ enum scmi_telemetry_collection current_mode;
+};
+
+/**
+ * struct scmi_telemetry_proto_ops - represents the various operations provided
+ * by SCMI Telemetry Protocol
+ *
+ * @info_get: get the general Telemetry information.
+ * @de_lookup: get a specific DE descriptor from the DE id.
+ * @res_get: get a reference to the Telemetry resources descriptor.
+ */
+struct scmi_telemetry_proto_ops {
+ const struct scmi_telemetry_info __must_check *(*info_get)
+ (const struct scmi_protocol_handle *ph);
+ const struct scmi_telemetry_de __must_check *(*de_lookup)
+ (const struct scmi_protocol_handle *ph, u32 id);
+ const struct scmi_telemetry_res_info __must_check *(*res_get)
+ (const struct scmi_protocol_handle *ph);
+};
+
/**
* struct scmi_notify_ops - represents notifications' operations provided by
* SCMI core
@@ -930,6 +1108,7 @@ enum scmi_std_protocol {
SCMI_PROTOCOL_VOLTAGE = 0x17,
SCMI_PROTOCOL_POWERCAP = 0x18,
SCMI_PROTOCOL_PINCTRL = 0x19,
+ SCMI_PROTOCOL_TELEMETRY = 0x1b,
};
enum scmi_system_events {
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v5 08/23] firmware: arm_scmi: Add support to parse SHMTIs areas
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
` (6 preceding siblings ...)
2026-07-03 12:35 ` [PATCH v5 07/23] firmware: arm_scmi: Add basic Telemetry support Cristian Marussi
@ 2026-07-03 12:35 ` Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 09/23] firmware: arm_scmi: Add Telemetry configuration operations Cristian Marussi
` (15 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:35 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi
Add logic to scan the SHMTI areas, parsing the TDCF descriptors while
collecting DataEvent, BlockTimestamp and UUID lines.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v4 --> v5
- converted TDE_HAS_TSTAMP to inline scmi_tde_has_tstamp
- fixes sparse warnings
v3 --> v4
- use kzalloc_obj
- track SHMTI-discovered Telemetry DE states
- refactor TDE cache with dedicated helpers
- force tstamp to zero when timestamp is NOT supported or disabled
v2 --> v3
- split from monolithic Telemetry patch
- avoid devres allocation for resources that are added to the xa_lines XArray
- simplify prototype of line parsing helpers to drop unneeded dev
- flip tstmap logic in scmi_telemetry_line_data_parse() to properly emit
a TLM ftrace event
- use ternary ops to simplify quite a few expressions
---
drivers/firmware/arm_scmi/telemetry.c | 626 ++++++++++++++++++++++++++
1 file changed, 626 insertions(+)
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
index 629a496d6d99..82e2d80c5bff 100644
--- a/drivers/firmware/arm_scmi/telemetry.c
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -253,6 +253,23 @@ struct uuid_line {
__le32 dwords[SCMI_TLM_DE_IMPL_MAX_DWORDS];
};
+#define LINE_DATA_GET(f) \
+({ \
+ typeof(f) _f = (f); \
+ \
+ (TO_CPU_64(_I(&_f->data_high), _I(&_f->data_low))); \
+})
+
+#define LINE_TSTAMP_GET(f) \
+({ \
+ typeof(f) _f = (f); \
+ \
+ (TO_CPU_64(_I(&_f->ts_high), _I(&_f->ts_low))); \
+})
+
+#define BLK_TS_STAMP(f) LINE_TSTAMP_GET(f)
+#define BLK_TS_RATE(p) PAYLD_ID(p)
+
enum tdcf_line_types {
TDCF_DATA_LINE,
TDCF_BLK_TS_LINE,
@@ -363,6 +380,7 @@ struct telemetry_line {
refcount_t users;
u32 last_magic;
struct payload __iomem *payld;
+ struct xarray *xa_lines;
/* Protect line accesses */
struct mutex mtx;
};
@@ -411,18 +429,31 @@ struct telemetry_de {
#define to_tde(d) container_of(d, struct telemetry_de, de)
+static inline bool scmi_tde_has_tstamp(struct telemetry_de *t)
+{
+ return t->de.tstamp_support && t->de.tstamp_enabled;
+}
+
#define DE_ENABLED_WITH_TSTAMP 2
+enum de_state {
+ ENA_STATE,
+ ENA_TSTAMP,
+ ENA_MAX
+};
+
struct telemetry_info {
bool streaming_mode;
unsigned int num_shmti;
unsigned int num_des_tstamp;
+ atomic_t des_enabled[ENA_MAX];
unsigned int default_blk_ts_rate;
const struct scmi_protocol_handle *ph;
struct telemetry_shmti *shmti;
struct telemetry_de *tdes;
struct scmi_telemetry_group *grps;
struct xarray xa_des;
+ struct xarray xa_lines;
/* Mutex to protect access to @free_des */
struct mutex free_mtx;
struct list_head free_des;
@@ -437,6 +468,21 @@ struct telemetry_info {
static struct scmi_telemetry_res_info *
__scmi_telemetry_resources_get(struct telemetry_info *ti);
+static inline void
+scmi_telemetry_de_state_update(struct telemetry_info *ti, enum de_state state,
+ bool *current_state, bool next_state)
+{
+ if (!current_state || *current_state != next_state)
+ atomic_add(next_state ? 1 : -1, &ti->des_enabled[state]);
+
+ if (current_state)
+ *current_state = next_state;
+
+ dev_dbg(ti->ph->dev, "Telemetry des_enabled[%s]:%u\n",
+ state == ENA_STATE ? "STATE" : "TSTAMP",
+ atomic_read(&ti->des_enabled[state]));
+}
+
static struct telemetry_de *
scmi_telemetry_free_tde_get(struct telemetry_info *ti)
{
@@ -516,6 +562,27 @@ static int scmi_telemetry_tde_register(struct telemetry_info *ti,
return ret;
}
+static bool
+scmi_telemetry_tde_cache_unchanged(struct telemetry_de *tde, u32 magic)
+{
+ guard(mutex)(&tde->mtx);
+
+ return tde->last_magic == magic;
+}
+
+static void
+scmi_telemetry_tde_cache_update(struct telemetry_de *tde, u64 val,
+ u64 *tstamp, u32 *magic)
+{
+ guard(mutex)(&tde->mtx);
+
+ tde->last_magic = magic ? *magic : TDCF_BAD_END_SEQ;
+ tde->last_val = val;
+ tde->last_ts = tstamp && scmi_tde_has_tstamp(tde) ? *tstamp : 0;
+ if (tstamp)
+ *tstamp = tde->last_ts;
+}
+
struct scmi_tlm_de_priv {
struct telemetry_info *ti;
void *next;
@@ -1136,6 +1203,555 @@ scmi_telemetry_resources_get(const struct scmi_protocol_handle *ph)
return ti->res_get(ti);
}
+static u64
+scmi_telemetry_blkts_read(u32 magic, struct telemetry_block_ts *bts)
+{
+ if (WARN_ON(!bts || !refcount_read(&bts->line.users)))
+ return 0;
+
+ guard(mutex)(&bts->line.mtx);
+
+ if (bts->line.last_magic == magic)
+ return bts->last_ts;
+
+ /* Note that the bts->last_rate can change ONLY on creation */
+ bts->last_ts = BLK_TS_STAMP(&bts->line.payld->blk_tsl);
+ bts->line.last_magic = magic;
+
+ return bts->last_ts;
+}
+
+static void scmi_telemetry_blkts_update(struct telemetry_info *ti, u32 magic,
+ struct telemetry_block_ts *bts)
+{
+ guard(mutex)(&bts->line.mtx);
+
+ if (bts->line.last_magic != magic) {
+ bts->last_ts = BLK_TS_STAMP(&bts->line.payld->blk_tsl);
+ bts->last_rate = BLK_TS_RATE(bts->line.payld);
+ /* BLK_TS clock rate value can change ONLY here on creation */
+ if (!bts->last_rate)
+ bts->last_rate = ti->default_blk_ts_rate;
+ bts->line.last_magic = magic;
+ }
+}
+
+static void scmi_telemetry_line_put(struct telemetry_line *line, void *blob)
+{
+ if (refcount_dec_and_test(&line->users)) {
+ scoped_guard(mutex, &line->mtx)
+ xa_erase(line->xa_lines, (unsigned long)line->payld);
+ kfree(blob);
+ }
+}
+
+static void scmi_telemetry_blkts_unlink(struct telemetry_de *tde)
+{
+ scmi_telemetry_line_put(&tde->bts->line, tde->bts);
+ tde->bts = NULL;
+}
+
+static void scmi_telemetry_uuid_unlink(struct telemetry_de *tde)
+{
+ scmi_telemetry_line_put(&tde->uuid->line, tde->uuid);
+ tde->uuid = NULL;
+}
+
+static void scmi_telemetry_de_unlink(struct scmi_telemetry_de *de)
+{
+ struct telemetry_de *tde = to_tde(de);
+
+ /* Unlink all related lines triggering their deallocation */
+ if (tde->bts)
+ scmi_telemetry_blkts_unlink(tde);
+ if (tde->uuid)
+ scmi_telemetry_uuid_unlink(tde);
+}
+
+static struct telemetry_line *
+scmi_telemetry_line_get(struct xarray *xa_lines, struct payload __iomem *payld)
+{
+ struct telemetry_line *line;
+
+ line = xa_load(xa_lines, (unsigned long)payld);
+ if (!line)
+ return NULL;
+
+ refcount_inc(&line->users);
+
+ return line;
+}
+
+static int
+scmi_telemetry_line_init(struct telemetry_line *line, struct xarray *xa_lines,
+ struct payload __iomem *payld)
+{
+ refcount_set(&line->users, 1);
+ line->payld = payld;
+ line->xa_lines = xa_lines;
+ mutex_init(&line->mtx);
+
+ return xa_insert(xa_lines, (unsigned long)payld, line, GFP_KERNEL);
+}
+
+static struct telemetry_block_ts *
+scmi_telemetry_blkts_create(struct device *dev, struct xarray *xa_lines,
+ struct payload __iomem *payld)
+{
+ struct telemetry_block_ts *bts;
+ int ret;
+
+ bts = kzalloc_obj(*bts);
+ if (!bts)
+ return NULL;
+
+ ret = scmi_telemetry_line_init(&bts->line, xa_lines, payld);
+ if (ret) {
+ kfree(bts);
+ return NULL;
+ }
+
+ trace_scmi_tlm_collect(0, (__force u64)payld, 0, "SHMTI_NEW_BLKTS");
+
+ return bts;
+}
+
+static struct telemetry_block_ts *
+scmi_telemetry_blkts_get_or_create(struct device *dev, struct xarray *xa_lines,
+ struct payload __iomem *payld)
+{
+ struct telemetry_line *line;
+
+ line = scmi_telemetry_line_get(xa_lines, payld);
+ if (line)
+ return to_blkts(line);
+
+ return scmi_telemetry_blkts_create(dev, xa_lines, payld);
+}
+
+static struct telemetry_uuid *
+scmi_telemetry_uuid_create(struct device *dev, struct xarray *xa_lines,
+ struct payload __iomem *payld)
+{
+ struct telemetry_uuid *uuid;
+ struct uuid_line __iomem *uuid_l = &payld->uuid_l;
+ int ret;
+
+ uuid = kzalloc_obj(*uuid);
+ if (!uuid)
+ return NULL;
+
+ for (int i = 0; i < SCMI_TLM_DE_IMPL_MAX_DWORDS; i++)
+ uuid->de_impl_version[i] = le32_to_cpu(uuid_l->dwords[i]);
+
+ ret = scmi_telemetry_line_init(&uuid->line, xa_lines, payld);
+ if (ret) {
+ kfree(uuid);
+ return NULL;
+ }
+
+ trace_scmi_tlm_collect(0, (__force u64)payld, 0, "SHMTI_NEW_UUID");
+
+ return uuid;
+}
+
+static struct telemetry_uuid *
+scmi_telemetry_uuid_get_or_create(struct device *dev, struct xarray *xa_lines,
+ struct payload __iomem *payld)
+{
+ struct telemetry_line *line;
+
+ line = scmi_telemetry_line_get(xa_lines, payld);
+ if (line)
+ return to_uuid(line);
+
+ return scmi_telemetry_uuid_create(dev, xa_lines, payld);
+}
+
+static void scmi_telemetry_tdcf_uuid_parse(struct telemetry_info *ti,
+ struct payload __iomem *payld,
+ struct telemetry_shmti *shmti,
+ void **active_uuid)
+{
+ struct telemetry_uuid *uuid;
+
+ if (UUID_INVALID(payld)) {
+ trace_scmi_tlm_access(0, "UUID_INVALID", 0, 0);
+ return;
+ }
+
+ /* A UUID descriptor MUST be returned: it is found or it is created */
+ uuid = scmi_telemetry_uuid_get_or_create(ti->ph->dev, &ti->xa_lines,
+ payld);
+ if (WARN_ON(!uuid))
+ return;
+
+ *active_uuid = uuid;
+}
+
+static struct payload __iomem *
+scmi_telemetry_nearest_line_by_type(struct telemetry_shmti *shmti,
+ void __iomem *last, enum tdcf_line_types ltype)
+{
+ struct tdcf __iomem *tdcf = shmti->base;
+ void __iomem *next, *found = NULL;
+
+ /* Scan from start of TDCF payloads up to last_payld */
+ next = tdcf->payld;
+ while (next < last) {
+ if (LINE_TYPE((struct payload __iomem *)next) == ltype)
+ found = next;
+
+ next += LINE_LENGTH_WORDS((struct payload __iomem *)next);
+ }
+
+ return found;
+}
+
+static struct telemetry_block_ts *
+scmi_telemetry_blkts_bind(struct device *dev, struct telemetry_shmti *shmti,
+ struct payload __iomem *payld, struct xarray *xa_lines,
+ struct payload __iomem *bts_payld)
+{
+ /* Trigger a manual search when no BLK_TS payload offset was provided */
+ if (!bts_payld) {
+ /* Find the BLK_TS immediately preceding this DE payld */
+ bts_payld = scmi_telemetry_nearest_line_by_type(shmti, payld,
+ TDCF_BLK_TS_LINE);
+ if (!bts_payld)
+ return NULL;
+ }
+
+ return scmi_telemetry_blkts_get_or_create(dev, xa_lines, bts_payld);
+}
+
+/**
+ * scmi_telemetry_tdcf_blkts_parse - A BLK_TS line parser
+ *
+ * @ti: A reference to the telemetry_info descriptor
+ * @payld: TDCF payld line to process
+ * @shmti: SHMTI descriptor inside which the scan is happening
+ * @active_bts: Input/output reference to keep track of the last blk_ts found
+ *
+ * Process a valid TDCF BLK_TS line and, after having looked up or created a
+ * blk_ts descriptor, update the related data and return it as the currently
+ * active blk_ts, given that it is effectively the last found during this
+ * scan.
+ */
+static void scmi_telemetry_tdcf_blkts_parse(struct telemetry_info *ti,
+ struct payload __iomem *payld,
+ struct telemetry_shmti *shmti,
+ void **active_bts)
+{
+ struct telemetry_block_ts *bts;
+
+ /* Check for spec compliance */
+ if (BLK_TS_INVALID(payld)) {
+ trace_scmi_tlm_access(0, "BLK_TS_INVALID", 0, 0);
+ return;
+ }
+
+ /* A BLK_TS descriptor MUST be returned: it is found or it is created */
+ bts = scmi_telemetry_blkts_get_or_create(ti->ph->dev,
+ &ti->xa_lines, payld);
+ if (WARN_ON(!bts))
+ return;
+
+ /* Update the descriptor with the lastest TS */
+ scmi_telemetry_blkts_update(ti, shmti->last_magic, bts);
+ *active_bts = bts;
+}
+
+static inline struct telemetry_de *
+scmi_telemetry_tde_allocate(struct telemetry_info *ti, u32 de_id,
+ struct payload __iomem *payld)
+{
+ struct telemetry_de *tde;
+
+ tde = scmi_telemetry_tde_get(ti, de_id);
+ if (IS_ERR(tde))
+ return NULL;
+
+ tde->de.info->id = de_id;
+ tde->de.enabled = true;
+ tde->de.tstamp_enabled = LINE_TS_VALID(payld) || USE_BLK_TS(payld);
+
+ if (scmi_telemetry_tde_register(ti, tde)) {
+ scmi_telemetry_free_tde_put(ti, tde);
+ return NULL;
+ }
+
+ scmi_telemetry_de_state_update(ti, ENA_STATE, NULL, true);
+ if (tde->de.tstamp_enabled)
+ scmi_telemetry_de_state_update(ti, ENA_TSTAMP, NULL, true);
+
+ return tde;
+}
+
+static inline void
+scmi_telemetry_line_data_parse(struct telemetry_de *tde, u64 *val, u64 *tstamp,
+ struct payload __iomem *payld, u32 magic)
+{
+ /* Data is always valid since we are NOT handling BLK TS lines here */
+ *val = LINE_DATA_GET(&payld->l);
+ if (tstamp) {
+ if (USE_BLK_TS(payld)) {
+ /* Read out the actual BLK_TS */
+ *tstamp = scmi_telemetry_blkts_read(magic, tde->bts);
+ } else if (LINE_TS_VALID(payld)) {
+ /*
+ * Note that LINE_TS_VALID implies HAS_LINE_EXT and that
+ * the per DE line_ts_rate is advertised in the DE
+ * descriptor.
+ */
+ *tstamp = LINE_TSTAMP_GET(&payld->tsl);
+ } else {
+ *tstamp = 0;
+ }
+ }
+
+ trace_scmi_tlm_collect(tstamp ? *tstamp : 0, tde->de.info->id,
+ *val, "SHMTI_DE_READ");
+
+ scmi_telemetry_tde_cache_update(tde, *val, tstamp, &magic);
+}
+
+static inline void scmi_telemetry_bts_link(struct telemetry_de *tde,
+ struct telemetry_block_ts *bts)
+{
+ refcount_inc(&bts->line.users);
+ tde->bts = bts;
+ /* Update TS clock rate if provided by the BLK_TS */
+ if (tde->bts->last_rate)
+ tde->de.info->ts_rate = tde->bts->last_rate;
+}
+
+static inline void scmi_telemetry_uuid_link(struct telemetry_de *tde,
+ struct telemetry_uuid *uuid)
+{
+ refcount_inc(&uuid->line.users);
+ tde->uuid = uuid;
+}
+
+/**
+ * scmi_telemetry_tdcf_data_parse - TDCF DataLine parsing
+ * @ti: A reference to the telemetry info descriptor
+ * @payld: Line payload to parse
+ * @shmti: A reference to the containing SHMTI area
+ * @mode: A flag to determine the behaviour of the scan
+ * @active_bts: A pointer to keep track and report any found BLK timestamp line
+ * @active_uuid: A pointer to keep track and report any found UUID line
+ *
+ * This routine takes care to:
+ * - verify line consistency in relation to the used flags and the current
+ * context: e.g. is there an active preceding BLK_TS line if the DataLine
+ * sports a USE_BLKTS flag ?
+ * - verify the related Data Event ID exists OR create a brand new DE
+ * (depending on the @mode of operation)
+ * - links any active BLK_TS or UUID line to the current DE
+ * - read and save value/tstamp for the DE ONLY if anything has changed (by
+ * tracking the last TDCF magic) and update related magic: this allows to
+ * minimize future needs of single-DE reads
+ *
+ * Modes of operation.
+ *
+ * The scan behaviour depends on the chosen @mode:
+ * - SCAN_LOOKUP: the basic scan which aims to update value associated to
+ * existing DEs. Any discovered DataLine that could NOT be
+ * matched to an existing, previously discovered, DE is
+ * discarded. This is the normal scan behaviour.
+ * - SCAN_UPDATE: a more advanced scan which provides all the SCAN_LOOKUP
+ * features plus takes care to update the DEs location
+ * coordinates inside the SHMTI: note that the related DEs are
+ * still supposed to have been previously discovered when
+ * this scan runs. This is used to update location
+ * coordinates for DEs contained in a Group when such group
+ * is enabled.
+ * - SCAN_DISCOVERY: the most advanced scan available which provides all
+ * the SCAN_LOOKUP features plus discovery capabilities:
+ * any DataLine referring to a previously unknown DE leads
+ * to the allocation of a new DE descriptor.
+ * This mode is used on the first scan at init time, ONLY
+ * if Telemetry was found to be already enabled at boot on
+ * the platform side: this helps to maximize gathered
+ * information when dealing with out of spec firmwares.
+ * Any usage of this discovery mode other than in a boot-on
+ * enabled scenario is discouraged since it can easily
+ * lead to spurious DE discoveries.
+ */
+static void scmi_telemetry_tdcf_data_parse(struct telemetry_info *ti,
+ struct payload __iomem *payld,
+ struct telemetry_shmti *shmti,
+ enum scan_mode mode,
+ void *active_bts, void *active_uuid)
+{
+ bool use_blk_ts = USE_BLK_TS(payld);
+ struct telemetry_de *tde;
+ u64 val, tstamp = 0;
+ u32 de_id;
+
+ de_id = PAYLD_ID(payld);
+ /* Discard malformed lines...a preceding BLK_TS must exist */
+ if (use_blk_ts && !active_bts) {
+ trace_scmi_tlm_access(de_id, "BAD_USE_BLK_TS", 0, 0);
+ return;
+ }
+
+ /* Is this DE ID known ? */
+ tde = scmi_telemetry_tde_lookup(ti, de_id);
+ if (!tde) {
+ if (mode != SCAN_DISCOVERY) {
+ trace_scmi_tlm_access(de_id, "DE_INVALID", 0, 0);
+ return;
+ }
+
+ /* In SCAN_DISCOVERY mode we allocate new DEs for unknown IDs */
+ tde = scmi_telemetry_tde_allocate(ti, de_id, payld);
+ if (!tde)
+ return;
+ }
+
+ /* Update DE location refs if requested: normally done only on enable */
+ if (mode >= SCAN_UPDATE) {
+ tde->base = shmti->base;
+ tde->eplg = SHMTI_EPLG(shmti);
+ tde->offset = (void __iomem *)payld - (void __iomem *)shmti->base;
+
+ dev_dbg(ti->ph->dev,
+ "TDCF-updated DE_ID:0x%08X - shmti:%pK offset:%u\n",
+ tde->de.info->id, tde->base, tde->offset);
+ }
+
+ /* Has any value/tstamp really changed ?*/
+ if (scmi_telemetry_tde_cache_unchanged(tde, shmti->last_magic))
+ return;
+
+ /* Link the related BTS when needed, it's unlinked on disable */
+ if (use_blk_ts && !tde->bts)
+ scmi_telemetry_bts_link(tde, active_bts);
+
+ /* Link the active UUID when existent, it's unlinked on disable */
+ if (active_uuid)
+ scmi_telemetry_uuid_link(tde, active_uuid);
+
+ /* Parse data words */
+ scmi_telemetry_line_data_parse(tde, &val, &tstamp, payld,
+ shmti->last_magic);
+}
+
+static int scmi_telemetry_tdcf_line_parse(struct telemetry_info *ti,
+ struct payload __iomem *payld,
+ struct telemetry_shmti *shmti,
+ enum scan_mode mode,
+ void **active_bts, void **active_uuid)
+{
+ int used_qwords;
+
+ used_qwords = LINE_LENGTH_QWORDS(payld);
+ /* Invalid lines are not an error, could simply be disabled DEs */
+ if (DATA_INVALID(payld)) {
+ trace_scmi_tlm_access(PAYLD_ID(payld), "TDCF_INVALID", 0, 0);
+ return used_qwords;
+ }
+
+ switch (LINE_TYPE(payld)) {
+ case TDCF_DATA_LINE:
+ scmi_telemetry_tdcf_data_parse(ti, payld, shmti, mode,
+ *active_bts, *active_uuid);
+ break;
+ case TDCF_BLK_TS_LINE:
+ scmi_telemetry_tdcf_blkts_parse(ti, payld, shmti, active_bts);
+ break;
+ case TDCF_UUID_LINE:
+ scmi_telemetry_tdcf_uuid_parse(ti, payld, shmti, active_uuid);
+ break;
+ default:
+ trace_scmi_tlm_access(PAYLD_ID(payld), "TDCF_UNKNOWN", 0, 0);
+ break;
+ }
+
+ return used_qwords;
+}
+
+/**
+ * scmi_telemetry_shmti_scan - Full SHMTI scan
+ * @ti: A reference to the telemetry info descriptor
+ * @shmti_id: ID of the SHMTI area that has to be scanned
+ * @mode: A flag to determine the behaviour of the scan
+ *
+ * Return: 0 on Success
+ */
+static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
+ unsigned int shmti_id, enum scan_mode mode)
+{
+ struct telemetry_shmti *shmti = &ti->shmti[shmti_id];
+ struct tdcf __iomem *tdcf = shmti->base;
+ int retries = SCMI_TLM_TDCF_MAX_RETRIES;
+ u32 startm = 0, endm = TDCF_BAD_END_SEQ;
+
+ if (!tdcf)
+ return -ENODEV;
+
+ do {
+ void *active_bts = NULL, *active_uuid = NULL;
+ unsigned int qwords;
+ void __iomem *next;
+
+ /* A bit of exponential backoff between retries */
+ fsleep((SCMI_TLM_TDCF_MAX_RETRIES - retries) * 1000);
+
+ /*
+ * Note that during a full SHMTI scan the magic seq numbers are
+ * checked only at the start and at the end of the scan, NOT
+ * between each parsed line and this has these consequences:
+ * - TDCF magic numbers accesses are reduced to 2 reads
+ * - the set of values obtained from a full scan belong all
+ * to the same platform update (same magic number)
+ * - a SHMTI full scan is an all or nothing operation: when
+ * a potentially corrupted read is detected along the way
+ * (MSEQ_MISMATCH) another full scan is triggered.
+ */
+ startm = TDCF_START_SEQ_GET(tdcf);
+ if (IS_BAD_START_SEQ(startm)) {
+ trace_scmi_tlm_access(0, "MSEQ_BADSTART", startm, 0);
+ continue;
+ }
+
+ /* On a BAD_SEQ this will be updated on the next attempt */
+ shmti->last_magic = startm;
+
+ qwords = QWORDS(tdcf);
+ next = tdcf->payld;
+ while (qwords) {
+ int used_qwords;
+
+ used_qwords = scmi_telemetry_tdcf_line_parse(ti, next,
+ shmti, mode,
+ &active_bts,
+ &active_uuid);
+ if (qwords < used_qwords) {
+ trace_scmi_tlm_access(PAYLD_ID(next),
+ "BAD_QWORDS", startm, 0);
+ return -EINVAL;
+ }
+
+ next += used_qwords * 8;
+ qwords -= used_qwords;
+ }
+
+ endm = TDCF_END_SEQ_GET(SHMTI_EPLG(shmti));
+ if (startm != endm)
+ trace_scmi_tlm_access(0, "MSEQ_MISMATCH", startm, endm);
+ } while (startm != endm && --retries);
+
+ if (startm != endm) {
+ trace_scmi_tlm_access(0, "TDCF_SCAN_FAIL", startm, endm);
+ return -EPROTO;
+ }
+
+ return 0;
+}
+
static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.info_get = scmi_telemetry_info_get,
.de_lookup = scmi_telemetry_de_lookup,
@@ -1225,6 +1841,13 @@ static void scmi_telemetry_resources_free(void *arg)
struct telemetry_info *ti = arg;
struct scmi_telemetry_res_info *rinfo = ti->rinfo;
+ /*
+ * Unlinking all the BLK_TS/UUID lines related to a DE triggers also
+ * the deallocation of such lines when the embedded refcount hits zero.
+ */
+ for (int i = 0; i < rinfo->num_des; i++)
+ scmi_telemetry_de_unlink(rinfo->des[i]);
+
kfree(ti->tdes);
kfree(rinfo->des);
kfree(rinfo->dei_store);
@@ -1330,6 +1953,9 @@ static int scmi_telemetry_instance_init(struct telemetry_info *ti)
return ret;
xa_init(&ti->xa_des);
+ xa_init(&ti->xa_lines);
+ atomic_set(&ti->des_enabled[ENA_STATE], 0);
+ atomic_set(&ti->des_enabled[ENA_TSTAMP], 0);
/* Setup resources lazy initialization */
atomic_set(&ti->rinfo_initializing, 0);
init_completion(&ti->rinfo_initdone);
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v5 09/23] firmware: arm_scmi: Add Telemetry configuration operations
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
` (7 preceding siblings ...)
2026-07-03 12:35 ` [PATCH v5 08/23] firmware: arm_scmi: Add support to parse SHMTIs areas Cristian Marussi
@ 2026-07-03 12:35 ` Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 10/23] firmware: arm_scmi: Add Telemetry DataEvent read capabilities Cristian Marussi
` (14 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:35 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi
Add support for basic Telemetry configuration operations to selectively
enable or disable DataEvents monitoring.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v4 --> v5
- use renamed grp_id instead of old id in group descriptors
- remove useless grp_ignore param from collection_configure
- fixes sparse warnings
v3 --> v4
- track configured Telemetry DE states
- add aggregate Telemetry state query
v2 --> v3
- split from monolithic Telemetry patch
- simplify clenaup with scmi_telemetry_de_unlink
---
drivers/firmware/arm_scmi/telemetry.c | 373 ++++++++++++++++++++++++++
include/linux/scmi_protocol.h | 16 ++
2 files changed, 389 insertions(+)
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
index 82e2d80c5bff..26bfde78ef21 100644
--- a/drivers/firmware/arm_scmi/telemetry.c
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -468,6 +468,9 @@ struct telemetry_info {
static struct scmi_telemetry_res_info *
__scmi_telemetry_resources_get(struct telemetry_info *ti);
+static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
+ unsigned int shmti_id, enum scan_mode mode);
+
static inline void
scmi_telemetry_de_state_update(struct telemetry_info *ti, enum de_state state,
bool *current_state, bool next_state)
@@ -1752,10 +1755,380 @@ static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
return 0;
}
+static int scmi_telemetry_group_state_update(struct telemetry_info *ti,
+ struct scmi_telemetry_group *grp,
+ bool *enable, bool *tstamp)
+{
+ struct scmi_telemetry_res_info *rinfo;
+
+ rinfo = ti->res_get(ti);
+ for (int i = 0; i < grp->info->num_des; i++) {
+ struct scmi_telemetry_de *de = rinfo->des[grp->des[i]];
+
+ if (enable)
+ scmi_telemetry_de_state_update(ti, ENA_STATE,
+ &de->enabled, *enable);
+
+ if (tstamp && de->tstamp_support)
+ scmi_telemetry_de_state_update(ti, ENA_TSTAMP,
+ &de->tstamp_enabled, *tstamp);
+ }
+
+ return 0;
+}
+
+static int
+scmi_telemetry_state_set_resp_process(struct telemetry_info *ti,
+ struct scmi_telemetry_de *de,
+ void *r, bool is_group)
+{
+ struct scmi_msg_resp_telemetry_de_configure *resp = r;
+ u32 sid = le32_to_cpu(resp->shmti_id);
+
+ /* Update DE SHMTI and offset, if applicable */
+ if (IS_SHMTI_ID_VALID(sid)) {
+ if (sid >= ti->num_shmti)
+ return -EPROTO;
+
+ /*
+ * Update SHMTI/offset while skipping non-SHMTI-DEs like
+ * FCs and notif-only.
+ */
+ if (!is_group) {
+ struct telemetry_de *tde;
+ struct payload __iomem *payld;
+ u32 de_offs;
+
+ de_offs = le32_to_cpu(resp->shmti_de_offset);
+ if (de_offs >= ti->shmti[sid].info.len - de->info->data_sz)
+ return -EPROTO;
+
+ tde = to_tde(de);
+ tde->base = ti->shmti[sid].base;
+ tde->offset = de_offs;
+ /* A handy reference to the Epilogue updated */
+ tde->eplg = SHMTI_EPLG(&ti->shmti[sid]);
+
+ payld = tde->base + tde->offset;
+ if (USE_BLK_TS(payld) && !tde->bts) {
+ struct payload __iomem *bts_payld;
+ u32 bts_offs;
+
+ bts_offs = le32_to_cpu(resp->blk_ts_offset);
+ bts_payld = (bts_offs) ? tde->base + bts_offs : NULL;
+ tde->bts = scmi_telemetry_blkts_bind(ti->ph->dev,
+ &ti->shmti[sid],
+ payld,
+ &ti->xa_lines,
+ bts_payld);
+ if (WARN_ON(!tde->bts))
+ return -EPROTO;
+ }
+ } else {
+ int ret;
+
+ /*
+ * A full SHMTI scan is needed when enabling a
+ * group or its timestamps in order to retrieve
+ * offsets: note that when group-timestamp is
+ * enabled for composing DEs a re-scan is needed
+ * since some DEs could have been relocated due
+ * to lack of space in the TDCF.
+ */
+ ret = scmi_telemetry_shmti_scan(ti, sid, SCAN_UPDATE);
+ if (ret)
+ dev_warn(ti->ph->dev,
+ "Failed group-scan of SHMTI ID:%d - ret:%d\n",
+ sid, ret);
+ }
+ } else if (!is_group) {
+ /* Unlink the related BLK_TS/UUID lines on disable */
+ scmi_telemetry_de_unlink(de);
+ }
+
+ return 0;
+}
+
+static int __scmi_telemetry_state_set(const struct scmi_protocol_handle *ph,
+ bool is_group, bool *enable,
+ bool *enabled_state, bool *tstamp,
+ bool *tstamp_enabled_state, void *obj)
+{
+ struct scmi_msg_resp_telemetry_de_configure *resp;
+ struct scmi_msg_telemetry_de_configure *msg;
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_telemetry_de *de = !is_group ? obj : NULL;
+ struct scmi_telemetry_group *grp = is_group ? obj : NULL;
+ unsigned int obj_id = !is_group ? de->info->id : grp->info->grp_id;
+ struct scmi_xfer *t;
+ int ret;
+
+ if (!enabled_state || !tstamp_enabled_state)
+ return -EINVAL;
+
+ /* Is anything to do at all on this DE ? */
+ if (!is_group && (!enable || *enable == *enabled_state) &&
+ (!tstamp || *tstamp == *tstamp_enabled_state))
+ return 0;
+
+ /*
+ * DE is currently disabled AND no enable state change was requested,
+ * while timestamp is being changed: update only local state...no need
+ * to send a message.
+ */
+ if (!is_group && !enable && !*enabled_state) {
+ if (de->tstamp_support)
+ scmi_telemetry_de_state_update(ti, ENA_TSTAMP,
+ tstamp_enabled_state,
+ *tstamp);
+
+ return 0;
+ }
+
+ ret = ph->xops->xfer_get_init(ph, TELEMETRY_DE_CONFIGURE,
+ sizeof(*msg), sizeof(*resp), &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ /* Note that BOTH DE and GROUPS have a first ID field.. */
+ msg->id = cpu_to_le32(obj_id);
+ /* Default to disable mode for one DE */
+ msg->flags = DE_DISABLE_ONE;
+ msg->flags |= cpu_to_le32(FIELD_PREP(GENMASK(3, 3),
+ is_group ? EVENT_GROUP : EVENT_DE));
+
+ if ((!enable && *enabled_state) || (enable && *enable)) {
+ /* Already enabled but tstamp_enabled state changed */
+ if (tstamp) {
+ /* Here, tstamp cannot be NULL too */
+ msg->flags |= cpu_to_le32(*tstamp ?
+ DE_ENABLE_WTH_TSTAMP : DE_ENABLE_NO_TSTAMP);
+ } else {
+ msg->flags |= cpu_to_le32(*tstamp_enabled_state ?
+ DE_ENABLE_WTH_TSTAMP : DE_ENABLE_NO_TSTAMP);
+ }
+ }
+
+ resp = t->rx.buf;
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret) {
+ ret = scmi_telemetry_state_set_resp_process(ti, obj, resp, is_group);
+ if (!ret) {
+ /* Update cached state on success */
+ if (enable) {
+ if (!is_group)
+ scmi_telemetry_de_state_update(ti, ENA_STATE,
+ enabled_state,
+ *enable);
+ else
+ *enabled_state = *enable;
+ }
+ if (tstamp) {
+ if (!is_group) {
+ if (de->tstamp_support)
+ scmi_telemetry_de_state_update(ti, ENA_TSTAMP,
+ tstamp_enabled_state,
+ *tstamp);
+ } else {
+ *tstamp_enabled_state = *tstamp;
+ }
+ }
+
+ if (is_group)
+ scmi_telemetry_group_state_update(ti, grp, enable,
+ tstamp);
+ }
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static int scmi_telemetry_state_get(const struct scmi_protocol_handle *ph,
+ u32 *id, bool *enabled, bool *tstamp_enabled)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_telemetry_de *de;
+
+ if (!enabled || !tstamp_enabled)
+ return -EINVAL;
+
+ if (!id) {
+ /* Returning the all_des_* state */
+ *enabled =
+ (atomic_read(&ti->des_enabled[ENA_STATE]) == ti->info.base.num_des);
+ *tstamp_enabled =
+ (atomic_read(&ti->des_enabled[ENA_TSTAMP]) == ti->num_des_tstamp);
+
+ return 0;
+ }
+
+ de = xa_load(&ti->xa_des, *id);
+ if (!de)
+ return -ENODEV;
+
+ *enabled = de->enabled;
+ *tstamp_enabled = de->tstamp_enabled;
+
+ return 0;
+}
+
+static int scmi_telemetry_state_set(const struct scmi_protocol_handle *ph,
+ bool is_group, u32 id, bool *enable,
+ bool *tstamp)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+ bool *enabled_state, *tstamp_enabled_state;
+ struct scmi_telemetry_res_info *rinfo;
+ void *obj;
+
+ rinfo = ti->res_get(ti);
+ if (!is_group) {
+ struct scmi_telemetry_de *de;
+
+ de = xa_load(&ti->xa_des, id);
+ if (!de)
+ return -ENODEV;
+
+ enabled_state = &de->enabled;
+ tstamp_enabled_state = &de->tstamp_enabled;
+ obj = de;
+ } else {
+ struct scmi_telemetry_group *grp;
+
+ if (id >= ti->info.base.num_groups)
+ return -EINVAL;
+
+ grp = &rinfo->grps[id];
+
+ enabled_state = &grp->enabled;
+ tstamp_enabled_state = &grp->tstamp_enabled;
+ obj = grp;
+ }
+
+ return __scmi_telemetry_state_set(ph, is_group, enable, enabled_state,
+ tstamp, tstamp_enabled_state, obj);
+}
+
+static int scmi_telemetry_all_disable(const struct scmi_protocol_handle *ph,
+ bool is_group)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_msg_telemetry_de_configure *msg;
+ struct scmi_telemetry_res_info *rinfo;
+ struct scmi_xfer *t;
+ int ret;
+
+ rinfo = ti->res_get(ti);
+ ret = ph->xops->xfer_get_init(ph, TELEMETRY_DE_CONFIGURE,
+ sizeof(*msg), 0, &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ msg->flags = cpu_to_le32(DE_DISABLE_ALL);
+ if (is_group)
+ msg->flags |= cpu_to_le32(GROUP_SELECTOR);
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret) {
+ for (int i = 0; i < ti->info.base.num_des; i++)
+ scmi_telemetry_de_state_update(ti, ENA_STATE,
+ &rinfo->des[i]->enabled,
+ false);
+
+ if (is_group) {
+ for (int i = 0; i < ti->info.base.num_groups; i++)
+ rinfo->grps[i].enabled = false;
+ }
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static int
+scmi_telemetry_collection_configure(const struct scmi_protocol_handle *ph,
+ unsigned int res_id, bool *enable,
+ unsigned int *update_interval_ms,
+ enum scmi_telemetry_collection *mode)
+{
+ bool tlm_enable, is_group = res_id != SCMI_TLM_GRP_INVALID;
+ enum scmi_telemetry_collection *current_mode, next_mode;
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_msg_telemetry_config_set *msg;
+ unsigned int *active_update_interval;
+ struct scmi_xfer *t;
+ __le32 interval;
+ u32 control;
+ int ret;
+
+ if (mode && *mode == SCMI_TLM_NOTIFICATION &&
+ !ti->info.continuos_update_support)
+ return -EINVAL;
+
+ if (is_group && res_id >= ti->info.base.num_groups)
+ return -EINVAL;
+
+ if (!is_group) {
+ active_update_interval = &ti->info.active_update_interval;
+ current_mode = &ti->info.current_mode;
+ } else {
+ struct scmi_telemetry_res_info *rinfo;
+
+ rinfo = ti->res_get(ti);
+ active_update_interval =
+ &rinfo->grps[res_id].active_update_interval;
+ current_mode = &rinfo->grps[res_id].current_mode;
+ }
+
+ if (!enable && !update_interval_ms && (!mode || *mode == *current_mode))
+ return 0;
+
+ ret = ph->xops->xfer_get_init(ph, TELEMETRY_CONFIG_SET,
+ sizeof(*msg), 0, &t);
+ if (ret)
+ return ret;
+
+ if (!update_interval_ms)
+ interval = cpu_to_le32(*active_update_interval);
+ else
+ interval = cpu_to_le32(*update_interval_ms);
+
+ tlm_enable = enable ? *enable : ti->info.enabled;
+ next_mode = mode ? *mode : *current_mode;
+
+ msg = t->tx.buf;
+ msg->grp_id = cpu_to_le32(res_id);
+ control = tlm_enable ? TELEMETRY_ENABLE : 0;
+ control |= !is_group ? TELEMETRY_SET_SELECTOR_ALL : TELEMETRY_SET_SELECTOR_GROUP;
+ control |= TELEMETRY_MODE_SET(next_mode);
+ msg->control = cpu_to_le32(control);
+ msg->sampling_rate = interval;
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret) {
+ ti->info.enabled = tlm_enable;
+ *current_mode = next_mode;
+ ti->info.notif_enabled = *current_mode == SCMI_TLM_NOTIFICATION;
+ if (update_interval_ms)
+ *active_update_interval = le32_to_cpu(interval);
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.info_get = scmi_telemetry_info_get,
.de_lookup = scmi_telemetry_de_lookup,
.res_get = scmi_telemetry_resources_get,
+ .state_get = scmi_telemetry_state_get,
+ .state_set = scmi_telemetry_state_set,
+ .all_disable = scmi_telemetry_all_disable,
+ .collection_configure = scmi_telemetry_collection_configure,
};
/**
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index a03921e9a698..fc6031ad1ea4 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -992,6 +992,13 @@ struct scmi_telemetry_info {
* @info_get: get the general Telemetry information.
* @de_lookup: get a specific DE descriptor from the DE id.
* @res_get: get a reference to the Telemetry resources descriptor.
+ * @state_get: retrieve the specific DE or GROUP state, if NULL returns the
+ * cumulative state of all DEs.
+ * @state_set: enable/disable the specific DE or GROUP with or without timestamps.
+ * @all_disable: disable ALL DEs or GROUPs.
+ * @collection_configure: choose a sampling rate and enable SHMTI/FC sampling
+ * for on demand collection via @de_data_read or async
+ * notificatioins for all the enabled DEs.
*/
struct scmi_telemetry_proto_ops {
const struct scmi_telemetry_info __must_check *(*info_get)
@@ -1000,6 +1007,15 @@ struct scmi_telemetry_proto_ops {
(const struct scmi_protocol_handle *ph, u32 id);
const struct scmi_telemetry_res_info __must_check *(*res_get)
(const struct scmi_protocol_handle *ph);
+ int (*state_get)(const struct scmi_protocol_handle *ph,
+ u32 *id, bool *enabled, bool *tstamp_enabled);
+ int (*state_set)(const struct scmi_protocol_handle *ph,
+ bool is_group, u32 id, bool *enable, bool *tstamp);
+ int (*all_disable)(const struct scmi_protocol_handle *ph, bool group);
+ int (*collection_configure)(const struct scmi_protocol_handle *ph,
+ unsigned int res_id, bool *enable,
+ unsigned int *update_interval_ms,
+ enum scmi_telemetry_collection *mode);
};
/**
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v5 10/23] firmware: arm_scmi: Add Telemetry DataEvent read capabilities
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
` (8 preceding siblings ...)
2026-07-03 12:35 ` [PATCH v5 09/23] firmware: arm_scmi: Add Telemetry configuration operations Cristian Marussi
@ 2026-07-03 12:35 ` Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 11/23] firmware: arm_scmi: Add support for Telemetry reset Cristian Marussi
` (13 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:35 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi
Add support for Telemetry operations needed to read DataEvent values and
timestamps as single entities or all together in a single bulk buffer.
The returned values are effectiely retrieved from the platform only
when strictly needed, i.e. when no fresh recent cached value was already
available.
The DataEvent values are fetched transparently from the platform origins
using the proper synchronization and consistency primitives, directly from
the SHMTIs areas or the FastChannels memory depending on the configuration.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v4 --> v5
- removed UAPI structures dependency
- cleaned up sample_get logic around groups
- fixes sparse warnings
v3 --> v4
- fix typos in comments
- extend TDE cache helpers with cache lookup routine and use cumulative
scmi_telemetry_de_sample param
v2 --> v3
- split from monolithic Telemetry patch
- simplity using a few assignement using ternary ops
- remove useless ts param from scanning function
- use a compound literal to simplify samples init
- add a missing __must_check on telemetry_ops
- changed errno on DE read troubles:
- ENODEV/ENOENT: DE is UNKNOWN
- EINVAL: DE is marked as DATA_INVALID
- ENODATA: TLM susbsystem or the specific DE is OFF
---
drivers/firmware/arm_scmi/telemetry.c | 470 ++++++++++++++++++++++++--
include/linux/scmi_protocol.h | 24 ++
2 files changed, 465 insertions(+), 29 deletions(-)
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
index 26bfde78ef21..2623d764b805 100644
--- a/drivers/firmware/arm_scmi/telemetry.c
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -574,16 +574,34 @@ scmi_telemetry_tde_cache_unchanged(struct telemetry_de *tde, u32 magic)
}
static void
-scmi_telemetry_tde_cache_update(struct telemetry_de *tde, u64 val,
- u64 *tstamp, u32 *magic)
+scmi_telemetry_tde_cache_update(struct telemetry_de *tde,
+ struct scmi_telemetry_de_sample *sample,
+ u32 *magic)
{
guard(mutex)(&tde->mtx);
tde->last_magic = magic ? *magic : TDCF_BAD_END_SEQ;
- tde->last_val = val;
- tde->last_ts = tstamp && scmi_tde_has_tstamp(tde) ? *tstamp : 0;
- if (tstamp)
- *tstamp = tde->last_ts;
+ tde->last_val = sample->val;
+ tde->last_ts = scmi_tde_has_tstamp(tde) ? sample->tstamp : 0;
+ /* Update returned value too */
+ sample->tstamp = tde->last_ts;
+}
+
+static int
+scmi_telemetry_tde_cache_lookup(struct telemetry_de *tde,
+ struct scmi_telemetry_de_sample *sample,
+ u32 *magic)
+{
+ guard(mutex)(&tde->mtx);
+
+ if (magic && tde->last_magic != *magic)
+ return -EAGAIN;
+
+ sample->id = tde->de.info->id;
+ sample->val = tde->last_val;
+ sample->tstamp = tde->last_ts;
+
+ return 0;
}
struct scmi_tlm_de_priv {
@@ -1492,31 +1510,31 @@ scmi_telemetry_tde_allocate(struct telemetry_info *ti, u32 de_id,
}
static inline void
-scmi_telemetry_line_data_parse(struct telemetry_de *tde, u64 *val, u64 *tstamp,
+scmi_telemetry_line_data_parse(struct telemetry_de *tde,
+ struct scmi_telemetry_de_sample *sample,
struct payload __iomem *payld, u32 magic)
{
/* Data is always valid since we are NOT handling BLK TS lines here */
- *val = LINE_DATA_GET(&payld->l);
- if (tstamp) {
- if (USE_BLK_TS(payld)) {
- /* Read out the actual BLK_TS */
- *tstamp = scmi_telemetry_blkts_read(magic, tde->bts);
- } else if (LINE_TS_VALID(payld)) {
- /*
- * Note that LINE_TS_VALID implies HAS_LINE_EXT and that
- * the per DE line_ts_rate is advertised in the DE
- * descriptor.
- */
- *tstamp = LINE_TSTAMP_GET(&payld->tsl);
- } else {
- *tstamp = 0;
- }
+ sample->val = LINE_DATA_GET(&payld->l);
+ if (USE_BLK_TS(payld)) {
+ /* Read out the actual BLK_TS */
+ sample->tstamp = scmi_telemetry_blkts_read(magic, tde->bts);
+ } else if (LINE_TS_VALID(payld)) {
+ /*
+ * Note that LINE_TS_VALID implies HAS_LINE_EXT and that
+ * the per DE line_ts_rate is advertised in the DE
+ * descriptor.
+ */
+ sample->tstamp = LINE_TSTAMP_GET(&payld->tsl);
+ } else {
+ sample->tstamp = 0;
}
- trace_scmi_tlm_collect(tstamp ? *tstamp : 0, tde->de.info->id,
- *val, "SHMTI_DE_READ");
+ /* Trace originally read tstamp */
+ trace_scmi_tlm_collect(sample->tstamp, tde->de.info->id,
+ sample->val, "SHMTI_DE_READ");
- scmi_telemetry_tde_cache_update(tde, *val, tstamp, &magic);
+ scmi_telemetry_tde_cache_update(tde, sample, &magic);
}
static inline void scmi_telemetry_bts_link(struct telemetry_de *tde,
@@ -1588,9 +1606,9 @@ static void scmi_telemetry_tdcf_data_parse(struct telemetry_info *ti,
enum scan_mode mode,
void *active_bts, void *active_uuid)
{
+ struct scmi_telemetry_de_sample sample = {};
bool use_blk_ts = USE_BLK_TS(payld);
struct telemetry_de *tde;
- u64 val, tstamp = 0;
u32 de_id;
de_id = PAYLD_ID(payld);
@@ -1637,9 +1655,8 @@ static void scmi_telemetry_tdcf_data_parse(struct telemetry_info *ti,
if (active_uuid)
scmi_telemetry_uuid_link(tde, active_uuid);
- /* Parse data words */
- scmi_telemetry_line_data_parse(tde, &val, &tstamp, payld,
- shmti->last_magic);
+ /* Parse data words ... only for the sake of updating TDE cache */
+ scmi_telemetry_line_data_parse(tde, &sample, payld, shmti->last_magic);
}
static int scmi_telemetry_tdcf_line_parse(struct telemetry_info *ti,
@@ -2121,6 +2138,398 @@ scmi_telemetry_collection_configure(const struct scmi_protocol_handle *ph,
return ret;
}
+static inline void
+scmi_telemetry_de_data_fc_read(struct telemetry_de *tde,
+ struct scmi_telemetry_de_sample *sample)
+{
+ struct fc_tsline __iomem *fc = tde->base + tde->offset;
+
+ sample->val = LINE_DATA_GET(fc);
+ sample->tstamp = LINE_TSTAMP_GET(fc);
+
+ /* Trace originally read tstamp */
+ trace_scmi_tlm_collect(sample->tstamp, tde->de.info->id, sample->val,
+ "FC_READ");
+
+ scmi_telemetry_tde_cache_update(tde, sample, NULL);
+}
+
+static void scmi_telemetry_scan_update(struct telemetry_info *ti)
+{
+ struct telemetry_de *tde;
+
+ /* Scan all SHMTIs ... */
+ for (int id = 0; id < ti->num_shmti; id++) {
+ int ret;
+
+ ret = scmi_telemetry_shmti_scan(ti, id, SCAN_LOOKUP);
+ if (ret)
+ dev_warn(ti->ph->dev,
+ "Failed update-scan of SHMTI ID:%d - ret:%d\n",
+ id, ret);
+ }
+
+ if (!ti->info.fc_support)
+ return;
+
+ /* Need to enumerate resources to access fastchannels */
+ ti->res_get(ti);
+ list_for_each_entry(tde, &ti->fcs_des, item) {
+ struct scmi_telemetry_de_sample sample = {};
+
+ if (!tde->de.enabled)
+ continue;
+
+ /* Only for the sake of updating TDE cache */
+ scmi_telemetry_de_data_fc_read(tde, &sample);
+ }
+}
+
+/*
+ * TDCF and TS Line Management Notes
+ * ---------------------------------
+ *
+ * TDCF Payload Metadata notable bits:
+ * - Bit[3]: USE BLK Tstamp
+ * - Bit[2]: Line-Extension Field present (LineTstamp)
+ * - Bit[1]: Tstamp VALID
+ * - Bit[0]: Data INVALID
+ *
+ * CASE_1:
+ * -------
+ * + A DE is enabled with timestamp disabled, so the TS fields are
+ * NOT present
+ * -> BIT[3:0] = 0000b
+ *
+ * - 1/A LINE_TSTAMP
+ * ------------------
+ * + that DE is then 're-enabled' with TS: so it was ON, it remains
+ * ON but using DE_CONFIGURE I now enabled also TS, so the
+ * platform relocates it at the end of the SHMTI and return the
+ * new offset
+ * -> BIT[3:0] = 0110b
+ *
+ * - 1/B BLK_TSTAMP
+ * ------------------
+ * + that DE is then 're-enabled' with BLK TS: so it was ON, it
+ * remains ON but using DE_CONFIGURE, we now also enabled the TS,
+ * so the platform will:
+ * - IF a preceding BLK_TS line exist (with same clk rate)
+ * it relocates the DE at the end of the SHMTI and return the
+ * new offset (if there is enough room, if not in another SHMTI)
+ * - IF a preceding BLK_TS line DOES NOT exist (with same clk rate)
+ * it creates a new BLK_TS line at the end of the SHMTI and then
+ * relocates the DE after the new BLK_TS and return the
+ * new offset (if there is enough room, if not in another SHMTI)
+ * -> BIT[3:1] = 1010b
+ *
+ * + the hole left from the relocated DE can be reused by the platform
+ * to fit another equally sized DE. (i.e. without shuffling around any
+ * other enabled DE, since that would cause a change of the known offset)
+ * anyway it will be marked as:
+ * -> BIT[3:0] = 0101b iff it was a LINE_TSTAMP
+ * -> BIT[3:0] = 0001b iff it was a BLK_TSTAMP
+ *
+ * CASE_2:
+ * -------
+ * + A DE is enabled with LINE timestamp enabled, so the TS_Line is there
+ * -> BIT[3:0] = 0110b
+ * + that DE has its timestamp disabled: again, you can do this without
+ * disabling it fully but just disabling the TS, so now that TS_line
+ * fields are still physically there but NOT valid
+ * -> BIT[3:0] = 0100b
+ * + the hole from the timestamp remain there unused until
+ * - you enable again the TS so the hole is used again
+ * -> BIT[3:0] = 0110b
+ * OR
+ * - you disable fully the DE and then re-enable it with the TS
+ * -> potentially CASE_1 the DE is relocated on enable
+ * + same kind of dynamic applies if the DE had a BLK_TS line
+ */
+static struct payload __iomem *
+scmi_telemetry_tdcf_de_payld_get(struct telemetry_de *tde)
+{
+ struct payload __iomem *payld;
+
+ payld = tde->base + tde->offset;
+ if (DATA_INVALID(payld)) {
+ trace_scmi_tlm_access(PAYLD_ID(payld), "DE_DATA_INVALID", 0, 0);
+ return IOMEM_ERR_PTR(-EINVAL);
+ }
+
+ if (IS_BLK_TS_LINE(payld)) {
+ trace_scmi_tlm_access(tde->de.info->id, "BAD_DE_META", 0, 0);
+ return IOMEM_ERR_PTR(-EPROTO);
+ }
+
+ if (PAYLD_ID(payld) != tde->de.info->id) {
+ trace_scmi_tlm_access(tde->de.info->id, "DE_ID_MISMATCH", 0, 0);
+ return IOMEM_ERR_PTR(-ENODEV);
+ }
+
+ /*
+ * A valid line using BLK_TS should have been initialized with the
+ * related BLK_TS when enabled.
+ */
+ if (WARN_ON((USE_BLK_TS(payld) && !tde->bts))) {
+ trace_scmi_tlm_access(tde->de.info->id, "BAD_USE_BLK_TS", 0, 0);
+ return IOMEM_ERR_PTR(-EPROTO);
+ }
+
+ return payld;
+}
+
+static int
+scmi_telemetry_tdcf_de_parse(struct telemetry_de *tde,
+ struct scmi_telemetry_de_sample *sample)
+{
+ int retries = SCMI_TLM_TDCF_MAX_RETRIES;
+ struct tdcf __iomem *tdcf = tde->base;
+ u32 startm, endm;
+
+ if (!tdcf)
+ return -ENODEV;
+
+ do {
+ struct payload __iomem *payld;
+ int err;
+
+ /* A bit of exponential backoff between retries */
+ fsleep((SCMI_TLM_TDCF_MAX_RETRIES - retries) * 1000);
+
+ startm = TDCF_START_SEQ_GET(tdcf);
+ if (IS_BAD_START_SEQ(startm)) {
+ trace_scmi_tlm_access(tde->de.info->id, "MSEQ_BADSTART",
+ startm, 0);
+ continue;
+ }
+
+ /* Return last readings if nothing changed */
+ err = scmi_telemetry_tde_cache_lookup(tde, sample, &startm);
+ if (!err)
+ return 0;
+
+ payld = scmi_telemetry_tdcf_de_payld_get(tde);
+ if (IS_ERR(payld))
+ return PTR_ERR(payld);
+
+ /* Parse data words */
+ scmi_telemetry_line_data_parse(tde, sample, payld, startm);
+
+ endm = TDCF_END_SEQ_GET(tde->eplg);
+ if (startm != endm)
+ trace_scmi_tlm_access(tde->de.info->id, "MSEQ_MISMATCH",
+ startm, endm);
+ } while (startm != endm && --retries);
+
+ if (startm != endm) {
+ trace_scmi_tlm_access(tde->de.info->id, "TDCF_DE_FAIL",
+ startm, endm);
+ return -EPROTO;
+ }
+
+ return 0;
+}
+
+static int scmi_telemetry_de_access(struct telemetry_de *tde,
+ struct scmi_telemetry_de_sample *sample)
+{
+ if (!tde->de.fc_support)
+ return scmi_telemetry_tdcf_de_parse(tde, sample);
+
+ scmi_telemetry_de_data_fc_read(tde, sample);
+
+ return 0;
+}
+
+static int scmi_telemetry_de_collect(struct telemetry_info *ti,
+ struct scmi_telemetry_de *de,
+ struct scmi_telemetry_de_sample *sample)
+{
+ struct telemetry_de *tde = to_tde(de);
+
+ if (!de->enabled)
+ return -ENODATA;
+
+ /*
+ * DE readings returns cached values when:
+ * - DE data value was retrieved via notification/delayed_response
+ */
+ if (tde->cached_msg)
+ return scmi_telemetry_tde_cache_lookup(tde, sample, NULL);
+
+ return scmi_telemetry_de_access(tde, sample);
+}
+
+static int scmi_telemetry_de_data_read(const struct scmi_protocol_handle *ph,
+ struct scmi_telemetry_de_sample *sample)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_telemetry_de *de;
+
+ if (!ti->info.enabled || !sample)
+ return -ENODATA;
+
+ de = xa_load(&ti->xa_des, sample->id);
+ if (!de)
+ return -ENOENT;
+
+ return scmi_telemetry_de_collect(ti, de, sample);
+}
+
+static int
+scmi_telemetry_samples_collect(struct telemetry_info *ti, int grp_id,
+ int *num_samples,
+ struct scmi_telemetry_de_sample *samples)
+{
+ struct scmi_telemetry_res_info *rinfo;
+ int max_samples;
+
+ max_samples = *num_samples;
+ *num_samples = 0;
+
+ rinfo = ti->res_get(ti);
+ for (int i = 0; i < rinfo->num_des; i++) {
+ struct scmi_telemetry_de *de;
+
+ de = rinfo->des[i];
+ /* Skip disabled DEs and not belonging to the selected Group */
+ if (!de->enabled ||
+ (grp_id != SCMI_TLM_GRP_INVALID &&
+ (!de->grp || de->grp->info->grp_id != grp_id)))
+ continue;
+
+ /* Must be checked here to skip disabled and non selected */
+ if (*num_samples == max_samples)
+ return -ENOSPC;
+
+ /* Note that this cannot fail when passing a NULL magic */
+ scmi_telemetry_tde_cache_lookup(to_tde(de),
+ &samples[(*num_samples)++], NULL);
+ }
+
+ return 0;
+}
+
+static int scmi_telemetry_des_bulk_read(const struct scmi_protocol_handle *ph,
+ int grp_id, int *num_samples,
+ struct scmi_telemetry_de_sample *samples)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ if (!ti->info.enabled || !num_samples || !samples)
+ return -EINVAL;
+
+ /* Trigger a full SHMTIs & FCs scan */
+ scmi_telemetry_scan_update(ti);
+
+ /* Collect all last cached values */
+ return scmi_telemetry_samples_collect(ti, grp_id, num_samples, samples);
+}
+
+static void
+scmi_telemetry_msg_payld_process(struct telemetry_info *ti,
+ unsigned int num_dwords, u32 *dwords)
+{
+ struct scmi_telemetry_res_info *rinfo;
+ u32 next = 0;
+
+ rinfo = ti->res_get(ti);
+ if (!rinfo->fully_enumerated) {
+ dev_warn_once(ti->ph->dev,
+ "Cannot process DEs in message payload. Drop.\n");
+ return;
+ }
+
+ while (next < num_dwords) {
+ struct payload *payld = (struct payload *)&dwords[next];
+ struct scmi_telemetry_de *de;
+ struct telemetry_de *tde;
+ u32 de_id;
+
+ next += LINE_LENGTH_WORDS(payld);
+
+ if (DATA_INVALID(payld)) {
+ dev_err(ti->ph->dev, "MSG - Received INVALID DATA line\n");
+ continue;
+ }
+
+ de_id = le32_to_cpu(payld->id);
+ de = xa_load(&ti->xa_des, de_id);
+ if (!de || !de->enabled) {
+ dev_err(ti->ph->dev,
+ "MSG - Received INVALID DE - ID:%u enabled:%c\n",
+ de_id, de ? (de->enabled ? 'Y' : 'N') : 'X');
+ continue;
+ }
+
+ tde = to_tde(de);
+ guard(mutex)(&tde->mtx);
+ tde->cached_msg = true;
+ tde->last_val = LINE_DATA_GET(&payld->tsl);
+ /* TODO BLK_TS in notification payloads */
+ tde->last_ts = HAS_LINE_EXT(payld) && LINE_TS_VALID(payld) ?
+ LINE_TSTAMP_GET(&payld->tsl) : 0;
+
+ trace_scmi_tlm_collect(tde->last_ts, tde->de.info->id,
+ tde->last_val, "MESSAGE");
+ }
+}
+
+static int scmi_telemetry_des_sample_get(const struct scmi_protocol_handle *ph,
+ int grp_id, int *num_samples,
+ struct scmi_telemetry_de_sample *samples)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_msg_telemetry_config_set *msg;
+ struct scmi_xfer *t;
+ bool is_group;
+ u32 control;
+ int ret;
+
+ if (!ti->info.enabled || !num_samples || !samples)
+ return -EINVAL;
+
+ is_group = grp_id != SCMI_TLM_GRP_INVALID;
+ if (is_group && grp_id >= ti->info.base.num_groups)
+ return -EINVAL;
+
+ ret = ph->xops->xfer_get_init(ph, TELEMETRY_CONFIG_SET,
+ sizeof(*msg), 0, &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ msg->grp_id = cpu_to_le32(grp_id);
+ control = TELEMETRY_ENABLE;
+ control |= !is_group ? TELEMETRY_SET_SELECTOR_ALL :
+ TELEMETRY_SET_SELECTOR_GROUP;
+ control |= TELEMETRY_MODE_SINGLE;
+ msg->control = cpu_to_le32(control);
+ msg->sampling_rate = 0;
+
+ ret = ph->xops->do_xfer_with_response(ph, t);
+ if (!ret) {
+ struct scmi_msg_resp_telemetry_reading_complete *r = t->rx.buf;
+
+ /* Update cached DEs values from payload */
+ if (r->num_dwords)
+ scmi_telemetry_msg_payld_process(ti, le32_to_cpu(r->num_dwords),
+ (u32 *)r->dwords);
+
+ /* Scan and update SMHTIs and FCs */
+ scmi_telemetry_scan_update(ti);
+
+ /* Collect all last cached values */
+ ret = scmi_telemetry_samples_collect(ti, grp_id, num_samples,
+ samples);
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.info_get = scmi_telemetry_info_get,
.de_lookup = scmi_telemetry_de_lookup,
@@ -2129,6 +2538,9 @@ static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.state_set = scmi_telemetry_state_set,
.all_disable = scmi_telemetry_all_disable,
.collection_configure = scmi_telemetry_collection_configure,
+ .de_data_read = scmi_telemetry_de_data_read,
+ .des_bulk_read = scmi_telemetry_des_bulk_read,
+ .des_sample_get = scmi_telemetry_des_sample_get,
};
/**
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index fc6031ad1ea4..8dc9187e6bb9 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -985,6 +985,13 @@ struct scmi_telemetry_info {
enum scmi_telemetry_collection current_mode;
};
+struct scmi_telemetry_de_sample {
+ unsigned int id;
+ unsigned int pad;
+ unsigned long long tstamp;
+ unsigned long long val;
+};
+
/**
* struct scmi_telemetry_proto_ops - represents the various operations provided
* by SCMI Telemetry Protocol
@@ -999,6 +1006,15 @@ struct scmi_telemetry_info {
* @collection_configure: choose a sampling rate and enable SHMTI/FC sampling
* for on demand collection via @de_data_read or async
* notificatioins for all the enabled DEs.
+ * @de_data_read: on-demand read of a single DE and related optional timestamp:
+ * the value will be retrieved at the proper SHMTI offset OR
+ * from the dedicated FC area (if supported by that DE).
+ * @des_bulk_read: on-demand read of all the currently enabled DEs, or just
+ * the ones belonging to a specific group when provided.
+ * @des_sample_get: on-demand read of all the currently enabled DEs, or just
+ * the ones belonging to a specific group when provided.
+ * This causes an immediate update platform-side of all the
+ * enabled DEs.
*/
struct scmi_telemetry_proto_ops {
const struct scmi_telemetry_info __must_check *(*info_get)
@@ -1016,6 +1032,14 @@ struct scmi_telemetry_proto_ops {
unsigned int res_id, bool *enable,
unsigned int *update_interval_ms,
enum scmi_telemetry_collection *mode);
+ int __must_check (*de_data_read)(const struct scmi_protocol_handle *ph,
+ struct scmi_telemetry_de_sample *sample);
+ int __must_check (*des_bulk_read)(const struct scmi_protocol_handle *ph,
+ int grp_id, int *num_samples,
+ struct scmi_telemetry_de_sample *samples);
+ int __must_check (*des_sample_get)(const struct scmi_protocol_handle *ph,
+ int grp_id, int *num_samples,
+ struct scmi_telemetry_de_sample *samples);
};
/**
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v5 11/23] firmware: arm_scmi: Add support for Telemetry reset
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
` (9 preceding siblings ...)
2026-07-03 12:35 ` [PATCH v5 10/23] firmware: arm_scmi: Add Telemetry DataEvent read capabilities Cristian Marussi
@ 2026-07-03 12:35 ` Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 12/23] firmware: arm_scmi: Add Telemetry notification support Cristian Marussi
` (12 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:35 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi
Add support for Telemetry operations needed to request platform to
reset telemetry current configuration and data events values.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v3 --> v4
- reset tracked Telemetry DE state
v2 --> v3
- split from monolithic Telemetry patch
- use scmi_telemetry_de_unlink
---
drivers/firmware/arm_scmi/telemetry.c | 47 +++++++++++++++++++++++++++
include/linux/scmi_protocol.h | 2 ++
2 files changed, 49 insertions(+)
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
index 2623d764b805..1541b94c38cb 100644
--- a/drivers/firmware/arm_scmi/telemetry.c
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -2530,6 +2530,52 @@ static int scmi_telemetry_des_sample_get(const struct scmi_protocol_handle *ph,
return ret;
}
+static void scmi_telemetry_local_resources_reset(struct telemetry_info *ti)
+{
+ struct scmi_telemetry_res_info *rinfo;
+
+ /* Get rinfo as it is...without triggering an enumeration */
+ rinfo = __scmi_telemetry_resources_get(ti);
+ /* Clear all local state...*/
+ for (int i = 0; i < rinfo->num_des; i++) {
+ rinfo->des[i]->enabled = false;
+ rinfo->des[i]->tstamp_enabled = false;
+
+ scmi_telemetry_de_unlink(rinfo->des[i]);
+ }
+ for (int i = 0; i < rinfo->num_groups; i++) {
+ rinfo->grps[i].enabled = false;
+ rinfo->grps[i].tstamp_enabled = false;
+ rinfo->grps[i].current_mode = SCMI_TLM_ONDEMAND;
+ rinfo->grps[i].active_update_interval = 0;
+ }
+
+ atomic_set(&ti->des_enabled[ENA_STATE], 0);
+ atomic_set(&ti->des_enabled[ENA_TSTAMP], 0);
+}
+
+static int scmi_telemetry_reset(const struct scmi_protocol_handle *ph)
+{
+ struct scmi_xfer *t;
+ int ret;
+
+ ret = ph->xops->xfer_get_init(ph, TELEMETRY_RESET, sizeof(u32), 0, &t);
+ if (ret)
+ return ret;
+
+ put_unaligned_le32(0, t->tx.buf);
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret) {
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ scmi_telemetry_local_resources_reset(ti);
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.info_get = scmi_telemetry_info_get,
.de_lookup = scmi_telemetry_de_lookup,
@@ -2541,6 +2587,7 @@ static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.de_data_read = scmi_telemetry_de_data_read,
.des_bulk_read = scmi_telemetry_des_bulk_read,
.des_sample_get = scmi_telemetry_des_sample_get,
+ .reset = scmi_telemetry_reset,
};
/**
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index 8dc9187e6bb9..4d72a5fbf1bc 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -1015,6 +1015,7 @@ struct scmi_telemetry_de_sample {
* the ones belonging to a specific group when provided.
* This causes an immediate update platform-side of all the
* enabled DEs.
+ * @reset: reset configuration and telemetry data.
*/
struct scmi_telemetry_proto_ops {
const struct scmi_telemetry_info __must_check *(*info_get)
@@ -1040,6 +1041,7 @@ 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 (*reset)(const struct scmi_protocol_handle *ph);
};
/**
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v5 12/23] firmware: arm_scmi: Add Telemetry notification support
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
` (10 preceding siblings ...)
2026-07-03 12:35 ` [PATCH v5 11/23] firmware: arm_scmi: Add support for Telemetry reset Cristian Marussi
@ 2026-07-03 12:35 ` Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 13/23] firmware: arm_scmi: Add support for boot-on Telemetry Cristian Marussi
` (11 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:35 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi
Add support for notifications to Telemetry protocol and register an
internal notifier during protocol initialization: any DE value received
inside a notification payload will be cached for future user consumption.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v3 -> v4
- use TDE cache to save DEs received via msg payload
v2 --> v3
- changed a few dev_err into traces
- split from monolithic telemetry protocol patch
- use memcpy_from_le32
---
drivers/firmware/arm_scmi/telemetry.c | 142 +++++++++++++++++++++++---
include/linux/scmi_protocol.h | 9 ++
2 files changed, 138 insertions(+), 13 deletions(-)
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
index 1541b94c38cb..2ae96f459017 100644
--- a/drivers/firmware/arm_scmi/telemetry.c
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -459,12 +459,16 @@ struct telemetry_info {
struct list_head free_des;
struct list_head fcs_des;
struct scmi_telemetry_info info;
+ struct notifier_block telemetry_nb;
atomic_t rinfo_initializing;
struct completion rinfo_initdone;
struct scmi_telemetry_res_info *rinfo;
struct scmi_telemetry_res_info *(*res_get)(struct telemetry_info *ti);
};
+#define telemetry_nb_to_info(x) \
+ container_of(x, struct telemetry_info, telemetry_nb)
+
static struct scmi_telemetry_res_info *
__scmi_telemetry_resources_get(struct telemetry_info *ti);
@@ -2443,6 +2447,7 @@ scmi_telemetry_msg_payld_process(struct telemetry_info *ti,
while (next < num_dwords) {
struct payload *payld = (struct payload *)&dwords[next];
+ struct scmi_telemetry_de_sample sample = {};
struct scmi_telemetry_de *de;
struct telemetry_de *tde;
u32 de_id;
@@ -2450,29 +2455,29 @@ scmi_telemetry_msg_payld_process(struct telemetry_info *ti,
next += LINE_LENGTH_WORDS(payld);
if (DATA_INVALID(payld)) {
- dev_err(ti->ph->dev, "MSG - Received INVALID DATA line\n");
+ trace_scmi_tlm_access(PAYLD_ID(payld), "MSG_INVALID", 0, 0);
continue;
}
de_id = le32_to_cpu(payld->id);
de = xa_load(&ti->xa_des, de_id);
if (!de || !de->enabled) {
- dev_err(ti->ph->dev,
- "MSG - Received INVALID DE - ID:%u enabled:%c\n",
- de_id, de ? (de->enabled ? 'Y' : 'N') : 'X');
+ trace_scmi_tlm_access(de_id, de ? "MSG_DE_DISABLED" :
+ "MSG_DE_UNKNOWN", 0, 0);
continue;
}
- tde = to_tde(de);
- guard(mutex)(&tde->mtx);
- tde->cached_msg = true;
- tde->last_val = LINE_DATA_GET(&payld->tsl);
- /* TODO BLK_TS in notification payloads */
- tde->last_ts = HAS_LINE_EXT(payld) && LINE_TS_VALID(payld) ?
+ sample.val = LINE_DATA_GET(&payld->tsl);
+ sample.tstamp = HAS_LINE_EXT(payld) && LINE_TS_VALID(payld) ?
LINE_TSTAMP_GET(&payld->tsl) : 0;
- trace_scmi_tlm_collect(tde->last_ts, tde->de.info->id,
- tde->last_val, "MESSAGE");
+ /* Trace originally read tstamp */
+ trace_scmi_tlm_collect(sample.tstamp, de->info->id, sample.val,
+ "MESSAGE");
+
+ tde = to_tde(de);
+ tde->cached_msg = true;
+ scmi_telemetry_tde_cache_update(tde, &sample, NULL);
}
}
@@ -2590,6 +2595,98 @@ static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.reset = scmi_telemetry_reset,
};
+static bool
+scmi_telemetry_notify_supported(const struct scmi_protocol_handle *ph,
+ u8 evt_id, u32 src_id)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ return ti->info.continuos_update_support;
+}
+
+static int
+scmi_telemetry_set_notify_enabled(const struct scmi_protocol_handle *ph,
+ u8 evt_id, u32 src_id, bool enable)
+{
+ return 0;
+}
+
+static void *
+scmi_telemetry_fill_custom_report(const struct scmi_protocol_handle *ph,
+ u8 evt_id, ktime_t timestamp,
+ const void *payld, size_t payld_sz,
+ void *report, u32 *src_id)
+{
+ const struct scmi_telemetry_update_notify_payld *p = payld;
+ struct scmi_telemetry_update_report *r = report;
+
+ /* At least sized as an empty notification */
+ if (payld_sz < sizeof(*p))
+ return NULL;
+
+ r->timestamp = timestamp;
+ r->agent_id = le32_to_cpu(p->agent_id);
+ r->status = le32_to_cpu(p->status);
+ r->num_dwords = le32_to_cpu(p->num_dwords);
+ /*
+ * Allocated dwords and report are sized as max_msg_size, so as
+ * to allow for the maximum payload permitted by the configured
+ * transport. Overflow is not possible since out-of-size messages
+ * are dropped at the transport layer.
+ */
+ if (r->num_dwords)
+ memcpy_from_le32(r->dwords, p->array, r->num_dwords);
+
+ *src_id = 0;
+
+ return r;
+}
+
+static const struct scmi_event tlm_events[] = {
+ {
+ .id = SCMI_EVENT_TELEMETRY_UPDATE,
+ .max_payld_sz = 0,
+ .max_report_sz = 0,
+ },
+};
+
+static const struct scmi_event_ops tlm_event_ops = {
+ .is_notify_supported = scmi_telemetry_notify_supported,
+ .set_notify_enabled = scmi_telemetry_set_notify_enabled,
+ .fill_custom_report = scmi_telemetry_fill_custom_report,
+};
+
+static const struct scmi_protocol_events tlm_protocol_events = {
+ .queue_sz = SCMI_PROTO_QUEUE_SZ,
+ .ops = &tlm_event_ops,
+ .evts = tlm_events,
+ .num_events = ARRAY_SIZE(tlm_events),
+ .num_sources = 1,
+};
+
+static int scmi_telemetry_notifier(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct scmi_telemetry_update_report *er = data;
+ struct telemetry_info *ti = telemetry_nb_to_info(nb);
+
+ if (er->status) {
+ trace_scmi_tlm_access(0, "BAD_NOTIF_MSG", 0, 0);
+ return NOTIFY_DONE;
+ }
+
+ trace_scmi_tlm_access(0, "TLM_UPDATE_MSG", 0, 0);
+ /* Lookup the embedded DEs in the notification payload ... */
+ if (er->num_dwords)
+ scmi_telemetry_msg_payld_process(ti, er->num_dwords, er->dwords);
+
+ /* ...scan the SHMTI/FCs for any other DE updates. */
+ if (ti->streaming_mode)
+ scmi_telemetry_scan_update(ti);
+
+ return NOTIFY_OK;
+}
+
/**
* scmi_telemetry_resources_alloc - Resources allocation
* @ti: A reference to the telemetry info descriptor for this instance
@@ -2838,7 +2935,25 @@ static int scmi_telemetry_protocol_init(const struct scmi_protocol_handle *ph)
ti->info.base.version = ph->version;
- return ph->set_priv(ph, ti);
+ ret = ph->set_priv(ph, ti);
+ if (ret)
+ return ret;
+
+ /*
+ * Register a notifier anyway straight upon protocol initialization
+ * since there could be some DEs that are ONLY reported by notifications
+ * even though the chosen collection method was SHMTI/FCs.
+ */
+ if (ti->info.continuos_update_support) {
+ ti->telemetry_nb.notifier_call = &scmi_telemetry_notifier;
+ ret = ph->notifier_register(ph, SCMI_EVENT_TELEMETRY_UPDATE,
+ NULL, &ti->telemetry_nb);
+ if (ret)
+ dev_warn(ph->dev,
+ "Could NOT register Telemetry notifications\n");
+ }
+
+ return ret;
}
static const struct scmi_protocol scmi_telemetry = {
@@ -2846,6 +2961,7 @@ static const struct scmi_protocol scmi_telemetry = {
.owner = THIS_MODULE,
.instance_init = &scmi_telemetry_protocol_init,
.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 4d72a5fbf1bc..44ace368fc99 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -1249,6 +1249,7 @@ enum scmi_notification_events {
SCMI_EVENT_SYSTEM_POWER_STATE_NOTIFIER = 0x0,
SCMI_EVENT_POWERCAP_CAP_CHANGED = 0x0,
SCMI_EVENT_POWERCAP_MEASUREMENTS_CHANGED = 0x1,
+ SCMI_EVENT_TELEMETRY_UPDATE = 0x0,
};
struct scmi_power_state_changed_report {
@@ -1336,4 +1337,12 @@ struct scmi_powercap_meas_changed_report {
unsigned int domain_id;
unsigned int power;
};
+
+struct scmi_telemetry_update_report {
+ ktime_t timestamp;
+ unsigned int agent_id;
+ int status;
+ unsigned int num_dwords;
+ unsigned int dwords[];
+};
#endif /* _LINUX_SCMI_PROTOCOL_H */
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v5 13/23] firmware: arm_scmi: Add support for boot-on Telemetry
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
` (11 preceding siblings ...)
2026-07-03 12:35 ` [PATCH v5 12/23] firmware: arm_scmi: Add Telemetry notification support Cristian Marussi
@ 2026-07-03 12:35 ` Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 14/23] firmware: arm_scmi: Add Telemetry generation counter Cristian Marussi
` (10 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:35 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi
Add the initialization and discovery logic needed to detect when the
platform SCMI server is configured with telemetry enabled at boot and
perform all the needed resource enumerations to keep the kernel telemetry
subsystem state aligned with the platform boot-on configurations.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v4 --> v5
- use renamed grp_id instead of old id in group descriptors
- fixes sparse warnings
v3 --> v4
- bail-out FW_BUG errors
- fix typos in comments
- track boot-on Telemetry DE states
v2 --> v3
- split from monolithic telemetry protocol patch
- swap logic in scmi_telemetry_initial_state_lookup
---
drivers/firmware/arm_scmi/telemetry.c | 206 ++++++++++++++++++++++++++
1 file changed, 206 insertions(+)
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
index 2ae96f459017..1cd24a58c51d 100644
--- a/drivers/firmware/arm_scmi/telemetry.c
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -818,6 +818,198 @@ static int iter_de_descr_process_response(const struct scmi_protocol_handle *ph,
return ret;
}
+static int scmi_telemetry_config_lookup(struct telemetry_info *ti,
+ unsigned int grp_id, bool *enabled,
+ unsigned int *active_update_interval)
+{
+ const struct scmi_protocol_handle *ph = ti->ph;
+ struct scmi_msg_telemetry_config_get *msg;
+ struct scmi_msg_resp_telemetry_config_get *resp;
+ struct scmi_xfer *t;
+ u32 flags;
+ int ret;
+
+ ret = ph->xops->xfer_get_init(ph, TELEMETRY_CONFIG_GET,
+ sizeof(*msg), sizeof(*resp), &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ msg->grp_id = cpu_to_le32(grp_id);
+ flags = grp_id == SCMI_TLM_GRP_INVALID ?
+ TELEMETRY_GET_SELECTOR_ORPHANS : TELEMETRY_GET_SELECTOR_GROUP;
+ msg->flags = cpu_to_le32(flags);
+
+ resp = t->rx.buf;
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret) {
+ *enabled = le32_get_bits(resp->control, TELEMETRY_ENABLE);
+ *active_update_interval =
+ SCMI_TLM_GET_UPDATE_INTERVAL(le32_to_cpu(resp->sampling_rate));
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return 0;
+}
+
+static int scmi_telemetry_group_config_lookup(struct telemetry_info *ti,
+ struct scmi_telemetry_group *grp)
+{
+ return scmi_telemetry_config_lookup(ti, grp->info->grp_id, &grp->enabled,
+ &grp->active_update_interval);
+}
+
+static void iter_enabled_list_prepare_message(void *message,
+ unsigned int desc_index,
+ const void *priv)
+{
+ struct scmi_msg_telemetry_de_enabled_list *msg = message;
+
+ msg->index = cpu_to_le32(desc_index);
+ msg->flags = 0;
+}
+
+static int iter_enabled_list_update_state(struct scmi_iterator_state *st,
+ const void *response, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_de_enabled_list *r = response;
+
+ st->num_returned = le32_get_bits(r->flags, GENMASK(15, 0));
+ st->num_remaining = le32_get_bits(r->flags, GENMASK(31, 16));
+
+ if (st->rx_len < (sizeof(*r) + sizeof(r->entry[0]) * st->num_returned))
+ return -EINVAL;
+
+ /*
+ * total enabled is not declared previously anywhere so we
+ * assume it's returned+remaining on first call.
+ */
+ if (!st->max_resources)
+ st->max_resources = st->num_returned + st->num_remaining;
+
+ return 0;
+}
+
+static int
+iter_enabled_list_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_de_enabled_list *r = response;
+ const struct scmi_enabled_de_desc *desc;
+ struct telemetry_info *ti = priv;
+ struct telemetry_de *tde;
+ u32 de_id;
+ int ret;
+
+ desc = &r->entry[st->loop_idx];
+ de_id = le32_to_cpu(desc->id);
+ if (scmi_telemetry_tde_lookup(ti, de_id)) {
+ dev_err(ph->dev,
+ "Found INVALID DE with DUPLICATED ID:0x%08X\n", de_id);
+ return -EINVAL;
+ }
+
+ tde = scmi_telemetry_tde_get(ti, de_id);
+ if (IS_ERR(tde))
+ return PTR_ERR(tde);
+
+ tde->de.info->id = de_id;
+ tde->de.enabled = true;
+ tde->de.tstamp_enabled = le32_to_cpu(desc->mode) == DE_ENABLED_WITH_TSTAMP;
+
+ ret = scmi_telemetry_tde_register(ti, tde);
+ if (ret) {
+ scmi_telemetry_free_tde_put(ti, tde);
+ return ret;
+ }
+
+ scmi_telemetry_de_state_update(ti, ENA_STATE, NULL, true);
+ if (tde->de.tstamp_enabled)
+ scmi_telemetry_de_state_update(ti, ENA_TSTAMP, NULL, true);
+
+ dev_dbg(ph->dev, "Registered new ENABLED DE with ID:0x%08X\n",
+ tde->de.info->id);
+
+ return 0;
+}
+
+static int scmi_telemetry_enumerate_des_enabled_list(struct telemetry_info *ti)
+{
+ struct scmi_telemetry_res_info *rinfo = ti->rinfo;
+ const struct scmi_protocol_handle *ph = ti->ph;
+ struct scmi_iterator_ops ops = {
+ .prepare_message = iter_enabled_list_prepare_message,
+ .update_state = iter_enabled_list_update_state,
+ .process_response = iter_enabled_list_process_response,
+ };
+ void *iter;
+ int ret;
+
+ iter = ph->hops->iter_response_init(ph, &ops, 0,
+ TELEMETRY_DE_ENABLED_LIST,
+ sizeof(u32) * 2, ti);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+
+ ret = ph->hops->iter_response_run(iter);
+ if (ret)
+ return ret;
+
+ dev_info(ti->ph->dev, "Found %u enabled DEs.\n", rinfo->num_des);
+
+ return 0;
+}
+
+static int scmi_telemetry_initial_state_lookup(struct telemetry_info *ti)
+{
+ struct device *dev = ti->ph->dev;
+ int ret;
+
+ ret = scmi_telemetry_config_lookup(ti, SCMI_TLM_GRP_INVALID,
+ &ti->info.enabled,
+ &ti->info.active_update_interval);
+ if (ret)
+ return ret;
+
+ if (!ti->info.enabled)
+ return 0;
+
+ /*
+ * When Telemetry is found already enabled on the platform, proceed with
+ * passive discovery using DE_ENABLED_LIST and TDCF scanning: note that
+ * this CAN only discover DEs exposed via SHMTIs.
+ * FastChannel DEs need a proper DE_DESCRIPTION enumeration, while, even
+ * though incoming Notifications could be used for passive discovery too,
+ * it would carry a considerable risk of assimilating trash as DEs.
+ */
+ dev_info(dev,
+ "Telemetry found enabled with update interval %lux10^%d\n",
+ SCMI_TLM_GET_UPDATE_INTERVAL_SECS(ti->info.active_update_interval),
+ SCMI_TLM_GET_UPDATE_INTERVAL_EXP(ti->info.active_update_interval));
+ /*
+ * Query enabled DEs list: collect states. It will include DEs from any
+ * interface. Enabled groups still NOT enumerated.
+ */
+ ret = scmi_telemetry_enumerate_des_enabled_list(ti);
+ if (ret) {
+ dev_err(dev, FW_BUG "Cannot query enabled DE list. Abort.\n");
+ return ret;
+ }
+
+ /* Discover DEs on SHMTis: collect states/offsets/values */
+ for (int id = 0; id < ti->num_shmti; id++) {
+ ret = scmi_telemetry_shmti_scan(ti, id, SCAN_DISCOVERY);
+ if (ret)
+ dev_warn(dev,
+ "Failed discovery-scan of SHMTI ID:%d - ret:%d\n",
+ id, ret);
+ }
+
+ return 0;
+}
+
static int
scmi_telemetry_de_groups_init(struct device *dev, struct telemetry_info *ti)
{
@@ -882,6 +1074,9 @@ scmi_telemetry_de_groups_init(struct device *dev, struct telemetry_info *ti)
}
}
+ for (int i = 0; i < ti->info.base.num_groups; i++)
+ scmi_telemetry_group_config_lookup(ti, &rinfo->grps[i]);
+
rinfo->num_groups = ti->info.base.num_groups;
return 0;
@@ -2574,6 +2769,11 @@ static int scmi_telemetry_reset(const struct scmi_protocol_handle *ph)
struct telemetry_info *ti = ph->get_priv(ph);
scmi_telemetry_local_resources_reset(ti);
+ /* Fetch again the states from platform. */
+ ret = scmi_telemetry_initial_state_lookup(ti);
+ if (ret)
+ dev_warn(ph->dev,
+ FW_BUG "Cannot retrieve initial state after reset.\n");
}
ph->xops->xfer_put(ph, t);
@@ -2933,6 +3133,12 @@ static int scmi_telemetry_protocol_init(const struct scmi_protocol_handle *ph)
return ret;
}
+ ret = scmi_telemetry_initial_state_lookup(ti);
+ if (ret) {
+ dev_err(dev, FW_BUG "Cannot retrieve initial state. Abort.\n");
+ return ret;
+ }
+
ti->info.base.version = ph->version;
ret = ph->set_priv(ph, ti);
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v5 14/23] firmware: arm_scmi: Add Telemetry generation counter
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
` (12 preceding siblings ...)
2026-07-03 12:35 ` [PATCH v5 13/23] firmware: arm_scmi: Add support for boot-on Telemetry Cristian Marussi
@ 2026-07-03 12:35 ` Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 15/23] firmware: arm_scmi: Add common per-protocol debugfs support Cristian Marussi
` (9 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:35 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi
Add a per-instance generation counter to track configuration changes and
expose a telemetry operations to get a related waitqueue for monitoring.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
drivers/firmware/arm_scmi/telemetry.c | 53 +++++++++++++++++++++++++++
include/linux/scmi_protocol.h | 5 +++
2 files changed, 58 insertions(+)
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
index 1cd24a58c51d..f54e7d262f7f 100644
--- a/drivers/firmware/arm_scmi/telemetry.c
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -21,6 +21,7 @@
#include <linux/sprintf.h>
#include <linux/string.h>
#include <linux/xarray.h>
+#include <linux/wait.h>
#include "protocols.h"
#include "notify.h"
@@ -30,6 +31,7 @@
/* Updated only after ALL the mandatory features for that version are merged */
#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x10000
+#define SCMI_TLM_GENERATION_ONE (SCMI_TLM_GENERATION_INVALID + 1U)
#define SCMI_TLM_TDCF_MAX_RETRIES 5
enum scmi_telemetry_protocol_cmd {
@@ -459,6 +461,7 @@ struct telemetry_info {
struct list_head free_des;
struct list_head fcs_des;
struct scmi_telemetry_info info;
+ struct wait_queue_head gen_wq;
struct notifier_block telemetry_nb;
atomic_t rinfo_initializing;
struct completion rinfo_initdone;
@@ -608,6 +611,29 @@ scmi_telemetry_tde_cache_lookup(struct telemetry_de *tde,
return 0;
}
+static inline void __scmi_telemetry_generation_set(struct telemetry_info *ti,
+ unsigned int new)
+{
+ atomic_set(&ti->info.generation, new);
+
+ wake_up_all(&ti->gen_wq);
+}
+
+static inline void scmi_telemetry_generation_update(struct telemetry_info *ti)
+{
+ unsigned int next;
+
+ /* Wrap around skipping invalid generation 0 */
+ next = (atomic_read(&ti->info.generation) + 1) ?: SCMI_TLM_GENERATION_ONE;
+
+ __scmi_telemetry_generation_set(ti, next);
+}
+
+static inline void scmi_telemetry_generation_reset(struct telemetry_info *ti)
+{
+ __scmi_telemetry_generation_set(ti, SCMI_TLM_GENERATION_ONE);
+}
+
struct scmi_tlm_de_priv {
struct telemetry_info *ti;
void *next;
@@ -2098,6 +2124,8 @@ static int __scmi_telemetry_state_set(const struct scmi_protocol_handle *ph,
tstamp_enabled_state,
*tstamp);
+ /* A local change can have an impact anyway */
+ scmi_telemetry_generation_update(ti);
return 0;
}
@@ -2159,6 +2187,9 @@ static int __scmi_telemetry_state_set(const struct scmi_protocol_handle *ph,
ph->xops->xfer_put(ph, t);
+ if (!ret)
+ scmi_telemetry_generation_update(ti);
+
return ret;
}
@@ -2262,6 +2293,9 @@ static int scmi_telemetry_all_disable(const struct scmi_protocol_handle *ph,
ph->xops->xfer_put(ph, t);
+ if (!ret)
+ scmi_telemetry_generation_update(ti);
+
return ret;
}
@@ -2334,6 +2368,9 @@ scmi_telemetry_collection_configure(const struct scmi_protocol_handle *ph,
ph->xops->xfer_put(ph, t);
+ if (!ret)
+ scmi_telemetry_generation_update(ti);
+
return ret;
}
@@ -2769,6 +2806,10 @@ static int scmi_telemetry_reset(const struct scmi_protocol_handle *ph)
struct telemetry_info *ti = ph->get_priv(ph);
scmi_telemetry_local_resources_reset(ti);
+
+ /* Reset generation now that server has been reset */
+ scmi_telemetry_generation_reset(ti);
+
/* Fetch again the states from platform. */
ret = scmi_telemetry_initial_state_lookup(ti);
if (ret)
@@ -2781,6 +2822,14 @@ static int scmi_telemetry_reset(const struct scmi_protocol_handle *ph)
return ret;
}
+static struct wait_queue_head *
+scmi_telemetry_event_wq_get(const struct scmi_protocol_handle *ph)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ return &ti->gen_wq;
+}
+
static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.info_get = scmi_telemetry_info_get,
.de_lookup = scmi_telemetry_de_lookup,
@@ -2793,6 +2842,7 @@ 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_wq_get = scmi_telemetry_event_wq_get,
};
static bool
@@ -3083,6 +3133,9 @@ static int scmi_telemetry_instance_init(struct telemetry_info *ti)
xa_init(&ti->xa_des);
xa_init(&ti->xa_lines);
+ /* Generation counter init */
+ atomic_set(&ti->info.generation, SCMI_TLM_GENERATION_ONE);
+ init_waitqueue_head(&ti->gen_wq);
atomic_set(&ti->des_enabled[ENA_STATE], 0);
atomic_set(&ti->des_enabled[ENA_TSTAMP], 0);
/* Setup resources lazy initialization */
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index 44ace368fc99..de6b3c7cc959 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -13,6 +13,7 @@
#include <linux/device.h>
#include <linux/notifier.h>
#include <linux/types.h>
+#include <linux/wait.h>
#define SCMI_MAX_STR_SIZE 64
#define SCMI_SHORT_NAME_MAX_SIZE 16
@@ -889,6 +890,7 @@ enum scmi_telemetry_collection {
SCMI_TLM_SINGLE_READ,
};
+#define SCMI_TLM_GENERATION_INVALID 0U
#define SCMI_TLM_GRP_INVALID 0xFFFFFFFF
#define SCMI_TLM_DE_IMPL_MAX_DWORDS 4
@@ -983,6 +985,7 @@ struct scmi_telemetry_info {
bool enabled;
bool notif_enabled;
enum scmi_telemetry_collection current_mode;
+ atomic_t generation;
};
struct scmi_telemetry_de_sample {
@@ -1016,6 +1019,7 @@ struct scmi_telemetry_de_sample {
* This causes an immediate update platform-side of all the
* enabled DEs.
* @reset: reset configuration and telemetry data.
+ * @event_wq_get: get a reference to the event waitqueue for this instance.
*/
struct scmi_telemetry_proto_ops {
const struct scmi_telemetry_info __must_check *(*info_get)
@@ -1042,6 +1046,7 @@ struct scmi_telemetry_proto_ops {
int grp_id, int *num_samples,
struct scmi_telemetry_de_sample *samples);
int (*reset)(const struct scmi_protocol_handle *ph);
+ struct wait_queue_head *(*event_wq_get)(const struct scmi_protocol_handle *ph);
};
/**
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v5 15/23] firmware: arm_scmi: Add common per-protocol debugfs support
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
` (13 preceding siblings ...)
2026-07-03 12:35 ` [PATCH v5 14/23] firmware: arm_scmi: Add Telemetry generation counter Cristian Marussi
@ 2026-07-03 12:35 ` Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 16/23] firmware: arm_scmi: Add Telemetry debugfs SHMTI dump support Cristian Marussi
` (8 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:35 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi
Allow interested SCMI protocols to register their own specific debugfs
entries under a common per-instance and per-protocol subtree rooted
at /sys/kernel/debug/scmi/<N>/protocols/<PROTO_ID>/
Expose a helper to enable protocol initialization code to get access to
such per-protocol/per-instance dentries in order to be able to install
their own dedicated debugfs entries.
Per-protocol debugfs support is configurable and default off.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
drivers/firmware/arm_scmi/Kconfig | 14 +++++++++++++
drivers/firmware/arm_scmi/common.h | 2 ++
drivers/firmware/arm_scmi/driver.c | 29 +++++++++++++++++++++++++++
drivers/firmware/arm_scmi/protocols.h | 6 ++++++
include/linux/scmi_protocol.h | 12 ++++++++++-
5 files changed, 62 insertions(+), 1 deletion(-)
diff --git a/drivers/firmware/arm_scmi/Kconfig b/drivers/firmware/arm_scmi/Kconfig
index e3fb36825978..06d2319420a0 100644
--- a/drivers/firmware/arm_scmi/Kconfig
+++ b/drivers/firmware/arm_scmi/Kconfig
@@ -69,6 +69,20 @@ config ARM_SCMI_DEBUG_COUNTERS
such useful debug counters. This can be helpful for debugging and
SCMI monitoring.
+config ARM_SCMI_DEBUG_PROTOCOLS
+ bool "Enable SCMI protocols debug"
+ select ARM_SCMI_NEED_DEBUGFS
+ depends on DEBUG_FS
+ default n
+ help
+ Enables per-protocol specific debug features, where available.
+ When provided, such per-protocol debugfs entries are grouped
+ inside a common a subtree named by the protocol number and rooted
+ under a per-instance 'protocols' directory.
+
+ Such per-protocol entries subtree structure is freely defined
+ within the related protocol code.
+
config ARM_SCMI_QUIRKS
bool "Enable SCMI Quirks framework"
depends on JUMP_LABEL || COMPILE_TEST
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index b9cd542302fb..8582e56ec488 100644
--- a/drivers/firmware/arm_scmi/common.h
+++ b/drivers/firmware/arm_scmi/common.h
@@ -322,6 +322,7 @@ enum debug_counters {
/**
* struct scmi_debug_info - Debug common info
* @top_dentry: A reference to the top debugfs dentry
+ * @protos: A reference to the top debugfs protocols subdirectory
* @name: Name of this SCMI instance
* @type: Type of this SCMI instance
* @is_atomic: Flag to state if the transport of this instance is atomic
@@ -329,6 +330,7 @@ enum debug_counters {
*/
struct scmi_debug_info {
struct dentry *top_dentry;
+ struct dentry *protos;
const char *name;
const char *type;
bool is_atomic;
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index f933b8b9e29c..d54d155e69a8 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -18,6 +18,7 @@
#include <linux/bitmap.h>
#include <linux/cleanup.h>
+#include <linux/dcache.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/export.h>
@@ -99,6 +100,8 @@ struct scmi_xfers_info {
* has completed.
* @ph: An embedded protocol handle that will be passed down to protocol
* initialization code to identify this instance.
+ * @dbg: An optional reference to this protocol top debugfs directory; it will
+ * be automatically recursively removed on protocol de-initialization.
*
* Each protocol is initialized independently once for each SCMI platform in
* which is defined by DT and implemented by the SCMI server fw.
@@ -112,6 +115,7 @@ struct scmi_protocol_instance {
unsigned int version;
unsigned int negotiated_version;
struct scmi_protocol_handle ph;
+ struct dentry *dbg;
};
#define ph_to_pi(h) container_of(h, struct scmi_protocol_instance, ph)
@@ -2085,6 +2089,25 @@ static void scmi_common_fastchannel_db_ring(struct scmi_fc_db_info *db)
SCMI_PROTO_FC_RING_DB(64);
}
+static struct dentry *
+scmi_debugfs_proto_dentry_get(const struct scmi_protocol_handle *ph)
+{
+ struct scmi_protocol_instance *pi = ph_to_pi(ph);
+ struct scmi_info *info = handle_to_scmi_info(pi->handle);
+
+ if (!IS_ENABLED(CONFIG_ARM_SCMI_DEBUG_PROTOCOLS))
+ return ERR_PTR(-ENODEV);
+
+ if (!pi->dbg) {
+ char proto_dir[8];
+
+ snprintf(proto_dir, 8, "0x%02X", pi->proto->id);
+ pi->dbg = debugfs_create_dir(proto_dir, info->dbg->protos);
+ }
+
+ return pi->dbg;
+}
+
static const struct scmi_proto_helpers_ops helpers_ops = {
.extended_name_get = scmi_common_extended_name_get,
.get_max_msg_size = scmi_common_get_max_msg_size,
@@ -2095,6 +2118,7 @@ static const struct scmi_proto_helpers_ops helpers_ops = {
.protocol_msg_check = scmi_protocol_msg_check,
.fastchannel_init = scmi_common_fastchannel_init,
.fastchannel_db_ring = scmi_common_fastchannel_db_ring,
+ .debugfs_proto_dentry_get = scmi_debugfs_proto_dentry_get,
};
/**
@@ -2389,6 +2413,8 @@ void scmi_protocol_release(const struct scmi_handle *handle, u8 protocol_id)
if (refcount_dec_and_test(&pi->users)) {
void *gid = pi->gid;
+ debugfs_remove_recursive(pi->dbg);
+
if (pi->proto->events)
scmi_deregister_protocol_events(handle, protocol_id);
@@ -3111,6 +3137,9 @@ static struct scmi_debug_info *scmi_debugfs_common_setup(struct scmi_info *info)
if (IS_ENABLED(CONFIG_ARM_SCMI_DEBUG_COUNTERS))
scmi_debugfs_counters_setup(dbg, trans);
+ if (IS_ENABLED(CONFIG_ARM_SCMI_DEBUG_PROTOCOLS))
+ dbg->protos = debugfs_create_dir("protocols", top_dentry);
+
dbg->top_dentry = top_dentry;
if (devm_add_action_or_reset(info->dev,
diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h
index cc67e512fb6a..29dc6f14ee6d 100644
--- a/drivers/firmware/arm_scmi/protocols.h
+++ b/drivers/firmware/arm_scmi/protocols.h
@@ -11,6 +11,7 @@
#include <linux/bitfield.h>
#include <linux/completion.h>
+#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/kernel.h>
@@ -280,6 +281,9 @@ struct scmi_fc_info {
* gathering FC descriptions from the SCMI platform server.
* @fastchannel_db_ring: A common helper to ring a FC doorbell.
* @get_max_msg_size: A common helper to get the maximum message size.
+ * @debugfs_proto_dentry_get: A common helper to get a per-protocol debugfs top
+ * directory to use as a root. It will be
+ * recursively removed on protocol de-initialization.
*/
struct scmi_proto_helpers_ops {
int (*extended_name_get)(const struct scmi_protocol_handle *ph,
@@ -303,6 +307,8 @@ struct scmi_proto_helpers_ops {
u32 *rate_limit);
void (*fastchannel_db_ring)(struct scmi_fc_db_info *db);
int (*get_max_msg_size)(const struct scmi_protocol_handle *ph);
+ struct dentry *(*debugfs_proto_dentry_get)
+ (const struct scmi_protocol_handle *ph);
};
/**
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index de6b3c7cc959..c932390b9a09 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -1126,6 +1126,12 @@ struct scmi_notify_ops {
* never sleep and act accordingly.
* An optional atomic threshold value could be returned
* where configured.
+ * @debugfs_entry_get: method to get, and possibly create, a debugfs dentry
+ * rooted under the top debugfs directory for the SCMI
+ * instance referred by handle.
+ * @debugfs_entry_put: method to put, and possibly destroy, a debugfs dentry
+ * rooted under the top debugfs directory for the SCMI
+ * instance referred by handle.
* @notify_ops: pointer to set of notifications related operations
*/
struct scmi_handle {
@@ -1140,7 +1146,11 @@ struct scmi_handle {
void (*devm_protocol_put)(struct scmi_device *sdev, u8 proto);
bool (*is_transport_atomic)(const struct scmi_handle *handle,
unsigned int *atomic_threshold);
-
+ struct dentry __must_check *
+ (*debugfs_entry_get)(const struct scmi_handle *handle,
+ const char *name);
+ void (*debugfs_entry_put)(const struct scmi_handle *handle,
+ struct dentry *dentry);
const struct scmi_notify_ops *notify_ops;
};
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v5 16/23] firmware: arm_scmi: Add Telemetry debugfs SHMTI dump support
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
` (14 preceding siblings ...)
2026-07-03 12:35 ` [PATCH v5 15/23] firmware: arm_scmi: Add common per-protocol debugfs support Cristian Marussi
@ 2026-07-03 12:35 ` Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 17/23] firmware: arm_scmi: Add Telemetry debugfs ABI documentation Cristian Marussi
` (7 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:35 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi
Expose one debugfs entry for each discovered SHMTI that can be used to
dump the related SHMTI in binary form from TBGN up to TEND markers.
No processing is done kernel side, beside using proper accessors for
device memory.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
drivers/firmware/arm_scmi/telemetry.c | 91 +++++++++++++++++++++++++++
1 file changed, 91 insertions(+)
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
index f54e7d262f7f..163098985a26 100644
--- a/drivers/firmware/arm_scmi/telemetry.c
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -11,6 +11,8 @@
#include <linux/compiler_types.h>
#include <linux/completion.h>
#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/limits.h>
@@ -369,6 +371,12 @@ struct telemetry_shmti {
struct scmi_telemetry_shmti_info info;
};
+struct scmi_telemetry_dbg_shmti {
+ struct telemetry_shmti *shmti;
+ size_t buf_sz;
+ void *buf;
+};
+
#define SHMTI_EPLG(s) \
({ \
struct telemetry_shmti *_s = (s); \
@@ -1297,6 +1305,86 @@ scmi_telemetry_enumerate_common_intervals(struct telemetry_info *ti)
ti->info.intervals);
}
+static int scmi_telemetry_dbg_shmti_open(struct inode *inode, struct file *filp)
+{
+ struct scmi_telemetry_dbg_shmti *sblob;
+ struct telemetry_shmti *shmti;
+
+ if (!inode->i_private)
+ return -ENODEV;
+
+ shmti = inode->i_private;
+ sblob = kzalloc_obj(*sblob);
+ if (!sblob)
+ return -ENOMEM;
+
+ sblob->shmti = shmti;
+ sblob->buf = kzalloc(shmti->info.len, GFP_KERNEL);
+ if (!sblob->buf) {
+ kfree(sblob);
+ return -ENOMEM;
+ }
+
+ filp->private_data = sblob;
+
+ return 0;
+}
+
+static ssize_t scmi_telemetry_dbg_shmti_read(struct file *filp, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct scmi_telemetry_dbg_shmti *sblob = filp->private_data;
+
+ /* Dump on first read and again after each rewind to start pos */
+ if (!sblob->buf_sz || *ppos == 0) {
+ memcpy_fromio(sblob->buf, sblob->shmti->base,
+ sblob->shmti->info.len);
+ sblob->buf_sz = sblob->shmti->info.len;
+ /* update inode timestamps */
+ simple_inode_init_ts(file_inode(filp));
+ }
+
+ return simple_read_from_buffer(buf, count, ppos, sblob->buf, sblob->buf_sz);
+}
+
+static int scmi_telemetry_dbg_shmti_release(struct inode *inode, struct file *filp)
+{
+ struct scmi_telemetry_dbg_shmti *sblob = filp->private_data;
+
+ kfree(sblob->buf);
+ kfree(sblob);
+
+ return 0;
+}
+
+static const struct file_operations scmi_telemetry_dbg_shmti_fops = {
+ .open = scmi_telemetry_dbg_shmti_open,
+ .release = scmi_telemetry_dbg_shmti_release,
+ .read = scmi_telemetry_dbg_shmti_read,
+ .llseek = generic_file_llseek,
+ .owner = THIS_MODULE,
+};
+
+static void scmi_telemetry_debugfs_initialize(struct telemetry_info *ti)
+{
+ const struct scmi_protocol_handle *ph = ti->ph;
+ struct dentry *top, *shmti_top;
+
+ top = ph->hops->debugfs_proto_dentry_get(ph);
+ shmti_top = debugfs_create_dir("shmtis", top);
+
+ for (unsigned int i = 0; i < ti->num_shmti; i++) {
+ struct dentry *d;
+ char id[16];
+
+ snprintf(id, 16, "%u", i);
+ d = debugfs_create_file(id, 0444, shmti_top, &ti->shmti[i],
+ &scmi_telemetry_dbg_shmti_fops);
+ if (!IS_ERR(d))
+ i_size_write(d->d_inode, ti->shmti[i].info.len);
+ }
+}
+
static int iter_shmti_update_state(struct scmi_iterator_state *st,
const void *response, void *priv)
{
@@ -3212,6 +3300,9 @@ static int scmi_telemetry_protocol_init(const struct scmi_protocol_handle *ph)
"Could NOT register Telemetry notifications\n");
}
+ if (IS_ENABLED(CONFIG_ARM_SCMI_DEBUG_PROTOCOLS))
+ scmi_telemetry_debugfs_initialize(ti);
+
return ret;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v5 17/23] firmware: arm_scmi: Add Telemetry debugfs ABI documentation
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
` (15 preceding siblings ...)
2026-07-03 12:35 ` [PATCH v5 16/23] firmware: arm_scmi: Add Telemetry debugfs SHMTI dump support Cristian Marussi
@ 2026-07-03 12:35 ` Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 18/23] firmware: arm_scmi: Expose per-instance identifier Cristian Marussi
` (6 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:35 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi
Add description of the debugfs SCMI Telemetry protocol ABI.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
Documentation/ABI/testing/debugfs-scmi | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/Documentation/ABI/testing/debugfs-scmi b/Documentation/ABI/testing/debugfs-scmi
index ee7179ab2edf..9026f75e0016 100644
--- a/Documentation/ABI/testing/debugfs-scmi
+++ b/Documentation/ABI/testing/debugfs-scmi
@@ -68,3 +68,25 @@ Description: Max number of concurrently allowed in-flight SCMI messages for
the currently configured SCMI transport for instance <n> on the
RX channels.
Users: Debugging, any userspace test suite
+
+What: /sys/kernel/debug/scmi/<n>/protocols/0x<m>/
+Date: Nov 2026
+KernelVersion: 7.3
+Contact: cristian.marussi@arm.com
+Description: A subdirectory grouping debug entries related to protocol <m>
+ for instance <n>. Each protocol owns and defines the subtree
+ of entries rooted under this directory.
+Users: Debugging, any userspace test suite
+
+What: /sys/kernel/debug/scmi/<n>/protocols/0x1B/shmtis/<n>
+Date: Nov 2026
+KernelVersion: 7.3
+Contact: cristian.marussi@arm.com
+Description: A set of RO files exposed by the Telemetry protocol (0x1B) in
+ order to dump the latest snapshot of each SHMTI memory area in
+ binary format: each file is named by its SHMTI id.
+ File is seekable and a seek to position zero on an open file
+ causes the SHMTI snapshot to refreshed; file timestamps are
+ updated after each snapshot.
+ This directory reports SHMTIs for instance <n>.
+Users: Debugging, any userspace test suite
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v5 18/23] firmware: arm_scmi: Expose per-instance identifier
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
` (16 preceding siblings ...)
2026-07-03 12:35 ` [PATCH v5 17/23] firmware: arm_scmi: Add Telemetry debugfs ABI documentation Cristian Marussi
@ 2026-07-03 12:35 ` Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 19/23] uapi: Add ARM SCMI Telemetry definitions Cristian Marussi
` (5 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:35 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi
When multiple SCMI instances are configured, the SCMI stack probes and
it is initialized multiple times: one core stack for each defined instance.
Each istance is simply identified internally with a number and such number
is already exposed to the user to name the root of the per-instance debugfs
subtree, if enabled.
Expose such unique identifier to the SCMI drivers so that they can use
that same identifier when in need to name their per-instance resources.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
drivers/firmware/arm_scmi/driver.c | 1 +
include/linux/scmi_protocol.h | 5 +++++
2 files changed, 6 insertions(+)
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index d54d155e69a8..619d01523729 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -3297,6 +3297,7 @@ static int scmi_probe(struct platform_device *pdev)
idr_init(&info->rx_idr);
handle = &info->handle;
+ handle->id = info->id;
handle->dev = info->dev;
handle->version = &info->version;
handle->devm_protocol_acquire = scmi_devm_protocol_acquire;
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index c932390b9a09..b90913235225 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -1109,6 +1109,10 @@ struct scmi_notify_ops {
/**
* struct scmi_handle - Handle returned to ARM SCMI clients for usage.
*
+ * @id: A unique positive natural integer identifying the SCMI Instance
+ * associated with this handle to be used across all drivers for
+ * naming purposes: same identifier used internally as the root for
+ * the debugfs per-instance tree.
* @dev: pointer to the SCMI device
* @version: pointer to the structure containing SCMI version information
* @devm_protocol_acquire: devres managed method to get hold of a protocol,
@@ -1135,6 +1139,7 @@ struct scmi_notify_ops {
* @notify_ops: pointer to set of notifications related operations
*/
struct scmi_handle {
+ unsigned int id;
struct device *dev;
struct scmi_base_info *version;
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v5 19/23] uapi: Add ARM SCMI Telemetry definitions
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
` (17 preceding siblings ...)
2026-07-03 12:35 ` [PATCH v5 18/23] firmware: arm_scmi: Expose per-instance identifier Cristian Marussi
@ 2026-07-03 12:35 ` Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 20/23] firmware: arm_scmi: Add System Telemetry driver Cristian Marussi
` (4 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:35 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi
Add a number of structures and ioctls definitions used by the ARM
SCMI Telemetry protocol.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v4 --> v5
- Added more structure padding
- Removed frowned upon flexible array in favour of u64 pointers to external
userspace provided buffer
- Added UAPI support to access SHMTIs from userspace
- Cleaned up Doxygen
v2 --> v3
- Change tstamp_exp tp ts_rate
- renamed num -> num_intervals in scmi_tlm_interval
- added padding in scmi_tlm_de_sample to avoid packing issues on 32bit
v1 --> v2
- Added proper __counted_by marks
- Fixed a few dox comments
- Renamed reserved[] fields to pad[]
---
MAINTAINERS | 1 +
include/uapi/linux/scmi.h | 359 ++++++++++++++++++++++++++++++++++++++
2 files changed, 360 insertions(+)
create mode 100644 include/uapi/linux/scmi.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 15011f5752a9..3ad7e86e4466 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26301,6 +26301,7 @@ F: drivers/regulator/scmi-regulator.c
F: drivers/reset/reset-scmi.c
F: include/linux/sc[mp]i_protocol.h
F: include/trace/events/scmi.h
+F: include/uapi/linux/scmi.h
F: include/uapi/linux/virtio_scmi.h
SYSTEM CONTROL MANAGEMENT INTERFACE (SCMI) i.MX Extension Message Protocol drivers
diff --git a/include/uapi/linux/scmi.h b/include/uapi/linux/scmi.h
new file mode 100644
index 000000000000..3fc4dbe1adf7
--- /dev/null
+++ b/include/uapi/linux/scmi.h
@@ -0,0 +1,359 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Copyright (C) 2026 ARM Ltd.
+ */
+#ifndef _UAPI_LINUX_SCMI_H
+#define _UAPI_LINUX_SCMI_H
+
+/*
+ * Userspace interface SCMI Telemetry
+ */
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+#define SCMI_TLM_DE_IMPL_MAX_DWORDS 4
+
+/**
+ * struct scmi_tlm_base_info - Basic information about an instance
+ *
+ * @version: SCMI Telemetry protocol version
+ * @de_impl_version: SCMI Telemetry DE implementation revision
+ * @num_de: Number of defined DEs
+ * @num_groups: Number of defined DEs groups
+ * @num_intervals: Number of available update intervals (instance-level)
+ * @num_shmtis: Number of discovered SHMTI areas
+ * @flags: Instance specific feature-support bitmap
+ *
+ * Used by:
+ * RO - SCMI_TLM_GET_INFO
+ */
+struct scmi_tlm_base_info {
+ __u32 version;
+ __u32 de_impl_version[SCMI_TLM_DE_IMPL_MAX_DWORDS];
+ __u32 num_des;
+ __u32 num_groups;
+ __u32 num_intervals;
+ __u32 num_shmtis;
+ __u32 flags;
+#define SCMI_TLM_BASE_CAN_RESET (1 << 0)
+};
+
+/**
+ * struct scmi_tlm_update_interval - Update interval descriptor
+ *
+ * @secs: integer representing seconds
+ * @exp: signed integer representing the base 10 exponent used as a multiplier
+ * with @secs to represent the interval
+ *
+ * The resulting update interval is calculated as: <secs> * 10 ^ <exp>
+ */
+struct scmi_tlm_update_interval {
+ __u32 secs;
+ __u32 exp;
+};
+
+/**
+ * struct scmi_tlm_config - Whole instance or group configuration
+ *
+ * @enable: Enable/Disable Telemetry for the whole instance or the group
+ * @t_enable: Enable/Disable timestamping for all the DEs belonging to a group
+ * @flags: Bitmask to represent special characteristics
+ * @pad: Padding fields to enforce alignment
+ * @grp_id: Identifier of the target group upon which this configuration will
+ * be applied: ignored if not marked as a group in @flags
+ * @active: Get/Set currently active update interval for the whole instance or
+ * a group
+ *
+ * Used by:
+ * RW - SCMI_TLM_GET_CFG
+ * WO - SCMI_TLM_SET_CFG
+ */
+struct scmi_tlm_config {
+ __u8 enable;
+ __u8 t_enable;
+ __u8 flags;
+#define SCMI_TLM_CONFIG_GROUP (1 << 0)
+#define SCMI_TLM_CONFIG_FLAGS (SCMI_TLM_CONFIG_GROUP)
+#define SCMI_TLM_CONFIG_IS_GROUP(f) ((f) & SCMI_TLM_CONFIG_GROUP)
+ __u8 pad;
+ __u32 grp_id;
+ struct scmi_tlm_update_interval active;
+};
+
+/**
+ * struct scmi_tlm_intervals - Update intervals descriptor
+ *
+ * @grp_id: Identifier of the target group upon which this configuration
+ * will be applied: ignored if not marked as a group in @flags
+ * @flags: Bitmask to represent special characteristics. When the interval
+ * is NOT marked as SCMI_TLM_INTERV_DISCRETE, @intervals will
+ * contain a triplet: min/max/step
+ * @pad: Padding fields to enforce alignment
+ * @num_intervals: Number of entries of @intervals
+ * @pad2: Padding fields to enforce alignment
+ * @intervals: A pointer to an array of struct scmi_tlm_update_interval
+ *
+ * Used by:
+ * RW - SCMI_TLM_GET_INTRVS
+ */
+struct scmi_tlm_intervals {
+ __u32 grp_id;
+ __u8 flags;
+#define SCMI_TLM_INTERV_GROUP (1 << 0)
+#define SCMI_TLM_INTERV_DISCRETE (1 << 1)
+#define SCMI_TLM_INTERV_FLAGS \
+ (SCMI_TLM_INTERV_GROUP | SCMI_TLM_INTERV_DISCRETE)
+#define SCMI_TLM_INTERV_IS_GROUP(f) ((f) & SCMI_TLM_INTERV_GROUP)
+ __u8 pad[3];
+ __u32 num_intervals;
+ __u32 pad2;
+#define SCMI_TLM_UPDATE_INTVL_SEGMENT_LOW 0
+#define SCMI_TLM_UPDATE_INTVL_SEGMENT_HIGH 1
+#define SCMI_TLM_UPDATE_INTVL_SEGMENT_STEP 2
+ __u64 intervals;
+};
+
+/**
+ * struct scmi_tlm_de_config - DE configuration
+ *
+ * @id: Identifier of the DE to act upon (ignored by SCMI_TLM_GET/SET_ALL_CFG)
+ * @enable: A boolean to enable/disable the DE
+ * @t_enable: A boolean to enable/disable the timestamp for this DE
+ * (if supported)
+ * @pad: Padding fields to enforce alignment
+ *
+ * Used by:
+ * RW - SCMI_TLM_GET_DE_CFG
+ * RW - SCMI_TLM_SET_DE_CFG
+ * RW - SCMI_TLM_GET_ALL_CFG
+ * RW - SCMI_TLM_SET_ALL_CFG
+ */
+struct scmi_tlm_de_config {
+ __u32 id;
+ __u32 enable;
+ __u32 t_enable;
+ __u32 pad;
+};
+
+/**
+ * struct scmi_tlm_de_info - DataEvent Descriptor
+ *
+ * @id: DE identifier
+ * @grp_id: Identifier of the group which this DE belongs to; valid only if
+ * this DE is marked as belonging to a group in @flags.
+ * @data_sz: DE data size in bytes
+ * @type: DE type
+ * @unit: DE unit of measurements
+ * @unit_exp: Power-of-10 multiplier for DE unit
+ * @ts_rate: Clock rate in kHz used to generate the DE timestamp
+ * @instance_id: DE instance ID
+ * @compo_instance_id: DE component instance ID
+ * @compo_type: Type of component which is associated to this DE
+ * @persistent: Data value for this DE survives reboot (non-cold ones)
+ * @flags: Bitmask to represent special characteristics
+ * @pad: Padding fields to enforce alignment
+ * @pad2: Padding fields to enforce alignment
+ * @name: Name of this DE
+ *
+ * Used to get the full description of a DE: it reflects DE Descriptors
+ * definitions in SCMI V4.0 specification at 3.12.4.6.
+ *
+ * Used by:
+ * RW - SCMI_TLM_GET_DE_INFO
+ */
+struct scmi_tlm_de_info {
+ __u32 id;
+ __u32 grp_id;
+ __u32 data_sz;
+ __u32 type;
+ __u32 unit;
+ __s32 unit_exp;
+ __u32 ts_rate;
+ __u32 instance_id;
+ __u32 compo_instance_id;
+ __u32 compo_type;
+ __u8 persistent;
+ __u8 flags;
+#define SCMI_TLM_DEINFO_GROUP (1 << 0)
+#define SCMI_TLM_DEINFO_FLAGS (SCMI_TLM_DEINFO_GROUP)
+#define SCMI_TLM_DEINFO_HAS_GROUP(f) ((f) & SCMI_TLM_DEINFO_GROUP)
+ __u8 pad[2];
+ __u32 pad2;
+ __u8 name[16];
+};
+
+/**
+ * struct scmi_tlm_des_list - List of all defined DEs
+ *
+ * @num_des: Number of entries in @des
+ * @pad: Padding fields to enforce alignment
+ * @des: A reference to an array containing struct scmi_tlm_de_info
+ * descriptors for all the existent DEs
+ *
+ * Used by:
+ * RW - SCMI_TLM_GET_DE_LIST
+ */
+struct scmi_tlm_des_list {
+ __u32 num_des;
+ __u32 pad;
+ __u64 des;
+};
+
+/**
+ * struct scmi_tlm_de_sample - A DataEvent reading
+ *
+ * @id: DE identifier
+ * @pad: Padding fields to enforce alignment.
+ * @tstamp: DE reading timestamp (0 if timestamp NOT supported)
+ * @val: Reading of the DE data value
+ *
+ * Used by:
+ * RW - SCMI_TLM_GET_DE_VALUE
+ */
+struct scmi_tlm_de_sample {
+ __u32 id;
+ __u32 pad;
+ __u64 tstamp;
+ __u64 val;
+};
+
+/**
+ * struct scmi_tlm_data_read - Bulk read of a number of DataEvents
+ *
+ * @grp_id: Optional group ID number, ignored if not marked as a group request
+ * in @flags and not supported by SCMI_TLM_BATCH_READ
+ * @flags: Bitmask to represent special characteristics
+ * @pad: Padding fields to enforce alignment
+ * @pad2: Padding fields to enforce alignment
+ * @num_samples: Number of entries returned in @samples
+ * @samples: A reference to an array of struct scmi_tlm_de_sample containing
+ * an entry for each DE
+ *
+ * Used by:
+ * RW - SCMI_TLM_SINGLE_SAMPLE
+ * RW - SCMI_TLM_BULK_READ
+ * RW - SCMI_TLM_BATCH_READ
+ */
+struct scmi_tlm_data_read {
+ __u32 grp_id;
+ __u8 flags;
+#define SCMI_TLM_READ_GROUP (1 << 0)
+#define SCMI_TLM_READ_FLAGS (SCMI_TLM_READ_GROUP)
+#define SCMI_TLM_READ_IS_GROUP(f) ((f) & SCMI_TLM_READ_GROUP)
+ __u8 pad[3];
+ __u32 pad2;
+ __u32 num_samples;
+ __u64 samples;
+};
+
+/**
+ * struct scmi_tlm_grp_info - Group info descriptor
+ *
+ * @grp_id: Group ID number
+ * @num_des: Number of DEs part of this group
+ * @num_intervals: Number of update intervals supported. Zero if group does not
+ * support per-group update interval configuration.
+ * @pad: Padding fields to enforce alignment
+ *
+ * Used by:
+ * RW - SCMI_TLM_GET_GRP_INFO
+ */
+struct scmi_tlm_grp_info {
+ __u32 grp_id;
+ __u32 num_des;
+ __u32 num_intervals;
+ __u32 pad;
+};
+
+/**
+ * struct scmi_tlm_grps_list - Group info descriptor list
+ *
+ * @num_grps: Number of entries returned in @grps
+ * @pad: Padding fields to enforce alignment
+ * @grps: A reference to an array of struct scmi_tlm_grp_info containing an
+ * entry for each defined group
+ *
+ * Used by:
+ * RW - SCMI_TLM_GET_GRP_LIST
+ */
+struct scmi_tlm_grps_list {
+ __u32 num_grps;
+ __u32 pad;
+ __u64 grps;
+};
+
+/**
+ * struct scmi_tlm_grp_desc - Group descriptor
+ *
+ * @grp_id: Group ID number
+ * @num_des: Number of DEs composing this group
+ * @composing_des: A reference to an array of __u32 elements containing the
+ * DataEvent IDs composing this group.
+ *
+ * Used by:
+ * RW - SCMI_TLM_GET_GRP_DESC
+ */
+struct scmi_tlm_grp_desc {
+ __u32 grp_id;
+ __u32 num_des;
+ __u64 composing_des;
+};
+
+/**
+ * struct scmi_tlm_shmti_info - SHMTI descriptor
+ *
+ * @sid: SHMTI ID
+ * @fd: Associated opened file descriptor to use for mmap
+ * @len: Size of the SHMTI to be used with mmap on this SHMTI
+ * @offset: Offset in the mmap where the specified SHMTI start
+ *
+ * Used by:
+ * RW - SCMI_TLM_GET_SHMTI_LIST
+ */
+struct scmi_tlm_shmti_info {
+ __u32 sid;
+ __u32 fd;
+ __u32 len;
+ __u32 offset;
+};
+
+/**
+ * struct scmi_tlm_shmtis_list - SHMTIs List
+ *
+ * @num_shmtis: Number of entries returned in @shmtis
+ * @pad: Padding fields to enforce alignment
+ * @shmtis: A reference to an array of struct scmi_tlm_shmti_info containing
+ * an entry for each defined SHMTI
+ *
+ * Used by:
+ * RW - SCMI_TLM_GET_SHMTI_LIST
+ */
+struct scmi_tlm_shmtis_list {
+ __u32 num_shmtis;
+ __u32 pad;
+ __u64 shmtis;
+};
+
+#define SCMI 0xF1
+
+#define SCMI_TLM_GET_INFO _IOR(SCMI, 0x00, struct scmi_tlm_base_info)
+#define SCMI_TLM_GET_CFG _IOWR(SCMI, 0x01, struct scmi_tlm_config)
+#define SCMI_TLM_SET_CFG _IOWR(SCMI, 0x02, struct scmi_tlm_config)
+#define SCMI_TLM_GET_INTRVS _IOWR(SCMI, 0x03, struct scmi_tlm_intervals)
+#define SCMI_TLM_GET_DE_CFG _IOWR(SCMI, 0x04, struct scmi_tlm_de_config)
+#define SCMI_TLM_SET_DE_CFG _IOWR(SCMI, 0x05, struct scmi_tlm_de_config)
+#define SCMI_TLM_GET_DE_INFO _IOWR(SCMI, 0x06, struct scmi_tlm_de_info)
+#define SCMI_TLM_GET_DE_LIST _IOWR(SCMI, 0x07, struct scmi_tlm_des_list)
+#define SCMI_TLM_GET_DE_VALUE _IOWR(SCMI, 0x08, struct scmi_tlm_de_sample)
+#define SCMI_TLM_GET_ALL_CFG _IOWR(SCMI, 0x09, struct scmi_tlm_de_config)
+#define SCMI_TLM_SET_ALL_CFG _IOWR(SCMI, 0x0A, struct scmi_tlm_de_config)
+#define SCMI_TLM_GET_GRP_LIST _IOWR(SCMI, 0x0B, struct scmi_tlm_grps_list)
+#define SCMI_TLM_GET_GRP_INFO _IOWR(SCMI, 0x0C, struct scmi_tlm_grp_info)
+#define SCMI_TLM_GET_GRP_DESC _IOWR(SCMI, 0x0D, struct scmi_tlm_grp_desc)
+#define SCMI_TLM_SINGLE_SAMPLE _IOWR(SCMI, 0x0E, struct scmi_tlm_data_read)
+#define SCMI_TLM_BULK_READ _IOWR(SCMI, 0x0F, struct scmi_tlm_data_read)
+#define SCMI_TLM_BATCH_READ _IOWR(SCMI, 0x10, struct scmi_tlm_data_read)
+#define SCMI_TLM_GET_SHMTI_LIST _IOWR(SCMI, 0x11, struct scmi_tlm_shmtis_list)
+
+#endif /* _UAPI_LINUX_SCMI_H */
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v5 20/23] firmware: arm_scmi: Add System Telemetry driver
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
` (18 preceding siblings ...)
2026-07-03 12:35 ` [PATCH v5 19/23] uapi: Add ARM SCMI Telemetry definitions Cristian Marussi
@ 2026-07-03 12:35 ` Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 21/23] docs: ioctl-number: Add SCMI Ioctls Cristian Marussi
` (3 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:35 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi
Add an SCMI driver to allow enumeration, configuration and data collection
of SCMI Telemetry data.
The driver exposes one character device for each found SCMI instance and a
set of related IOCTLs can be used to enumerate, configure and collect
Telemetry resources.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
drivers/firmware/arm_scmi/Kconfig | 10 +
drivers/firmware/arm_scmi/Makefile | 1 +
.../firmware/arm_scmi/scmi_system_telemetry.c | 1233 +++++++++++++++++
3 files changed, 1244 insertions(+)
create mode 100644 drivers/firmware/arm_scmi/scmi_system_telemetry.c
diff --git a/drivers/firmware/arm_scmi/Kconfig b/drivers/firmware/arm_scmi/Kconfig
index 06d2319420a0..1fc9182224cc 100644
--- a/drivers/firmware/arm_scmi/Kconfig
+++ b/drivers/firmware/arm_scmi/Kconfig
@@ -113,4 +113,14 @@ config ARM_SCMI_POWER_CONTROL
called scmi_power_control. Note this may needed early in boot to catch
early shutdown/reboot SCMI requests.
+config ARM_SCMI_SYSTEM_TELEMETRY
+ tristate "SCMI System Telemetry driver"
+ depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF)
+ help
+ This enables SCMI Systemn Telemetry support that allows userspace to
+ retrieve ARM Telemetry data made available via SCMI.
+
+ This driver can also be built as a module. If so, the module will be
+ called scmi_system_telemetry.
+
endmenu
diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
index fe55b7aa0707..20f8d55840a5 100644
--- a/drivers/firmware/arm_scmi/Makefile
+++ b/drivers/firmware/arm_scmi/Makefile
@@ -18,3 +18,4 @@ obj-$(CONFIG_ARM_SCMI_PROTOCOL) += scmi-core.o
obj-$(CONFIG_ARM_SCMI_PROTOCOL) += scmi-module.o
obj-$(CONFIG_ARM_SCMI_POWER_CONTROL) += scmi_power_control.o
+obj-$(CONFIG_ARM_SCMI_SYSTEM_TELEMETRY) += scmi_system_telemetry.o
diff --git a/drivers/firmware/arm_scmi/scmi_system_telemetry.c b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
new file mode 100644
index 000000000000..e72ac363cf10
--- /dev/null
+++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
@@ -0,0 +1,1233 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SCMI - System Telemetry Driver
+ *
+ * Copyright (C) 2026 ARM Ltd.
+ */
+
+#include <linux/anon_inodes.h>
+#include <linux/atomic.h>
+#include <linux/ctype.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/list.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/overflow.h>
+#include <linux/scmi_protocol.h>
+#include <linux/slab.h>
+#include <linux/sprintf.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include <linux/wait.h>
+
+#include <uapi/linux/scmi.h>
+
+#define SCMI_TLM_DRIVER "scmi-telemetry-driver"
+#define SCMI_TLM_NUM_DEVS 256
+
+#define MAX_BULK_LINE_CHAR_LENGTH 64
+
+/*
+ * Break if inadvertently struct scmi_telemetry_de_sample deviates from the
+ * UAPI counterpart struct scmi_tlm_de_sample: this allows more performant
+ * single DE reads.
+ */
+#define SCMI_DE_SAMPLE_SANITY_CHECK \
+({ \
+ BUILD_BUG_ON_MSG(sizeof(struct scmi_telemetry_de_sample) != \
+ sizeof(struct scmi_tlm_de_sample), \
+ "UAPI DE sample mismatch !"); \
+ ASSERT_STRUCT_OFFSET(struct scmi_telemetry_de_sample, id, \
+ offsetof(struct scmi_tlm_de_sample, id)); \
+ ASSERT_STRUCT_OFFSET(struct scmi_telemetry_de_sample, pad, \
+ offsetof(struct scmi_tlm_de_sample, pad)); \
+ ASSERT_STRUCT_OFFSET(struct scmi_telemetry_de_sample, tstamp, \
+ offsetof(struct scmi_tlm_de_sample, tstamp));\
+ ASSERT_STRUCT_OFFSET(struct scmi_telemetry_de_sample, val, \
+ offsetof(struct scmi_tlm_de_sample, val)); \
+})
+
+static dev_t stlm_devt;
+
+struct scmi_tlm_setup;
+
+struct scmi_tlm_priv {
+ char *buf;
+ size_t buf_sz;
+ int buf_len;
+ unsigned int num_samples;
+ struct scmi_tlm_setup *tsp;
+};
+
+/**
+ * struct scmi_tlm_setup - Telemetry setup descriptor
+ * @dev: A reference to the related device
+ * @ph: A reference to the protocol handle to be used with the ops
+ * @rinfo: A reference to the resource info descriptor
+ * @ops: A reference to the protocol ops
+ */
+struct scmi_tlm_setup {
+ struct device *dev;
+ struct scmi_protocol_handle *ph;
+ const struct scmi_telemetry_res_info *rinfo;
+ const struct scmi_telemetry_proto_ops *ops;
+};
+
+/**
+ * struct scmi_tlm_instance - Telemetry instance descriptor
+ * @id: Progressive number identifying this probed instance.
+ * @devt: The dev_t descriptor attached to this instance
+ * @cdev:
+ * @node: A node to link this in the list of all instances.
+ * @tsp: A reference to the SCMI instance data.
+ * @info: A reference to this instance SCMI Telemetry info data.
+ */
+struct scmi_tlm_instance {
+ unsigned int id;
+ dev_t devt;
+ struct cdev cdev;
+ struct list_head node;
+ struct scmi_tlm_setup *tsp;
+ const struct scmi_telemetry_info *info;
+};
+
+#define to_instance(c) container_of(c, struct scmi_tlm_instance, cdev)
+
+struct scmi_tlm_shmti_ctx {
+ struct scmi_telemetry_shmti_info *shmti;
+ struct kref kref;
+};
+
+#define to_shmti_ctx(c) container_of(c, struct scmi_tlm_shmti_ctx, kref)
+
+static int scmi_tlm_buffer_fill(struct device *dev, char *buf, size_t size,
+ unsigned int *len, unsigned int num,
+ struct scmi_telemetry_de_sample *samples)
+{
+ unsigned int idx, bytes = 0;
+
+ /* Loop till there space for the next line */
+ for (idx = 0; idx < num && size - bytes >= MAX_BULK_LINE_CHAR_LENGTH; idx++) {
+ bytes += scnprintf(buf + bytes, size - bytes,
+ "0x%08X %llu %016llX\n", samples[idx].id,
+ samples[idx].tstamp, samples[idx].val);
+ }
+
+ if (idx < num) {
+ dev_err(dev, "Bulk buffer truncated !\n");
+ return -ENOSPC;
+ }
+
+ if (len)
+ *len = bytes;
+
+ return 0;
+}
+
+static int scmi_tlm_bulk_buffer_fill(struct scmi_tlm_priv *tp)
+{
+ struct scmi_tlm_setup *tsp = tp->tsp;
+ int ret;
+
+ struct scmi_telemetry_de_sample *samples __free(kfree) =
+ kcalloc(tp->num_samples, sizeof(*samples), GFP_KERNEL);
+ if (!samples)
+ return -ENOMEM;
+
+ ret = tsp->ops->des_bulk_read(tsp->ph, SCMI_TLM_GRP_INVALID,
+ &tp->num_samples, samples);
+ if (ret)
+ return ret;
+
+ return scmi_tlm_buffer_fill(tsp->dev, tp->buf, tp->buf_sz, &tp->buf_len,
+ tp->num_samples, samples);
+}
+
+static int scmi_tlm_priv_data_open(struct inode *ino, struct file *filp)
+{
+ struct scmi_tlm_instance *ti = to_instance(ino->i_cdev);
+
+ if (!ti)
+ return -ENODEV;
+
+ struct scmi_tlm_priv *tp __free(kfree) = kzalloc_obj(*tp);
+ if (!tp)
+ return -ENOMEM;
+
+ /* Allocate and empty buffer bug enough to contain all possible DEs */
+ tp->num_samples = ti->info->base.num_des;
+ tp->buf_sz = tp->num_samples * MAX_BULK_LINE_CHAR_LENGTH;
+
+ /*
+ * Note that tp->buf is a scratch buffer, filled once, used to
+ * support multiple chunked read and freed in
+ * scmi_tlm_priv_release.
+ */
+ tp->buf = kzalloc(tp->buf_sz, GFP_KERNEL);
+ if (!tp->buf)
+ return -ENOMEM;
+
+ tp->tsp = ti->tsp;
+ filp->private_data = no_free_ptr(tp);
+
+ return 0;
+}
+
+static ssize_t scmi_tlm_priv_read(struct file *filp, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct scmi_tlm_priv *tp = filp->private_data;
+
+ /*
+ * Retrieve fresh data snapshot on the first read and on each
+ * subsequent seek to zero
+ */
+ if (!tp->buf_len || *ppos == 0) {
+ int ret;
+
+ ret = scmi_tlm_bulk_buffer_fill(tp);
+ if (ret)
+ return ret;
+
+ /* update inode timestamps */
+ simple_inode_init_ts(file_inode(filp));
+ }
+
+ return simple_read_from_buffer(buf, count, ppos, tp->buf, tp->buf_len);
+}
+
+static int scmi_tlm_priv_release(struct inode *ino, struct file *filp)
+{
+ struct scmi_tlm_priv *tp = filp->private_data;
+
+ kfree(tp->buf);
+ kfree(tp);
+
+ return 0;
+}
+
+static inline void scmi_tlm_to_uapi_base_info(struct scmi_tlm_base_info *out,
+ const struct scmi_telemetry_info *in)
+{
+ /* This is a trivial copy */
+ out->version = in->base.version;
+ memcpy(out->de_impl_version, in->base.de_impl_version,
+ sizeof(out->de_impl_version));
+ out->num_des = in->base.num_des;
+ out->num_groups = in->base.num_groups;
+ out->num_intervals = in->base.num_intervals;
+ out->num_shmtis = in->base.num_shmtis;
+ out->flags |= in->reset_support ? SCMI_TLM_BASE_CAN_RESET : 0;
+}
+
+static inline int
+scmi_tlm_to_uapi_intervals(struct scmi_tlm_intervals *out,
+ struct scmi_telemetry_intervals *in,
+ struct scmi_tlm_update_interval *out_interv)
+{
+ unsigned int *in_interv = in->update_intervals;
+
+ /* Does it fit provided param ? */
+ if (out->num_intervals < in->num_intervals)
+ return -EINVAL;
+
+ for (int i = 0; i < in->num_intervals; i++) {
+ out_interv[i].secs =
+ SCMI_TLM_GET_UPDATE_INTERVAL_SECS(in_interv[i]);
+ out_interv[i].exp =
+ SCMI_TLM_GET_UPDATE_INTERVAL_EXP(in_interv[i]);
+ }
+
+ /* Just in case less elements where provided */
+ out->num_intervals = in->num_intervals;
+
+ return 0;
+}
+
+static inline void scmi_tlm_to_uapi_de_info(struct scmi_tlm_de_info *out,
+ struct scmi_telemetry_de_info *in)
+{
+ out->id = in->id;
+ out->grp_id = in->grp_id;
+ out->data_sz = in->data_sz;
+ out->type = in->type;
+ out->unit = in->unit;
+ out->unit_exp = in->unit_exp;
+ out->ts_rate = in->ts_rate;
+ out->instance_id = in->instance_id;
+ out->compo_instance_id = in->compo_instance_id;
+ out->compo_type = in->compo_type;
+ out->persistent = !!in->persistent;
+ out->flags = in->grp_id != SCMI_TLM_GRP_INVALID ?
+ SCMI_TLM_DEINFO_GROUP : 0;
+ out->pad[0] = 0;
+ out->pad[1] = 0;
+ out->pad2 = 0;
+ memcpy(out->name, in->name, 16);
+}
+
+static inline int
+scmi_tlm_to_uapi_des_list(struct scmi_tlm_des_list *out,
+ const struct scmi_telemetry_res_info *in,
+ struct scmi_tlm_de_info *des)
+{
+ struct scmi_tlm_de_info *out_des = des;
+
+ /* Does it fit provided param ? */
+ if (out->num_des < in->num_des)
+ return -EINVAL;
+
+ for (int i = 0; i < in->num_des; i++)
+ scmi_tlm_to_uapi_de_info(&out_des[i], &in->dei_store[i]);
+
+ /* Just in case less elements where provided */
+ out->num_des = in->num_des;
+
+ return 0;
+}
+
+static inline void scmi_tlm_to_uapi_grp_info(struct scmi_tlm_grp_info *out,
+ struct scmi_telemetry_grp_info *in)
+{
+ out->grp_id = in->grp_id;
+ out->num_des = in->num_des;
+ out->num_intervals = in->num_intervals;
+ out->pad = 0;
+}
+
+static inline int
+scmi_tlm_to_uapi_grps_list(struct scmi_tlm_grps_list *out,
+ const struct scmi_telemetry_res_info *in,
+ struct scmi_tlm_grp_info *ginfo)
+{
+ struct scmi_tlm_grp_info *out_ginfo = ginfo;
+
+ /* Does it fit provided param ? */
+ if (out->num_grps < in->num_groups)
+ return -EINVAL;
+
+ for (int i = 0; i < in->num_groups; i++)
+ scmi_tlm_to_uapi_grp_info(&out_ginfo[i], &in->grps_store[i]);
+
+ /* Just in case less elements where provided */
+ out->num_grps = in->num_groups;
+
+ return 0;
+}
+
+static inline int
+scmi_tlm_to_uapi_composing_des(struct scmi_tlm_grp_desc *out,
+ const struct scmi_telemetry_res_info *in,
+ u32 *composing_des)
+{
+ struct scmi_telemetry_grp_info *ginfo = &in->grps_store[out->grp_id];
+ struct scmi_telemetry_group *grp = &in->grps[out->grp_id];
+
+ /* Does it fit provided param ? */
+ if (out->num_des < ginfo->num_des)
+ return -EINVAL;
+
+ for (int i = 0; i < ginfo->num_des; i++)
+ composing_des[i] = in->des[grp->des[i]]->info->id;
+
+ /* Just in case less elements where provided */
+ out->num_des = ginfo->num_des;
+
+ return 0;
+}
+
+/**
+ * scmi_telemetry_res_info_get - Resources info getter
+ * @tsp: A reference to the telemetry instance setup
+ *
+ * On first call this helper takes care to retrieve and cache all the resources
+ * descriptor from the platform, then, on the following invocations it will
+ * always return the cached value.
+ */
+static inline const struct scmi_telemetry_res_info *
+scmi_telemetry_res_info_get(struct scmi_tlm_setup *tsp)
+{
+ const struct scmi_telemetry_res_info *rinfo;
+
+ if (tsp->rinfo)
+ return tsp->rinfo;
+
+ rinfo = tsp->ops->res_get(tsp->ph);
+ /* Cache the retrieved resource info value */
+ smp_store_mb(tsp->rinfo, rinfo);
+
+ return rinfo;
+}
+
+static long
+scmi_tlm_info_get_ioctl(const struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_base_info base;
+
+ scmi_tlm_to_uapi_base_info(&base, ti->info);
+ if (copy_to_user(uptr, &base, sizeof(base)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long
+scmi_tlm_config_get_ioctl(const struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_config cfg;
+
+ if (copy_from_user(&cfg, uptr, sizeof(cfg)))
+ return -EFAULT;
+
+ if (cfg.pad || cfg.flags & ~SCMI_TLM_CONFIG_FLAGS)
+ return -EINVAL;
+
+ if (!(SCMI_TLM_CONFIG_IS_GROUP(cfg.flags))) {
+ unsigned int active = ti->info->active_update_interval;
+
+ cfg.enable = !!ti->info->enabled;
+ cfg.active.secs = SCMI_TLM_GET_UPDATE_INTERVAL_SECS(active);
+ cfg.active.exp = SCMI_TLM_GET_UPDATE_INTERVAL_EXP(active);
+ } else {
+ const struct scmi_telemetry_res_info *rinfo;
+ struct scmi_telemetry_group *grp;
+ unsigned int active;
+
+ rinfo = scmi_telemetry_res_info_get(ti->tsp);
+ if (cfg.grp_id >= rinfo->num_groups)
+ return -EINVAL;
+
+ grp = &rinfo->grps[cfg.grp_id];
+ active = grp->active_update_interval;
+
+ cfg.enable = grp->enabled;
+ cfg.t_enable = grp->tstamp_enabled;
+ cfg.active.secs = SCMI_TLM_GET_UPDATE_INTERVAL_SECS(active);
+ cfg.active.exp = SCMI_TLM_GET_UPDATE_INTERVAL_EXP(active);
+ }
+
+ if (copy_to_user(uptr, &cfg, sizeof(cfg)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long
+scmi_tlm_config_set_ioctl(const struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ struct scmi_tlm_config cfg;
+ unsigned int active;
+
+ if (copy_from_user(&cfg, uptr, sizeof(cfg)))
+ return -EFAULT;
+
+ if (cfg.pad || cfg.flags & ~SCMI_TLM_CONFIG_FLAGS)
+ return -EINVAL;
+
+ if (!SCMI_TLM_CONFIG_IS_GROUP(cfg.flags)) {
+ cfg.grp_id = SCMI_TLM_GRP_INVALID;
+ } else {
+ int ret;
+
+ ret = tsp->ops->state_set(tsp->ph, true, cfg.grp_id,
+ (bool *)&cfg.enable,
+ (bool *)&cfg.t_enable);
+ if (ret)
+ return ret;
+ }
+
+ active = SCMI_TLM_BUILD_UPDATE_INTERVAL(cfg.active.secs, cfg.active.exp);
+
+ return tsp->ops->collection_configure(tsp->ph, cfg.grp_id,
+ (bool *)&cfg.enable, &active, NULL);
+}
+
+static long
+scmi_tlm_intervals_get_ioctl(const struct scmi_tlm_instance *ti,
+ unsigned long arg)
+{
+ struct scmi_telemetry_intervals *tlm_ivs;
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_intervals ivs;
+ size_t ivs_intrv_sz;
+ int ret;
+
+ if (copy_from_user(&ivs, uptr, sizeof(ivs)))
+ return -EFAULT;
+
+ if (ivs.pad[0] || ivs.pad[1] || ivs.pad[2] || ivs.pad2 ||
+ ivs.flags & ~SCMI_TLM_INTERV_FLAGS)
+ return -EINVAL;
+
+ if (!SCMI_TLM_INTERV_IS_GROUP(ivs.flags)) {
+ tlm_ivs = ti->info->intervals;
+ } else {
+ const struct scmi_telemetry_res_info *rinfo;
+
+ rinfo = scmi_telemetry_res_info_get(ti->tsp);
+ if (ivs.grp_id >= rinfo->num_groups)
+ return -EINVAL;
+
+ tlm_ivs = rinfo->grps[ivs.grp_id].intervals;
+ }
+
+ /* A local scratch buffer... */
+ ivs_intrv_sz = ivs.num_intervals * sizeof(struct scmi_tlm_update_interval);
+ struct scmi_tlm_update_interval *ivs_intrv __free(kfree) =
+ kzalloc(ivs_intrv_sz, GFP_KERNEL);
+ if (!ivs_intrv)
+ return -ENOMEM;
+
+ if (copy_from_user(ivs_intrv, u64_to_user_ptr(ivs.intervals),
+ ivs_intrv_sz))
+ return -EFAULT;
+
+ /* Passing the array as a param avoids dancing with uptr->intervals */
+ ret = scmi_tlm_to_uapi_intervals(&ivs, tlm_ivs, ivs_intrv);
+ if (ret)
+ return ret;
+
+ if (copy_to_user(u64_to_user_ptr(ivs.intervals),
+ (void *)ivs_intrv, ivs_intrv_sz))
+ return -EFAULT;
+
+ if (copy_to_user(uptr, &ivs, sizeof(ivs)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long
+scmi_tlm_de_config_set_ioctl(const struct scmi_tlm_instance *ti,
+ unsigned long arg, bool all)
+{
+ const struct scmi_telemetry_res_info *rinfo;
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ struct scmi_tlm_de_config tcfg = {};
+ int ret;
+
+ if (copy_from_user(&tcfg, uptr, sizeof(tcfg)))
+ return -EFAULT;
+
+ if (tcfg.pad)
+ return -EINVAL;
+
+ if (!all)
+ return tsp->ops->state_set(tsp->ph, false, tcfg.id,
+ (bool *)&tcfg.enable,
+ (bool *)&tcfg.t_enable);
+
+ rinfo = scmi_telemetry_res_info_get(tsp);
+ for (int i = 0; i < rinfo->num_des; i++) {
+ ret = tsp->ops->state_set(tsp->ph, false,
+ rinfo->des[i]->info->id,
+ (bool *)&tcfg.enable,
+ (bool *)&tcfg.t_enable);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static long
+scmi_tlm_de_config_get_ioctl(const struct scmi_tlm_instance *ti,
+ unsigned long arg, bool all)
+{
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ struct scmi_tlm_de_config tcfg;
+ int ret;
+
+ if (copy_from_user(&tcfg, uptr, sizeof(tcfg)))
+ return -EFAULT;
+
+ if (tcfg.pad)
+ return -EINVAL;
+
+ ret = tsp->ops->state_get(tsp->ph, all ? NULL : &tcfg.id,
+ (bool *)&tcfg.enable, (bool *)&tcfg.t_enable);
+ if (ret)
+ return ret;
+
+ if (copy_to_user(uptr, &tcfg, sizeof(tcfg)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long
+scmi_tlm_de_info_get_ioctl(const struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ const struct scmi_telemetry_de *de;
+ struct scmi_tlm_de_info dei;
+
+ if (copy_from_user(&dei, uptr, sizeof(dei)))
+ return -EFAULT;
+
+ /* Flags does NOT make sense in the input param */
+ if (dei.pad[0] || dei.pad[1] || dei.pad[2] || dei.flags)
+ return -EINVAL;
+
+ de = tsp->ops->de_lookup(tsp->ph, dei.id);
+ if (!de)
+ return -EINVAL;
+
+ scmi_tlm_to_uapi_de_info(&dei, de->info);
+ if (copy_to_user(uptr, &dei, sizeof(dei)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long
+scmi_tlm_des_list_get_ioctl(const struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ const struct scmi_telemetry_res_info *rinfo;
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_des_list dsl;
+ size_t dsl_des_sz;
+ int ret;
+
+ rinfo = scmi_telemetry_res_info_get(ti->tsp);
+ if (copy_from_user(&dsl, uptr, sizeof(dsl)))
+ return -EFAULT;
+
+ if (dsl.pad || dsl.num_des < rinfo->num_des)
+ return -EINVAL;
+
+ /* A local scratch buffer... */
+ dsl_des_sz = dsl.num_des * sizeof(struct scmi_tlm_de_info);
+ struct scmi_tlm_de_info *dsl_des __free(kfree) =
+ kzalloc(dsl_des_sz, GFP_KERNEL);
+ if (!dsl_des)
+ return -ENOMEM;
+
+ if (copy_from_user(dsl_des, u64_to_user_ptr(dsl.des), dsl_des_sz))
+ return -EFAULT;
+
+ /* Passing the array as a param avoids dancing with uptr->intervals */
+ ret = scmi_tlm_to_uapi_des_list(&dsl, rinfo, dsl_des);
+ if (ret)
+ return ret;
+
+ if (copy_to_user(u64_to_user_ptr(dsl.des), (void *)dsl_des, dsl_des_sz))
+ return -EFAULT;
+
+ if (copy_to_user(uptr, &dsl, sizeof(dsl)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long
+scmi_tlm_de_value_get_ioctl(const struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ struct scmi_tlm_de_sample sample;
+ int ret;
+
+ SCMI_DE_SAMPLE_SANITY_CHECK;
+
+ if (copy_from_user(&sample, uptr, sizeof(sample)))
+ return -EFAULT;
+
+ if (sample.pad)
+ return -EINVAL;
+
+ ret = tsp->ops->de_data_read(tsp->ph,
+ (struct scmi_telemetry_de_sample *)&sample);
+ if (ret)
+ return ret;
+
+ if (copy_to_user(uptr, &sample, sizeof(sample)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long
+scmi_tlm_grp_info_get_ioctl(const struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ const struct scmi_telemetry_res_info *rinfo;
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_grp_info ginfo;
+
+ if (copy_from_user(&ginfo, uptr, sizeof(ginfo)))
+ return -EFAULT;
+
+ if (ginfo.pad)
+ return -EINVAL;
+
+ rinfo = scmi_telemetry_res_info_get(ti->tsp);
+ if (ginfo.grp_id > rinfo->num_groups)
+ return -EINVAL;
+
+ scmi_tlm_to_uapi_grp_info(&ginfo, &rinfo->grps_store[ginfo.grp_id]);
+
+ if (copy_to_user(uptr, &ginfo, sizeof(ginfo)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long
+scmi_tlm_grp_desc_get_ioctl(const struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ const struct scmi_telemetry_res_info *rinfo;
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_grp_desc gdesc;
+ size_t composing_sz;
+ int ret;
+
+ if (copy_from_user(&gdesc, uptr, sizeof(gdesc)))
+ return -EFAULT;
+
+ rinfo = scmi_telemetry_res_info_get(ti->tsp);
+ if (gdesc.grp_id > rinfo->num_groups)
+ return -EINVAL;
+
+ /* A local scratch buffer... */
+ composing_sz = gdesc.num_des * sizeof(u32);
+ u32 *composing_des __free(kfree) = kzalloc(composing_sz, GFP_KERNEL);
+ if (!composing_des)
+ return -ENOMEM;
+
+ if (copy_from_user(composing_des, u64_to_user_ptr(gdesc.composing_des),
+ composing_sz))
+ return -EFAULT;
+
+ /* Passing the array as a param avoids dancing with uptr->intervals */
+ ret = scmi_tlm_to_uapi_composing_des(&gdesc, rinfo, composing_des);
+ if (ret)
+ return ret;
+
+ if (copy_to_user(u64_to_user_ptr(gdesc.composing_des), composing_des,
+ composing_sz))
+ return -EFAULT;
+
+ if (copy_to_user(uptr, &gdesc, sizeof(gdesc)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long
+scmi_tlm_grps_list_get_ioctl(const struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ const struct scmi_telemetry_res_info *rinfo;
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_grps_list gsl;
+ size_t ginfo_sz;
+ int ret;
+
+ if (copy_from_user(&gsl, uptr, sizeof(gsl)))
+ return -EFAULT;
+
+ if (gsl.pad)
+ return -EINVAL;
+
+ rinfo = scmi_telemetry_res_info_get(ti->tsp);
+ if (gsl.num_grps < rinfo->num_groups)
+ return -EINVAL;
+
+ /* A local scratch buffer... */
+ ginfo_sz = gsl.num_grps * sizeof(struct scmi_tlm_grp_info);
+ struct scmi_tlm_grp_info *ginfo __free(kfree) =
+ kzalloc(ginfo_sz, GFP_KERNEL);
+ if (!ginfo)
+ return -ENOMEM;
+
+ if (copy_from_user(ginfo, u64_to_user_ptr(gsl.grps), ginfo_sz))
+ return -EFAULT;
+
+ ret = scmi_tlm_to_uapi_grps_list(&gsl, rinfo, ginfo);
+ if (ret)
+ return ret;
+
+ if (copy_to_user(u64_to_user_ptr(gsl.grps), ginfo, ginfo_sz))
+ return -EFAULT;
+
+ if (copy_to_user(uptr, &gsl, sizeof(gsl)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long scmi_tlm_des_read_ioctl(const struct scmi_tlm_instance *ti,
+ unsigned long arg, bool single)
+{
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ struct scmi_tlm_data_read bulk;
+ unsigned int grp_id;
+ int ret;
+
+ if (copy_from_user(&bulk, uptr, sizeof(bulk)))
+ return -EFAULT;
+
+ if (bulk.pad[0] || bulk.pad[1] || bulk.pad[2] || bulk.pad2 ||
+ bulk.flags & ~SCMI_TLM_READ_FLAGS)
+ return -EINVAL;
+
+ if (!SCMI_TLM_READ_IS_GROUP(bulk.flags)) {
+ grp_id = SCMI_TLM_GRP_INVALID;
+ } else {
+ const struct scmi_telemetry_res_info *rinfo;
+
+ rinfo = scmi_telemetry_res_info_get(tsp);
+ if (bulk.grp_id >= rinfo->num_groups)
+ return -EINVAL;
+
+ grp_id = bulk.grp_id;
+ }
+
+ struct scmi_telemetry_de_sample *samples __free(kfree) =
+ kcalloc(bulk.num_samples, sizeof(*samples), GFP_KERNEL);
+ if (!samples)
+ return -ENOMEM;
+
+ if (!single)
+ ret = tsp->ops->des_bulk_read(tsp->ph, grp_id,
+ &bulk.num_samples, samples);
+ else
+ ret = tsp->ops->des_sample_get(tsp->ph, grp_id,
+ &bulk.num_samples, samples);
+ if (ret)
+ return ret;
+
+ if (copy_to_user(u64_to_user_ptr(bulk.samples), samples,
+ bulk.num_samples * sizeof(*samples)))
+ return -EFAULT;
+
+ if (copy_to_user(uptr, &bulk, sizeof(bulk)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long scmi_tlm_des_batch_read_ioctl(const struct scmi_tlm_instance *ti,
+ unsigned long arg)
+{
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ struct scmi_tlm_data_read batch;
+ int ret;
+
+ if (copy_from_user(&batch, uptr, sizeof(batch)))
+ return -EFAULT;
+
+ if (batch.pad[0] || batch.pad[1] || batch.pad[2] || batch.pad2 ||
+ batch.flags & ~SCMI_TLM_READ_FLAGS)
+ return -EINVAL;
+
+ struct scmi_telemetry_de_sample *samples __free(kfree) =
+ kcalloc(batch.num_samples, sizeof(*samples), GFP_KERNEL);
+ if (!samples)
+ return -ENOMEM;
+
+ /* Read all the requested samples */
+ if (copy_from_user(samples, u64_to_user_ptr(batch.samples),
+ batch.num_samples * sizeof(*samples)))
+ return -EFAULT;
+
+ for (int i = 0; i < batch.num_samples; i++) {
+ ret = tsp->ops->de_data_read(tsp->ph, &samples[i]);
+ if (ret)
+ return -EINVAL;
+ }
+
+ if (copy_to_user(u64_to_user_ptr(batch.samples), samples,
+ batch.num_samples * sizeof(*samples)))
+ return -EFAULT;
+
+ if (copy_to_user(uptr, &batch, sizeof(batch)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static struct scmi_tlm_shmti_ctx *
+scmi_tlm_shmti_ctx_alloc(struct scmi_telemetry_shmti_info *shmti)
+{
+ struct scmi_tlm_shmti_ctx *ctx;
+
+ ctx = kzalloc_obj(*ctx);
+ if (!ctx)
+ return ERR_PTR(-ENOMEM);
+
+ ctx->shmti = shmti;
+ kref_init(&ctx->kref);
+
+ return ctx;
+}
+
+static void scmi_tlm_shmti_ctx_release(struct kref *kref)
+{
+ struct scmi_tlm_shmti_ctx *ctx = to_shmti_ctx(kref);
+
+ kfree(ctx);
+}
+
+static struct scmi_tlm_shmti_ctx *
+scmi_tlm_shmti_ctx_get(struct scmi_tlm_shmti_ctx *ctx)
+{
+ kref_get(&ctx->kref);
+
+ return ctx;
+}
+
+static void scmi_tlm_shmti_ctx_put(struct scmi_tlm_shmti_ctx *ctx)
+{
+ kref_put(&ctx->kref, scmi_tlm_shmti_ctx_release);
+}
+
+static void scmi_tlm_shmti_vma_open(struct vm_area_struct *vma)
+{
+ scmi_tlm_shmti_ctx_get(vma->vm_private_data);
+}
+
+static void scmi_tlm_shmti_vma_close(struct vm_area_struct *vma)
+{
+ scmi_tlm_shmti_ctx_put(vma->vm_private_data);
+}
+
+static const struct vm_operations_struct scmi_tlm_shmti_vm_ops = {
+ .open = scmi_tlm_shmti_vma_open,
+ .close = scmi_tlm_shmti_vma_close,
+};
+
+static int scmi_tlm_shmti_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ struct scmi_tlm_shmti_ctx *ctx = filp->private_data;
+ unsigned long req = vma->vm_end - vma->vm_start;
+ phys_addr_t base;
+ size_t map_sz;
+ int ret;
+
+ if (vma->vm_flags & VM_WRITE)
+ return -EPERM;
+
+ if (req < ctx->shmti->len)
+ return -EINVAL;
+
+ /* Align */
+ base = ctx->shmti->phys & PAGE_MASK;
+ map_sz = PAGE_ALIGN(ctx->shmti->offset + req);
+
+ vma->vm_private_data = scmi_tlm_shmti_ctx_get(ctx);
+ vma->vm_ops = &scmi_tlm_shmti_vm_ops;
+ /* Set appropriate caching attributes */
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+
+ /* Map physical address range into user space */
+ ret = vm_iomap_memory(vma, base, map_sz);
+ if (ret) {
+ scmi_tlm_shmti_ctx_put(ctx);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int scmi_tlm_shmti_release(struct inode *ino, struct file *filp)
+{
+ scmi_tlm_shmti_ctx_put(filp->private_data);
+
+ return 0;
+}
+
+static const struct file_operations scmi_tlm_shmti_fops = {
+ .owner = THIS_MODULE,
+ .mmap = scmi_tlm_shmti_mmap,
+ .release = scmi_tlm_shmti_release,
+};
+
+static int
+scmi_tlm_allocate_anon_fds(const struct scmi_telemetry_info *in,
+ struct scmi_tlm_shmtis_list *out,
+ struct scmi_tlm_shmti_info *shinfo)
+{
+ unsigned int num_shmtis;
+
+ if (out->num_shmtis < in->base.num_shmtis)
+ return -EINVAL;
+
+ /* Count effectively returned ... */
+ num_shmtis = out->num_shmtis;
+ out->num_shmtis = 0;
+
+ for (int i = 0; i < num_shmtis; i++) {
+ struct scmi_telemetry_shmti_info *shmti = in->shmtis[i];
+ struct scmi_tlm_shmti_ctx *ctx;
+ int fd;
+
+ ctx = scmi_tlm_shmti_ctx_alloc(shmti);
+ if (IS_ERR(ctx)) {
+ pr_err("ERR CTX_ALLOC on SID: %u\n", shmti->sid);
+ continue;
+ }
+
+ fd = anon_inode_getfd(SCMI_TLM_DRIVER, &scmi_tlm_shmti_fops, ctx,
+ O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ scmi_tlm_shmti_ctx_put(ctx);
+ pr_err("ERR ANON INODE on SID: %u\n", shmti->sid);
+ continue;
+ }
+
+ shinfo[i].sid = shmti->sid;
+ shinfo[i].fd = fd;
+ shinfo[i].len = shmti->len;
+ shinfo[i].offset = shmti->offset;
+
+ out->num_shmtis++;
+ }
+
+ return 0;
+}
+
+static long
+scmi_tlm_shmtis_list_get_ioctl(const struct scmi_tlm_instance *ti,
+ unsigned long arg)
+{
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_shmtis_list ssl;
+ size_t shinfo_sz;
+ int ret;
+
+ if (copy_from_user(&ssl, uptr, sizeof(ssl)))
+ return -EFAULT;
+
+ if (ssl.pad)
+ return -EINVAL;
+
+ /* A local scratch buffer... */
+ shinfo_sz = ssl.num_shmtis * sizeof(struct scmi_tlm_shmti_info);
+ struct scmi_tlm_shmti_info *shinfo __free(kfree) =
+ kzalloc(shinfo_sz, GFP_KERNEL);
+ if (!shinfo)
+ return -ENOMEM;
+
+ if (copy_from_user(shinfo, u64_to_user_ptr(ssl.shmtis), shinfo_sz))
+ return -EFAULT;
+
+ ret = scmi_tlm_allocate_anon_fds(ti->info, &ssl, shinfo);
+ if (ret)
+ return ret;
+
+ if (copy_to_user(u64_to_user_ptr(ssl.shmtis), shinfo, shinfo_sz))
+ return -EFAULT;
+
+ if (copy_to_user(uptr, &ssl, sizeof(ssl)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long scmi_tlm_unlocked_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct inode *ino = file_inode(filp);
+ struct scmi_tlm_instance *ti = to_instance(ino->i_cdev);
+
+ switch (cmd) {
+ case SCMI_TLM_GET_INFO:
+ return scmi_tlm_info_get_ioctl(ti, arg);
+ case SCMI_TLM_GET_CFG:
+ return scmi_tlm_config_get_ioctl(ti, arg);
+ case SCMI_TLM_SET_CFG:
+ return scmi_tlm_config_set_ioctl(ti, arg);
+ case SCMI_TLM_GET_INTRVS:
+ return scmi_tlm_intervals_get_ioctl(ti, arg);
+ case SCMI_TLM_GET_DE_CFG:
+ return scmi_tlm_de_config_get_ioctl(ti, arg, false);
+ case SCMI_TLM_SET_DE_CFG:
+ return scmi_tlm_de_config_set_ioctl(ti, arg, false);
+ case SCMI_TLM_GET_DE_INFO:
+ return scmi_tlm_de_info_get_ioctl(ti, arg);
+ case SCMI_TLM_GET_DE_LIST:
+ return scmi_tlm_des_list_get_ioctl(ti, arg);
+ case SCMI_TLM_GET_DE_VALUE:
+ return scmi_tlm_de_value_get_ioctl(ti, arg);
+ case SCMI_TLM_GET_ALL_CFG:
+ return scmi_tlm_de_config_get_ioctl(ti, arg, true);
+ case SCMI_TLM_SET_ALL_CFG:
+ return scmi_tlm_de_config_set_ioctl(ti, arg, true);
+ case SCMI_TLM_GET_GRP_LIST:
+ return scmi_tlm_grps_list_get_ioctl(ti, arg);
+ case SCMI_TLM_GET_GRP_INFO:
+ return scmi_tlm_grp_info_get_ioctl(ti, arg);
+ case SCMI_TLM_GET_GRP_DESC:
+ return scmi_tlm_grp_desc_get_ioctl(ti, arg);
+ case SCMI_TLM_SINGLE_SAMPLE:
+ return scmi_tlm_des_read_ioctl(ti, arg, true);
+ case SCMI_TLM_BULK_READ:
+ return scmi_tlm_des_read_ioctl(ti, arg, false);
+ case SCMI_TLM_BATCH_READ:
+ return scmi_tlm_des_batch_read_ioctl(ti, arg);
+ case SCMI_TLM_GET_SHMTI_LIST:
+ return scmi_tlm_shmtis_list_get_ioctl(ti, arg);
+ default:
+ return -ENOTTY;
+ }
+}
+
+static const struct file_operations stlm_fops = {
+ .owner = THIS_MODULE,
+ .open = scmi_tlm_priv_data_open,
+ .read = scmi_tlm_priv_read,
+ .llseek = generic_file_llseek,
+ .unlocked_ioctl = scmi_tlm_unlocked_ioctl,
+ .release = scmi_tlm_priv_release,
+};
+
+static struct scmi_tlm_instance *scmi_tlm_init(struct scmi_tlm_setup *tsp,
+ unsigned int instance_id)
+{
+ struct device *dev = tsp->dev;
+ struct scmi_tlm_instance *ti;
+
+ ti = devm_kzalloc(dev, sizeof(*ti), GFP_KERNEL);
+ if (!ti)
+ return ERR_PTR(-ENOMEM);
+
+ ti->info = tsp->ops->info_get(tsp->ph);
+ if (!ti->info)
+ return dev_err_ptr_probe(dev,
+ -EINVAL, "invalid Telemetry info !\n");
+
+ ti->id = instance_id;
+ ti->tsp = tsp;
+
+ return ti;
+}
+
+static char *scmi_tlm_devnode(const struct device *dev, umode_t *mode)
+{
+ return kasprintf(GFP_KERNEL, "scmi/%s", dev_name(dev));
+}
+
+static struct class stlm_class = {
+ .name = "stlm",
+ .devnode = scmi_tlm_devnode,
+};
+
+static int scmi_telemetry_probe(struct scmi_device *sdev)
+{
+ const struct scmi_handle *handle = sdev->handle;
+ struct device *tdev, *dev = &sdev->dev;
+ struct scmi_protocol_handle *ph;
+ struct scmi_tlm_instance *ti;
+ struct scmi_tlm_setup *tsp;
+ unsigned int instance;
+ const void *ops;
+ int ret;
+
+ if (!handle)
+ return -ENODEV;
+
+ ops = handle->devm_protocol_get(sdev, sdev->protocol_id, &ph);
+ if (IS_ERR(ops))
+ return dev_err_probe(dev, PTR_ERR(ops),
+ "Cannot access protocol:0x%X\n",
+ sdev->protocol_id);
+
+ tsp = devm_kzalloc(dev, sizeof(*tsp), GFP_KERNEL);
+ if (!tsp)
+ return -ENOMEM;
+
+ tsp->dev = dev;
+ tsp->ops = ops;
+ tsp->ph = ph;
+
+ instance = handle->id;
+ if (instance >= SCMI_TLM_NUM_DEVS)
+ return -ENOSPC;
+
+ ti = scmi_tlm_init(tsp, instance);
+ if (IS_ERR(ti))
+ return PTR_ERR(ti);
+
+ cdev_init(&ti->cdev, &stlm_fops);
+ ti->cdev.owner = THIS_MODULE;
+ ti->devt = MKDEV(MAJOR(stlm_devt), MINOR(stlm_devt) + instance);
+ ret = cdev_add(&ti->cdev, ti->devt, 1);
+ if (ret)
+ return ret;
+
+ tdev = device_create(&stlm_class, NULL, ti->devt, ti, "tlm_%d", ti->id);
+ if (IS_ERR(tdev)) {
+ cdev_del(&ti->cdev);
+ return PTR_ERR(tdev);
+ }
+
+ dev_set_drvdata(&sdev->dev, ti);
+
+ return 0;
+}
+
+static void scmi_telemetry_remove(struct scmi_device *sdev)
+{
+ struct scmi_tlm_instance *ti;
+
+ ti = dev_get_drvdata(&sdev->dev);
+ if (!ti)
+ return;
+
+ device_destroy(&stlm_class, ti->devt);
+}
+
+static const struct scmi_device_id scmi_id_table[] = {
+ { SCMI_PROTOCOL_TELEMETRY, "telemetry" },
+ { }
+};
+MODULE_DEVICE_TABLE(scmi, scmi_id_table);
+
+static struct scmi_driver scmi_telemetry_driver = {
+ .name = SCMI_TLM_DRIVER,
+ .probe = scmi_telemetry_probe,
+ .remove = scmi_telemetry_remove,
+ .id_table = scmi_id_table,
+};
+
+static int __init scmi_telemetry_init(void)
+{
+ int ret;
+
+ ret = alloc_chrdev_region(&stlm_devt, 0, SCMI_TLM_NUM_DEVS,
+ SCMI_TLM_DRIVER);
+ if (ret)
+ return ret;
+
+ ret = class_register(&stlm_class);
+ if (ret)
+ return ret;
+
+ return scmi_register(&scmi_telemetry_driver);
+}
+module_init(scmi_telemetry_init);
+
+static void __exit scmi_telemetry_exit(void)
+{
+ scmi_unregister(&scmi_telemetry_driver);
+ unregister_chrdev_region(stlm_devt, SCMI_TLM_NUM_DEVS);
+ class_unregister(&stlm_class);
+}
+module_exit(scmi_telemetry_exit);
+
+MODULE_AUTHOR("Cristian Marussi <cristian.marussi@arm.com>");
+MODULE_DESCRIPTION("ARM SCMI Telemetry Driver");
+MODULE_LICENSE("GPL");
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v5 21/23] docs: ioctl-number: Add SCMI Ioctls
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
` (19 preceding siblings ...)
2026-07-03 12:35 ` [PATCH v5 20/23] firmware: arm_scmi: Add System Telemetry driver Cristian Marussi
@ 2026-07-03 12:35 ` Cristian Marussi
2026-07-03 12:36 ` [PATCH v5 22/23] Documentation: Add SCMI System Telemetry documentation Cristian Marussi
` (2 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:35 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi, Jonathan Corbet, Shuah Khan
Add SCMI Subsystem IOCTLs numbers
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Shuah Khan <skhan@linuxfoundation.org>
Cc: linux-doc@vger.kernel.org
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
Documentation/userspace-api/ioctl/ioctl-number.rst | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
index 3f0ef1e27eb0..de136c5967f4 100644
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
@@ -399,6 +399,7 @@ Code Seq# Include File Comments
0xE5 00-3F linux/fuse.h
0xEC 00-01 drivers/platform/chrome/cros_ec_dev.h ChromeOS EC driver
0xEE 00-09 uapi/linux/pfrut.h Platform Firmware Runtime Update and Telemetry
+0xF1 00-FF uapi/linux/scmi.h ARM SCMI Subsystem
0xF3 00-3F drivers/usb/misc/sisusbvga/sisusb.h sisfb (in development)
<mailto:thomas@winischhofer.net>
0xF6 all LTTng Linux Trace Toolkit Next Generation
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v5 22/23] Documentation: Add SCMI System Telemetry documentation
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
` (20 preceding siblings ...)
2026-07-03 12:35 ` [PATCH v5 21/23] docs: ioctl-number: Add SCMI Ioctls Cristian Marussi
@ 2026-07-03 12:36 ` Cristian Marussi
2026-07-03 12:36 ` [PATCH v5 23/23] [RFC] tools/scmi: Add SCMI Telemetry testing tool Cristian Marussi
2026-07-07 6:31 ` [PATCH v5 00/23] Introduce SCMI Telemetry support Subrahmanya Lingappa
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:36 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi, Jonathan Corbet, Shuah Khan
Document ARM SCMI Telemetry IOCTLs interface.
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Shuah Khan <skhan@linuxfoundation.org>
Cc: linux-doc@vger.kernel.org
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
Documentation/userspace-api/index.rst | 1 +
Documentation/userspace-api/stlm.rst | 143 ++++++++++++++++++++++++++
2 files changed, 144 insertions(+)
create mode 100644 Documentation/userspace-api/stlm.rst
diff --git a/Documentation/userspace-api/index.rst b/Documentation/userspace-api/index.rst
index a68b1bea57a8..22daa7ebf78e 100644
--- a/Documentation/userspace-api/index.rst
+++ b/Documentation/userspace-api/index.rst
@@ -54,6 +54,7 @@ Devices and I/O
dcdbas
vduse
isapnp
+ stlm
Everything else
===============
diff --git a/Documentation/userspace-api/stlm.rst b/Documentation/userspace-api/stlm.rst
new file mode 100644
index 000000000000..ca02281dfa2f
--- /dev/null
+++ b/Documentation/userspace-api/stlm.rst
@@ -0,0 +1,143 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. stlm:
+
+=======================================
+STLM - ARM SCMI Telemetry Userspace API
+=======================================
+
+.. contents::
+
+Overview
+========
+
+ARM SCMI is a System and Configuration Management protocol, based on a
+client-server model, that defines a number of messages that allows a
+client/agent like Linux to discover, configure and make use of services
+provided by the server/platform firmware.
+
+SCMI v4.0 [1] introduced support for System Telemetry, through which an
+agent can dynamically enumerate, configure and collect Telemetry Data
+Events (DE) exposed by the platform.
+
+The SCMI System Telemetry driver expose one or more dedicated devices,
+named as tlm_<N>, grouped under /dev/scmi/, one for each discovered SCMI
+instance.
+
+A IOCTL based interface is defined in order to enumerate, configure and
+read telemetry data, as succinctly described in the following.
+
+For more details on the uAPI, please see include/uapi/linux/scmi.h
+
+Resources Enumeration
+=====================
+
+ - SCMI_TLM_GET_INFO: Gather basic Telemetry features like number of
+ resources and versioning information.
+
+ - SCMI_TLM_GET_INTRVS: Enumerate available update intervals.
+
+ - SCMI_TLM_GET_DE_INFO: Retrieve DataEvent description.
+
+ - SCMI_TLM_GET_DE_LIST: Retrieve the list of all the existent DataEvent
+ dwscriptors.
+
+ - SCMI_TLM_GET_GRP_LIST: Gather a list of descriptors for all defined Groups.
+
+ - SCMI_TLM_GET_GRP_INFO: Gather information for a specific Group.
+
+ - SCMI_TLM_GET_GRP_DESC: Gather detailed group composition information for
+ the specified Group.
+
+
+Configuration
+=============
+
+ - SCMI_TLM_GET_CFG / SCMI_TLM_SET_CFG: Get or set the whole istance, or a
+ specific group, configuration.
+
+ - SCMI_TLM_GET_DE_CFG / SCMI_TLM_SET_DE_CFG: Get or set the configuration
+ of a specific DataEvent.
+
+ - SCMI_TLM_GET_ALL_CFG / SCMI_TLM_SET_ALL_CFG: Get or set the cumulative
+ configuration of ALL the DataEvents defined on the platform.
+
+Data Collection
+===============
+
+ - SCMI_TLM_GET_DE_VALUE: Report the last udpated sample for the specified
+ DataEvent.
+
+ - SCMI_TLM_BULK_READ: Report the last samples for all the currently enabled
+ DataEvents.
+
+ - SCMI_TLM_BATCH_READ: Report the last samples for the DataEvents IDs
+ specified within the samples input params.
+
+ - SCMI_TLM_SINGLE_SAMPLE: Trigger an immediate platform-side DataEvent
+ update andc report the collected samples.
+
+Memory Mapped Raw Access
+------------------------
+
+ - SCMI_TLM_GET_SHMTI_LIST: Gather a list of open file descriptors, one for
+ each SHMTI memory areas defined for this instance, that can be used to
+ memory-map such areas in the calling process address space so as to be
+ able to directly access the SCMI Telemetry SHMTI areas shared by the
+ platform firmware, and parse the TDCF format.
+ The SCMI Telemetry TDCF format is defined in the specification at [1].
+
+
+Example
+=======
+
+.. code-block:: c
+
+ int main(int argc, char **argv)
+ {
+ struct scmi_tlm_de_config des_cfg = {};
+ struct scmi_tlm_config cfg = {};
+ struct scmi_tlm_de_sample samples[3] = {};
+ struct scmi_tlm_data_read data = {};
+ int fd, ret;
+
+ fd = open("/dev/scmi/tlm_0", O_RDWR);
+ if (fd < 0)
+ return fd;
+
+ /* Enable ALL Data Events with timestamps*/
+ des_cfg.enable = 1;
+ des_cfg.t_enable = 1;
+ ret = ioctl(fd, SCMI_TLM_SET_ALL_CFG, &des_cfg);
+ if (ret)
+ return ret;
+
+ /* Enable Telemetry as a whole, set a 400ms update interval */
+ cfg.enable = 1;
+ cfg.active.secs = 400;
+ cfg.active.exp = -3;
+
+ ret = ioctl(fd, SCMI_TLM_SET_CFG, &cfg);
+ if (ret)
+ return ret;
+
+ /* Read a selection of DEs */
+ samples[0].id = 0xA001;
+ samples[1].id = 0x0002;
+ samples[2].id = 0x1010;
+ data.num_samples = 3;
+ data.samples = (__u64)samples;
+
+ ret = ioctl(fd, SCMI_TLM_BATCH_READ, &data);
+ if (ret)
+ return ret;
+
+ for (int i = 0; i < 3; i++)
+ fprintf(stdout, "%llu: 0x%08X -> %llu\n",
+ samples[i].tstamp, samples[i].id, samples[i].val);
+
+ return 0;
+ }
+
+References
+==========
+[1]: https://developer.arm.com/documentation/den0056/latest/
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v5 23/23] [RFC] tools/scmi: Add SCMI Telemetry testing tool
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
` (21 preceding siblings ...)
2026-07-03 12:36 ` [PATCH v5 22/23] Documentation: Add SCMI System Telemetry documentation Cristian Marussi
@ 2026-07-03 12:36 ` Cristian Marussi
2026-07-07 6:31 ` [PATCH v5 00/23] Introduce SCMI Telemetry support Subrahmanya Lingappa
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-03 12:36 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, d-gole, jic23,
elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team, Cristian Marussi
Add a testing tool that exercises the SCMI ioctls UAPI interface: as of
now the tool simply queries the initial state of the SCMI Telemetry
subsystem, tries to enable all the existent Data Events and dumps all
the Telemetry data.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
Basic implementation just to exercise a few IOCTls: to be refined and
extended to support a more interactive usage.
v4 --> v5
- changes to adapt to new chardev/IOCTLs ABI
- change IOCTLs param to avoid flexible arrays
- added SHMTIs mmap dump
v3 --> v4
- added generation file support
---
tools/testing/scmi/Makefile | 25 ++
tools/testing/scmi/stlm.c | 648 ++++++++++++++++++++++++++++++++++++
2 files changed, 673 insertions(+)
create mode 100644 tools/testing/scmi/Makefile
create mode 100644 tools/testing/scmi/stlm.c
diff --git a/tools/testing/scmi/Makefile b/tools/testing/scmi/Makefile
new file mode 100644
index 000000000000..a6a101f8398b
--- /dev/null
+++ b/tools/testing/scmi/Makefile
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+CC?=$(CROSS_COMPILE)gcc
+OBJS = stlm.o
+
+CFLAGS=-Wall -static -std=gnu11 -I ../../../include/uapi/
+ifneq ($(DEBUG), )
+ CFLAGS+=-O0 -g -ggdb
+else
+ CFLAGS+=-static
+endif
+
+all: stlm
+
+stlm: $(OBJS)
+ $(CC) $(CFLAGS) $^ -o $@
+
+%.o: %.c
+ $(CC) $(CFLAGS) -c $<
+
+clean:
+ rm -f *.o
+ rm -f stlm
+
+.PHONY: clean
diff --git a/tools/testing/scmi/stlm.c b/tools/testing/scmi/stlm.c
new file mode 100644
index 000000000000..4d7d19899d5c
--- /dev/null
+++ b/tools/testing/scmi/stlm.c
@@ -0,0 +1,648 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+
+#include <unistd.h>
+
+#include <linux/scmi.h>
+
+#define SLEEP_MS 3000
+
+#define IOCTL_ERR_STR(_ioctl) "IOCTL:" #_ioctl
+
+struct tlm_de {
+ struct scmi_tlm_de_info *info;
+ struct scmi_tlm_de_config cfg;
+ struct scmi_tlm_de_sample sample;
+};
+
+struct tlm_group {
+ int fd;
+ struct scmi_tlm_grp_info *info;
+ struct scmi_tlm_grp_desc *desc;
+ struct scmi_tlm_intervals *ivs;
+};
+
+struct tlm_state {
+ int dfd;
+ int fd;
+ int g_dfd;
+ const char *path;
+ struct scmi_tlm_base_info info;
+ struct scmi_tlm_config cfg;
+ struct scmi_tlm_intervals *ivs;
+ struct scmi_tlm_shmtis_list *ssl;
+ unsigned int num_des;
+ struct tlm_de *des;
+ unsigned int num_groups;
+ struct tlm_group *grps;
+};
+
+static inline void dump_state(struct tlm_state *st)
+{
+ uint32_t *uuid32 = st->info.de_impl_version;
+ uint16_t *uuid16 = (uint16_t *)&st->info.de_impl_version[1];
+ struct scmi_tlm_update_interval *intervals;
+ struct scmi_tlm_shmti_info *shinfo;
+
+ fprintf(stdout, "- SYSTEM TELEMETRY @instance: %s\n\n", st->path);
+ fprintf(stdout, "+ Version: 0x%08X\n", st->info.version);
+ fprintf(stdout, "+ DEs#: %d\n", st->info.num_des);
+ fprintf(stdout, "+ GRPS#: %d\n", st->info.num_groups);
+ fprintf(stdout, "+ INTRV#: %d\n", st->info.num_intervals);
+ fprintf(stdout, "+ SHMTIS#: %d\n", st->info.num_shmtis);
+
+ fprintf(stdout, "+ UUID: ");
+ fprintf(stdout, "%X-", uuid32[0]);
+ fprintf(stdout, "%X-", uuid16[0]);
+ fprintf(stdout, "%X-", uuid16[1]);
+ fprintf(stdout, "%X", uuid16[2]);
+ fprintf(stdout, "%X\n", uuid32[3]);
+
+ fprintf(stdout, "\n+ TLM_ENABLED: %d\n", st->cfg.enable);
+ fprintf(stdout, "+ CURRENT_UPDATE_INTERVAL: %u,%d\n",
+ st->cfg.active.secs, st->cfg.active.exp);
+
+ fprintf(stdout, "+ Found #%u Global Update Intervals\n",
+ st->ivs->num_intervals);
+ intervals = (struct scmi_tlm_update_interval *)st->ivs->intervals;
+ for (int i = 0; i < st->ivs->num_intervals; i++)
+ fprintf(stdout, "\t[%d]::%u,%d\n", i,
+ intervals[i].secs, intervals[i].exp);
+
+ fprintf(stdout, "+ Found #%u SHMTI areas\n", st->ssl->num_shmtis);
+ shinfo = (struct scmi_tlm_shmti_info *)st->ssl->shmtis;
+ for (int i = 0; i < st->ssl->num_shmtis; i++)
+ fprintf(stdout, "\t[%d]:: FD:%u LEN:%u\n",
+ shinfo[i].sid, shinfo[i].fd, shinfo[i].len);
+
+ if (st->info.num_des != st->num_des) {
+ fprintf(stdout, "\n++++++ DES NOT FULLY_ENUMERATED ++++++\n");
+ fprintf(stdout, "+++ DECLARED:%u ENUMERATED:%u +++\n",
+ st->info.num_des, st->num_des);
+ }
+
+ fprintf(stdout, "\n+ Found #%d DEs:\n", st->num_des);
+ for (int i = 0; i < st->num_des; i++) {
+ fprintf(stdout, "\t[0x%08X][%s]\n",
+ st->des[i].info->id, st->des[i].info->name);
+ fprintf(stdout, "\t\tGRP_ID:0x%08X SZ:%u TYPE:%u UNIT:%u UNIT_EXP:%d TS_RATE:%d\n",
+ st->des[i].info->grp_id, st->des[i].info->data_sz,
+ st->des[i].info->type, st->des[i].info->unit,
+ st->des[i].info->unit_exp, st->des[i].info->ts_rate);
+ fprintf(stdout, "\t\tINST_ID:%u COMPO_ID:%u COMPO_TYPE:%u PERSIST:%u\n\n",
+ st->des[i].info->instance_id, st->des[i].info->compo_instance_id,
+ st->des[i].info->compo_type, st->des[i].info->persistent);
+ }
+ fprintf(stdout, "\n");
+
+ for (int i = 0; i < st->num_des; i++) {
+ fprintf(stdout, "\t[0x%08X][%s]\t\t%s %s\n\t\t-->> TS:%16llu %016llX\n",
+ st->des[i].info->id,st->des[i].info->name,
+ st->des[i].cfg.enable ? "ON" : "OFF",
+ st->des[i].cfg.t_enable ? "/TS_ON " : "/TS_OFF",
+ st->des[i].sample.tstamp, st->des[i].sample.val);
+ }
+ fprintf(stdout, "\n");
+
+ fprintf(stdout, "+ Found %d GRPs: ", st->num_groups);
+ for (int i = 0; i < st->num_groups; i++) {
+ struct scmi_tlm_update_interval *grp_intrvs;
+ struct tlm_group *grp = &st->grps[i];
+ uint32_t *composing_des;
+
+ fprintf(stdout, "\n\tGRP_ID:%d DES#:%d INTRVS#:%d\n",
+ grp->info->grp_id, grp->info->num_des,
+ grp->info->num_intervals);
+
+ fprintf(stdout, "\tGRP Intervals:\n");
+ grp_intrvs = (struct scmi_tlm_update_interval *)grp->ivs->intervals;
+ for (int j = 0; j < grp->ivs->num_intervals; j++)
+ fprintf(stdout, "\t[%d]::%u,%d\n", j,
+ grp_intrvs[j].secs, grp_intrvs[j].exp);
+
+ composing_des = (uint32_t *)grp->desc->composing_des;
+ fprintf(stdout, "\tCOMPOSING_DES:");
+ for (int j = 0; j < grp->desc->num_des; j++)
+ fprintf(stdout, "0x%08X ", composing_des[j]);
+ fprintf(stdout, "\n");
+ }
+}
+
+static int discover_base_info(int fd, struct scmi_tlm_base_info *info)
+{
+ int ret;
+
+ ret = ioctl(fd, SCMI_TLM_GET_INFO, info);
+ if (ret) {
+ perror(IOCTL_ERR_STR(SCMI_TLM_GET_INFO));
+ return ret;
+ }
+
+ return ret;
+}
+
+static struct scmi_tlm_des_list *scmi_get_des_list(int fd, int num_des)
+{
+ struct scmi_tlm_des_list *dsl;
+ struct scmi_tlm_de_info *dei;
+ size_t sz, d_sz;
+ int ret;
+
+ sz = sizeof(*dsl);
+ dsl = malloc(sz);
+ if (!dsl)
+ return NULL;
+
+ bzero(dsl, sz);
+
+ d_sz = sizeof(*dei) * num_des;
+ dei = malloc(d_sz);
+ if (!dei) {
+ free(dsl);
+ return NULL;
+ }
+
+ bzero(dei, d_sz);
+
+ dsl->num_des = num_des;
+ dsl->des = (uint64_t)dei;
+ ret = ioctl(fd, SCMI_TLM_GET_DE_LIST, dsl);
+ if (ret) {
+ perror(IOCTL_ERR_STR(SCMI_TLM_GET_DE_LIST));
+ return NULL;
+ }
+
+ return dsl;
+}
+
+static struct tlm_de *enumerate_des(struct tlm_state *st)
+{
+ struct scmi_tlm_des_list *dsl;
+ struct tlm_de *des;
+
+ dsl = scmi_get_des_list(st->fd, st->info.num_des);
+ if (!dsl)
+ return NULL;
+
+ st->num_des = dsl->num_des;
+ des = malloc(sizeof(*des) * st->num_des);
+ if (!des)
+ return NULL;
+
+ bzero(des, sizeof(*des) * st->num_des);
+ for (int i = 0; i < st->num_des; i++) {
+ struct tlm_de *de = &des[i];
+ int ret;
+
+ de->info = &(((struct scmi_tlm_de_info *)dsl->des)[i]);
+ de->cfg.id = de->info->id;
+ ret = ioctl(st->fd, SCMI_TLM_GET_DE_CFG, &de->cfg);
+ if (ret) {
+ perror(IOCTL_ERR_STR(SCMI_TLM_GET_DE_CFG));
+ continue;
+ }
+
+ if (!de->cfg.enable)
+ continue;
+
+ /* Collect initial sample */
+ de->sample.id = de->info->id;
+ ret = ioctl(st->fd, SCMI_TLM_GET_DE_VALUE, &de->sample);
+ if (ret) {
+ perror(IOCTL_ERR_STR(SCMI_TLM_GET_DE_VALUE));
+ continue;
+ }
+ }
+
+ return des;
+}
+
+static int get_current_config(int fd, struct scmi_tlm_config *cfg)
+{
+ int ret;
+
+ ret = ioctl(fd, SCMI_TLM_GET_CFG, cfg);
+ if (ret) {
+ perror(IOCTL_ERR_STR(SCMI_TLM_GET_CFG));
+ return ret;
+ }
+
+ return ret;
+}
+
+static struct scmi_tlm_grps_list *scmi_get_grps_list(int fd, int num_groups)
+{
+ struct scmi_tlm_grps_list *gsl;
+ struct scmi_tlm_grp_info *grps;
+ size_t sz, grps_sz;
+ int ret;
+
+ sz = sizeof(*gsl);
+ gsl = malloc(sz);
+ if (!gsl)
+ return NULL;
+
+ bzero(gsl, sz);
+ grps_sz = sizeof(*grps) * num_groups;
+ grps = malloc(grps_sz);
+ if (!grps) {
+ free(gsl);
+ return NULL;
+ }
+
+ gsl->num_grps = num_groups;
+ gsl->grps = (uint64_t)grps;
+ ret = ioctl(fd, SCMI_TLM_GET_GRP_LIST, gsl);
+ if (ret) {
+ perror(IOCTL_ERR_STR(SCMI_TLM_GET_GRP_LIST));
+ free(grps);
+ free(gsl);
+ return NULL;
+ }
+
+ return gsl;
+}
+
+static struct scmi_tlm_intervals *
+enumerate_intervals(int fd, int num_intervals, unsigned *grp_id)
+{
+ struct scmi_tlm_update_interval *intervs;
+ struct scmi_tlm_intervals *ivs;
+ size_t sz, i_sz;
+ int ret;
+
+ sz = sizeof(*ivs);
+ ivs = malloc(sz);
+ if (!ivs)
+ return NULL;
+
+ bzero(ivs, sz);
+
+ i_sz = sizeof(*intervs) * num_intervals;
+ intervs = malloc(i_sz);
+ if (!intervs) {
+ free(ivs);
+ return NULL;
+ }
+
+ bzero(intervs, i_sz);
+
+ ivs->num_intervals = num_intervals;
+ ivs->intervals = (uint64_t)intervs;
+ if (grp_id) {
+ ivs->grp_id = *grp_id;
+ ivs->flags = SCMI_TLM_INTERV_GROUP;
+ }
+
+ ret = ioctl(fd, SCMI_TLM_GET_INTRVS, ivs);
+ if (ret) {
+ perror(IOCTL_ERR_STR(SCMI_TLM_GET_INTRVS));
+ free(ivs);
+ free(intervs);
+ return NULL;
+ }
+
+ return ivs;
+}
+
+static struct scmi_tlm_shmtis_list *enumerate_shmtis(struct tlm_state *st)
+{
+ struct scmi_tlm_shmti_info *shmtis;
+ struct scmi_tlm_shmtis_list *ssl;
+ size_t sz, shmtis_sz;
+ int ret;
+
+ sz = sizeof(*ssl);
+ ssl = malloc(sz);
+ if (!ssl)
+ return NULL;
+
+ bzero(ssl, sz);
+ shmtis_sz = sizeof(*shmtis) * st->info.num_shmtis;
+ shmtis = malloc(shmtis_sz);
+ if (!shmtis) {
+ free(ssl);
+ return NULL;
+ }
+
+ ssl->num_shmtis = st->info.num_shmtis;
+ ssl->shmtis = (uint64_t)shmtis;
+ ret = ioctl(st->fd, SCMI_TLM_GET_SHMTI_LIST, ssl);
+ if (ret) {
+ perror(IOCTL_ERR_STR(SCMI_TLM_GET_SHMTI_LIST));
+ free(shmtis);
+ free(ssl);
+ return NULL;
+ }
+
+ return ssl;
+}
+
+static struct tlm_group *enumerate_groups(struct tlm_state *st)
+{
+ struct scmi_tlm_grps_list *gsl;
+ struct tlm_group *grps;
+
+ gsl = scmi_get_grps_list(st->fd, st->info.num_groups);
+ if (!gsl)
+ return NULL;
+
+ st->num_groups = gsl->num_grps;
+ grps = malloc(sizeof(*grps) * st->num_groups);
+ if (!grps)
+ return NULL;
+
+ bzero(grps, sizeof(*grps) * st->num_groups);
+ for (int i = 0; i < st->num_groups; i++) {
+ struct tlm_group *grp = &grps[i];
+ size_t size, composing_sz;
+ uint32_t *composing_des;
+ int ret;
+
+ grp->info = &(((struct scmi_tlm_grp_info *)gsl->grps)[i]);
+ size = sizeof(*grp->desc);
+ grp->desc = malloc(size);
+ if (!grp->desc)
+ return NULL;
+
+ bzero(grp->desc, size);
+ composing_sz = sizeof(uint32_t) * grp->info->num_des;
+ composing_des = malloc(composing_sz);
+ if (!composing_des) //XXX BAD Cleanup
+ return NULL;
+
+ grp->desc->grp_id = grp->info->grp_id;
+ grp->desc->num_des = grp->info->num_des;
+ grp->desc->composing_des = (uint64_t)composing_des;
+ ret = ioctl(st->fd, SCMI_TLM_GET_GRP_DESC, grp->desc);
+ if (ret) {
+ perror(IOCTL_ERR_STR(SCMI_TLM_GET_GRP_DESC));
+ continue;
+ }
+
+ grp->ivs = enumerate_intervals(st->fd, grp->info->num_intervals,
+ &grp->info->grp_id);
+ }
+
+ return grps;
+}
+
+static int get_tlm_state(const char *path, struct tlm_state *st)
+{
+ int ret;
+
+ st->dfd = open(path, O_RDONLY);
+ if (st->dfd < 0) {
+ perror("open");
+ return st->dfd;
+ }
+
+ st->fd = openat(st->dfd, "tlm_0", O_RDWR);
+ if (st->fd < 0) {
+ perror("openat");
+ return st->fd;
+ }
+
+ ret = discover_base_info(st->fd, &st->info);
+ if (ret)
+ return ret;
+
+ st->ivs = enumerate_intervals(st->fd, st->info.num_intervals, NULL);
+ if (!st->ivs)
+ return -1;
+
+ ret = get_current_config(st->fd, &st->cfg);
+ if (ret)
+ return ret;
+
+ if (st->info.num_des)
+ st->des = enumerate_des(st);
+
+ if (st->info.num_groups)
+ st->grps = enumerate_groups(st);
+
+ if (st->info.num_shmtis)
+ st->ssl = enumerate_shmtis(st);
+
+ st->path = path;
+
+ return 0;
+}
+
+#define MAX_GENERATIONS 5
+
+static void get_tlm_generation(struct tlm_state *st)
+{
+ int fd, i = 0;
+ struct pollfd pfds[1];
+
+ fd = openat(st->dfd, "generation", O_RDONLY);
+ if (fd < 0) {
+ perror("openat");
+ return ;
+ }
+
+ pfds[0].fd = fd;
+ pfds[0].events = POLLIN;
+
+ do {
+ int ret;
+
+ pfds[0].revents = 0;
+ ret = poll(pfds, 1, -1);
+ if (ret < 0 ) {
+ perror("poll generation");
+ break;;
+ }
+
+ if (!pfds[0].revents)
+ continue;
+
+ if (pfds[0].revents & POLLIN) {
+ int n;
+ char buf[32] = {};
+
+ n = read(fd, buf, 32);
+ if (n < 0) {
+ perror("read generation");
+ break;
+ }
+
+ fprintf(stdout, "Generation[%u]: %s\n", i, buf);
+ }
+ } while (i++ < MAX_GENERATIONS);
+
+ close(fd);
+}
+
+static int dump_shmti(struct scmi_tlm_shmti_info *shmti)
+{
+ void *tdcf;
+ ssize_t bytes = 0;
+
+ tdcf = mmap(NULL, shmti->len, PROT_READ, MAP_SHARED, shmti->fd, 0);
+ if (tdcf == MAP_FAILED) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ return -1;
+ }
+
+ tdcf += shmti->offset;
+ do {
+ bytes += write(1, tdcf + bytes, shmti->len - bytes);
+ if (bytes < 0)
+ return -1;
+ } while (bytes < shmti->len);
+
+ return bytes;
+}
+
+int main(int argc, char **argv)
+{
+ const char *tlm_root_instance = "/dev/scmi/";
+ struct scmi_tlm_data_read *bulk;
+ struct scmi_tlm_de_config de_cfg = {};
+ struct scmi_tlm_de_sample *samples;
+ struct scmi_tlm_shmti_info *shmtis;
+ size_t bulk_sz, samples_sz;
+ struct tlm_state st = {};
+ int ret;
+
+ ret = get_tlm_state(tlm_root_instance, &st);
+ if (ret)
+ return ret;
+
+ if (argc > 1) {
+ struct scmi_tlm_shmti_info *shmti;
+ int ret, sid = atoi(argv[1]);
+
+ if (sid >= st.ssl->num_shmtis)
+ return -1;
+
+ shmti = &(((struct scmi_tlm_shmti_info *)(st.ssl->shmtis))[sid]);
+
+ fprintf(stderr, "Binary DUMP of SHMTI[%u] LEN:%u\n",
+ shmti->sid, shmti->len);
+
+ ret = dump_shmti(shmti);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+ }
+
+ dump_state(&st);
+
+ //get_tlm_generation(&st);
+ bulk_sz = sizeof(*bulk);
+ bulk = malloc(bulk_sz);
+ if (!bulk)
+ return -1;
+
+ bzero(bulk, bulk_sz);
+ samples_sz = st.info.num_des * sizeof(*samples);
+ samples = malloc(samples_sz);
+ if (!samples)
+ return -1;
+
+ bzero(samples, samples_sz);
+ bulk->samples = (uint64_t)samples;
+ bulk->num_samples = st.info.num_des;
+ ret = ioctl(st.fd, SCMI_TLM_SINGLE_SAMPLE, bulk);
+ if (ret) {
+ perror(IOCTL_ERR_STR(SCMI_TLM_SINGLE_SAMPLE));
+ return -1;
+ }
+
+ fprintf(stdout, "\n- Single ASYNC read -\n-------------------\n");
+ for (int i = 0; i < bulk->num_samples; i++)
+ fprintf(stdout, "0x%08X %016llu %016llX\n",
+ samples[i].id, samples[i].tstamp, samples[i].val);
+
+ fprintf(stdout, "\n--- Enabling ALL DEs with timestamp...\n");
+ de_cfg.enable = 1;
+ de_cfg.t_enable = 1;
+ ret = ioctl(st.fd, SCMI_TLM_SET_ALL_CFG, &de_cfg);
+ if (ret) {
+ perror(IOCTL_ERR_STR(SCMI_TLM_SET_ALL_CFG));
+ return ret;
+ }
+
+ bzero(samples, samples_sz);
+ bulk->num_samples = st.info.num_des;
+ ret = ioctl(st.fd, SCMI_TLM_BULK_READ, bulk);
+ if (ret) {
+ perror(IOCTL_ERR_STR(SCMI_TLM_BULK_READ));
+ return -1;
+ }
+
+ fprintf(stdout, "\n- BULK read -\n-------------------\n");
+ for (int i = 0; i < bulk->num_samples; i++)
+ fprintf(stdout, "0x%08X %016llu %016llX\n",
+ samples[i].id, samples[i].tstamp, samples[i].val);
+
+ bzero(samples, samples_sz);
+ bulk->num_samples = 5;
+ samples[0].id = 0x1010;
+ samples[1].id = 0xA001;
+ samples[2].id = 0xA005;
+ samples[3].id = 0x2020;
+ samples[4].id = 0x0003;
+ ret = ioctl(st.fd, SCMI_TLM_BATCH_READ, bulk);
+ if (ret) {
+ perror(IOCTL_ERR_STR(SCMI_TLM_BATCH_READ));
+ return -1;
+ }
+
+ fprintf(stdout, "\n- BATCH read -\n-------------------\n");
+ for (int i = 0; i < bulk->num_samples; i++)
+ fprintf(stdout, "0x%08X %016llu %016llX\n",
+ samples[i].id, samples[i].tstamp, samples[i].val);
+
+ fprintf(stdout, "\n- SHMTI mmap dump -\n-------------------\n");
+ shmtis = (struct scmi_tlm_shmti_info *)st.ssl->shmtis;
+ for (int i = 0; i < st.ssl->num_shmtis; i++) {
+ unsigned long *start, *end;
+ void *tdcf;
+ char *buf;
+
+ fprintf(stdout, "Mapping SHMTI[%u] - SID:%u LEN:%u FD:%u OFFS:%u\n",
+ i, shmtis[i].sid, shmtis[i].len, shmtis[i].fd, shmtis[i].offset);
+
+ tdcf = mmap(NULL, shmtis[i].len, PROT_READ, MAP_SHARED,
+ shmtis[i].fd, 0);
+ if (tdcf == MAP_FAILED) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ return -1;
+ }
+
+ start = tdcf + shmtis[i].offset;
+ end = tdcf + shmtis[i].offset + shmtis[i].len;
+
+ fprintf(stdout, "-----------------------------------------------------\n");
+ fprintf(stdout, "Mapped SHMTI - SID:%u %u@%p\n",
+ shmtis[i].sid, shmtis[i].offset, tdcf);
+
+ buf = (char *)start;
+ for (int j = 0; j < 4; j++)
+ fprintf(stdout, "%c ", buf[j]);
+
+ fprintf(stdout, " ===> ");
+
+ buf = ((char *)end) - 4;
+ for (int j = 0 ; j < 4; j++)
+ fprintf(stdout, "%c ", buf[j]);
+ fprintf(stdout, "\n-----------------------------------------------------\n");
+ }
+
+ return 0;
+}
--
2.54.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* Re: [PATCH v5 00/23] Introduce SCMI Telemetry support
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
` (22 preceding siblings ...)
2026-07-03 12:36 ` [PATCH v5 23/23] [RFC] tools/scmi: Add SCMI Telemetry testing tool Cristian Marussi
@ 2026-07-07 6:31 ` Subrahmanya Lingappa
2026-07-08 20:05 ` David Hildenbrand (Arm)
2026-07-10 8:32 ` Cristian Marussi
23 siblings, 2 replies; 27+ messages in thread
From: Subrahmanya Lingappa @ 2026-07-07 6:31 UTC (permalink / raw)
To: Cristian Marussi
Cc: arm-scmi, linux-arm-kernel, linux-kernel, linux-doc, sudeep.holla,
james.quinlan, f.fainelli, vincent.guittot, etienne.carriere,
peng.fan, michal.simek, d-gole, jic23, elif.topuz, lukasz.luba,
philip.radford, brauner, david, souvik.chakravarty, leitao, kas,
puranjay, usama.arif, kernel-team
Hi Cristian,
Thanks for posting this. I think the direction of not forcing these
Telemetry Data Events into hwmon/IIO/perf prematurely is the right one.
The cover letter explains the core problem well: most DE IDs are
platform-defined, and without external metadata the kernel cannot reliably
know whether a DE is a thermal sensor, a performance counter, a power value,
or something entirely platform-specific.
That said, I think the current layering is still too SCMI-shaped for the
ABI it is trying to introduce.
The thing being exposed here is not only "SCMI Telemetry"; it is a more
general kernel concept of telemetry:
- enumerate provider-specific data/event descriptors
- expose capabilities, units, data format and topology/grouping metadata
- configure collection rate and collection mode where supported
- collect samples with timestamp/freshness/validity information
- track generation/state changes
- optionally expose provider-private raw backing storage for debugging
SCMI is one provider of that model. It happens to transport the data through
SCMI v4.0 concepts such as DEs, groups, SHMTIs and TDCF. But another firmware
or control-plane specification could show up later with the same broad
telemetry model and a different wire format. Call it XXMI or YYMI: the
protocol mechanics would differ, but the kernel consumers should not have to
learn a new in-kernel and userspace ABI for each one.
The common ABI should therefore avoid treating the DE numeric namespace or
the wire/storage format as the abstraction. The useful common contract is
closer to:
- what is being measured: component/type/instance/name;
- how to interpret it: unit, exponent/rate unit, data width and signedness;
- how it behaves: instantaneous, accumulating, average, state/counter, etc.;
- how it is controlled: individual event vs group-wide collection;
- how fresh it is: timestamp, sequence/generation and validity/error flags.
So I would strongly consider inserting a generic telemetry layer above SCMI,
with SCMI registering as a telemetry provider rather than owning the stable
userspace ABI directly.
Roughly:
drivers/telemetry/
provider registration
descriptor/sample/config abstractions
generation tracking and poll support
common userspace ABI
drivers/firmware/arm_scmi/telemetry.c
SCMI protocol implementation
TDCF/SHMTI/notification parsing
translation into generic telemetry descriptors/samples
optional provider-private raw/debug path
SCMI SHMTI mmap or dumps, if kept, behind explicit privilege/debug
policy rather than as the baseline telemetry ABI
With that shape, future in-kernel consumers could bind to generic telemetry
objects and capabilities instead of SCMI internals. Architected/well-known
DEs could later grow adapters into hwmon/IIO/perf where the semantics are
clear, while platform-specific DEs remain available through the generic
telemetry interface plus userspace metadata.
This would also help avoid making the SCMI protocol's internal resource
storage part of the ABI. Right now the public protocol header exposes
structures such as scmi_telemetry_res_info, scmi_telemetry_group and
scmi_telemetry_de, and the chardev indexes those objects directly. That feels
too tightly coupled for something that may need more consumers later. I would
prefer copy-out or iterator-style provider ops instead, for example:
info_get()
de_count_get()
de_info_get()
group_info_get()
intervals_get()
collection_set()
collection_get()
sample_read()
reset()
where the backing arrays, xarrays, lazy enumeration, SHMTI mappings and TDCF
parsing remain private to the SCMI provider.
The sample side probably needs an explicit common result contract too. A
sample is not only an integer value: it may have optional timestamping,
provider data width, stale/not-yet-valid state, partial collection failures,
hardware-fault indications, and a sequence/generation value that lets
userspace detect races against reconfiguration or shared-memory updates. Those
details can still be backed by SCMI-specific status codes and TDCF parsing
internally, but I think the stable ABI should expose them in provider-neutral
terms.
I would also be careful about freezing the raw SHMTI mmap part as a baseline
ABI. It is useful for bring-up and high-performance tooling, but it exposes
firmware-owned shared memory layout and TDCF parsing rules directly to
userspace. If this stays, I think it should be clearly separated as a
privileged/debug/raw provider facility, with a precise mmap contract:
- required mmap length
- vm_pgoff semantics
- page alignment
- VMA flags
- cache/coherency expectations
- lifetime across reset/remove/reprobe
- required capability, if any
Even outside the raw mmap case, telemetry can be a sensitive high-rate view
of system behavior. The ABI should make access policy explicit: who can
enumerate, who can read, who can change collection state, and whether
production systems can restrict sampling granularity or intervals
independently of what firmware advertises.
For the stable telemetry ABI, I would start smaller: descriptor enumeration,
sample reads with validity metadata, optional group-aware configuration, and a
generation counter/poll mechanism so userspace can detect reconfiguration or
partial re-enumeration races.
One other point related to this layering: configuration appears to be global
to the SCMI instance. Any process opening /dev/scmi/tlm_N can change
collection mode, sampling interval, DE enablement and timestamp state for
other users. That may be OK, but it should be an explicit policy decision.
A generic telemetry layer could make the model clearer: multi-reader with a
privileged/single writer, or fully shared global controls, with instance-level
locking and generation updates after each visible state change.
So my high-level suggestion for v6 would be:
1. define the generic telemetry provider/consumer model first;
2. make SCMI Telemetry one provider of that model;
3. keep SCMI/TDCF/SHMTI details below the provider boundary;
4. keep raw SHMTI access separate from the stable ABI;
5. define common sample validity/freshness/error semantics;
6. add generation/poll semantics to the common ABI;
7. make lifetime, access policy and configuration concurrency explicit.
I do not think this invalidates the protocol work in the series. Most of the
SCMI parsing and resource discovery can still be the provider implementation.
The main question is where the stable kernel/userspace contract should sit.
My preference would be that it sits at "telemetry", not at "SCMI Telemetry",
so that SCMI, and any future XXMI/YYMI-style provider, can share one consumer
interface.
Thanks,
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v5 00/23] Introduce SCMI Telemetry support
2026-07-07 6:31 ` [PATCH v5 00/23] Introduce SCMI Telemetry support Subrahmanya Lingappa
@ 2026-07-08 20:05 ` David Hildenbrand (Arm)
2026-07-10 8:32 ` Cristian Marussi
1 sibling, 0 replies; 27+ messages in thread
From: David Hildenbrand (Arm) @ 2026-07-08 20:05 UTC (permalink / raw)
To: Subrahmanya Lingappa, Cristian Marussi
Cc: arm-scmi, linux-arm-kernel, linux-kernel, linux-doc, sudeep.holla,
james.quinlan, f.fainelli, vincent.guittot, etienne.carriere,
peng.fan, michal.simek, d-gole, jic23, elif.topuz, lukasz.luba,
philip.radford, brauner, souvik.chakravarty, leitao, kas,
puranjay, usama.arif, kernel-team
On 7/7/26 08:31, Subrahmanya Lingappa wrote:
> Hi Cristian,
Hi!
Thanks for having a look. Christian is out this week, so let me ask some
clarifying questions and raise some points.
>
> Thanks for posting this. I think the direction of not forcing these
> Telemetry Data Events into hwmon/IIO/perf prematurely is the right one.
Good.
> The cover letter explains the core problem well: most DE IDs are
> platform-defined, and without external metadata the kernel cannot reliably
> know whether a DE is a thermal sensor, a performance counter, a power value,
> or something entirely platform-specific.
Right.
>
> That said, I think the current layering is still too SCMI-shaped for the
> ABI it is trying to introduce.
Just to be clear: the data is exposed through /dev/scmi/ device nodes. It is
expected to be very, very SCMI specific as it stands :)
>
> The thing being exposed here is not only "SCMI Telemetry"; it is a more
> general kernel concept of telemetry:
>
> - enumerate provider-specific data/event descriptors
> - expose capabilities, units, data format and topology/grouping metadata
> - configure collection rate and collection mode where supported
> - collect samples with timestamp/freshness/validity information
> - track generation/state changes
> - optionally expose provider-private raw backing storage for debugging
Yes, there are some generic concepts there. However, when we talk about
generalizing, I suspect that other telemetry implementations might vary quite
already even on these generic points?
In particular when it comes about enumeration (as discussed previously, some
telemetry solutions might expose way more detailed information about events) and
configurations (and, in particular, things like configuration groups).
But also efficient ways to receive data in user space.
I mean, sure, if someone uses the exact same concept but instead of calling it
SCMI, calls it XXMI, that's one thing.
But a true generic telemetry concept would have to take way more options into
account.
So defining something actual generic and extensible might be rather challenging,
no? And for that, I would expect a pretty good reason to do so.
"Make tool X easier to implement" would not be a good reason in my option. I'm
sure you have something in mind, what would that be?
>
> SCMI is one provider of that model. It happens to transport the data through
> SCMI v4.0 concepts such as DEs, groups, SHMTIs and TDCF.
Right.
> But another firmware
> or control-plane specification could show up later with the same broad
> telemetry model and a different wire format. Call it XXMI or YYMI: the
"could" is a bit vague. Do you have any concrete examples in mind? It will be
rather challenging to figure out what's generic without other real examples.
> protocol mechanics would differ, but the kernel consumers should not have to
> learn a new in-kernel and userspace ABI for each one.
So far, the tools that consume this data are expected to be very platform specific.
IOW, not your ordinary perf/top/whatever :)
Assuming there is some XXMI telemetry in the future, I would expect tools that
could operate across multiple platforms to build upon some generic user space
library that abstracts these things and knows how to talk to the different devices.
>
> The common ABI should therefore avoid treating the DE numeric namespace or
> the wire/storage format as the abstraction. The useful common contract is
> closer to:
>
> - what is being measured: component/type/instance/name;
> - how to interpret it: unit, exponent/rate unit, data width and signedness;
> - how it behaves: instantaneous, accumulating, average, state/counter, etc.;
> - how it is controlled: individual event vs group-wide collection;
> - how fresh it is: timestamp, sequence/generation and validity/error flags.
>
> So I would strongly consider inserting a generic telemetry layer above SCMI,
> with SCMI registering as a telemetry provider rather than owning the stable
> userspace ABI directly.
Again, I think that's rather hard. And it's not immediately clear to me why this
abstraction must be had in the kernel, instead of in user space?
So it would be good if you could elaborate on that.
>
> Roughly:
>
> drivers/telemetry/
> provider registration
> descriptor/sample/config abstractions
> generation tracking and poll support
> common userspace ABI
That will be a struggle to define in a future-proof way I'm afraid.
>
> drivers/firmware/arm_scmi/telemetry.c
> SCMI protocol implementation
> TDCF/SHMTI/notification parsing
> translation into generic telemetry descriptors/samples
>
> optional provider-private raw/debug path
> SCMI SHMTI mmap or dumps, if kept, behind explicit privilege/debug
> policy rather than as the baseline telemetry ABI
That makes the SCMI SHMTI mmap interface a second-class citizen, which IIUC is
rather bad performance wise.
>
> With that shape, future in-kernel consumers could bind to generic telemetry
> objects and capabilities instead of SCMI internals. Architected/well-known
> DEs could later grow adapters into hwmon/IIO/perf where the semantics are
> clear, while platform-specific DEs remain available through the generic
> telemetry interface plus userspace metadata.
>
> This would also help avoid making the SCMI protocol's internal resource
> storage part of the ABI. Right now the public protocol header exposes
> structures such as scmi_telemetry_res_info, scmi_telemetry_group and
> scmi_telemetry_de, and the chardev indexes those objects directly. That feels
> too tightly coupled for something that may need more consumers later. I would
> prefer copy-out or iterator-style provider ops instead, for example:
>
> info_get()
> de_count_get()
> de_info_get()
> group_info_get()
> intervals_get()
> collection_set()
> collection_get()
> sample_read()
> reset()
>
> where the backing arrays, xarrays, lazy enumeration, SHMTI mappings and TDCF
> parsing remain private to the SCMI provider.
>
> The sample side probably needs an explicit common result contract too. A
> sample is not only an integer value: it may have optional timestamping,
> provider data width, stale/not-yet-valid state, partial collection failures,
> hardware-fault indications, and a sequence/generation value that lets
> userspace detect races against reconfiguration or shared-memory updates. Those
> details can still be backed by SCMI-specific status codes and TDCF parsing
> internally, but I think the stable ABI should expose them in provider-neutral
> terms.
>
> I would also be careful about freezing the raw SHMTI mmap part as a baseline
> ABI. It is useful for bring-up and high-performance tooling, but it exposes
Correct me if I'm wrong: most user space tools we expect will fall into the
category of "high-performance tooling"?
Or which tools do you have in mind that have different requirements?
> firmware-owned shared memory layout and TDCF parsing rules directly to
> userspace. If this stays, I think it should be clearly separated as a
> privileged/debug/raw provider facility, with a precise mmap contract:
>
> - required mmap length
> - vm_pgoff semantics
> - page alignment
> - VMA flags
> - cache/coherency expectations
> - lifetime across reset/remove/reprobe
> - required capability, if any
>
> Even outside the raw mmap case, telemetry can be a sensitive high-rate view
> of system behavior. The ABI should make access policy explicit: who can
> enumerate, who can read, who can change collection state, and whether
> production systems can restrict sampling granularity or intervals
> independently of what firmware advertises.
>
> For the stable telemetry ABI, I would start smaller: descriptor enumeration,
> sample reads with validity metadata, optional group-aware configuration, and a
> generation counter/poll mechanism so userspace can detect reconfiguration or
> partial re-enumeration races.
>
> One other point related to this layering: configuration appears to be global
> to the SCMI instance. Any process opening /dev/scmi/tlm_N can change
> collection mode, sampling interval, DE enablement and timestamp state for
> other users. That may be OK, but it should be an explicit policy decision.
> A generic telemetry layer could make the model clearer: multi-reader with a
> privileged/single writer, or fully shared global controls, with instance-level
> locking and generation updates after each visible state change.
>
> So my high-level suggestion for v6 would be:
>
> 1. define the generic telemetry provider/consumer model first;
> 2. make SCMI Telemetry one provider of that model;
> 3. keep SCMI/TDCF/SHMTI details below the provider boundary;
> 4. keep raw SHMTI access separate from the stable ABI;
> 5. define common sample validity/freshness/error semantics;
> 6. add generation/poll semantics to the common ABI;
> 7. make lifetime, access policy and configuration concurrency explicit.
>
> I do not think this invalidates the protocol work in the series. Most of the
> SCMI parsing and resource discovery can still be the provider implementation.
> The main question is where the stable kernel/userspace contract should sit.
> My preference would be that it sits at "telemetry", not at "SCMI Telemetry",
> so that SCMI, and any future XXMI/YYMI-style provider, can share one consumer
> interface.
Again, I'd like to understand why you think this abstraction should be had in
the kernel, when it could also be had in user space. I'm sure there is a good
reason you have in mind.
In particular, I am curious about tools you would expect to run across platforms
that would even care about this.
Last but not least, which other telemetry implementations do you have in mind
that are on the horizon?
In particular, I'm afraid a generic abstraction in the kernel for a handful of
specialized user space tools might take a rather long time to flesh out.
Finally, I wonder if something generic is really required when talking about an
ioctl interface. I would completely agree if we would be talking about something
like resctl, that actually exposes the data to user space in an abstracted,
easy-to-use fashion -- not just an ioctl interface to talk to a device
Thanks for taking the time to write all this up. I'll have to leave all the SCMI
details to Christian!
--
Cheers,
David
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v5 00/23] Introduce SCMI Telemetry support
2026-07-07 6:31 ` [PATCH v5 00/23] Introduce SCMI Telemetry support Subrahmanya Lingappa
2026-07-08 20:05 ` David Hildenbrand (Arm)
@ 2026-07-10 8:32 ` Cristian Marussi
1 sibling, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-07-10 8:32 UTC (permalink / raw)
To: Subrahmanya Lingappa
Cc: Cristian Marussi, arm-scmi, linux-arm-kernel, linux-kernel,
linux-doc, sudeep.holla, james.quinlan, f.fainelli,
vincent.guittot, etienne.carriere, peng.fan, michal.simek, d-gole,
jic23, elif.topuz, lukasz.luba, philip.radford, brauner, david,
souvik.chakravarty, leitao, kas, puranjay, usama.arif,
kernel-team
On Tue, Jul 07, 2026 at 12:01:37PM +0530, Subrahmanya Lingappa wrote:
> Hi Cristian,
Hi,
I will add a few considerations here aside the main points highlighed
already by David in this thread.
>
> Thanks for posting this. I think the direction of not forcing these
> Telemetry Data Events into hwmon/IIO/perf prematurely is the right one.
> The cover letter explains the core problem well: most DE IDs are
> platform-defined, and without external metadata the kernel cannot reliably
> know whether a DE is a thermal sensor, a performance counter, a power value,
> or something entirely platform-specific.
>
> That said, I think the current layering is still too SCMI-shaped for the
> ABI it is trying to introduce.
>
well... "Introduce SCMI Telemetry" seems a pretty much telling $SUBJECT :P
> The thing being exposed here is not only "SCMI Telemetry"; it is a more
> general kernel concept of telemetry:
>
> - enumerate provider-specific data/event descriptors
> - expose capabilities, units, data format and topology/grouping metadata
> - configure collection rate and collection mode where supported
> - collect samples with timestamp/freshness/validity information
> - track generation/state changes
> - optionally expose provider-private raw backing storage for debugging
>
> SCMI is one provider of that model. It happens to transport the data through
> SCMI v4.0 concepts such as DEs, groups, SHMTIs and TDCF. But another firmware
> or control-plane specification could show up later with the same broad
> telemetry model and a different wire format. Call it XXMI or YYMI: the
> protocol mechanics would differ, but the kernel consumers should not have to
> learn a new in-kernel and userspace ABI for each one.
>
So, in the early episodes of this Telemetry 'saga' when it still was an
RFC [1] in which I was exploring/experimenting/evaluating different approaches
I DID mention that one possible evolutions of whatever interface would have been
used (SysFS vs chardev vs FileSystem) could have been:
"D. generalize enough one of the above choices to make it abstract enough
that other non-SCMI based telemetry can plug into some sort of geenric
Telemetry subsystem"
...BUT the reality check that immediately stopped this possibility onto
his tracks for me was the fact that at that time, like now, there was/is
ONLY the SCMI protocol acting as a possible Telemetry provider so that would
have meant:
1. limited real use case examples to use to build a common Telemetry abstraction
resulting most probably in a 'generic' common framework heavily
biased toward SCMI mechanisms...for the simple fact that only SCMI really
existed that provided such broad range of generic telemetry data....
2. sure rejection from upstream, having created a supposedly generic common
framework, sadly used by....just ONLY one user
These concerns came on top of the basic main fact that, as David pointed out,
a fully generic and common abstraction of Telemetry concepts is hard...
...it definitely seems easy at first by looking at some basic simple
common ideas, but it quickly becomes very challenging to represent in a
common way very speficic bits like, as an example, topology, grouping
etc etc ...and again especially when you have ONLY one user of this new
abstraction to look at for inspiration ...
> The common ABI should therefore avoid treating the DE numeric namespace or
> the wire/storage format as the abstraction. The useful common contract is
> closer to:
>
> - what is being measured: component/type/instance/name;
> - how to interpret it: unit, exponent/rate unit, data width and signedness;
> - how it behaves: instantaneous, accumulating, average, state/counter, etc.;
> - how it is controlled: individual event vs group-wide collection;
> - how fresh it is: timestamp, sequence/generation and validity/error flags.
>
> So I would strongly consider inserting a generic telemetry layer above SCMI,
> with SCMI registering as a telemetry provider rather than owning the stable
> userspace ABI directly.
>
> Roughly:
>
> drivers/telemetry/
> provider registration
> descriptor/sample/config abstractions
> generation tracking and poll support
> common userspace ABI
>
> drivers/firmware/arm_scmi/telemetry.c
> SCMI protocol implementation
> TDCF/SHMTI/notification parsing
> translation into generic telemetry descriptors/samples
>
> optional provider-private raw/debug path
> SCMI SHMTI mmap or dumps, if kept, behind explicit privilege/debug
> policy rather than as the baseline telemetry ABI
>
> With that shape, future in-kernel consumers could bind to generic telemetry
> objects and capabilities instead of SCMI internals. Architected/well-known
> DEs could later grow adapters into hwmon/IIO/perf where the semantics are
> clear, while platform-specific DEs remain available through the generic
> telemetry interface plus userspace metadata.
>
> This would also help avoid making the SCMI protocol's internal resource
> storage part of the ABI. Right now the public protocol header exposes
> structures such as scmi_telemetry_res_info, scmi_telemetry_group and
Well here you are mentioning the SCMI internal API as exposed by the SCMI
core in include/linux/scmi_protocol.h and used by SCMI drivers to build
on top of the common protocol layer...of course it is SCMI specific,
always will be, even when/if a common Telemetry framework would exist.
> scmi_telemetry_de, and the chardev indexes those objects directly. That feels
> too tightly coupled for something that may need more consumers later. I would
> prefer copy-out or iterator-style provider ops instead, for example:
>
> info_get()
> de_count_get()
> de_info_get()
> group_info_get()
> intervals_get()
> collection_set()
> collection_get()
> sample_read()
> reset()
>
> where the backing arrays, xarrays, lazy enumeration, SHMTI mappings and TDCF
> parsing remain private to the SCMI provider.
I could agree on this...IF any of this would have a chance to exist now
with the current existing Telemetry providers...thing that seems not
plausible for the reasons above....
>
> The sample side probably needs an explicit common result contract too. A
> sample is not only an integer value: it may have optional timestamping,
> provider data width, stale/not-yet-valid state, partial collection failures,
> hardware-fault indications, and a sequence/generation value that lets
> userspace detect races against reconfiguration or shared-memory updates. Those
> details can still be backed by SCMI-specific status codes and TDCF parsing
> internally, but I think the stable ABI should expose them in provider-neutral
> terms.
Yes some of these are theoretically possible, but NOT really in the only
example we have so far that is SCMI....
>
> I would also be careful about freezing the raw SHMTI mmap part as a baseline
> ABI. It is useful for bring-up and high-performance tooling, but it exposes
> firmware-owned shared memory layout and TDCF parsing rules directly to
> userspace. If this stays, I think it should be clearly separated as a
> privileged/debug/raw provider facility, with a precise mmap contract:
>
> - required mmap length
> - vm_pgoff semantics
> - page alignment
> - VMA flags
> - cache/coherency expectations
> - lifetime across reset/remove/reprobe
> - required capability, if any
I would also prefer to expose only kernel mediated Telemetry data to
avoid fragmentation (even within the same protocol when implemented by
different vendors...), BUT RAW access I am afraid is inevitable to be
able to write high-perf tools, and those will have necessarily to be
protocol/backend specific...
...which leads straight to the other point raised by David...
...Is really a common Telemetry UAPI needed ?
...if anyway multiple protocols would dictate multiple custom tools
probably not...
> Even outside the raw mmap case, telemetry can be a sensitive high-rate view
> of system behavior. The ABI should make access policy explicit: who can
> enumerate, who can read, who can change collection state, and whether
> production systems can restrict sampling granularity or intervals
> independently of what firmware advertises.
>
Yes this has defintely to be documented better...but..
..this is something again very much protocol specific, that evidentiates
once more that a common ABI to rule them all is not probably the way...
...in SCMI the platform firmware is the ultimate arbiter, it is in charge
of any kind of policying...it can cope even with the most unreasonable
asks coming from the agents, even come from root privileged users...
...simply because those requests will get denied OR silently ignored...
...in the case of SCMI Telemetry, as an example, it is up to the server expose
to the Linux Agent ONLY what it deems to be safe enough to be shared with normal
world entities without the risk of enabling side channel attacks...
IOW, in the SCMI world, in any protocol, the agents see and live in an
illusion that is created and enforced by the SCMI server, if you are
switching your point of view and start considering how to enforce policies
around SCMI resources at the agent level ("...only root will be able to do
that"), you are already doing it wrong...
Can this view of the platform be absracted, Telemetry-wise, in a common
interface across any other protocol and any other arch ?
It seems to me, again, very unlikely...
> For the stable telemetry ABI, I would start smaller: descriptor enumeration,
> sample reads with validity metadata, optional group-aware configuration, and a
> generation counter/poll mechanism so userspace can detect reconfiguration or
> partial re-enumeration races.
>
> One other point related to this layering: configuration appears to be global
> to the SCMI instance. Any process opening /dev/scmi/tlm_N can change
> collection mode, sampling interval, DE enablement and timestamp state for
> other users. That may be OK, but it should be an explicit policy decision.
Based on the SCMI architecture and the SCMI implementation in Linux, the
only boundary that you can use to identify a 'user' is am SCMI instance
(i.e. one /dev/scmi/tlm_<N>) because that is what the server(s) on the other
side can identify as a distinct agent and that it can fed with a well defined
set of resources at enumeration time...especially in the case of Telemetry
it seems dificult to be able to partition resources at the agent side...
As it was with the defunct Filesyste approach, I would NOT in general
enfore any policy on configuration...last config request will win PLUS
a generation counter to spot unexpected reconfig happening in background...
If you are using 2 tools or daemons to configure telemetry on the same
system in a conflicting way, it is NONE of kernel businness, you have a
misconfigured platform..
Last but NOT least, I think that maybe a Telemetry framework subsystem
could be instead useful for future possible in-kernel users of Telemetry
(you may have mentioned that too..) ... should someone decide to use this
type of data in-kernel, it would certainly be useful to a have common
in-kernel API to consume these data no matter which is the provider from
which that data is collected...
...BUT, again, in this case we do NOT even have a single user as of now
to model this intefrcae against, since we are currently NOT using SCMI
Telemetry for that...so it is seems even more a remote/impossible thing
to get something like this upstream for now...
Thanks,
Cristian
[1]: https://lore.kernel.org/arm-scmi/20250620192813.2463367-1-cristian.marussi@arm.com/
^ permalink raw reply [flat|nested] 27+ messages in thread
end of thread, other threads:[~2026-07-10 8:33 UTC | newest]
Thread overview: 27+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-03 12:35 [PATCH v5 00/23] Introduce SCMI Telemetry support Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 01/23] firmware: arm_scmi: Add new SCMIv4.0 error codes definitions Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 02/23] firmware: arm_scmi: Reduce the scope of protocols mutex Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 03/23] firmware: arm_scmi: Allow registration of unknown-size events/reports Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 04/23] firmware: arm_scmi: Allow protocols to register for notifications Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 05/23] dt-bindings: firmware: arm,scmi: Add support for telemetry protocol Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 06/23] include: trace: Add Telemetry trace events Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 07/23] firmware: arm_scmi: Add basic Telemetry support Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 08/23] firmware: arm_scmi: Add support to parse SHMTIs areas Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 09/23] firmware: arm_scmi: Add Telemetry configuration operations Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 10/23] firmware: arm_scmi: Add Telemetry DataEvent read capabilities Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 11/23] firmware: arm_scmi: Add support for Telemetry reset Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 12/23] firmware: arm_scmi: Add Telemetry notification support Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 13/23] firmware: arm_scmi: Add support for boot-on Telemetry Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 14/23] firmware: arm_scmi: Add Telemetry generation counter Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 15/23] firmware: arm_scmi: Add common per-protocol debugfs support Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 16/23] firmware: arm_scmi: Add Telemetry debugfs SHMTI dump support Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 17/23] firmware: arm_scmi: Add Telemetry debugfs ABI documentation Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 18/23] firmware: arm_scmi: Expose per-instance identifier Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 19/23] uapi: Add ARM SCMI Telemetry definitions Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 20/23] firmware: arm_scmi: Add System Telemetry driver Cristian Marussi
2026-07-03 12:35 ` [PATCH v5 21/23] docs: ioctl-number: Add SCMI Ioctls Cristian Marussi
2026-07-03 12:36 ` [PATCH v5 22/23] Documentation: Add SCMI System Telemetry documentation Cristian Marussi
2026-07-03 12:36 ` [PATCH v5 23/23] [RFC] tools/scmi: Add SCMI Telemetry testing tool Cristian Marussi
2026-07-07 6:31 ` [PATCH v5 00/23] Introduce SCMI Telemetry support Subrahmanya Lingappa
2026-07-08 20:05 ` David Hildenbrand (Arm)
2026-07-10 8:32 ` Cristian Marussi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox