public inbox for linux-fsdevel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 00/17] Introduce SCMI Telemetry FS support
@ 2026-01-14 11:46 Cristian Marussi
  2026-01-14 11:46 ` [PATCH v2 01/17] firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value Cristian Marussi
                   ` (16 more replies)
  0 siblings, 17 replies; 35+ messages in thread
From: Cristian Marussi @ 2026-01-14 11:46 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty, Cristian Marussi

Hi all,

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 deeemd more suitable for the usecase at hand. (amongst the various
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 firmware advertises a number of
Telemetry Data Events, each one identified by a 32bit unique ID, and an
agent, 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 or direct dedicated FastChannels (another kind of
SCMI MMIO): all of this underlying mechanism is anyway hidden to the user
since it is mediated by the kernel 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 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 above 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, in the future, to feed/register some of the
discovered DEs 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.

In deciding which kind of interface to expose SCMI Telemetry data to a
user, this new SCMI Telemetry driver aims at satisfying 2 main reqs:

 - exposing some sort of FS-based human-readable interface that can be used
   to discover, configure and access our Telemetry data directly from the
   shell

 - exposing alternative machine-friendly, more-performant, binary
   interfaces that can be used to avoid the overhead of multiple accesses
   to the VFS and that can be more suitable to be accessed with a custom
   tool

In the initial RFC posted a few months ago [2], the above was achieved
with a combination of a SysFS interface, for the human-readable side of
the story, and a classic chardev/ioctl for the plain binary access.

Since V1, instead, we moved away from this combined approach, especially
away from SysFS, for the following reason:

 1. "Abusing SysFS": SysFS is a handy way to expose device related
      properties in a common way, using a few common helpers built on
      kernfs; this means, though, that unfortunately in our scenario I had
      to generate a dummy simple device for EACH SCMI Telemetry DataEvent
      that I got to discover at runtime and attach to them, all of the
      properties I need.
      This by itself seemed to me abusing the SysFS framework, but even
      ignoring this, the impact on the system when we have to deal with
      hundreds or tens of thousands od DEs is sensible.
      In some test scenario I ended with 50k devices and half-a-millon
      related property files ... O_o

 2. "SysFS constraints": SysFS usage itself has its well-known constraints
      and best practices, like the one-file/one-value rule, and due to the
      fact that any virtual file with a complex structure or handling logic
      is frowned upon, you can forget about IOTCLs and mmap'ing to provide
      a more performant interface, which is the reason why, in the previous
      RFC, there was an additional alternative chardev interface.
      These latter limitations around the implementation of files with a
      more complex semantic (i.e. with a broader set of file_operations)
      derive from the underlying KernFS support, so KernFS is equally not
      suitable as a building block for our implementation.

 2. "Chardev limitations": Given the nature of the protocol, the hybrid
      approach employing character devices was itself problematic: first
      of all because there is an upper limit on the number of chardev we
      can create, dictated by the range of available minor numbers and
      then because the fact itself to have to maintain 2 completely
      different interfaces (FS + chardev) is a pain.

As a final remark, please NOTE THAT all of this is supposed to be available
in production systems across a number of heterogeneous platforms: for these
reasons the easy choice, debugFS, is not an option.

Due to the above reasoning, since V1 we opted for a new approach with the
proposed interfaces now based on a full fledged, unified, virtual pseudo
filesystem implemented from scratch so that we can:

 - expose all the DEs property we like as before with SysFS, but without
   any of the constraint imposed by the usage of SysFs or kernfs.

 - easily expose additional alternative views of the same set of DEs
   using symlinking capabilities (e.g. alternative topological view)

 - additionally expose a few alternative and more performant interfaces
   by embedding in that same FS, a few special virtual files:

   + 'control': to issue IOCTLs for quicker discovery and on-demand access
   		to data
   + 'pipe' [TBD]: to provide a stream of events using a virtual
		   infinite-style file
   + 'raw_<N>' [TBD]: to provide direct memory mapped access to the raw
   		      SCMI Telemetry data

 - use a mount option to enable a lazy enumeration operation mode to delay
   SCMI related background discovery activities to the effective point in
   time when the user needs it (if ever)


INTERFACES
===========

We propose a couple of interfaces, both rooted in the same unified
SCMI Telemetry Filesystem STLMFS, which can be mounted with:

	mount -t stlmfs none /sys/fs/arm_telemetry/

In a nutshell, we expose the following interfaces, rooted at different
points in the FS:

 1. a FS based human-readable API tree

   This API present the discovered DEs and DEs-groups rooted under a
   structrure like this:

	/sys/fs/arm_telemetry/tlm_0/
	|-- all_des_enable
	|-- all_des_tstamp_enable
	|-- available_update_intervals_ms
	|-- current_update_interval_ms
	|-- de_implementation_version
	|-- des
	|   |-- 0x00000000
	|   |-- 0x00000016
	|   |-- 0x00001010
	|   |-- 0x0000A000
	|   |-- 0x0000A001
	|   |-- 0x0000A002
	|   |-- 0x0000A005
	|   |-- 0x0000A007
	|   |-- 0x0000A008
	|   |-- 0x0000A00A
	|   |-- 0x0000A00B
	|   |-- 0x0000A00C
	|   `-- 0x0000A010
	|-- des_bulk_read
	|-- des_single_sample_read
	|-- groups
	|   |-- 0
	|   `-- 1
	|-- intervals_discrete
	|-- reset
	|-- tlm_enable
	`-- version

	At the top level we have general configuration knobs to:

	- enable/disable all DEs with or without tstamp
	- configure the update interval that the platform will use
	- enable Telemetry as a whole rest the whole stack
	- read all the enabled DEs in a buffer one-per-line
		<DE_ID> <TIMESTAMP> <DATA_VALUE>
	- des_single_sample_read to request an immediate updated read of
	  all the enabled DEs in a single buffer one-per-line:
		<DE_ID> <TIMESTAMP> <DATA_VALUE>
        
	where each DE in turn is represented by a flat subtree like:

	tlm_0/des/0xA001/
	|-- compo_instance_id
	|-- compo_type
	|-- enable
	|-- instance_id
	|-- name
	|-- persistent
	|-- tstamp_enable
	|-- tstamp_exp
	|-- type
	|-- unit
	|-- unit_exp
	`-- value

	where, beside a bunch of description items, you can:

	- enable/disable a single DE
	- read back its tstamp and data from 'value' as in:
		<TIMESTAMP>: <DATA_VALUE>

	then for each discovered group of DEs:

	scmi_tlm_0/groups/0/
	|-- available_update_intervals_ms
	|-- composing_des
	|-- current_update_interval_ms
	|-- des_bulk_read
	|-- des_single_sample_read
	|-- enable
	|-- intervals_discrete
	`-- tstamp_enable

	you can find the knobs to:
	
	- enable/disable the group as a whole
	- lookup group composition
	- set a per-group update interval (if supported)
	- des_bulk_read to read all the enabled DEs for this group in a
	  single buffer one-per-line:
		<DE_ID> <TIMESTAMP> <DATA_VALUE>
	- des_single_sample_read to request an immediate updated read of
	  all the enabled DEs for this group in a single buffer
	  one-per-line:
		<DE_ID> <TIMESTAMP> <DATA_VALUE>

 2. Leveraging the capabilities of a full-fledged filesystem and the
    topological information provided by SCMI Telemetry we expose also
    and alternative view of the above tree, by symlinking a few of the
    same entries above under another, topologically sorted, subtree:

    components/
    |-- cpu
    |   |-- 0
    |   |   |-- celsius
    |   |   |   `-- 0
    |   |   |       `-- 0x0001
    |   |   |           |-- compo_instance_id
    |   |   |           |-- compo_type
    |   |   |           |-- enable
    |   |   |           |-- instance_id
    |   |   |           |-- name
    |   |   |           |-- persistent
    |   |   |           |-- tstamp_enable
    |   |   |           |-- tstamp_exp
    |   |   |           |-- type
    |   |   |           |-- unit
    |   |   |           |-- unit_exp
    |   |   |           `-- value
    |   |   `-- cycles
    |   |       |-- 0
    |   |       |   `-- 0x1010
    |   |	    |    ....
	.................
	...............
    |   |-- 1
    |   |   `-- celsius
    |   |       `-- 0
    |   |           `-- 0x0002
    |   |           .........
    |   `-- 2
    |       `-- celsius
    |           `-- 0
    |               `-- 0x0003
    |-- interconnnect
    |   `-- 0
    |       `-- hertz
    |           `-- 0
    |               |-- 0xA008
    |               `-- 0xA00B
    |-- mem_cntrl
    |   `-- 0
    |       |-- bps
    |       |   `-- 0
    |       |       `-- 0xA00A
    |       |-- celsius
    |       |   `-- 0
    |       |       `-- 0xA007
    |       `-- joules
    |           `-- 0
    |               `-- 0xA002
    |-- periph
    |   |-- 0
    |   |   `-- messages
    |   |       `-- 0
    |   |           `-- 0x0016
    |   |-- 1
    |   |   `-- messages
    |   |       `-- 0
    |   |           `-- 0x0017
    |   `-- 2
    |       `-- messages
    |           `-- 0
    |               `-- 0x0018
    `-- unspec
    `-- 0
    |-- celsius
    |   `-- 0
    |       `-- 0xA005


   ...so as to provide the human user with a more understandable layout.


All of this is nice and fancy human-readable, easily scriptable, but
certainly not the fastest possible to access especially on huge trees...

 ... so for the afore-mentioned reasons we alternatively expose also:

 3. a more performant API based on IOCTLs as described fully in:

	include/uapi/linux/scmi.h

   As described succinctly in the above UAPI header too, this API is meant
   to be called on a few special files named 'control' that are populated
   into the tree:

   .
   |-- all_des_enable
   .....
   |-- components
   |   |-- cpu
   |   |-- ...
   |   |-- periph
   |   `-- unspec
   |-- control                   <<<<<<<<<<<<<<
   .....................

   |-- groups
   |   |-- 0
   |   |   |-- available_update_intervals_ms
   |   |   |-- composing_des
   |   |   |-- control           <<<<<<<<<<<<<<
   .....................
   |   |-- 1
   |   |   |-- available_update_intervals_ms
   |   |   |-- composing_des
   |   |   |-- control           <<<<<<<<<<<<<<
   .....................
   |   `-- 2
   |       |-- available_update_intervals_ms
   |       |-- composing_des
   |       |-- control           <<<<<<<<<<<<<<
   .....................

  This will allow a tool to:

   - use some IOCTLs to configure a set of properties equivalent to the
     ones above in FS
   - use some other IOCTLs for direct access to data in binary format
     for a single DEs or all of them

 4. [FUTURE/NOT IN THIS V2]
    Another alternative and completely binary direct raw access interface
    accessible via a new set of memory .mmap'able special files so as to
    allow userspace tools to access SCMI Telemetry data directly in binary
    form without any kernel mediation.


NOTE THAT this series, though, at the firmware interface level still
supports ONLY the previous SCMI Telemetry ALPHA_0 [1] specification NOT the
new recently released BETA [0].


Missing feats & next steps
--------------------------
 - support SCMI v4.0 BETA
 - add direct access interface via mmap-able 'raw' files
 - add streaming mode interface via 'pipe' file (tentative)
 - evolve/enhance app in tools/testing/scmi/stlm to be interactive
 

KNOWN ISSUES
------------
 - STLMFS code layout and location...nothing lives in fs/ and no FS Kconfig
 - lazy filesystem population implementation is dubious (albeit simpler:D)
 - Documentation is still incomplete
 - residual sparse/smatch static analyzers errors
 - stlm tool utility is minimal for testing or development

Based on V6.19-rc3, tested on an emulated setup.

Any feedback welcome,

Thanks,
Cristian

----

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/fb/?lang=en
[1]: https://developer.arm.com/documentation/den0056/f/?lang=en
[2]: https://lore.kernel.org/arm-scmi/20250620192813.2463367-1-cristian.marussi@arm.com/


Cristian Marussi (17):
  firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value
  firmware: arm_scmi: Reduce the scope of protocols mutex
  firmware: arm_scmi: Allow protocols to register for notifications
  uapi: Add ARM SCMI definitions
  firmware: arm_scmi: Add Telemetry protocol support
  include: trace: Add Telemetry trace events
  firmware: arm_scmi: Use new Telemetry traces
  firmware: arm_scmi: Add System Telemetry driver
  fs/stlmfs: Document ARM SCMI Telemetry filesystem
  firmware: arm_scmi: Add System Telemetry ioctls support
  fs/stlmfs: Document alternative ioctl based binary interface
  firmware: arm_scmi: Add Telemetry components view
  fs/stlmfs: Document alternative topological view
  [RFC] docs: stlmfs: Document ARM SCMI Telemetry FS ABI
  [RFC] firmware: arm_scmi: Add lazy population support to Telemetry FS
  fs/stlmfs: Document lazy mode and related mount option
  [RFC] tools/scmi: Add SCMI Telemetry testing tool

 Documentation/ABI/testing/stlmfs              |  153 +
 Documentation/filesystems/stlmfs.rst          |  311 ++
 MAINTAINERS                                   |    1 +
 drivers/firmware/arm_scmi/Kconfig             |   10 +
 drivers/firmware/arm_scmi/Makefile            |    3 +-
 drivers/firmware/arm_scmi/common.h            |    4 +
 drivers/firmware/arm_scmi/driver.c            |   64 +-
 drivers/firmware/arm_scmi/notify.c            |   32 +-
 drivers/firmware/arm_scmi/notify.h            |    8 +-
 drivers/firmware/arm_scmi/protocols.h         |    7 +
 .../firmware/arm_scmi/scmi_system_telemetry.c | 2927 +++++++++++++++++
 drivers/firmware/arm_scmi/telemetry.c         | 2710 +++++++++++++++
 include/linux/scmi_protocol.h                 |  191 +-
 include/trace/events/scmi.h                   |   48 +-
 include/uapi/linux/scmi.h                     |  287 ++
 tools/testing/scmi/Makefile                   |   25 +
 tools/testing/scmi/stlm.c                     |  385 +++
 17 files changed, 7125 insertions(+), 41 deletions(-)
 create mode 100644 Documentation/ABI/testing/stlmfs
 create mode 100644 Documentation/filesystems/stlmfs.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.52.0


^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH v2 01/17] firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value
  2026-01-14 11:46 [PATCH v2 00/17] Introduce SCMI Telemetry FS support Cristian Marussi
@ 2026-01-14 11:46 ` Cristian Marussi
  2026-01-19 11:18   ` Jonathan Cameron
  2026-01-14 11:46 ` [PATCH v2 02/17] firmware: arm_scmi: Reduce the scope of protocols mutex Cristian Marussi
                   ` (15 subsequent siblings)
  16 siblings, 1 reply; 35+ messages in thread
From: Cristian Marussi @ 2026-01-14 11:46 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty, Cristian Marussi

Add a common definition of SCMI_MAX_PROTOCOLS and use it all over the
SCMI stack.

Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
 drivers/firmware/arm_scmi/notify.c | 4 +---
 include/linux/scmi_protocol.h      | 3 +++
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/drivers/firmware/arm_scmi/notify.c b/drivers/firmware/arm_scmi/notify.c
index dee9f238f6fd..78e9e27dc9ec 100644
--- a/drivers/firmware/arm_scmi/notify.c
+++ b/drivers/firmware/arm_scmi/notify.c
@@ -94,8 +94,6 @@
 #include "common.h"
 #include "notify.h"
 
-#define SCMI_MAX_PROTO		256
-
 #define PROTO_ID_MASK		GENMASK(31, 24)
 #define EVT_ID_MASK		GENMASK(23, 16)
 #define SRC_ID_MASK		GENMASK(15, 0)
@@ -1673,7 +1671,7 @@ int scmi_notification_init(struct scmi_handle *handle)
 	ni->gid = gid;
 	ni->handle = handle;
 
-	ni->registered_protocols = devm_kcalloc(handle->dev, SCMI_MAX_PROTO,
+	ni->registered_protocols = devm_kcalloc(handle->dev, SCMI_MAX_PROTOCOLS,
 						sizeof(char *), GFP_KERNEL);
 	if (!ni->registered_protocols)
 		goto err;
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index aafaac1496b0..c6efe4f371ac 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -926,8 +926,11 @@ enum scmi_std_protocol {
 	SCMI_PROTOCOL_VOLTAGE = 0x17,
 	SCMI_PROTOCOL_POWERCAP = 0x18,
 	SCMI_PROTOCOL_PINCTRL = 0x19,
+	SCMI_PROTOCOL_LAST = 0x7f,
 };
 
+#define SCMI_MAX_PROTOCOLS	256
+
 enum scmi_system_events {
 	SCMI_SYSTEM_SHUTDOWN,
 	SCMI_SYSTEM_COLDRESET,
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 02/17] firmware: arm_scmi: Reduce the scope of protocols mutex
  2026-01-14 11:46 [PATCH v2 00/17] Introduce SCMI Telemetry FS support Cristian Marussi
  2026-01-14 11:46 ` [PATCH v2 01/17] firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value Cristian Marussi
@ 2026-01-14 11:46 ` Cristian Marussi
  2026-01-19 11:21   ` Jonathan Cameron
  2026-01-14 11:46 ` [PATCH v2 03/17] firmware: arm_scmi: Allow protocols to register for notifications Cristian Marussi
                   ` (14 subsequent siblings)
  16 siblings, 1 reply; 35+ messages in thread
From: Cristian Marussi @ 2026-01-14 11:46 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty, 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 | 50 ++++++++++++++----------------
 1 file changed, 24 insertions(+), 26 deletions(-)

diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index 7e5429eff35d..b198c58da1dd 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -17,6 +17,7 @@
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
 #include <linux/bitmap.h>
+#include <linux/cleanup.h>
 #include <linux/debugfs.h>
 #include <linux/device.h>
 #include <linux/export.h>
@@ -2123,7 +2124,6 @@ static int scmi_protocol_version_negotiate(struct scmi_protocol_handle *ph)
  * 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.
@@ -2162,8 +2162,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;
 
@@ -2226,27 +2228,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);
 }
 
 /**
@@ -2277,10 +2277,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;
@@ -2299,9 +2300,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.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 03/17] firmware: arm_scmi: Allow protocols to register for notifications
  2026-01-14 11:46 [PATCH v2 00/17] Introduce SCMI Telemetry FS support Cristian Marussi
  2026-01-14 11:46 ` [PATCH v2 01/17] firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value Cristian Marussi
  2026-01-14 11:46 ` [PATCH v2 02/17] firmware: arm_scmi: Reduce the scope of protocols mutex Cristian Marussi
@ 2026-01-14 11:46 ` Cristian Marussi
  2026-01-19 11:33   ` Jonathan Cameron
  2026-01-14 11:46 ` [PATCH v2 04/17] uapi: Add ARM SCMI definitions Cristian Marussi
                   ` (13 subsequent siblings)
  16 siblings, 1 reply; 35+ messages in thread
From: Cristian Marussi @ 2026-01-14 11:46 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty, Cristian Marussi

Allow protocols themselves to register for their own notifications and
providing their own notifier callbacks. While at that, allow for a protocol
to register events with compilation-time unknown report/event sizes: such
events will use the maximum transport size.

Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
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    | 28 ++++++++++++++++++++-------
 drivers/firmware/arm_scmi/notify.h    |  8 ++++++--
 drivers/firmware/arm_scmi/protocols.h |  6 ++++++
 5 files changed, 49 insertions(+), 9 deletions(-)

diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index 3b24831094b6..9f9a5a4bcf35 100644
--- a/drivers/firmware/arm_scmi/common.h
+++ b/drivers/firmware/arm_scmi/common.h
@@ -17,6 +17,7 @@
 #include <linux/hashtable.h>
 #include <linux/list.h>
 #include <linux/module.h>
+#include <linux/notifier.h>
 #include <linux/refcount.h>
 #include <linux/scmi_protocol.h>
 #include <linux/spinlock.h>
@@ -527,5 +528,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 b198c58da1dd..1085c70ca457 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 = {
 	.version_get = version_get,
 	.xfer_get_init = xfer_get_init,
@@ -2156,6 +2167,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);
 	/* proto->init is assured NON NULL by scmi_protocol_register */
 	ret = pi->proto->instance_init(&pi->ph);
diff --git a/drivers/firmware/arm_scmi/notify.c b/drivers/firmware/arm_scmi/notify.c
index 78e9e27dc9ec..e84b4dbefe82 100644
--- a/drivers/firmware/arm_scmi/notify.c
+++ b/drivers/firmware/arm_scmi/notify.c
@@ -593,7 +593,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;
 	}
@@ -752,7 +758,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;
@@ -767,6 +773,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;
@@ -779,8 +787,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++)
+	for (i = 0; i < ee->num_events; i++) {
+		if (evt[i].max_payld_sz == 0) {
+			payld_sz = max_msg_sz;
+			break;
+		}
 		payld_sz = max_t(size_t, 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,
@@ -809,7 +822,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;
 
@@ -1373,9 +1387,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/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
diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h
index d62c4469d1fd..afca1336267b 100644
--- a/drivers/firmware/arm_scmi/protocols.h
+++ b/drivers/firmware/arm_scmi/protocols.h
@@ -163,6 +163,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:
@@ -182,6 +185,9 @@ struct scmi_protocol_handle {
 	int (*set_priv)(const struct scmi_protocol_handle *ph, void *priv,
 			u32 version);
 	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.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 04/17] uapi: Add ARM SCMI definitions
  2026-01-14 11:46 [PATCH v2 00/17] Introduce SCMI Telemetry FS support Cristian Marussi
                   ` (2 preceding siblings ...)
  2026-01-14 11:46 ` [PATCH v2 03/17] firmware: arm_scmi: Allow protocols to register for notifications Cristian Marussi
@ 2026-01-14 11:46 ` Cristian Marussi
  2026-01-19 11:43   ` Jonathan Cameron
  2026-01-14 11:46 ` [PATCH v2 05/17] firmware: arm_scmi: Add Telemetry protocol support Cristian Marussi
                   ` (12 subsequent siblings)
  16 siblings, 1 reply; 35+ messages in thread
From: Cristian Marussi @ 2026-01-14 11:46 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty, 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>
---
v1 --> v2
 - Added proper __counted_by marks
 - Fixed a few dox comments
 - Renamed reserved[] fields to pad[]
---
 MAINTAINERS               |   1 +
 include/uapi/linux/scmi.h | 287 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 288 insertions(+)
 create mode 100644 include/uapi/linux/scmi.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 12f49de7fe03..1c0ccaddc3f3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -25392,6 +25392,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..e4e9939a1bf8
--- /dev/null
+++ b/include/uapi/linux/scmi.h
@@ -0,0 +1,287 @@
+/* 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
+
+#define SCMI_TLM_GRP_INVALID            0xFFFFFFFF
+
+/**
+ * scmi_tlm_base_info - Basic info 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 update intervals available (instance-level)
+ * @flags: Instance specific feature-support bitmap
+ *
+ * Used by:
+ *	RO - SCMI_TLM_GET_INFO
+ *
+ * Supported by:
+ *	control/
+ */
+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 flags;
+#define SCMI_TLM_CAN_RESET	(1 << 0)
+};
+
+/**
+ * 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.
+ * @pad: Padding fields to enforce alignment.
+ * @current_update_interval: Get/Set currently active update interval for the
+ *			     whole instance or a group.
+ *
+ * Used by:
+ *	RO - SCMI_TLM_GET_CFG
+ *	WO - SCMI_TLM_SET_CFG
+ *
+ * Supported by:
+ *	control/
+ *	groups/<N>/control
+ */
+struct scmi_tlm_config {
+	__u8 enable;
+	__u8 t_enable;
+	__u8 pad[2];
+	__u32 current_update_interval;
+};
+
+/**
+ * scmi_tlm_intervals  - Update intervals descriptor
+ *
+ * @discrete: Flag to indicate the nature of the intervals described in
+ *	      @update_intervals.
+ *	      When 'false' @update_intervals is a triplet: min/max/step
+ * @pad: Padding fields to enforce alignment.
+ * @num: Number of entries of @update_intervals
+ * @update_intervals: A variably-sized array containing the update intervals
+ *
+ * Used by:
+ *	RW - SCMI_TLM_GET_INTRVS
+ *
+ * Supported by:
+ *	control/
+ *	groups/<N>/control
+ */
+struct scmi_tlm_intervals {
+	__u8 discrete;
+	__u8 pad[3];
+	__u32 num;
+#define SCMI_TLM_UPDATE_INTVL_SEGMENT_LOW	0
+#define SCMI_TLM_UPDATE_INTVL_SEGMENT_HIGH	1
+#define SCMI_TLM_UPDATE_INTVL_SEGMENT_STEP	2
+	__u32 update_intervals[] __counted_by(num);
+};
+
+/**
+ * scmi_tlm_de_config  - DE configuration
+ *
+ * @id: Identifier of the DE to act upon (ignored by SCMI_TLM_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)
+ *
+ * Used by:
+ *	RW - SCMI_TLM_GET_DE_CFG
+ *	RW - SCMI_TLM_SET_DE_CFG
+ *	WO - SCMI_TLM_SET_ALL_CFG
+ *
+ * Supported by:
+ *	control/
+ */
+struct scmi_tlm_de_config {
+	__u32 id;
+	__u32 enable;
+	__u32 t_enable;
+};
+
+/**
+ * scmi_tlm_de_info  - DE Descriptor
+ *
+ * @id: DE identifier
+ * @grp_id: Identifier of the group which this DE belongs to; reported as
+ *	    SCMI_TLM_GRP_INVALID when not part of any group
+ * @data_sz: DE data size in bytes
+ * @type: DE type
+ * @unit: DE unit of measurements
+ * @unit_exp: Power-of-10 multiplier for DE unit
+ * @tstamp_exp: Power-of-10 multiplier for DE timestamp (if supported)
+ * @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)
+ * @name: Optional name of this DE
+ *
+ * Used to get the full description of a DE: it reflects DE Descriptors
+ * definitions in 3.12.4.6.
+ *
+ * Used by:
+ *	RW - SCMI_TLM_GET_DE_INFO
+ *
+ * Supported by:
+ *	control/
+ */
+struct scmi_tlm_de_info {
+	__u32 id;
+	__u32 grp_id;
+	__u32 data_sz;
+	__u32 type;
+	__u32 unit;
+	__s32 unit_exp;
+	__s32 tstamp_exp;
+	__u32 instance_id;
+	__u32 compo_instance_id;
+	__u32 compo_type;
+	__u32 persistent;
+	__u8 name[16];
+};
+
+/**
+ * scmi_tlm_des_list  - List of all defined DEs
+ *
+ * @num_des: Number of entries in @des
+ * @des: An array containing descriptors for all defined DEs
+ *
+ * Used by:
+ *	RW - SCMI_TLM_GET_DE_LIST
+ *
+ * Supported by:
+ *	control/
+ */
+struct scmi_tlm_des_list {
+	__u32 num_des;
+	struct scmi_tlm_de_info des[] __counted_by(num_des);
+};
+
+/**
+ * scmi_tlm_de_sample - A DE reading
+ *
+ * @id: DE identifier
+ * @tstamp: DE reading timestamp (equal 0 is NOT supported)
+ * @val: Reading of the DE data value
+ *
+ * Used by:
+ *	RW - SCMI_TLM_GET_DE_VALUE
+ *
+ * Supported by:
+ *	control/
+ */
+struct scmi_tlm_de_sample {
+	__u32 id;
+	__u64 tstamp;
+	__u64 val;
+};
+
+/**
+ * scmi_tlm_data_read - Bulk read of multiple DEs
+ *
+ * @num_samples: Number of entries returned in @samples
+ * @samples: An array of samples containing an entry for each DE that was
+ *	     enabled when the single sample read request was issued.
+ *
+ * Used by:
+ *	RW - SCMI_TLM_SINGLE_SAMPLE
+ *	RW - SCMI_TLM_BULK_READ
+ *
+ * Supported by:
+ *	control/
+ *	groups/<N>/control
+ */
+struct scmi_tlm_data_read {
+	__u32 num_samples;
+	struct scmi_tlm_de_sample samples[] __counted_by(num_samples);
+};
+
+/**
+ * scmi_tlm_grp_info  - DE-group descriptor
+ *
+ * @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.
+ *
+ * Used by:
+ *	RO - SCMI_TLM_GET_GRP_INFO
+ *
+ * Supported by:
+ *	groups/<N>control/
+ */
+struct scmi_tlm_grp_info {
+	__u32 id;
+	__u32 num_des;
+	__u32 num_intervals;
+};
+
+/**
+ * scmi_tlm_grps_list  - DE-groups List
+ *
+ * @num_grps: Number of entries returned in @grps
+ * @grps: An array containing descriptors for all defined DE Groups
+ *
+ * Used by:
+ *	RW - SCMI_TLM_GET_GRP_LIST
+ *
+ * Supported by:
+ *	control/
+ */
+struct scmi_tlm_grps_list {
+	__u32 num_grps;
+	struct scmi_tlm_grp_info grps[] __counted_by(num_grps);
+};
+
+/**
+ * scmi_tlm_grp_desc  - Group descriptor
+ *
+ * @num_des: Number of DEs part of this group
+ * @composing_des: An array containing the DE IDs that belongs to this group.
+ *
+ * Used by:
+ *	RW - SCMI_TLM_GET_GRP_DESC
+ *
+ * Supported by:
+ *	groups/<N>control/
+ */
+struct scmi_tlm_grp_desc {
+	__u32 num_des;
+	__u32 composing_des[] __counted_by(num_des);
+};
+
+#define SCMI 0xF1
+
+#define SCMI_TLM_GET_INFO	_IOR(SCMI,  0x00, struct scmi_tlm_base_info)
+#define SCMI_TLM_GET_CFG	_IOR(SCMI,  0x01, struct scmi_tlm_config)
+#define SCMI_TLM_SET_CFG	_IOW(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_SET_ALL_CFG	_IOW(SCMI,  0x09, struct scmi_tlm_de_config)
+#define SCMI_TLM_GET_GRP_LIST	_IOWR(SCMI, 0x0A, struct scmi_tlm_grps_list)
+#define SCMI_TLM_GET_GRP_INFO	_IOR(SCMI,  0x0B, struct scmi_tlm_grp_info)
+#define SCMI_TLM_GET_GRP_DESC	_IOWR(SCMI, 0x0C, struct scmi_tlm_grp_desc)
+#define SCMI_TLM_SINGLE_SAMPLE	_IOWR(SCMI, 0x0D, struct scmi_tlm_data_read)
+#define SCMI_TLM_BULK_READ	_IOWR(SCMI, 0x0E, struct scmi_tlm_data_read)
+
+#endif /* _UAPI_LINUX_SCMI_H */
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 05/17] firmware: arm_scmi: Add Telemetry protocol support
  2026-01-14 11:46 [PATCH v2 00/17] Introduce SCMI Telemetry FS support Cristian Marussi
                   ` (3 preceding siblings ...)
  2026-01-14 11:46 ` [PATCH v2 04/17] uapi: Add ARM SCMI definitions Cristian Marussi
@ 2026-01-14 11:46 ` Cristian Marussi
  2026-01-19 16:29   ` Jonathan Cameron
  2026-01-23 11:00   ` Elif Topuz
  2026-01-14 11:46 ` [PATCH v2 06/17] include: trace: Add Telemetry trace events Cristian Marussi
                   ` (11 subsequent siblings)
  16 siblings, 2 replies; 35+ messages in thread
From: Cristian Marussi @ 2026-01-14 11:46 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty, Cristian Marussi

Add basic support for SCMI V4.0 Telemetry protocol including SHMTI,
FastChannels, Notifications and Single Sample Reads collection methods.

Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v1 --> v2
 - Add proper ioread accessors for TDCF areas
 - Rework resource allocation logic and lifecycle
 - Introduce new resources accessors and res_get() operation to
   implement lazy enumeration
 - Support boot-on telemetry:
   + Add DE_ENABLED_LIST cmd support
   + Add CONFIG_GET cmd support
   + Add TDCF_SCAN for best effort enumeration
   + Harden driver against out-of-spec FW with out of spec cmds
 - Use FCs list
 - Rework de_info_lookup to a moer general de_lookup
 - Fixed reset to use CONFIG_GET and DE_ENABLED_LIST to gather initial
   state
 - Using sign_extend32 helper
 - Added counted_by marker where appropriate
---
 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 | 2671 +++++++++++++++++++++++++
 include/linux/scmi_protocol.h         |  188 +-
 5 files changed, 2862 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 1085c70ca457..dd5da75a19b0 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -3450,6 +3450,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);
 }
@@ -3468,6 +3469,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 afca1336267b..766b68a3084e 100644
--- a/drivers/firmware/arm_scmi/protocols.h
+++ b/drivers/firmware/arm_scmi/protocols.h
@@ -385,5 +385,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..16bcdcdc1dc3
--- /dev/null
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -0,0 +1,2671 @@
+// 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"
+
+/* 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)		((x) & BIT(31))
+#define SUPPORTS_CONTINUOS_UPDATE(x)	((x) & BIT(30))
+#define SUPPORTS_PER_GROUP_CONFIG(x)	((x) & BIT(18))
+#define SUPPORTS_RESET(x)		((x) & BIT(17))
+#define SUPPORTS_FC(x)			((x) & BIT(16))
+};
+
+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;
+};
+
+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)	((d)->attr_1 & BIT(31))
+#define	IS_FC_SUPPORTED(d)	((d)->attr_1 & BIT(30))
+#define	GET_DE_TYPE(d)		(le32_get_bits((d)->attr_1, GENMASK(29, 22)))
+#define	IS_PERSISTENT(d)	((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 GET_DE_TSTAMP_EXP(d)						\
+	({								\
+		__u32 __signed_exp =					\
+			FIELD_GET(GENMASK(4, 1), (d)->attr_1);		\
+									\
+		sign_extend32(__signed_exp, 3);				\
+	})
+#define	IS_TSTAMP_SUPPORTED(d)	((d)->attr_1 & BIT(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)	(!((x) & BIT(12)))
+	__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 tdcf_de_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;
+#define TELEMETRY_MODE_GET		(FIELD_GET(GENMASK(4, 1)))
+	__le32 sampling_rate;
+};
+
+/* TDCF */
+
+#define _I(__a)		(ioread32((void __iomem *)(__a)))
+
+#define TO_CPU_64(h, l)	((((u64)(h)) << 32) | (l))
+
+enum scan_mode {
+	SCAN_LOOKUP,
+	SCAN_UPDATE,
+	SCAN_DISCOVERY
+};
+
+struct fc_line {
+	u32 data_low;
+	u32 data_high;
+};
+
+struct fc_tsline {
+	u32 data_low;
+	u32 data_high;
+	u32 ts_low;
+	u32 ts_high;
+};
+
+struct line {
+	u32 data_low;
+	u32 data_high;
+};
+
+struct blk_tsline {
+	u32 ts_low;
+	u32 ts_high;
+};
+
+struct tsline {
+	u32 data_low;
+	u32 data_high;
+	u32 ts_low;
+	u32 ts_high;
+};
+
+#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_TSTAMP_GET(f)	LINE_TSTAMP_GET(f)
+
+struct payload {
+	u32 meta;
+#define IS_BLK_TS(x)	(_I(&((x)->meta)) & BIT(4))
+#define USE_BLK_TS(x)	(_I(&((x)->meta)) & BIT(3))
+#define USE_LINE_TS(x)	(_I(&((x)->meta)) & BIT(2))
+#define TS_VALID(x)	(_I(&((x)->meta)) & BIT(1))
+#define	DATA_INVALID(x) (_I(&((x)->meta)) & BIT(0))
+	u32 id;
+	union {
+		struct line l;
+		struct tsline tsl;
+		struct blk_tsline blk_tsl;
+	};
+};
+
+#define PAYLD_ID(x)	(_I(&(((struct payload *)(x))->id)))
+
+#define LINE_DATA_PAYLD_WORDS						       \
+	((sizeof(u32) + sizeof(u32) + sizeof(struct line)) / sizeof(u32))
+#define TS_LINE_DATA_PAYLD_WORDS					       \
+	((sizeof(u32) + sizeof(u32) + sizeof(struct tsline)) / sizeof(u32))
+
+#define QWORDS_LINE_DATA_PAYLD		(LINE_DATA_PAYLD_WORDS / 2)
+#define QWORDS_TS_LINE_DATA_PAYLD	(TS_LINE_DATA_PAYLD_WORDS / 2)
+
+struct prlg {
+	u32 seq_low;
+	u32 seq_high;
+	u32 num_qwords;
+	u32 _meta_header_high;
+};
+
+struct eplg {
+	u32 seq_low;
+	u32 seq_high;
+};
+
+#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_BAD_END_SEQ	GENMASK_U64(63, 0)
+#define TDCF_START_SEQ_GET(x)						       \
+	({								       \
+		u64 _val;						       \
+		struct prlg *_p = &((x)->prlg);				       \
+									       \
+		_val = TO_CPU_64(_I(&_p->seq_high), _I(&_p->seq_low));         \
+		(_val);							       \
+	})
+
+#define IS_BAD_START_SEQ(s)	((s) & 0x1)
+
+#define	TDCF_END_SEQ_GET(e)						       \
+	({								       \
+		u64 _val;						       \
+		struct eplg *_e = (e);					       \
+									       \
+		_val = TO_CPU_64(_I(&_e->seq_high), _I(&_e->seq_low));	       \
+		(_val);							       \
+	 })
+
+struct telemetry_shmti {
+	int id;
+	void __iomem *base;
+	u32 len;
+	u64 last_magic;
+};
+
+#define SHMTI_EPLG(s)						\
+	({							\
+		struct telemetry_shmti *_s = (s);		\
+		void *_eplg;					\
+								\
+		_eplg = _s->base + _s->len - TDCF_EPLG_SZ;	\
+		(_eplg);					\
+	})
+
+struct telemetry_block_ts {
+	refcount_t users;
+	/* Protect block_ts accesses  */
+	struct mutex mtx;
+	u64 last_ts;
+	u64 last_magic;
+	struct payload __iomem *payld;
+	struct xarray *xa_bts;
+};
+
+struct telemetry_de {
+	bool enumerated;
+	bool cached;
+	void __iomem *base;
+	void __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;
+	u64 last_magic;
+	struct list_head item;
+	struct telemetry_block_ts *bts;
+	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;
+	int num_shmti;
+	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_bts;
+	/* 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;
+	struct notifier_block telemetry_nb;
+	atomic_t rinfo_initializing;
+	struct completion rinfo_initdone;
+	struct scmi_telemetry_res_info __private *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);
+
+static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
+				     unsigned int shmti_id, u64 ts,
+				     enum scan_mode mode);
+
+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)
+{
+	int ret;
+
+	if (ti->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 */
+	ti->rinfo->des[ti->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));
+	}
+
+	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));
+
+	/* 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.info->grp_id = grp_id;
+		tde->de.grp = &rinfo->grps[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->tstamp_exp = GET_DE_TSTAMP_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->de.tstamp_support = IS_TSTAMP_SUPPORTED(desc);
+	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->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_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;
+	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 = grp_id;
+	msg->flags = grp_id == SCMI_TLM_GRP_INVALID ?
+		TELEMETRY_GET_SELECTOR_ORPHANS : TELEMETRY_GET_SELECTOR_GROUP;
+
+	resp = t->rx.buf;
+	ret = ph->xops->do_xfer(ph, t);
+	if (!ret) {
+		*enabled = resp->control & TELEMETRY_ENABLE;
+		*active_update_interval =
+			SCMI_TLM_GET_UPDATE_INTERVAL(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->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));
+
+	/*
+	 * 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 = desc->mode == DE_ENABLED_WITH_TSTAMP;
+
+	ret = scmi_telemetry_tde_register(ti, tde);
+	if (ret) {
+		scmi_telemetry_free_tde_put(ti, tde);
+		return ret;
+	}
+
+	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)
+{
+	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", ti->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) {
+		/*
+		 * When Telemetry is found already enabled on the platform,
+		 * proceed with passive discovery using DE_ENABLED_LIST and
+		 * TCDF 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 %ux10^%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_warn(dev, FW_BUG "Cannot query enabled DE list. Carry-on.\n");
+
+		/* Discover DEs on SHMTis: collect states/offsets/values */
+		for (int id = 0; id < ti->num_shmti; id++) {
+			ret = scmi_telemetry_shmti_scan(ti, id, 0, SCAN_DISCOVERY);
+			if (ret)
+				dev_warn(dev, "Failed discovery-scan of SHMTI ID:%d\n", id);
+		}
+	}
+
+	return 0;
+}
+
+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%04X%c",
+					rinfo->des[grp->des[j]]->info->id, term);
+
+			buf += len;
+			bufsize -= len;
+		}
+	}
+
+	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;
+}
+
+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_tlm_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 = 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));
+
+	/*
+	 * 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;
+		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;
+		struct scmi_tlm_intervals *intrvs __free(kfree) =
+			kzalloc(sizeof(*intrvs) + inum * sizeof(__u32), GFP_KERNEL);
+		if (!intrvs)
+			return -ENOMEM;
+
+		intrvs->num = inum;
+		intrvs->discrete = discrete;
+		st->max_resources = intrvs->num;
+
+		*p->intrvs = no_free_ptr(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_tlm_intervals *intrvs = *p->intrvs;
+	unsigned int idx = st->loop_idx;
+
+	intrvs->update_intervals[st->desc_index + idx] = r->intervals[idx];
+
+	return 0;
+}
+
+static int
+scmi_tlm_enumerate_update_intervals(struct telemetry_info *ti,
+				    struct scmi_tlm_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;
+	}
+
+	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;
+
+	/* 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));
+
+	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->id = le32_to_cpu(desc->id);
+	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)
+		return -EINVAL;
+
+	addr = devm_ioremap(ph->dev, phys_addr, len);
+	if (!addr)
+		return -EADDRNOTAVAIL;
+
+	shmti->base = addr;
+	shmti->len = len;
+
+	return 0;
+}
+
+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;
+	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;
+	}
+
+	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);
+	struct scmi_telemetry_de *de;
+
+	ti->res_get(ti);
+	de = xa_load(&ti->xa_des, id);
+	if (!de)
+		return NULL;
+
+	return de;
+}
+
+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 u64
+scmi_telemetry_blkts_read(u64 magic, struct telemetry_block_ts *bts)
+{
+	if (WARN_ON(!bts || !refcount_read(&bts->users)))
+		return 0;
+
+	guard(mutex)(&bts->mtx);
+
+	if (bts->last_magic == magic)
+		return bts->last_ts;
+
+	bts->last_ts = BLK_TSTAMP_GET(&bts->payld->blk_tsl);
+	bts->last_magic = magic;
+
+	return bts->last_ts;
+}
+
+static void scmi_telemetry_blkts_update(u64 magic,
+					struct telemetry_block_ts *bts)
+{
+	guard(mutex)(&bts->mtx);
+
+	if (bts->last_magic != magic) {
+		bts->last_ts = BLK_TSTAMP_GET(&bts->payld->blk_tsl);
+		bts->last_magic = magic;
+	}
+}
+
+static void scmi_telemetry_blkts_put(struct device *dev,
+				     struct telemetry_block_ts *bts)
+{
+	if (refcount_dec_and_test(&bts->users)) {
+		scoped_guard(mutex, &bts->mtx)
+			xa_erase(bts->xa_bts, (unsigned long)bts->payld);
+		devm_kfree(dev, bts);
+	}
+}
+
+static struct telemetry_block_ts *
+scmi_telemetry_blkts_get(struct xarray *xa_bts, struct payload *payld)
+{
+	struct telemetry_block_ts *bts;
+
+	bts = xa_load(xa_bts, (unsigned long)payld);
+	if (!bts)
+		return NULL;
+
+	refcount_inc(&bts->users);
+
+	return bts;
+}
+
+static struct payload *
+scmi_telemetry_nearest_blk_ts(struct telemetry_shmti *shmti,
+			      struct payload *last_payld)
+{
+	struct payload *payld, *bts_payld = NULL;
+	struct tdcf __iomem *tdcf = shmti->base;
+	u32 *next;
+
+	/* Scan from start of TDCF payloads up to last_payld */
+	payld = (struct payload *)tdcf->payld;
+	next = (u32 *)payld;
+	while (payld < last_payld) {
+		if (IS_BLK_TS(payld))
+			bts_payld = payld;
+
+		next += USE_LINE_TS(payld) ?
+			TS_LINE_DATA_PAYLD_WORDS : LINE_DATA_PAYLD_WORDS;
+		payld = (struct payload *)next;
+	}
+
+	return bts_payld;
+}
+
+static struct telemetry_block_ts *
+scmi_telemetry_blkts_lookup(struct device *dev, struct xarray *xa_bts,
+			    struct payload *payld)
+{
+	struct telemetry_block_ts *bts;
+
+	bts = xa_load(xa_bts, (unsigned long)payld);
+	if (!bts) {
+		int ret;
+
+		bts = devm_kzalloc(dev, sizeof(*bts), GFP_KERNEL);
+		if (!bts)
+			return NULL;
+
+		refcount_set(&bts->users, 1);
+		bts->payld = payld;
+		bts->xa_bts = xa_bts;
+		mutex_init(&bts->mtx);
+		ret = xa_insert(xa_bts, (unsigned long)payld, bts, GFP_KERNEL);
+		if (ret) {
+			devm_kfree(dev, bts);
+			return NULL;
+		}
+	}
+
+	return bts;
+}
+
+static struct telemetry_block_ts *
+scmi_telemetry_blkts_bind(struct device *dev, struct telemetry_shmti *shmti,
+			  struct payload *payld, struct xarray *xa_bts)
+{
+	struct telemetry_block_ts *bts;
+	struct payload *bts_payld;
+
+	/* Find the BLK_TS immediately preceding this DE payld */
+	bts_payld = scmi_telemetry_nearest_blk_ts(shmti, payld);
+	if (!bts_payld)
+		return NULL;
+
+	bts = scmi_telemetry_blkts_get(xa_bts, bts_payld);
+	if (bts)
+		return bts;
+
+	return scmi_telemetry_blkts_lookup(dev, xa_bts, payld);
+}
+
+static void scmi_telemetry_tdcf_blkts_parse(struct telemetry_info *ti,
+					    struct payload __iomem *payld,
+					    struct telemetry_shmti *shmti)
+{
+	struct telemetry_block_ts *bts;
+
+	/* Check for spec compliance */
+	if (USE_LINE_TS(payld) || USE_BLK_TS(payld) ||
+	    DATA_INVALID(payld) || (PAYLD_ID(payld) != 0))
+		return;
+
+	/* A BLK_TS descriptor MUST be returned: it is found or it is crated */
+	bts = scmi_telemetry_blkts_lookup(ti->ph->dev, &ti->xa_bts, payld);
+	if (WARN_ON(!bts))
+		return;
+
+	/* Update the descriptor with the lastest TS*/
+	scmi_telemetry_blkts_update(shmti->last_magic, bts);
+}
+
+static void scmi_telemetry_tdcf_data_parse(struct telemetry_info *ti,
+					   struct payload __iomem *payld,
+					   struct telemetry_shmti *shmti,
+					   enum scan_mode mode)
+{
+	bool ts_valid = TS_VALID(payld);
+	struct telemetry_de *tde;
+	bool discovered = false;
+	u64 val, tstamp = 0;
+	u32 de_id;
+
+	de_id = PAYLD_ID(payld);
+	/* Is thi DE ID know ? */
+	tde = scmi_telemetry_tde_lookup(ti, de_id);
+	if (!tde) {
+		if (mode != SCAN_DISCOVERY)
+			return;
+
+		/* In SCAN_DISCOVERY mode we allocate new DEs for unknown IDs */
+		tde = scmi_telemetry_tde_get(ti, de_id);
+		if (IS_ERR(tde))
+			return;
+
+		tde->de.info->id = de_id;
+		tde->de.enabled = true;
+		tde->de.tstamp_enabled = ts_valid;
+		discovered = true;
+	}
+
+	/* 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 *)payld - (void *)shmti->base;
+
+		dev_dbg(ti->ph->dev,
+			"TDCF-updated DE_ID:0x%08X - shmti:%pX  offset:%u\n",
+			tde->de.info->id, tde->base, tde->offset);
+	}
+
+	if (discovered) {
+		if (scmi_telemetry_tde_register(ti, tde)) {
+			scmi_telemetry_free_tde_put(ti, tde);
+			return;
+		}
+	}
+
+	scoped_guard(mutex, &tde->mtx) {
+		if (tde->last_magic == shmti->last_magic)
+			return;
+	}
+
+	/* Data is always valid since we are NOT handling BLK TS lines here */
+	val = LINE_DATA_GET(&payld->l);
+	/* Collect the right TS */
+	if (ts_valid) {
+		if (USE_LINE_TS(payld)) {
+			tstamp = LINE_TSTAMP_GET(&payld->tsl);
+		} else if (USE_BLK_TS(payld)) {
+			if (!tde->bts) {
+				/*
+				 * Scanning a TDCF looking for the nearest
+				 * previous valid BLK_TS, after having found a
+				 * USE_BLK_TS() payload, MUST succeed.
+				 */
+				tde->bts = scmi_telemetry_blkts_bind(ti->ph->dev,
+								     shmti, payld,
+								     &ti->xa_bts);
+				if (WARN_ON(!tde->bts))
+					return;
+			}
+
+			tstamp = scmi_telemetry_blkts_read(tde->last_magic,
+							   tde->bts);
+		}
+	}
+
+	guard(mutex)(&tde->mtx);
+	tde->last_magic = shmti->last_magic;
+	tde->last_val = val;
+	if (tde->de.tstamp_enabled)
+		tde->last_ts = tstamp;
+	else
+		tde->last_ts = 0;
+}
+
+static int scmi_telemetry_tdcf_line_parse(struct telemetry_info *ti,
+					  struct payload __iomem *payld,
+					  struct telemetry_shmti *shmti,
+					  enum scan_mode mode)
+{
+	int used_qwords;
+
+	used_qwords = (USE_LINE_TS(payld) && TS_VALID(payld)) ?
+		QWORDS_TS_LINE_DATA_PAYLD : QWORDS_LINE_DATA_PAYLD;
+
+	/* Invalid lines are not an error, could simply be disabled DEs */
+	if (DATA_INVALID(payld))
+		return used_qwords;
+
+	if (!IS_BLK_TS(payld))
+		scmi_telemetry_tdcf_data_parse(ti, payld, shmti, mode);
+	else
+		scmi_telemetry_tdcf_blkts_parse(ti, payld, shmti);
+
+	return used_qwords;
+}
+
+static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
+				     unsigned int shmti_id, u64 ts,
+				     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;
+	u64 startm = 0, endm = TDCF_BAD_END_SEQ;
+	void *eplg = SHMTI_EPLG(shmti);
+
+	if (!tdcf)
+		return -ENODEV;
+
+	do {
+		unsigned int qwords;
+		void __iomem *next;
+
+		/* 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))
+			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);
+			if (qwords < used_qwords)
+				return -EINVAL;
+
+			next += used_qwords * 8;
+			qwords -= used_qwords;
+		}
+
+		endm = TDCF_END_SEQ_GET(eplg);
+	} while (startm != endm && --retries);
+
+	if (startm != endm)
+		return -EPROTO;
+
+	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)
+			de->enabled = *enable;
+		if (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 *payld;
+			u32 offs;
+
+			offs = le32_to_cpu(resp->tdcf_de_offset);
+			if (offs >= ti->shmti[sid].len - de->info->data_sz)
+				return -EPROTO;
+
+			tde = to_tde(de);
+			tde->base = ti->shmti[sid].base;
+			tde->offset = 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) {
+				tde->bts = scmi_telemetry_blkts_bind(ti->ph->dev,
+								     &ti->shmti[sid],
+								     payld,
+								     &ti->xa_bts);
+				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: node 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, 0, SCAN_UPDATE);
+			if (ret)
+				dev_warn(ti->ph->dev,
+					 "Failed group-scan of SHMTI ID:%d\n", sid);
+		}
+	} else if (!is_group) {
+		struct telemetry_de *tde;
+
+		tde = to_tde(de);
+		if (tde->bts) {
+			/* Unlink the related BLK_TS on disable */
+			scmi_telemetry_blkts_put(ti->ph->dev, tde->bts);
+			tde->bts = NULL;
+		}
+	}
+
+	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_group *grp;
+	struct scmi_telemetry_de *de;
+	unsigned int obj_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) {
+		*tstamp_enabled_state = *tstamp;
+		return 0;
+	}
+
+	if (!is_group) {
+		de = obj;
+		obj_id = de->info->id;
+	} else {
+		grp = obj;
+		obj_id = grp->info->id;
+	}
+
+	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 |= 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 |= *tstamp ? DE_ENABLE_WTH_TSTAMP :
+				DE_ENABLE_NO_TSTAMP;
+		} else {
+			msg->flags |= *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, de, resp, is_group);
+		if (!ret) {
+			/* Update cached state on success */
+			if (enable)
+				*enabled_state = *enable;
+			if (tstamp)
+				*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;
+
+	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 = DE_DISABLE_ALL;
+	if (is_group)
+		msg->flags |= GROUP_SELECTOR;
+	ret = ph->xops->do_xfer(ph, t);
+	if (!ret) {
+		for (int i = 0; i < ti->info.base.num_des; i++)
+			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 grp_ignore,
+				    bool *enable,
+				    unsigned int *update_interval_ms,
+				    enum scmi_telemetry_collection *mode)
+{
+	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;
+	bool tlm_enable;
+	u32 interval;
+	int ret;
+
+	if (mode && *mode == SCMI_TLM_NOTIFICATION &&
+	    !ti->info.continuos_update_support)
+		return -EINVAL;
+
+	if (res_id != SCMI_TLM_GRP_INVALID && res_id >= ti->info.base.num_groups)
+		return -EINVAL;
+
+	if (res_id == SCMI_TLM_GRP_INVALID || grp_ignore) {
+		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 = *update_interval_ms;
+
+	tlm_enable = enable ? *enable : ti->info.enabled;
+	next_mode = mode ? *mode : *current_mode;
+
+	msg = t->tx.buf;
+	msg->grp_id = res_id;
+	msg->control = tlm_enable ? TELEMETRY_ENABLE : 0;
+	msg->control |= grp_ignore ? TELEMETRY_SET_SELECTOR_ALL :
+		TELEMETRY_SET_SELECTOR_GROUP;
+	msg->control |= TELEMETRY_MODE_SET(next_mode);
+	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 = interval;
+	}
+
+	ph->xops->xfer_put(ph, t);
+
+	return ret;
+}
+
+static inline void scmi_telemetry_de_data_fc_read(struct telemetry_de *tde,
+						  u64 *tstamp, u64 *val)
+{
+	struct fc_tsline __iomem *fc = tde->base + tde->offset;
+
+	*val = LINE_DATA_GET(fc);
+	if (tstamp) {
+		if (tde->de.tstamp_support)
+			*tstamp = LINE_TSTAMP_GET(fc);
+		else
+			*tstamp = 0;
+	}
+}
+
+static void scmi_telemetry_scan_update(struct telemetry_info *ti, u64 ts)
+{
+	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, ts, SCAN_LOOKUP);
+		if (ret)
+			dev_warn(ti->ph->dev,
+				 "Failed update-scan of SHMTI ID:%d\n", id);
+	}
+
+	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) {
+		u64 val, tstamp;
+
+		if (!tde->de.enabled)
+			continue;
+
+		scmi_telemetry_de_data_fc_read(tde, &tstamp, &val);
+
+		guard(mutex)(&tde->mtx);
+		tde->last_val = val;
+		if (tde->de.tstamp_enabled)
+			tde->last_ts = tstamp;
+		else
+			tde->last_ts = 0;
+	}
+}
+
+/*
+ * TDCF and TS Line Management Notes
+ * ---------------------------------
+ *  (from a chat with ATG)
+ *
+ * TCDF Payload Metadata notable bits:
+ *  - Bit[3]: USE BLK Tstamp
+ *  - Bit[2]: USE LINE Tstamp
+ *  - Bit[1]: Tstamp VALID
+ *
+ * CASE_1:
+ * -------
+ *	+ A DE is enabled with timestamp disabled, so the TS fields are
+ *	  NOT present
+ *	  -> BIT[3:1] = 000b
+ *
+ *	  - 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:1] = 011b
+ *
+ *	  - 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 clock freq)
+ *	         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 (same clock freq)
+ *	         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] = 101b
+ *
+ *	+ 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)
+ *
+ * CASE_2:
+ * -------
+ *	+ A DE is enabled with LINE timestamp enabled, so the TS_Line is there
+ *	  -> BIT[3:1] = 011b
+ *	+ 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 physiclly there but NOT valid
+ *	  -> BIT[3:1] = 010b
+ *	+ the hole from the timestamp remain there unused until
+ *		- you enable again the TS so the hole is used again
+ *		  -> BIT[3:1] = 011b
+ *			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 int scmi_telemetry_tdcf_de_parse(struct telemetry_de *tde,
+					u64 *tstamp, u64 *val)
+{
+	struct tdcf __iomem *tdcf = tde->base;
+	u64 startm, endm;
+	int retries = SCMI_TLM_TDCF_MAX_RETRIES;
+
+	if (!tdcf)
+		return -ENODEV;
+
+	do {
+		struct payload __iomem *payld;
+
+		/* 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))
+			continue;
+
+		/* Has anything changed at all at the SHMTI level ? */
+		scoped_guard(mutex, &tde->mtx) {
+			if (tde->last_magic == startm) {
+				*val = tde->last_val;
+				if (tstamp)
+					*tstamp = tde->last_ts;
+				return 0;
+			}
+		}
+
+		payld = tde->base + tde->offset;
+		if (DATA_INVALID(payld))
+			return -EINVAL;
+
+		if (IS_BLK_TS(payld))
+			return -EINVAL;
+
+		if (PAYLD_ID(payld) != tde->de.info->id)
+			return -EINVAL;
+
+		/* Data is always valid since NOT handling BLK TS lines here */
+		*val = LINE_DATA_GET(&payld->l);
+		/* Collect the right TS */
+		if (tstamp) {
+			if (!TS_VALID(payld)) {
+				*tstamp = 0;
+			} else if (USE_LINE_TS(payld)) {
+				*tstamp = LINE_TSTAMP_GET(&payld->tsl);
+			} else if (USE_BLK_TS(payld)) {
+				/*
+				 * A valid line using BLK_TS should have been
+				 * initialized with the related BLK_TS when
+				 * enabled.
+				 */
+				if (WARN_ON(!tde->bts))
+					return -EPROTO;
+				*tstamp = scmi_telemetry_blkts_read(startm,
+								    tde->bts);
+			}
+		}
+
+		endm = TDCF_END_SEQ_GET(tde->eplg);
+	} while (startm != endm && --retries);
+
+	if (startm != endm)
+		return -EPROTO;
+
+	guard(mutex)(&tde->mtx);
+	tde->last_magic = startm;
+	tde->last_val = *val;
+	if (tstamp)
+		tde->last_ts = *tstamp;
+
+	return 0;
+}
+
+static int scmi_telemetry_de_read(struct telemetry_de *tde, u64 *tstamp, u64 *val)
+{
+	if (!tde->de.fc_support)
+		return scmi_telemetry_tdcf_de_parse(tde, tstamp, val);
+
+	scmi_telemetry_de_data_fc_read(tde, tstamp, val);
+
+	return 0;
+}
+
+static int scmi_telemetry_de_collect(struct telemetry_info *ti,
+				     struct scmi_telemetry_de *de,
+				     u64 *tstamp, u64 *val)
+{
+	struct telemetry_de *tde = to_tde(de);
+
+	if (!de->enabled)
+		return -EINVAL;
+
+	/*
+	 * DE readings returns cached values when:
+	 *  - DE data value was retrieved via notification
+	 */
+	scoped_guard(mutex, &tde->mtx) {
+		if (tde->cached) {
+			*val = tde->last_val;
+			if (tstamp)
+				*tstamp = tde->last_ts;
+			return 0;
+		}
+	}
+
+	return scmi_telemetry_de_read(tde, tstamp, val);
+}
+
+static int scmi_telemetry_de_cached_read(struct telemetry_info *ti,
+					 struct scmi_telemetry_de *de,
+					 u64 *tstamp, u64 *val)
+{
+	struct telemetry_de *tde = to_tde(de);
+
+	if (!de->enabled)
+		return -EINVAL;
+
+	guard(mutex)(&tde->mtx);
+	*val = tde->last_val;
+	if (tstamp)
+		*tstamp = tde->last_ts;
+
+	return 0;
+}
+
+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 -EINVAL;
+
+	de = xa_load(&ti->xa_des, sample->id);
+	if (!de)
+		return -ENODEV;
+
+	return scmi_telemetry_de_collect(ti, de, &sample->tstamp, &sample->val);
+}
+
+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;
+		u64 val, tstamp;
+		int ret;
+
+		de = rinfo->des[i];
+		if (grp_id != SCMI_TLM_GRP_INVALID &&
+		    (!de->grp || de->grp->info->id != grp_id))
+			continue;
+
+		ret = scmi_telemetry_de_cached_read(ti, de, &tstamp, &val);
+		if (ret)
+			continue;
+
+		if (*num_samples == max_samples)
+			return -ENOSPC;
+
+		samples[*num_samples].tstamp = tstamp;
+		samples[*num_samples].val = val;
+		samples[*num_samples].id = de->info->id;
+
+		(*num_samples)++;
+	}
+
+	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, 0);
+
+	/* 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,
+				 ktime_t timestamp)
+{
+	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 += USE_LINE_TS(payld) ?
+			TS_LINE_DATA_PAYLD_WORDS : LINE_DATA_PAYLD_WORDS;
+
+		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:%d\n",
+				de_id, de ? (de->enabled ? 'Y' : 'N') : 'X');
+			continue;
+		}
+
+		tde = to_tde(de);
+		guard(mutex)(&tde->mtx);
+		tde->cached = true;
+		tde->last_val = LINE_DATA_GET(&payld->tsl);
+		/* TODO BLK_TS in notification payloads */
+		if (USE_LINE_TS(payld) && TS_VALID(payld))
+			tde->last_ts = LINE_TSTAMP_GET(&payld->tsl);
+		else
+			tde->last_ts = 0;
+	}
+}
+
+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 grp_ignore;
+	int ret;
+
+	if (!ti->info.enabled || !num_samples || !samples)
+		return -EINVAL;
+
+	grp_ignore = grp_id == SCMI_TLM_GRP_INVALID ? true : false;
+	if (!grp_ignore && 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 = grp_id;
+	msg->control = TELEMETRY_ENABLE;
+	msg->control |= grp_ignore ? TELEMETRY_SET_SELECTOR_ALL :
+		TELEMETRY_SET_SELECTOR_GROUP;
+	msg->control |= TELEMETRY_MODE_SINGLE;
+	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, r->num_dwords,
+							 r->dwords, 0);
+		/* Scan and update SMHTIs and FCs */
+		scmi_telemetry_scan_update(ti, 0);
+
+		/* 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 int scmi_telemetry_config_get(const struct scmi_protocol_handle *ph,
+				     bool *enabled, int *mode,
+				     u32 *update_interval)
+{
+	return -EOPNOTSUPP;
+}
+
+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;
+	}
+	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;
+	}
+}
+
+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);
+		/* Fetch agaon states state 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);
+
+	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,
+	.de_data_read = scmi_telemetry_de_data_read,
+	.des_bulk_read = scmi_telemetry_des_bulk_read,
+	.des_sample_get = scmi_telemetry_des_sample_get,
+	.config_get = scmi_telemetry_config_get,
+	.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(r->dwords, p->array, r->num_dwords * sizeof(u32));
+
+	*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) {
+		dev_err(ti->ph->dev, "Bad Telemetry update notification - ret: %dn",
+			er->status);
+		return NOTIFY_DONE;
+	}
+
+	/* Lookup the embedded DEs in the notification payload ... */
+	if (er->num_dwords)
+		scmi_telemetry_msg_payld_process(ti, er->num_dwords,
+						 er->dwords, er->timestamp);
+
+	/* ...scan the SHMTI/FCs for any other DE updates. */
+	if (ti->streaming_mode)
+		scmi_telemetry_scan_update(ti, er->timestamp);
+
+	return NOTIFY_OK;
+}
+
+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_tlm_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_tlm_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].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;
+
+	kfree(ti->tdes);
+	kfree(ti->rinfo->des);
+	kfree(ti->rinfo->dei_store);
+	kfree(ti->rinfo->grps);
+	kfree(ti->rinfo->grps_store);
+
+	kfree(ti->rinfo);
+}
+
+static struct scmi_telemetry_res_info *
+__scmi_telemetry_resources_get(struct telemetry_info *ti)
+{
+	return ACCESS_PRIVATE(ti, rinfo);
+}
+
+static struct scmi_telemetry_res_info *
+scmi_telemetry_resources_enumerate(struct telemetry_info *ti)
+{
+	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 enumerate DEs resources. Carry-on.\n");
+		goto done;
+	}
+
+	ret = scmi_telemetry_enumerate_groups_intervals(ti);
+	if (ret) {
+		dev_err(dev, FW_BUG "Cannot enumerate group intervals. Carry-on.\n");
+		goto done;
+	}
+
+	ti->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 ti->rinfo;
+}
+
+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;
+	u32 version;
+	int ret;
+
+	ret = ph->xops->version_get(ph, &version);
+	if (ret)
+		return ret;
+
+	dev_dbg(dev, "Telemetry Version %d.%d\n",
+		PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(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_warn(dev, FW_BUG "Cannot enumerate update intervals. Carry-on.\n");
+
+	ret = scmi_telemetry_enumerate_shmti(ti);
+	if (ret)
+		dev_warn(dev, FW_BUG "Cannot enumerate SHMTIs. Carry-on.\n");
+
+	ret = scmi_telemetry_initial_state_lookup(ti);
+	if (ret)
+		dev_warn(dev, FW_BUG "Cannot retrieve initial state. Carry-on.\n");
+
+	ti->info.base.version = version;
+
+	ret = ph->set_priv(ph, ti, version);
+	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 = {
+	.id = SCMI_PROTOCOL_TELEMETRY,
+	.owner = THIS_MODULE,
+	.instance_init = &scmi_telemetry_protocol_init,
+	.ops = &tlm_proto_ops,
+	.events = &tlm_protocol_events,
+	.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 c6efe4f371ac..d58b81ffd81e 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -2,17 +2,21 @@
 /*
  * 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>
 
+#include <uapi/linux/limits.h>
+#include <uapi/linux/scmi.h>
+
 #define SCMI_MAX_STR_SIZE		64
 #define SCMI_SHORT_NAME_MAX_SIZE	16
 #define SCMI_MAX_NUM_RATES		16
@@ -820,6 +824,178 @@ 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)				\
+	(le32_get_bits((x), GENMASK(20, 5)))
+#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
+struct scmi_telemetry_group {
+	bool enabled;
+	bool tstamp_enabled;
+	unsigned int *des;
+	char *des_str;
+	struct scmi_tlm_grp_info *info;
+	unsigned int active_update_interval;
+	struct scmi_tlm_intervals *intervals;
+	enum scmi_telemetry_collection current_mode;
+};
+
+struct scmi_telemetry_de {
+	bool tstamp_support;
+	bool fc_support;
+	bool name_support;
+	struct scmi_tlm_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_tlm_de_info *dei_store;
+	unsigned int num_groups;
+	struct scmi_telemetry_group *grps;
+	struct scmi_tlm_grp_info *grps_store;
+};
+
+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_tlm_base_info base;
+	unsigned int active_update_interval;
+	struct scmi_tlm_intervals *intervals;
+	bool enabled;
+	bool notif_enabled;
+	enum scmi_telemetry_collection current_mode;
+};
+
+struct scmi_telemetry_de_sample {
+	u32 id;
+	u64 tstamp;
+	u64 val;
+};
+
+/**
+ * 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.
+ * @state_get: retrieve the specific DE or GROUP state.
+ * @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.
+ * @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.
+ * @config_get: retrieve current telemetry configuration.
+ * @reset: reset configuration and telemetry data.
+ */
+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);
+	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 grp_ignore,
+				    bool *enable,
+				    unsigned int *update_interval_ms,
+				    enum scmi_telemetry_collection *mode);
+	int (*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);
+	int (*config_get)(const struct scmi_protocol_handle *ph, bool *enabled,
+			  int *mode, u32 *update_interval);
+	int (*reset)(const struct scmi_protocol_handle *ph);
+};
+
 /**
  * struct scmi_notify_ops  - represents notifications' operations provided by
  * SCMI core
@@ -926,6 +1102,7 @@ enum scmi_std_protocol {
 	SCMI_PROTOCOL_VOLTAGE = 0x17,
 	SCMI_PROTOCOL_POWERCAP = 0x18,
 	SCMI_PROTOCOL_PINCTRL = 0x19,
+	SCMI_PROTOCOL_TELEMETRY = 0x1b,
 	SCMI_PROTOCOL_LAST = 0x7f,
 };
 
@@ -1027,6 +1204,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 {
@@ -1114,4 +1292,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.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 06/17] include: trace: Add Telemetry trace events
  2026-01-14 11:46 [PATCH v2 00/17] Introduce SCMI Telemetry FS support Cristian Marussi
                   ` (4 preceding siblings ...)
  2026-01-14 11:46 ` [PATCH v2 05/17] firmware: arm_scmi: Add Telemetry protocol support Cristian Marussi
@ 2026-01-14 11:46 ` Cristian Marussi
  2026-01-14 11:46 ` [PATCH v2 07/17] firmware: arm_scmi: Use new Telemetry traces Cristian Marussi
                   ` (10 subsequent siblings)
  16 siblings, 0 replies; 35+ messages in thread
From: Cristian Marussi @ 2026-01-14 11:46 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty, 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.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 07/17] firmware: arm_scmi: Use new Telemetry traces
  2026-01-14 11:46 [PATCH v2 00/17] Introduce SCMI Telemetry FS support Cristian Marussi
                   ` (5 preceding siblings ...)
  2026-01-14 11:46 ` [PATCH v2 06/17] include: trace: Add Telemetry trace events Cristian Marussi
@ 2026-01-14 11:46 ` Cristian Marussi
  2026-01-23 11:43   ` Elif Topuz
  2026-01-14 11:46 ` [PATCH v2 08/17] firmware: arm_scmi: Add System Telemetry driver Cristian Marussi
                   ` (9 subsequent siblings)
  16 siblings, 1 reply; 35+ messages in thread
From: Cristian Marussi @ 2026-01-14 11:46 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty, Cristian Marussi

Track failed SHMTI accesses and received notifications.

Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
 drivers/firmware/arm_scmi/telemetry.c | 57 ++++++++++++++++++++++-----
 1 file changed, 48 insertions(+), 9 deletions(-)

diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
index 16bcdcdc1dc3..443e032a3553 100644
--- a/drivers/firmware/arm_scmi/telemetry.c
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -25,6 +25,8 @@
 #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
 
@@ -1366,8 +1368,10 @@ static void scmi_telemetry_tdcf_blkts_parse(struct telemetry_info *ti,
 
 	/* Check for spec compliance */
 	if (USE_LINE_TS(payld) || USE_BLK_TS(payld) ||
-	    DATA_INVALID(payld) || (PAYLD_ID(payld) != 0))
+	    DATA_INVALID(payld) || (PAYLD_ID(payld) != 0)) {
+		trace_scmi_tlm_access(0, "BLK_TS_INVALID", 0, 0);
 		return;
+	}
 
 	/* A BLK_TS descriptor MUST be returned: it is found or it is crated */
 	bts = scmi_telemetry_blkts_lookup(ti->ph->dev, &ti->xa_bts, payld);
@@ -1376,6 +1380,9 @@ static void scmi_telemetry_tdcf_blkts_parse(struct telemetry_info *ti,
 
 	/* Update the descriptor with the lastest TS*/
 	scmi_telemetry_blkts_update(shmti->last_magic, bts);
+
+	trace_scmi_tlm_collect(bts->last_ts, (u64)payld,
+			       bts->last_magic, "SHMTI_BLK_TS");
 }
 
 static void scmi_telemetry_tdcf_data_parse(struct telemetry_info *ti,
@@ -1393,8 +1400,10 @@ static void scmi_telemetry_tdcf_data_parse(struct telemetry_info *ti,
 	/* Is thi DE ID know ? */
 	tde = scmi_telemetry_tde_lookup(ti, de_id);
 	if (!tde) {
-		if (mode != SCAN_DISCOVERY)
+		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_get(ti, de_id);
@@ -1462,6 +1471,8 @@ static void scmi_telemetry_tdcf_data_parse(struct telemetry_info *ti,
 		tde->last_ts = tstamp;
 	else
 		tde->last_ts = 0;
+
+	trace_scmi_tlm_collect(0, tde->de.info->id, tde->last_val, "SHMTI_DE_UPDT");
 }
 
 static int scmi_telemetry_tdcf_line_parse(struct telemetry_info *ti,
@@ -1507,8 +1518,10 @@ static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
 		fsleep((SCMI_TLM_TDCF_MAX_RETRIES - retries) * 1000);
 
 		startm = TDCF_START_SEQ_GET(tdcf);
-		if (IS_BAD_START_SEQ(startm))
+		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;
@@ -1520,18 +1533,25 @@ static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
 
 			used_qwords = scmi_telemetry_tdcf_line_parse(ti, next,
 								     shmti, mode);
-			if (qwords < used_qwords)
+			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(eplg);
+		if (startm != endm)
+			trace_scmi_tlm_access(0, "MSEQ_MISMATCH", startm, endm);
 	} while (startm != endm && --retries);
 
-	if (startm != endm)
+	if (startm != endm) {
+		trace_scmi_tlm_access(0, "TDCF_SCAN_FAIL", startm, endm);
 		return -EPROTO;
+	}
 
 	return 0;
 }
@@ -1923,6 +1943,8 @@ static void scmi_telemetry_scan_update(struct telemetry_info *ti, u64 ts)
 			tde->last_ts = tstamp;
 		else
 			tde->last_ts = 0;
+
+		trace_scmi_tlm_collect(ts, tde->de.info->id, tde->last_val, "FC_UPDATE");
 	}
 }
 
@@ -2001,8 +2023,11 @@ static int scmi_telemetry_tdcf_de_parse(struct telemetry_de *tde,
 		fsleep((SCMI_TLM_TDCF_MAX_RETRIES - retries) * 1000);
 
 		startm = TDCF_START_SEQ_GET(tdcf);
-		if (IS_BAD_START_SEQ(startm))
+		if (IS_BAD_START_SEQ(startm)) {
+			trace_scmi_tlm_access(tde->de.info->id, "MSEQ_BADSTART",
+					      startm, 0);
 			continue;
+		}
 
 		/* Has anything changed at all at the SHMTI level ? */
 		scoped_guard(mutex, &tde->mtx) {
@@ -2018,11 +2043,16 @@ static int scmi_telemetry_tdcf_de_parse(struct telemetry_de *tde,
 		if (DATA_INVALID(payld))
 			return -EINVAL;
 
-		if (IS_BLK_TS(payld))
+		if (IS_BLK_TS(payld)) {
+			trace_scmi_tlm_access(tde->de.info->id,
+					      "BAD_DE_META", 0, 0);
 			return -EINVAL;
+		}
 
-		if (PAYLD_ID(payld) != tde->de.info->id)
+		if (PAYLD_ID(payld) != tde->de.info->id) {
+			trace_scmi_tlm_access(tde->de.info->id, "DE_INVALID", 0, 0);
 			return -EINVAL;
+		}
 
 		/* Data is always valid since NOT handling BLK TS lines here */
 		*val = LINE_DATA_GET(&payld->l);
@@ -2046,10 +2076,16 @@ static int scmi_telemetry_tdcf_de_parse(struct telemetry_de *tde,
 		}
 
 		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)
+	if (startm != endm) {
+		trace_scmi_tlm_access(tde->de.info->id, "TDCF_DE_FAIL",
+				      startm, endm);
 		return -EPROTO;
+	}
 
 	guard(mutex)(&tde->mtx);
 	tde->last_magic = startm;
@@ -2230,6 +2266,9 @@ scmi_telemetry_msg_payld_process(struct telemetry_info *ti,
 			tde->last_ts = LINE_TSTAMP_GET(&payld->tsl);
 		else
 			tde->last_ts = 0;
+
+		trace_scmi_tlm_collect(timestamp, tde->de.info->id, tde->last_val,
+				       "MESSAGE");
 	}
 }
 
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 08/17] firmware: arm_scmi: Add System Telemetry driver
  2026-01-14 11:46 [PATCH v2 00/17] Introduce SCMI Telemetry FS support Cristian Marussi
                   ` (6 preceding siblings ...)
  2026-01-14 11:46 ` [PATCH v2 07/17] firmware: arm_scmi: Use new Telemetry traces Cristian Marussi
@ 2026-01-14 11:46 ` Cristian Marussi
  2026-01-14 11:46 ` [PATCH v2 09/17] fs/stlmfs: Document ARM SCMI Telemetry filesystem Cristian Marussi
                   ` (8 subsequent siblings)
  16 siblings, 0 replies; 35+ messages in thread
From: Cristian Marussi @ 2026-01-14 11:46 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty, Cristian Marussi

Add a new SCMI System Telemetry driver which gathers platform Telemetry
data through the new the SCMI Telemetry protocol and expose all of the
discovered Telemetry data events on a dedicated pseudo-filesystem that
can be used to interactively configure SCMI Telemetry and access its
provided data.

Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v1 --> v2
 - Harden System Telemetry writes, DO report errors
 - New 'secs[, <exp>]' for current_interval_update_ms
 - Use new mount_api based on fs_context
 - Use new res_get() operation to make use of new accessors
 - Move des/groups enumeration to mount time
 - Support partial out-of-spec FW lacking some cmds (best effort)
 - Reworked init/exit sequence
 - Using dev_err_probe
 - Reworked probing races handling
 - Avoid disabling telemetry on module removal and drop remove() code
---
 drivers/firmware/arm_scmi/Kconfig             |   10 +
 drivers/firmware/arm_scmi/Makefile            |    1 +
 .../firmware/arm_scmi/scmi_system_telemetry.c | 1436 +++++++++++++++++
 3 files changed, 1447 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 e3fb36825978..9e51b3cd0c93 100644
--- a/drivers/firmware/arm_scmi/Kconfig
+++ b/drivers/firmware/arm_scmi/Kconfig
@@ -99,4 +99,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..b48f2d4eecae
--- /dev/null
+++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
@@ -0,0 +1,1436 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SCMI - System Telemetry Driver
+ *
+ * Copyright (C) 2026 ARM Ltd.
+ */
+
+#include <linux/atomic.h>
+#include <linux/bitfield.h>
+#include <linux/ctype.h>
+#include <linux/dcache.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/fs_context.h>
+#include <linux/fs_parser.h>
+#include <linux/kstrtox.h>
+#include <linux/list.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/uaccess.h>
+
+#define TLM_FS_MAGIC		0x75C01C80
+#define TLM_FS_NAME		"stlmfs"
+#define TLM_FS_MNT		"arm_telemetry"
+
+#define MAX_AVAILABLE_INTERV_CHAR_LENGTH	25
+#define MAX_BULK_LINE_CHAR_LENGTH		64
+
+static struct kmem_cache *stlmfs_inode_cachep;
+
+static DEFINE_MUTEX(stlmfs_mtx);
+static struct super_block *stlmfs_sb;
+
+static atomic_t scmi_tlm_instance_count = ATOMIC_INIT(0);
+
+struct scmi_tlm_setup;
+
+struct scmi_tlm_priv {
+	char *buf;
+	size_t buf_sz;
+	int buf_len;
+	int (*bulk_retrieve)(struct scmi_tlm_setup *tsp,
+			     int res_id, int *num_samples,
+			     struct scmi_telemetry_de_sample *samples);
+};
+
+/**
+ * struct scmi_tlm_buffer  - Output Telemetry buffer descriptor
+ * @used: Current number of used bytes in @buf
+ * @buf: Actual buffer for output data
+ *
+ * This describes an output buffer which will be made available to each r/w
+ * entry file_operations.
+ */
+struct scmi_tlm_buffer {
+	size_t used;
+#define SCMI_TLM_MAX_BUF_SZ	128
+	unsigned char buf[SCMI_TLM_MAX_BUF_SZ];
+};
+
+/**
+ * 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 __private *rinfo;
+	const struct scmi_telemetry_proto_ops *ops;
+};
+
+/**
+ * struct scmi_tlm_class  - Telemetry class descriptor
+ * @name: A string to be used for filesystem dentry name.
+ * @mode: Filesystem mode mask.
+ * @flags: Optional misc flags that can slighly modify provided @f_op behaviour;
+ *	   this way the same @scmi_tlm_class can be used to describe multiple
+ *	   entries in the filesystem whose @f_op behaviour is very similar.
+ * @f_op: Optional file ops attached to this object. Used to initialized inodes.
+ * @i_op: Optional inode ops attached to this object. Used to initialize inodes.
+ *
+ * This structure describes a class of telemetry entities that will be
+ * associated with filesystem inodes having the same behaviour, i.e. the same
+ * @f_op and @i_op: this way it will be possible to statically define a set of
+ * common descriptors to describe all the possible behaviours and then link it
+ * to the effective inodes that will be created to support the set of DEs
+ * effectively discovered at run-time via SCMI.
+ */
+struct scmi_tlm_class {
+	const char *name;
+	umode_t mode;
+	int flags;
+#define	TLM_IS_STATE	BIT(0)
+#define	TLM_IS_GROUP	BIT(1)
+#define	TLM_IS_DYNAMIC	BIT(2)
+#define IS_STATE(_f)	((_f) & TLM_IS_STATE)
+#define IS_GROUP(_f)	((_f) & TLM_IS_GROUP)
+#define IS_DYNAMIC(_f)	((_f) & TLM_IS_DYNAMIC)
+	const struct file_operations *f_op;
+	const struct inode_operations *i_op;
+};
+
+#define TLM_ANON_CLASS(_n, _f, _m, _fo, _io)	\
+	{					\
+		.name = _n,			\
+		.flags = _f,			\
+		.f_op = _fo,			\
+		.i_op = _io,			\
+		.mode = _m,			\
+	}
+
+#define DEFINE_TLM_CLASS(_tag, _ns, _fl, _mo, _fop, _iop)	\
+	static const struct scmi_tlm_class _tag =		\
+		TLM_ANON_CLASS(_ns, _fl, _mo, _fop, _iop)
+
+/**
+ * struct scmi_tlm_inode  - Telemetry node descriptor
+ * @tsp: A reference to a structure holding data needed to interact with
+ *	 the SCMI instance associated to this inode.
+ * @cls: A reference to the @scmi_tlm_class describing the behaviour of this
+ *	 inode.
+ * @priv: Generic private data reference.
+ * @de: SCMI DE data reference.
+ * @grp: SCMI Group data reference.
+ * @info: SCMI instance information data reference.
+ * @vfs_inode: The embedded VFS inode that will be initialized and plugged
+ *	       into the live filesystem at mount time.
+ *
+ * This structure is used to describe each SCMI Telemetry entity discovered
+ * at probe time, store its related SCMI data, and link to the proper
+ * telemetry class @scmi_tlm_class.
+ */
+struct scmi_tlm_inode {
+	struct scmi_tlm_setup *tsp;
+	const struct scmi_tlm_class *cls;
+	union {
+		const void *priv;
+		const struct scmi_telemetry_de *de;
+		const struct scmi_telemetry_group *grp;
+		const struct scmi_telemetry_info *info;
+	};
+	struct inode vfs_inode;
+};
+
+#define to_tlm_inode(t)	container_of(t, struct scmi_tlm_inode, vfs_inode)
+
+#define	MAX_INST_NAME		32
+
+#define TOP_NODES_NUM		32
+#define NODES_PER_DE_NUM	12
+#define NODES_PER_GRP_NUM	 9
+
+/**
+ * struct scmi_tlm_instance  - Telemetry instance descriptor
+ * @id: Progressive number identifying this probed instance; it will be used
+ *	to name the top node at the root of this instance.
+ * @res_enumerated: A flag to indicate if full resources enumeration has been
+ *		    successfully performed.
+ * @name: Name to be used for the top root node of the instance. (tlm_<id>)
+ * @node: A node to link this in the list of all instances.
+ * @sb: A reference to the current super_block.
+ * @tsp: A reference to the SCMI instance data.
+ * @top_cls: A class to represent the top node behaviour.
+ * @top_dentry: A reference to the top dentry for this instance.
+ * @des_dentry: A reference to the DES dentry for this instance.
+ * @grps_dentry: A reference to the groups dentry for this instance.
+ * @info: A handy reference to this instance SCMI Telemetry info data.
+ *
+ */
+struct scmi_tlm_instance {
+	int id;
+	bool res_enumerated;
+	char name[MAX_INST_NAME];
+	struct list_head node;
+	struct super_block *sb;
+	struct scmi_tlm_setup *tsp;
+	struct scmi_tlm_class top_cls;
+	struct dentry *top_dentry;
+	struct dentry *des_dentry;
+	struct dentry *grps_dentry;
+	const struct scmi_telemetry_info *info;
+};
+
+static int scmi_telemetry_instance_register(struct super_block *sb,
+					    struct scmi_tlm_instance *ti);
+
+static LIST_HEAD(scmi_telemetry_instances);
+
+static struct inode *stlmfs_get_inode(struct super_block *sb)
+{
+	struct inode *inode = new_inode(sb);
+
+	if (inode) {
+		inode->i_ino = get_next_ino();
+		simple_inode_init_ts(inode);
+	}
+
+	return inode;
+}
+
+static int stlmfs_failed_creating(struct dentry *dentry)
+{
+	simple_done_creating(dentry);
+
+	return -ENOMEM;
+}
+
+static struct dentry *
+stlmfs_create_dentry(struct super_block *sb, struct scmi_tlm_setup *tsp,
+		     struct dentry *parent, const struct scmi_tlm_class *cls,
+		     const void *priv)
+{
+	struct scmi_tlm_inode *tlmi;
+	struct dentry *dentry;
+	struct inode *inode;
+
+	if (!parent)
+		parent = sb->s_root;
+
+	if (IS_ERR(parent))
+		return parent;
+
+	dentry = simple_start_creating(parent, cls->name);
+	if (IS_ERR(dentry))
+		return dentry;
+
+	inode = stlmfs_get_inode(sb);
+	if (unlikely(!inode)) {
+		dev_err(tsp->dev,
+			"out of free dentries, cannot create '%s'",
+			cls->name);
+		return ERR_PTR(stlmfs_failed_creating(dentry));
+	}
+
+	if (S_ISDIR(cls->mode)) {
+		inode->i_op = cls->i_op ?: &simple_dir_inode_operations;
+		inode->i_fop = cls->f_op ?: &simple_dir_operations;
+	} else {
+		inode->i_op = cls->i_op ?: &simple_dir_inode_operations;
+		inode->i_fop = cls->f_op;
+	}
+
+	inode->i_mode = cls->mode;
+	inode_init_owner(&nop_mnt_idmap, inode, NULL, inode->i_mode);
+	inode->i_private = (void *)priv;
+
+	tlmi = to_tlm_inode(inode);
+
+	tlmi->cls = cls;
+	tlmi->tsp = tsp;
+	tlmi->priv = priv;
+
+	d_make_persistent(dentry, inode);
+
+	simple_done_creating(dentry);
+
+	return dentry;
+}
+
+static inline int
+__scmi_tlm_generic_open(struct inode *ino, struct file *filp,
+			int (*bulk_op)(struct scmi_tlm_setup *tsp,
+				       int res_id, int *num_samples,
+				       struct scmi_telemetry_de_sample *samples))
+{
+	struct scmi_tlm_priv *tp;
+
+	tp = kzalloc(sizeof(*tp), GFP_KERNEL);
+	if (!tp)
+		return -ENOMEM;
+
+	tp->bulk_retrieve = bulk_op;
+
+	filp->private_data = tp;
+
+	return nonseekable_open(ino, filp);
+}
+
+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 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 ACCESS_PRIVATE(tsp, rinfo);
+
+	rinfo = tsp->ops->res_get(tsp->ph);
+	/* Cache the retrieved resource info value */
+	smp_store_mb(tsp->rinfo, rinfo);
+
+	return rinfo;
+}
+
+static ssize_t scmi_tlm_all_des_write(struct file *filp,
+				      const char __user *buf,
+				      size_t count, loff_t *ppos)
+{
+	struct scmi_tlm_inode *tlmi = to_tlm_inode(file_inode(filp));
+	struct scmi_tlm_setup *tsp = tlmi->tsp;
+	const struct scmi_tlm_class *cls = tlmi->cls;
+	bool enable;
+	int ret;
+
+	ret = kstrtobool_from_user(buf, count, &enable);
+	if (ret)
+		return ret;
+
+	/* When !IS_STATE imply that is a tstamp_enable operation */
+	if (IS_STATE(cls->flags) && !enable) {
+		ret = tsp->ops->all_disable(tsp->ph, false);
+		if (ret)
+			return ret;
+	} else {
+		const struct scmi_telemetry_res_info *rinfo;
+
+		rinfo = scmi_telemetry_res_info_get(tsp);
+		if (!rinfo)
+			return -ENODEV;
+
+		for (int i = 0; i < rinfo->num_des; i++) {
+			ret = tsp->ops->state_set(tsp->ph, false,
+						  rinfo->des[i]->info->id,
+						  IS_STATE(cls->flags) ? &enable : NULL,
+						  !IS_STATE(cls->flags) ? &enable : NULL);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return count;
+}
+
+static const struct file_operations all_des_fops = {
+	.open = nonseekable_open,
+	.write = scmi_tlm_all_des_write,
+};
+
+static ssize_t scmi_tlm_obj_enable_write(struct file *filp,
+					 const char __user *buf,
+					 size_t count, loff_t *ppos)
+{
+	struct scmi_tlm_inode *tlmi = to_tlm_inode(file_inode(filp));
+	struct scmi_tlm_setup *tsp = tlmi->tsp;
+	const struct scmi_tlm_class *cls = tlmi->cls;
+	bool enabled, is_group = IS_GROUP(cls->flags);
+	int ret, res_id;
+
+	ret = kstrtobool_from_user(buf, count, &enabled);
+	if (ret)
+		return ret;
+
+	res_id = !is_group ? tlmi->de->info->id : tlmi->grp->info->id;
+	ret = tsp->ops->state_set(tsp->ph, is_group, res_id,
+				  IS_STATE(cls->flags) ? &enabled : NULL,
+				  !IS_STATE(cls->flags) ? &enabled : NULL);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static ssize_t scmi_tlm_obj_enable_read(struct file *filp, char __user *buf,
+					size_t count, loff_t *ppos)
+{
+	struct scmi_tlm_inode *tlmi = to_tlm_inode(file_inode(filp));
+	const bool *enabled_state, *tstamp_enabled_state;
+	char o_buf[2];
+	bool enabled;
+
+	if (!IS_GROUP(tlmi->cls->flags)) {
+		enabled_state = &tlmi->de->enabled;
+		tstamp_enabled_state = &tlmi->de->tstamp_enabled;
+	} else {
+		enabled_state = &tlmi->grp->enabled;
+		tstamp_enabled_state = &tlmi->grp->tstamp_enabled;
+	}
+
+	enabled = IS_STATE(tlmi->cls->flags) ? *enabled_state : *tstamp_enabled_state;
+	o_buf[0] = enabled ? 'Y' : 'N';
+	o_buf[1] = '\n';
+
+	return simple_read_from_buffer(buf, count, ppos, o_buf, 2);
+}
+
+static const struct file_operations obj_enable_fops = {
+	.open = nonseekable_open,
+	.write = scmi_tlm_obj_enable_write,
+	.read = scmi_tlm_obj_enable_read,
+};
+
+static int scmi_tlm_open(struct inode *ino, struct file *filp)
+{
+	struct scmi_tlm_buffer *data;
+
+	/* Allocate some per-open buffer */
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	filp->private_data = data;
+
+	return nonseekable_open(ino, filp);
+}
+
+static int scmi_tlm_release(struct inode *ino, struct file *filp)
+{
+	kfree(filp->private_data);
+
+	return 0;
+}
+
+static ssize_t
+scmi_tlm_update_interval_read(struct file *filp, char __user *buf,
+			      size_t count, loff_t *ppos)
+{
+	struct scmi_tlm_inode *tlmi = to_tlm_inode(file_inode(filp));
+	struct scmi_tlm_buffer *data = filp->private_data;
+	unsigned int active_update_interval;
+
+	if (!data)
+		return 0;
+
+	if (!IS_GROUP(tlmi->cls->flags))
+		active_update_interval = tlmi->info->active_update_interval;
+	else
+		active_update_interval = tlmi->grp->active_update_interval;
+
+	if (!data->used)
+		data->used =
+			scnprintf(data->buf, SCMI_TLM_MAX_BUF_SZ, "%u, %d\n",
+				  SCMI_TLM_GET_UPDATE_INTERVAL_SECS(active_update_interval),
+				  SCMI_TLM_GET_UPDATE_INTERVAL_EXP(active_update_interval));
+
+	return simple_read_from_buffer(buf, count, ppos, data->buf, data->used);
+}
+
+static ssize_t
+scmi_tlm_update_interval_write(struct file *filp, const char __user *buf,
+			       size_t count, loff_t *ppos)
+{
+	struct scmi_tlm_inode *tlmi = to_tlm_inode(file_inode(filp));
+	struct scmi_tlm_setup *tsp = tlmi->tsp;
+	struct scmi_tlm_buffer *data = filp->private_data;
+	bool is_group = IS_GROUP(tlmi->cls->flags);
+	unsigned int update_interval_ms = 0, secs = 0;
+	int ret, grp_id, exp = -3;
+	char *p, *token;
+
+	if (count >= SCMI_TLM_MAX_BUF_SZ)
+		return -ENOSPC;
+
+	if (copy_from_user(data->buf, buf, count))
+		return -EFAULT;
+
+	p = data->buf;
+	token = strsep(&p, ",");
+	if (!token || iscntrl(token[0]))
+		return -EINVAL;
+
+	ret = kstrtouint(strim(token), 0, &secs);
+	if (ret)
+		return ret;
+
+	if (p) {
+		token = p;
+		if (!token || iscntrl(token[0]))
+			return -EINVAL;
+
+		ret = kstrtoint(strim(token), 0, &exp);
+		if (ret)
+			return ret;
+	}
+
+	update_interval_ms = SCMI_TLM_BUILD_UPDATE_INTERVAL(secs, exp);
+
+	grp_id = !is_group ? SCMI_TLM_GRP_INVALID : tlmi->grp->info->id;
+	ret = tsp->ops->collection_configure(tsp->ph, grp_id, !is_group, NULL,
+					     &update_interval_ms, NULL);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static const struct file_operations current_interval_fops = {
+	.open = scmi_tlm_open,
+	.read = scmi_tlm_update_interval_read,
+	.write = scmi_tlm_update_interval_write,
+	.release = scmi_tlm_release,
+};
+
+static ssize_t scmi_tlm_de_read(struct file *filp, char __user *buf,
+				size_t count, loff_t *ppos)
+{
+	struct scmi_tlm_inode *tlmi = to_tlm_inode(file_inode(filp));
+	struct scmi_tlm_setup *tsp = tlmi->tsp;
+	struct scmi_tlm_buffer *data = filp->private_data;
+	int ret;
+
+	if (!data)
+		return 0;
+
+	if (!data->used) {
+		struct scmi_telemetry_de_sample sample;
+
+		sample.id = tlmi->de->info->id;
+		ret = tsp->ops->de_data_read(tsp->ph, &sample);
+		if (ret)
+			return ret;
+
+		data->used = scnprintf(data->buf, SCMI_TLM_MAX_BUF_SZ,
+				       "%llu: %016llX\n", sample.tstamp,
+				       sample.val);
+	}
+
+	return simple_read_from_buffer(buf, count, ppos, data->buf, data->used);
+}
+
+static const struct file_operations de_read_fops = {
+	.open = scmi_tlm_open,
+	.read = scmi_tlm_de_read,
+	.release = scmi_tlm_release,
+};
+
+static ssize_t
+scmi_tlm_enable_read(struct file *filp, char __user *buf, size_t count,
+		     loff_t *ppos)
+{
+	struct scmi_tlm_inode *tlmi = to_tlm_inode(file_inode(filp));
+	char o_buf[2];
+
+	o_buf[0] = tlmi->info->enabled ? 'Y' : 'N';
+	o_buf[1] = '\n';
+
+	return simple_read_from_buffer(buf, count, ppos, o_buf, 2);
+}
+
+static ssize_t
+scmi_tlm_enable_write(struct file *filp, const char __user *buf, size_t count,
+		      loff_t *ppos)
+{
+	struct scmi_tlm_inode *tlmi = to_tlm_inode(file_inode(filp));
+	enum scmi_telemetry_collection mode = SCMI_TLM_ONDEMAND;
+	struct scmi_tlm_setup *tsp = tlmi->tsp;
+	bool enabled;
+	int ret;
+
+	ret = kstrtobool_from_user(buf, count, &enabled);
+	if (ret)
+		return ret;
+
+	ret = tsp->ops->collection_configure(tsp->ph, SCMI_TLM_GRP_INVALID, true,
+					     &enabled, NULL, &mode);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static const struct file_operations tlm_enable_fops = {
+	.open = nonseekable_open,
+	.read = scmi_tlm_enable_read,
+	.write = scmi_tlm_enable_write,
+};
+
+static ssize_t
+scmi_tlm_intrv_discrete_read(struct file *filp, char __user *buf,
+			     size_t count, loff_t *ppos)
+{
+	struct scmi_tlm_inode *tlmi = to_tlm_inode(file_inode(filp));
+	bool discrete;
+	char o_buf[2];
+
+	discrete = !IS_GROUP(tlmi->cls->flags) ?
+		tlmi->info->intervals->discrete : tlmi->grp->intervals->discrete;
+
+	o_buf[0] = discrete ? 'Y' : 'N';
+	o_buf[1] = '\n';
+
+	return simple_read_from_buffer(buf, count, ppos, o_buf, 2);
+}
+
+static const struct file_operations intrv_discrete_fops = {
+	.open = nonseekable_open,
+	.read = scmi_tlm_intrv_discrete_read,
+};
+
+static ssize_t
+scmi_tlm_reset_write(struct file *filp, const char __user *buf, size_t count,
+		     loff_t *ppos)
+{
+	struct scmi_tlm_inode *tlmi = to_tlm_inode(file_inode(filp));
+	int ret;
+
+	ret = tlmi->tsp->ops->reset(tlmi->tsp->ph);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static const struct file_operations reset_fops = {
+	.open = nonseekable_open,
+	.write = scmi_tlm_reset_write,
+};
+
+static int sa_u32_get(void *data, u64 *val)
+{
+	*val = *(u32 *)data;
+	return 0;
+}
+
+static int sa_u32_set(void *data, u64 val)
+{
+	*(u32 *)data = val;
+	return 0;
+}
+
+static int sa_u32_open(struct inode *ino, struct file *filp)
+{
+	return simple_attr_open(ino, filp, sa_u32_get, sa_u32_set, "%u\n");
+}
+
+static int sa_s32_open(struct inode *ino, struct file *filp)
+{
+	return simple_attr_open(ino, filp, sa_u32_get, sa_u32_set, "%d\n");
+}
+
+static int sa_x32_open(struct inode *ino, struct file *filp)
+{
+	return simple_attr_open(ino, filp, sa_u32_get, sa_u32_set, "0x%X\n");
+}
+
+static const struct file_operations sa_x32_ro_fops = {
+	.open = sa_x32_open,
+	.read = simple_attr_read,
+	.release = simple_attr_release,
+};
+
+static const struct file_operations sa_u32_ro_fops = {
+	.open = sa_u32_open,
+	.read = simple_attr_read,
+	.release = simple_attr_release,
+};
+
+static const struct file_operations sa_s32_ro_fops = {
+	.open = sa_s32_open,
+	.read = simple_attr_read,
+	.release = simple_attr_release,
+};
+
+static ssize_t
+scmi_de_impl_version_read(struct file *filp, char __user *buf, size_t count,
+			  loff_t *ppos)
+{
+	struct scmi_tlm_inode *tlmi = to_tlm_inode(file_inode(filp));
+	struct scmi_tlm_buffer *data = filp->private_data;
+
+	if (!data)
+		return 0;
+
+	if (!data->used)
+		data->used = scnprintf(data->buf, SCMI_TLM_MAX_BUF_SZ,
+				       "%pUL\n", tlmi->info->base.de_impl_version);
+
+	return simple_read_from_buffer(buf, count, ppos, data->buf, data->used);
+}
+
+static const struct file_operations de_impl_vers_fops = {
+	.open = scmi_tlm_open,
+	.read = scmi_de_impl_version_read,
+	.release = scmi_tlm_release,
+};
+
+static ssize_t scmi_string_read(struct file *filp, char __user *buf,
+				size_t count, loff_t *ppos)
+{
+	struct scmi_tlm_priv *tp = filp->private_data;
+
+	/*
+	 * Note that tp->buf is a scratch buffer, filled once, used to support
+	 * multiple chunked read and freed in scmi_tlm_priv_release.
+	 */
+	if (!tp->buf) {
+		struct scmi_tlm_inode *tlmi = to_tlm_inode(file_inode(filp));
+		const char *str = tlmi->priv;
+
+		tp->buf = kasprintf(GFP_KERNEL, "%s\n", str);
+		if (!tp->buf)
+			return -ENOMEM;
+
+		tp->buf_len = strlen(tp->buf) + 1;
+	}
+
+	return simple_read_from_buffer(buf, count, ppos, tp->buf, tp->buf_len);
+}
+
+static int scmi_tlm_priv_open(struct inode *ino, struct file *filp)
+{
+	return __scmi_tlm_generic_open(ino, filp, NULL);
+}
+
+static const struct file_operations string_ro_fops = {
+	.open = scmi_tlm_priv_open,
+	.read = scmi_string_read,
+	.release = scmi_tlm_priv_release,
+};
+
+static ssize_t scmi_available_interv_read(struct file *filp, char __user *buf,
+					  size_t count, loff_t *ppos)
+{
+	struct scmi_tlm_priv *tp = filp->private_data;
+
+	/*
+	 * Note that tp->buf is a scratch buffer, filled once, used to support
+	 * multiple chunked read and freed in scmi_tlm_priv_release.
+	 */
+	if (!tp->buf) {
+		struct scmi_tlm_inode *tlmi = to_tlm_inode(file_inode(filp));
+		struct scmi_tlm_intervals *intervals;
+		int len = 0;
+
+		intervals = !IS_GROUP(tlmi->cls->flags) ?
+			tlmi->info->intervals : tlmi->grp->intervals;
+		tp->buf_len = intervals->num * MAX_AVAILABLE_INTERV_CHAR_LENGTH;
+		tp->buf = kzalloc(tp->buf_len, GFP_KERNEL);
+		if (!tp->buf)
+			return -ENOMEM;
+
+		for (int i = 0; i < intervals->num; i++) {
+			u32 ivl;
+
+			ivl = intervals->update_intervals[i];
+			len += scnprintf(tp->buf + len, tp->buf_len - len,
+					 "%u,%d ",
+					 SCMI_TLM_GET_UPDATE_INTERVAL_SECS(ivl),
+					 SCMI_TLM_GET_UPDATE_INTERVAL_EXP(ivl));
+		}
+		tp->buf[len - 1] = '\n';
+	}
+
+	return simple_read_from_buffer(buf, count, ppos, tp->buf, tp->buf_len);
+}
+
+static const struct file_operations available_interv_fops = {
+	.open = scmi_tlm_priv_open,
+	.read = scmi_available_interv_read,
+	.release = scmi_tlm_priv_release,
+};
+
+static const struct scmi_tlm_class tlm_tops[] = {
+	TLM_ANON_CLASS("all_des_enable", TLM_IS_STATE,
+		       S_IFREG | S_IWUSR, &all_des_fops, NULL),
+	TLM_ANON_CLASS("all_des_tstamp_enable", 0,
+		       S_IFREG | S_IWUSR, &all_des_fops, NULL),
+	TLM_ANON_CLASS("current_update_interval_ms", 0,
+		       S_IFREG | S_IRUSR | S_IWUSR, &current_interval_fops, NULL),
+	TLM_ANON_CLASS("intervals_discrete", 0,
+		       S_IFREG | S_IRUSR, &intrv_discrete_fops, NULL),
+	TLM_ANON_CLASS("available_update_intervals_ms", 0,
+		       S_IFREG | S_IRUSR, &available_interv_fops, NULL),
+	TLM_ANON_CLASS("de_implementation_version", 0,
+		       S_IFREG | S_IRUSR, &de_impl_vers_fops, NULL),
+	TLM_ANON_CLASS("tlm_enable", 0,
+		       S_IFREG | S_IRUSR | S_IWUSR, &tlm_enable_fops, NULL),
+	TLM_ANON_CLASS(NULL, 0, 0, NULL, NULL),
+};
+
+DEFINE_TLM_CLASS(reset_tlmo, "reset", 0, S_IFREG | S_IWUSR, &reset_fops, NULL);
+
+DEFINE_TLM_CLASS(des_dir_cls, "des", 0,
+		 S_IFDIR | S_IRWXU, NULL, NULL);
+DEFINE_TLM_CLASS(name_tlmo, "name", 0,
+		 S_IFREG | S_IRUSR, &string_ro_fops, NULL);
+DEFINE_TLM_CLASS(ena_tlmo, "enable", TLM_IS_STATE,
+		 S_IFREG | S_IRUSR | S_IWUSR, &obj_enable_fops, NULL);
+DEFINE_TLM_CLASS(tstamp_ena_tlmo, "tstamp_enable", 0,
+		 S_IFREG | S_IRUSR | S_IWUSR, &obj_enable_fops, NULL);
+DEFINE_TLM_CLASS(type_tlmo, "type", 0,
+		 S_IFREG | S_IRUSR, &sa_u32_ro_fops, NULL);
+DEFINE_TLM_CLASS(unit_tlmo, "unit", 0,
+		 S_IFREG | S_IRUSR, &sa_u32_ro_fops, NULL);
+DEFINE_TLM_CLASS(unit_exp_tlmo, "unit_exp", 0,
+		 S_IFREG | S_IRUSR, &sa_s32_ro_fops, NULL);
+DEFINE_TLM_CLASS(instance_id_tlmo, "instance_id", 0,
+		 S_IFREG | S_IRUSR, &sa_u32_ro_fops, NULL);
+DEFINE_TLM_CLASS(compo_type_tlmo, "compo_type", 0,
+		 S_IFREG | S_IRUSR, &sa_u32_ro_fops, NULL);
+DEFINE_TLM_CLASS(compo_inst_id_tlmo, "compo_instance_id", 0,
+		 S_IFREG | S_IRUSR, &sa_u32_ro_fops, NULL);
+DEFINE_TLM_CLASS(tstamp_exp_tlmo, "tstamp_exp", 0,
+		 S_IFREG | S_IRUSR, &sa_s32_ro_fops, NULL);
+DEFINE_TLM_CLASS(persistent_tlmo, "persistent", 0,
+		 S_IFREG | S_IRUSR, &sa_u32_ro_fops, NULL);
+DEFINE_TLM_CLASS(value_tlmo, "value", 0,
+		 S_IFREG | S_IRUSR, &de_read_fops, NULL);
+
+static int scmi_telemetry_de_populate(struct super_block *sb,
+				      struct scmi_tlm_setup *tsp,
+				      struct dentry *parent,
+				      const struct scmi_telemetry_de *de,
+				      bool fully_enumerated)
+{
+	struct scmi_tlm_de_info *dei = de->info;
+
+	stlmfs_create_dentry(sb, tsp, parent, &ena_tlmo, de);
+	stlmfs_create_dentry(sb, tsp, parent, &value_tlmo, de);
+	if (!fully_enumerated)
+		return 0;
+
+	if (de->name_support)
+		stlmfs_create_dentry(sb, tsp, parent, &name_tlmo, dei->name);
+
+	if (de->tstamp_support) {
+		stlmfs_create_dentry(sb, tsp, parent, &tstamp_ena_tlmo, de);
+		stlmfs_create_dentry(sb, tsp, parent, &tstamp_exp_tlmo,
+				     &dei->tstamp_exp);
+	}
+
+	stlmfs_create_dentry(sb, tsp, parent, &type_tlmo, &dei->type);
+	stlmfs_create_dentry(sb, tsp, parent, &unit_tlmo, &dei->unit);
+	stlmfs_create_dentry(sb, tsp, parent, &unit_exp_tlmo, &dei->unit_exp);
+	stlmfs_create_dentry(sb, tsp, parent, &instance_id_tlmo, &dei->instance_id);
+	stlmfs_create_dentry(sb, tsp, parent, &compo_type_tlmo, &dei->compo_type);
+	stlmfs_create_dentry(sb, tsp, parent, &compo_inst_id_tlmo,
+			     &dei->compo_instance_id);
+	stlmfs_create_dentry(sb, tsp, parent, &persistent_tlmo, &dei->persistent);
+
+	return 0;
+}
+
+static int
+scmi_telemetry_des_lazy_enumerate(struct scmi_tlm_instance *ti,
+				  const struct scmi_telemetry_res_info *rinfo)
+{
+	struct scmi_tlm_setup *tsp = ti->tsp;
+	struct super_block *sb = ti->sb;
+
+	for (int i = 0; i < rinfo->num_des; i++) {
+		const struct scmi_telemetry_de *de = rinfo->des[i];
+		struct dentry *de_dir_dentry;
+		int ret;
+
+		struct scmi_tlm_class *de_tlm_cls __free(kfree) =
+			kzalloc(sizeof(*de_tlm_cls), GFP_KERNEL);
+		if (!de_tlm_cls)
+			return -ENOMEM;
+
+		de_tlm_cls->name = kasprintf(GFP_KERNEL, "0x%08X", de->info->id);
+		if (!de_tlm_cls->name)
+			return -ENOMEM;
+
+		de_tlm_cls->mode = S_IFDIR | S_IRWXU;
+		de_tlm_cls->flags = TLM_IS_DYNAMIC;
+		de_dir_dentry = stlmfs_create_dentry(sb, tsp, ti->des_dentry,
+						     de_tlm_cls, de);
+
+		ret = scmi_telemetry_de_populate(sb, tsp, de_dir_dentry, de,
+						 rinfo->fully_enumerated);
+		if (ret)
+			return ret;
+
+		retain_and_null_ptr(de_tlm_cls);
+	}
+
+	ti->res_enumerated = true;
+
+	dev_info(tsp->dev, "Found %d Telemetry DE resources.\n", rinfo->num_des);
+
+	return 0;
+}
+
+static int scmi_telemetry_des_initialize(struct scmi_tlm_instance *ti)
+{
+	const struct scmi_telemetry_res_info *rinfo;
+
+	rinfo = scmi_telemetry_res_info_get(ti->tsp);
+	if (!rinfo)
+		return -ENODEV;
+
+	return scmi_telemetry_des_lazy_enumerate(ti, rinfo);
+}
+
+DEFINE_TLM_CLASS(version_tlmo, "version", 0,
+		 S_IFREG | S_IRUSR, &sa_x32_ro_fops, NULL);
+
+static int scmi_tlm_bulk_on_demand(struct scmi_tlm_setup *tsp,
+				   int res_id, int *num_samples,
+				   struct scmi_telemetry_de_sample *samples)
+{
+	return tsp->ops->des_bulk_read(tsp->ph, res_id, num_samples, samples);
+}
+
+static int scmi_tlm_data_open(struct inode *ino, struct file *filp)
+{
+	return __scmi_tlm_generic_open(ino, filp, scmi_tlm_bulk_on_demand);
+}
+
+static int scmi_tlm_buffer_fill(struct device *dev, char *buf, size_t size,
+				int *len, int num,
+				struct scmi_telemetry_de_sample *samples)
+{
+	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_allocate_and_fill(struct scmi_tlm_inode *tlmi,
+						  struct scmi_tlm_priv *tp)
+{
+	struct scmi_tlm_setup *tsp = tlmi->tsp;
+	const struct scmi_tlm_class *cls = tlmi->cls;
+	struct scmi_telemetry_de_sample *samples;
+	bool is_group = IS_GROUP(cls->flags);
+	int ret, num_samples, res_id;
+
+	num_samples = !is_group ? tlmi->info->base.num_des :
+		tlmi->grp->info->num_des;
+	tp->buf_sz = num_samples * MAX_BULK_LINE_CHAR_LENGTH;
+	tp->buf = kzalloc(tp->buf_sz, GFP_KERNEL);
+	if (!tp->buf)
+		return -ENOMEM;
+
+	res_id = is_group ? tlmi->grp->info->id : SCMI_TLM_GRP_INVALID;
+	samples = kcalloc(num_samples, sizeof(*samples), GFP_KERNEL);
+	if (!samples) {
+		kfree(tp->buf);
+		return -ENOMEM;
+	}
+
+	ret = tp->bulk_retrieve(tsp, res_id, &num_samples, samples);
+	if (ret) {
+		kfree(tp->buf);
+		kfree(samples);
+		return ret;
+	}
+
+	/*
+	 * Note that tp->buf is a scratch buffer, filled once, used to support
+	 * multiple chunked read and freed in scmi_tlm_priv_release.
+	 */
+	ret = scmi_tlm_buffer_fill(tsp->dev, tp->buf, tp->buf_sz, &tp->buf_len,
+				   num_samples, samples);
+	kfree(samples);
+
+	return ret;
+}
+
+static ssize_t scmi_tlm_generic_data_read(struct file *filp, char __user *buf,
+					  size_t count, loff_t *ppos)
+{
+	struct scmi_tlm_inode *tlmi = to_tlm_inode(file_inode(filp));
+	struct scmi_tlm_priv *tp = filp->private_data;
+	int ret;
+
+	if (!tp->buf) {
+		ret = scmi_tlm_bulk_buffer_allocate_and_fill(tlmi, tp);
+		if (ret)
+			return ret;
+	}
+
+	return simple_read_from_buffer(buf, count, ppos, tp->buf, tp->buf_len);
+}
+
+static const struct file_operations scmi_tlm_data_fops = {
+	.owner = THIS_MODULE,
+	.open = scmi_tlm_data_open,
+	.read = scmi_tlm_generic_data_read,
+	.release = scmi_tlm_priv_release,
+};
+
+DEFINE_TLM_CLASS(data_tlmo, "des_bulk_read", 0,
+		 S_IFREG | S_IRUSR, &scmi_tlm_data_fops, NULL);
+
+static int scmi_tlm_bulk_single_read(struct scmi_tlm_setup *tsp,
+				     int res_id, int *num_samples,
+				     struct scmi_telemetry_de_sample *samples)
+{
+	return tsp->ops->des_sample_get(tsp->ph, res_id, num_samples, samples);
+}
+
+static int scmi_tlm_single_read_open(struct inode *ino, struct file *filp)
+{
+	return __scmi_tlm_generic_open(ino, filp, scmi_tlm_bulk_single_read);
+}
+
+static const struct file_operations scmi_tlm_single_sample_fops = {
+	.owner = THIS_MODULE,
+	.open = scmi_tlm_single_read_open,
+	.read = scmi_tlm_generic_data_read,
+	.release = scmi_tlm_priv_release,
+};
+
+DEFINE_TLM_CLASS(single_sample_tlmo, "des_single_sample_read", 0,
+		 S_IFREG | S_IRUSR, &scmi_tlm_single_sample_fops, NULL);
+
+static const struct scmi_tlm_class tlm_grps[] = {
+	TLM_ANON_CLASS("enable", TLM_IS_STATE | TLM_IS_GROUP,
+		       S_IFREG | S_IRUSR | S_IWUSR, &obj_enable_fops, NULL),
+	TLM_ANON_CLASS("tstamp_enable", TLM_IS_GROUP,
+		       S_IFREG | S_IRUSR | S_IWUSR, &obj_enable_fops, NULL),
+	TLM_ANON_CLASS(NULL, 0, 0, NULL, NULL),
+};
+
+DEFINE_TLM_CLASS(grp_data_tlmo, "des_bulk_read", TLM_IS_GROUP,
+		 S_IFREG | S_IRUSR, &scmi_tlm_data_fops, NULL);
+
+DEFINE_TLM_CLASS(groups_dir_cls, "groups", 0, S_IFDIR | S_IRWXU, NULL, NULL);
+
+DEFINE_TLM_CLASS(grp_single_sample_tlmo, "des_single_sample_read", TLM_IS_GROUP,
+		 S_IFREG | S_IRUSR, &scmi_tlm_single_sample_fops, NULL);
+
+DEFINE_TLM_CLASS(grp_composing_des_tlmo, "composing_des", TLM_IS_GROUP,
+		 S_IFREG | S_IRUSR, &string_ro_fops, NULL);
+
+DEFINE_TLM_CLASS(grp_current_interval_tlmo, "current_update_interval_ms",
+		 TLM_IS_GROUP, S_IFREG | S_IRUSR | S_IWUSR,
+		 &current_interval_fops, NULL);
+
+DEFINE_TLM_CLASS(grp_available_interval_tlmo, "available_update_intervals_ms",
+		 TLM_IS_GROUP, S_IFREG | S_IRUSR, &available_interv_fops, NULL);
+
+DEFINE_TLM_CLASS(grp_intervals_discrete_tlmo, "intervals_discrete",
+		 TLM_IS_GROUP, S_IFREG | S_IRUSR, &intrv_discrete_fops, NULL);
+
+static int scmi_telemetry_groups_initialize(struct scmi_tlm_instance *ti)
+{
+	const struct scmi_telemetry_res_info *rinfo;
+	struct scmi_tlm_setup *tsp = ti->tsp;
+	struct super_block *sb = ti->sb;
+	struct device *dev = tsp->dev;
+	struct dentry *grp_dir_dentry;
+
+	if (ti->info->base.num_groups == 0)
+		return 0;
+
+	rinfo = scmi_telemetry_res_info_get(tsp);
+	if (!rinfo)
+		return -ENODEV;
+
+	for (int i = 0; i < rinfo->num_groups; i++) {
+		const struct scmi_telemetry_group *grp = &rinfo->grps[i];
+
+		struct scmi_tlm_class *grp_tlm_cls __free(kfree) =
+			kzalloc(sizeof(*grp_tlm_cls), GFP_KERNEL);
+		if (!grp_tlm_cls)
+			return -ENOMEM;
+
+		grp_tlm_cls->name = kasprintf(GFP_KERNEL, "%u", grp->info->id);
+		if (!grp_tlm_cls->name)
+			return -ENOMEM;
+
+		grp_tlm_cls->mode = S_IFDIR | S_IRWXU;
+		grp_tlm_cls->flags = TLM_IS_DYNAMIC;
+
+		grp_dir_dentry = stlmfs_create_dentry(sb, tsp, ti->grps_dentry,
+						      grp_tlm_cls, grp);
+
+		for (const struct scmi_tlm_class *gto = tlm_grps; gto->name; gto++)
+			stlmfs_create_dentry(sb, tsp, grp_dir_dentry, gto, grp);
+
+		stlmfs_create_dentry(sb, tsp, grp_dir_dentry,
+				     &grp_composing_des_tlmo, grp->des_str);
+
+		stlmfs_create_dentry(sb, tsp, grp_dir_dentry, &grp_data_tlmo, grp);
+		stlmfs_create_dentry(sb, tsp, grp_dir_dentry,
+				     &grp_single_sample_tlmo, grp);
+
+		if (ti->info->per_group_config_support) {
+			stlmfs_create_dentry(sb, tsp, grp_dir_dentry,
+					     &grp_current_interval_tlmo, grp);
+			stlmfs_create_dentry(sb, tsp, grp_dir_dentry,
+					     &grp_available_interval_tlmo, grp);
+			stlmfs_create_dentry(sb, tsp, grp_dir_dentry,
+					     &grp_intervals_discrete_tlmo, grp);
+		}
+
+		retain_and_null_ptr(grp_tlm_cls);
+	}
+
+	dev_info(dev, "Found %d Telemetry GROUPS resources.\n",
+		 rinfo->num_groups);
+
+	return 0;
+}
+
+static struct scmi_tlm_instance *scmi_tlm_init(struct scmi_tlm_setup *tsp,
+					       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 int scmi_telemetry_probe(struct scmi_device *sdev)
+{
+	const struct scmi_handle *handle = sdev->handle;
+	struct scmi_protocol_handle *ph;
+	struct device *dev = &sdev->dev;
+	struct scmi_tlm_instance *ti;
+	struct scmi_tlm_setup *tsp;
+	struct super_block *sb;
+	const void *ops;
+
+	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;
+
+	ti = scmi_tlm_init(tsp, atomic_fetch_inc(&scmi_tlm_instance_count));
+	if (IS_ERR(ti))
+		return PTR_ERR(ti);
+
+	mutex_lock(&stlmfs_mtx);
+	list_add(&ti->node, &scmi_telemetry_instances);
+	sb = stlmfs_sb;
+	mutex_unlock(&stlmfs_mtx);
+
+	/*
+	 * If the file system was already mounted by the time this
+	 * instance was probed, register explicitly, since the list
+	 * has been scanned already.
+	 */
+	if (sb) {
+		int ret;
+
+		ret = scmi_telemetry_instance_register(sb, ti);
+		if (ret) {
+			dev_err(dev, "Failed to register instance %u at probe.\n",
+				ti->id);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+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-telemetry-driver",
+	.probe = scmi_telemetry_probe,
+	.id_table = scmi_id_table,
+};
+
+static struct inode *stlmfs_alloc_inode(struct super_block *sb)
+{
+	struct scmi_tlm_inode *tlmi;
+
+	tlmi = alloc_inode_sb(sb, stlmfs_inode_cachep, GFP_KERNEL);
+	if (!tlmi)
+		return NULL;
+
+	tlmi->cls = NULL;
+
+	return &tlmi->vfs_inode;
+}
+
+static void stlmfs_free_inode(struct inode *inode)
+{
+	struct scmi_tlm_inode *tlmi = to_tlm_inode(inode);
+
+	if (tlmi->cls && IS_DYNAMIC(tlmi->cls->flags)) {
+		kfree(tlmi->cls->name);
+		kfree(tlmi->cls);
+	}
+
+	kmem_cache_free(stlmfs_inode_cachep, tlmi);
+}
+
+static const struct super_operations tlm_sops = {
+	.statfs = simple_statfs,
+	.alloc_inode = stlmfs_alloc_inode,
+	.free_inode = stlmfs_free_inode,
+};
+
+static struct dentry *stlmfs_create_root_dentry(struct super_block *sb)
+{
+	struct dentry *dentry;
+	struct inode *inode;
+
+	inode = stlmfs_get_inode(sb);
+	if (!inode)
+		return ERR_PTR(-ENOMEM);
+
+	inode->i_op = &simple_dir_inode_operations;
+	inode->i_fop = &simple_dir_operations;
+	inode_init_owner(&nop_mnt_idmap, inode, NULL, S_IFDIR | S_IRWXU);
+
+	dentry = d_make_root(inode);
+	if (!dentry)
+		return ERR_PTR(-ENOMEM);
+
+	return dentry;
+}
+
+static int scmi_tlm_root_dentries_initialize(struct scmi_tlm_instance *ti)
+{
+	struct scmi_tlm_setup *tsp = ti->tsp;
+	struct super_block *sb = ti->sb;
+
+	scnprintf(ti->name, MAX_INST_NAME, "tlm_%d", ti->id);
+
+	/* Allocate top instance node */
+	ti->top_cls.name = ti->name;
+	ti->top_cls.mode = S_IFDIR | S_IRWXU;
+
+	/* Create the root of this instance */
+	ti->top_dentry = stlmfs_create_dentry(sb, tsp, sb->s_root, &ti->top_cls, NULL);
+	for (const struct scmi_tlm_class *tlmo = tlm_tops; tlmo->name; tlmo++)
+		stlmfs_create_dentry(sb, tsp, ti->top_dentry, tlmo, ti->info);
+
+	if (ti->info->reset_support)
+		stlmfs_create_dentry(sb, tsp, ti->top_dentry, &reset_tlmo, NULL);
+
+	stlmfs_create_dentry(sb, tsp, ti->top_dentry, &version_tlmo,
+			     &ti->info->base.version);
+	stlmfs_create_dentry(sb, tsp, ti->top_dentry, &data_tlmo, ti->info);
+	stlmfs_create_dentry(sb, tsp, ti->top_dentry, &single_sample_tlmo, ti->info);
+	ti->des_dentry =
+		stlmfs_create_dentry(sb, tsp, ti->top_dentry, &des_dir_cls, NULL);
+	ti->grps_dentry =
+		stlmfs_create_dentry(sb, tsp, ti->top_dentry, &groups_dir_cls, NULL);
+
+	return 0;
+}
+
+static int scmi_telemetry_instance_register(struct super_block *sb,
+					    struct scmi_tlm_instance *ti)
+{
+	int ret;
+
+	ti->sb = sb;
+	ret = scmi_tlm_root_dentries_initialize(ti);
+	if (ret)
+		return ret;
+
+	ret = scmi_telemetry_des_initialize(ti);
+	if (ret)
+		return ret;
+
+	ret = scmi_telemetry_groups_initialize(ti);
+	if (ret) {
+		dev_warn(ti->tsp->dev,
+			 "Failed to initialize groups for instance %s.\n",
+			 ti->top_cls.name);
+	}
+
+	return 0;
+}
+
+static int stlmfs_fill_super(struct super_block *sb, struct fs_context *fc)
+{
+	struct scmi_tlm_instance *ti;
+	struct dentry *root_dentry;
+	int ret;
+
+	sb->s_magic = TLM_FS_MAGIC;
+	sb->s_blocksize = PAGE_SIZE;
+	sb->s_blocksize_bits = PAGE_SHIFT;
+	sb->s_op = &tlm_sops;
+
+	root_dentry = stlmfs_create_root_dentry(sb);
+	if (IS_ERR(root_dentry))
+		return PTR_ERR(root_dentry);
+
+	sb->s_root = root_dentry;
+
+	mutex_lock(&stlmfs_mtx);
+	list_for_each_entry(ti, &scmi_telemetry_instances, node) {
+		mutex_unlock(&stlmfs_mtx);
+		ret = scmi_telemetry_instance_register(sb, ti);
+		if (ret)
+			dev_err(ti->tsp->dev,
+				"Failed to register instance %u.\n", ti->id);
+		mutex_lock(&stlmfs_mtx);
+	}
+	stlmfs_sb = sb;
+	mutex_unlock(&stlmfs_mtx);
+
+	return 0;
+}
+
+static int stlmfs_get_tree(struct fs_context *fc)
+{
+	return get_tree_single(fc, stlmfs_fill_super);
+}
+
+static const struct fs_context_operations stlmfs_fc_ops = {
+	.get_tree = stlmfs_get_tree,
+};
+
+static int stlmfs_init_fs_context(struct fs_context *fc)
+{
+	fc->ops = &stlmfs_fc_ops;
+
+	return 0;
+}
+
+static void stlmfs_kill_sb(struct super_block *sb)
+{
+	kill_anon_super(sb);
+}
+
+static struct file_system_type scmi_telemetry_fs = {
+	.owner = THIS_MODULE,
+	.name = TLM_FS_NAME,
+	.kill_sb = stlmfs_kill_sb,
+	.init_fs_context = stlmfs_init_fs_context,
+	.fs_flags = 0,
+};
+
+static void stlmfs_init_once(void *arg)
+{
+	struct scmi_tlm_inode *tlmi = arg;
+
+	inode_init_once(&tlmi->vfs_inode);
+}
+
+static int __init scmi_telemetry_init(void)
+{
+	int ret;
+
+	ret = sysfs_create_mount_point(fs_kobj, TLM_FS_MNT);
+	if (ret && ret != -EEXIST)
+		return ret;
+
+	stlmfs_inode_cachep = kmem_cache_create("stlmfs_inode_cache",
+						sizeof(struct scmi_tlm_inode), 0,
+						SLAB_RECLAIM_ACCOUNT | SLAB_ACCOUNT,
+						stlmfs_init_once);
+	if (!stlmfs_inode_cachep) {
+		ret = -ENOMEM;
+		goto out_mnt;
+	}
+
+	ret = register_filesystem(&scmi_telemetry_fs);
+	if (ret)
+		goto out_kmem;
+
+	ret = scmi_register(&scmi_telemetry_driver);
+	if (ret)
+		goto out_reg;
+
+	return 0;
+
+out_reg:
+	unregister_filesystem(&scmi_telemetry_fs);
+out_kmem:
+	kmem_cache_destroy(stlmfs_inode_cachep);
+out_mnt:
+	sysfs_remove_mount_point(fs_kobj, TLM_FS_MNT);
+
+	return ret;
+}
+module_init(scmi_telemetry_init);
+
+static void __exit scmi_telemetry_exit(void)
+{
+	int ret;
+
+	scmi_unregister(&scmi_telemetry_driver);
+	ret = unregister_filesystem(&scmi_telemetry_fs);
+	if (ret)
+		pr_err("Failed to unregister %s\n", TLM_FS_NAME);
+
+	sysfs_remove_mount_point(fs_kobj, TLM_FS_MNT);
+	kmem_cache_destroy(stlmfs_inode_cachep);
+}
+module_exit(scmi_telemetry_exit);
+
+MODULE_AUTHOR("Cristian Marussi <cristian.marussi@arm.com>");
+MODULE_DESCRIPTION("ARM SCMI Telemetry Driver");
+MODULE_LICENSE("GPL");
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 09/17] fs/stlmfs: Document ARM SCMI Telemetry filesystem
  2026-01-14 11:46 [PATCH v2 00/17] Introduce SCMI Telemetry FS support Cristian Marussi
                   ` (7 preceding siblings ...)
  2026-01-14 11:46 ` [PATCH v2 08/17] firmware: arm_scmi: Add System Telemetry driver Cristian Marussi
@ 2026-01-14 11:46 ` Cristian Marussi
  2026-01-14 11:46 ` [PATCH v2 10/17] firmware: arm_scmi: Add System Telemetry ioctls support Cristian Marussi
                   ` (7 subsequent siblings)
  16 siblings, 0 replies; 35+ messages in thread
From: Cristian Marussi @ 2026-01-14 11:46 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty, Cristian Marussi

Introduce initial ARM SCMI Telemetry filesystem documentation.

Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
 Documentation/filesystems/stlmfs.rst | 198 +++++++++++++++++++++++++++
 1 file changed, 198 insertions(+)
 create mode 100644 Documentation/filesystems/stlmfs.rst

diff --git a/Documentation/filesystems/stlmfs.rst b/Documentation/filesystems/stlmfs.rst
new file mode 100644
index 000000000000..7ea8878098f7
--- /dev/null
+++ b/Documentation/filesystems/stlmfs.rst
@@ -0,0 +1,198 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=============================================
+STLMFS - Arm SCMI Telemetry Pseudo Filesystem
+=============================================
+
+.. 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 introduced support for System Telemetry, through which an agent
+can dynamically enumerate configure and collect Telemetry Data Events (DE)
+exposed by the platform.
+
+This filesystem, in turn, exposes to userspace the set of discovered DEs
+allowing for their configuration and retrieval.
+
+Rationale
+=========
+
+**Why not using SysFS/KernFS or DebugFS ?**
+
+The provided userspace interface aims to satisfy 2 main concurrent
+requirements:
+
+ - expose an FS-based human-readable interface that can be used to
+   discover, configure and access Telemetry data directly from the
+   shell
+
+ - allow also alternative machine-friendly, more-performant, binary
+   interfaces that can be used by custom tools without the overhead of
+   multiple accesses through the VFS layers and the hassle of navigating
+   vast filesystem tree structures
+
+All of the above is meant to be available on production systems, not
+simply as a tool for development or testing, so no debugFS option here.
+
+An initial design based on SysFS and chardev/ioctl based interfaces was
+dropped in favour of this full-fledged filesystem implementation since:
+
+- SysFS is a standard way to expose device related properties using a few
+  common helpers built on kernfs; this means, though, that unfortunately in
+  our scenario we would have to generate a dummy simple device for each
+  discovered DE.
+  This by itself seems an abuse of the SysFS framework, but even ignoring
+  this, the sheer number of potentially discoverable DEs (in the order of
+  tens of thousands easily) would have led to the creation of a sensibly
+  vast number of dummy DE devices.
+
+- SysFS usage itself has its well-known constraints and best practices,
+  like the one-file/one-value rule, that hardly cope with SCMI Telemetry
+  needs.
+
+- The need to implement more complex file operations (ioctls/mmap) in
+  order to support the alternative binary interfaces does not fit with
+  SysFS/kernFS facilities.
+
+- Given the nature of the Telemetry protocol, the hybrid approach with
+  chardev/ioctl was itself problematic: on one side being upper-limited
+  in the number of chardev potentially created by the minor numbers
+  availability, on the other side the hassle of having to maintain a
+  completely different interface on the side of a FS based one.
+
+Design
+======
+
+STLMFS is a pseudo filesystem used to expose ARM SCMI Telemetry data
+discovered dynamically at run-time via SCMI.
+
+Inodes are all dynamically created at mount-time from a dedicated
+kmem_cache based on the gathered available SCMI Telemetry information.
+
+Since inodes represent the discovered Telemetry entities, which in turn are
+statically defined at the platform level and immutable throughout the same
+session (boot), allocated inodes are freed only at unmount-time and the
+user is not allowed to delete or create any kind of file within the STLMFS
+filesystem after mount has completed.
+
+A single instance of STLMFS is created at the filesystem level, using
+get_single_tree(), given that the same SCMI backend entities will be
+involved no matter how many times you mount it.
+
+Mountpoints
+===========
+
+A pre-defined mountpoint is available at::
+
+	/sys/fs/arm_telemetry/
+
+Usage
+=====
+
+.. Note::
+	See Documentation/ABI/testing/stlmfs for a detailed description of
+	this ABI.
+
+The filesystem can be typically mounted with::
+
+	mount -t stlmfs none /sys/fs/arm_telemetry
+
+It will proceed to create a top subdirectory for each of the discovered
+SCMI Telemetry instances named as 'tlm_<N>' under which it will create
+the following directory structure::
+
+	/sys/fs/arm_telemetry/tlm_0/
+	|-- all_des_enable
+	|-- all_des_tstamp_enable
+	|-- available_update_intervals_ms
+	|-- current_update_interval_ms
+	|-- de_implementation_version
+	|-- des/
+	|   |-- ...
+	|   |-- ...
+	|   `-- ...
+	|-- des_bulk_read
+	|-- des_single_sample_read
+	|-- groups
+	|   |-- ...
+	|   |-- ...
+	|   `-- ...
+	|-- intervals_discrete
+	|-- reset
+	|-- tlm_enable
+	`-- version
+
+Each subdirectory is defined as follows.
+
+des/
+----
+A subtree containing in turn one subdirectory for each discovered DE and
+named by Data Event ID in hexadecimal form as in::
+
+	|-- des
+	|   |-- 0x00000000
+	|   |-- 0x00000016
+	|   |-- 0x00001010
+	|   |-- 0x0000A000
+	|   |-- 0x0000A001
+	|   |-- 0x0000A002
+	|   |-- 0x0000A005
+	|   |-- ..........
+	|   |-- ..........
+	|   |-- 0x0000A007
+	|   |-- 0x0000A008
+	|   |-- 0x0000A00A
+	|   |-- 0x0000A00B
+	|   |-- 0x0000A00C
+	|   `-- 0x0000A010
+
+where each dedicated DE subdirectory in turn will contain files used to
+describe some DE characteristics, configure it, or read its current data
+value as in::
+
+	tlm_0/des/0xA001/
+	|-- compo_instance_id
+	|-- compo_type
+	|-- enable
+	|-- instance_id
+	|-- name
+	|-- persistent
+	|-- tstamp_enable
+	|-- tstamp_exp
+	|-- type
+	|-- unit
+	|-- unit_exp
+	`-- value
+
+groups/
+-------
+
+An optional subtree containing in turn one subdirectory for each discovered
+Group and named by Group ID as in::
+
+	|-- groups
+	|   |-- 0
+	|   |-- ..
+	|   `-- 1
+
+where each dedicated GROUP subdirectory in turn will contain files used to
+describe some Group characteristics, configure it, or read its current data
+values, as in::
+
+	scmi_tlm_0/groups/0/
+	|-- available_update_intervals_ms
+	|-- composing_des
+	|-- current_update_interval_ms
+	|-- des_bulk_read
+	|-- des_single_sample_read
+	|-- enable
+	|-- intervals_discrete
+	`-- tstamp_enable
+
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 10/17] firmware: arm_scmi: Add System Telemetry ioctls support
  2026-01-14 11:46 [PATCH v2 00/17] Introduce SCMI Telemetry FS support Cristian Marussi
                   ` (8 preceding siblings ...)
  2026-01-14 11:46 ` [PATCH v2 09/17] fs/stlmfs: Document ARM SCMI Telemetry filesystem Cristian Marussi
@ 2026-01-14 11:46 ` Cristian Marussi
  2026-01-14 11:46 ` [PATCH v2 11/17] fs/stlmfs: Document alternative ioctl based binary interface Cristian Marussi
                   ` (6 subsequent siblings)
  16 siblings, 0 replies; 35+ messages in thread
From: Cristian Marussi @ 2026-01-14 11:46 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty, Cristian Marussi

Extend the filesystem based interface with special 'control' files that can
be used to configure and retrieve SCMI Telemetry data in binary form using
the ioctls-based ABI described in uapi/linux/scmi.h.

This alternative ABI is meant to provide a more performant access to SCMI
Telemetry configuration and data events, without the hassle of navigating
the human readable VFS based intwerface.

Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v1 --> v2
 - Use new res_get() operation which use new resource accessors
 - Use new de_lookup() tlm_ops
 - Using cleanup.h
---
 .../firmware/arm_scmi/scmi_system_telemetry.c | 404 ++++++++++++++++++
 1 file changed, 404 insertions(+)

diff --git a/drivers/firmware/arm_scmi/scmi_system_telemetry.c b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
index b48f2d4eecae..721de615bec3 100644
--- a/drivers/firmware/arm_scmi/scmi_system_telemetry.c
+++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
@@ -24,6 +24,8 @@
 #include <linux/string.h>
 #include <linux/uaccess.h>
 
+#include <uapi/linux/scmi.h>
+
 #define TLM_FS_MAGIC		0x75C01C80
 #define TLM_FS_NAME		"stlmfs"
 #define TLM_FS_MNT		"arm_telemetry"
@@ -1056,6 +1058,406 @@ DEFINE_TLM_CLASS(grp_available_interval_tlmo, "available_update_intervals_ms",
 DEFINE_TLM_CLASS(grp_intervals_discrete_tlmo, "intervals_discrete",
 		 TLM_IS_GROUP, S_IFREG | S_IRUSR, &intrv_discrete_fops, NULL);
 
+static long
+scmi_tlm_info_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned long arg)
+{
+	const struct scmi_telemetry_info *info = tlmi->priv;
+	void * __user uptr = (void * __user)arg;
+
+	if (copy_to_user(uptr, &info->base, sizeof(info->base)))
+		return -EFAULT;
+
+	return 0;
+}
+
+static long
+scmi_tlm_intervals_get_ioctl(const struct scmi_tlm_inode *tlmi,
+			     unsigned long arg, bool is_group)
+{
+	struct scmi_tlm_intervals ivs, *tlm_ivs;
+	void * __user uptr = (void * __user)arg;
+
+	if (copy_from_user(&ivs, uptr, sizeof(ivs)))
+		return -EFAULT;
+
+	if (!is_group) {
+		const struct scmi_telemetry_info *info = tlmi->priv;
+
+		tlm_ivs = info->intervals;
+	} else {
+		const struct scmi_telemetry_group *grp = tlmi->priv;
+
+		tlm_ivs = grp->intervals;
+	}
+
+	if (ivs.num != tlm_ivs->num)
+		return -EINVAL;
+
+	if (copy_to_user(uptr, tlm_ivs,
+			 sizeof(*tlm_ivs) + sizeof(u32) * ivs.num))
+		return -EFAULT;
+
+	return 0;
+}
+
+static long
+scmi_tlm_de_config_set_ioctl(const struct scmi_tlm_inode *tlmi,
+			     unsigned long arg, bool all)
+{
+	const struct scmi_telemetry_res_info *rinfo;
+	void * __user uptr = (void * __user)arg;
+	struct scmi_tlm_setup *tsp = tlmi->tsp;
+	struct scmi_tlm_de_config tcfg = {};
+	int ret;
+
+	if (copy_from_user(&tcfg, uptr, sizeof(tcfg)))
+		return -EFAULT;
+
+	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_inode *tlmi,
+			     unsigned long arg)
+{
+	struct scmi_tlm_setup *tsp = tlmi->tsp;
+	void * __user uptr = (void * __user)arg;
+	struct scmi_tlm_de_config tcfg = {};
+	int ret;
+
+	if (copy_from_user(&tcfg, uptr, sizeof(tcfg)))
+		return -EFAULT;
+
+	ret = tsp->ops->state_get(tsp->ph, 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_config_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned long arg,
+			  bool is_group)
+{
+	void * __user uptr = (void * __user)arg;
+	struct scmi_tlm_config cfg;
+
+	if (!is_group) {
+		const struct scmi_telemetry_info *info = tlmi->priv;
+
+		cfg.enable = !!info->enabled;
+		cfg.current_update_interval = info->active_update_interval;
+	} else {
+		const struct scmi_telemetry_group *grp = tlmi->priv;
+
+		cfg.enable = !!grp->enabled;
+		cfg.t_enable = !!grp->tstamp_enabled;
+		cfg.current_update_interval = grp->active_update_interval;
+	}
+
+	if (copy_to_user(uptr, &cfg, sizeof(cfg)))
+		return -EFAULT;
+
+	return 0;
+}
+
+static long
+scmi_tlm_config_set_ioctl(const struct scmi_tlm_inode *tlmi, unsigned long arg,
+			  bool is_group)
+{
+	struct scmi_tlm_setup *tsp = tlmi->tsp;
+	void * __user uptr = (void * __user)arg;
+	struct scmi_tlm_config cfg = {};
+	bool grp_ignore;
+	int res_id;
+
+	if (copy_from_user(&cfg, uptr, sizeof(cfg)))
+		return -EFAULT;
+
+	if (!is_group) {
+		res_id = SCMI_TLM_GRP_INVALID;
+		grp_ignore = true;
+	} else {
+		const struct scmi_telemetry_group *grp = tlmi->priv;
+		int ret;
+
+		res_id = grp->info->id;
+		grp_ignore = false;
+		ret = tsp->ops->state_set(tsp->ph, true, res_id,
+					  (bool *)&cfg.enable,
+					  (bool *)&cfg.t_enable);
+		if (ret)
+			return ret;
+	}
+
+	return tsp->ops->collection_configure(tsp->ph, res_id, grp_ignore,
+					      (bool *)&cfg.enable,
+					      &cfg.current_update_interval,
+					      NULL);
+}
+
+static long
+scmi_tlm_de_info_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned long arg)
+{
+	struct scmi_tlm_setup *tsp = tlmi->tsp;
+	void * __user uptr = (void * __user)arg;
+	const struct scmi_telemetry_de *de;
+	struct scmi_tlm_de_info dei;
+
+	if (copy_from_user(&dei, uptr, sizeof(dei)))
+		return -EFAULT;
+
+	de = tsp->ops->de_lookup(tsp->ph, dei.id);
+	if (!de)
+		return -EINVAL;
+
+	if (copy_to_user(uptr, de->info, sizeof(*de->info)))
+		return -EFAULT;
+
+	return 0;
+}
+
+static long
+scmi_tlm_des_list_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned long arg)
+{
+	struct scmi_tlm_setup *tsp = tlmi->tsp;
+	const struct scmi_telemetry_res_info *rinfo;
+	void * __user uptr = (void * __user)arg;
+	struct scmi_tlm_des_list dsl;
+
+	rinfo = scmi_telemetry_res_info_get(tsp);
+	if (copy_from_user(&dsl, uptr, sizeof(dsl)))
+		return -EFAULT;
+
+	if (dsl.num_des < rinfo->num_des)
+		return -EINVAL;
+
+	if (copy_to_user(uptr, &rinfo->num_des, sizeof(rinfo->num_des)))
+		return -EFAULT;
+
+	if (copy_to_user(uptr + sizeof(rinfo->num_des), rinfo->dei_store,
+			 rinfo->num_des * sizeof(*rinfo->dei_store)))
+		return -EFAULT;
+
+	return 0;
+}
+
+static long
+scmi_tlm_de_value_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned long arg)
+{
+	struct scmi_tlm_setup *tsp = tlmi->tsp;
+	void * __user uptr = (void * __user)arg;
+	struct scmi_tlm_de_sample sample;
+	int ret;
+
+	if (copy_from_user(&sample, uptr, sizeof(sample)))
+		return -EFAULT;
+
+	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_inode *tlmi, unsigned long arg)
+{
+	const struct scmi_telemetry_group *grp = tlmi->priv;
+	void * __user uptr = (void * __user)arg;
+
+	if (copy_to_user(uptr, grp->info, sizeof(*grp->info)))
+		return -EFAULT;
+
+	return 0;
+}
+
+static long
+scmi_tlm_grp_desc_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned long arg)
+{
+	const struct scmi_telemetry_group *grp = tlmi->priv;
+	unsigned int num_des = grp->info->num_des;
+	void * __user uptr = (void * __user)arg;
+	struct scmi_tlm_grp_desc grp_desc;
+
+	if (copy_from_user(&grp_desc, uptr, sizeof(grp_desc)))
+		return -EFAULT;
+
+	if (grp_desc.num_des < num_des)
+		return -EINVAL;
+
+	if (copy_to_user(uptr, &num_des, sizeof(num_des)))
+		return -EFAULT;
+
+	if (copy_to_user(uptr + sizeof(num_des), grp->des,
+			 sizeof(*grp->des) * num_des))
+		return -EFAULT;
+
+	return 0;
+}
+
+static long
+scmi_tlm_grps_list_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned long arg)
+{
+	struct scmi_tlm_setup *tsp = tlmi->tsp;
+	const struct scmi_telemetry_res_info *rinfo;
+	void * __user uptr = (void * __user)arg;
+	struct scmi_tlm_grps_list gsl;
+
+	if (copy_from_user(&gsl, uptr, sizeof(gsl)))
+		return -EFAULT;
+
+	rinfo = scmi_telemetry_res_info_get(tsp);
+	if (gsl.num_grps < rinfo->num_groups)
+		return -EINVAL;
+
+	if (copy_to_user(uptr, &rinfo->num_groups, sizeof(rinfo->num_groups)))
+		return -EFAULT;
+
+	if (copy_to_user(uptr + sizeof(rinfo->num_groups), rinfo->grps_store,
+			 rinfo->num_groups * sizeof(*rinfo->grps_store)))
+		return -EFAULT;
+
+	return 0;
+}
+
+static long scmi_tlm_des_read_ioctl(const struct scmi_tlm_inode *tlmi,
+				    unsigned long arg, bool single,
+				    bool is_group)
+{
+	struct scmi_tlm_setup *tsp = tlmi->tsp;
+	void * __user uptr = (void * __user)arg;
+	struct scmi_tlm_data_read bulk;
+	int ret, grp_id = SCMI_TLM_GRP_INVALID;
+
+	if (copy_from_user(&bulk, uptr, sizeof(bulk)))
+		return -EFAULT;
+
+	struct scmi_tlm_data_read *bulk_ptr __free(kfree) =
+		kzalloc(struct_size(bulk_ptr, samples, bulk.num_samples),
+			GFP_KERNEL);
+	if (!bulk_ptr)
+		return -ENOMEM;
+
+	if (is_group) {
+		const struct scmi_telemetry_group *grp = tlmi->priv;
+
+		grp_id = grp->info->id;
+	}
+
+	bulk_ptr->num_samples = bulk.num_samples;
+	if (!single)
+		ret = tsp->ops->des_bulk_read(tsp->ph, grp_id,
+					      &bulk_ptr->num_samples,
+			  (struct scmi_telemetry_de_sample *)bulk_ptr->samples);
+	else
+		ret = tsp->ops->des_sample_get(tsp->ph, grp_id,
+					       &bulk_ptr->num_samples,
+					       (struct scmi_telemetry_de_sample *)bulk_ptr->samples);
+	if (ret)
+		return ret;
+
+	if (copy_to_user(uptr, bulk_ptr, sizeof(*bulk_ptr) +
+			 bulk_ptr->num_samples * sizeof(bulk_ptr->samples[0])))
+		return -EFAULT;
+
+	return 0;
+}
+
+static long scmi_tlm_unlocked_ioctl(struct file *filp, unsigned int cmd,
+				    unsigned long arg)
+{
+	struct scmi_tlm_inode *tlmi = to_tlm_inode(file_inode(filp));
+	bool is_group = IS_GROUP(tlmi->cls->flags);
+
+	switch (cmd) {
+	case SCMI_TLM_GET_INFO:
+		if (is_group)
+			return -EOPNOTSUPP;
+		return scmi_tlm_info_get_ioctl(tlmi, arg);
+	case SCMI_TLM_GET_CFG:
+		return scmi_tlm_config_get_ioctl(tlmi, arg, is_group);
+	case SCMI_TLM_SET_CFG:
+		return scmi_tlm_config_set_ioctl(tlmi, arg, is_group);
+	case SCMI_TLM_GET_INTRVS:
+		return scmi_tlm_intervals_get_ioctl(tlmi, arg, is_group);
+	case SCMI_TLM_GET_DE_CFG:
+		if (is_group)
+			return -EOPNOTSUPP;
+		return scmi_tlm_de_config_get_ioctl(tlmi, arg);
+	case SCMI_TLM_SET_DE_CFG:
+		if (is_group)
+			return -EOPNOTSUPP;
+		return scmi_tlm_de_config_set_ioctl(tlmi, arg, false);
+	case SCMI_TLM_GET_DE_INFO:
+		if (is_group)
+			return -EOPNOTSUPP;
+		return scmi_tlm_de_info_get_ioctl(tlmi, arg);
+	case SCMI_TLM_GET_DE_LIST:
+		if (is_group)
+			return -EOPNOTSUPP;
+		return scmi_tlm_des_list_get_ioctl(tlmi, arg);
+	case SCMI_TLM_GET_DE_VALUE:
+		if (is_group)
+			return -EOPNOTSUPP;
+		return scmi_tlm_de_value_get_ioctl(tlmi, arg);
+	case SCMI_TLM_SET_ALL_CFG:
+		return scmi_tlm_de_config_set_ioctl(tlmi, arg, true);
+	case SCMI_TLM_GET_GRP_LIST:
+		if (is_group)
+			return -EOPNOTSUPP;
+		return scmi_tlm_grps_list_get_ioctl(tlmi, arg);
+	case SCMI_TLM_GET_GRP_INFO:
+		if (!is_group)
+			return -EOPNOTSUPP;
+		return scmi_tlm_grp_info_get_ioctl(tlmi, arg);
+	case SCMI_TLM_GET_GRP_DESC:
+		if (!is_group)
+			return -EOPNOTSUPP;
+		return scmi_tlm_grp_desc_get_ioctl(tlmi, arg);
+	case SCMI_TLM_SINGLE_SAMPLE:
+		return scmi_tlm_des_read_ioctl(tlmi, arg, true, is_group);
+	case SCMI_TLM_BULK_READ:
+		return scmi_tlm_des_read_ioctl(tlmi, arg, false, is_group);
+	default:
+		return -ENOTTY;
+	}
+}
+
+static const struct file_operations scmi_tlm_ctrl_fops = {
+	.owner = THIS_MODULE,
+	.open = nonseekable_open,
+	.unlocked_ioctl = scmi_tlm_unlocked_ioctl,
+};
+
+DEFINE_TLM_CLASS(ctrl_tlmo, "control", 0,
+		 S_IFREG | S_IRUSR | S_IWUSR, &scmi_tlm_ctrl_fops, NULL);
+DEFINE_TLM_CLASS(grp_ctrl_tlmo, "control", TLM_IS_GROUP,
+		 S_IFREG | S_IRUSR | S_IWUSR, &scmi_tlm_ctrl_fops, NULL);
+
 static int scmi_telemetry_groups_initialize(struct scmi_tlm_instance *ti)
 {
 	const struct scmi_telemetry_res_info *rinfo;
@@ -1095,6 +1497,7 @@ static int scmi_telemetry_groups_initialize(struct scmi_tlm_instance *ti)
 		stlmfs_create_dentry(sb, tsp, grp_dir_dentry,
 				     &grp_composing_des_tlmo, grp->des_str);
 
+		stlmfs_create_dentry(sb, tsp, grp_dir_dentry, &grp_ctrl_tlmo, grp);
 		stlmfs_create_dentry(sb, tsp, grp_dir_dentry, &grp_data_tlmo, grp);
 		stlmfs_create_dentry(sb, tsp, grp_dir_dentry,
 				     &grp_single_sample_tlmo, grp);
@@ -1279,6 +1682,7 @@ static int scmi_tlm_root_dentries_initialize(struct scmi_tlm_instance *ti)
 			     &ti->info->base.version);
 	stlmfs_create_dentry(sb, tsp, ti->top_dentry, &data_tlmo, ti->info);
 	stlmfs_create_dentry(sb, tsp, ti->top_dentry, &single_sample_tlmo, ti->info);
+	stlmfs_create_dentry(sb, tsp, ti->top_dentry, &ctrl_tlmo, ti->info);
 	ti->des_dentry =
 		stlmfs_create_dentry(sb, tsp, ti->top_dentry, &des_dir_cls, NULL);
 	ti->grps_dentry =
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 11/17] fs/stlmfs: Document alternative ioctl based binary interface
  2026-01-14 11:46 [PATCH v2 00/17] Introduce SCMI Telemetry FS support Cristian Marussi
                   ` (9 preceding siblings ...)
  2026-01-14 11:46 ` [PATCH v2 10/17] firmware: arm_scmi: Add System Telemetry ioctls support Cristian Marussi
@ 2026-01-14 11:46 ` Cristian Marussi
  2026-01-14 11:46 ` [PATCH v2 12/17] firmware: arm_scmi: Add Telemetry components view Cristian Marussi
                   ` (5 subsequent siblings)
  16 siblings, 0 replies; 35+ messages in thread
From: Cristian Marussi @ 2026-01-14 11:46 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty, Cristian Marussi

Document the additionally provided special files and their usage in the
context of the alternative binary ioctl-based interface.

Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
 Documentation/filesystems/stlmfs.rst | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/Documentation/filesystems/stlmfs.rst b/Documentation/filesystems/stlmfs.rst
index 7ea8878098f7..5c23f7e5f12c 100644
--- a/Documentation/filesystems/stlmfs.rst
+++ b/Documentation/filesystems/stlmfs.rst
@@ -112,6 +112,7 @@ the following directory structure::
 	|-- all_des_enable
 	|-- all_des_tstamp_enable
 	|-- available_update_intervals_ms
+	|-- control
 	|-- current_update_interval_ms
 	|-- de_implementation_version
 	|-- des/
@@ -129,6 +130,10 @@ the following directory structure::
 	|-- tlm_enable
 	`-- version
 
+.. Note::
+	The control/ special file can be used to use the alternative
+	binary interface described in include/uapi/linux/scmi.h
+
 Each subdirectory is defined as follows.
 
 des/
@@ -189,6 +194,7 @@ values, as in::
 	scmi_tlm_0/groups/0/
 	|-- available_update_intervals_ms
 	|-- composing_des
+	|-- control
 	|-- current_update_interval_ms
 	|-- des_bulk_read
 	|-- des_single_sample_read
@@ -196,3 +202,21 @@ values, as in::
 	|-- intervals_discrete
 	`-- tstamp_enable
 
+Alternative Binary Interfaces - Special files
+=============================================
+
+Special files are populated across the filesystem so as to implement the support
+of more performant alternative binary interfaces that can be used instead of the
+main human readable ABI.
+
+IOCTLs Interface
+----------------
+
+The ioctl-based interface is detailed in::
+
+	include/uapi/linux/smci.h
+
+The filesystem provides special files named *control/* to be used with the
+ioctl interface mentioned above: note that the behaviour of some of the ioctls
+is dependent on which *control/* file is used to invoke them (as detailed in the
+UAPI header above).
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 12/17] firmware: arm_scmi: Add Telemetry components view
  2026-01-14 11:46 [PATCH v2 00/17] Introduce SCMI Telemetry FS support Cristian Marussi
                   ` (10 preceding siblings ...)
  2026-01-14 11:46 ` [PATCH v2 11/17] fs/stlmfs: Document alternative ioctl based binary interface Cristian Marussi
@ 2026-01-14 11:46 ` Cristian Marussi
  2026-01-16 13:35   ` Elif Topuz
  2026-01-14 11:46 ` [PATCH v2 13/17] fs/stlmfs: Document alternative topological view Cristian Marussi
                   ` (4 subsequent siblings)
  16 siblings, 1 reply; 35+ messages in thread
From: Cristian Marussi @ 2026-01-14 11:46 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty, Cristian Marussi

Add an alternative filesystem view for the discovered Data Events, where
the tree of DEs is laid out following the discovered topological order
instead of the existing flat layout.

Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v1 --> v2
 - Use new FS API
 - Introduce new stlmfs_lookup_by_name helper
---
 .../firmware/arm_scmi/scmi_system_telemetry.c | 684 ++++++++++++++++++
 1 file changed, 684 insertions(+)

diff --git a/drivers/firmware/arm_scmi/scmi_system_telemetry.c b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
index 721de615bec3..1221520356fd 100644
--- a/drivers/firmware/arm_scmi/scmi_system_telemetry.c
+++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
@@ -174,6 +174,7 @@ struct scmi_tlm_inode {
  * @top_dentry: A reference to the top dentry for this instance.
  * @des_dentry: A reference to the DES dentry for this instance.
  * @grps_dentry: A reference to the groups dentry for this instance.
+ * @compo_dentry: A reference to the components dentry for this instance.
  * @info: A handy reference to this instance SCMI Telemetry info data.
  *
  */
@@ -188,6 +189,7 @@ struct scmi_tlm_instance {
 	struct dentry *top_dentry;
 	struct dentry *des_dentry;
 	struct dentry *grps_dentry;
+	struct dentry *compo_dentry;
 	const struct scmi_telemetry_info *info;
 };
 
@@ -196,6 +198,526 @@ static int scmi_telemetry_instance_register(struct super_block *sb,
 
 static LIST_HEAD(scmi_telemetry_instances);
 
+#define TYPES_ARRAY_SZ		256
+
+static const char *compo_types[TYPES_ARRAY_SZ] = {
+	"unspec",
+	"cpu",
+	"cluster",
+	"gpu",
+	"npu",
+	"interconnnect",
+	"mem_cntrl",
+	"l1_cache",
+	"l2_cache",
+	"l3_cache",
+	"ll_cache",
+	"sys_cache",
+	"disp_cntrl",
+	"ipu",
+	"chiplet",
+	"package",
+	"soc",
+	"system",
+	"smcu",
+	"accel",
+	"battery",
+	"charger",
+	"pmic",
+	"board",
+	"memory",
+	"periph",
+	"periph_subc",
+	"lid",
+	"display",
+	"res_29",
+	"res_30",
+	"res_31",
+	"res_32",
+	"res_33",
+	"res_34",
+	"res_35",
+	"res_36",
+	"res_37",
+	"res_38",
+	"res_39",
+	"res_40",
+	"res_41",
+	"res_42",
+	"res_43",
+	"res_44",
+	"res_45",
+	"res_46",
+	"res_47",
+	"res_48",
+	"res_49",
+	"res_50",
+	"res_51",
+	"res_52",
+	"res_53",
+	"res_54",
+	"res_55",
+	"res_56",
+	"res_57",
+	"res_58",
+	"res_59",
+	"res_60",
+	"res_61",
+	"res_62",
+	"res_63",
+	"res_64",
+	"res_65",
+	"res_66",
+	"res_67",
+	"res_68",
+	"res_69",
+	"res_70",
+	"res_71",
+	"res_72",
+	"res_73",
+	"res_74",
+	"res_75",
+	"res_76",
+	"res_77",
+	"res_78",
+	"res_79",
+	"res_80",
+	"res_81",
+	"res_82",
+	"res_83",
+	"res_84",
+	"res_85",
+	"res_86",
+	"res_87",
+	"res_88",
+	"res_89",
+	"res_90",
+	"res_91",
+	"res_92",
+	"res_93",
+	"res_94",
+	"res_95",
+	"res_96",
+	"res_97",
+	"res_98",
+	"res_99",
+	"res_100",
+	"res_101",
+	"res_102",
+	"res_103",
+	"res_104",
+	"res_105",
+	"res_106",
+	"res_107",
+	"res_108",
+	"res_109",
+	"res_110",
+	"res_111",
+	"res_112",
+	"res_113",
+	"res_114",
+	"res_115",
+	"res_116",
+	"res_117",
+	"res_118",
+	"res_119",
+	"res_120",
+	"res_121",
+	"res_122",
+	"res_123",
+	"res_124",
+	"res_125",
+	"res_126",
+	"res_127",
+	"res_128",
+	"res_129",
+	"res_130",
+	"res_131",
+	"res_132",
+	"res_133",
+	"res_134",
+	"res_135",
+	"res_136",
+	"res_137",
+	"res_138",
+	"res_139",
+	"res_140",
+	"res_141",
+	"res_142",
+	"res_143",
+	"res_144",
+	"res_145",
+	"res_146",
+	"res_147",
+	"res_148",
+	"res_149",
+	"res_150",
+	"res_151",
+	"res_152",
+	"res_153",
+	"res_154",
+	"res_155",
+	"res_156",
+	"res_157",
+	"res_158",
+	"res_159",
+	"res_160",
+	"res_161",
+	"res_162",
+	"res_163",
+	"res_164",
+	"res_165",
+	"res_166",
+	"res_167",
+	"res_168",
+	"res_169",
+	"res_170",
+	"res_171",
+	"res_172",
+	"res_173",
+	"res_174",
+	"res_175",
+	"res_176",
+	"res_177",
+	"res_178",
+	"res_179",
+	"res_180",
+	"res_181",
+	"res_182",
+	"res_183",
+	"res_184",
+	"res_185",
+	"res_186",
+	"res_187",
+	"res_188",
+	"res_189",
+	"res_190",
+	"res_191",
+	"res_192",
+	"res_193",
+	"res_194",
+	"res_195",
+	"res_196",
+	"res_197",
+	"res_198",
+	"res_199",
+	"res_200",
+	"res_201",
+	"res_202",
+	"res_203",
+	"res_204",
+	"res_205",
+	"res_206",
+	"res_207",
+	"res_208",
+	"res_209",
+	"res_210",
+	"res_211",
+	"res_212",
+	"res_213",
+	"res_214",
+	"res_215",
+	"res_216",
+	"res_217",
+	"res_218",
+	"res_219",
+	"res_220",
+	"res_221",
+	"res_222",
+	"res_223",
+	"oem_224",
+	"oem_225",
+	"oem_226",
+	"oem_227",
+	"oem_228",
+	"oem_229",
+	"oem_230",
+	"oem_231",
+	"oem_232",
+	"oem_233",
+	"oem_234",
+	"oem_235",
+	"oem_236",
+	"oem_237",
+	"oem_238",
+	"oem_239",
+	"oem_240",
+	"oem_241",
+	"oem_242",
+	"oem_243",
+	"oem_244",
+	"oem_245",
+	"oem_246",
+	"oem_247",
+	"oem_248",
+	"oem_249",
+	"oem_250",
+	"oem_251",
+	"oem_252",
+	"oem_253",
+	"oem_254",
+	"oem_255",
+};
+
+static const char *unit_types[TYPES_ARRAY_SZ] = {
+	"none",
+	"unspec",
+	"celsius",
+	"fahrenheit",
+	"kelvin",
+	"volts",
+	"amps",
+	"watts",
+	"joules",
+	"coulombs",
+	"va",
+	"nits",
+	"lumens",
+	"lux",
+	"candelas",
+	"kpa",
+	"psi",
+	"newtons",
+	"cfm",
+	"rpm",
+	"hertz",
+	"seconds",
+	"minutes",
+	"hours",
+	"days",
+	"weeks",
+	"mils",
+	"inches",
+	"feet",
+	"cubic_inches",
+	"cubic_feet",
+	"meters",
+	"cubic_centimeters",
+	"cubic_meters",
+	"liters",
+	"fluid_ounces",
+	"radians",
+	"steradians",
+	"revolutions",
+	"cycles",
+	"gravities",
+	"ounces",
+	"pounds",
+	"foot_pounds",
+	"ounce_inches",
+	"gauss",
+	"gilberts",
+	"henries",
+	"farads",
+	"ohms",
+	"siemens",
+	"moles",
+	"becquerels",
+	"ppm",
+	"decibels",
+	"dba",
+	"dbc",
+	"grays",
+	"sieverts",
+	"color_temp_kelvin",
+	"bits",
+	"bytes",
+	"words",
+	"dwords",
+	"qwords",
+	"percentage",
+	"pascals",
+	"counts",
+	"grams",
+	"newton_meters",
+	"hits",
+	"misses",
+	"retries",
+	"overruns",
+	"underruns",
+	"collisions",
+	"packets",
+	"messages",
+	"chars",
+	"errors",
+	"corrected_err",
+	"uncorrectable_err",
+	"square_mils",
+	"square_inches",
+	"square_feet",
+	"square_centimeters",
+	"square_meters",
+	"radians_per_secs",
+	"beats_per_minute",
+	"meters_per_secs_squared",
+	"meters_per_secs",
+	"cubic_meter_per_secs",
+	"millimeters_mercury",
+	"radians_per_secs_squared",
+	"state",
+	"bps",
+	"res_96",
+	"res_97",
+	"res_98",
+	"res_99",
+	"res_100",
+	"res_101",
+	"res_102",
+	"res_103",
+	"res_104",
+	"res_105",
+	"res_106",
+	"res_107",
+	"res_108",
+	"res_109",
+	"res_110",
+	"res_111",
+	"res_112",
+	"res_113",
+	"res_114",
+	"res_115",
+	"res_116",
+	"res_117",
+	"res_118",
+	"res_119",
+	"res_120",
+	"res_121",
+	"res_122",
+	"res_123",
+	"res_124",
+	"res_125",
+	"res_126",
+	"res_127",
+	"res_128",
+	"res_129",
+	"res_130",
+	"res_131",
+	"res_132",
+	"res_133",
+	"res_134",
+	"res_135",
+	"res_136",
+	"res_137",
+	"res_138",
+	"res_139",
+	"res_140",
+	"res_141",
+	"res_142",
+	"res_143",
+	"res_144",
+	"res_145",
+	"res_146",
+	"res_147",
+	"res_148",
+	"res_149",
+	"res_150",
+	"res_151",
+	"res_152",
+	"res_153",
+	"res_154",
+	"res_155",
+	"res_156",
+	"res_157",
+	"res_158",
+	"res_159",
+	"res_160",
+	"res_161",
+	"res_162",
+	"res_163",
+	"res_164",
+	"res_165",
+	"res_166",
+	"res_167",
+	"res_168",
+	"res_169",
+	"res_170",
+	"res_171",
+	"res_172",
+	"res_173",
+	"res_174",
+	"res_175",
+	"res_176",
+	"res_177",
+	"res_178",
+	"res_179",
+	"res_180",
+	"res_181",
+	"res_182",
+	"res_183",
+	"res_184",
+	"res_185",
+	"res_186",
+	"res_187",
+	"res_188",
+	"res_189",
+	"res_190",
+	"res_191",
+	"res_192",
+	"res_193",
+	"res_194",
+	"res_195",
+	"res_196",
+	"res_197",
+	"res_198",
+	"res_199",
+	"res_200",
+	"res_201",
+	"res_202",
+	"res_203",
+	"res_204",
+	"res_205",
+	"res_206",
+	"res_207",
+	"res_208",
+	"res_209",
+	"res_210",
+	"res_211",
+	"res_212",
+	"res_213",
+	"res_214",
+	"res_215",
+	"res_216",
+	"res_217",
+	"res_218",
+	"res_219",
+	"res_220",
+	"res_221",
+	"res_222",
+	"res_223",
+	"res_224",
+	"res_225",
+	"res_226",
+	"res_227",
+	"res_228",
+	"res_229",
+	"res_230",
+	"res_231",
+	"res_232",
+	"res_233",
+	"res_234",
+	"res_235",
+	"res_236",
+	"res_237",
+	"res_238",
+	"res_239",
+	"res_240",
+	"res_241",
+	"res_242",
+	"res_243",
+	"res_244",
+	"res_245",
+	"res_246",
+	"res_247",
+	"res_248",
+	"res_249",
+	"res_250",
+	"res_251",
+	"res_252",
+	"res_253",
+	"res_254",
+	"oem_unit",
+};
+
 static struct inode *stlmfs_get_inode(struct super_block *sb)
 {
 	struct inode *inode = new_inode(sb);
@@ -815,6 +1337,18 @@ DEFINE_TLM_CLASS(persistent_tlmo, "persistent", 0,
 DEFINE_TLM_CLASS(value_tlmo, "value", 0,
 		 S_IFREG | S_IRUSR, &de_read_fops, NULL);
 
+static inline struct dentry *
+stlmfs_lookup_by_name(struct dentry *parent, const char *dname)
+{
+	struct qstr qstr;
+
+	qstr.name = dname;
+	qstr.len = strlen(dname);
+	qstr.hash = full_name_hash(parent, qstr.name, qstr.len);
+
+	return d_lookup(parent, &qstr);
+}
+
 static int scmi_telemetry_de_populate(struct super_block *sb,
 				      struct scmi_tlm_setup *tsp,
 				      struct dentry *parent,
@@ -1659,6 +2193,150 @@ static struct dentry *stlmfs_create_root_dentry(struct super_block *sb)
 	return dentry;
 }
 
+static int scmi_telemetry_de_subdir_symlink(struct super_block *sb,
+					    struct scmi_tlm_setup *tsp,
+					    const struct scmi_telemetry_de *de,
+					    struct dentry *parent)
+{
+	struct dentry *dentry;
+	struct inode *inode;
+	int ret;
+
+	if (IS_ERR(parent))
+		return 0;
+
+	char *name __free(kfree) = kasprintf(GFP_KERNEL, "0x%08X", de->info->id);
+	if (!name)
+		return -ENOMEM;
+
+	char *link __free(kfree) =
+		kasprintf(GFP_KERNEL, "../../../../../des/0x%08X", de->info->id);
+	if (!link)
+		return -ENOMEM;
+
+	dentry = simple_start_creating(parent, name);
+	if (IS_ERR(dentry))
+		return PTR_ERR(dentry);
+
+	inode = stlmfs_get_inode(sb);
+	if (unlikely(!inode)) {
+		dev_err(tsp->dev,
+			"out of free dentries, cannot create '%s'", name);
+		return stlmfs_failed_creating(dentry);
+	}
+
+	inode->i_mode = S_IFLNK | 0777;
+	inode->i_op = &simple_symlink_inode_operations;
+	inode_init_owner(&nop_mnt_idmap, inode, NULL, inode->i_mode);
+	inode->i_link = no_free_ptr(link);
+
+	//d_add(dentry, inode);
+	d_make_persistent(dentry, inode);
+
+	simple_done_creating(dentry);
+
+	return ret;
+}
+
+static struct dentry *
+scmi_telemetry_topology_path_get(struct super_block *sb,
+				 struct scmi_tlm_setup *tsp,
+				 struct dentry *parent, const char *dname)
+{
+	struct dentry *dentry;
+
+	dentry = stlmfs_lookup_by_name(parent, dname);
+	if (!dentry) {
+		struct scmi_tlm_class *dir_tlm_cls __free(kfree) =
+			kzalloc(sizeof(*dir_tlm_cls), GFP_KERNEL);
+		if (!dir_tlm_cls)
+			return NULL;
+
+		dir_tlm_cls->name = kasprintf(GFP_KERNEL, "%s", dname);
+		if (!dir_tlm_cls->name)
+			return NULL;
+
+		dir_tlm_cls->mode = S_IFDIR | S_IRWXU;
+		dir_tlm_cls->flags = TLM_IS_DYNAMIC;
+
+		dentry = stlmfs_create_dentry(sb, tsp, parent,
+					      dir_tlm_cls, NULL);
+		if (!IS_ERR(dentry))
+			retain_and_null_ptr(dir_tlm_cls);
+	}
+
+	return dentry;
+}
+
+static int scmi_telemetry_topology_add_node(struct super_block *sb,
+					    struct scmi_tlm_instance *ti,
+					    const struct scmi_telemetry_de *de)
+{
+	struct dentry *ctype, *cinst, *cunit, *dinst;
+	struct scmi_tlm_de_info *dei = de->info;
+	char inst_str[32];
+	int ret;
+
+	/* by_compo_type/<COMPO_TYPE_STR>/ */
+	ctype = scmi_telemetry_topology_path_get(sb, ti->tsp, ti->compo_dentry,
+						 compo_types[dei->compo_type]);
+	if (!ctype)
+		return -ENOMEM;
+
+	/* by_compo_type/<COMPO_TYPE_STR>/<N>/ */
+	snprintf(inst_str, 32, "%u", dei->compo_instance_id);
+	cinst = scmi_telemetry_topology_path_get(sb, ti->tsp, ctype, inst_str);
+	dput(ctype);
+	if (!cinst)
+		return -ENOMEM;
+
+	/* by_compo_type/<COMPO_TYPE_STR>/<N>/<DE_UNIT_TYPE_STR>/ */
+	cunit = scmi_telemetry_topology_path_get(sb, ti->tsp, cinst,
+						 unit_types[dei->unit]);
+	dput(cinst);
+	if (!cunit)
+		return -ENOMEM;
+
+	/* by_compo_type/<COMPO_TYPE_STR>/<N>/<DE_UNIT_TYPE_STR>/<N> */
+	snprintf(inst_str, 32, "%u", dei->instance_id);
+	dinst = scmi_telemetry_topology_path_get(sb, ti->tsp, cunit, inst_str);
+	dput(cunit);
+	if (!dinst)
+		return -ENOMEM;
+
+	ret = scmi_telemetry_de_subdir_symlink(sb, ti->tsp, de, dinst);
+	dput(dinst);
+
+	return ret;
+}
+
+DEFINE_TLM_CLASS(compo_dir_cls, "components", 0, S_IFDIR | S_IRWXU, NULL, NULL);
+
+static int scmi_telemetry_topology_view_add(struct scmi_tlm_instance *ti)
+{
+	const struct scmi_telemetry_res_info *rinfo;
+	struct scmi_tlm_setup *tsp = ti->tsp;
+	struct device *dev = tsp->dev;
+
+	rinfo = scmi_telemetry_res_info_get(tsp);
+	if (!rinfo || !rinfo->fully_enumerated)
+		return -ENODEV;
+
+	ti->compo_dentry =
+		stlmfs_create_dentry(ti->sb, tsp, ti->top_dentry, &compo_dir_cls, NULL);
+
+	for (int i = 0; i < rinfo->num_des; i++) {
+		int ret;
+
+		ret = scmi_telemetry_topology_add_node(ti->sb, ti, rinfo->des[i]);
+		if (ret)
+			dev_err(dev, "Fail to add node %s to topology. Skip.\n",
+				rinfo->des[i]->info->name);
+	}
+
+	return 0;
+}
+
 static int scmi_tlm_root_dentries_initialize(struct scmi_tlm_instance *ti)
 {
 	struct scmi_tlm_setup *tsp = ti->tsp;
@@ -1712,6 +2390,12 @@ static int scmi_telemetry_instance_register(struct super_block *sb,
 			 ti->top_cls.name);
 	}
 
+	ret = scmi_telemetry_topology_view_add(ti);
+	if (ret)
+		dev_warn(ti->tsp->dev,
+			 "Failed to create topology view for instance %s.\n",
+			 ti->top_cls.name);
+
 	return 0;
 }
 
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 13/17] fs/stlmfs: Document alternative topological view
  2026-01-14 11:46 [PATCH v2 00/17] Introduce SCMI Telemetry FS support Cristian Marussi
                   ` (11 preceding siblings ...)
  2026-01-14 11:46 ` [PATCH v2 12/17] firmware: arm_scmi: Add Telemetry components view Cristian Marussi
@ 2026-01-14 11:46 ` Cristian Marussi
  2026-01-14 11:46 ` [PATCH v2 14/17] [RFC] docs: stlmfs: Document ARM SCMI Telemetry FS ABI Cristian Marussi
                   ` (3 subsequent siblings)
  16 siblings, 0 replies; 35+ messages in thread
From: Cristian Marussi @ 2026-01-14 11:46 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty, Cristian Marussi

The human readable interface presents an alternative view based on the
discovered topological relations between the DEs.

Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
 Documentation/filesystems/stlmfs.rst | 72 ++++++++++++++++++++++++++++
 1 file changed, 72 insertions(+)

diff --git a/Documentation/filesystems/stlmfs.rst b/Documentation/filesystems/stlmfs.rst
index 5c23f7e5f12c..cc9585f77ba5 100644
--- a/Documentation/filesystems/stlmfs.rst
+++ b/Documentation/filesystems/stlmfs.rst
@@ -112,6 +112,7 @@ the following directory structure::
 	|-- all_des_enable
 	|-- all_des_tstamp_enable
 	|-- available_update_intervals_ms
+	|-- components/
 	|-- control
 	|-- current_update_interval_ms
 	|-- de_implementation_version
@@ -202,6 +203,77 @@ values, as in::
 	|-- intervals_discrete
 	`-- tstamp_enable
 
+components/
+-----------
+
+An alternative topological view of the des/ directory based on the topology
+relationship information described in des/ ::
+
+	components/
+	├── cpu
+	│   ├── 0
+	│   │   ├── celsius
+	│   │   │   └── 0
+	│   │   │       └── 0x00000001 -> ../../../../../des/0x00000001
+	│   │   └── cycles
+	│   │       ├── 0
+	│   │       │   └── 0x00001010 -> ../../../../../des/0x00001010
+	│   │       └── 1
+	│   │           └── 0x00002020 -> ../../../../../des/0x00002020
+	│   ├── 1
+	│   │   └── celsius
+	│   │       └── 0
+	│   │           └── 0x00000002 -> ../../../../../des/0x00000002
+	│   └── 2
+	│       └── celsius
+	│           └── 0
+	│               └── 0x00000003 -> ../../../../../des/0x00000003
+	├── interconnnect
+	│   └── 0
+	│       └── hertz
+	│           └── 0
+	│               ├── 0x0000A008 -> ../../../../../des/0x0000A008
+	│               └── 0x0000A00B -> ../../../../../des/0x0000A00B
+	├── mem_cntrl
+	│   └── 0
+	│       ├── bps
+	│       │   └── 0
+	│       │       └── 0x0000A00A -> ../../../../../des/0x0000A00A
+	│       ├── celsius
+	│       │   └── 0
+	│       │       └── 0x0000A007 -> ../../../../../des/0x0000A007
+	│       └── joules
+	│           └── 0
+	│               └── 0x0000A002 -> ../../../../../des/0x0000A002
+	├── periph
+	│   ├── 0
+	│   │   └── messages
+	│   │       └── 0
+	│   │           └── 0x00000016 -> ../../../../../des/0x00000016
+	│   ├── 1
+	│   │  	└── messages
+	│   │       └── 0
+	│   │           └── 0x00000017 -> ../../../../../des/0x00000017
+	│   └── 2
+	│       └── messages
+	│           └── 0
+	│               └── 0x00000018 -> ../../../../../des/0x00000018
+	└── unspec
+	└── 0
+	├── celsius
+	│   └── 0
+	│       └── 0x0000A005 -> ../../../../../des/0x0000A005
+	├── counts
+	│   └── 0
+	│       └── 0x0000A00C -> ../../../../../des/0x0000A00C
+	├── joules
+	│   └── 0
+	│      	├── 0x0000A000 -> ../../../../../des/0x0000A000
+	│       └── 0x0000A001 -> ../../../../../des/0x0000A001
+	└── state
+	└── 0
+	└── 0x0000A010 -> ../../../../../des/0x0000A010
+
 Alternative Binary Interfaces - Special files
 =============================================
 
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 14/17] [RFC] docs: stlmfs: Document ARM SCMI Telemetry FS ABI
  2026-01-14 11:46 [PATCH v2 00/17] Introduce SCMI Telemetry FS support Cristian Marussi
                   ` (12 preceding siblings ...)
  2026-01-14 11:46 ` [PATCH v2 13/17] fs/stlmfs: Document alternative topological view Cristian Marussi
@ 2026-01-14 11:46 ` Cristian Marussi
  2026-01-14 11:46 ` [PATCH v2 15/17] [RFC] firmware: arm_scmi: Add lazy population support to Telemetry FS Cristian Marussi
                   ` (2 subsequent siblings)
  16 siblings, 0 replies; 35+ messages in thread
From: Cristian Marussi @ 2026-01-14 11:46 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty, Cristian Marussi

Add full ABI dcoumentation for stlmfs under testing/

Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
RFC since the documentation is still NOT complete and unsure if place
this into stable/ or testing/
---
 Documentation/ABI/testing/stlmfs | 153 +++++++++++++++++++++++++++++++
 1 file changed, 153 insertions(+)
 create mode 100644 Documentation/ABI/testing/stlmfs

diff --git a/Documentation/ABI/testing/stlmfs b/Documentation/ABI/testing/stlmfs
new file mode 100644
index 000000000000..efa001a7d82f
--- /dev/null
+++ b/Documentation/ABI/testing/stlmfs
@@ -0,0 +1,153 @@
+What:		/sys/fs/arm_telemetry/tlm_<N>/all_des_enable
+Date:		January 2026
+KernelVersion:	7.0
+Contact:	cristian.marussi@arm.com
+Description:	A boolean WO entry to enable all the discovered Data Events for
+		SCMI instance <N>.
+Users:		Any userspace telemetry tool
+
+What:		/sys/fs/arm_telemetry/tlm_<N>/all_tstamp_des_enable
+Date:		January 2026
+KernelVersion:	7.0
+Contact:	cristian.marussi@arm.com
+Description:	A boolean WO entry to enable timestamps for all the discovered
+		Data Events for SCMI instance <N>. (when available)
+Users:		Any userspace telemetry tool
+
+What:		/sys/fs/arm_telemetry/tlm_<N>/available_update_intervals_ms
+Date:		January 2026
+KernelVersion:	7.0
+Contact:	cristian.marussi@arm.com
+Description:	A RO entry that returns a space separated list of tuples of
+		values, separated by a coma, each one representing a
+		configurable update interval for SCMI instance <N>.
+		Each tuple describes a possible update interval using the
+		format <secs>,<exp> where the final represented interval is
+		calculated as: <secs> * 10 ^ <exp>
+		An example of list of tuples that can be read from this entry:
+			3,0 4,-1 75,-2 300,-3 1,1 5,3 222,-7
+Users:		Any userspace telemetry tool
+
+What:		/sys/fs/arm_telemetry/tlm_<N>/current_update_intervals_ms
+Date:		January 2026
+KernelVersion:	7.0
+Contact:	cristian.marussi@arm.com
+Description:	An RW entry that can be used to get or set the platform update
+		interval for SCMI instance <N>.
+		On read the returned tuple represents the current update
+		interval using the format <secs>,<exp> where the final
+		represented interval is calculated as: <secs> * 10 ^ <exp>
+		On write the accepted format is the same as on read <secs>,<exp>
+		but, optionally, the second element of the tuple can be omitted
+		and in that case the assumed value for the exponent will default
+		to -3 signifying milliseconds.
+Users:		Any userspace telemetry tool
+
+What:		/sys/fs/arm_telemetry/tlm_<N>/control
+Date:		January 2026
+KernelVersion:	7.0
+Contact:	cristian.marussi@arm.com
+Description:	An RW entry that can be used to discover, configure and retrieve
+		Telemetry data using the alternative binary interface based on
+		ioctls which is documented in include/uapi/linux/scmi.h
+Users:		Any userspace telemetry tool
+
+What:		/sys/fs/arm_telemetry/tlm_<N>/de_implementation_version
+Date:		January 2026
+KernelVersion:	7.0
+Contact:	cristian.marussi@arm.com
+Description:	A RO entry that returns a string representing the 128bit UUID
+		that uniquely identifies the set of SCMI Telemetry Data Events
+		and their semantic for SCMI instance <N>.
+		This is compliant with the DE_IMPLEMENTATION_REVISION described
+		in SCMI v4.0 Telemetry 3.12.4.3.
+Users:		Any userspace telemetry tool
+
+What:		/sys/fs/arm_telemetry/tlm_<N>/des_bulk_read
+Date:		January 2026
+KernelVersion:	7.0
+Contact:	cristian.marussi@arm.com
+Description:	A RO entry that returns a multi-line string containing all the
+		the DEs enabled for SCMI instance <N>, one-per-line, formmatted
+		as: <DE_ID> <TIMESTAMP> <DATA_VALUE>.
+		These DEs readings represent the last value updated by the
+		platform following the configured update interval: on the
+		backend they may have been collected in a number of different
+		ways: on-demand SHMTI lookup, notifications, fastchannels.
+Users:		Any userspace telemetry tool
+
+What:		/sys/fs/arm_telemetry/tlm_<N>/des_single_sample_read
+Date:		January 2026
+KernelVersion:	7.0
+Contact:	cristian.marussi@arm.com
+Description:	A RO entry that returns a multi-line string containing all the
+		the DEs enabled for SCMI instance <N>, one-per-line, formmatted
+		as: <DE_ID> <TIMESTAMP> <DATA_VALUE>.
+		These DEs readings are generated by triggering an explicit and
+		immediate platform update using single sample asynchronous
+		collect methods.
+Users:		Any userspace telemetry tool
+
+What:		/sys/fs/arm_telemetry/tlm_<N>/intervals_discrete
+Date:		January 2026
+KernelVersion:	7.0
+Contact:	cristian.marussi@arm.com
+Description:	A boolean RO entry to specify if the intervals reported for
+		SCMI instance <N> in available_update_intervals_ms are a list of
+		discrete intervals or a triplet of values representing
+		<LOWEST_UPDATE_INTERVAL> <HIGHEST_UPDATE_INTERVAL> <STEP_SIZE>.
+Users:		Any userspace telemetry tool
+
+What:		/sys/fs/arm_telemetry/tlm_<N>/reset
+Date:		January 2026
+KernelVersion:	7.0
+Contact:	cristian.marussi@arm.com
+Description:	A boolean WO entry that can be used the full reset of the SCMI
+		Telemetry subsystem, both of the configurations and of the
+		collected data, as specified in SCMI v4.0 3.12.4.12
+Users:		Any userspace telemetry tool
+
+What:		/sys/fs/arm_telemetry/tlm_<N>/tlm_enable
+Date:		January 2026
+KernelVersion:	7.0
+Contact:	cristian.marussi@arm.com
+Description:	A boolean RW entry that can be used to get or set the general
+		enable status of the Telemetry subsystem. Temporarily disabling
+		Telemetry as a whole does NOT reset the current configuration,
+		it only stops all the DEs updates platform side.
+Users:		Any userspace telemetry tool
+
+What:		/sys/fs/arm_telemetry/tlm_<N>/version
+Date:		January 2026
+KernelVersion:	7.0
+Contact:	cristian.marussi@arm.com
+Description:	A RO entry used to report the SCMI Telemetry protocol version
+		used in this implementation.
+Users:		Any userspace telemetry tool
+
+What:		/sys/fs/arm_telemetry/tlm_<N>/des/0x<NNNNNNNN>/value
+Date:		January 2026
+KernelVersion:	7.0
+Contact:	cristian.marussi@arm.com
+Description:	A RO entry used to read the last value reported for Data Event
+		with id	0x<NNNNNNNN> for SCMI instance <N>.
+Users:		Any userspace telemetry tool
+
+What:		/sys/fs/arm_telemetry/tlm_<N>/des/0x<NNNNNNNN>/enable
+Date:		January 2026
+KernelVersion:	7.0
+Contact:	cristian.marussi@arm.com
+Description:	A RW boolean entry used to enable or disable Data Event
+		with id	0x<NNNNNNNN> for SCMI instance <N>.
+Users:		Any userspace telemetry tool
+
+What:		/sys/fs/arm_telemetry/tlm_<N>/des/0x<NNNNNNNN>/tstamp_enable
+Date:		January 2026
+KernelVersion:	7.0
+Contact:	cristian.marussi@arm.com
+Description:	A RW boolean entry used to enable or disable timestamping for
+		Data Event with id 0x<NNNNNNNN> for SCMI instance <N>.
+Users:		Any userspace telemetry tool
+
+
+... To BE CONTINUED ...
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 15/17] [RFC] firmware: arm_scmi: Add lazy population support to Telemetry FS
  2026-01-14 11:46 [PATCH v2 00/17] Introduce SCMI Telemetry FS support Cristian Marussi
                   ` (13 preceding siblings ...)
  2026-01-14 11:46 ` [PATCH v2 14/17] [RFC] docs: stlmfs: Document ARM SCMI Telemetry FS ABI Cristian Marussi
@ 2026-01-14 11:46 ` Cristian Marussi
  2026-01-14 11:46 ` [PATCH v2 16/17] fs/stlmfs: Document lazy mode and related mount option Cristian Marussi
  2026-01-14 11:46 ` [PATCH v2 17/17] [RFC] tools/scmi: Add SCMI Telemetry testing tool Cristian Marussi
  16 siblings, 0 replies; 35+ messages in thread
From: Cristian Marussi @ 2026-01-14 11:46 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty, Cristian Marussi

Add a filesystem mount option to the SCMI Telemetry filesystem so as to
delay resources enumeration and related fs subtrees population to the
last possible moment when the related fs paths are accessed.

Only basic global fs entries are populated at mount time when the lazy
mount option is used.

Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
Posted as an RFC since not sure if the lazy population methods used in
this patch are acceptable from the FS standpoint.
---
 .../firmware/arm_scmi/scmi_system_telemetry.c | 581 +++++++++++++++---
 1 file changed, 492 insertions(+), 89 deletions(-)

diff --git a/drivers/firmware/arm_scmi/scmi_system_telemetry.c b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
index 1221520356fd..543977b4b7a5 100644
--- a/drivers/firmware/arm_scmi/scmi_system_telemetry.c
+++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
@@ -33,10 +33,36 @@
 #define MAX_AVAILABLE_INTERV_CHAR_LENGTH	25
 #define MAX_BULK_LINE_CHAR_LENGTH		64
 
+enum {
+	Opt_lazy,
+};
+
+static const struct fs_parameter_spec stlmfs_param_spec[] = {
+	fsparam_flag("lazy", Opt_lazy),
+	{}
+};
+
+struct stlmfs_fs_context {
+	bool lazy;
+};
+
+struct stlmfs_lazy_tracker {
+	bool des;
+	bool grps;
+	bool topo;
+};
+
+struct stlmfs_sb_info {
+	bool lazy;
+	unsigned int num_inst;
+	struct stlmfs_lazy_tracker populated[] __counted_by(num_inst);
+};
+
 static struct kmem_cache *stlmfs_inode_cachep;
 
 static DEFINE_MUTEX(stlmfs_mtx);
 static struct super_block *stlmfs_sb;
+static unsigned int stlmfs_instances;
 
 static atomic_t scmi_tlm_instance_count = ATOMIC_INIT(0);
 
@@ -103,9 +129,11 @@ struct scmi_tlm_class {
 #define	TLM_IS_STATE	BIT(0)
 #define	TLM_IS_GROUP	BIT(1)
 #define	TLM_IS_DYNAMIC	BIT(2)
+#define	TLM_IS_LAZY	BIT(3)
 #define IS_STATE(_f)	((_f) & TLM_IS_STATE)
 #define IS_GROUP(_f)	((_f) & TLM_IS_GROUP)
 #define IS_DYNAMIC(_f)	((_f) & TLM_IS_DYNAMIC)
+#define IS_LAZY(_f)	((_f) & TLM_IS_LAZY)
 	const struct file_operations *f_op;
 	const struct inode_operations *i_op;
 };
@@ -135,6 +163,10 @@ struct scmi_tlm_class {
  * @info: SCMI instance information data reference.
  * @vfs_inode: The embedded VFS inode that will be initialized and plugged
  *	       into the live filesystem at mount time.
+ * @node: List item field.
+ * @children: A list containing all the children of this node.
+ * @num_children: Number of items stored in the @children list.
+ * @mtx: A mutex to protect the @children list.
  *
  * This structure is used to describe each SCMI Telemetry entity discovered
  * at probe time, store its related SCMI data, and link to the proper
@@ -150,6 +182,11 @@ struct scmi_tlm_inode {
 		const struct scmi_telemetry_info *info;
 	};
 	struct inode vfs_inode;
+	struct list_head node;
+	struct list_head children;
+	unsigned int num_children;
+	/* Mutext to protect @children list */
+	struct mutex mtx;
 };
 
 #define to_tlm_inode(t)	container_of(t, struct scmi_tlm_inode, vfs_inode)
@@ -164,8 +201,6 @@ struct scmi_tlm_inode {
  * struct scmi_tlm_instance  - Telemetry instance descriptor
  * @id: Progressive number identifying this probed instance; it will be used
  *	to name the top node at the root of this instance.
- * @res_enumerated: A flag to indicate if full resources enumeration has been
- *		    successfully performed.
  * @name: Name to be used for the top root node of the instance. (tlm_<id>)
  * @node: A node to link this in the list of all instances.
  * @sb: A reference to the current super_block.
@@ -180,7 +215,6 @@ struct scmi_tlm_inode {
  */
 struct scmi_tlm_instance {
 	int id;
-	bool res_enumerated;
 	char name[MAX_INST_NAME];
 	struct list_head node;
 	struct super_block *sb;
@@ -193,6 +227,8 @@ struct scmi_tlm_instance {
 	const struct scmi_telemetry_info *info;
 };
 
+static int scmi_telemetry_groups_initialize(const struct scmi_tlm_instance *ti);
+static int scmi_telemetry_topology_view_initialize(const struct scmi_tlm_instance *ti);
 static int scmi_telemetry_instance_register(struct super_block *sb,
 					    struct scmi_tlm_instance *ti);
 
@@ -742,17 +778,23 @@ stlmfs_create_dentry(struct super_block *sb, struct scmi_tlm_setup *tsp,
 		     struct dentry *parent, const struct scmi_tlm_class *cls,
 		     const void *priv)
 {
-	struct scmi_tlm_inode *tlmi;
+	struct scmi_tlm_inode *tlmi, *tlmi_parent;
+	struct stlmfs_sb_info *sbi = sb->s_fs_info;
 	struct dentry *dentry;
-	struct inode *inode;
-
-	if (!parent)
-		parent = sb->s_root;
+	struct inode *inode, *i_parent;
 
 	if (IS_ERR(parent))
 		return parent;
 
-	dentry = simple_start_creating(parent, cls->name);
+	i_parent = d_inode(parent);
+	if (!i_parent)
+		return ERR_PTR(-ENOENT);
+
+	if (!sbi->lazy)
+		dentry = simple_start_creating(parent, cls->name);
+	else
+		dentry = d_alloc_name(parent, cls->name);
+
 	if (IS_ERR(dentry))
 		return dentry;
 
@@ -777,14 +819,24 @@ stlmfs_create_dentry(struct super_block *sb, struct scmi_tlm_setup *tsp,
 	inode->i_private = (void *)priv;
 
 	tlmi = to_tlm_inode(inode);
-
 	tlmi->cls = cls;
 	tlmi->tsp = tsp;
 	tlmi->priv = priv;
 
+	tlmi_parent = to_tlm_inode(i_parent);
+	if (sbi->lazy && tlmi_parent->cls && IS_LAZY(tlmi_parent->cls->flags)) {
+		scoped_guard(mutex, &tlmi_parent->mtx) {
+			list_add(&tlmi->node, &tlmi_parent->children);
+			tlmi_parent->num_children++;
+		}
+	}
+
 	d_make_persistent(dentry, inode);
 
-	simple_done_creating(dentry);
+	if (!sbi->lazy)
+		simple_done_creating(dentry);
+	else
+		dput(dentry);
 
 	return dentry;
 }
@@ -1310,8 +1362,6 @@ static const struct scmi_tlm_class tlm_tops[] = {
 
 DEFINE_TLM_CLASS(reset_tlmo, "reset", 0, S_IFREG | S_IWUSR, &reset_fops, NULL);
 
-DEFINE_TLM_CLASS(des_dir_cls, "des", 0,
-		 S_IFDIR | S_IRWXU, NULL, NULL);
 DEFINE_TLM_CLASS(name_tlmo, "name", 0,
 		 S_IFREG | S_IRUSR, &string_ro_fops, NULL);
 DEFINE_TLM_CLASS(ena_tlmo, "enable", TLM_IS_STATE,
@@ -1383,48 +1433,72 @@ static int scmi_telemetry_de_populate(struct super_block *sb,
 	return 0;
 }
 
+static struct dentry *
+scmi_telemetry_subdir_create(struct super_block *sb, struct scmi_tlm_setup *tsp,
+			     const char *dname, struct dentry *parent,
+			     const void *priv)
+{
+	struct stlmfs_sb_info *sbi = sb->s_fs_info;
+	struct dentry *dentry;
+
+	struct scmi_tlm_class *tlm_cls __free(kfree) =
+		kzalloc(sizeof(*tlm_cls), GFP_KERNEL);
+	if (!tlm_cls)
+		return ERR_PTR(-ENOMEM);
+
+	tlm_cls->name = dname;
+	tlm_cls->mode = S_IFDIR | S_IRWXU;
+	tlm_cls->flags = TLM_IS_DYNAMIC;
+	if (sbi->lazy)
+		tlm_cls->flags |= TLM_IS_LAZY;
+	dentry = stlmfs_create_dentry(sb, tsp, parent, tlm_cls, priv);
+	if (IS_ERR(dentry))
+		return dentry;
+
+	retain_and_null_ptr(tlm_cls);
+
+	return dentry;
+}
+
 static int
-scmi_telemetry_des_lazy_enumerate(struct scmi_tlm_instance *ti,
-				  const struct scmi_telemetry_res_info *rinfo)
+scmi_telemetry_des_enumerate(const struct scmi_tlm_instance *ti,
+			     const struct scmi_telemetry_res_info *rinfo)
 {
 	struct scmi_tlm_setup *tsp = ti->tsp;
 	struct super_block *sb = ti->sb;
+	struct stlmfs_sb_info *sbi = sb->s_fs_info;
 
 	for (int i = 0; i < rinfo->num_des; i++) {
 		const struct scmi_telemetry_de *de = rinfo->des[i];
 		struct dentry *de_dir_dentry;
 		int ret;
 
-		struct scmi_tlm_class *de_tlm_cls __free(kfree) =
-			kzalloc(sizeof(*de_tlm_cls), GFP_KERNEL);
-		if (!de_tlm_cls)
+		const char *dname __free(kfree) =
+			kasprintf(GFP_KERNEL, "0x%08X", de->info->id);
+		if (!dname)
 			return -ENOMEM;
 
-		de_tlm_cls->name = kasprintf(GFP_KERNEL, "0x%08X", de->info->id);
-		if (!de_tlm_cls->name)
-			return -ENOMEM;
-
-		de_tlm_cls->mode = S_IFDIR | S_IRWXU;
-		de_tlm_cls->flags = TLM_IS_DYNAMIC;
-		de_dir_dentry = stlmfs_create_dentry(sb, tsp, ti->des_dentry,
-						     de_tlm_cls, de);
+		de_dir_dentry = scmi_telemetry_subdir_create(sb, tsp, dname,
+							     ti->des_dentry, de);
+		if (IS_ERR(de_dir_dentry))
+			return PTR_ERR(de_dir_dentry);
 
 		ret = scmi_telemetry_de_populate(sb, tsp, de_dir_dentry, de,
 						 rinfo->fully_enumerated);
 		if (ret)
 			return ret;
 
-		retain_and_null_ptr(de_tlm_cls);
+		retain_and_null_ptr(dname);
 	}
 
-	ti->res_enumerated = true;
+	sbi->populated[ti->id].des = true;
 
 	dev_info(tsp->dev, "Found %d Telemetry DE resources.\n", rinfo->num_des);
 
 	return 0;
 }
 
-static int scmi_telemetry_des_initialize(struct scmi_tlm_instance *ti)
+static int scmi_telemetry_des_initialize(const struct scmi_tlm_instance *ti)
 {
 	const struct scmi_telemetry_res_info *rinfo;
 
@@ -1432,9 +1506,196 @@ static int scmi_telemetry_des_initialize(struct scmi_tlm_instance *ti)
 	if (!rinfo)
 		return -ENODEV;
 
-	return scmi_telemetry_des_lazy_enumerate(ti, rinfo);
+	return scmi_telemetry_des_enumerate(ti, rinfo);
+}
+
+static inline struct dentry *
+scmi_telemetry_dentry_lookup(struct inode *dir, struct dentry *dentry,
+			     unsigned int flags)
+{
+	struct dentry *d, *dentry_dir;
+
+	const char *dname __free(kfree) =
+		kmemdup_nul(dentry->d_name.name, dentry->d_name.len, GFP_KERNEL);
+	if (!dname)
+		return ERR_PTR(-ENOMEM);
+
+	dentry_dir = d_find_alias(dir);
+	if (!dentry_dir)
+		return simple_lookup(dir, dentry, flags);
+
+	d = stlmfs_lookup_by_name(dentry_dir, dname);
+	dput(dentry_dir);
+
+	return d;
+}
+
+static struct dentry *
+stlmfs_lazy_des_lookup(struct inode *dir, struct dentry *dentry,
+		       unsigned int flags)
+{
+	struct scmi_tlm_inode *tlmi = to_tlm_inode(dir);
+	struct scmi_tlm_instance *ti = (struct scmi_tlm_instance *)tlmi->priv;
+	struct super_block *sb = ti->sb;
+	struct stlmfs_sb_info *sbi = sb->s_fs_info;
+	int ret;
+
+	if (sbi->populated[ti->id].des)
+		return simple_lookup(dir, dentry, flags);
+
+	ret = scmi_telemetry_des_initialize(ti);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return scmi_telemetry_dentry_lookup(dir, dentry, flags);
+}
+
+static const struct inode_operations lazy_des_dir_iops = {
+	.lookup = stlmfs_lazy_des_lookup,
+};
+
+static struct dentry *
+stlmfs_lazy_grps_lookup(struct inode *dir, struct dentry *dentry,
+			unsigned int flags)
+{
+	struct scmi_tlm_inode *tlmi = to_tlm_inode(dir);
+	struct scmi_tlm_instance *ti = (struct scmi_tlm_instance *)tlmi->priv;
+	struct super_block *sb = ti->sb;
+	struct stlmfs_sb_info *sbi = sb->s_fs_info;
+	int ret;
+
+	if (sbi->populated[ti->id].grps)
+		return simple_lookup(dir, dentry, flags);
+
+	ret = scmi_telemetry_groups_initialize(ti);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return scmi_telemetry_dentry_lookup(dir, dentry, flags);
+}
+
+static const struct inode_operations lazy_grps_dir_iops = {
+	.lookup = stlmfs_lazy_grps_lookup,
+};
+
+static struct dentry *
+stlmfs_lazy_compo_lookup(struct inode *dir, struct dentry *dentry,
+			 unsigned int flags)
+{
+	struct scmi_tlm_inode *tlmi = to_tlm_inode(dir);
+	struct scmi_tlm_instance *ti = (struct scmi_tlm_instance *)tlmi->priv;
+	struct super_block *sb = ti->sb;
+	struct stlmfs_sb_info *sbi = sb->s_fs_info;
+	int ret;
+
+	if (sbi->populated[ti->id].topo)
+		return simple_lookup(dir, dentry, flags);
+
+	ret = scmi_telemetry_topology_view_initialize(ti);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return scmi_telemetry_dentry_lookup(dir, dentry, flags);
+}
+
+static const struct inode_operations lazy_compo_dir_iops = {
+	.lookup = stlmfs_lazy_compo_lookup,
+};
+
+static inline void
+scmi_telemetry_children_dir_emit(struct dir_context *ctx,
+				 struct scmi_tlm_inode *tlmi_parent)
+{
+	struct scmi_tlm_inode *tlmi;
+
+	if (ctx->pos >= tlmi_parent->num_children)
+		return;
+
+	guard(mutex)(&tlmi_parent->mtx);
+	list_for_each_entry(tlmi, &tlmi_parent->children, node) {
+		if (!dir_emit(ctx, tlmi->cls->name, strlen(tlmi->cls->name),
+			      tlmi->vfs_inode.i_ino,
+			      S_ISDIR(tlmi->cls->mode) ? DT_DIR : DT_REG))
+			break;
+		ctx->pos++;
+	}
+}
+
+static int
+stlmfs_lazy_des_iterate_shared(struct file *filp, struct dir_context *ctx)
+{
+	struct scmi_tlm_inode *tlmi_des = to_tlm_inode(file_inode(filp));
+	const struct scmi_tlm_instance *ti = tlmi_des->priv;
+	struct super_block *sb = ti->sb;
+	struct stlmfs_sb_info *sbi = sb->s_fs_info;
+
+	if (!sbi->populated[ti->id].des) {
+		int ret;
+
+		ret = scmi_telemetry_des_initialize(ti);
+		if (ret)
+			return ret;
+	}
+
+	scmi_telemetry_children_dir_emit(ctx, tlmi_des);
+
+	return 0;
+}
+
+static const struct file_operations lazy_des_fops = {
+	.iterate_shared = stlmfs_lazy_des_iterate_shared,
+};
+
+static int
+stlmfs_lazy_grps_iterate_shared(struct file *filp, struct dir_context *ctx)
+{
+	struct scmi_tlm_inode *tlmi_des = to_tlm_inode(file_inode(filp));
+	const struct scmi_tlm_instance *ti = tlmi_des->priv;
+	struct super_block *sb = ti->sb;
+	struct stlmfs_sb_info *sbi = sb->s_fs_info;
+
+	if (!sbi->populated[ti->id].grps) {
+		int ret;
+
+		ret = scmi_telemetry_groups_initialize(ti);
+		if (ret)
+			return ret;
+	}
+
+	scmi_telemetry_children_dir_emit(ctx, tlmi_des);
+
+	return 0;
 }
 
+static const struct file_operations lazy_grps_fops = {
+	.iterate_shared = stlmfs_lazy_grps_iterate_shared,
+};
+
+static int
+stlmfs_lazy_compo_iterate_shared(struct file *filp, struct dir_context *ctx)
+{
+	struct scmi_tlm_inode *tlmi_des = to_tlm_inode(file_inode(filp));
+	const struct scmi_tlm_instance *ti = tlmi_des->priv;
+	struct super_block *sb = ti->sb;
+	struct stlmfs_sb_info *sbi = sb->s_fs_info;
+
+	if (!sbi->populated[ti->id].topo) {
+		int ret;
+
+		ret = scmi_telemetry_topology_view_initialize(ti);
+		if (ret)
+			return ret;
+	}
+
+	scmi_telemetry_children_dir_emit(ctx, tlmi_des);
+
+	return 0;
+}
+
+static const struct file_operations lazy_compo_fops = {
+	.iterate_shared = stlmfs_lazy_compo_iterate_shared,
+};
+
 DEFINE_TLM_CLASS(version_tlmo, "version", 0,
 		 S_IFREG | S_IRUSR, &sa_x32_ro_fops, NULL);
 
@@ -1574,8 +1835,6 @@ static const struct scmi_tlm_class tlm_grps[] = {
 DEFINE_TLM_CLASS(grp_data_tlmo, "des_bulk_read", TLM_IS_GROUP,
 		 S_IFREG | S_IRUSR, &scmi_tlm_data_fops, NULL);
 
-DEFINE_TLM_CLASS(groups_dir_cls, "groups", 0, S_IFDIR | S_IRWXU, NULL, NULL);
-
 DEFINE_TLM_CLASS(grp_single_sample_tlmo, "des_single_sample_read", TLM_IS_GROUP,
 		 S_IFREG | S_IRUSR, &scmi_tlm_single_sample_fops, NULL);
 
@@ -1992,66 +2251,82 @@ DEFINE_TLM_CLASS(ctrl_tlmo, "control", 0,
 DEFINE_TLM_CLASS(grp_ctrl_tlmo, "control", TLM_IS_GROUP,
 		 S_IFREG | S_IRUSR | S_IWUSR, &scmi_tlm_ctrl_fops, NULL);
 
-static int scmi_telemetry_groups_initialize(struct scmi_tlm_instance *ti)
+static int
+scmi_telemetry_grp_populate(struct super_block *sb, struct scmi_tlm_setup *tsp,
+			    struct dentry *parent,
+			    const struct scmi_telemetry_group *grp,
+			    bool per_group_config_support)
+{
+	for (const struct scmi_tlm_class *gto = tlm_grps; gto->name; gto++)
+		stlmfs_create_dentry(sb, tsp, parent, gto, grp);
+
+	stlmfs_create_dentry(sb, tsp, parent, &grp_composing_des_tlmo,
+			     grp->des_str);
+
+	stlmfs_create_dentry(sb, tsp, parent, &grp_ctrl_tlmo, grp);
+	stlmfs_create_dentry(sb, tsp, parent, &grp_data_tlmo, grp);
+	stlmfs_create_dentry(sb, tsp, parent, &grp_single_sample_tlmo, grp);
+
+	if (per_group_config_support) {
+		stlmfs_create_dentry(sb, tsp, parent,
+				     &grp_current_interval_tlmo, grp);
+		stlmfs_create_dentry(sb, tsp, parent,
+				     &grp_available_interval_tlmo, grp);
+		stlmfs_create_dentry(sb, tsp, parent,
+				     &grp_intervals_discrete_tlmo, grp);
+	}
+
+	return 0;
+}
+
+static int
+scmi_telemetry_groups_enumerate(const struct scmi_tlm_instance *ti,
+				const struct scmi_telemetry_res_info *rinfo)
 {
-	const struct scmi_telemetry_res_info *rinfo;
 	struct scmi_tlm_setup *tsp = ti->tsp;
 	struct super_block *sb = ti->sb;
-	struct device *dev = tsp->dev;
-	struct dentry *grp_dir_dentry;
-
-	if (ti->info->base.num_groups == 0)
-		return 0;
-
-	rinfo = scmi_telemetry_res_info_get(tsp);
-	if (!rinfo)
-		return -ENODEV;
+	struct stlmfs_sb_info *sbi = sb->s_fs_info;
 
 	for (int i = 0; i < rinfo->num_groups; i++) {
-		const struct scmi_telemetry_group *grp = &rinfo->grps[i];
-
-		struct scmi_tlm_class *grp_tlm_cls __free(kfree) =
-			kzalloc(sizeof(*grp_tlm_cls), GFP_KERNEL);
-		if (!grp_tlm_cls)
-			return -ENOMEM;
+		struct dentry *grp_dentry;
+		int ret;
 
-		grp_tlm_cls->name = kasprintf(GFP_KERNEL, "%u", grp->info->id);
-		if (!grp_tlm_cls->name)
+		const char *dname __free(kfree) =
+			kasprintf(GFP_KERNEL, "%u", rinfo->grps[i].info->id);
+		if (!dname)
 			return -ENOMEM;
 
-		grp_tlm_cls->mode = S_IFDIR | S_IRWXU;
-		grp_tlm_cls->flags = TLM_IS_DYNAMIC;
+		grp_dentry = scmi_telemetry_subdir_create(sb, tsp, dname,
+							  ti->grps_dentry,
+							  &rinfo->grps[i]);
+		if (IS_ERR(grp_dentry))
+			return PTR_ERR(grp_dentry);
 
-		grp_dir_dentry = stlmfs_create_dentry(sb, tsp, ti->grps_dentry,
-						      grp_tlm_cls, grp);
+		ret = scmi_telemetry_grp_populate(sb, tsp, grp_dentry,
+						  &rinfo->grps[i],
+						  ti->info->per_group_config_support);
+		if (ret)
+			return ret;
 
-		for (const struct scmi_tlm_class *gto = tlm_grps; gto->name; gto++)
-			stlmfs_create_dentry(sb, tsp, grp_dir_dentry, gto, grp);
+		retain_and_null_ptr(dname);
+	}
 
-		stlmfs_create_dentry(sb, tsp, grp_dir_dentry,
-				     &grp_composing_des_tlmo, grp->des_str);
+	sbi->populated[ti->id].grps = true;
 
-		stlmfs_create_dentry(sb, tsp, grp_dir_dentry, &grp_ctrl_tlmo, grp);
-		stlmfs_create_dentry(sb, tsp, grp_dir_dentry, &grp_data_tlmo, grp);
-		stlmfs_create_dentry(sb, tsp, grp_dir_dentry,
-				     &grp_single_sample_tlmo, grp);
+	dev_info(tsp->dev, "Found %d Telemetry GROUPS resources.\n", rinfo->num_groups);
 
-		if (ti->info->per_group_config_support) {
-			stlmfs_create_dentry(sb, tsp, grp_dir_dentry,
-					     &grp_current_interval_tlmo, grp);
-			stlmfs_create_dentry(sb, tsp, grp_dir_dentry,
-					     &grp_available_interval_tlmo, grp);
-			stlmfs_create_dentry(sb, tsp, grp_dir_dentry,
-					     &grp_intervals_discrete_tlmo, grp);
-		}
+	return 0;
+}
 
-		retain_and_null_ptr(grp_tlm_cls);
-	}
+static int scmi_telemetry_groups_initialize(const struct scmi_tlm_instance *ti)
+{
+	const struct scmi_telemetry_res_info *rinfo;
 
-	dev_info(dev, "Found %d Telemetry GROUPS resources.\n",
-		 rinfo->num_groups);
+	rinfo = scmi_telemetry_res_info_get(ti->tsp);
+	if (!rinfo || !rinfo->fully_enumerated)
+		return -ENODEV;
 
-	return 0;
+	return scmi_telemetry_groups_enumerate(ti, rinfo);
 }
 
 static struct scmi_tlm_instance *scmi_tlm_init(struct scmi_tlm_setup *tsp,
@@ -2108,6 +2383,7 @@ static int scmi_telemetry_probe(struct scmi_device *sdev)
 
 	mutex_lock(&stlmfs_mtx);
 	list_add(&ti->node, &scmi_telemetry_instances);
+	stlmfs_instances++;
 	sb = stlmfs_sb;
 	mutex_unlock(&stlmfs_mtx);
 
@@ -2151,6 +2427,9 @@ static struct inode *stlmfs_alloc_inode(struct super_block *sb)
 		return NULL;
 
 	tlmi->cls = NULL;
+	mutex_init(&tlmi->mtx);
+	INIT_LIST_HEAD(&tlmi->children);
+	tlmi->num_children = 0;
 
 	return &tlmi->vfs_inode;
 }
@@ -2243,6 +2522,7 @@ scmi_telemetry_topology_path_get(struct super_block *sb,
 				 struct scmi_tlm_setup *tsp,
 				 struct dentry *parent, const char *dname)
 {
+	struct stlmfs_sb_info *sbi = sb->s_fs_info;
 	struct dentry *dentry;
 
 	dentry = stlmfs_lookup_by_name(parent, dname);
@@ -2258,6 +2538,8 @@ scmi_telemetry_topology_path_get(struct super_block *sb,
 
 		dir_tlm_cls->mode = S_IFDIR | S_IRWXU;
 		dir_tlm_cls->flags = TLM_IS_DYNAMIC;
+		if (sbi->lazy)
+			dir_tlm_cls->flags |= TLM_IS_LAZY;
 
 		dentry = stlmfs_create_dentry(sb, tsp, parent,
 					      dir_tlm_cls, NULL);
@@ -2269,7 +2551,7 @@ scmi_telemetry_topology_path_get(struct super_block *sb,
 }
 
 static int scmi_telemetry_topology_add_node(struct super_block *sb,
-					    struct scmi_tlm_instance *ti,
+					    const struct scmi_tlm_instance *ti,
 					    const struct scmi_telemetry_de *de)
 {
 	struct dentry *ctype, *cinst, *cunit, *dinst;
@@ -2310,21 +2592,19 @@ static int scmi_telemetry_topology_add_node(struct super_block *sb,
 	return ret;
 }
 
-DEFINE_TLM_CLASS(compo_dir_cls, "components", 0, S_IFDIR | S_IRWXU, NULL, NULL);
-
-static int scmi_telemetry_topology_view_add(struct scmi_tlm_instance *ti)
+static int
+scmi_telemetry_topology_view_initialize(const struct scmi_tlm_instance *ti)
 {
 	const struct scmi_telemetry_res_info *rinfo;
 	struct scmi_tlm_setup *tsp = ti->tsp;
+	struct super_block *sb = ti->sb;
+	struct stlmfs_sb_info *sbi = sb->s_fs_info;
 	struct device *dev = tsp->dev;
 
 	rinfo = scmi_telemetry_res_info_get(tsp);
 	if (!rinfo || !rinfo->fully_enumerated)
 		return -ENODEV;
 
-	ti->compo_dentry =
-		stlmfs_create_dentry(ti->sb, tsp, ti->top_dentry, &compo_dir_cls, NULL);
-
 	for (int i = 0; i < rinfo->num_des; i++) {
 		int ret;
 
@@ -2334,13 +2614,51 @@ static int scmi_telemetry_topology_view_add(struct scmi_tlm_instance *ti)
 				rinfo->des[i]->info->name);
 	}
 
+	sbi->populated[ti->id].topo = true;
+
+	if (sbi->lazy && !sbi->populated[ti->id].des) {
+		int ret;
+
+		ret = scmi_telemetry_des_initialize(ti);
+		if (ret)
+			return ret;
+	}
+
 	return 0;
 }
 
+static struct dentry *
+scmi_telemetry_top_dentry_create(struct scmi_tlm_instance *ti, bool lazy,
+				 const char *dname, struct dentry *parent,
+				 const struct file_operations *lazy_fops,
+				 const struct inode_operations *lazy_dir_iops,
+				 void *priv)
+{
+	struct scmi_tlm_setup *tsp = ti->tsp;
+	struct super_block *sb = ti->sb;
+
+	struct scmi_tlm_class *tlm_cls __free(kfree) =
+		kzalloc(sizeof(*tlm_cls), GFP_KERNEL);
+	if (!tlm_cls)
+		return ERR_PTR(-ENOMEM);
+
+	tlm_cls->name = kasprintf(GFP_KERNEL, "%s", dname);
+	tlm_cls->mode = S_IFDIR | S_IRWXU;
+	tlm_cls->flags = TLM_IS_DYNAMIC;
+	if (lazy) {
+		tlm_cls->flags |= TLM_IS_LAZY;
+		tlm_cls->f_op = lazy_fops;
+		tlm_cls->i_op = lazy_dir_iops;
+	}
+
+	return stlmfs_create_dentry(sb, tsp, parent, no_free_ptr(tlm_cls), priv);
+}
+
 static int scmi_tlm_root_dentries_initialize(struct scmi_tlm_instance *ti)
 {
 	struct scmi_tlm_setup *tsp = ti->tsp;
 	struct super_block *sb = ti->sb;
+	struct stlmfs_sb_info *sbi = sb->s_fs_info;
 
 	scnprintf(ti->name, MAX_INST_NAME, "tlm_%d", ti->id);
 
@@ -2361,10 +2679,25 @@ static int scmi_tlm_root_dentries_initialize(struct scmi_tlm_instance *ti)
 	stlmfs_create_dentry(sb, tsp, ti->top_dentry, &data_tlmo, ti->info);
 	stlmfs_create_dentry(sb, tsp, ti->top_dentry, &single_sample_tlmo, ti->info);
 	stlmfs_create_dentry(sb, tsp, ti->top_dentry, &ctrl_tlmo, ti->info);
-	ti->des_dentry =
-		stlmfs_create_dentry(sb, tsp, ti->top_dentry, &des_dir_cls, NULL);
-	ti->grps_dentry =
-		stlmfs_create_dentry(sb, tsp, ti->top_dentry, &groups_dir_cls, NULL);
+
+	ti->des_dentry = scmi_telemetry_top_dentry_create(ti, sbi->lazy, "des",
+							  ti->top_dentry,
+							  &lazy_des_fops,
+							  &lazy_des_dir_iops,
+							  ti);
+
+	ti->grps_dentry = scmi_telemetry_top_dentry_create(ti, sbi->lazy, "groups",
+							   ti->top_dentry,
+							   &lazy_grps_fops,
+							   &lazy_grps_dir_iops,
+							   ti);
+
+	ti->compo_dentry = scmi_telemetry_top_dentry_create(ti, sbi->lazy,
+							    "components",
+							    ti->top_dentry,
+							    &lazy_compo_fops,
+							    &lazy_compo_dir_iops,
+							    ti);
 
 	return 0;
 }
@@ -2372,6 +2705,7 @@ static int scmi_tlm_root_dentries_initialize(struct scmi_tlm_instance *ti)
 static int scmi_telemetry_instance_register(struct super_block *sb,
 					    struct scmi_tlm_instance *ti)
 {
+	struct stlmfs_sb_info *sbi = sb->s_fs_info;
 	int ret;
 
 	ti->sb = sb;
@@ -2379,6 +2713,9 @@ static int scmi_telemetry_instance_register(struct super_block *sb,
 	if (ret)
 		return ret;
 
+	if (sbi->lazy)
+		return 0;
+
 	ret = scmi_telemetry_des_initialize(ti);
 	if (ret)
 		return ret;
@@ -2390,21 +2727,37 @@ static int scmi_telemetry_instance_register(struct super_block *sb,
 			 ti->top_cls.name);
 	}
 
-	ret = scmi_telemetry_topology_view_add(ti);
-	if (ret)
+	ret = scmi_telemetry_topology_view_initialize(ti);
+	if (ret) {
 		dev_warn(ti->tsp->dev,
 			 "Failed to create topology view for instance %s.\n",
 			 ti->top_cls.name);
+	}
 
 	return 0;
 }
 
 static int stlmfs_fill_super(struct super_block *sb, struct fs_context *fc)
 {
+	struct stlmfs_fs_context *ctx;
 	struct scmi_tlm_instance *ti;
 	struct dentry *root_dentry;
 	int ret;
 
+	/* Bail out if already initialized */
+	if (sb->s_fs_info)
+		return 0;
+
+	struct stlmfs_sb_info *sbi __free(kfree) =
+		kzalloc(struct_size(sbi, populated, stlmfs_instances), GFP_KERNEL);
+	if (!sbi)
+		return -ENOMEM;
+
+	ctx = fc->fs_private;
+	sbi->num_inst = stlmfs_instances;
+	sbi->lazy = ctx->lazy;
+
+	sb->s_fs_info = sbi;
 	sb->s_magic = TLM_FS_MAGIC;
 	sb->s_blocksize = PAGE_SIZE;
 	sb->s_blocksize_bits = PAGE_SHIFT;
@@ -2414,6 +2767,7 @@ static int stlmfs_fill_super(struct super_block *sb, struct fs_context *fc)
 	if (IS_ERR(root_dentry))
 		return PTR_ERR(root_dentry);
 
+	retain_and_null_ptr(sbi);
 	sb->s_root = root_dentry;
 
 	mutex_lock(&stlmfs_mtx);
@@ -2431,17 +2785,61 @@ static int stlmfs_fill_super(struct super_block *sb, struct fs_context *fc)
 	return 0;
 }
 
+static void stlmfs_free(struct fs_context *fc)
+{
+	struct stlmfs_fs_context *ctx;
+
+	ctx = fc->fs_private;
+
+	kfree(ctx);
+}
+
 static int stlmfs_get_tree(struct fs_context *fc)
 {
 	return get_tree_single(fc, stlmfs_fill_super);
 }
 
+static int stlmfs_parse_param(struct fs_context *fc, struct fs_parameter *param)
+{
+	struct stlmfs_fs_context *ctx;
+	struct fs_parse_result result;
+	int opt;
+
+	opt = fs_parse(fc, stlmfs_param_spec, param, &result);
+	if (opt < 0)
+		return opt;
+
+	ctx = fc->fs_private;
+
+	switch (opt) {
+	case Opt_lazy:
+		ctx->lazy = true;
+		break;
+	default:
+		return -ENOPARAM;
+	}
+
+	return 0;
+}
+
 static const struct fs_context_operations stlmfs_fc_ops = {
 	.get_tree = stlmfs_get_tree,
+	.parse_param = stlmfs_parse_param,
+	.free = stlmfs_free,
 };
 
 static int stlmfs_init_fs_context(struct fs_context *fc)
 {
+	struct stlmfs_fs_context *ctx;
+
+	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	/* defaults */
+	ctx->lazy = false;
+
+	fc->fs_private = ctx;
 	fc->ops = &stlmfs_fc_ops;
 
 	return 0;
@@ -2449,7 +2847,11 @@ static int stlmfs_init_fs_context(struct fs_context *fc)
 
 static void stlmfs_kill_sb(struct super_block *sb)
 {
+	struct stlmfs_sb_info *sbi = sb->s_fs_info;
+
 	kill_anon_super(sb);
+
+	kfree(sbi);
 }
 
 static struct file_system_type scmi_telemetry_fs = {
@@ -2457,6 +2859,7 @@ static struct file_system_type scmi_telemetry_fs = {
 	.name = TLM_FS_NAME,
 	.kill_sb = stlmfs_kill_sb,
 	.init_fs_context = stlmfs_init_fs_context,
+	.parameters = stlmfs_param_spec,
 	.fs_flags = 0,
 };
 
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 16/17] fs/stlmfs: Document lazy mode and related mount option
  2026-01-14 11:46 [PATCH v2 00/17] Introduce SCMI Telemetry FS support Cristian Marussi
                   ` (14 preceding siblings ...)
  2026-01-14 11:46 ` [PATCH v2 15/17] [RFC] firmware: arm_scmi: Add lazy population support to Telemetry FS Cristian Marussi
@ 2026-01-14 11:46 ` Cristian Marussi
  2026-01-14 11:46 ` [PATCH v2 17/17] [RFC] tools/scmi: Add SCMI Telemetry testing tool Cristian Marussi
  16 siblings, 0 replies; 35+ messages in thread
From: Cristian Marussi @ 2026-01-14 11:46 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty, Cristian Marussi

Document optional lazy enumeration behaviour and related mount option.

Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
 Documentation/filesystems/stlmfs.rst | 21 +++++++++++++++++++--
 1 file changed, 19 insertions(+), 2 deletions(-)

diff --git a/Documentation/filesystems/stlmfs.rst b/Documentation/filesystems/stlmfs.rst
index cc9585f77ba5..79633cdc71b8 100644
--- a/Documentation/filesystems/stlmfs.rst
+++ b/Documentation/filesystems/stlmfs.rst
@@ -73,8 +73,12 @@ Design
 STLMFS is a pseudo filesystem used to expose ARM SCMI Telemetry data
 discovered dynamically at run-time via SCMI.
 
-Inodes are all dynamically created at mount-time from a dedicated
-kmem_cache based on the gathered available SCMI Telemetry information.
+Normally all of the top level file/inodes are dynamically created at
+mount-time from a dedicated kmem_cache based on the gathered available
+SCMI Telemetry information, but it is possible to enable a lazy enumeration
+and FS population mode that delays SCMI Telemetry resources enumerations
+and related FS population till the moment a user steps into the related FS
+subdirectories: *des/* *groups/* and *components/*.
 
 Since inodes represent the discovered Telemetry entities, which in turn are
 statically defined at the platform level and immutable throughout the same
@@ -104,6 +108,19 @@ The filesystem can be typically mounted with::
 
 	mount -t stlmfs none /sys/fs/arm_telemetry
 
+It is possible to mount it in lazy-mode by using the *lazy* mount option::
+
+	mount -t stlmfs -o lazy none /sys/fs/arm_telemetry
+
+In this latter case, the des/ groups/ and components/ directory will be
+created empty at mount-time and only filled later when 'walked in'.
+
+This allows a user to benefit from a lazy enumeration scheme of the SCMI
+Telemetry resources by delaying such, usually expensive, message exchanges
+to the last possible moment: ideally, even never, if using some of the
+other alternative binary interfaces that does not need any resource
+enumeration at all.
+
 It will proceed to create a top subdirectory for each of the discovered
 SCMI Telemetry instances named as 'tlm_<N>' under which it will create
 the following directory structure::
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 17/17] [RFC] tools/scmi: Add SCMI Telemetry testing tool
  2026-01-14 11:46 [PATCH v2 00/17] Introduce SCMI Telemetry FS support Cristian Marussi
                   ` (15 preceding siblings ...)
  2026-01-14 11:46 ` [PATCH v2 16/17] fs/stlmfs: Document lazy mode and related mount option Cristian Marussi
@ 2026-01-14 11:46 ` Cristian Marussi
  16 siblings, 0 replies; 35+ messages in thread
From: Cristian Marussi @ 2026-01-14 11:46 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty, 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.
---
 tools/testing/scmi/Makefile |  25 +++
 tools/testing/scmi/stlm.c   | 385 ++++++++++++++++++++++++++++++++++++
 2 files changed, 410 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..137ef278d1dd
--- /dev/null
+++ b/tools/testing/scmi/stlm.c
@@ -0,0 +1,385 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+
+#include <unistd.h>
+
+#include <linux/scmi.h>
+
+#define SLEEP_MS	3000
+#define DEF_TLM_ROOT	"/sys/fs/arm_telemetry/"
+
+#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;
+	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];
+
+	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, "+ 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: %d\n",
+		st->cfg.current_update_interval);
+
+	fprintf(stdout, "+ Found #%u Global Update Intervals\n",
+		st->info.num_intervals);
+	for (int i = 0; i < st->ivs->num; i++)
+		fprintf(stdout, "\t[%d]::%u\n", i, st->ivs->update_intervals[i]);
+
+	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, "\t0x%08X %s %s -- TS:%16llu %016llX\n",
+			st->des[i].info->id,
+			st->des[i].cfg.enable ? "ON" : "--",
+			st->des[i].cfg.t_enable ? "TS_ON" : "-----",
+			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++) {
+		fprintf(stdout, "\n\tGRP_ID:%d  DES#:%d  INTRVS#:%d\n",
+			st->grps[i].info->id, st->grps[i].info->num_des,
+			st->grps[i].info->num_intervals);
+
+		fprintf(stdout, "\tCOMPOSING_DES:");
+		for (int j = 0; j < st->grps[i].desc->num_des; j++)
+			fprintf(stdout, "0x%08X ",
+				st->grps[i].desc->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;
+	size_t size = sizeof(*dsl) + num_des * sizeof(dsl->des[0]);
+	int ret;
+
+	dsl = malloc(size);
+	if (!dsl)
+		return NULL;
+
+	bzero(dsl, size);
+	dsl->num_des = num_des;
+	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 = &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;
+	size_t size = sizeof(*gsl) + num_groups * sizeof(gsl->grps[0]);
+	int ret;
+
+	gsl = malloc(size);
+	if (!gsl)
+		return NULL;
+
+	bzero(gsl, size);
+	gsl->num_grps = num_groups;
+	ret = ioctl(fd, SCMI_TLM_GET_GRP_LIST, gsl);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_GET_GRP_LIST));
+		return NULL;
+	}
+
+	return gsl;
+}
+
+static struct scmi_tlm_intervals *enumerate_intervals(int fd, int num_intervals)
+{
+	struct scmi_tlm_intervals *ivs;
+	size_t sz;
+	int ret;
+
+	sz = sizeof(*ivs) + sizeof(*ivs->update_intervals) * num_intervals;
+	ivs = malloc(sz);
+	if (!ivs)
+		return NULL;
+
+	memset(ivs, 0, sz);
+
+	ivs->num = num_intervals;
+	ret = ioctl(fd, SCMI_TLM_GET_INTRVS, ivs);
+	if (ret) {
+		perror(IOCTL_ERR_STR(SCMI_TLM_GET_INTRVS));
+		free(ivs);
+		return NULL;
+	}
+
+	return ivs;
+}
+
+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->g_dfd = openat(st->dfd, "groups", O_RDONLY);
+	if (st->g_dfd < 0)
+		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];
+		char gctrl[32];
+		size_t size;
+		int ret;
+
+		snprintf(gctrl, 32, "%d/control", i);
+		grp->fd = openat(st->g_dfd, gctrl, O_RDWR);
+		if (grp->fd < 0)
+			return NULL;
+
+		grp->info = &gsl->grps[i];
+		size = sizeof(*grp->desc) + sizeof(uint32_t) * grp->info->num_des;
+		grp->desc = malloc(size);
+		if (!grp->desc)
+			return NULL;
+
+		bzero(grp->desc, size);
+		grp->desc->num_des = grp->info->num_des;
+		ret = ioctl(grp->fd, SCMI_TLM_GET_GRP_DESC, grp->desc);
+		if (ret) {
+			perror(IOCTL_ERR_STR(SCMI_TLM_GET_GRP_DESC));
+			continue;
+		}
+
+		grp->ivs = enumerate_intervals(grp->fd, grp->info->num_intervals);
+	}
+
+	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, "control", 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);
+	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);
+
+	st->path = path;
+
+	return 0;
+}
+
+int main(int argc, char **argv)
+{
+	const char *tlm_root_instance = DEF_TLM_ROOT "tlm_0/";
+	struct scmi_tlm_data_read *bulk;
+	struct scmi_tlm_de_config de_cfg = {};
+	struct tlm_state st = {};
+	size_t bulk_sz;
+	int ret;
+
+	ret = get_tlm_state(tlm_root_instance, &st);
+	if (ret)
+		return ret;
+
+	dump_state(&st);
+
+	bulk_sz = sizeof(*bulk) + sizeof(bulk->samples[0]) * st.info.num_des;
+	bulk = malloc(bulk_sz);
+	if (!bulk)
+		return -1;
+
+	bzero(bulk, bulk_sz);
+	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--- 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;
+	}
+
+	fprintf(stdout, "\n- Single ASYNC read -\n-------------------\n");
+	for (int i = 0; i < bulk->num_samples; i++)
+		fprintf(stdout, "0x%08X %016llu %016llX\n",
+			bulk->samples[i].id, bulk->samples[i].tstamp,
+			bulk->samples[i].val);
+
+	bzero(bulk, bulk_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",
+			bulk->samples[i].id, bulk->samples[i].tstamp,
+			bulk->samples[i].val);
+
+	return 0;
+}
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 12/17] firmware: arm_scmi: Add Telemetry components view
  2026-01-14 11:46 ` [PATCH v2 12/17] firmware: arm_scmi: Add Telemetry components view Cristian Marussi
@ 2026-01-16 13:35   ` Elif Topuz
  2026-01-19 15:42     ` Cristian Marussi
  0 siblings, 1 reply; 35+ messages in thread
From: Elif Topuz @ 2026-01-16 13:35 UTC (permalink / raw)
  To: Cristian Marussi, linux-kernel, linux-arm-kernel, arm-scmi,
	linux-fsdevel
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	jonathan.cameron, lukasz.luba, philip.radford, souvik.chakravarty,
	elif.topuz


Hi Cristian,

On 14/01/2026 11:46, Cristian Marussi wrote:
> Add an alternative filesystem view for the discovered Data Events, where
> the tree of DEs is laid out following the discovered topological order
> instead of the existing flat layout.
> 
> Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
> ---
> v1 --> v2
>  - Use new FS API
>  - Introduce new stlmfs_lookup_by_name helper
> ---
>  .../firmware/arm_scmi/scmi_system_telemetry.c | 684 ++++++++++++++++++
>  1 file changed, 684 insertions(+)
> 
> diff --git a/drivers/firmware/arm_scmi/scmi_system_telemetry.c b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
> index 721de615bec3..1221520356fd 100644
> --- a/drivers/firmware/arm_scmi/scmi_system_telemetry.c
> +++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
> @@ -174,6 +174,7 @@ struct scmi_tlm_inode {
>   * @top_dentry: A reference to the top dentry for this instance.
>   * @des_dentry: A reference to the DES dentry for this instance.
>   * @grps_dentry: A reference to the groups dentry for this instance.
> + * @compo_dentry: A reference to the components dentry for this instance.
>   * @info: A handy reference to this instance SCMI Telemetry info data.
>   *
>   */
> @@ -188,6 +189,7 @@ struct scmi_tlm_instance {
>  	struct dentry *top_dentry;
>  	struct dentry *des_dentry;
>  	struct dentry *grps_dentry;
> +	struct dentry *compo_dentry;
>  	const struct scmi_telemetry_info *info;
>  };
>  
> @@ -196,6 +198,526 @@ static int scmi_telemetry_instance_register(struct super_block *sb,
>  
>  static LIST_HEAD(scmi_telemetry_instances);
>  
> +#define TYPES_ARRAY_SZ		256
> +
> +static const char *compo_types[TYPES_ARRAY_SZ] = {
> +	"unspec",
> +	"cpu",
> +	"cluster",
> +	"gpu",
> +	"npu",
> +	"interconnnect",
> +	"mem_cntrl",
> +	"l1_cache",
> +	"l2_cache",
> +	"l3_cache",
> +	"ll_cache",
> +	"sys_cache",
> +	"disp_cntrl",
> +	"ipu",
> +	"chiplet",
> +	"package",
> +	"soc",
> +	"system",
> +	"smcu",
> +	"accel",
> +	"battery",
> +	"charger",
> +	"pmic",
> +	"board",
> +	"memory",
> +	"periph",
> +	"periph_subc",
> +	"lid",
> +	"display",
> +	"res_29",
> +	"res_30",
> +	"res_31",
> +	"res_32",
> +	"res_33",
> +	"res_34",
> +	"res_35",
> +	"res_36",
> +	"res_37",
> +	"res_38",
> +	"res_39",
> +	"res_40",
> +	"res_41",
> +	"res_42",
> +	"res_43",
> +	"res_44",
> +	"res_45",
> +	"res_46",
> +	"res_47",
> +	"res_48",
> +	"res_49",
> +	"res_50",
> +	"res_51",
> +	"res_52",
> +	"res_53",
> +	"res_54",
> +	"res_55",
> +	"res_56",
> +	"res_57",
> +	"res_58",
> +	"res_59",
> +	"res_60",
> +	"res_61",
> +	"res_62",
> +	"res_63",
> +	"res_64",
> +	"res_65",
> +	"res_66",
> +	"res_67",
> +	"res_68",
> +	"res_69",
> +	"res_70",
> +	"res_71",
> +	"res_72",
> +	"res_73",
> +	"res_74",
> +	"res_75",
> +	"res_76",
> +	"res_77",
> +	"res_78",
> +	"res_79",
> +	"res_80",
> +	"res_81",
> +	"res_82",
> +	"res_83",
> +	"res_84",
> +	"res_85",
> +	"res_86",
> +	"res_87",
> +	"res_88",
> +	"res_89",
> +	"res_90",
> +	"res_91",
> +	"res_92",
> +	"res_93",
> +	"res_94",
> +	"res_95",
> +	"res_96",
> +	"res_97",
> +	"res_98",
> +	"res_99",
> +	"res_100",
> +	"res_101",
> +	"res_102",
> +	"res_103",
> +	"res_104",
> +	"res_105",
> +	"res_106",
> +	"res_107",
> +	"res_108",
> +	"res_109",
> +	"res_110",
> +	"res_111",
> +	"res_112",
> +	"res_113",
> +	"res_114",
> +	"res_115",
> +	"res_116",
> +	"res_117",
> +	"res_118",
> +	"res_119",
> +	"res_120",
> +	"res_121",
> +	"res_122",
> +	"res_123",
> +	"res_124",
> +	"res_125",
> +	"res_126",
> +	"res_127",
> +	"res_128",
> +	"res_129",
> +	"res_130",
> +	"res_131",
> +	"res_132",
> +	"res_133",
> +	"res_134",
> +	"res_135",
> +	"res_136",
> +	"res_137",
> +	"res_138",
> +	"res_139",
> +	"res_140",
> +	"res_141",
> +	"res_142",
> +	"res_143",
> +	"res_144",
> +	"res_145",
> +	"res_146",
> +	"res_147",
> +	"res_148",
> +	"res_149",
> +	"res_150",
> +	"res_151",
> +	"res_152",
> +	"res_153",
> +	"res_154",
> +	"res_155",
> +	"res_156",
> +	"res_157",
> +	"res_158",
> +	"res_159",
> +	"res_160",
> +	"res_161",
> +	"res_162",
> +	"res_163",
> +	"res_164",
> +	"res_165",
> +	"res_166",
> +	"res_167",
> +	"res_168",
> +	"res_169",
> +	"res_170",
> +	"res_171",
> +	"res_172",
> +	"res_173",
> +	"res_174",
> +	"res_175",
> +	"res_176",
> +	"res_177",
> +	"res_178",
> +	"res_179",
> +	"res_180",
> +	"res_181",
> +	"res_182",
> +	"res_183",
> +	"res_184",
> +	"res_185",
> +	"res_186",
> +	"res_187",
> +	"res_188",
> +	"res_189",
> +	"res_190",
> +	"res_191",
> +	"res_192",
> +	"res_193",
> +	"res_194",
> +	"res_195",
> +	"res_196",
> +	"res_197",
> +	"res_198",
> +	"res_199",
> +	"res_200",
> +	"res_201",
> +	"res_202",
> +	"res_203",
> +	"res_204",
> +	"res_205",
> +	"res_206",
> +	"res_207",
> +	"res_208",
> +	"res_209",
> +	"res_210",
> +	"res_211",
> +	"res_212",
> +	"res_213",
> +	"res_214",
> +	"res_215",
> +	"res_216",
> +	"res_217",
> +	"res_218",
> +	"res_219",
> +	"res_220",
> +	"res_221",
> +	"res_222",
> +	"res_223",
> +	"oem_224",
> +	"oem_225",
> +	"oem_226",
> +	"oem_227",
> +	"oem_228",
> +	"oem_229",
> +	"oem_230",
> +	"oem_231",
> +	"oem_232",
> +	"oem_233",
> +	"oem_234",
> +	"oem_235",
> +	"oem_236",
> +	"oem_237",
> +	"oem_238",
> +	"oem_239",
> +	"oem_240",
> +	"oem_241",
> +	"oem_242",
> +	"oem_243",
> +	"oem_244",
> +	"oem_245",
> +	"oem_246",
> +	"oem_247",
> +	"oem_248",
> +	"oem_249",
> +	"oem_250",
> +	"oem_251",
> +	"oem_252",
> +	"oem_253",
> +	"oem_254",
> +	"oem_255",
> +};
> +
> +static const char *unit_types[TYPES_ARRAY_SZ] = {
> +	"none",
> +	"unspec",
> +	"celsius",
> +	"fahrenheit",
> +	"kelvin",
> +	"volts",
> +	"amps",
> +	"watts",
> +	"joules",
> +	"coulombs",
> +	"va",
> +	"nits",
> +	"lumens",
> +	"lux",
> +	"candelas",
> +	"kpa",
> +	"psi",
> +	"newtons",
> +	"cfm",
> +	"rpm",
> +	"hertz",
> +	"seconds",
> +	"minutes",
> +	"hours",
> +	"days",
> +	"weeks",
> +	"mils",
> +	"inches",
> +	"feet",
> +	"cubic_inches",
> +	"cubic_feet",
> +	"meters",
> +	"cubic_centimeters",
> +	"cubic_meters",
> +	"liters",
> +	"fluid_ounces",
> +	"radians",
> +	"steradians",
> +	"revolutions",
> +	"cycles",
> +	"gravities",
> +	"ounces",
> +	"pounds",
> +	"foot_pounds",
> +	"ounce_inches",
> +	"gauss",
> +	"gilberts",
> +	"henries",
> +	"farads",
> +	"ohms",
> +	"siemens",
> +	"moles",
> +	"becquerels",
> +	"ppm",
> +	"decibels",
> +	"dba",
> +	"dbc",
> +	"grays",
> +	"sieverts",
> +	"color_temp_kelvin",
> +	"bits",
> +	"bytes",
> +	"words",
> +	"dwords",
> +	"qwords",
> +	"percentage",
> +	"pascals",
> +	"counts",
> +	"grams",
> +	"newton_meters",
> +	"hits",
> +	"misses",
> +	"retries",
> +	"overruns",
> +	"underruns",
> +	"collisions",
> +	"packets",
> +	"messages",
> +	"chars",
> +	"errors",
> +	"corrected_err",
> +	"uncorrectable_err",
> +	"square_mils",
> +	"square_inches",
> +	"square_feet",
> +	"square_centimeters",
> +	"square_meters",
> +	"radians_per_secs",
> +	"beats_per_minute",
> +	"meters_per_secs_squared",
> +	"meters_per_secs",
> +	"cubic_meter_per_secs",
> +	"millimeters_mercury",
> +	"radians_per_secs_squared",
> +	"state",
> +	"bps",
> +	"res_96",
> +	"res_97",
> +	"res_98",
> +	"res_99",
> +	"res_100",
> +	"res_101",
> +	"res_102",
> +	"res_103",
> +	"res_104",
> +	"res_105",
> +	"res_106",
> +	"res_107",
> +	"res_108",
> +	"res_109",
> +	"res_110",
> +	"res_111",
> +	"res_112",
> +	"res_113",
> +	"res_114",
> +	"res_115",
> +	"res_116",
> +	"res_117",
> +	"res_118",
> +	"res_119",
> +	"res_120",
> +	"res_121",
> +	"res_122",
> +	"res_123",
> +	"res_124",
> +	"res_125",
> +	"res_126",
> +	"res_127",
> +	"res_128",
> +	"res_129",
> +	"res_130",
> +	"res_131",
> +	"res_132",
> +	"res_133",
> +	"res_134",
> +	"res_135",
> +	"res_136",
> +	"res_137",
> +	"res_138",
> +	"res_139",
> +	"res_140",
> +	"res_141",
> +	"res_142",
> +	"res_143",
> +	"res_144",
> +	"res_145",
> +	"res_146",
> +	"res_147",
> +	"res_148",
> +	"res_149",
> +	"res_150",
> +	"res_151",
> +	"res_152",
> +	"res_153",
> +	"res_154",
> +	"res_155",
> +	"res_156",
> +	"res_157",
> +	"res_158",
> +	"res_159",
> +	"res_160",
> +	"res_161",
> +	"res_162",
> +	"res_163",
> +	"res_164",
> +	"res_165",
> +	"res_166",
> +	"res_167",
> +	"res_168",
> +	"res_169",
> +	"res_170",
> +	"res_171",
> +	"res_172",
> +	"res_173",
> +	"res_174",
> +	"res_175",
> +	"res_176",
> +	"res_177",
> +	"res_178",
> +	"res_179",
> +	"res_180",
> +	"res_181",
> +	"res_182",
> +	"res_183",
> +	"res_184",
> +	"res_185",
> +	"res_186",
> +	"res_187",
> +	"res_188",
> +	"res_189",
> +	"res_190",
> +	"res_191",
> +	"res_192",
> +	"res_193",
> +	"res_194",
> +	"res_195",
> +	"res_196",
> +	"res_197",
> +	"res_198",
> +	"res_199",
> +	"res_200",
> +	"res_201",
> +	"res_202",
> +	"res_203",
> +	"res_204",
> +	"res_205",
> +	"res_206",
> +	"res_207",
> +	"res_208",
> +	"res_209",
> +	"res_210",
> +	"res_211",
> +	"res_212",
> +	"res_213",
> +	"res_214",
> +	"res_215",
> +	"res_216",
> +	"res_217",
> +	"res_218",
> +	"res_219",
> +	"res_220",
> +	"res_221",
> +	"res_222",
> +	"res_223",
> +	"res_224",
> +	"res_225",
> +	"res_226",
> +	"res_227",
> +	"res_228",
> +	"res_229",
> +	"res_230",
> +	"res_231",
> +	"res_232",
> +	"res_233",
> +	"res_234",
> +	"res_235",
> +	"res_236",
> +	"res_237",
> +	"res_238",
> +	"res_239",
> +	"res_240",
> +	"res_241",
> +	"res_242",
> +	"res_243",
> +	"res_244",
> +	"res_245",
> +	"res_246",
> +	"res_247",
> +	"res_248",
> +	"res_249",
> +	"res_250",
> +	"res_251",
> +	"res_252",
> +	"res_253",
> +	"res_254",
> +	"oem_unit",
> +};
> +
>  static struct inode *stlmfs_get_inode(struct super_block *sb)
>  {
>  	struct inode *inode = new_inode(sb);
> @@ -815,6 +1337,18 @@ DEFINE_TLM_CLASS(persistent_tlmo, "persistent", 0,
>  DEFINE_TLM_CLASS(value_tlmo, "value", 0,
>  		 S_IFREG | S_IRUSR, &de_read_fops, NULL);
>  
> +static inline struct dentry *
> +stlmfs_lookup_by_name(struct dentry *parent, const char *dname)
> +{
> +	struct qstr qstr;
> +
> +	qstr.name = dname;
> +	qstr.len = strlen(dname);
> +	qstr.hash = full_name_hash(parent, qstr.name, qstr.len);
> +
> +	return d_lookup(parent, &qstr);
> +}
> +
>  static int scmi_telemetry_de_populate(struct super_block *sb,
>  				      struct scmi_tlm_setup *tsp,
>  				      struct dentry *parent,
> @@ -1659,6 +2193,150 @@ static struct dentry *stlmfs_create_root_dentry(struct super_block *sb)
>  	return dentry;
>  }
>  
> +static int scmi_telemetry_de_subdir_symlink(struct super_block *sb,
> +					    struct scmi_tlm_setup *tsp,
> +					    const struct scmi_telemetry_de *de,
> +					    struct dentry *parent)
> +{
> +	struct dentry *dentry;
> +	struct inode *inode;
> +	int ret;
I notice that ret isn't assigned a value and the function returns ret without
initialising.

> +
> +	if (IS_ERR(parent))
> +		return 0;
> +
> +	char *name __free(kfree) = kasprintf(GFP_KERNEL, "0x%08X", de->info->id);
> +	if (!name)
> +		return -ENOMEM;
> +
> +	char *link __free(kfree) =
> +		kasprintf(GFP_KERNEL, "../../../../../des/0x%08X", de->info->id);
> +	if (!link)
> +		return -ENOMEM;
> +
> +	dentry = simple_start_creating(parent, name);
> +	if (IS_ERR(dentry))
> +		return PTR_ERR(dentry);
> +
> +	inode = stlmfs_get_inode(sb);
> +	if (unlikely(!inode)) {
> +		dev_err(tsp->dev,
> +			"out of free dentries, cannot create '%s'", name);
> +		return stlmfs_failed_creating(dentry);
> +	}
> +
> +	inode->i_mode = S_IFLNK | 0777;
> +	inode->i_op = &simple_symlink_inode_operations;
> +	inode_init_owner(&nop_mnt_idmap, inode, NULL, inode->i_mode);
> +	inode->i_link = no_free_ptr(link);
> +
> +	//d_add(dentry, inode);
> +	d_make_persistent(dentry, inode);
> +
> +	simple_done_creating(dentry);
> +
> +	return ret;
> +}
> +
> +static struct dentry *
> +scmi_telemetry_topology_path_get(struct super_block *sb,
> +				 struct scmi_tlm_setup *tsp,
> +				 struct dentry *parent, const char *dname)
> +{
> +	struct dentry *dentry;
> +
> +	dentry = stlmfs_lookup_by_name(parent, dname);
> +	if (!dentry) {
> +		struct scmi_tlm_class *dir_tlm_cls __free(kfree) =
> +			kzalloc(sizeof(*dir_tlm_cls), GFP_KERNEL);
> +		if (!dir_tlm_cls)
> +			return NULL;
> +
> +		dir_tlm_cls->name = kasprintf(GFP_KERNEL, "%s", dname);
> +		if (!dir_tlm_cls->name)
> +			return NULL;
> +
> +		dir_tlm_cls->mode = S_IFDIR | S_IRWXU;
> +		dir_tlm_cls->flags = TLM_IS_DYNAMIC;
> +
> +		dentry = stlmfs_create_dentry(sb, tsp, parent,
> +					      dir_tlm_cls, NULL);
> +		if (!IS_ERR(dentry))
> +			retain_and_null_ptr(dir_tlm_cls);
> +	}
> +
> +	return dentry;
> +}
> +
> +static int scmi_telemetry_topology_add_node(struct super_block *sb,
> +					    struct scmi_tlm_instance *ti,
> +					    const struct scmi_telemetry_de *de)
> +{
> +	struct dentry *ctype, *cinst, *cunit, *dinst;
> +	struct scmi_tlm_de_info *dei = de->info;
> +	char inst_str[32];
> +	int ret;
> +
> +	/* by_compo_type/<COMPO_TYPE_STR>/ */
> +	ctype = scmi_telemetry_topology_path_get(sb, ti->tsp, ti->compo_dentry,
> +						 compo_types[dei->compo_type]);
> +	if (!ctype)
> +		return -ENOMEM;
> +
> +	/* by_compo_type/<COMPO_TYPE_STR>/<N>/ */
> +	snprintf(inst_str, 32, "%u", dei->compo_instance_id);
> +	cinst = scmi_telemetry_topology_path_get(sb, ti->tsp, ctype, inst_str);
> +	dput(ctype);
> +	if (!cinst)
> +		return -ENOMEM;
> +
> +	/* by_compo_type/<COMPO_TYPE_STR>/<N>/<DE_UNIT_TYPE_STR>/ */
> +	cunit = scmi_telemetry_topology_path_get(sb, ti->tsp, cinst,
> +						 unit_types[dei->unit]);
> +	dput(cinst);
> +	if (!cunit)
> +		return -ENOMEM;
> +
> +	/* by_compo_type/<COMPO_TYPE_STR>/<N>/<DE_UNIT_TYPE_STR>/<N> */
> +	snprintf(inst_str, 32, "%u", dei->instance_id);
> +	dinst = scmi_telemetry_topology_path_get(sb, ti->tsp, cunit, inst_str);
> +	dput(cunit);
> +	if (!dinst)
> +		return -ENOMEM;
> +
> +	ret = scmi_telemetry_de_subdir_symlink(sb, ti->tsp, de, dinst);
> +	dput(dinst);
> +
> +	return ret;
> +}
> +
> +DEFINE_TLM_CLASS(compo_dir_cls, "components", 0, S_IFDIR | S_IRWXU, NULL, NULL);
> +
> +static int scmi_telemetry_topology_view_add(struct scmi_tlm_instance *ti)
> +{
> +	const struct scmi_telemetry_res_info *rinfo;
> +	struct scmi_tlm_setup *tsp = ti->tsp;
> +	struct device *dev = tsp->dev;
> +
> +	rinfo = scmi_telemetry_res_info_get(tsp);
> +	if (!rinfo || !rinfo->fully_enumerated)
> +		return -ENODEV;
> +
> +	ti->compo_dentry =
> +		stlmfs_create_dentry(ti->sb, tsp, ti->top_dentry, &compo_dir_cls, NULL);
> +
> +	for (int i = 0; i < rinfo->num_des; i++) {
> +		int ret;
> +
> +		ret = scmi_telemetry_topology_add_node(ti->sb, ti, rinfo->des[i]);
> +		if (ret)
> +			dev_err(dev, "Fail to add node %s to topology. Skip.\n",
> +				rinfo->des[i]->info->name);
> +	}
> +
> +	return 0;
> +}
> +
>  static int scmi_tlm_root_dentries_initialize(struct scmi_tlm_instance *ti)
>  {
>  	struct scmi_tlm_setup *tsp = ti->tsp;
> @@ -1712,6 +2390,12 @@ static int scmi_telemetry_instance_register(struct super_block *sb,
>  			 ti->top_cls.name);
>  	}
>  
> +	ret = scmi_telemetry_topology_view_add(ti);
> +	if (ret)
> +		dev_warn(ti->tsp->dev,
> +			 "Failed to create topology view for instance %s.\n",
> +			 ti->top_cls.name);
> +
>  	return 0;
>  }
>  

I will continue reviewing,
Thanks,
Elif

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 01/17] firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value
  2026-01-14 11:46 ` [PATCH v2 01/17] firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value Cristian Marussi
@ 2026-01-19 11:18   ` Jonathan Cameron
  2026-01-19 15:43     ` Cristian Marussi
  2026-01-20  6:44     ` Dhruva Gole
  0 siblings, 2 replies; 35+ messages in thread
From: Jonathan Cameron @ 2026-01-19 11:18 UTC (permalink / raw)
  To: Cristian Marussi
  Cc: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
	sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	elif.topuz, lukasz.luba, philip.radford, souvik.chakravarty

On Wed, 14 Jan 2026 11:46:05 +0000
Cristian Marussi <cristian.marussi@arm.com> wrote:

> Add a common definition of SCMI_MAX_PROTOCOLS and use it all over the
> SCMI stack.
> 
> Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
Hi Cristian,

Mention the introduction of SCMI_PROTOCOL_LAST in the patch description
and probably say why it takes that value (which is much less than
the SCMI_MAX_PROTOCOLS value).

Jonathan

> ---
>  drivers/firmware/arm_scmi/notify.c | 4 +---
>  include/linux/scmi_protocol.h      | 3 +++
>  2 files changed, 4 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/firmware/arm_scmi/notify.c b/drivers/firmware/arm_scmi/notify.c
> index dee9f238f6fd..78e9e27dc9ec 100644
> --- a/drivers/firmware/arm_scmi/notify.c
> +++ b/drivers/firmware/arm_scmi/notify.c
> @@ -94,8 +94,6 @@
>  #include "common.h"
>  #include "notify.h"
>  
> -#define SCMI_MAX_PROTO		256
> -
>  #define PROTO_ID_MASK		GENMASK(31, 24)
>  #define EVT_ID_MASK		GENMASK(23, 16)
>  #define SRC_ID_MASK		GENMASK(15, 0)
> @@ -1673,7 +1671,7 @@ int scmi_notification_init(struct scmi_handle *handle)
>  	ni->gid = gid;
>  	ni->handle = handle;
>  
> -	ni->registered_protocols = devm_kcalloc(handle->dev, SCMI_MAX_PROTO,
> +	ni->registered_protocols = devm_kcalloc(handle->dev, SCMI_MAX_PROTOCOLS,
>  						sizeof(char *), GFP_KERNEL);
>  	if (!ni->registered_protocols)
>  		goto err;
> diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
> index aafaac1496b0..c6efe4f371ac 100644
> --- a/include/linux/scmi_protocol.h
> +++ b/include/linux/scmi_protocol.h
> @@ -926,8 +926,11 @@ enum scmi_std_protocol {
>  	SCMI_PROTOCOL_VOLTAGE = 0x17,
>  	SCMI_PROTOCOL_POWERCAP = 0x18,
>  	SCMI_PROTOCOL_PINCTRL = 0x19,
> +	SCMI_PROTOCOL_LAST = 0x7f,
>  };
>  
> +#define SCMI_MAX_PROTOCOLS	256
> +
>  enum scmi_system_events {
>  	SCMI_SYSTEM_SHUTDOWN,
>  	SCMI_SYSTEM_COLDRESET,


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 02/17] firmware: arm_scmi: Reduce the scope of protocols mutex
  2026-01-14 11:46 ` [PATCH v2 02/17] firmware: arm_scmi: Reduce the scope of protocols mutex Cristian Marussi
@ 2026-01-19 11:21   ` Jonathan Cameron
  2026-01-19 15:45     ` Cristian Marussi
  0 siblings, 1 reply; 35+ messages in thread
From: Jonathan Cameron @ 2026-01-19 11:21 UTC (permalink / raw)
  To: Cristian Marussi
  Cc: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
	sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	elif.topuz, lukasz.luba, philip.radford, souvik.chakravarty

On Wed, 14 Jan 2026 11:46:06 +0000
Cristian Marussi <cristian.marussi@arm.com> wrote:

> 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>
I haven't checked carefully that the new scope is appropriate but
as a change in of itself, the code is correct and clean.
With that in mind.
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>




^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 03/17] firmware: arm_scmi: Allow protocols to register for notifications
  2026-01-14 11:46 ` [PATCH v2 03/17] firmware: arm_scmi: Allow protocols to register for notifications Cristian Marussi
@ 2026-01-19 11:33   ` Jonathan Cameron
  2026-01-19 15:49     ` Cristian Marussi
  0 siblings, 1 reply; 35+ messages in thread
From: Jonathan Cameron @ 2026-01-19 11:33 UTC (permalink / raw)
  To: Cristian Marussi
  Cc: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
	sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	elif.topuz, lukasz.luba, philip.radford, souvik.chakravarty

On Wed, 14 Jan 2026 11:46:07 +0000
Cristian Marussi <cristian.marussi@arm.com> wrote:

> Allow protocols themselves to register for their own notifications and

and provide their own notifier callbacks.

> providing their own notifier callbacks. While at that, allow for a protocol
> to register events with compilation-time unknown report/event sizes: such
> events will use the maximum transport size.

I'm not keen on the 'while at that' part of the patch. In an ideal
world that's a separate patch.

One other comment inline.

Jonathan
p.s. You get to my review victim whilst I run a particularly annoying
bisection on the other screen (completely unrelated!) :)

> 
> Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
> ---
> 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    | 28 ++++++++++++++++++++-------
>  drivers/firmware/arm_scmi/notify.h    |  8 ++++++--
>  drivers/firmware/arm_scmi/protocols.h |  6 ++++++
>  5 files changed, 49 insertions(+), 9 deletions(-)
> 


> diff --git a/drivers/firmware/arm_scmi/notify.c b/drivers/firmware/arm_scmi/notify.c
> index 78e9e27dc9ec..e84b4dbefe82 100644
> --- a/drivers/firmware/arm_scmi/notify.c
> +++ b/drivers/firmware/arm_scmi/notify.c


> @@ -779,8 +787,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++)
> +	for (i = 0; i < ee->num_events; i++) {
> +		if (evt[i].max_payld_sz == 0) {
> +			payld_sz = max_msg_sz;
> +			break;
> +		}
>  		payld_sz = max_t(size_t, payld_sz, evt[i].max_payld_sz);

Everything here seems to already be a size_t.  It is rare that we actually need max_t over
max, and definitely not when all the types match.
		payld_sz = max(payl_sz, evt[i].max_payld_sz);

> +	}
>  	payld_sz += sizeof(struct scmi_event_header);


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 04/17] uapi: Add ARM SCMI definitions
  2026-01-14 11:46 ` [PATCH v2 04/17] uapi: Add ARM SCMI definitions Cristian Marussi
@ 2026-01-19 11:43   ` Jonathan Cameron
  2026-01-19 15:51     ` Cristian Marussi
  0 siblings, 1 reply; 35+ messages in thread
From: Jonathan Cameron @ 2026-01-19 11:43 UTC (permalink / raw)
  To: Cristian Marussi
  Cc: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
	sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	elif.topuz, lukasz.luba, philip.radford, souvik.chakravarty

On Wed, 14 Jan 2026 11:46:08 +0000
Cristian Marussi <cristian.marussi@arm.com> wrote:

> Add a number of structures and ioctls definitions used by the ARM
> SCMI Telemetry protocol.
> 
> Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>

A few drive by comments.

> diff --git a/include/uapi/linux/scmi.h b/include/uapi/linux/scmi.h
> new file mode 100644
> index 000000000000..e4e9939a1bf8
> --- /dev/null
> +++ b/include/uapi/linux/scmi.h
> @@ -0,0 +1,287 @@
> +/* 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>


> +/**
> + * scmi_tlm_intervals  - Update intervals descriptor
> + *
> + * @discrete: Flag to indicate the nature of the intervals described in
> + *	      @update_intervals.
> + *	      When 'false' @update_intervals is a triplet: min/max/step
> + * @pad: Padding fields to enforce alignment.
> + * @num: Number of entries of @update_intervals
> + * @update_intervals: A variably-sized array containing the update intervals
> + *
> + * Used by:
> + *	RW - SCMI_TLM_GET_INTRVS
> + *
> + * Supported by:
> + *	control/
> + *	groups/<N>/control
> + */
> +struct scmi_tlm_intervals {
> +	__u8 discrete;
> +	__u8 pad[3];
> +	__u32 num;

Trivial but this seems a little inconsistent. In other
'num' entries (e.g. num_des) below a more specific name
is used.

> +#define SCMI_TLM_UPDATE_INTVL_SEGMENT_LOW	0
> +#define SCMI_TLM_UPDATE_INTVL_SEGMENT_HIGH	1
> +#define SCMI_TLM_UPDATE_INTVL_SEGMENT_STEP	2
> +	__u32 update_intervals[] __counted_by(num);
> +};

> +
> +/**
> + * scmi_tlm_des_list  - List of all defined DEs
> + *
> + * @num_des: Number of entries in @des
> + * @des: An array containing descriptors for all defined DEs
> + *
> + * Used by:
> + *	RW - SCMI_TLM_GET_DE_LIST
> + *
> + * Supported by:
> + *	control/
> + */
> +struct scmi_tlm_des_list {
> +	__u32 num_des;
> +	struct scmi_tlm_de_info des[] __counted_by(num_des);
> +};
> +
> +/**
> + * scmi_tlm_de_sample - A DE reading
> + *
> + * @id: DE identifier
> + * @tstamp: DE reading timestamp (equal 0 is NOT supported)
> + * @val: Reading of the DE data value
> + *
> + * Used by:
> + *	RW - SCMI_TLM_GET_DE_VALUE
> + *
> + * Supported by:
> + *	control/
> + */
> +struct scmi_tlm_de_sample {
> +	__u32 id;

Packing issues maybe if this ever ends up on 32 bit machines.
Even more so once it's in an array below.

> +	__u64 tstamp;
> +	__u64 val;
> +};
> +
> +/**
> + * scmi_tlm_data_read - Bulk read of multiple DEs
> + *
> + * @num_samples: Number of entries returned in @samples
> + * @samples: An array of samples containing an entry for each DE that was
> + *	     enabled when the single sample read request was issued.
> + *
> + * Used by:
> + *	RW - SCMI_TLM_SINGLE_SAMPLE
> + *	RW - SCMI_TLM_BULK_READ
> + *
> + * Supported by:
> + *	control/
> + *	groups/<N>/control
> + */
> +struct scmi_tlm_data_read {
> +	__u32 num_samples;
> +	struct scmi_tlm_de_sample samples[] __counted_by(num_samples);
> +};

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 12/17] firmware: arm_scmi: Add Telemetry components view
  2026-01-16 13:35   ` Elif Topuz
@ 2026-01-19 15:42     ` Cristian Marussi
  0 siblings, 0 replies; 35+ messages in thread
From: Cristian Marussi @ 2026-01-19 15:42 UTC (permalink / raw)
  To: Elif Topuz
  Cc: Cristian Marussi, linux-kernel, linux-arm-kernel, arm-scmi,
	linux-fsdevel, sudeep.holla, james.quinlan, f.fainelli,
	vincent.guittot, etienne.carriere, peng.fan, michal.simek,
	dan.carpenter, d-gole, jonathan.cameron, lukasz.luba,
	philip.radford, souvik.chakravarty

On Fri, Jan 16, 2026 at 01:35:00PM +0000, Elif Topuz wrote:
> 
> Hi Cristian,

H Elif,

> 
> On 14/01/2026 11:46, Cristian Marussi wrote:
> > Add an alternative filesystem view for the discovered Data Events, where
> > the tree of DEs is laid out following the discovered topological order
> > instead of the existing flat layout.
> > 
> > Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
> > ---
> > v1 --> v2
> >  - Use new FS API
> >  - Introduce new stlmfs_lookup_by_name helper
> > ---
> >  .../firmware/arm_scmi/scmi_system_telemetry.c | 684 ++++++++++++++++++
> >  1 file changed, 684 insertions(+)
> > 
> > diff --git a/drivers/firmware/arm_scmi/scmi_system_telemetry.c b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
> > index 721de615bec3..1221520356fd 100644
> > --- a/drivers/firmware/arm_scmi/scmi_system_telemetry.c
> > +++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
> > @@ -174,6 +174,7 @@ struct scmi_tlm_inode {
> >   * @top_dentry: A reference to the top dentry for this instance.
> >   * @des_dentry: A reference to the DES dentry for this instance.
> >   * @grps_dentry: A reference to the groups dentry for this instance.
> > + * @compo_dentry: A reference to the components dentry for this instance.
> >   * @info: A handy reference to this instance SCMI Telemetry info data.
> >   *
> >   */
> > @@ -188,6 +189,7 @@ struct scmi_tlm_instance {
> >  	struct dentry *top_dentry;
> >  	struct dentry *des_dentry;
> >  	struct dentry *grps_dentry;
> > +	struct dentry *compo_dentry;
> >  	const struct scmi_telemetry_info *info;
> >  };
> >  
> > @@ -196,6 +198,526 @@ static int scmi_telemetry_instance_register(struct super_block *sb,
> >  
> >  static LIST_HEAD(scmi_telemetry_instances);
> >  
> > +#define TYPES_ARRAY_SZ		256
> > +
> > +static const char *compo_types[TYPES_ARRAY_SZ] = {
> > +	"unspec",
> > +	"cpu",
> > +	"cluster",
> > +	"gpu",
> > +	"npu",
> > +	"interconnnect",
> > +	"mem_cntrl",
> > +	"l1_cache",
> > +	"l2_cache",
> > +	"l3_cache",
> > +	"ll_cache",
> > +	"sys_cache",
> > +	"disp_cntrl",
> > +	"ipu",
> > +	"chiplet",
> > +	"package",
> > +	"soc",
> > +	"system",
> > +	"smcu",
> > +	"accel",
> > +	"battery",
> > +	"charger",
> > +	"pmic",
> > +	"board",
> > +	"memory",
> > +	"periph",
> > +	"periph_subc",
> > +	"lid",
> > +	"display",
> > +	"res_29",
> > +	"res_30",
> > +	"res_31",
> > +	"res_32",
> > +	"res_33",
> > +	"res_34",
> > +	"res_35",
> > +	"res_36",
> > +	"res_37",
> > +	"res_38",
> > +	"res_39",
> > +	"res_40",
> > +	"res_41",
> > +	"res_42",
> > +	"res_43",
> > +	"res_44",
> > +	"res_45",
> > +	"res_46",
> > +	"res_47",
> > +	"res_48",
> > +	"res_49",
> > +	"res_50",
> > +	"res_51",
> > +	"res_52",
> > +	"res_53",
> > +	"res_54",
> > +	"res_55",
> > +	"res_56",
> > +	"res_57",
> > +	"res_58",
> > +	"res_59",
> > +	"res_60",
> > +	"res_61",
> > +	"res_62",
> > +	"res_63",
> > +	"res_64",
> > +	"res_65",
> > +	"res_66",
> > +	"res_67",
> > +	"res_68",
> > +	"res_69",
> > +	"res_70",
> > +	"res_71",
> > +	"res_72",
> > +	"res_73",
> > +	"res_74",
> > +	"res_75",
> > +	"res_76",
> > +	"res_77",
> > +	"res_78",
> > +	"res_79",
> > +	"res_80",
> > +	"res_81",
> > +	"res_82",
> > +	"res_83",
> > +	"res_84",
> > +	"res_85",
> > +	"res_86",
> > +	"res_87",
> > +	"res_88",
> > +	"res_89",
> > +	"res_90",
> > +	"res_91",
> > +	"res_92",
> > +	"res_93",
> > +	"res_94",
> > +	"res_95",
> > +	"res_96",
> > +	"res_97",
> > +	"res_98",
> > +	"res_99",
> > +	"res_100",
> > +	"res_101",
> > +	"res_102",
> > +	"res_103",
> > +	"res_104",
> > +	"res_105",
> > +	"res_106",
> > +	"res_107",
> > +	"res_108",
> > +	"res_109",
> > +	"res_110",
> > +	"res_111",
> > +	"res_112",
> > +	"res_113",
> > +	"res_114",
> > +	"res_115",
> > +	"res_116",
> > +	"res_117",
> > +	"res_118",
> > +	"res_119",
> > +	"res_120",
> > +	"res_121",
> > +	"res_122",
> > +	"res_123",
> > +	"res_124",
> > +	"res_125",
> > +	"res_126",
> > +	"res_127",
> > +	"res_128",
> > +	"res_129",
> > +	"res_130",
> > +	"res_131",
> > +	"res_132",
> > +	"res_133",
> > +	"res_134",
> > +	"res_135",
> > +	"res_136",
> > +	"res_137",
> > +	"res_138",
> > +	"res_139",
> > +	"res_140",
> > +	"res_141",
> > +	"res_142",
> > +	"res_143",
> > +	"res_144",
> > +	"res_145",
> > +	"res_146",
> > +	"res_147",
> > +	"res_148",
> > +	"res_149",
> > +	"res_150",
> > +	"res_151",
> > +	"res_152",
> > +	"res_153",
> > +	"res_154",
> > +	"res_155",
> > +	"res_156",
> > +	"res_157",
> > +	"res_158",
> > +	"res_159",
> > +	"res_160",
> > +	"res_161",
> > +	"res_162",
> > +	"res_163",
> > +	"res_164",
> > +	"res_165",
> > +	"res_166",
> > +	"res_167",
> > +	"res_168",
> > +	"res_169",
> > +	"res_170",
> > +	"res_171",
> > +	"res_172",
> > +	"res_173",
> > +	"res_174",
> > +	"res_175",
> > +	"res_176",
> > +	"res_177",
> > +	"res_178",
> > +	"res_179",
> > +	"res_180",
> > +	"res_181",
> > +	"res_182",
> > +	"res_183",
> > +	"res_184",
> > +	"res_185",
> > +	"res_186",
> > +	"res_187",
> > +	"res_188",
> > +	"res_189",
> > +	"res_190",
> > +	"res_191",
> > +	"res_192",
> > +	"res_193",
> > +	"res_194",
> > +	"res_195",
> > +	"res_196",
> > +	"res_197",
> > +	"res_198",
> > +	"res_199",
> > +	"res_200",
> > +	"res_201",
> > +	"res_202",
> > +	"res_203",
> > +	"res_204",
> > +	"res_205",
> > +	"res_206",
> > +	"res_207",
> > +	"res_208",
> > +	"res_209",
> > +	"res_210",
> > +	"res_211",
> > +	"res_212",
> > +	"res_213",
> > +	"res_214",
> > +	"res_215",
> > +	"res_216",
> > +	"res_217",
> > +	"res_218",
> > +	"res_219",
> > +	"res_220",
> > +	"res_221",
> > +	"res_222",
> > +	"res_223",
> > +	"oem_224",
> > +	"oem_225",
> > +	"oem_226",
> > +	"oem_227",
> > +	"oem_228",
> > +	"oem_229",
> > +	"oem_230",
> > +	"oem_231",
> > +	"oem_232",
> > +	"oem_233",
> > +	"oem_234",
> > +	"oem_235",
> > +	"oem_236",
> > +	"oem_237",
> > +	"oem_238",
> > +	"oem_239",
> > +	"oem_240",
> > +	"oem_241",
> > +	"oem_242",
> > +	"oem_243",
> > +	"oem_244",
> > +	"oem_245",
> > +	"oem_246",
> > +	"oem_247",
> > +	"oem_248",
> > +	"oem_249",
> > +	"oem_250",
> > +	"oem_251",
> > +	"oem_252",
> > +	"oem_253",
> > +	"oem_254",
> > +	"oem_255",
> > +};
> > +
> > +static const char *unit_types[TYPES_ARRAY_SZ] = {
> > +	"none",
> > +	"unspec",
> > +	"celsius",
> > +	"fahrenheit",
> > +	"kelvin",
> > +	"volts",
> > +	"amps",
> > +	"watts",
> > +	"joules",
> > +	"coulombs",
> > +	"va",
> > +	"nits",
> > +	"lumens",
> > +	"lux",
> > +	"candelas",
> > +	"kpa",
> > +	"psi",
> > +	"newtons",
> > +	"cfm",
> > +	"rpm",
> > +	"hertz",
> > +	"seconds",
> > +	"minutes",
> > +	"hours",
> > +	"days",
> > +	"weeks",
> > +	"mils",
> > +	"inches",
> > +	"feet",
> > +	"cubic_inches",
> > +	"cubic_feet",
> > +	"meters",
> > +	"cubic_centimeters",
> > +	"cubic_meters",
> > +	"liters",
> > +	"fluid_ounces",
> > +	"radians",
> > +	"steradians",
> > +	"revolutions",
> > +	"cycles",
> > +	"gravities",
> > +	"ounces",
> > +	"pounds",
> > +	"foot_pounds",
> > +	"ounce_inches",
> > +	"gauss",
> > +	"gilberts",
> > +	"henries",
> > +	"farads",
> > +	"ohms",
> > +	"siemens",
> > +	"moles",
> > +	"becquerels",
> > +	"ppm",
> > +	"decibels",
> > +	"dba",
> > +	"dbc",
> > +	"grays",
> > +	"sieverts",
> > +	"color_temp_kelvin",
> > +	"bits",
> > +	"bytes",
> > +	"words",
> > +	"dwords",
> > +	"qwords",
> > +	"percentage",
> > +	"pascals",
> > +	"counts",
> > +	"grams",
> > +	"newton_meters",
> > +	"hits",
> > +	"misses",
> > +	"retries",
> > +	"overruns",
> > +	"underruns",
> > +	"collisions",
> > +	"packets",
> > +	"messages",
> > +	"chars",
> > +	"errors",
> > +	"corrected_err",
> > +	"uncorrectable_err",
> > +	"square_mils",
> > +	"square_inches",
> > +	"square_feet",
> > +	"square_centimeters",
> > +	"square_meters",
> > +	"radians_per_secs",
> > +	"beats_per_minute",
> > +	"meters_per_secs_squared",
> > +	"meters_per_secs",
> > +	"cubic_meter_per_secs",
> > +	"millimeters_mercury",
> > +	"radians_per_secs_squared",
> > +	"state",
> > +	"bps",
> > +	"res_96",
> > +	"res_97",
> > +	"res_98",
> > +	"res_99",
> > +	"res_100",
> > +	"res_101",
> > +	"res_102",
> > +	"res_103",
> > +	"res_104",
> > +	"res_105",
> > +	"res_106",
> > +	"res_107",
> > +	"res_108",
> > +	"res_109",
> > +	"res_110",
> > +	"res_111",
> > +	"res_112",
> > +	"res_113",
> > +	"res_114",
> > +	"res_115",
> > +	"res_116",
> > +	"res_117",
> > +	"res_118",
> > +	"res_119",
> > +	"res_120",
> > +	"res_121",
> > +	"res_122",
> > +	"res_123",
> > +	"res_124",
> > +	"res_125",
> > +	"res_126",
> > +	"res_127",
> > +	"res_128",
> > +	"res_129",
> > +	"res_130",
> > +	"res_131",
> > +	"res_132",
> > +	"res_133",
> > +	"res_134",
> > +	"res_135",
> > +	"res_136",
> > +	"res_137",
> > +	"res_138",
> > +	"res_139",
> > +	"res_140",
> > +	"res_141",
> > +	"res_142",
> > +	"res_143",
> > +	"res_144",
> > +	"res_145",
> > +	"res_146",
> > +	"res_147",
> > +	"res_148",
> > +	"res_149",
> > +	"res_150",
> > +	"res_151",
> > +	"res_152",
> > +	"res_153",
> > +	"res_154",
> > +	"res_155",
> > +	"res_156",
> > +	"res_157",
> > +	"res_158",
> > +	"res_159",
> > +	"res_160",
> > +	"res_161",
> > +	"res_162",
> > +	"res_163",
> > +	"res_164",
> > +	"res_165",
> > +	"res_166",
> > +	"res_167",
> > +	"res_168",
> > +	"res_169",
> > +	"res_170",
> > +	"res_171",
> > +	"res_172",
> > +	"res_173",
> > +	"res_174",
> > +	"res_175",
> > +	"res_176",
> > +	"res_177",
> > +	"res_178",
> > +	"res_179",
> > +	"res_180",
> > +	"res_181",
> > +	"res_182",
> > +	"res_183",
> > +	"res_184",
> > +	"res_185",
> > +	"res_186",
> > +	"res_187",
> > +	"res_188",
> > +	"res_189",
> > +	"res_190",
> > +	"res_191",
> > +	"res_192",
> > +	"res_193",
> > +	"res_194",
> > +	"res_195",
> > +	"res_196",
> > +	"res_197",
> > +	"res_198",
> > +	"res_199",
> > +	"res_200",
> > +	"res_201",
> > +	"res_202",
> > +	"res_203",
> > +	"res_204",
> > +	"res_205",
> > +	"res_206",
> > +	"res_207",
> > +	"res_208",
> > +	"res_209",
> > +	"res_210",
> > +	"res_211",
> > +	"res_212",
> > +	"res_213",
> > +	"res_214",
> > +	"res_215",
> > +	"res_216",
> > +	"res_217",
> > +	"res_218",
> > +	"res_219",
> > +	"res_220",
> > +	"res_221",
> > +	"res_222",
> > +	"res_223",
> > +	"res_224",
> > +	"res_225",
> > +	"res_226",
> > +	"res_227",
> > +	"res_228",
> > +	"res_229",
> > +	"res_230",
> > +	"res_231",
> > +	"res_232",
> > +	"res_233",
> > +	"res_234",
> > +	"res_235",
> > +	"res_236",
> > +	"res_237",
> > +	"res_238",
> > +	"res_239",
> > +	"res_240",
> > +	"res_241",
> > +	"res_242",
> > +	"res_243",
> > +	"res_244",
> > +	"res_245",
> > +	"res_246",
> > +	"res_247",
> > +	"res_248",
> > +	"res_249",
> > +	"res_250",
> > +	"res_251",
> > +	"res_252",
> > +	"res_253",
> > +	"res_254",
> > +	"oem_unit",
> > +};
> > +
> >  static struct inode *stlmfs_get_inode(struct super_block *sb)
> >  {
> >  	struct inode *inode = new_inode(sb);
> > @@ -815,6 +1337,18 @@ DEFINE_TLM_CLASS(persistent_tlmo, "persistent", 0,
> >  DEFINE_TLM_CLASS(value_tlmo, "value", 0,
> >  		 S_IFREG | S_IRUSR, &de_read_fops, NULL);
> >  
> > +static inline struct dentry *
> > +stlmfs_lookup_by_name(struct dentry *parent, const char *dname)
> > +{
> > +	struct qstr qstr;
> > +
> > +	qstr.name = dname;
> > +	qstr.len = strlen(dname);
> > +	qstr.hash = full_name_hash(parent, qstr.name, qstr.len);
> > +
> > +	return d_lookup(parent, &qstr);
> > +}
> > +
> >  static int scmi_telemetry_de_populate(struct super_block *sb,
> >  				      struct scmi_tlm_setup *tsp,
> >  				      struct dentry *parent,
> > @@ -1659,6 +2193,150 @@ static struct dentry *stlmfs_create_root_dentry(struct super_block *sb)
> >  	return dentry;
> >  }
> >  
> > +static int scmi_telemetry_de_subdir_symlink(struct super_block *sb,
> > +					    struct scmi_tlm_setup *tsp,
> > +					    const struct scmi_telemetry_de *de,
> > +					    struct dentry *parent)
> > +{
> > +	struct dentry *dentry;
> > +	struct inode *inode;
> > +	int ret;
> I notice that ret isn't assigned a value and the function returns ret without
> initialising.
>

Yep, my bad: I think it is just a leftover from some last minute cleanup
I have done...it seems to cause only harmless (but noisy) error
messages...thanks for testing...I will fix !

> > +
> > +	if (IS_ERR(parent))
> >  

[snip]

> 
> I will continue reviewing,

Yes please, thanks for having a look.

Thanks,
Cristian

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 01/17] firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value
  2026-01-19 11:18   ` Jonathan Cameron
@ 2026-01-19 15:43     ` Cristian Marussi
  2026-01-20  6:44     ` Dhruva Gole
  1 sibling, 0 replies; 35+ messages in thread
From: Cristian Marussi @ 2026-01-19 15:43 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Cristian Marussi, linux-kernel, linux-arm-kernel, arm-scmi,
	linux-fsdevel, sudeep.holla, james.quinlan, f.fainelli,
	vincent.guittot, etienne.carriere, peng.fan, michal.simek,
	dan.carpenter, d-gole, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty

On Mon, Jan 19, 2026 at 11:18:27AM +0000, Jonathan Cameron wrote:
> On Wed, 14 Jan 2026 11:46:05 +0000
> Cristian Marussi <cristian.marussi@arm.com> wrote:
> 
> > Add a common definition of SCMI_MAX_PROTOCOLS and use it all over the
> > SCMI stack.
> > 
> > Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
> Hi Cristian,
> 
> Mention the introduction of SCMI_PROTOCOL_LAST in the patch description
> and probably say why it takes that value (which is much less than
> the SCMI_MAX_PROTOCOLS value).

I'll do.

Thanks,
Cristian

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 02/17] firmware: arm_scmi: Reduce the scope of protocols mutex
  2026-01-19 11:21   ` Jonathan Cameron
@ 2026-01-19 15:45     ` Cristian Marussi
  0 siblings, 0 replies; 35+ messages in thread
From: Cristian Marussi @ 2026-01-19 15:45 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Cristian Marussi, linux-kernel, linux-arm-kernel, arm-scmi,
	linux-fsdevel, sudeep.holla, james.quinlan, f.fainelli,
	vincent.guittot, etienne.carriere, peng.fan, michal.simek,
	dan.carpenter, d-gole, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty

On Mon, Jan 19, 2026 at 11:21:54AM +0000, Jonathan Cameron wrote:
> On Wed, 14 Jan 2026 11:46:06 +0000
> Cristian Marussi <cristian.marussi@arm.com> wrote:
> 
> > Currently the mutex dedicated to the protection of the list of registered
> > protocols is held during all the protocol initialization phase.

Hi Jonathan,

> > 
> > 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>
> I haven't checked carefully that the new scope is appropriate but
> as a change in of itself, the code is correct and clean.
> With that in mind.

I will double check after more testing.

> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
> 

Thanks for having a look.

Cristian

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 03/17] firmware: arm_scmi: Allow protocols to register for notifications
  2026-01-19 11:33   ` Jonathan Cameron
@ 2026-01-19 15:49     ` Cristian Marussi
  0 siblings, 0 replies; 35+ messages in thread
From: Cristian Marussi @ 2026-01-19 15:49 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Cristian Marussi, linux-kernel, linux-arm-kernel, arm-scmi,
	linux-fsdevel, sudeep.holla, james.quinlan, f.fainelli,
	vincent.guittot, etienne.carriere, peng.fan, michal.simek,
	dan.carpenter, d-gole, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty

On Mon, Jan 19, 2026 at 11:33:26AM +0000, Jonathan Cameron wrote:
> On Wed, 14 Jan 2026 11:46:07 +0000
> Cristian Marussi <cristian.marussi@arm.com> wrote:
> 
> > Allow protocols themselves to register for their own notifications and
> 
> and provide their own notifier callbacks.
> 
> > providing their own notifier callbacks. While at that, allow for a protocol
> > to register events with compilation-time unknown report/event sizes: such
> > events will use the maximum transport size.
> 
> I'm not keen on the 'while at that' part of the patch. In an ideal
> world that's a separate patch.

Yes indeed...it was tempting to do it together with the rework since it
was the only usecase that triggered the 'while-at' change...

...but this series in general needs more splitting both at the protocol
level and at the FS level (once I get some feedback from FS guys) so I
will split this out too.

> 
> One other comment inline.
> 
> Jonathan
> p.s. You get to my review victim whilst I run a particularly annoying
> bisection on the other screen (completely unrelated!) :)
> 
> > 
> > Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
> > ---
> > 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    | 28 ++++++++++++++++++++-------
> >  drivers/firmware/arm_scmi/notify.h    |  8 ++++++--
> >  drivers/firmware/arm_scmi/protocols.h |  6 ++++++
> >  5 files changed, 49 insertions(+), 9 deletions(-)
> > 
> 
> 
> > diff --git a/drivers/firmware/arm_scmi/notify.c b/drivers/firmware/arm_scmi/notify.c
> > index 78e9e27dc9ec..e84b4dbefe82 100644
> > --- a/drivers/firmware/arm_scmi/notify.c
> > +++ b/drivers/firmware/arm_scmi/notify.c
> 
> 
> > @@ -779,8 +787,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++)
> > +	for (i = 0; i < ee->num_events; i++) {
> > +		if (evt[i].max_payld_sz == 0) {
> > +			payld_sz = max_msg_sz;
> > +			break;
> > +		}
> >  		payld_sz = max_t(size_t, payld_sz, evt[i].max_payld_sz);
> 
> Everything here seems to already be a size_t.  It is rare that we actually need max_t over
> max, and definitely not when all the types match.
> 		payld_sz = max(payl_sz, evt[i].max_payld_sz);

...indeed...I will fix.

Thanks,
Cristian

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 04/17] uapi: Add ARM SCMI definitions
  2026-01-19 11:43   ` Jonathan Cameron
@ 2026-01-19 15:51     ` Cristian Marussi
  0 siblings, 0 replies; 35+ messages in thread
From: Cristian Marussi @ 2026-01-19 15:51 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Cristian Marussi, linux-kernel, linux-arm-kernel, arm-scmi,
	linux-fsdevel, sudeep.holla, james.quinlan, f.fainelli,
	vincent.guittot, etienne.carriere, peng.fan, michal.simek,
	dan.carpenter, d-gole, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty

On Mon, Jan 19, 2026 at 11:43:43AM +0000, Jonathan Cameron wrote:
> On Wed, 14 Jan 2026 11:46:08 +0000
> Cristian Marussi <cristian.marussi@arm.com> wrote:
> 
> > Add a number of structures and ioctls definitions used by the ARM
> > SCMI Telemetry protocol.
> > 
> > Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
> 
> A few drive by comments.

Hi,

> 
> > diff --git a/include/uapi/linux/scmi.h b/include/uapi/linux/scmi.h
> > new file mode 100644
> > index 000000000000..e4e9939a1bf8
> > --- /dev/null
> > +++ b/include/uapi/linux/scmi.h
> > @@ -0,0 +1,287 @@
> > +/* 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>
> 
> 
> > +/**
> > + * scmi_tlm_intervals  - Update intervals descriptor
> > + *
> > + * @discrete: Flag to indicate the nature of the intervals described in
> > + *	      @update_intervals.
> > + *	      When 'false' @update_intervals is a triplet: min/max/step
> > + * @pad: Padding fields to enforce alignment.
> > + * @num: Number of entries of @update_intervals
> > + * @update_intervals: A variably-sized array containing the update intervals
> > + *
> > + * Used by:
> > + *	RW - SCMI_TLM_GET_INTRVS
> > + *
> > + * Supported by:
> > + *	control/
> > + *	groups/<N>/control
> > + */
> > +struct scmi_tlm_intervals {
> > +	__u8 discrete;
> > +	__u8 pad[3];
> > +	__u32 num;
> 
> Trivial but this seems a little inconsistent. In other
> 'num' entries (e.g. num_des) below a more specific name
> is used.

Yes, agreed. I will fix.

> 
> > +#define SCMI_TLM_UPDATE_INTVL_SEGMENT_LOW	0
> > +#define SCMI_TLM_UPDATE_INTVL_SEGMENT_HIGH	1
> > +#define SCMI_TLM_UPDATE_INTVL_SEGMENT_STEP	2
> > +	__u32 update_intervals[] __counted_by(num);
> > +};
> 
> > +
> > +/**
> > + * scmi_tlm_des_list  - List of all defined DEs
> > + *
> > + * @num_des: Number of entries in @des
> > + * @des: An array containing descriptors for all defined DEs
> > + *
> > + * Used by:
> > + *	RW - SCMI_TLM_GET_DE_LIST
> > + *
> > + * Supported by:
> > + *	control/
> > + */
> > +struct scmi_tlm_des_list {
> > +	__u32 num_des;
> > +	struct scmi_tlm_de_info des[] __counted_by(num_des);
> > +};
> > +
> > +/**
> > + * scmi_tlm_de_sample - A DE reading
> > + *
> > + * @id: DE identifier
> > + * @tstamp: DE reading timestamp (equal 0 is NOT supported)
> > + * @val: Reading of the DE data value
> > + *
> > + * Used by:
> > + *	RW - SCMI_TLM_GET_DE_VALUE
> > + *
> > + * Supported by:
> > + *	control/
> > + */
> > +struct scmi_tlm_de_sample {
> > +	__u32 id;
> 
> Packing issues maybe if this ever ends up on 32 bit machines.
> Even more so once it's in an array below.
>

Oh yes, I missed this...
 
> > +	__u64 tstamp;
> > +	__u64 val;
> > +};
> > +

Thanks,
Cristian

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 05/17] firmware: arm_scmi: Add Telemetry protocol support
  2026-01-14 11:46 ` [PATCH v2 05/17] firmware: arm_scmi: Add Telemetry protocol support Cristian Marussi
@ 2026-01-19 16:29   ` Jonathan Cameron
  2026-01-19 18:25     ` Cristian Marussi
  2026-01-23 11:00   ` Elif Topuz
  1 sibling, 1 reply; 35+ messages in thread
From: Jonathan Cameron @ 2026-01-19 16:29 UTC (permalink / raw)
  To: Cristian Marussi
  Cc: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
	sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	elif.topuz, lukasz.luba, philip.radford, souvik.chakravarty

On Wed, 14 Jan 2026 11:46:09 +0000
Cristian Marussi <cristian.marussi@arm.com> wrote:

> Add basic support for SCMI V4.0 Telemetry protocol including SHMTI,
> FastChannels, Notifications and Single Sample Reads collection methods.
> 
> Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
> ---
> v1 --> v2
>  - Add proper ioread accessors for TDCF areas
>  - Rework resource allocation logic and lifecycle
>  - Introduce new resources accessors and res_get() operation to
>    implement lazy enumeration
>  - Support boot-on telemetry:
>    + Add DE_ENABLED_LIST cmd support
>    + Add CONFIG_GET cmd support
>    + Add TDCF_SCAN for best effort enumeration
>    + Harden driver against out-of-spec FW with out of spec cmds
>  - Use FCs list
>  - Rework de_info_lookup to a moer general de_lookup
>  - Fixed reset to use CONFIG_GET and DE_ENABLED_LIST to gather initial
>    state
>  - Using sign_extend32 helper
>  - Added counted_by marker where appropriate
> ---
>  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 | 2671 +++++++++++++++++++++++++

Ouch. Might be worth splitting this up into more bite sized pieces.

It's a bit of a take a deep breath before diving in patch at the moment.
So the following is rather superficial.

>  include/linux/scmi_protocol.h         |  188 +-
>  5 files changed, 2862 insertions(+), 2 deletions(-)
>  create mode 100644 drivers/firmware/arm_scmi/telemetry.c
> 



> diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
> new file mode 100644
> index 000000000000..16bcdcdc1dc3
> --- /dev/null
> +++ b/drivers/firmware/arm_scmi/telemetry.c
> @@ -0,0 +1,2671 @@

> +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 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));
> +
> +	/* Initialized to first descriptor */
> +	p->next = (void *)r->desc;

No need to cast to a void *
C always lets you do this implicitly if the target type is a void *.

> +
> +	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) {

I'd flip logic to reduce indent
	if (!ti->info.enabled)
		return 0;

	/*
	 *...

> +		/*
> +		 * When Telemetry is found already enabled on the platform,
> +		 * proceed with passive discovery using DE_ENABLED_LIST and
> +		 * TCDF 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 %ux10^%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_warn(dev, FW_BUG "Cannot query enabled DE list. Carry-on.\n");
> +
> +		/* Discover DEs on SHMTis: collect states/offsets/values */
> +		for (int id = 0; id < ti->num_shmti; id++) {
> +			ret = scmi_telemetry_shmti_scan(ti, id, 0, SCAN_DISCOVERY);
> +			if (ret)
> +				dev_warn(dev, "Failed discovery-scan of SHMTI ID:%d\n", id);
> +		}
> +	}
> +
> +	return 0;
> +}
}

> +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));
> +
> +	/*
> +	 * 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;
> +		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;
> +		struct scmi_tlm_intervals *intrvs __free(kfree) =
> +			kzalloc(sizeof(*intrvs) + inum * sizeof(__u32), GFP_KERNEL);

Unless this is going to get more complex, the __free() isn't doing anything useful
as there are no other error paths before the no_free_ptr().

> +		if (!intrvs)
> +			return -ENOMEM;
> +
> +		intrvs->num = inum;
> +		intrvs->discrete = discrete;
> +		st->max_resources = intrvs->num;
> +
> +		*p->intrvs = no_free_ptr(intrvs);
> +	}
> +
> +	return 0;

> +static int
> +scmi_tlm_enumerate_update_intervals(struct telemetry_info *ti,
> +				    struct scmi_tlm_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),
This alignment is unusual.  Given the length of that type name I'd do this as:

	iter = ph->hops->iter_response_init(ph, &ops, 0,
		TELEMETRY_LIST_UPDATE_INTERVALS,
		sizeof(struct scmi_msg_telemetry_update_intervals),
		&ipriv);

> +					    &ipriv);
> +	if (IS_ERR(iter))
> +		return PTR_ERR(iter);
> +
> +	return ph->hops->iter_response_run(iter);
> +}

> +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);
> +	struct scmi_telemetry_de *de;
> +
> +	ti->res_get(ti);
> +	de = xa_load(&ti->xa_des, id);
> +	if (!de)
> +		return NULL;
> +
> +	return de;

	return xa_load(&ti->xa_des, id);

> +}


> +static struct payload *
> +scmi_telemetry_nearest_blk_ts(struct telemetry_shmti *shmti,
> +			      struct payload *last_payld)
> +{
> +	struct payload *payld, *bts_payld = NULL;
> +	struct tdcf __iomem *tdcf = shmti->base;
> +	u32 *next;
> +
> +	/* Scan from start of TDCF payloads up to last_payld */
> +	payld = (struct payload *)tdcf->payld;

casting away the __iomem is usualy a bad idea.
Shouldn't a readl or similar be used to get next below.


> +	next = (u32 *)payld;
> +	while (payld < last_payld) {
> +		if (IS_BLK_TS(payld))
> +			bts_payld = payld;
> +
> +		next += USE_LINE_TS(payld) ?
> +			TS_LINE_DATA_PAYLD_WORDS : LINE_DATA_PAYLD_WORDS;
> +		payld = (struct payload *)next;
> +	}
> +
> +	return bts_payld;
> +}
> +
> +static struct telemetry_block_ts *
> +scmi_telemetry_blkts_lookup(struct device *dev, struct xarray *xa_bts,
> +			    struct payload *payld)
> +{
> +	struct telemetry_block_ts *bts;
> +
> +	bts = xa_load(xa_bts, (unsigned long)payld);
> +	if (!bts) {
> +		int ret;
> +
> +		bts = devm_kzalloc(dev, sizeof(*bts), GFP_KERNEL);

I'd not normally expect to see xa_insert using devm allocated memory
because you sort of hand ownership to the xa with that call so I'd expect
on exist we'd see a walk of the xa clearing out everything it is tracking.

> +		if (!bts)
> +			return NULL;
> +
> +		refcount_set(&bts->users, 1);
> +		bts->payld = payld;
> +		bts->xa_bts = xa_bts;
> +		mutex_init(&bts->mtx);
> +		ret = xa_insert(xa_bts, (unsigned long)payld, bts, GFP_KERNEL);
> +		if (ret) {
> +			devm_kfree(dev, bts);
> +			return NULL;
> +		}
> +	}
> +
> +	return bts;
> +}

> +
> +static void scmi_telemetry_tdcf_data_parse(struct telemetry_info *ti,
> +					   struct payload __iomem *payld,
> +					   struct telemetry_shmti *shmti,
> +					   enum scan_mode mode)
> +{
> +	bool ts_valid = TS_VALID(payld);
> +	struct telemetry_de *tde;
> +	bool discovered = false;
> +	u64 val, tstamp = 0;
> +	u32 de_id;
> +
> +	de_id = PAYLD_ID(payld);
> +	/* Is thi DE ID know ? */

That comment needs a rewrite.

> +	tde = scmi_telemetry_tde_lookup(ti, de_id);
> +	if (!tde) {
> +		if (mode != SCAN_DISCOVERY)
> +			return;
> +
> +		/* In SCAN_DISCOVERY mode we allocate new DEs for unknown IDs */
> +		tde = scmi_telemetry_tde_get(ti, de_id);
> +		if (IS_ERR(tde))
> +			return;
> +
> +		tde->de.info->id = de_id;
> +		tde->de.enabled = true;
> +		tde->de.tstamp_enabled = ts_valid;
> +		discovered = true;
> +	}
> +
> +	/* 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 *)payld - (void *)shmti->base;
> +
> +		dev_dbg(ti->ph->dev,
> +			"TDCF-updated DE_ID:0x%08X - shmti:%pX  offset:%u\n",
> +			tde->de.info->id, tde->base, tde->offset);
> +	}
> +
> +	if (discovered) {
> +		if (scmi_telemetry_tde_register(ti, tde)) {
> +			scmi_telemetry_free_tde_put(ti, tde);
> +			return;
> +		}
> +	}
> +
> +	scoped_guard(mutex, &tde->mtx) {
> +		if (tde->last_magic == shmti->last_magic)
> +			return;
> +	}
> +
> +	/* Data is always valid since we are NOT handling BLK TS lines here */
> +	val = LINE_DATA_GET(&payld->l);
> +	/* Collect the right TS */
> +	if (ts_valid) {
> +		if (USE_LINE_TS(payld)) {
> +			tstamp = LINE_TSTAMP_GET(&payld->tsl);
> +		} else if (USE_BLK_TS(payld)) {
> +			if (!tde->bts) {
> +				/*
> +				 * Scanning a TDCF looking for the nearest
> +				 * previous valid BLK_TS, after having found a
> +				 * USE_BLK_TS() payload, MUST succeed.
> +				 */
> +				tde->bts = scmi_telemetry_blkts_bind(ti->ph->dev,
> +								     shmti, payld,
> +								     &ti->xa_bts);
> +				if (WARN_ON(!tde->bts))
> +					return;
> +			}
> +
> +			tstamp = scmi_telemetry_blkts_read(tde->last_magic,
> +							   tde->bts);
> +		}
> +	}
> +
> +	guard(mutex)(&tde->mtx);
> +	tde->last_magic = shmti->last_magic;
> +	tde->last_val = val;
> +	if (tde->de.tstamp_enabled)

ternary perhaps
	tde->last_ts = tde->de.tstamp_enabled ? tstamp : 0;


> +		tde->last_ts = tstamp;
> +	else
> +		tde->last_ts = 0;
> +}




> +static inline void scmi_telemetry_de_data_fc_read(struct telemetry_de *tde,
> +						  u64 *tstamp, u64 *val)
> +{
> +	struct fc_tsline __iomem *fc = tde->base + tde->offset;
> +
> +	*val = LINE_DATA_GET(fc);
> +	if (tstamp) {
> +		if (tde->de.tstamp_support)
> +			*tstamp = LINE_TSTAMP_GET(fc);
> +		else
> +			*tstamp = 0;

		*tstamp = tde->de.tstam_support ? LINE_TIMESTAMP_GET(fc) : 0;

> +	}
> +}
> +
> +static void scmi_telemetry_scan_update(struct telemetry_info *ti, u64 ts)
> +{
> +	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, ts, SCAN_LOOKUP);
> +		if (ret)

Might as well simplify given value of ret only used here.

		if (scmi_telemetry_shmti_scan(ti, id, ts, SCAN_LOOKUP)) {
			dev_warn();

> +			dev_warn(ti->ph->dev,
> +				 "Failed update-scan of SHMTI ID:%d\n", id);
> +	}
> +
> +	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) {
> +		u64 val, tstamp;
> +
> +		if (!tde->de.enabled)
> +			continue;
> +
> +		scmi_telemetry_de_data_fc_read(tde, &tstamp, &val);
> +
> +		guard(mutex)(&tde->mtx);
> +		tde->last_val = val;
> +		if (tde->de.tstamp_enabled)
> +			tde->last_ts = tstamp;
> +		else
> +			tde->last_ts = 0;
> +	}
> +}
> +
> +/*
> + * TDCF and TS Line Management Notes
> + * ---------------------------------
> + *  (from a chat with ATG)

That's probably not a detail we want in the long term record, nice
and helpful as they are :)


> + *
> + * TCDF Payload Metadata notable bits:
> + *  - Bit[3]: USE BLK Tstamp
> + *  - Bit[2]: USE LINE Tstamp
> + *  - Bit[1]: Tstamp VALID
> + *
> + * CASE_1:
...


> +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;
> +		u64 val, tstamp;
> +		int ret;
> +
> +		de = rinfo->des[i];
> +		if (grp_id != SCMI_TLM_GRP_INVALID &&
> +		    (!de->grp || de->grp->info->id != grp_id))
> +			continue;
> +
> +		ret = scmi_telemetry_de_cached_read(ti, de, &tstamp, &val);
> +		if (ret)
> +			continue;
> +
> +		if (*num_samples == max_samples)
> +			return -ENOSPC;
> +
> +		samples[*num_samples].tstamp = tstamp;
> +		samples[*num_samples].val = val;
> +		samples[*num_samples].id = de->info->id;
Maybe worth doing
		samples[(*num_samples)++] = (struct scmi_telemetry_de_sample) {
			.tstamp = tstamp,
			.val = val,
			.id = de->info->id,
		};
so that it is immediately obvious you are filling whole record in (or zeroing
any other fields though not relevant here)

> +
> +		(*num_samples)++;
> +	}
> +
> +	return 0;
> +}

> +
> +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 grp_ignore;
> +	int ret;
> +
> +	if (!ti->info.enabled || !num_samples || !samples)
> +		return -EINVAL;
> +
> +	grp_ignore = grp_id == SCMI_TLM_GRP_INVALID ? true : false;
> +	if (!grp_ignore && 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 = grp_id;
> +	msg->control = TELEMETRY_ENABLE;
> +	msg->control |= grp_ignore ? TELEMETRY_SET_SELECTOR_ALL :
> +		TELEMETRY_SET_SELECTOR_GROUP;
> +	msg->control |= TELEMETRY_MODE_SINGLE;
> +	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;

Feels like that type might benefit form a shorter name
		struct scmi_msg_resp_telemetry_rd_comp
maybe?

> +
> +		/* Update cached DEs values from payload */
> +		if (r->num_dwords)
> +			scmi_telemetry_msg_payld_process(ti, r->num_dwords,
> +							 r->dwords, 0);
> +		/* Scan and update SMHTIs and FCs */
> +		scmi_telemetry_scan_update(ti, 0);
> +
> +		/* 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 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);
> +		/* Fetch agaon states state from platform.*/

Not sure what that comment means.

> +		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);
> +
> +	return ret;
> +}

> +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(r->dwords, p->array, r->num_dwords * sizeof(u32));

This needs le32 magic as you are copying from an array of those to an array
of unsigned int (if you do this as a memcpy that should be u32 to make the
size explicit).

memcpy_from_le32() should do what you need here.


> +
> +	*src_id = 0;
> +
> +	return r;
> +}


> diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
> index c6efe4f371ac..d58b81ffd81e 100644
> --- a/include/linux/scmi_protocol.h
> +++ b/include/linux/scmi_protocol.h

> +
> +/**
> + * 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.
> + * @state_get: retrieve the specific DE or GROUP state.
> + * @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.
> + * @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.
> + * @config_get: retrieve current telemetry configuration.
> + * @reset: reset configuration and telemetry data.
> + */
> +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);
> +	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 grp_ignore,
> +				    bool *enable,
> +				    unsigned int *update_interval_ms,
> +				    enum scmi_telemetry_collection *mode);
> +	int (*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,

I'm curious. What about this one makes it suitable for a __must_check?
Seems a bit random.

> +					  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);
> +	int (*config_get)(const struct scmi_protocol_handle *ph, bool *enabled,
> +			  int *mode, u32 *update_interval);
> +	int (*reset)(const struct scmi_protocol_handle *ph);
> +};

> +
> +struct scmi_telemetry_update_report {
> +	ktime_t		timestamp;
> +	unsigned int	agent_id;
> +	int		status;
> +	unsigned int	num_dwords;
> +	unsigned int	dwords[];

__counted_by not appropriate?

> +};
>  #endif /* _LINUX_SCMI_PROTOCOL_H */

> +	put_unaligned_le32(0, t->tx.buf);
> +	ret = ph->xops->do_xfer(ph, t);

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 05/17] firmware: arm_scmi: Add Telemetry protocol support
  2026-01-19 16:29   ` Jonathan Cameron
@ 2026-01-19 18:25     ` Cristian Marussi
  0 siblings, 0 replies; 35+ messages in thread
From: Cristian Marussi @ 2026-01-19 18:25 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Cristian Marussi, linux-kernel, linux-arm-kernel, arm-scmi,
	linux-fsdevel, sudeep.holla, james.quinlan, f.fainelli,
	vincent.guittot, etienne.carriere, peng.fan, michal.simek,
	dan.carpenter, d-gole, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty

On Mon, Jan 19, 2026 at 04:29:32PM +0000, Jonathan Cameron wrote:
> On Wed, 14 Jan 2026 11:46:09 +0000
> Cristian Marussi <cristian.marussi@arm.com> wrote:
> 
> > Add basic support for SCMI V4.0 Telemetry protocol including SHMTI,
> > FastChannels, Notifications and Single Sample Reads collection methods.
> > 

Hi Jonathan,

> > Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
> > ---
> > v1 --> v2
> >  - Add proper ioread accessors for TDCF areas
> >  - Rework resource allocation logic and lifecycle
> >  - Introduce new resources accessors and res_get() operation to
> >    implement lazy enumeration
> >  - Support boot-on telemetry:
> >    + Add DE_ENABLED_LIST cmd support
> >    + Add CONFIG_GET cmd support
> >    + Add TDCF_SCAN for best effort enumeration
> >    + Harden driver against out-of-spec FW with out of spec cmds
> >  - Use FCs list
> >  - Rework de_info_lookup to a moer general de_lookup
> >  - Fixed reset to use CONFIG_GET and DE_ENABLED_LIST to gather initial
> >    state
> >  - Using sign_extend32 helper
> >  - Added counted_by marker where appropriate
> > ---
> >  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 | 2671 +++++++++++++++++++++++++
> 
> Ouch. Might be worth splitting this up into more bite sized pieces.
> 
> It's a bit of a take a deep breath before diving in patch at the moment.
> So the following is rather superficial.

Yes, indeed this has to be split..usually I post the protocol unit in
one patch...but telemetry is rather big...I will split this on V3 once I
had some mofre feedback especially on the FS part and its supposed
location in the siource tree (now lives all in drivers and I suppose/guess
is frowned upon...also FS is a bit more split..but it can be further
split)

> 
> >  include/linux/scmi_protocol.h         |  188 +-
> >  5 files changed, 2862 insertions(+), 2 deletions(-)
> >  create mode 100644 drivers/firmware/arm_scmi/telemetry.c
> > 
> 
> 
> 
> > diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
> > new file mode 100644
> > index 000000000000..16bcdcdc1dc3
> > --- /dev/null
> > +++ b/drivers/firmware/arm_scmi/telemetry.c
> > @@ -0,0 +1,2671 @@
> 
> > +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 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));
> > +
> > +	/* Initialized to first descriptor */
> > +	p->next = (void *)r->desc;
> 
> No need to cast to a void *
> C always lets you do this implicitly if the target type is a void *.

Indeed...I think I recall that (as usual) is because I have to drop the
const part from r without upsetting the compiler...and next is just a
reference to iterate the payload so it is fine to be non const

> 
> > +
> > +	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) {
> 
> I'd flip logic to reduce indent
> 	if (!ti->info.enabled)
> 		return 0;

Yep, I will do.

> 
> 	/*
> 	 *...
> 
> > +		/*
> > +		 * When Telemetry is found already enabled on the platform,
> > +		 * proceed with passive discovery using DE_ENABLED_LIST and
> > +		 * TCDF 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 %ux10^%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_warn(dev, FW_BUG "Cannot query enabled DE list. Carry-on.\n");
> > +
> > +		/* Discover DEs on SHMTis: collect states/offsets/values */
> > +		for (int id = 0; id < ti->num_shmti; id++) {
> > +			ret = scmi_telemetry_shmti_scan(ti, id, 0, SCAN_DISCOVERY);
> > +			if (ret)
> > +				dev_warn(dev, "Failed discovery-scan of SHMTI ID:%d\n", id);
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> }
> 
> > +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));
> > +
> > +	/*
> > +	 * 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;
> > +		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;
> > +		struct scmi_tlm_intervals *intrvs __free(kfree) =
> > +			kzalloc(sizeof(*intrvs) + inum * sizeof(__u32), GFP_KERNEL);
> 
> Unless this is going to get more complex, the __free() isn't doing anything useful
> as there are no other error paths before the no_free_ptr().

Yes indeed...I was trying to be consistent...but here makes no sense.

> 
> > +		if (!intrvs)
> > +			return -ENOMEM;
> > +
> > +		intrvs->num = inum;
> > +		intrvs->discrete = discrete;
> > +		st->max_resources = intrvs->num;
> > +
> > +		*p->intrvs = no_free_ptr(intrvs);
> > +	}
> > +
> > +	return 0;
> 
> > +static int
> > +scmi_tlm_enumerate_update_intervals(struct telemetry_info *ti,
> > +				    struct scmi_tlm_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),
> This alignment is unusual.  Given the length of that type name I'd do this as:
> 
> 	iter = ph->hops->iter_response_init(ph, &ops, 0,
> 		TELEMETRY_LIST_UPDATE_INTERVALS,
> 		sizeof(struct scmi_msg_telemetry_update_intervals),
> 		&ipriv);
> 

Ok.

> > +					    &ipriv);
> > +	if (IS_ERR(iter))
> > +		return PTR_ERR(iter);
> > +
> > +	return ph->hops->iter_response_run(iter);
> > +}
> 
> > +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);
> > +	struct scmi_telemetry_de *de;
> > +
> > +	ti->res_get(ti);
> > +	de = xa_load(&ti->xa_des, id);
> > +	if (!de)
> > +		return NULL;
> > +
> > +	return de;
> 
> 	return xa_load(&ti->xa_des, id);

Agreed.

> 
> > +}
> 
> 
> > +static struct payload *
> > +scmi_telemetry_nearest_blk_ts(struct telemetry_shmti *shmti,
> > +			      struct payload *last_payld)
> > +{
> > +	struct payload *payld, *bts_payld = NULL;
> > +	struct tdcf __iomem *tdcf = shmti->base;
> > +	u32 *next;
> > +
> > +	/* Scan from start of TDCF payloads up to last_payld */
> > +	payld = (struct payload *)tdcf->payld;
> 
> casting away the __iomem is usualy a bad idea.
> Shouldn't a readl or similar be used to get next below.

Yes I still have to properly rework a bit of these __iomem access paths
in a more consistent way (sparse/smatch still screams a lot)...

..all the below accessors macros are defined to use transparently an
ioread32() or similar...next is just a reference to track the payld
is only payload that is effectly accessed and needs proper iomem
accessors...

> 
> 
> > +	next = (u32 *)payld;
> > +	while (payld < last_payld) {
> > +		if (IS_BLK_TS(payld))
> > +			bts_payld = payld;
> > +
> > +		next += USE_LINE_TS(payld) ?
> > +			TS_LINE_DATA_PAYLD_WORDS : LINE_DATA_PAYLD_WORDS;
> > +		payld = (struct payload *)next;
> > +	}
> > +
> > +	return bts_payld;
> > +}
> > +
> > +static struct telemetry_block_ts *
> > +scmi_telemetry_blkts_lookup(struct device *dev, struct xarray *xa_bts,
> > +			    struct payload *payld)
> > +{
> > +	struct telemetry_block_ts *bts;
> > +
> > +	bts = xa_load(xa_bts, (unsigned long)payld);
> > +	if (!bts) {
> > +		int ret;
> > +
> > +		bts = devm_kzalloc(dev, sizeof(*bts), GFP_KERNEL);
> 
> I'd not normally expect to see xa_insert using devm allocated memory
> because you sort of hand ownership to the xa with that call so I'd expect
> on exist we'd see a walk of the xa clearing out everything it is tracking.

Good point...I have to say, though, to my excuse, that this part of the protocol
code related to Block Timestamp (_blt_ts_) is knowingly very poorly curated and
reworked since V1 given that it has been deeply changed at the spec level in the
latest BETA spec (a few weks ago) so it will be changed substantially in the
next V3 when BETA will be supported...

...anyway...my bad I should have warned about this in the inline comments...now it
is only generically mentioned in the cover letter that the series is still implementing
ALPA_0 spec.

> 
> > +		if (!bts)
> > +			return NULL;
> > +
> > +		refcount_set(&bts->users, 1);
> > +		bts->payld = payld;
> > +		bts->xa_bts = xa_bts;
> > +		mutex_init(&bts->mtx);
> > +		ret = xa_insert(xa_bts, (unsigned long)payld, bts, GFP_KERNEL);
> > +		if (ret) {
> > +			devm_kfree(dev, bts);
> > +			return NULL;
> > +		}
> > +	}
> > +
> > +	return bts;
> > +}
> 
> > +
> > +static void scmi_telemetry_tdcf_data_parse(struct telemetry_info *ti,
> > +					   struct payload __iomem *payld,
> > +					   struct telemetry_shmti *shmti,
> > +					   enum scan_mode mode)
> > +{
> > +	bool ts_valid = TS_VALID(payld);
> > +	struct telemetry_de *tde;
> > +	bool discovered = false;
> > +	u64 val, tstamp = 0;
> > +	u32 de_id;
> > +
> > +	de_id = PAYLD_ID(payld);
> > +	/* Is thi DE ID know ? */
> 
> That comment needs a rewrite.
> 

Yes.

> > +	tde = scmi_telemetry_tde_lookup(ti, de_id);
> > +	if (!tde) {
> > +		if (mode != SCAN_DISCOVERY)
> > +			return;
> > +
> > +		/* In SCAN_DISCOVERY mode we allocate new DEs for unknown IDs */
> > +		tde = scmi_telemetry_tde_get(ti, de_id);
> > +		if (IS_ERR(tde))
> > +			return;
> > +
> > +		tde->de.info->id = de_id;
> > +		tde->de.enabled = true;
> > +		tde->de.tstamp_enabled = ts_valid;
> > +		discovered = true;
> > +	}
> > +
> > +	/* 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 *)payld - (void *)shmti->base;
> > +
> > +		dev_dbg(ti->ph->dev,
> > +			"TDCF-updated DE_ID:0x%08X - shmti:%pX  offset:%u\n",
> > +			tde->de.info->id, tde->base, tde->offset);
> > +	}
> > +
> > +	if (discovered) {
> > +		if (scmi_telemetry_tde_register(ti, tde)) {
> > +			scmi_telemetry_free_tde_put(ti, tde);
> > +			return;
> > +		}
> > +	}
> > +
> > +	scoped_guard(mutex, &tde->mtx) {
> > +		if (tde->last_magic == shmti->last_magic)
> > +			return;
> > +	}
> > +
> > +	/* Data is always valid since we are NOT handling BLK TS lines here */
> > +	val = LINE_DATA_GET(&payld->l);
> > +	/* Collect the right TS */
> > +	if (ts_valid) {
> > +		if (USE_LINE_TS(payld)) {
> > +			tstamp = LINE_TSTAMP_GET(&payld->tsl);
> > +		} else if (USE_BLK_TS(payld)) {
> > +			if (!tde->bts) {
> > +				/*
> > +				 * Scanning a TDCF looking for the nearest
> > +				 * previous valid BLK_TS, after having found a
> > +				 * USE_BLK_TS() payload, MUST succeed.
> > +				 */
> > +				tde->bts = scmi_telemetry_blkts_bind(ti->ph->dev,
> > +								     shmti, payld,
> > +								     &ti->xa_bts);
> > +				if (WARN_ON(!tde->bts))
> > +					return;
> > +			}
> > +
> > +			tstamp = scmi_telemetry_blkts_read(tde->last_magic,
> > +							   tde->bts);
> > +		}
> > +	}
> > +
> > +	guard(mutex)(&tde->mtx);
> > +	tde->last_magic = shmti->last_magic;
> > +	tde->last_val = val;
> > +	if (tde->de.tstamp_enabled)
> 
> ternary perhaps
> 	tde->last_ts = tde->de.tstamp_enabled ? tstamp : 0;
> 

Yes much better.

> 
> > +		tde->last_ts = tstamp;
> > +	else
> > +		tde->last_ts = 0;
> > +}
> 
> 
> 
> 
> > +static inline void scmi_telemetry_de_data_fc_read(struct telemetry_de *tde,
> > +						  u64 *tstamp, u64 *val)
> > +{
> > +	struct fc_tsline __iomem *fc = tde->base + tde->offset;
> > +
> > +	*val = LINE_DATA_GET(fc);
> > +	if (tstamp) {
> > +		if (tde->de.tstamp_support)
> > +			*tstamp = LINE_TSTAMP_GET(fc);
> > +		else
> > +			*tstamp = 0;
> 
> 		*tstamp = tde->de.tstam_support ? LINE_TIMESTAMP_GET(fc) : 0;

Indeed.

> 
> > +	}
> > +}
> > +
> > +static void scmi_telemetry_scan_update(struct telemetry_info *ti, u64 ts)
> > +{
> > +	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, ts, SCAN_LOOKUP);
> > +		if (ret)
> 
> Might as well simplify given value of ret only used here.
> 
> 		if (scmi_telemetry_shmti_scan(ti, id, ts, SCAN_LOOKUP)) {
> 			dev_warn();

I'll do.

> 
> > +			dev_warn(ti->ph->dev,
> > +				 "Failed update-scan of SHMTI ID:%d\n", id);
> > +	}
> > +
> > +	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) {
> > +		u64 val, tstamp;
> > +
> > +		if (!tde->de.enabled)
> > +			continue;
> > +
> > +		scmi_telemetry_de_data_fc_read(tde, &tstamp, &val);
> > +
> > +		guard(mutex)(&tde->mtx);
> > +		tde->last_val = val;
> > +		if (tde->de.tstamp_enabled)
> > +			tde->last_ts = tstamp;
> > +		else
> > +			tde->last_ts = 0;
> > +	}
> > +}
> > +
> > +/*
> > + * TDCF and TS Line Management Notes
> > + * ---------------------------------
> > + *  (from a chat with ATG)
> 
> That's probably not a detail we want in the long term record, nice
> and helpful as they are :)

Mmmm....it was to keep track of somehow of these discussions, the
reasons and the origin of the (supposed) truth...but yes I can strip
down the details :P ... especially because some of these clarifications
indeed have now been merged into the BETA spec so the wording around
this should be less ammbiguos in BETA...

> 
> 
> > + *
> > + * TCDF Payload Metadata notable bits:
> > + *  - Bit[3]: USE BLK Tstamp
> > + *  - Bit[2]: USE LINE Tstamp
> > + *  - Bit[1]: Tstamp VALID
> > + *
> > + * CASE_1:
> ...
> 
> 
> > +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;
> > +		u64 val, tstamp;
> > +		int ret;
> > +
> > +		de = rinfo->des[i];
> > +		if (grp_id != SCMI_TLM_GRP_INVALID &&
> > +		    (!de->grp || de->grp->info->id != grp_id))
> > +			continue;
> > +
> > +		ret = scmi_telemetry_de_cached_read(ti, de, &tstamp, &val);
> > +		if (ret)
> > +			continue;
> > +
> > +		if (*num_samples == max_samples)
> > +			return -ENOSPC;
> > +
> > +		samples[*num_samples].tstamp = tstamp;
> > +		samples[*num_samples].val = val;
> > +		samples[*num_samples].id = de->info->id;
> Maybe worth doing
> 		samples[(*num_samples)++] = (struct scmi_telemetry_de_sample) {
> 			.tstamp = tstamp,
> 			.val = val,
> 			.id = de->info->id,
> 		};
> so that it is immediately obvious you are filling whole record in (or zeroing
> any other fields though not relevant here)

wow...that is defintely a construct I would not have come up with :P
..thanks for the hint..

> 
> > +
> > +		(*num_samples)++;
> > +	}
> > +
> > +	return 0;
> > +}
> 
> > +
> > +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 grp_ignore;
> > +	int ret;
> > +
> > +	if (!ti->info.enabled || !num_samples || !samples)
> > +		return -EINVAL;
> > +
> > +	grp_ignore = grp_id == SCMI_TLM_GRP_INVALID ? true : false;
> > +	if (!grp_ignore && 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 = grp_id;
> > +	msg->control = TELEMETRY_ENABLE;
> > +	msg->control |= grp_ignore ? TELEMETRY_SET_SELECTOR_ALL :
> > +		TELEMETRY_SET_SELECTOR_GROUP;
> > +	msg->control |= TELEMETRY_MODE_SINGLE;
> > +	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;
> 
> Feels like that type might benefit form a shorter name
> 		struct scmi_msg_resp_telemetry_rd_comp
> maybe?

Yes...we try to stick to some standard msg/reply struct naming across protocols
definition ... but this leads to awfully long names...

> 
> > +
> > +		/* Update cached DEs values from payload */
> > +		if (r->num_dwords)
> > +			scmi_telemetry_msg_payld_process(ti, r->num_dwords,
> > +							 r->dwords, 0);
> > +		/* Scan and update SMHTIs and FCs */
> > +		scmi_telemetry_scan_update(ti, 0);
> > +
> > +		/* 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 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);
> > +		/* Fetch agaon states state from platform.*/
> 
> Not sure what that comment means.

That there was a problem between the screen and the keyboard :D ...
..I will fix...the meaning was simply...

	Fetch again the states from the platform.

... but seemed more gaelic from my mis-spelling :P 

> 
> > +		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);
> > +
> > +	return ret;
> > +}
> 
> > +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(r->dwords, p->array, r->num_dwords * sizeof(u32));
> 
> This needs le32 magic as you are copying from an array of those to an array
> of unsigned int (if you do this as a memcpy that should be u32 to make the
> size explicit).
> 
> memcpy_from_le32() should do what you need here.

So...this is the usual per-protocol events handler that is used to parse
the notification payload and build a notification report for the users
interested in this notification to use...

...in this series this report is really used by this protocol itself in
scmi_telemetry_msg_payld_process() to cache the received DE data...and
in this last routine all teh LE32 handling happens ...
...this is the only protocol indeed that makes use and process notifcation
reports from within...

...so it works properly at the end ...BUT is wrong as you pointed out since
any other user interested in these generic notification support will not
enjoy these post-processing conversion and also the report itself uses
u32 fields already so....no excuses..

I will fix...and sparse/smatch will scream a little less..

> 
> 
> > +
> > +	*src_id = 0;
> > +
> > +	return r;
> > +}
> 
> 
> > diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
> > index c6efe4f371ac..d58b81ffd81e 100644
> > --- a/include/linux/scmi_protocol.h
> > +++ b/include/linux/scmi_protocol.h
> 
> > +
> > +/**
> > + * 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.
> > + * @state_get: retrieve the specific DE or GROUP state.
> > + * @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.
> > + * @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.
> > + * @config_get: retrieve current telemetry configuration.
> > + * @reset: reset configuration and telemetry data.
> > + */
> > +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);
> > +	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 grp_ignore,
> > +				    bool *enable,
> > +				    unsigned int *update_interval_ms,
> > +				    enum scmi_telemetry_collection *mode);
> > +	int (*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,
> 
> I'm curious. What about this one makes it suitable for a __must_check?
> Seems a bit random.

So some of these read functions will return -EINVAL when called with an
invalid setup while in some other cases could return simply an empty buffer

e.g.:

	de_data_read () -> a single DE read...-EINVAL if the DE is NOT
			   enabled

	des_bulk_read() - > returns only the enabled DEs and so it can
			    return an empty buffer when NO DEs are enabled
			    BUT it returns -EINVAL if called when Telemetry
			    is disabled as a whole

	des_sample_get() -> same logic as des_bulk_read() BUT with async
			    messages

..so I would say..it is NOT random...but needs to be reviewed when the
__must_check is applied...since as an example is probably missing in
de_data_read()

> 
> > +					  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);
> > +	int (*config_get)(const struct scmi_protocol_handle *ph, bool *enabled,
> > +			  int *mode, u32 *update_interval);
> > +	int (*reset)(const struct scmi_protocol_handle *ph);
> > +};
> 

Thanks a lot Jonathan for having a look at this series.
It still needs some work to cleanup and split as you could see.

Thanks,
Cristian

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 01/17] firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value
  2026-01-19 11:18   ` Jonathan Cameron
  2026-01-19 15:43     ` Cristian Marussi
@ 2026-01-20  6:44     ` Dhruva Gole
  2026-01-20 10:55       ` Cristian Marussi
  1 sibling, 1 reply; 35+ messages in thread
From: Dhruva Gole @ 2026-01-20  6:44 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Cristian Marussi, linux-kernel, linux-arm-kernel, arm-scmi,
	linux-fsdevel, sudeep.holla, james.quinlan, f.fainelli,
	vincent.guittot, etienne.carriere, peng.fan, michal.simek,
	dan.carpenter, elif.topuz, lukasz.luba, philip.radford,
	souvik.chakravarty

On Jan 19, 2026 at 11:18:27 +0000, Jonathan Cameron wrote:
> On Wed, 14 Jan 2026 11:46:05 +0000
> Cristian Marussi <cristian.marussi@arm.com> wrote:
> 
> > Add a common definition of SCMI_MAX_PROTOCOLS and use it all over the
> > SCMI stack.
> > 
> > Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
> Hi Cristian,
> 
> Mention the introduction of SCMI_PROTOCOL_LAST in the patch description
> and probably say why it takes that value (which is much less than
> the SCMI_MAX_PROTOCOLS value).

Rather I wonder why even add it? Is it just like a documentation/ marker
or is some other usage even planned for it?

> 
> Jonathan
> 
> > ---
> >  drivers/firmware/arm_scmi/notify.c | 4 +---
> >  include/linux/scmi_protocol.h      | 3 +++
> >  2 files changed, 4 insertions(+), 3 deletions(-)
> > 
> > diff --git a/drivers/firmware/arm_scmi/notify.c b/drivers/firmware/arm_scmi/notify.c
> > index dee9f238f6fd..78e9e27dc9ec 100644
> > --- a/drivers/firmware/arm_scmi/notify.c
> > +++ b/drivers/firmware/arm_scmi/notify.c
> > @@ -94,8 +94,6 @@
> >  #include "common.h"
> >  #include "notify.h"
> >  
> > -#define SCMI_MAX_PROTO		256
> > -
> >  #define PROTO_ID_MASK		GENMASK(31, 24)
> >  #define EVT_ID_MASK		GENMASK(23, 16)
> >  #define SRC_ID_MASK		GENMASK(15, 0)
> > @@ -1673,7 +1671,7 @@ int scmi_notification_init(struct scmi_handle *handle)
> >  	ni->gid = gid;
> >  	ni->handle = handle;
> >  
> > -	ni->registered_protocols = devm_kcalloc(handle->dev, SCMI_MAX_PROTO,
> > +	ni->registered_protocols = devm_kcalloc(handle->dev, SCMI_MAX_PROTOCOLS,
> >  						sizeof(char *), GFP_KERNEL);
> >  	if (!ni->registered_protocols)
> >  		goto err;
> > diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
> > index aafaac1496b0..c6efe4f371ac 100644
> > --- a/include/linux/scmi_protocol.h
> > +++ b/include/linux/scmi_protocol.h
> > @@ -926,8 +926,11 @@ enum scmi_std_protocol {
> >  	SCMI_PROTOCOL_VOLTAGE = 0x17,
> >  	SCMI_PROTOCOL_POWERCAP = 0x18,
> >  	SCMI_PROTOCOL_PINCTRL = 0x19,
> > +	SCMI_PROTOCOL_LAST = 0x7f,
> >  };
> >  
> > +#define SCMI_MAX_PROTOCOLS	256
> > +
> >  enum scmi_system_events {
> >  	SCMI_SYSTEM_SHUTDOWN,
> >  	SCMI_SYSTEM_COLDRESET,
> 

-- 
Best regards,
Dhruva Gole
Texas Instruments Incorporated

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 01/17] firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value
  2026-01-20  6:44     ` Dhruva Gole
@ 2026-01-20 10:55       ` Cristian Marussi
  0 siblings, 0 replies; 35+ messages in thread
From: Cristian Marussi @ 2026-01-20 10:55 UTC (permalink / raw)
  To: Dhruva Gole
  Cc: Jonathan Cameron, Cristian Marussi, linux-kernel,
	linux-arm-kernel, arm-scmi, linux-fsdevel, sudeep.holla,
	james.quinlan, f.fainelli, vincent.guittot, etienne.carriere,
	peng.fan, michal.simek, dan.carpenter, elif.topuz, lukasz.luba,
	philip.radford, souvik.chakravarty

On Tue, Jan 20, 2026 at 12:14:19PM +0530, Dhruva Gole wrote:
> On Jan 19, 2026 at 11:18:27 +0000, Jonathan Cameron wrote:
> > On Wed, 14 Jan 2026 11:46:05 +0000
> > Cristian Marussi <cristian.marussi@arm.com> wrote:
> > 
> > > Add a common definition of SCMI_MAX_PROTOCOLS and use it all over the
> > > SCMI stack.
> > > 
> > > Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
> > Hi Cristian,
> > 
> > Mention the introduction of SCMI_PROTOCOL_LAST in the patch description
> > and probably say why it takes that value (which is much less than
> > the SCMI_MAX_PROTOCOLS value).
> 
> Rather I wonder why even add it? Is it just like a documentation/ marker
> or is some other usage even planned for it?

It was a cleanup related to some changes that I then dropped from this
public series...I kept it since it seemed fine, but I will definitely drop
it in V3 since it is no more related or needed by anything in this
series.

Thanks,
Cristian

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 05/17] firmware: arm_scmi: Add Telemetry protocol support
  2026-01-14 11:46 ` [PATCH v2 05/17] firmware: arm_scmi: Add Telemetry protocol support Cristian Marussi
  2026-01-19 16:29   ` Jonathan Cameron
@ 2026-01-23 11:00   ` Elif Topuz
  2026-03-26 13:32     ` Cristian Marussi
  1 sibling, 1 reply; 35+ messages in thread
From: Elif Topuz @ 2026-01-23 11:00 UTC (permalink / raw)
  To: Cristian Marussi, linux-kernel, linux-arm-kernel, arm-scmi,
	linux-fsdevel
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	jonathan.cameron, lukasz.luba, philip.radford, souvik.chakravarty,
	elif.topuz


Hi Cristian,

On 14/01/2026 11:46, Cristian Marussi wrote:
> Add basic support for SCMI V4.0 Telemetry protocol including SHMTI,
> FastChannels, Notifications and Single Sample Reads collection methods.
> 
> Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
> ---
> v1 --> v2
>  - Add proper ioread accessors for TDCF areas
>  - Rework resource allocation logic and lifecycle
>  - Introduce new resources accessors and res_get() operation to
>    implement lazy enumeration
>  - Support boot-on telemetry:
>    + Add DE_ENABLED_LIST cmd support
>    + Add CONFIG_GET cmd support
>    + Add TDCF_SCAN for best effort enumeration
>    + Harden driver against out-of-spec FW with out of spec cmds
>  - Use FCs list
>  - Rework de_info_lookup to a moer general de_lookup
>  - Fixed reset to use CONFIG_GET and DE_ENABLED_LIST to gather initial
>    state
>  - Using sign_extend32 helper
>  - Added counted_by marker where appropriate
> ---
>  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 | 2671 +++++++++++++++++++++++++
>  include/linux/scmi_protocol.h         |  188 +-
>  5 files changed, 2862 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 1085c70ca457..dd5da75a19b0 100644
> --- a/drivers/firmware/arm_scmi/driver.c
> +++ b/drivers/firmware/arm_scmi/driver.c
> @@ -3450,6 +3450,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);
>  }
> @@ -3468,6 +3469,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 afca1336267b..766b68a3084e 100644
> --- a/drivers/firmware/arm_scmi/protocols.h
> +++ b/drivers/firmware/arm_scmi/protocols.h
> @@ -385,5 +385,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..16bcdcdc1dc3
> --- /dev/null
> +++ b/drivers/firmware/arm_scmi/telemetry.c
> @@ -0,0 +1,2671 @@
> +// 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"
> +
> +/* 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)		((x) & BIT(31))
> +#define SUPPORTS_CONTINUOS_UPDATE(x)	((x) & BIT(30))
> +#define SUPPORTS_PER_GROUP_CONFIG(x)	((x) & BIT(18))
> +#define SUPPORTS_RESET(x)		((x) & BIT(17))
> +#define SUPPORTS_FC(x)			((x) & BIT(16))
> +};
> +
> +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;
> +};
> +
> +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)	((d)->attr_1 & BIT(31))
> +#define	IS_FC_SUPPORTED(d)	((d)->attr_1 & BIT(30))
> +#define	GET_DE_TYPE(d)		(le32_get_bits((d)->attr_1, GENMASK(29, 22)))
> +#define	IS_PERSISTENT(d)	((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 GET_DE_TSTAMP_EXP(d)						\
> +	({								\
> +		__u32 __signed_exp =					\
> +			FIELD_GET(GENMASK(4, 1), (d)->attr_1);		\
> +									\
> +		sign_extend32(__signed_exp, 3);				\
> +	})
> +#define	IS_TSTAMP_SUPPORTED(d)	((d)->attr_1 & BIT(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)	(!((x) & BIT(12)))
> +	__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 tdcf_de_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;
> +#define TELEMETRY_MODE_GET		(FIELD_GET(GENMASK(4, 1)))
> +	__le32 sampling_rate;
> +};
> +
> +/* TDCF */
> +
> +#define _I(__a)		(ioread32((void __iomem *)(__a)))
> +
> +#define TO_CPU_64(h, l)	((((u64)(h)) << 32) | (l))
> +
> +enum scan_mode {
> +	SCAN_LOOKUP,
> +	SCAN_UPDATE,
> +	SCAN_DISCOVERY
> +};
> +
> +struct fc_line {
> +	u32 data_low;
> +	u32 data_high;
> +};
> +
> +struct fc_tsline {
> +	u32 data_low;
> +	u32 data_high;
> +	u32 ts_low;
> +	u32 ts_high;
> +};
> +
> +struct line {
> +	u32 data_low;
> +	u32 data_high;
> +};
> +
> +struct blk_tsline {
> +	u32 ts_low;
> +	u32 ts_high;
> +};
> +
> +struct tsline {
> +	u32 data_low;
> +	u32 data_high;
> +	u32 ts_low;
> +	u32 ts_high;
> +};
> +
> +#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_TSTAMP_GET(f)	LINE_TSTAMP_GET(f)
> +
> +struct payload {
> +	u32 meta;
> +#define IS_BLK_TS(x)	(_I(&((x)->meta)) & BIT(4))
> +#define USE_BLK_TS(x)	(_I(&((x)->meta)) & BIT(3))
> +#define USE_LINE_TS(x)	(_I(&((x)->meta)) & BIT(2))
> +#define TS_VALID(x)	(_I(&((x)->meta)) & BIT(1))
> +#define	DATA_INVALID(x) (_I(&((x)->meta)) & BIT(0))
> +	u32 id;
> +	union {
> +		struct line l;
> +		struct tsline tsl;
> +		struct blk_tsline blk_tsl;
> +	};
> +};
> +
> +#define PAYLD_ID(x)	(_I(&(((struct payload *)(x))->id)))
> +
> +#define LINE_DATA_PAYLD_WORDS						       \
> +	((sizeof(u32) + sizeof(u32) + sizeof(struct line)) / sizeof(u32))
> +#define TS_LINE_DATA_PAYLD_WORDS					       \
> +	((sizeof(u32) + sizeof(u32) + sizeof(struct tsline)) / sizeof(u32))
> +
> +#define QWORDS_LINE_DATA_PAYLD		(LINE_DATA_PAYLD_WORDS / 2)
> +#define QWORDS_TS_LINE_DATA_PAYLD	(TS_LINE_DATA_PAYLD_WORDS / 2)
> +
> +struct prlg {
> +	u32 seq_low;
> +	u32 seq_high;
> +	u32 num_qwords;
> +	u32 _meta_header_high;
> +};
> +
> +struct eplg {
> +	u32 seq_low;
> +	u32 seq_high;
> +};
> +
> +#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_BAD_END_SEQ	GENMASK_U64(63, 0)
> +#define TDCF_START_SEQ_GET(x)						       \
> +	({								       \
> +		u64 _val;						       \
> +		struct prlg *_p = &((x)->prlg);				       \
> +									       \
> +		_val = TO_CPU_64(_I(&_p->seq_high), _I(&_p->seq_low));         \
> +		(_val);							       \
> +	})
> +
> +#define IS_BAD_START_SEQ(s)	((s) & 0x1)
> +
> +#define	TDCF_END_SEQ_GET(e)						       \
> +	({								       \
> +		u64 _val;						       \
> +		struct eplg *_e = (e);					       \
> +									       \
> +		_val = TO_CPU_64(_I(&_e->seq_high), _I(&_e->seq_low));	       \
> +		(_val);							       \
> +	 })
> +
> +struct telemetry_shmti {
> +	int id;
> +	void __iomem *base;
> +	u32 len;
> +	u64 last_magic;
> +};
> +
> +#define SHMTI_EPLG(s)						\
> +	({							\
> +		struct telemetry_shmti *_s = (s);		\
> +		void *_eplg;					\
> +								\
> +		_eplg = _s->base + _s->len - TDCF_EPLG_SZ;	\
> +		(_eplg);					\
> +	})
> +
> +struct telemetry_block_ts {
> +	refcount_t users;
> +	/* Protect block_ts accesses  */
> +	struct mutex mtx;
> +	u64 last_ts;
> +	u64 last_magic;
> +	struct payload __iomem *payld;
> +	struct xarray *xa_bts;
> +};
> +
> +struct telemetry_de {
> +	bool enumerated;
> +	bool cached;
> +	void __iomem *base;
> +	void __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;
> +	u64 last_magic;
> +	struct list_head item;
> +	struct telemetry_block_ts *bts;
> +	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;
> +	int num_shmti;
> +	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_bts;
> +	/* 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;
> +	struct notifier_block telemetry_nb;
> +	atomic_t rinfo_initializing;
> +	struct completion rinfo_initdone;
> +	struct scmi_telemetry_res_info __private *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);
> +
> +static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
> +				     unsigned int shmti_id, u64 ts,
> +				     enum scan_mode mode);
> +
> +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)
> +{
> +	int ret;
> +
> +	if (ti->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 */
> +	ti->rinfo->des[ti->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));
> +	}
> +
> +	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));
> +
> +	/* 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.info->grp_id = grp_id;
> +		tde->de.grp = &rinfo->grps[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->tstamp_exp = GET_DE_TSTAMP_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->de.tstamp_support = IS_TSTAMP_SUPPORTED(desc);
> +	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->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_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;
> +	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 = grp_id;
> +	msg->flags = grp_id == SCMI_TLM_GRP_INVALID ?
> +		TELEMETRY_GET_SELECTOR_ORPHANS : TELEMETRY_GET_SELECTOR_GROUP;
> +
> +	resp = t->rx.buf;
> +	ret = ph->xops->do_xfer(ph, t);
> +	if (!ret) {
> +		*enabled = resp->control & TELEMETRY_ENABLE;
> +		*active_update_interval =
> +			SCMI_TLM_GET_UPDATE_INTERVAL(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->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));
> +
> +	/*
> +	 * 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 = desc->mode == DE_ENABLED_WITH_TSTAMP;
> +
> +	ret = scmi_telemetry_tde_register(ti, tde);
> +	if (ret) {
> +		scmi_telemetry_free_tde_put(ti, tde);
> +		return ret;
> +	}
> +
> +	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)
> +{
> +	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", ti->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) {
> +		/*
> +		 * When Telemetry is found already enabled on the platform,
> +		 * proceed with passive discovery using DE_ENABLED_LIST and
> +		 * TCDF 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 %ux10^%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_warn(dev, FW_BUG "Cannot query enabled DE list. Carry-on.\n");
> +
> +		/* Discover DEs on SHMTis: collect states/offsets/values */
> +		for (int id = 0; id < ti->num_shmti; id++) {
> +			ret = scmi_telemetry_shmti_scan(ti, id, 0, SCAN_DISCOVERY);
> +			if (ret)
> +				dev_warn(dev, "Failed discovery-scan of SHMTI ID:%d\n", id);
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +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%04X%c",

I think it should be 0x%08X%c to print 8 bytes.

> +					rinfo->des[grp->des[j]]->info->id, term);
> +
> +			buf += len;
> +			bufsize -= len;
> +		}
> +	}
> +
> +	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;
> +}
> +
> +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_tlm_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 = 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));
> +
> +	/*
> +	 * 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;
> +		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;
> +		struct scmi_tlm_intervals *intrvs __free(kfree) =
> +			kzalloc(sizeof(*intrvs) + inum * sizeof(__u32), GFP_KERNEL);
> +		if (!intrvs)
> +			return -ENOMEM;
> +
> +		intrvs->num = inum;
> +		intrvs->discrete = discrete;
> +		st->max_resources = intrvs->num;
> +
> +		*p->intrvs = no_free_ptr(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_tlm_intervals *intrvs = *p->intrvs;
> +	unsigned int idx = st->loop_idx;
> +
> +	intrvs->update_intervals[st->desc_index + idx] = r->intervals[idx];
> +
> +	return 0;
> +}
> +
> +static int
> +scmi_tlm_enumerate_update_intervals(struct telemetry_info *ti,
> +				    struct scmi_tlm_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;
> +	}
> +
> +	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;
> +
> +	/* 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));
> +
> +	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->id = le32_to_cpu(desc->id);
> +	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)
> +		return -EINVAL;
> +
> +	addr = devm_ioremap(ph->dev, phys_addr, len);
> +	if (!addr)
> +		return -EADDRNOTAVAIL;
> +
> +	shmti->base = addr;
> +	shmti->len = len;
> +
> +	return 0;
> +}
> +
> +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;
> +	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;
> +	}
> +
> +	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);
> +	struct scmi_telemetry_de *de;
> +
> +	ti->res_get(ti);
> +	de = xa_load(&ti->xa_des, id);
> +	if (!de)
> +		return NULL;
> +
> +	return de;
> +}
> +
> +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 u64
> +scmi_telemetry_blkts_read(u64 magic, struct telemetry_block_ts *bts)
> +{
> +	if (WARN_ON(!bts || !refcount_read(&bts->users)))
> +		return 0;
> +
> +	guard(mutex)(&bts->mtx);
> +
> +	if (bts->last_magic == magic)
> +		return bts->last_ts;
> +
> +	bts->last_ts = BLK_TSTAMP_GET(&bts->payld->blk_tsl);
> +	bts->last_magic = magic;
> +
> +	return bts->last_ts;
> +}
> +
> +static void scmi_telemetry_blkts_update(u64 magic,
> +					struct telemetry_block_ts *bts)
> +{
> +	guard(mutex)(&bts->mtx);
> +
> +	if (bts->last_magic != magic) {
> +		bts->last_ts = BLK_TSTAMP_GET(&bts->payld->blk_tsl);
> +		bts->last_magic = magic;
> +	}
> +}
> +
> +static void scmi_telemetry_blkts_put(struct device *dev,
> +				     struct telemetry_block_ts *bts)
> +{
> +	if (refcount_dec_and_test(&bts->users)) {
> +		scoped_guard(mutex, &bts->mtx)
> +			xa_erase(bts->xa_bts, (unsigned long)bts->payld);
> +		devm_kfree(dev, bts);
> +	}
> +}
> +
> +static struct telemetry_block_ts *
> +scmi_telemetry_blkts_get(struct xarray *xa_bts, struct payload *payld)
> +{
> +	struct telemetry_block_ts *bts;
> +
> +	bts = xa_load(xa_bts, (unsigned long)payld);
> +	if (!bts)
> +		return NULL;
> +
> +	refcount_inc(&bts->users);
> +
> +	return bts;
> +}
> +
> +static struct payload *
> +scmi_telemetry_nearest_blk_ts(struct telemetry_shmti *shmti,
> +			      struct payload *last_payld)
> +{
> +	struct payload *payld, *bts_payld = NULL;
> +	struct tdcf __iomem *tdcf = shmti->base;
> +	u32 *next;
> +
> +	/* Scan from start of TDCF payloads up to last_payld */
> +	payld = (struct payload *)tdcf->payld;
> +	next = (u32 *)payld;
> +	while (payld < last_payld) {
> +		if (IS_BLK_TS(payld))
> +			bts_payld = payld;
> +
> +		next += USE_LINE_TS(payld) ?
> +			TS_LINE_DATA_PAYLD_WORDS : LINE_DATA_PAYLD_WORDS;
> +		payld = (struct payload *)next;
> +	}
> +
> +	return bts_payld;
> +}
> +
> +static struct telemetry_block_ts *
> +scmi_telemetry_blkts_lookup(struct device *dev, struct xarray *xa_bts,
> +			    struct payload *payld)
> +{
> +	struct telemetry_block_ts *bts;
> +
> +	bts = xa_load(xa_bts, (unsigned long)payld);
> +	if (!bts) {
> +		int ret;
> +
> +		bts = devm_kzalloc(dev, sizeof(*bts), GFP_KERNEL);
> +		if (!bts)
> +			return NULL;
> +
> +		refcount_set(&bts->users, 1);
> +		bts->payld = payld;
> +		bts->xa_bts = xa_bts;
> +		mutex_init(&bts->mtx);
> +		ret = xa_insert(xa_bts, (unsigned long)payld, bts, GFP_KERNEL);
> +		if (ret) {
> +			devm_kfree(dev, bts);
> +			return NULL;
> +		}
> +	}
> +
> +	return bts;
> +}
> +
> +static struct telemetry_block_ts *
> +scmi_telemetry_blkts_bind(struct device *dev, struct telemetry_shmti *shmti,
> +			  struct payload *payld, struct xarray *xa_bts)
> +{
> +	struct telemetry_block_ts *bts;
> +	struct payload *bts_payld;
> +
> +	/* Find the BLK_TS immediately preceding this DE payld */
> +	bts_payld = scmi_telemetry_nearest_blk_ts(shmti, payld);
> +	if (!bts_payld)
> +		return NULL;
> +
> +	bts = scmi_telemetry_blkts_get(xa_bts, bts_payld);
> +	if (bts)
> +		return bts;
> +
> +	return scmi_telemetry_blkts_lookup(dev, xa_bts, payld);
> +}
> +
> +static void scmi_telemetry_tdcf_blkts_parse(struct telemetry_info *ti,
> +					    struct payload __iomem *payld,
> +					    struct telemetry_shmti *shmti)
> +{
> +	struct telemetry_block_ts *bts;
> +
> +	/* Check for spec compliance */
> +	if (USE_LINE_TS(payld) || USE_BLK_TS(payld) ||
> +	    DATA_INVALID(payld) || (PAYLD_ID(payld) != 0))
> +		return;
> +
> +	/* A BLK_TS descriptor MUST be returned: it is found or it is crated */
> +	bts = scmi_telemetry_blkts_lookup(ti->ph->dev, &ti->xa_bts, payld);
> +	if (WARN_ON(!bts))
> +		return;
> +
> +	/* Update the descriptor with the lastest TS*/
> +	scmi_telemetry_blkts_update(shmti->last_magic, bts);
> +}
> +
> +static void scmi_telemetry_tdcf_data_parse(struct telemetry_info *ti,
> +					   struct payload __iomem *payld,
> +					   struct telemetry_shmti *shmti,
> +					   enum scan_mode mode)
> +{
> +	bool ts_valid = TS_VALID(payld);
> +	struct telemetry_de *tde;
> +	bool discovered = false;
> +	u64 val, tstamp = 0;
> +	u32 de_id;
> +
> +	de_id = PAYLD_ID(payld);
> +	/* Is thi DE ID know ? */
> +	tde = scmi_telemetry_tde_lookup(ti, de_id);
> +	if (!tde) {
> +		if (mode != SCAN_DISCOVERY)
> +			return;
> +
> +		/* In SCAN_DISCOVERY mode we allocate new DEs for unknown IDs */
> +		tde = scmi_telemetry_tde_get(ti, de_id);
> +		if (IS_ERR(tde))
> +			return;
> +
> +		tde->de.info->id = de_id;
> +		tde->de.enabled = true;
> +		tde->de.tstamp_enabled = ts_valid;
> +		discovered = true;
> +	}
> +
> +	/* 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 *)payld - (void *)shmti->base;
> +
> +		dev_dbg(ti->ph->dev,
> +			"TDCF-updated DE_ID:0x%08X - shmti:%pX  offset:%u\n",
> +			tde->de.info->id, tde->base, tde->offset);
> +	}
> +
> +	if (discovered) {
> +		if (scmi_telemetry_tde_register(ti, tde)) {
> +			scmi_telemetry_free_tde_put(ti, tde);
> +			return;
> +		}
> +	}
> +
> +	scoped_guard(mutex, &tde->mtx) {
> +		if (tde->last_magic == shmti->last_magic)
> +			return;
> +	}
> +
> +	/* Data is always valid since we are NOT handling BLK TS lines here */
> +	val = LINE_DATA_GET(&payld->l);
> +	/* Collect the right TS */
> +	if (ts_valid) {
> +		if (USE_LINE_TS(payld)) {
> +			tstamp = LINE_TSTAMP_GET(&payld->tsl);
> +		} else if (USE_BLK_TS(payld)) {
> +			if (!tde->bts) {
> +				/*
> +				 * Scanning a TDCF looking for the nearest
> +				 * previous valid BLK_TS, after having found a
> +				 * USE_BLK_TS() payload, MUST succeed.
> +				 */
> +				tde->bts = scmi_telemetry_blkts_bind(ti->ph->dev,
> +								     shmti, payld,
> +								     &ti->xa_bts);
> +				if (WARN_ON(!tde->bts))
> +					return;
> +			}
> +
> +			tstamp = scmi_telemetry_blkts_read(tde->last_magic,
> +							   tde->bts);
> +		}
> +	}
> +
> +	guard(mutex)(&tde->mtx);
> +	tde->last_magic = shmti->last_magic;
> +	tde->last_val = val;
> +	if (tde->de.tstamp_enabled)
> +		tde->last_ts = tstamp;
> +	else
> +		tde->last_ts = 0;
> +}
> +
> +static int scmi_telemetry_tdcf_line_parse(struct telemetry_info *ti,
> +					  struct payload __iomem *payld,
> +					  struct telemetry_shmti *shmti,
> +					  enum scan_mode mode)
> +{
> +	int used_qwords;
> +
> +	used_qwords = (USE_LINE_TS(payld) && TS_VALID(payld)) ?
> +		QWORDS_TS_LINE_DATA_PAYLD : QWORDS_LINE_DATA_PAYLD;

If I understand correctly from the "chat with ATG" comments below, when
timestamp is disabled (after DE is enabled with timestamp), physically ts line
is still there (use_line_ts = 1 and ts_valid = 0). That's why I think we need to
drop TS_VALID(payld) check. Otherwise qwords will be QWORDS_LINE_DATA_PAYLD
although ts line exists.

Also, for the alpha version of the spec, the combination of use_line_ts = 1 and
ts_valid = 0 doesn't seem to be possible as I understand. But I think beta
version allows us?

> +
> +	/* Invalid lines are not an error, could simply be disabled DEs */
> +	if (DATA_INVALID(payld))
> +		return used_qwords;
> +
> +	if (!IS_BLK_TS(payld))
> +		scmi_telemetry_tdcf_data_parse(ti, payld, shmti, mode);
> +	else
> +		scmi_telemetry_tdcf_blkts_parse(ti, payld, shmti);
> +
> +	return used_qwords;
> +}
> +
> +static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
> +				     unsigned int shmti_id, u64 ts,
> +				     enum scan_mode mode)

ts is not used in the function.

> +{
> +	struct telemetry_shmti *shmti = &ti->shmti[shmti_id];
> +	struct tdcf __iomem *tdcf = shmti->base;
> +	int retries = SCMI_TLM_TDCF_MAX_RETRIES;
> +	u64 startm = 0, endm = TDCF_BAD_END_SEQ;
> +	void *eplg = SHMTI_EPLG(shmti);
> +
> +	if (!tdcf)
> +		return -ENODEV;
> +
> +	do {
> +		unsigned int qwords;
> +		void __iomem *next;
> +
> +		/* 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))
> +			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);
> +			if (qwords < used_qwords)
> +				return -EINVAL;
> +
> +			next += used_qwords * 8;
> +			qwords -= used_qwords;
> +		}
> +
> +		endm = TDCF_END_SEQ_GET(eplg);
> +	} while (startm != endm && --retries);
> +
> +	if (startm != endm)
> +		return -EPROTO;
> +
> +	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)
> +			de->enabled = *enable;
> +		if (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 *payld;
> +			u32 offs;
> +
> +			offs = le32_to_cpu(resp->tdcf_de_offset);
> +			if (offs >= ti->shmti[sid].len - de->info->data_sz)
> +				return -EPROTO;
> +
> +			tde = to_tde(de);
> +			tde->base = ti->shmti[sid].base;
> +			tde->offset = 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) {
> +				tde->bts = scmi_telemetry_blkts_bind(ti->ph->dev,
> +								     &ti->shmti[sid],
> +								     payld,
> +								     &ti->xa_bts);
> +				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: node 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, 0, SCAN_UPDATE);
> +			if (ret)
> +				dev_warn(ti->ph->dev,
> +					 "Failed group-scan of SHMTI ID:%d\n", sid);
> +		}
> +	} else if (!is_group) {
> +		struct telemetry_de *tde;
> +
> +		tde = to_tde(de);
> +		if (tde->bts) {
> +			/* Unlink the related BLK_TS on disable */
> +			scmi_telemetry_blkts_put(ti->ph->dev, tde->bts);
> +			tde->bts = NULL;
> +		}
> +	}
> +
> +	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_group *grp;
> +	struct scmi_telemetry_de *de;
> +	unsigned int obj_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) {
> +		*tstamp_enabled_state = *tstamp;
> +		return 0;
> +	}
> +
> +	if (!is_group) {
> +		de = obj;
> +		obj_id = de->info->id;
> +	} else {
> +		grp = obj;
> +		obj_id = grp->info->id;
> +	}
> +
> +	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 |= 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 |= *tstamp ? DE_ENABLE_WTH_TSTAMP :
> +				DE_ENABLE_NO_TSTAMP;
> +		} else {
> +			msg->flags |= *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, de, resp, is_group);
> +		if (!ret) {
> +			/* Update cached state on success */
> +			if (enable)
> +				*enabled_state = *enable;
> +			if (tstamp)
> +				*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;
> +
> +	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 = DE_DISABLE_ALL;
> +	if (is_group)
> +		msg->flags |= GROUP_SELECTOR;
> +	ret = ph->xops->do_xfer(ph, t);
> +	if (!ret) {
> +		for (int i = 0; i < ti->info.base.num_des; i++)
> +			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 grp_ignore,
> +				    bool *enable,
> +				    unsigned int *update_interval_ms,
> +				    enum scmi_telemetry_collection *mode)
> +{
> +	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;
> +	bool tlm_enable;
> +	u32 interval;
> +	int ret;
> +
> +	if (mode && *mode == SCMI_TLM_NOTIFICATION &&
> +	    !ti->info.continuos_update_support)
> +		return -EINVAL;
> +
> +	if (res_id != SCMI_TLM_GRP_INVALID && res_id >= ti->info.base.num_groups)
> +		return -EINVAL;
> +
> +	if (res_id == SCMI_TLM_GRP_INVALID || grp_ignore) {
> +		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 = *update_interval_ms;
> +
> +	tlm_enable = enable ? *enable : ti->info.enabled;
> +	next_mode = mode ? *mode : *current_mode;
> +
> +	msg = t->tx.buf;
> +	msg->grp_id = res_id;
> +	msg->control = tlm_enable ? TELEMETRY_ENABLE : 0;
> +	msg->control |= grp_ignore ? TELEMETRY_SET_SELECTOR_ALL :
> +		TELEMETRY_SET_SELECTOR_GROUP;
> +	msg->control |= TELEMETRY_MODE_SET(next_mode);
> +	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 = interval;
> +	}
> +
> +	ph->xops->xfer_put(ph, t);
> +
> +	return ret;
> +}
> +
> +static inline void scmi_telemetry_de_data_fc_read(struct telemetry_de *tde,
> +						  u64 *tstamp, u64 *val)
> +{
> +	struct fc_tsline __iomem *fc = tde->base + tde->offset;
> +
> +	*val = LINE_DATA_GET(fc);
> +	if (tstamp) {
> +		if (tde->de.tstamp_support)
> +			*tstamp = LINE_TSTAMP_GET(fc);
> +		else
> +			*tstamp = 0;
> +	}
> +}
> +
> +static void scmi_telemetry_scan_update(struct telemetry_info *ti, u64 ts)
> +{
> +	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, ts, SCAN_LOOKUP);
> +		if (ret)
> +			dev_warn(ti->ph->dev,
> +				 "Failed update-scan of SHMTI ID:%d\n", id);
> +	}
> +
> +	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) {
> +		u64 val, tstamp;
> +
> +		if (!tde->de.enabled)
> +			continue;
> +
> +		scmi_telemetry_de_data_fc_read(tde, &tstamp, &val);
> +
> +		guard(mutex)(&tde->mtx);
> +		tde->last_val = val;
> +		if (tde->de.tstamp_enabled)
> +			tde->last_ts = tstamp;
> +		else
> +			tde->last_ts = 0;
> +	}
> +}
> +
> +/*
> + * TDCF and TS Line Management Notes
> + * ---------------------------------
> + *  (from a chat with ATG)
> + *
> + * TCDF Payload Metadata notable bits:
> + *  - Bit[3]: USE BLK Tstamp
> + *  - Bit[2]: USE LINE Tstamp
> + *  - Bit[1]: Tstamp VALID
> + *
> + * CASE_1:
> + * -------
> + *	+ A DE is enabled with timestamp disabled, so the TS fields are
> + *	  NOT present
> + *	  -> BIT[3:1] = 000b
> + *
> + *	  - 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:1] = 011b
> + *
> + *	  - 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 clock freq)
> + *	         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 (same clock freq)
> + *	         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] = 101b
> + *
> + *	+ 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)
> + *
> + * CASE_2:
> + * -------
> + *	+ A DE is enabled with LINE timestamp enabled, so the TS_Line is there
> + *	  -> BIT[3:1] = 011b
> + *	+ 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 physiclly there but NOT valid
> + *	  -> BIT[3:1] = 010b
> + *	+ the hole from the timestamp remain there unused until
> + *		- you enable again the TS so the hole is used again
> + *		  -> BIT[3:1] = 011b
> + *			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 int scmi_telemetry_tdcf_de_parse(struct telemetry_de *tde,
> +					u64 *tstamp, u64 *val)
> +{
> +	struct tdcf __iomem *tdcf = tde->base;
> +	u64 startm, endm;
> +	int retries = SCMI_TLM_TDCF_MAX_RETRIES;
> +
> +	if (!tdcf)
> +		return -ENODEV;
> +
> +	do {
> +		struct payload __iomem *payld;
> +
> +		/* 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))
> +			continue;
> +
> +		/* Has anything changed at all at the SHMTI level ? */
> +		scoped_guard(mutex, &tde->mtx) {
> +			if (tde->last_magic == startm) {
> +				*val = tde->last_val;
> +				if (tstamp)
> +					*tstamp = tde->last_ts;
> +				return 0;
> +			}
> +		}
> +
> +		payld = tde->base + tde->offset;
> +		if (DATA_INVALID(payld))
> +			return -EINVAL;
> +
> +		if (IS_BLK_TS(payld))
> +			return -EINVAL;
> +
> +		if (PAYLD_ID(payld) != tde->de.info->id)
> +			return -EINVAL;
> +
> +		/* Data is always valid since NOT handling BLK TS lines here */
> +		*val = LINE_DATA_GET(&payld->l);
> +		/* Collect the right TS */
> +		if (tstamp) {
> +			if (!TS_VALID(payld)) {
> +				*tstamp = 0;
> +			} else if (USE_LINE_TS(payld)) {
> +				*tstamp = LINE_TSTAMP_GET(&payld->tsl);
> +			} else if (USE_BLK_TS(payld)) {
> +				/*
> +				 * A valid line using BLK_TS should have been
> +				 * initialized with the related BLK_TS when
> +				 * enabled.
> +				 */
> +				if (WARN_ON(!tde->bts))
> +					return -EPROTO;
> +				*tstamp = scmi_telemetry_blkts_read(startm,
> +								    tde->bts);
> +			}
> +		}
> +
> +		endm = TDCF_END_SEQ_GET(tde->eplg);
> +	} while (startm != endm && --retries);
> +
> +	if (startm != endm)
> +		return -EPROTO;
> +
> +	guard(mutex)(&tde->mtx);
> +	tde->last_magic = startm;
> +	tde->last_val = *val;
> +	if (tstamp)
> +		tde->last_ts = *tstamp;
> +
> +	return 0;
> +}
> +
> +static int scmi_telemetry_de_read(struct telemetry_de *tde, u64 *tstamp, u64 *val)
> +{
> +	if (!tde->de.fc_support)
> +		return scmi_telemetry_tdcf_de_parse(tde, tstamp, val);
> +
> +	scmi_telemetry_de_data_fc_read(tde, tstamp, val);
> +
> +	return 0;
> +}
> +
> +static int scmi_telemetry_de_collect(struct telemetry_info *ti,
> +				     struct scmi_telemetry_de *de,
> +				     u64 *tstamp, u64 *val)
> +{
> +	struct telemetry_de *tde = to_tde(de);
> +
> +	if (!de->enabled)
> +		return -EINVAL;
> +
> +	/*
> +	 * DE readings returns cached values when:
> +	 *  - DE data value was retrieved via notification
> +	 */
> +	scoped_guard(mutex, &tde->mtx) {
> +		if (tde->cached) {
> +			*val = tde->last_val;
> +			if (tstamp)
> +				*tstamp = tde->last_ts;
> +			return 0;
> +		}
> +	}
> +
> +	return scmi_telemetry_de_read(tde, tstamp, val);
> +}
> +
> +static int scmi_telemetry_de_cached_read(struct telemetry_info *ti,
> +					 struct scmi_telemetry_de *de,
> +					 u64 *tstamp, u64 *val)
> +{
> +	struct telemetry_de *tde = to_tde(de);
> +
> +	if (!de->enabled)
> +		return -EINVAL;
> +
> +	guard(mutex)(&tde->mtx);
> +	*val = tde->last_val;
> +	if (tstamp)
> +		*tstamp = tde->last_ts;
> +
> +	return 0;
> +}
> +
> +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 -EINVAL;
> +
> +	de = xa_load(&ti->xa_des, sample->id);
> +	if (!de)
> +		return -ENODEV;
> +
> +	return scmi_telemetry_de_collect(ti, de, &sample->tstamp, &sample->val);
> +}
> +
> +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;
> +		u64 val, tstamp;
> +		int ret;
> +
> +		de = rinfo->des[i];
> +		if (grp_id != SCMI_TLM_GRP_INVALID &&
> +		    (!de->grp || de->grp->info->id != grp_id))
> +			continue;
> +
> +		ret = scmi_telemetry_de_cached_read(ti, de, &tstamp, &val);
> +		if (ret)
> +			continue;
> +
> +		if (*num_samples == max_samples)
> +			return -ENOSPC;
> +
> +		samples[*num_samples].tstamp = tstamp;
> +		samples[*num_samples].val = val;
> +		samples[*num_samples].id = de->info->id;
> +
> +		(*num_samples)++;
> +	}
> +
> +	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, 0);
> +
> +	/* 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,
> +				 ktime_t timestamp)
> +{
> +	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 += USE_LINE_TS(payld) ?
> +			TS_LINE_DATA_PAYLD_WORDS : LINE_DATA_PAYLD_WORDS;
> +
> +		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:%d\n",

enabled:%c?

> +				de_id, de ? (de->enabled ? 'Y' : 'N') : 'X');
> +			continue;
> +		}
> +
> +		tde = to_tde(de);
> +		guard(mutex)(&tde->mtx);
> +		tde->cached = true;
> +		tde->last_val = LINE_DATA_GET(&payld->tsl);
> +		/* TODO BLK_TS in notification payloads */
> +		if (USE_LINE_TS(payld) && TS_VALID(payld))
> +			tde->last_ts = LINE_TSTAMP_GET(&payld->tsl);
> +		else
> +			tde->last_ts = 0;
> +	}
> +}
> +
> +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 grp_ignore;
> +	int ret;
> +
> +	if (!ti->info.enabled || !num_samples || !samples)
> +		return -EINVAL;
> +
> +	grp_ignore = grp_id == SCMI_TLM_GRP_INVALID ? true : false;
> +	if (!grp_ignore && 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 = grp_id;
> +	msg->control = TELEMETRY_ENABLE;
> +	msg->control |= grp_ignore ? TELEMETRY_SET_SELECTOR_ALL :
> +		TELEMETRY_SET_SELECTOR_GROUP;
> +	msg->control |= TELEMETRY_MODE_SINGLE;
> +	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, r->num_dwords,
> +							 r->dwords, 0);
> +		/* Scan and update SMHTIs and FCs */
> +		scmi_telemetry_scan_update(ti, 0);
> +
> +		/* 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 int scmi_telemetry_config_get(const struct scmi_protocol_handle *ph,
> +				     bool *enabled, int *mode,
> +				     u32 *update_interval)
> +{
> +	return -EOPNOTSUPP;
> +}
> +
> +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;
> +	}
> +	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;
> +	}
> +}
> +
> +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);
> +		/* Fetch agaon states state 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);
> +
> +	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,
> +	.de_data_read = scmi_telemetry_de_data_read,
> +	.des_bulk_read = scmi_telemetry_des_bulk_read,
> +	.des_sample_get = scmi_telemetry_des_sample_get,
> +	.config_get = scmi_telemetry_config_get,
> +	.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(r->dwords, p->array, r->num_dwords * sizeof(u32));
> +
> +	*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) {
> +		dev_err(ti->ph->dev, "Bad Telemetry update notification - ret: %dn",
> +			er->status);
> +		return NOTIFY_DONE;
> +	}
> +
> +	/* Lookup the embedded DEs in the notification payload ... */
> +	if (er->num_dwords)
> +		scmi_telemetry_msg_payld_process(ti, er->num_dwords,
> +						 er->dwords, er->timestamp);
> +
> +	/* ...scan the SHMTI/FCs for any other DE updates. */
> +	if (ti->streaming_mode)
> +		scmi_telemetry_scan_update(ti, er->timestamp);
> +
> +	return NOTIFY_OK;
> +}
> +
> +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_tlm_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_tlm_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].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;
> +
> +	kfree(ti->tdes);
> +	kfree(ti->rinfo->des);
> +	kfree(ti->rinfo->dei_store);
> +	kfree(ti->rinfo->grps);
> +	kfree(ti->rinfo->grps_store);
> +
> +	kfree(ti->rinfo);
> +}
> +
> +static struct scmi_telemetry_res_info *
> +__scmi_telemetry_resources_get(struct telemetry_info *ti)
> +{
> +	return ACCESS_PRIVATE(ti, rinfo);
> +}
> +
> +static struct scmi_telemetry_res_info *
> +scmi_telemetry_resources_enumerate(struct telemetry_info *ti)
> +{
> +	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 enumerate DEs resources. Carry-on.\n");
> +		goto done;
> +	}
> +
> +	ret = scmi_telemetry_enumerate_groups_intervals(ti);
> +	if (ret) {
> +		dev_err(dev, FW_BUG "Cannot enumerate group intervals. Carry-on.\n");
> +		goto done;
> +	}
> +
> +	ti->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 ti->rinfo;
> +}
> +
> +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;
> +	u32 version;
> +	int ret;
> +
> +	ret = ph->xops->version_get(ph, &version);
> +	if (ret)
> +		return ret;
> +
> +	dev_dbg(dev, "Telemetry Version %d.%d\n",
> +		PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(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_warn(dev, FW_BUG "Cannot enumerate update intervals. Carry-on.\n");
> +
> +	ret = scmi_telemetry_enumerate_shmti(ti);
> +	if (ret)
> +		dev_warn(dev, FW_BUG "Cannot enumerate SHMTIs. Carry-on.\n");
> +
> +	ret = scmi_telemetry_initial_state_lookup(ti);
> +	if (ret)
> +		dev_warn(dev, FW_BUG "Cannot retrieve initial state. Carry-on.\n");
> +
> +	ti->info.base.version = version;
> +
> +	ret = ph->set_priv(ph, ti, version);
> +	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 = {
> +	.id = SCMI_PROTOCOL_TELEMETRY,
> +	.owner = THIS_MODULE,
> +	.instance_init = &scmi_telemetry_protocol_init,
> +	.ops = &tlm_proto_ops,
> +	.events = &tlm_protocol_events,
> +	.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 c6efe4f371ac..d58b81ffd81e 100644
> --- a/include/linux/scmi_protocol.h
> +++ b/include/linux/scmi_protocol.h
> @@ -2,17 +2,21 @@
>  /*
>   * 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>
>  
> +#include <uapi/linux/limits.h>
> +#include <uapi/linux/scmi.h>
> +
>  #define SCMI_MAX_STR_SIZE		64
>  #define SCMI_SHORT_NAME_MAX_SIZE	16
>  #define SCMI_MAX_NUM_RATES		16
> @@ -820,6 +824,178 @@ 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)				\
> +	(le32_get_bits((x), GENMASK(20, 5)))
> +#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
> +struct scmi_telemetry_group {
> +	bool enabled;
> +	bool tstamp_enabled;
> +	unsigned int *des;
> +	char *des_str;
> +	struct scmi_tlm_grp_info *info;
> +	unsigned int active_update_interval;
> +	struct scmi_tlm_intervals *intervals;
> +	enum scmi_telemetry_collection current_mode;
> +};
> +
> +struct scmi_telemetry_de {
> +	bool tstamp_support;
> +	bool fc_support;
> +	bool name_support;
> +	struct scmi_tlm_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_tlm_de_info *dei_store;
> +	unsigned int num_groups;
> +	struct scmi_telemetry_group *grps;
> +	struct scmi_tlm_grp_info *grps_store;
> +};
> +
> +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_tlm_base_info base;
> +	unsigned int active_update_interval;
> +	struct scmi_tlm_intervals *intervals;
> +	bool enabled;
> +	bool notif_enabled;
> +	enum scmi_telemetry_collection current_mode;
> +};
> +
> +struct scmi_telemetry_de_sample {
> +	u32 id;
> +	u64 tstamp;
> +	u64 val;
> +};
> +
> +/**
> + * 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.
> + * @state_get: retrieve the specific DE or GROUP state.
> + * @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.
> + * @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.
> + * @config_get: retrieve current telemetry configuration.
> + * @reset: reset configuration and telemetry data.
> + */
> +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);
> +	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 grp_ignore,
> +				    bool *enable,
> +				    unsigned int *update_interval_ms,
> +				    enum scmi_telemetry_collection *mode);
> +	int (*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);
> +	int (*config_get)(const struct scmi_protocol_handle *ph, bool *enabled,
> +			  int *mode, u32 *update_interval);
> +	int (*reset)(const struct scmi_protocol_handle *ph);
> +};
> +
>  /**
>   * struct scmi_notify_ops  - represents notifications' operations provided by
>   * SCMI core
> @@ -926,6 +1102,7 @@ enum scmi_std_protocol {
>  	SCMI_PROTOCOL_VOLTAGE = 0x17,
>  	SCMI_PROTOCOL_POWERCAP = 0x18,
>  	SCMI_PROTOCOL_PINCTRL = 0x19,
> +	SCMI_PROTOCOL_TELEMETRY = 0x1b,
>  	SCMI_PROTOCOL_LAST = 0x7f,
>  };
>  
> @@ -1027,6 +1204,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 {
> @@ -1114,4 +1292,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 */

Thanks,
Elif


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 07/17] firmware: arm_scmi: Use new Telemetry traces
  2026-01-14 11:46 ` [PATCH v2 07/17] firmware: arm_scmi: Use new Telemetry traces Cristian Marussi
@ 2026-01-23 11:43   ` Elif Topuz
  0 siblings, 0 replies; 35+ messages in thread
From: Elif Topuz @ 2026-01-23 11:43 UTC (permalink / raw)
  To: Cristian Marussi, linux-kernel, linux-arm-kernel, arm-scmi,
	linux-fsdevel
  Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
	etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
	jonathan.cameron, lukasz.luba, philip.radford, souvik.chakravarty,
	elif.topuz


Hi Cristian,

On 14/01/2026 11:46, Cristian Marussi wrote:
> Track failed SHMTI accesses and received notifications.
> 
> Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
> ---
>  drivers/firmware/arm_scmi/telemetry.c | 57 ++++++++++++++++++++++-----
>  1 file changed, 48 insertions(+), 9 deletions(-)
> 
> diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
> index 16bcdcdc1dc3..443e032a3553 100644
> --- a/drivers/firmware/arm_scmi/telemetry.c
> +++ b/drivers/firmware/arm_scmi/telemetry.c
> @@ -25,6 +25,8 @@
>  #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
>  
> @@ -1366,8 +1368,10 @@ static void scmi_telemetry_tdcf_blkts_parse(struct telemetry_info *ti,
>  
>  	/* Check for spec compliance */
>  	if (USE_LINE_TS(payld) || USE_BLK_TS(payld) ||
> -	    DATA_INVALID(payld) || (PAYLD_ID(payld) != 0))
> +	    DATA_INVALID(payld) || (PAYLD_ID(payld) != 0)) {
> +		trace_scmi_tlm_access(0, "BLK_TS_INVALID", 0, 0);
>  		return;
> +	}
>  
>  	/* A BLK_TS descriptor MUST be returned: it is found or it is crated */
>  	bts = scmi_telemetry_blkts_lookup(ti->ph->dev, &ti->xa_bts, payld);
> @@ -1376,6 +1380,9 @@ static void scmi_telemetry_tdcf_blkts_parse(struct telemetry_info *ti,
>  
>  	/* Update the descriptor with the lastest TS*/
>  	scmi_telemetry_blkts_update(shmti->last_magic, bts);
> +
> +	trace_scmi_tlm_collect(bts->last_ts, (u64)payld,
> +			       bts->last_magic, "SHMTI_BLK_TS");
>  }
>  
>  static void scmi_telemetry_tdcf_data_parse(struct telemetry_info *ti,
> @@ -1393,8 +1400,10 @@ static void scmi_telemetry_tdcf_data_parse(struct telemetry_info *ti,
>  	/* Is thi DE ID know ? */
>  	tde = scmi_telemetry_tde_lookup(ti, de_id);
>  	if (!tde) {
> -		if (mode != SCAN_DISCOVERY)
> +		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_get(ti, de_id);
> @@ -1462,6 +1471,8 @@ static void scmi_telemetry_tdcf_data_parse(struct telemetry_info *ti,
>  		tde->last_ts = tstamp;
>  	else
>  		tde->last_ts = 0;
> +
> +	trace_scmi_tlm_collect(0, tde->de.info->id, tde->last_val, "SHMTI_DE_UPDT");

tde->last_ts instead of 0?

>  }
>  
>  static int scmi_telemetry_tdcf_line_parse(struct telemetry_info *ti,
> @@ -1507,8 +1518,10 @@ static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
>  		fsleep((SCMI_TLM_TDCF_MAX_RETRIES - retries) * 1000);
>  
>  		startm = TDCF_START_SEQ_GET(tdcf);
> -		if (IS_BAD_START_SEQ(startm))
> +		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;
> @@ -1520,18 +1533,25 @@ static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
>  
>  			used_qwords = scmi_telemetry_tdcf_line_parse(ti, next,
>  								     shmti, mode);
> -			if (qwords < used_qwords)
> +			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(eplg);
> +		if (startm != endm)
> +			trace_scmi_tlm_access(0, "MSEQ_MISMATCH", startm, endm);
>  	} while (startm != endm && --retries);
>  
> -	if (startm != endm)
> +	if (startm != endm) {
> +		trace_scmi_tlm_access(0, "TDCF_SCAN_FAIL", startm, endm);
>  		return -EPROTO;
> +	}
>  
>  	return 0;
>  }
> @@ -1923,6 +1943,8 @@ static void scmi_telemetry_scan_update(struct telemetry_info *ti, u64 ts)
>  			tde->last_ts = tstamp;
>  		else
>  			tde->last_ts = 0;
> +
> +		trace_scmi_tlm_collect(ts, tde->de.info->id, tde->last_val, "FC_UPDATE");

tde->last_ts instead of ts?

>  	}
>  }
>  
> @@ -2001,8 +2023,11 @@ static int scmi_telemetry_tdcf_de_parse(struct telemetry_de *tde,
>  		fsleep((SCMI_TLM_TDCF_MAX_RETRIES - retries) * 1000);
>  
>  		startm = TDCF_START_SEQ_GET(tdcf);
> -		if (IS_BAD_START_SEQ(startm))
> +		if (IS_BAD_START_SEQ(startm)) {
> +			trace_scmi_tlm_access(tde->de.info->id, "MSEQ_BADSTART",
> +					      startm, 0);
>  			continue;
> +		}
>  
>  		/* Has anything changed at all at the SHMTI level ? */
>  		scoped_guard(mutex, &tde->mtx) {
> @@ -2018,11 +2043,16 @@ static int scmi_telemetry_tdcf_de_parse(struct telemetry_de *tde,
>  		if (DATA_INVALID(payld))
>  			return -EINVAL;
>  
> -		if (IS_BLK_TS(payld))
> +		if (IS_BLK_TS(payld)) {
> +			trace_scmi_tlm_access(tde->de.info->id,
> +					      "BAD_DE_META", 0, 0);
>  			return -EINVAL;
> +		}
>  
> -		if (PAYLD_ID(payld) != tde->de.info->id)
> +		if (PAYLD_ID(payld) != tde->de.info->id) {
> +			trace_scmi_tlm_access(tde->de.info->id, "DE_INVALID", 0, 0);
>  			return -EINVAL;
> +		}
>  
>  		/* Data is always valid since NOT handling BLK TS lines here */
>  		*val = LINE_DATA_GET(&payld->l);
> @@ -2046,10 +2076,16 @@ static int scmi_telemetry_tdcf_de_parse(struct telemetry_de *tde,
>  		}
>  
>  		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)
> +	if (startm != endm) {
> +		trace_scmi_tlm_access(tde->de.info->id, "TDCF_DE_FAIL",
> +				      startm, endm);
>  		return -EPROTO;
> +	}
>  
>  	guard(mutex)(&tde->mtx);
>  	tde->last_magic = startm;
> @@ -2230,6 +2266,9 @@ scmi_telemetry_msg_payld_process(struct telemetry_info *ti,
>  			tde->last_ts = LINE_TSTAMP_GET(&payld->tsl);
>  		else
>  			tde->last_ts = 0;
> +
> +		trace_scmi_tlm_collect(timestamp, tde->de.info->id, tde->last_val,
> +				       "MESSAGE");

tde->last_ts instead of timestamp? If I understand correctly, tde->last_ts
corresponds to the time coming from the platform. We have kernel time anyway
coming from ftrace format.

>  	}
>  }
>  

Thanks,
Elif

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 05/17] firmware: arm_scmi: Add Telemetry protocol support
  2026-01-23 11:00   ` Elif Topuz
@ 2026-03-26 13:32     ` Cristian Marussi
  0 siblings, 0 replies; 35+ messages in thread
From: Cristian Marussi @ 2026-03-26 13:32 UTC (permalink / raw)
  To: Elif Topuz
  Cc: Cristian Marussi, linux-kernel, linux-arm-kernel, arm-scmi,
	linux-fsdevel, sudeep.holla, james.quinlan, f.fainelli,
	vincent.guittot, etienne.carriere, peng.fan, michal.simek,
	dan.carpenter, d-gole, jonathan.cameron, lukasz.luba,
	philip.radford, souvik.chakravarty

On Fri, Jan 23, 2026 at 11:00:46AM +0000, Elif Topuz wrote:
> 
> Hi Cristian,

HI Elif,

a long overdue reply from me !

Thanks for having a look at this series...

> 
> On 14/01/2026 11:46, Cristian Marussi wrote:
> > Add basic support for SCMI V4.0 Telemetry protocol including SHMTI,
> > FastChannels, Notifications and Single Sample Reads collection methods.
> > 
> > Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
> > ---
> > v1 --> v2
> >  - Add proper ioread accessors for TDCF areas
> >  - Rework resource allocation logic and lifecycle
> >  - Introduce new resources accessors and res_get() operation to
> >    implement lazy enumeration
> >  - Support boot-on telemetry:
> >    + Add DE_ENABLED_LIST cmd support
> >    + Add CONFIG_GET cmd support
> >    + Add TDCF_SCAN for best effort enumeration
> >    + Harden driver against out-of-spec FW with out of spec cmds
> >  - Use FCs list
> >  - Rework de_info_lookup to a moer general de_lookup
> >  - Fixed reset to use CONFIG_GET and DE_ENABLED_LIST to gather initial
> >    state
> >  - Using sign_extend32 helper
> >  - Added counted_by marker where appropriate
> > ---
> >  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 | 2671 +++++++++++++++++++++++++
> >  include/linux/scmi_protocol.h         |  188 +-
> >  5 files changed, 2862 insertions(+), 2 deletions(-)
> >  create mode 100644 drivers/firmware/arm_scmi/telemetry.c
> > 

[snip]

> > +	/* 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%04X%c",
> 
> I think it should be 0x%08X%c to print 8 bytes.
> 

Good catch ! I will fix..

> > +					rinfo->des[grp->des[j]]->info->id, term);
> > +
> > +			buf += len;
> > +			bufsize -= len;
> > +		}
> > +	}
> > +

[snip]

> > +
> > +static int scmi_telemetry_tdcf_line_parse(struct telemetry_info *ti,
> > +					  struct payload __iomem *payld,
> > +					  struct telemetry_shmti *shmti,
> > +					  enum scan_mode mode)
> > +{
> > +	int used_qwords;
> > +
> > +	used_qwords = (USE_LINE_TS(payld) && TS_VALID(payld)) ?
> > +		QWORDS_TS_LINE_DATA_PAYLD : QWORDS_LINE_DATA_PAYLD;
> 
> If I understand correctly from the "chat with ATG" comments below, when
> timestamp is disabled (after DE is enabled with timestamp), physically ts line
> is still there (use_line_ts = 1 and ts_valid = 0). That's why I think we need to
> drop TS_VALID(payld) check. Otherwise qwords will be QWORDS_LINE_DATA_PAYLD
> although ts line exists.
> 
> Also, for the alpha version of the spec, the combination of use_line_ts = 1 and
> ts_valid = 0 doesn't seem to be possible as I understand. But I think beta
> version allows us?

Yes indeed ALPHA_0 definition of TS Line fields was ambiguos but now in
BETA the field is more explicitly called as LineExtension bit field...so
the code in the upcoming V3 will change accordingly.

> 
> > +
> > +	/* Invalid lines are not an error, could simply be disabled DEs */
> > +	if (DATA_INVALID(payld))
> > +		return used_qwords;
> > +
> > +	if (!IS_BLK_TS(payld))
> > +		scmi_telemetry_tdcf_data_parse(ti, payld, shmti, mode);
> > +	else
> > +		scmi_telemetry_tdcf_blkts_parse(ti, payld, shmti);
> > +
> > +	return used_qwords;
> > +}
> > +
> > +static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
> > +				     unsigned int shmti_id, u64 ts,
> > +				     enum scan_mode mode)
> 
> ts is not used in the function.
> 

Yes indeed I have removed it in V3.

> > +{
> > +	struct telemetry_shmti *shmti = &ti->shmti[shmti_id];
> > +	struct tdcf __iomem *tdcf = shmti->base;
> > +	int retries = SCMI_TLM_TDCF_MAX_RETRIES;
> > +	u64 startm = 0, endm = TDCF_BAD_END_SEQ;
> > +	void *eplg = SHMTI_EPLG(shmti);
> > +
> > +	if (!tdcf)
> > +		return -ENODEV;
> > +

[snip]

> > +
> > +		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:%d\n",
> 
> enabled:%c?
> 

Yep

> > +				de_id, de ? (de->enabled ? 'Y' : 'N') : 'X');
> > +			continue;

[snip]

> >  struct scmi_power_state_changed_report {
> > @@ -1114,4 +1292,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 */
> 
> Thanks,
> Elif

Thanks for the review !
Cristian

PS: (nitpick) you should trim your review replies to remove all the long
bunch of code that you are not comenting on  (leaving a [snip] placeholder
as I did above)... it helps finding your comments in the sea of code...


^ permalink raw reply	[flat|nested] 35+ messages in thread

end of thread, other threads:[~2026-03-26 13:32 UTC | newest]

Thread overview: 35+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-14 11:46 [PATCH v2 00/17] Introduce SCMI Telemetry FS support Cristian Marussi
2026-01-14 11:46 ` [PATCH v2 01/17] firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value Cristian Marussi
2026-01-19 11:18   ` Jonathan Cameron
2026-01-19 15:43     ` Cristian Marussi
2026-01-20  6:44     ` Dhruva Gole
2026-01-20 10:55       ` Cristian Marussi
2026-01-14 11:46 ` [PATCH v2 02/17] firmware: arm_scmi: Reduce the scope of protocols mutex Cristian Marussi
2026-01-19 11:21   ` Jonathan Cameron
2026-01-19 15:45     ` Cristian Marussi
2026-01-14 11:46 ` [PATCH v2 03/17] firmware: arm_scmi: Allow protocols to register for notifications Cristian Marussi
2026-01-19 11:33   ` Jonathan Cameron
2026-01-19 15:49     ` Cristian Marussi
2026-01-14 11:46 ` [PATCH v2 04/17] uapi: Add ARM SCMI definitions Cristian Marussi
2026-01-19 11:43   ` Jonathan Cameron
2026-01-19 15:51     ` Cristian Marussi
2026-01-14 11:46 ` [PATCH v2 05/17] firmware: arm_scmi: Add Telemetry protocol support Cristian Marussi
2026-01-19 16:29   ` Jonathan Cameron
2026-01-19 18:25     ` Cristian Marussi
2026-01-23 11:00   ` Elif Topuz
2026-03-26 13:32     ` Cristian Marussi
2026-01-14 11:46 ` [PATCH v2 06/17] include: trace: Add Telemetry trace events Cristian Marussi
2026-01-14 11:46 ` [PATCH v2 07/17] firmware: arm_scmi: Use new Telemetry traces Cristian Marussi
2026-01-23 11:43   ` Elif Topuz
2026-01-14 11:46 ` [PATCH v2 08/17] firmware: arm_scmi: Add System Telemetry driver Cristian Marussi
2026-01-14 11:46 ` [PATCH v2 09/17] fs/stlmfs: Document ARM SCMI Telemetry filesystem Cristian Marussi
2026-01-14 11:46 ` [PATCH v2 10/17] firmware: arm_scmi: Add System Telemetry ioctls support Cristian Marussi
2026-01-14 11:46 ` [PATCH v2 11/17] fs/stlmfs: Document alternative ioctl based binary interface Cristian Marussi
2026-01-14 11:46 ` [PATCH v2 12/17] firmware: arm_scmi: Add Telemetry components view Cristian Marussi
2026-01-16 13:35   ` Elif Topuz
2026-01-19 15:42     ` Cristian Marussi
2026-01-14 11:46 ` [PATCH v2 13/17] fs/stlmfs: Document alternative topological view Cristian Marussi
2026-01-14 11:46 ` [PATCH v2 14/17] [RFC] docs: stlmfs: Document ARM SCMI Telemetry FS ABI Cristian Marussi
2026-01-14 11:46 ` [PATCH v2 15/17] [RFC] firmware: arm_scmi: Add lazy population support to Telemetry FS Cristian Marussi
2026-01-14 11:46 ` [PATCH v2 16/17] fs/stlmfs: Document lazy mode and related mount option Cristian Marussi
2026-01-14 11:46 ` [PATCH v2 17/17] [RFC] tools/scmi: Add SCMI Telemetry testing tool Cristian Marussi

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox