* [PATCH v3 00/24] Introduce SCMI Telemetry FS support
@ 2026-03-29 16:33 Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 01/24] firmware: arm_scmi: Add new SCMIv4.0 error codes definitions Cristian Marussi
` (23 more replies)
0 siblings, 24 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, 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 deeemed more suitable for the usecase at hand. (...amongst the various
possible collection methods allowed by SCMI specification)
Without delving into the gory details of the whole SCMI Telemetry protocol
let's just say that the SCMI platform/server firmware advertises a number
of Telemetry Data Events, each one identified by a 32bit unique ID, and an
SCMI agent/client, like Linux, can discover them and read back at will the
associated data value in a number of ways.
Data collection is mainly intended to happen on demand via shared memory
areas exposed by the platform firmware, discovered dynamically via SCMI
Telemetry and accessed by Linux on-demand, but some DE can also be reported
via SCMI Notifications asynchronous messages or via direct dedicated
FastChannels (another kind of SCMI memory based access): all of this
underlying mechanism is anyway hidden to the user since it is mediated by
the kernel driver which will return the proper data value when queried.
Anyway, the set of well-known architected DE IDs defined by the spec is
limited to a dozen IDs, which means that the vast majority of DE IDs are
customizable per-platform: as a consequence, though, the same ID, say
'0x1234', could represent completely different things on different systems.
Precise definitions and semantic of such custom Data Event IDs are out of
the scope of the SCMI Telemetry specification and of this implementation:
they are supposed to be provided using some kind of JSON-like description
file that will have to be consumed by a userspace tool which would be
finally in charge of making sense of the set of available DEs.
IOW, in turn, this means that even though the DEs enumerated via SCMI come
with some sort of topological and qualitative description provided by the
protocol (like unit of measurements, name, topology info etc), kernel-wise
we CANNOT be completely sure of "what is what" without being fed-back some
sort of information about the DEs by the afore mentioned userspace tool.
For these reasons, currently this series does NOT attempt to register any
of these DEs with any of the usual in-kernel subsystems (like HWMON, IIO,
PERF etc), simply because we cannot be sure which DE is suitable, or even
desirable, for a given subsystem. This also means there are NO in-kernel
users of these Telemetry data events as of now.
So, while we do not exclude, for the future, to feed/register some of the
discovered DEs to/with some of the above mentioned Kernel subsystems, as
of now we have ONLY modeled a custom userspace API to make SCMI Telemetry
available to userspace tools.
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 an FS-based human-readable interface that can be used to
discover, configure and access our Telemetry data directly also from
the shell without special tools
- 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 access with custom tools
In the initial RFC posted a few months ago [1], 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 of DEs is sensible.
In some test scenario I ended with 50k DE 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 IOCTLs and mmap'ing to provide
a more performant interface within SysFs, 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 painful.
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 here.
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 from userspace
- 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) so as to mitigate the effect at
boot-time of the initial SCMI full discovery process
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/
The new pseudo FS rationale, design and related ABI interface is documented
in detail at:
- Documentation/filesystems/stlmfs.rst
- Documentation/ABI/testing/stlmfs
...anyway, roughly, STLMFS exposes 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
- 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/0x0000A001/
|-- 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 (optionally) 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 offered by the full-fledged filesystem
implementation 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:
by_components/
├── cpu
│ ├── 0
│ │ ├── celsius
│ │ │ └── 0
│ │ │ └── 0x00000001[pe_0] -> ../../../../../des/0x00000001
│ │ └── cycles
│ │ ├── 0
│ │ │ └── 0x00001010[] -> ../../../../../des/0x00001010
│ │ └── 1
│ │ └── 0x00002020[] -> ../../../../../des/0x00002020
│ ├── 1
│ │ └── celsius
│ │ └── 0
│ │ └── 0x00000002[pe_1] -> ../../../../../des/0x00000002
│ └── 2
│ └── celsius
│ └── 0
│ └── 0x00000003[pe_2] -> ../../../../../des/0x00000003
├── interconnnect
│ └── 0
│ └── hertz
│ └── 0
│ ├── 0x0000A008[A008_de] -> ../../../../../des/0x0000A008
│ └── 0x0000A00B[] -> ../../../../../des/0x0000A00B
├── mem_cntrl
│ └── 0
│ ├── bps
│ │ └── 0
│ │ └── 0x0000A00A[] -> ../../../../../des/0x0000A00A
│ ├── celsius
│ │ └── 0
│ │ └── 0x0000A007[DRAM_temp] -> ../../../../../des/0x0000A007
│ └── joules
│ └── 0
│ └── 0x0000A002[DRAM_energy] -> ../../../../../des/0x0000A002
├── periph
│ ├── 0
│ │ └── messages
│ │ └── 0
│ │ └── 0x00000016[device_16] -> ../../../../../des/0x00000016
│ ├── 1
│ │ └── messages
│ │ └── 0
│ │ └── 0x00000017[device_17] -> ../../../../../des/0x00000017
│ └── 2
│ └── messages
│ └── 0
│ └── 0x00000018[device_18] -> ../../../../../des/0x00000018
└── unspec
└── 0
├── celsius
│ └── 0
│ └── 0x0000A005[] -> ../../../../../des/0x0000A005
├── counts
│ └── 0
│ └── 0x0000A00C[] -> ../../../../../des/0x0000A00C
├── joules
│ └── 0
│ ├── 0x0000A000[SOC_Energy] -> ../../../../../des/0x0000A000
│ └── 0x0000A001[] -> ../../../../../des/0x0000A001
└── state
└── 0
└── 0x0000A010[] -> ../../../../../des/0x0000A010
...so as to provide the human user with a more understandable topological
layout of the madness...
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
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
| |-- interconnnect
| |-- mem_cntrl
| |-- 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 allows 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 V3]
Add another alternative and completely binary direct raw access
interface via a new set of memory mappable 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, at the firmware interface level NOW supports ONLY
the latest SCMI v4.0 BETA specification [0].
Missing feats & next steps
--------------------------
- 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 distinct
FS Kconfig...but the SCMI Telemetry driver itself has no point in existing
without the FS that exposes...so should I split the pure FS part into fs/
anyway or not ?
- residual sparse/smatch static analyzers errors
- stlm tool utility is minimal for testing or development
Based on V7.0-rc5, tested on an emulated setup.
This series is available also at [2].
If you still reading...any feedback welcome :P
Thanks,
Cristian
----
V2 --> V3
- rebased on v7.0-rc5
- ported the firmware interface to SCMI v4.0 BETA
- split the SCMI protocol layer in a lot of small patches
- completd filesystem and ABI documentation
- renamed components subtree to by_components
- fixed uninitialized var in scmi_telemetry_de_subdir_symlink
- renamd tstamp_exp to tstamp_rate
- swap logic in scmi_telemetry_initial_state_lookup
- use memcpy_from_le32 where required
- changed a dfew dev_err into Telemetry traces
- define and use new helper scmi_telemetry_de_unlink
- simplify a few assignments with ternary ops
- added a missing __mmust_check on the internal SCMI API
- reworked and clarified de_data_read returned errno:
ENODATA vs EINVAL vs ENODEV/ENOENT
- removed some risky/unneeded devres allocations
- various checkpatch fixes
- reworked and clarified usage of traces in Telemetry
- added the missing DT binding for protocol 0x1B
- split out unrelated change around notification from patch
adding support for protocol internal notifier
- more comments
V1 --> V2
- rebased on v6.19-rc3
- harden TDCF shared memory areas accesses by using proper accessors
- reworked protocol resources lifecycle to allow lazy enumeration
- using NEW FS mount API
- reworked FS inode allocation to use a std kmem_cache
- fixed a few IOCTLs support routine to support lazy enumeration
- added (RFC) a new FS lazy mount option to support lazily population of
some subtrees of the FS (des/ groups/ components/)
- reworked implementation of components/ alternative FS view to use
symlinks instead of hardlinks
- added a basic simple (RFC) testing tool to exercise UAPI ioctls interface
- hardened Telmetry protocol and driver to support partial out-of-spec FW
lacking some cmds (best effort)
- reworked probing races handling
- reviewed behaviour on unmount/unload
- added support for Boot_ON Telemetry by supporting SCMI Telemetry cmds:
+ DE_ENABLED_LIST
+ CONFIG_GET
- added FS and ABI docs
RFC --> V1
---
- moved from SysFS/chardev to a full fledged FS
- added support for SCMI Telemetry BLK timestamps
Thanks,
Cristian
[0]: https://developer.arm.com/documentation/den0056/fb/?lang=en
[1]: https://lore.kernel.org/arm-scmi/20250620192813.2463367-1-cristian.marussi@arm.com/
[2]: https://git.kernel.org/pub/scm/linux/kernel/git/cris/linux.git/log/?h=scmi/scmi_telemetry_unified_fs_V3
Cristian Marussi (24):
firmware: arm_scmi: Add new SCMIv4.0 error codes definitions
firmware: arm_scmi: Reduce the scope of protocols mutex
firmware: arm_scmi: Allow registration of unknown-size events/reports
firmware: arm_scmi: Allow protocols to register for notifications
uapi: Add ARM SCMI definitions
dt-bindings: firmware: arm,scmi: Add support for telemetry protocol
include: trace: Add Telemetry trace events
firmware: arm_scmi: Add basic Telemetry support
firmware: arm_scmi: Add support to parse SHMTIs areas
firmware: arm_scmi: Add Telemetry configuration operations
firmware: arm_scmi: Add Telemetry DataEvent read capabilities
firmware: arm_scmi: Add support for Telemetry reset
firmware: arm_scmi: Add Telemetry notification support
firmware: arm_scmi: Add support for boot-on Telemetry
firmware: arm_scmi: Add System Telemetry filesystem 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
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 | 297 ++
.../bindings/firmware/arm,scmi.yaml | 8 +
Documentation/filesystems/stlmfs.rst | 312 ++
MAINTAINERS | 1 +
drivers/firmware/arm_scmi/Kconfig | 10 +
drivers/firmware/arm_scmi/Makefile | 3 +-
drivers/firmware/arm_scmi/common.h | 10 +
drivers/firmware/arm_scmi/driver.c | 64 +-
drivers/firmware/arm_scmi/notify.c | 30 +-
drivers/firmware/arm_scmi/notify.h | 8 +-
drivers/firmware/arm_scmi/protocols.h | 7 +
.../firmware/arm_scmi/scmi_system_telemetry.c | 2946 ++++++++++++++++
drivers/firmware/arm_scmi/telemetry.c | 3081 +++++++++++++++++
include/linux/scmi_protocol.h | 185 +-
include/trace/events/scmi.h | 48 +-
include/uapi/linux/scmi.h | 289 ++
tools/testing/scmi/Makefile | 25 +
tools/testing/scmi/stlm.c | 385 ++
18 files changed, 7670 insertions(+), 39 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.53.0
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v3 01/24] firmware: arm_scmi: Add new SCMIv4.0 error codes definitions
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 02/24] firmware: arm_scmi: Reduce the scope of protocols mutex Cristian Marussi
` (22 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, souvik.chakravarty, Cristian Marussi
SCMIv4.0 introduces a couple of new possible protocol error codes: add
the needed definitions and mappings to Linux error values.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
drivers/firmware/arm_scmi/common.h | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index 7c35c95fddba..44af2018e21d 100644
--- a/drivers/firmware/arm_scmi/common.h
+++ b/drivers/firmware/arm_scmi/common.h
@@ -45,6 +45,8 @@ enum scmi_error_codes {
SCMI_ERR_GENERIC = -8, /* Generic Error */
SCMI_ERR_HARDWARE = -9, /* Hardware Error */
SCMI_ERR_PROTOCOL = -10,/* Protocol Error */
+ SCMI_ERR_IN_USE = -11, /* In Use Error */
+ SCMI_ERR_PARTIAL = -12, /* Partial Error */
};
static const int scmi_linux_errmap[] = {
@@ -60,6 +62,8 @@ static const int scmi_linux_errmap[] = {
-EIO, /* SCMI_ERR_GENERIC */
-EREMOTEIO, /* SCMI_ERR_HARDWARE */
-EPROTO, /* SCMI_ERR_PROTOCOL */
+ -EPERM, /* SCMI_ERR_IN_USE */
+ -EINVAL, /* SCMI_ERR_PARTIAL */
};
static inline int scmi_to_linux_errno(int errno)
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 02/24] firmware: arm_scmi: Reduce the scope of protocols mutex
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 01/24] firmware: arm_scmi: Add new SCMIv4.0 error codes definitions Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 03/24] firmware: arm_scmi: Allow registration of unknown-size events/reports Cristian Marussi
` (21 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, 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 3e76a3204ba4..26f192b8d7a9 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>
@@ -2190,7 +2191,6 @@ static void scmi_protocol_version_initialize(struct device *dev,
* all resources management is handled via a dedicated per-protocol devres
* group.
*
- * Context: Assumes to be called with @protocols_mtx already acquired.
* Return: A reference to a freshly allocated and initialized protocol instance
* or ERR_PTR on failure. On failure the @proto reference is at first
* put using @scmi_protocol_put() before releasing all the devres group.
@@ -2236,8 +2236,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;
@@ -2284,27 +2286,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);
}
/**
@@ -2335,10 +2335,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;
@@ -2357,9 +2358,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.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 03/24] firmware: arm_scmi: Allow registration of unknown-size events/reports
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 01/24] firmware: arm_scmi: Add new SCMIv4.0 error codes definitions Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 02/24] firmware: arm_scmi: Reduce the scope of protocols mutex Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 04/24] firmware: arm_scmi: Allow protocols to register for notifications Cristian Marussi
` (20 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, souvik.chakravarty, Cristian Marussi
Allow protocols to register events with build-time unknown sizes: such
events can be declared zero-sized and let the core SCMI stack perform the
needed safe-net boundary checks based on the configured transport size.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2 --> v3
- split out of previous patch on protocol notifier
- use max() instead of max_t()
---
drivers/firmware/arm_scmi/notify.c | 24 +++++++++++++++++++-----
drivers/firmware/arm_scmi/notify.h | 8 ++++++--
2 files changed, 25 insertions(+), 7 deletions(-)
diff --git a/drivers/firmware/arm_scmi/notify.c b/drivers/firmware/arm_scmi/notify.c
index 40ec184eedae..3e4c97ab7b61 100644
--- a/drivers/firmware/arm_scmi/notify.c
+++ b/drivers/firmware/arm_scmi/notify.c
@@ -595,7 +595,13 @@ int scmi_notify(const struct scmi_handle *handle, u8 proto_id, u8 evt_id,
if (!r_evt)
return -EINVAL;
- if (len > r_evt->evt->max_payld_sz) {
+ /*
+ * Events with a zero max_payld_sz are sized to be of the maximum
+ * size allowed by the transport: no need to be size-checked here
+ * since the transport layer would have already dropped such
+ * over-sized messages.
+ */
+ if (r_evt->evt->max_payld_sz && len > r_evt->evt->max_payld_sz) {
dev_err(handle->dev, "discard badly sized message\n");
return -EINVAL;
}
@@ -754,7 +760,7 @@ int scmi_register_protocol_events(const struct scmi_handle *handle, u8 proto_id,
const struct scmi_protocol_handle *ph,
const struct scmi_protocol_events *ee)
{
- int i;
+ int i, max_msg_sz;
unsigned int num_sources;
size_t payld_sz = 0;
struct scmi_registered_events_desc *pd;
@@ -769,6 +775,8 @@ int scmi_register_protocol_events(const struct scmi_handle *handle, u8 proto_id,
if (!ni)
return -ENOMEM;
+ max_msg_sz = ph->hops->get_max_msg_size(ph);
+
/* num_sources cannot be <= 0 */
if (ee->num_sources) {
num_sources = ee->num_sources;
@@ -781,8 +789,13 @@ int scmi_register_protocol_events(const struct scmi_handle *handle, u8 proto_id,
}
evt = ee->evts;
- for (i = 0; i < ee->num_events; i++)
- payld_sz = max_t(size_t, payld_sz, evt[i].max_payld_sz);
+ for (i = 0; i < ee->num_events; i++) {
+ if (evt[i].max_payld_sz == 0) {
+ payld_sz = max_msg_sz;
+ break;
+ }
+ payld_sz = max(payld_sz, evt[i].max_payld_sz);
+ }
payld_sz += sizeof(struct scmi_event_header);
pd = scmi_allocate_registered_events_desc(ni, proto_id, ee->queue_sz,
@@ -811,7 +824,8 @@ int scmi_register_protocol_events(const struct scmi_handle *handle, u8 proto_id,
mutex_init(&r_evt->sources_mtx);
r_evt->report = devm_kzalloc(ni->handle->dev,
- evt->max_report_sz, GFP_KERNEL);
+ evt->max_report_sz ?: max_msg_sz,
+ GFP_KERNEL);
if (!r_evt->report)
return -ENOMEM;
diff --git a/drivers/firmware/arm_scmi/notify.h b/drivers/firmware/arm_scmi/notify.h
index 76758a736cf4..ecfa4b746487 100644
--- a/drivers/firmware/arm_scmi/notify.h
+++ b/drivers/firmware/arm_scmi/notify.h
@@ -18,8 +18,12 @@
/**
* struct scmi_event - Describes an event to be supported
* @id: Event ID
- * @max_payld_sz: Max possible size for the payload of a notification message
- * @max_report_sz: Max possible size for the report of a notification message
+ * @max_payld_sz: Max possible size for the payload of a notification message.
+ * Set to zero to use the maximum payload size allowed by the
+ * transport.
+ * @max_report_sz: Max possible size for the report of a notification message.
+ * Set to zero to use the maximum payload size allowed by the
+ * transport.
*
* Each SCMI protocol, during its initialization phase, can describe the events
* it wishes to support in a few struct scmi_event and pass them to the core
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 04/24] firmware: arm_scmi: Allow protocols to register for notifications
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
` (2 preceding siblings ...)
2026-03-29 16:33 ` [PATCH v3 03/24] firmware: arm_scmi: Allow registration of unknown-size events/reports Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 05/24] uapi: Add ARM SCMI definitions Cristian Marussi
` (19 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, souvik.chakravarty, Cristian Marussi
Allow protocols themselves to register for their own notifications and
provide their own notifier callbacks.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2-->v3
- split out unrelated changes on event sizing
v1-->v2
- Fixed multiline comment format
---
drivers/firmware/arm_scmi/common.h | 6 ++++++
drivers/firmware/arm_scmi/driver.c | 12 ++++++++++++
drivers/firmware/arm_scmi/notify.c | 6 +++---
drivers/firmware/arm_scmi/protocols.h | 6 ++++++
4 files changed, 27 insertions(+), 3 deletions(-)
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index 44af2018e21d..7989c79e9bd9 100644
--- a/drivers/firmware/arm_scmi/common.h
+++ b/drivers/firmware/arm_scmi/common.h
@@ -17,6 +17,9 @@
#include <linux/hashtable.h>
#include <linux/list.h>
#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/property.h>
#include <linux/refcount.h>
#include <linux/scmi_protocol.h>
#include <linux/spinlock.h>
@@ -529,5 +532,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 26f192b8d7a9..c4aefbeead62 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -1655,6 +1655,17 @@ static void *scmi_get_protocol_priv(const struct scmi_protocol_handle *ph)
return pi->priv;
}
+static int
+scmi_register_instance_notifier(const struct scmi_protocol_handle *ph,
+ u8 evt_id, const u32 *src_id,
+ struct notifier_block *nb)
+{
+ const struct scmi_protocol_instance *pi = ph_to_pi(ph);
+
+ return scmi_notifier_register(pi->handle, pi->proto->id,
+ evt_id, src_id, nb);
+}
+
static const struct scmi_xfer_ops xfer_ops = {
.xfer_get_init = xfer_get_init,
.reset_rx_to_maxsz = reset_rx_to_maxsz,
@@ -2223,6 +2234,7 @@ scmi_alloc_init_protocol_instance(struct scmi_info *info,
pi->ph.hops = &helpers_ops;
pi->ph.set_priv = scmi_set_protocol_priv;
pi->ph.get_priv = scmi_get_protocol_priv;
+ pi->ph.notifier_register = scmi_register_instance_notifier;
refcount_set(&pi->users, 1);
/*
diff --git a/drivers/firmware/arm_scmi/notify.c b/drivers/firmware/arm_scmi/notify.c
index 3e4c97ab7b61..2a8efdf0bab8 100644
--- a/drivers/firmware/arm_scmi/notify.c
+++ b/drivers/firmware/arm_scmi/notify.c
@@ -1389,9 +1389,9 @@ static int scmi_event_handler_enable_events(struct scmi_event_handler *hndl)
*
* Return: 0 on Success
*/
-static int scmi_notifier_register(const struct scmi_handle *handle,
- u8 proto_id, u8 evt_id, const u32 *src_id,
- struct notifier_block *nb)
+int scmi_notifier_register(const struct scmi_handle *handle,
+ u8 proto_id, u8 evt_id, const u32 *src_id,
+ struct notifier_block *nb)
{
int ret = 0;
u32 evt_key;
diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h
index f51245aca259..3e7b6f8aa72c 100644
--- a/drivers/firmware/arm_scmi/protocols.h
+++ b/drivers/firmware/arm_scmi/protocols.h
@@ -166,6 +166,9 @@ struct scmi_proto_helpers_ops;
* can be used by the protocol implementation to generate SCMI messages.
* @set_priv: A method to set protocol private data for this instance.
* @get_priv: A method to get protocol private data previously set.
+ * @notifier_register: A method to register interest for notifications from
+ * within a protocol implementation unit: notifiers can
+ * be registered only for the same protocol.
*
* This structure represents a protocol initialized against specific SCMI
* instance and it will be used as follows:
@@ -185,6 +188,9 @@ struct scmi_protocol_handle {
const struct scmi_proto_helpers_ops *hops;
int (*set_priv)(const struct scmi_protocol_handle *ph, void *priv);
void *(*get_priv)(const struct scmi_protocol_handle *ph);
+ int (*notifier_register)(const struct scmi_protocol_handle *ph,
+ u8 evt_id, const u32 *src_id,
+ struct notifier_block *nb);
};
/**
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 05/24] uapi: Add ARM SCMI definitions
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
` (3 preceding siblings ...)
2026-03-29 16:33 ` [PATCH v3 04/24] firmware: arm_scmi: Allow protocols to register for notifications Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 06/24] dt-bindings: firmware: arm,scmi: Add support for telemetry protocol Cristian Marussi
` (18 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, 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>
---
v2 --> v3
- Change tstamp_exp tp ts_rate
- renamed num -> num_intervals in scmi_tlm_interval
- added padding in scmi_tlm_de_sample to avoid packing issues on 32bit
v1 --> v2
- Added proper __counted_by marks
- Fixed a few dox comments
- Renamed reserved[] fields to pad[]
---
MAINTAINERS | 1 +
include/uapi/linux/scmi.h | 289 ++++++++++++++++++++++++++++++++++++++
2 files changed, 290 insertions(+)
create mode 100644 include/uapi/linux/scmi.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 7d10988cbc62..dcf2c97e4e5c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -25708,6 +25708,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..abf68bb99960
--- /dev/null
+++ b/include/uapi/linux/scmi.h
@@ -0,0 +1,289 @@
+/* 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_intervals: 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_intervals;
+#define SCMI_TLM_UPDATE_INTVL_SEGMENT_LOW 0
+#define SCMI_TLM_UPDATE_INTVL_SEGMENT_HIGH 1
+#define SCMI_TLM_UPDATE_INTVL_SEGMENT_STEP 2
+ __u32 update_intervals[] __counted_by(num_intervals);
+};
+
+/**
+ * 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
+ * @ts_rate: Clock rate in kHz used to generate the DE timestamp
+ * @instance_id: DE instance ID
+ * @compo_instance_id: DE component instance ID
+ * @compo_type: Type of component which is associated to this DE
+ * @persistent: Data value for this DE survives reboot (non-cold ones)
+ * @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 ts_rate;
+ __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
+ * @pad: Padding fields to enforce alignment.
+ * @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;
+ __u32 pad;
+ __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.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 06/24] dt-bindings: firmware: arm,scmi: Add support for telemetry protocol
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
` (4 preceding siblings ...)
2026-03-29 16:33 ` [PATCH v3 05/24] uapi: Add ARM SCMI definitions Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 17:31 ` Rob Herring (Arm)
2026-03-29 16:33 ` [PATCH v3 07/24] include: trace: Add Telemetry trace events Cristian Marussi
` (17 subsequent siblings)
23 siblings, 1 reply; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, souvik.chakravarty, Cristian Marussi, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, devicetree
Add new SCMI v4.0 Telemetry protocol bindings definitions.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
Cc: Rob Herring <robh@kernel.org>
Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
Cc: Conor Dooley <conor+dt@kernel.org>
Cc: devicetree@vger.kernel.org
---
Documentation/devicetree/bindings/firmware/arm,scmi.yaml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/Documentation/devicetree/bindings/firmware/arm,scmi.yaml b/Documentation/devicetree/bindings/firmware/arm,scmi.yaml
index be817fd9cc34..e936ae7c0fb9 100644
--- a/Documentation/devicetree/bindings/firmware/arm,scmi.yaml
+++ b/Documentation/devicetree/bindings/firmware/arm,scmi.yaml
@@ -298,6 +298,14 @@ properties:
reg:
const: 0x19
+ protocol@1B:
+ $ref: '#/$defs/protocol-node'
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ const: 0x1B
+
patternProperties:
'-pins$':
type: object
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 07/24] include: trace: Add Telemetry trace events
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
` (5 preceding siblings ...)
2026-03-29 16:33 ` [PATCH v3 06/24] dt-bindings: firmware: arm,scmi: Add support for telemetry protocol Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 08/24] firmware: arm_scmi: Add basic Telemetry support Cristian Marussi
` (16 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, 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.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 08/24] firmware: arm_scmi: Add basic Telemetry support
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
` (6 preceding siblings ...)
2026-03-29 16:33 ` [PATCH v3 07/24] include: trace: Add Telemetry trace events Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 09/24] firmware: arm_scmi: Add support to parse SHMTIs areas Cristian Marussi
` (15 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, souvik.chakravarty, Cristian Marussi
Add SCMIv4.0 Telemetry basic support to enable initialization and resources
enumeration: add all the telemetry messages definitions and parsing logic
but only a few simple state gathering protocol operations.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2 --> v3
- split from monolithic Telemetry patch
- fix checkpatch macros complaints
- fix ACCESS_PRIVATE usage
- add a few comments on allocation/enumeration lifetime
- use interval.num_intervals
- removed needless cleanup handler usage
- simply return from scmi_telemetry_de_lookup()
- fixed composing_des name length to 08X
---
drivers/firmware/arm_scmi/Makefile | 2 +-
drivers/firmware/arm_scmi/driver.c | 2 +
drivers/firmware/arm_scmi/protocols.h | 1 +
drivers/firmware/arm_scmi/telemetry.c | 1375 +++++++++++++++++++++++++
include/linux/scmi_protocol.h | 135 ++-
5 files changed, 1513 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 c4aefbeead62..a4a2e52e1f3d 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -3508,6 +3508,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);
}
@@ -3526,6 +3527,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 3e7b6f8aa72c..3250d981664b 100644
--- a/drivers/firmware/arm_scmi/protocols.h
+++ b/drivers/firmware/arm_scmi/protocols.h
@@ -386,5 +386,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..7e5af7bd9fdc
--- /dev/null
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -0,0 +1,1375 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * System Control and Management Interface (SCMI) Telemetry Protocol
+ *
+ * Copyright (C) 2026 ARM Ltd.
+ */
+
+#include <linux/atomic.h>
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/compiler_types.h>
+#include <linux/completion.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/refcount.h>
+#include <linux/slab.h>
+#include <linux/sprintf.h>
+#include <linux/string.h>
+#include <linux/xarray.h>
+
+#include "protocols.h"
+#include "notify.h"
+
+#include <trace/events/scmi.h>
+
+/* Updated only after ALL the mandatory features for that version are merged */
+#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x10000
+
+#define SCMI_TLM_TDCF_MAX_RETRIES 5
+
+enum scmi_telemetry_protocol_cmd {
+ TELEMETRY_LIST_SHMTI = 0x3,
+ TELEMETRY_DE_DESCRIPTION = 0x4,
+ TELEMETRY_LIST_UPDATE_INTERVALS = 0x5,
+ TELEMETRY_DE_CONFIGURE = 0x6,
+ TELEMETRY_DE_ENABLED_LIST = 0x7,
+ TELEMETRY_CONFIG_SET = 0x8,
+ TELEMETRY_READING_COMPLETE = TELEMETRY_CONFIG_SET,
+ TELEMETRY_CONFIG_GET = 0x9,
+ TELEMETRY_RESET = 0xA,
+};
+
+struct scmi_msg_resp_telemetry_protocol_attributes {
+ __le32 de_num;
+ __le32 groups_num;
+ __le32 de_implementation_rev_dword[SCMI_TLM_DE_IMPL_MAX_DWORDS];
+ __le32 attributes;
+#define SUPPORTS_SINGLE_READ(x) ((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))
+ __le32 default_blk_ts_rate;
+};
+
+struct scmi_telemetry_update_notify_payld {
+ __le32 agent_id;
+ __le32 status;
+ __le32 num_dwords;
+ __le32 array[] __counted_by(num_dwords);
+};
+
+struct scmi_shmti_desc {
+ __le32 id;
+ __le32 addr_low;
+ __le32 addr_high;
+ __le32 length;
+ __le32 flags;
+};
+
+struct scmi_msg_resp_telemetry_shmti_list {
+ __le32 num_shmti;
+ struct scmi_shmti_desc desc[] __counted_by(num_shmti);
+};
+
+struct de_desc_fc {
+ __le32 addr_low;
+ __le32 addr_high;
+ __le32 size;
+};
+
+struct scmi_de_desc {
+ __le32 id;
+ __le32 grp_id;
+ __le32 data_sz;
+ __le32 attr_1;
+#define IS_NAME_SUPPORTED(d) ((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 TSTAMP_SUPPORT(d) (le32_get_bits((d)->attr_1, GENMASK(1, 0)))
+ __le32 attr_2;
+#define GET_DE_INSTA_ID(d) (le32_get_bits((d)->attr_2, GENMASK(31, 24)))
+#define GET_COMPO_INSTA_ID(d) (le32_get_bits((d)->attr_2, GENMASK(23, 8)))
+#define GET_COMPO_TYPE(d) (le32_get_bits((d)->attr_2, GENMASK(7, 0)))
+ __le32 reserved;
+};
+
+struct scmi_msg_resp_telemetry_de_description {
+ __le32 num_desc;
+ struct scmi_de_desc desc[] __counted_by(num_desc);
+};
+
+struct scmi_msg_telemetry_update_intervals {
+ __le32 index;
+ __le32 group_identifier;
+#define ALL_DES_NO_GROUP 0x0
+#define SPECIFIC_GROUP_DES 0x1
+#define ALL_DES_ANY_GROUP 0x2
+ __le32 flags;
+};
+
+struct scmi_msg_resp_telemetry_update_intervals {
+ __le32 flags;
+#define INTERVALS_DISCRETE(x) (!((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 shmti_de_offset;
+ __le32 blk_ts_offset;
+};
+
+struct scmi_msg_telemetry_config_set {
+ __le32 grp_id;
+ __le32 control;
+#define TELEMETRY_ENABLE (BIT(0))
+
+#define TELEMETRY_MODE_SET(x) (FIELD_PREP(GENMASK(4, 1), (x)))
+#define TLM_ONDEMAND (0)
+#define TLM_NOTIFS (1)
+#define TLM_SINGLE (2)
+#define TELEMETRY_MODE_ONDEMAND TELEMETRY_MODE_SET(TLM_ONDEMAND)
+#define TELEMETRY_MODE_NOTIFS TELEMETRY_MODE_SET(TLM_NOTIFS)
+#define TELEMETRY_MODE_SINGLE TELEMETRY_MODE_SET(TLM_SINGLE)
+
+#define TLM_ORPHANS (0)
+#define TLM_GROUP (1)
+#define TLM_ALL (2)
+#define TELEMETRY_SET_SELECTOR(x) (FIELD_PREP(GENMASK(8, 5), (x)))
+#define TELEMETRY_SET_SELECTOR_ORPHANS TELEMETRY_SET_SELECTOR(TLM_ORPHANS)
+#define TELEMETRY_SET_SELECTOR_GROUP TELEMETRY_SET_SELECTOR(TLM_GROUP)
+#define TELEMETRY_SET_SELECTOR_ALL TELEMETRY_SET_SELECTOR(TLM_ALL)
+ __le32 sampling_rate;
+};
+
+struct scmi_msg_resp_telemetry_reading_complete {
+ __le32 num_dwords;
+ __le32 dwords[] __counted_by(num_dwords);
+};
+
+struct scmi_msg_telemetry_config_get {
+ __le32 grp_id;
+ __le32 flags;
+#define TELEMETRY_GET_SELECTOR(x) (FIELD_PREP(GENMASK(3, 0), (x)))
+#define TELEMETRY_GET_SELECTOR_ORPHANS TELEMETRY_GET_SELECTOR(TLM_ORPHANS)
+#define TELEMETRY_GET_SELECTOR_GROUP TELEMETRY_GET_SELECTOR(TLM_GROUP)
+#define TELEMETRY_GET_SELECTOR_ALL TELEMETRY_GET_SELECTOR(TLM_ALL)
+};
+
+struct scmi_msg_resp_telemetry_config_get {
+ __le32 control;
+#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))
+
+/*
+ * Define the behaviour of a SHMTI scan defining what information will
+ * be gathered and which Telemetry items can be updated.
+ */
+enum scan_mode {
+ SCAN_LOOKUP, /* Update only value/tstamp */
+ SCAN_UPDATE, /* Update also location offset */
+ SCAN_DISCOVERY /* Update xa_des: allows for new DEs to be discovered */
+};
+
+struct fc_line {
+ 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;
+};
+
+struct uuid_line {
+ u32 dwords[SCMI_TLM_DE_IMPL_MAX_DWORDS];
+};
+
+enum tdcf_line_types {
+ TDCF_DATA_LINE,
+ TDCF_BLK_TS_LINE,
+ TDCF_UUID_LINE,
+};
+
+struct payload {
+ u32 meta;
+#define LINE_TYPE(x) (le32_get_bits(_I(&((x)->meta)), GENMASK(7, 4)))
+#define IS_DATA_LINE(x) (LINE_TYPE(x) == TDCF_DATA_LINE)
+#define IS_BLK_TS_LINE(x) (LINE_TYPE(x) == TDCF_BLK_TS_LINE)
+#define IS_UUID_LINE(x) (LINE_TYPE(x) == TDCF_UUID_LINE)
+#define USE_BLK_TS(x) (_I(&((x)->meta)) & BIT(3))
+#define HAS_LINE_EXT(x) (_I(&((x)->meta)) & BIT(2))
+#define LINE_TS_VALID(x) (_I(&((x)->meta)) & BIT(1))
+#define DATA_INVALID(x) (_I(&((x)->meta)) & BIT(0))
+#define BLK_TS_INVALID(p) \
+({ \
+ typeof(p) _p = (p); \
+ bool invalid; \
+ \
+ invalid = LINE_TS_VALID(_p) || HAS_LINE_EXT(_p) || \
+ USE_BLK_TS(_p) || DATA_INVALID(_p); \
+ invalid; \
+})
+
+#define UUID_INVALID(p) \
+({ \
+ typeof(p) _p = (p); \
+ bool invalid; \
+ \
+ invalid = LINE_TS_VALID(_p) || USE_BLK_TS(_p) || \
+ DATA_INVALID(_p) || !HAS_LINE_EXT(_p); \
+ invalid; \
+})
+ u32 id;
+ union {
+ struct line l;
+ struct tsline tsl;
+ struct blk_tsline blk_tsl;
+ struct uuid_line uuid_l;
+ };
+};
+
+#define PAYLD_ID(x) (_I(&(((struct payload *)(x))->id)))
+
+#define LINE_DATA_PAYLD_WORDS \
+ ((sizeof(u32) + sizeof(u32) + sizeof(struct line)) / sizeof(u32))
+#define EXT_LINE_DATA_PAYLD_WORDS \
+ ((sizeof(u32) + sizeof(u32) + sizeof(struct tsline)) / sizeof(u32))
+
+#define LINE_LENGTH_WORDS(x) \
+ (HAS_LINE_EXT((x)) ? EXT_LINE_DATA_PAYLD_WORDS : LINE_DATA_PAYLD_WORDS)
+
+#define LINE_LENGTH_QWORDS(x) ((LINE_LENGTH_WORDS(x)) / 2)
+
+struct prlg {
+ u32 sign_start;
+#define SIGNATURE_START 0x5442474E /* TBGN */
+ u32 match_start;
+ u32 num_qwords;
+ u32 hdr_meta_1;
+#define TDCF_REVISION_GET(x) (le32_get_bits((x)->hdr_meta_1, GENMASK(7, 0)))
+};
+
+struct eplg {
+ u32 match_end;
+ u32 sign_end;
+#define SIGNATURE_END 0x54454E44 /* TEND */
+};
+
+#define TDCF_EPLG_SZ (sizeof(struct eplg))
+
+struct tdcf {
+ struct prlg prlg;
+ unsigned char payld[];
+};
+
+#define QWORDS(_t) (_I(&(_t)->prlg.num_qwords))
+
+#define SHMTI_MIN_SIZE (sizeof(struct tdcf) + TDCF_EPLG_SZ)
+
+#define TDCF_START_SIGNATURE(x) (_I(&((x)->prlg.sign_start)))
+#define TDCF_START_SEQ_GET(x) (_I(&((x)->prlg.match_start)))
+#define IS_BAD_START_SEQ(s) ((s) & 0x1)
+
+#define TDCF_END_SEQ_GET(e) (_I(&((e)->match_end)))
+#define TDCF_END_SIGNATURE(e) (_I(&((e)->sign_end)))
+#define TDCF_BAD_END_SEQ GENMASK(31, 0)
+
+struct telemetry_shmti {
+ int id;
+ u32 flags;
+ void __iomem *base;
+ u32 len;
+ u32 last_magic;
+};
+
+#define SHMTI_EPLG(s) \
+ ({ \
+ struct telemetry_shmti *_s = (s); \
+ struct eplg *_eplg; \
+ \
+ _eplg = _s->base + _s->len - TDCF_EPLG_SZ; \
+ (_eplg); \
+ })
+
+struct telemetry_line {
+ refcount_t users;
+ u32 last_magic;
+ struct payload __iomem *payld;
+ /* Protect line accesses */
+ struct mutex mtx;
+};
+
+struct telemetry_block_ts {
+ u64 last_ts;
+ u32 last_rate;
+ struct telemetry_line line;
+};
+
+#define to_blkts(l) container_of(l, struct telemetry_block_ts, line)
+
+struct telemetry_uuid {
+ u32 de_impl_version[SCMI_TLM_DE_IMPL_MAX_DWORDS];
+ struct telemetry_line line;
+};
+
+#define to_uuid(l) container_of(l, struct telemetry_uuid, line)
+
+enum timestamps {
+ TSTAMP_NONE,
+ TSTAMP_LINE,
+ TSTAMP_BLK
+};
+
+struct telemetry_de {
+ enum timestamps ts_type;
+ u32 ts_rate;
+ bool enumerated;
+ bool cached;
+ void __iomem *base;
+ struct eplg __iomem *eplg;
+ u32 offset;
+ /* NOTE THAT DE data_sz is registered in scmi_telemetry_de */
+ u32 fc_size;
+ /* Protect last_val/ts/magic accesses */
+ struct mutex mtx;
+ u64 last_val;
+ u64 last_ts;
+ u32 last_magic;
+ struct list_head item;
+ struct telemetry_block_ts *bts;
+ struct telemetry_uuid *uuid;
+ struct scmi_telemetry_de de;
+};
+
+#define to_tde(d) container_of(d, struct telemetry_de, de)
+
+#define DE_ENABLED_WITH_TSTAMP 2
+
+struct telemetry_info {
+ bool streaming_mode;
+ unsigned int num_shmti;
+ unsigned int default_blk_ts_rate;
+ const struct scmi_protocol_handle *ph;
+ struct telemetry_shmti *shmti;
+ struct telemetry_de *tdes;
+ struct scmi_telemetry_group *grps;
+ struct xarray xa_des;
+ /* Mutex to protect access to @free_des */
+ struct mutex free_mtx;
+ struct list_head free_des;
+ struct list_head fcs_des;
+ struct scmi_telemetry_info info;
+ atomic_t rinfo_initializing;
+ struct completion rinfo_initdone;
+ struct scmi_telemetry_res_info __private *rinfo;
+ struct scmi_telemetry_res_info *(*res_get)(struct telemetry_info *ti);
+};
+
+static struct scmi_telemetry_res_info *
+__scmi_telemetry_resources_get(struct telemetry_info *ti);
+
+static struct telemetry_de *
+scmi_telemetry_free_tde_get(struct telemetry_info *ti)
+{
+ struct telemetry_de *tde;
+
+ guard(mutex)(&ti->free_mtx);
+
+ tde = list_first_entry_or_null(&ti->free_des, struct telemetry_de, item);
+ if (!tde)
+ return tde;
+
+ list_del(&tde->item);
+
+ return tde;
+}
+
+static void scmi_telemetry_free_tde_put(struct telemetry_info *ti,
+ struct telemetry_de *tde)
+{
+ guard(mutex)(&ti->free_mtx);
+
+ list_add_tail(&tde->item, &ti->free_des);
+}
+
+static struct telemetry_de *scmi_telemetry_tde_lookup(struct telemetry_info *ti,
+ unsigned int de_id)
+{
+ struct scmi_telemetry_de *de;
+
+ de = xa_load(&ti->xa_des, de_id);
+ if (!de)
+ return NULL;
+
+ return to_tde(de);
+}
+
+static struct telemetry_de *scmi_telemetry_tde_get(struct telemetry_info *ti,
+ unsigned int de_id)
+{
+ static struct telemetry_de *tde;
+
+ /* Pick a new tde */
+ tde = scmi_telemetry_free_tde_get(ti);
+ if (!tde) {
+ dev_err(ti->ph->dev, "Cannot allocate DE for ID:0x%08X\n", de_id);
+ return ERR_PTR(-ENOSPC);
+ }
+
+ return tde;
+}
+
+static int scmi_telemetry_tde_register(struct telemetry_info *ti,
+ struct telemetry_de *tde)
+{
+ struct scmi_telemetry_res_info *rinfo = ACCESS_PRIVATE(ti, rinfo);
+ int ret;
+
+ if (rinfo->num_des >= ti->info.base.num_des) {
+ ret = -ENOSPC;
+ goto err;
+ }
+
+ /* Store DE pointer by de_id ... */
+ ret = xa_insert(&ti->xa_des, tde->de.info->id, &tde->de, GFP_KERNEL);
+ if (ret)
+ goto err;
+
+ /* ... and in the general array */
+ rinfo->des[rinfo->num_des++] = &tde->de;
+
+ return 0;
+
+err:
+ dev_err(ti->ph->dev, "Cannot register DE for ID:0x%08X\n",
+ tde->de.info->id);
+
+ return ret;
+}
+
+struct scmi_tlm_de_priv {
+ struct telemetry_info *ti;
+ void *next;
+};
+
+static int
+scmi_telemetry_protocol_attributes_get(struct telemetry_info *ti)
+{
+ struct scmi_msg_resp_telemetry_protocol_attributes *resp;
+ const struct scmi_protocol_handle *ph = ti->ph;
+ struct scmi_xfer *t;
+ int ret;
+
+ ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0,
+ sizeof(*resp), &t);
+ if (ret)
+ return ret;
+
+ resp = t->rx.buf;
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret) {
+ __le32 attr = resp->attributes;
+
+ ti->info.base.num_des = le32_to_cpu(resp->de_num);
+ ti->info.base.num_groups = le32_to_cpu(resp->groups_num);
+ for (int i = 0; i < SCMI_TLM_DE_IMPL_MAX_DWORDS; i++)
+ ti->info.base.de_impl_version[i] =
+ le32_to_cpu(resp->de_implementation_rev_dword[i]);
+ ti->info.single_read_support = SUPPORTS_SINGLE_READ(attr);
+ ti->info.continuos_update_support = SUPPORTS_CONTINUOS_UPDATE(attr);
+ ti->info.per_group_config_support = SUPPORTS_PER_GROUP_CONFIG(attr);
+ ti->info.reset_support = SUPPORTS_RESET(attr);
+ ti->info.fc_support = SUPPORTS_FC(attr);
+ ti->num_shmti = le32_get_bits(attr, GENMASK(15, 0));
+ ti->default_blk_ts_rate = le32_to_cpu(resp->default_blk_ts_rate);
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static void iter_tlm_prepare_message(void *message,
+ unsigned int desc_index, const void *priv)
+{
+ put_unaligned_le32(desc_index, message);
+}
+
+static int iter_de_descr_update_state(struct scmi_iterator_state *st,
+ const void *response, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_de_description *r = response;
+ struct scmi_tlm_de_priv *p = priv;
+
+ st->num_returned = le32_get_bits(r->num_desc, GENMASK(15, 0));
+ st->num_remaining = le32_get_bits(r->num_desc, GENMASK(31, 16));
+
+ if (st->rx_len < (sizeof(*r) + sizeof(r->desc[0]) * st->num_returned))
+ return -EINVAL;
+
+ /* Initialized to first descriptor */
+ p->next = (void *)r->desc;
+
+ return 0;
+}
+
+static int scmi_telemetry_de_descriptor_parse(struct telemetry_info *ti,
+ struct telemetry_de *tde,
+ void **next)
+{
+ struct scmi_telemetry_res_info *rinfo = ACCESS_PRIVATE(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->instance_id = GET_DE_INSTA_ID(desc);
+ tde->de.info->compo_instance_id = GET_COMPO_INSTA_ID(desc);
+ tde->de.info->compo_type = GET_COMPO_TYPE(desc);
+ tde->de.info->persistent = IS_PERSISTENT(desc);
+ tde->ts_type = TSTAMP_SUPPORT(desc);
+ tde->de.tstamp_support = !!tde->ts_type;
+ tde->de.fc_support = IS_FC_SUPPORTED(desc);
+ tde->de.name_support = IS_NAME_SUPPORTED(desc);
+ /* Update DE_DESCRIPTOR size for the next iteration */
+ *next += sizeof(*desc);
+
+ if (tde->ts_type == TSTAMP_LINE) {
+ u32 *line_ts_rate = *next;
+
+ tde->de.info->ts_rate = *line_ts_rate;
+
+ /* Variably sized depending on TS support */
+ *next += sizeof(*line_ts_rate);
+ } else if (tde->ts_type == TSTAMP_BLK) {
+ /* Setup default BLK TS value at first */
+ tde->de.info->ts_rate = ti->default_blk_ts_rate;
+ }
+
+ if (tde->de.fc_support) {
+ u32 size;
+ u64 phys_addr;
+ void __iomem *addr;
+ struct de_desc_fc *dfc;
+
+ dfc = *next;
+ phys_addr = le32_to_cpu(dfc->addr_low);
+ phys_addr |= (u64)le32_to_cpu(dfc->addr_high) << 32;
+
+ size = le32_to_cpu(dfc->size);
+ addr = devm_ioremap(ti->ph->dev, phys_addr, size);
+ if (!addr)
+ return -EADDRNOTAVAIL;
+
+ tde->base = addr;
+ tde->offset = 0;
+ tde->fc_size = size;
+
+ /* Add to FastChannels list */
+ list_add(&tde->item, &ti->fcs_des);
+
+ /* Variably sized depending on FC support */
+ *next += sizeof(*dfc);
+ }
+
+ if (tde->de.name_support) {
+ const char *de_name = *next;
+
+ strscpy(tde->de.info->name, de_name, SCMI_SHORT_NAME_MAX_SIZE);
+ /* Variably sized depending on name support */
+ *next += SCMI_SHORT_NAME_MAX_SIZE;
+ }
+
+ return 0;
+}
+
+static int iter_de_descr_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st,
+ void *priv)
+{
+ struct scmi_tlm_de_priv *p = priv;
+ struct telemetry_info *ti = p->ti;
+ const struct scmi_de_desc *desc = p->next;
+ struct telemetry_de *tde;
+ bool discovered = false;
+ unsigned int de_id;
+ int ret;
+
+ de_id = le32_to_cpu(desc->id);
+ /* Check if this DE has already been discovered by other means... */
+ tde = scmi_telemetry_tde_lookup(ti, de_id);
+ if (!tde) {
+ /* Create a new one */
+ tde = scmi_telemetry_tde_get(ti, de_id);
+ if (IS_ERR(tde))
+ return PTR_ERR(tde);
+
+ discovered = true;
+ } else if (tde->enumerated) {
+ /* Cannot be a duplicate of a DE already created by enumeration */
+ dev_err(ph->dev,
+ "Discovered INVALID DE with DUPLICATED ID:0x%08X\n",
+ de_id);
+ return -EINVAL;
+ }
+
+ ret = scmi_telemetry_de_descriptor_parse(ti, tde, &p->next);
+ if (ret)
+ goto err;
+
+ if (discovered) {
+ /* Register if it was not already ... */
+ ret = scmi_telemetry_tde_register(ti, tde);
+ if (ret)
+ goto err;
+
+ tde->enumerated = true;
+ }
+
+ /* Account for this DE in group num_de counter */
+ if (tde->de.grp)
+ tde->de.grp->info->num_des++;
+
+ return 0;
+
+err:
+ /* DE not enumerated at this point were created in this call */
+ if (!tde->enumerated)
+ scmi_telemetry_free_tde_put(ti, tde);
+
+ return ret;
+}
+
+static int
+scmi_telemetry_de_groups_init(struct device *dev, struct telemetry_info *ti)
+{
+ struct scmi_telemetry_res_info *rinfo = ACCESS_PRIVATE(ti, rinfo);
+
+ /* Allocate all groups DEs IDs arrays at first ... */
+ for (int i = 0; i < ti->info.base.num_groups; i++) {
+ struct scmi_telemetry_group *grp = &rinfo->grps[i];
+ size_t des_str_sz;
+
+ unsigned int *des __free(kfree) = kcalloc(grp->info->num_des,
+ sizeof(unsigned int),
+ GFP_KERNEL);
+ if (!des)
+ return -ENOMEM;
+
+ /*
+ * Max size 32bit ID string in Hex: 0xCAFECAFE
+ * - 10 digits + ' '/'\n' = 11 bytes per number
+ * - terminating NUL character
+ */
+ des_str_sz = grp->info->num_des * 11 + 1;
+ char *des_str __free(kfree) = kzalloc(des_str_sz, GFP_KERNEL);
+ if (!des_str)
+ return -ENOMEM;
+
+ grp->des = no_free_ptr(des);
+ grp->des_str = no_free_ptr(des_str);
+ /* Reset group DE counter */
+ grp->info->num_des = 0;
+ }
+
+ /* Scan DEs and populate DE IDs arrays for all groups */
+ for (int i = 0; i < rinfo->num_des; i++) {
+ struct scmi_telemetry_group *grp = rinfo->des[i]->grp;
+
+ if (!grp)
+ continue;
+
+ /*
+ * Note that, at this point, num_des is guaranteed to be
+ * sane (in-bounds) by construction.
+ */
+ grp->des[grp->info->num_des++] = i;
+ }
+
+ /* Build composing DES string */
+ for (int i = 0; i < ti->info.base.num_groups; i++) {
+ struct scmi_telemetry_group *grp = &rinfo->grps[i];
+ size_t bufsize = grp->info->num_des * 11 + 1;
+ char *buf = grp->des_str;
+
+ for (int j = 0; j < grp->info->num_des; j++) {
+ char term = j != (grp->info->num_des - 1) ? ' ' : '\0';
+ int len;
+
+ len = scnprintf(buf, bufsize, "0x%08X%c",
+ rinfo->des[grp->des[j]]->info->id, term);
+
+ buf += len;
+ bufsize -= len;
+ }
+ }
+
+ rinfo->num_groups = ti->info.base.num_groups;
+
+ return 0;
+}
+
+static int scmi_telemetry_de_descriptors_get(struct telemetry_info *ti)
+{
+ const struct scmi_protocol_handle *ph = ti->ph;
+
+ struct scmi_iterator_ops ops = {
+ .prepare_message = iter_tlm_prepare_message,
+ .update_state = iter_de_descr_update_state,
+ .process_response = iter_de_descr_process_response,
+ };
+ struct scmi_tlm_de_priv tpriv = {
+ .ti = ti,
+ .next = NULL,
+ };
+ void *iter;
+ int ret;
+
+ if (!ti->info.base.num_des)
+ return 0;
+
+ iter = ph->hops->iter_response_init(ph, &ops, ti->info.base.num_des,
+ TELEMETRY_DE_DESCRIPTION,
+ sizeof(u32), &tpriv);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+
+ ret = ph->hops->iter_response_run(iter);
+ if (ret)
+ return ret;
+
+ return scmi_telemetry_de_groups_init(ph->dev, ti);
+}
+
+struct scmi_tlm_ivl_priv {
+ struct device *dev;
+ struct scmi_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));
+
+ if (st->rx_len < (sizeof(*r) + sizeof(r->intervals[0]) * st->num_returned))
+ return -EINVAL;
+
+ /*
+ * total intervals is not declared previously anywhere so we
+ * assume it's returned+remaining on first call.
+ */
+ if (!st->max_resources) {
+ struct scmi_tlm_ivl_priv *p = priv;
+ struct scmi_tlm_intervals *intrvs;
+ bool discrete;
+ int inum;
+
+ discrete = INTERVALS_DISCRETE(r->flags);
+ /* Check consistency on first call */
+ if (!discrete && (st->num_returned != 3 || st->num_remaining != 0))
+ return -EINVAL;
+
+ inum = st->num_returned + st->num_remaining;
+ intrvs = kzalloc(sizeof(*intrvs) + inum * sizeof(__u32), GFP_KERNEL);
+ if (!intrvs)
+ return -ENOMEM;
+
+ intrvs->num_intervals = inum;
+ intrvs->discrete = discrete;
+ st->max_resources = intrvs->num_intervals;
+
+ *p->intrvs = intrvs;
+ }
+
+ return 0;
+}
+
+static int
+iter_intervals_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_update_intervals *r = response;
+ struct scmi_tlm_ivl_priv *p = priv;
+ struct scmi_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 = ACCESS_PRIVATE(ti, rinfo);
+
+ if (!ti->info.per_group_config_support)
+ return 0;
+
+ for (int id = 0; id < rinfo->num_groups; id++) {
+ int ret;
+
+ ret = scmi_tlm_enumerate_update_intervals(ti,
+ &rinfo->grps[id].intervals,
+ id, SPECIFIC_GROUP_DES);
+ if (ret)
+ return ret;
+
+ rinfo->grps_store[id].num_intervals =
+ rinfo->grps[id].intervals->num_intervals;
+ }
+
+ return 0;
+}
+
+static void scmi_telemetry_intervals_free(void *interval)
+{
+ kfree(interval);
+}
+
+static int
+scmi_telemetry_enumerate_common_intervals(struct telemetry_info *ti)
+{
+ unsigned int flags;
+ int ret;
+
+ flags = !ti->info.per_group_config_support ?
+ ALL_DES_ANY_GROUP : ALL_DES_NO_GROUP;
+
+ ret = scmi_tlm_enumerate_update_intervals(ti, &ti->info.intervals,
+ SCMI_TLM_GRP_INVALID, flags);
+ if (ret)
+ return ret;
+
+ /* A copy for UAPI access... */
+ ti->info.base.num_intervals = ti->info.intervals->num_intervals;
+
+ /* Delegate freeing of allocated intervals to unbind time */
+ return devm_add_action_or_reset(ti->ph->dev,
+ scmi_telemetry_intervals_free,
+ ti->info.intervals);
+}
+
+static int iter_shmti_update_state(struct scmi_iterator_state *st,
+ const void *response, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_shmti_list *r = response;
+
+ st->num_returned = le32_get_bits(r->num_shmti, GENMASK(15, 0));
+ st->num_remaining = le32_get_bits(r->num_shmti, GENMASK(31, 16));
+
+ if (st->rx_len < (sizeof(*r) + sizeof(r->desc[0]) * st->num_returned))
+ return -EINVAL;
+
+ return 0;
+}
+
+static inline int
+scmi_telemetry_shmti_validate(struct device *dev, struct telemetry_shmti *shmti)
+{
+ struct tdcf __iomem *tdcf = shmti->base;
+ u32 sign_start, sign_end;
+
+ sign_start = TDCF_START_SIGNATURE(tdcf);
+ sign_end = TDCF_END_SIGNATURE(SHMTI_EPLG(shmti));
+
+ if (sign_start != SIGNATURE_START || sign_end != SIGNATURE_END) {
+ dev_err(dev,
+ "BAD signature for SHMTI ID:%u @phys:%pK - START:0x%04X END:0x%04X\n",
+ shmti->id, shmti->base, sign_start, sign_end);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int iter_shmti_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st,
+ void *priv)
+{
+ const struct scmi_msg_resp_telemetry_shmti_list *r = response;
+ struct telemetry_info *ti = priv;
+ struct telemetry_shmti *shmti;
+ const struct scmi_shmti_desc *desc;
+ void __iomem *addr;
+ u64 phys_addr;
+ u32 len;
+
+ desc = &r->desc[st->loop_idx];
+ shmti = &ti->shmti[st->desc_index + st->loop_idx];
+
+ shmti->id = le32_to_cpu(desc->id);
+ shmti->flags = le32_to_cpu(desc->flags);
+ phys_addr = le32_to_cpu(desc->addr_low);
+ phys_addr |= (u64)le32_to_cpu(desc->addr_high) << 32;
+
+ len = le32_to_cpu(desc->length);
+ if (len < SHMTI_MIN_SIZE) {
+ dev_err(ph->dev, "Invalid length for SHMTI ID:%u len:%u\n",
+ shmti->id, len);
+ return -EINVAL;
+ }
+
+ addr = devm_ioremap(ph->dev, phys_addr, len);
+ if (!addr)
+ return -EADDRNOTAVAIL;
+
+ shmti->base = addr;
+ shmti->len = len;
+
+ return scmi_telemetry_shmti_validate(ph->dev, shmti);
+}
+
+static int scmi_telemetry_shmti_list(const struct scmi_protocol_handle *ph,
+ struct telemetry_info *ti)
+{
+ struct scmi_iterator_ops ops = {
+ .prepare_message = iter_tlm_prepare_message,
+ .update_state = iter_shmti_update_state,
+ .process_response = iter_shmti_process_response,
+ };
+ void *iter;
+
+ iter = ph->hops->iter_response_init(ph, &ops, ti->info.base.num_des,
+ TELEMETRY_LIST_SHMTI,
+ sizeof(u32), ti);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+
+ return ph->hops->iter_response_run(iter);
+}
+
+static int scmi_telemetry_enumerate_shmti(struct telemetry_info *ti)
+{
+ const struct scmi_protocol_handle *ph = ti->ph;
+ 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);
+
+ ti->res_get(ti);
+ return xa_load(&ti->xa_des, id);
+}
+
+static const struct scmi_telemetry_res_info *
+scmi_telemetry_resources_get(const struct scmi_protocol_handle *ph)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ return ti->res_get(ti);
+}
+
+static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
+ .info_get = scmi_telemetry_info_get,
+ .de_lookup = scmi_telemetry_de_lookup,
+ .res_get = scmi_telemetry_resources_get,
+};
+
+/**
+ * scmi_telemetry_resources_alloc - Resources allocation
+ * @ti: A reference to the telemetry info descriptor for this instance
+ *
+ * This allocates and initializes dedicated resources for the maximum possible
+ * number of needed telemetry resources, based on information gathered from
+ * the initial enumeration: these allocations represent an upper bound on
+ * the number of discoverable telemetry resources and they will be later
+ * populated during late deferred further discovery phases.
+ *
+ * Return: 0 on Success, errno otherwise
+ */
+static int scmi_telemetry_resources_alloc(struct telemetry_info *ti)
+{
+ /* Array to hold pointers to discovered DEs */
+ struct scmi_telemetry_de **des __free(kfree) =
+ kcalloc(ti->info.base.num_des, sizeof(*des), GFP_KERNEL);
+ if (!des)
+ return -ENOMEM;
+
+ /* The allocated DE descriptors */
+ struct telemetry_de *tdes __free(kfree) =
+ kcalloc(ti->info.base.num_des, sizeof(*tdes), GFP_KERNEL);
+ if (!tdes)
+ return -ENOMEM;
+
+ /* Allocate a set of contiguous DE info descriptors. */
+ struct scmi_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);
+
+ ACCESS_PRIVATE(ti, rinfo) = no_free_ptr(rinfo);
+
+ return 0;
+}
+
+static void scmi_telemetry_resources_free(void *arg)
+{
+ struct telemetry_info *ti = arg;
+ struct scmi_telemetry_res_info *rinfo = ACCESS_PRIVATE(ti, rinfo);
+
+ kfree(ti->tdes);
+ kfree(rinfo->des);
+ kfree(rinfo->dei_store);
+ kfree(rinfo->grps);
+ kfree(rinfo->grps_store);
+
+ kfree(rinfo);
+
+ ACCESS_PRIVATE(ti, rinfo) = NULL;
+}
+
+static struct scmi_telemetry_res_info *
+__scmi_telemetry_resources_get(struct telemetry_info *ti)
+{
+ return ACCESS_PRIVATE(ti, rinfo);
+}
+
+/**
+ * scmi_telemetry_resources_enumerate - Enumeration helper
+ * @ti: A reference to the telemetry info descriptor for this instance
+ *
+ * This helper is configured to be called once on the first enumeration
+ * attempt, when triggered by invoking ti->res_get() from somewhere else.
+ * Once run it substitues itself in ti->res_get() with the simple accessor
+ * __scmi_telemetry_resources_get, which returns a descriptor to the resources
+ * that were possibly discovered.
+ *
+ * Note that, while it attempts to fully enumerate Data Events and Groups, it
+ * does NOT fail when such enumerations fail, instead it simply gives up with
+ * the end result that only a partially populated, but consistent, resources
+ * descriptor will be returned; in such a case the incomplete descriptor will
+ * be marked as NOT fully_enumerated: this design enables the kernel to deal
+ * with badly implemented out-of-spec firmware support while keep on providing
+ * a minimal sane, albeit possibly incomplete, set of telemetry respources.
+ *
+ * Return: A reference to a fully or partially populated resources descriptor
+ */
+static struct scmi_telemetry_res_info *
+scmi_telemetry_resources_enumerate(struct telemetry_info *ti)
+{
+ struct scmi_telemetry_res_info *rinfo = ACCESS_PRIVATE(ti, rinfo);
+ struct device *dev = ti->ph->dev;
+ int ret;
+
+ /*
+ * Ensure this init function can be called only once and
+ * handles properly concurrent calls.
+ */
+ if (atomic_cmpxchg(&ti->rinfo_initializing, 0, 1)) {
+ if (!completion_done(&ti->rinfo_initdone))
+ wait_for_completion(&ti->rinfo_initdone);
+ goto out;
+ }
+
+ ret = scmi_telemetry_de_descriptors_get(ti);
+ if (ret) {
+ dev_err(dev, FW_BUG "Cannot 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;
+ }
+
+ /* If we got here, the enumeration was fully successful */
+ rinfo->fully_enumerated = true;
+done:
+ /* Disable initialization permanently */
+ smp_store_mb(ti->res_get, __scmi_telemetry_resources_get);
+ complete_all(&ti->rinfo_initdone);
+
+out:
+ return rinfo;
+}
+
+/**
+ * scmi_telemetry_instance_init - Instance initializer
+ * @ti: A reference to the telemetry info descriptor for this instance
+ *
+ * Note that this allocates and initialize all the resources possibly needed
+ * and then setups the @scmi_telemetry_resources_enumerate helper as the
+ * default method for the first call to ti->res_get(): this mechanism enables
+ * the possibility of optionally implementing deferred enumeration policies
+ * which optionally delay the discovery phase and related SCMI message exchanges
+ * to a later point in time.
+ *
+ * Return: 0 on Success, errno otherwise
+ */
+static int scmi_telemetry_instance_init(struct telemetry_info *ti)
+{
+ int ret;
+
+ /* Allocate and Initialize on first call... */
+ ret = scmi_telemetry_resources_alloc(ti);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(ti->ph->dev,
+ scmi_telemetry_resources_free, ti);
+ if (ret)
+ return ret;
+
+ xa_init(&ti->xa_des);
+ /* Setup resources lazy initialization */
+ atomic_set(&ti->rinfo_initializing, 0);
+ init_completion(&ti->rinfo_initdone);
+ /* Ensure the new res_get() operation is visible after this point */
+ smp_store_mb(ti->res_get, scmi_telemetry_resources_enumerate);
+
+ return 0;
+}
+
+static int scmi_telemetry_protocol_init(const struct scmi_protocol_handle *ph)
+{
+ struct device *dev = ph->dev;
+ struct telemetry_info *ti;
+ int ret;
+
+ dev_dbg(dev, "Telemetry Version %d.%d\n",
+ PROTOCOL_REV_MAJOR(ph->version), PROTOCOL_REV_MINOR(ph->version));
+
+ ti = devm_kzalloc(dev, sizeof(*ti), GFP_KERNEL);
+ if (!ti)
+ return -ENOMEM;
+
+ ti->ph = ph;
+
+ ret = scmi_telemetry_protocol_attributes_get(ti);
+ if (ret) {
+ dev_err(dev, FW_BUG "Cannot retrieve protocol attributes. Abort.\n");
+ return ret;
+ }
+
+ ret = scmi_telemetry_instance_init(ti);
+ if (ret) {
+ dev_err(dev, "Cannot initialize instance. Abort.\n");
+ return ret;
+ }
+
+ ret = scmi_telemetry_enumerate_common_intervals(ti);
+ if (ret)
+ dev_warn(dev, FW_BUG "Cannot enumerate update intervals. Carry-on.\n");
+
+ ret = scmi_telemetry_enumerate_shmti(ti);
+ if (ret) {
+ dev_err(dev, FW_BUG "Cannot enumerate SHMTIs. Abort.\n");
+ return ret;
+ }
+
+ ti->info.base.version = ph->version;
+
+ return ph->set_priv(ph, ti);
+}
+
+static const struct scmi_protocol scmi_telemetry = {
+ .id = SCMI_PROTOCOL_TELEMETRY,
+ .owner = THIS_MODULE,
+ .instance_init = &scmi_telemetry_protocol_init,
+ .ops = &tlm_proto_ops,
+ .supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
+};
+
+DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(telemetry, scmi_telemetry)
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index aafaac1496b0..fcb45bd4b44c 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,134 @@ 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_proto_ops - represents the various operations provided
+ * by SCMI Telemetry Protocol
+ *
+ * @info_get: get the general Telemetry information.
+ * @de_lookup: get a specific DE descriptor from the DE id.
+ * @res_get: get a reference to the Telemetry resources descriptor.
+ */
+struct scmi_telemetry_proto_ops {
+ const struct scmi_telemetry_info __must_check *(*info_get)
+ (const struct scmi_protocol_handle *ph);
+ const struct scmi_telemetry_de __must_check *(*de_lookup)
+ (const struct scmi_protocol_handle *ph, u32 id);
+ const struct scmi_telemetry_res_info __must_check *(*res_get)
+ (const struct scmi_protocol_handle *ph);
+};
+
/**
* struct scmi_notify_ops - represents notifications' operations provided by
* SCMI core
@@ -926,6 +1058,7 @@ enum scmi_std_protocol {
SCMI_PROTOCOL_VOLTAGE = 0x17,
SCMI_PROTOCOL_POWERCAP = 0x18,
SCMI_PROTOCOL_PINCTRL = 0x19,
+ SCMI_PROTOCOL_TELEMETRY = 0x1b,
};
enum scmi_system_events {
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 09/24] firmware: arm_scmi: Add support to parse SHMTIs areas
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
` (7 preceding siblings ...)
2026-03-29 16:33 ` [PATCH v3 08/24] firmware: arm_scmi: Add basic Telemetry support Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 10/24] firmware: arm_scmi: Add Telemetry configuration operations Cristian Marussi
` (14 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, souvik.chakravarty, Cristian Marussi
Add logic to scan the SHMTI areas, parsing the TDCF descriptors while
collecting DataEvent, BlockTimestamp and UUID lines.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2 --> v3
- split from monolithic Telemetry patch
- avoid devres allocation for resources that are added to the xa_lines XArray
- simplify prototype of line parsing helpers to drop unneeded dev
- flip tstmap logic in scmi_telemetry_line_data_parse() to properly emit
a TLM ftrace event
- use ternary ops to simplify quite a few expressions
---
drivers/firmware/arm_scmi/telemetry.c | 576 ++++++++++++++++++++++++++
1 file changed, 576 insertions(+)
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
index 7e5af7bd9fdc..6c5a988b2aac 100644
--- a/drivers/firmware/arm_scmi/telemetry.c
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -254,6 +254,23 @@ struct uuid_line {
u32 dwords[SCMI_TLM_DE_IMPL_MAX_DWORDS];
};
+#define LINE_DATA_GET(f) \
+({ \
+ typeof(f) _f = (f); \
+ \
+ (TO_CPU_64(_I(&_f->data_high), _I(&_f->data_low))); \
+})
+
+#define LINE_TSTAMP_GET(f) \
+({ \
+ typeof(f) _f = (f); \
+ \
+ (TO_CPU_64(_I(&_f->ts_high), _I(&_f->ts_low))); \
+})
+
+#define BLK_TS_STAMP(f) LINE_TSTAMP_GET(f)
+#define BLK_TS_RATE(p) PAYLD_ID(p)
+
enum tdcf_line_types {
TDCF_DATA_LINE,
TDCF_BLK_TS_LINE,
@@ -365,6 +382,7 @@ struct telemetry_line {
refcount_t users;
u32 last_magic;
struct payload __iomem *payld;
+ struct xarray *xa_lines;
/* Protect line accesses */
struct mutex mtx;
};
@@ -424,6 +442,7 @@ struct telemetry_info {
struct telemetry_de *tdes;
struct scmi_telemetry_group *grps;
struct xarray xa_des;
+ struct xarray xa_lines;
/* Mutex to protect access to @free_des */
struct mutex free_mtx;
struct list_head free_des;
@@ -1119,6 +1138,555 @@ scmi_telemetry_resources_get(const struct scmi_protocol_handle *ph)
return ti->res_get(ti);
}
+static u64
+scmi_telemetry_blkts_read(u32 magic, struct telemetry_block_ts *bts)
+{
+ if (WARN_ON(!bts || !refcount_read(&bts->line.users)))
+ return 0;
+
+ guard(mutex)(&bts->line.mtx);
+
+ if (bts->line.last_magic == magic)
+ return bts->last_ts;
+
+ /* Note that the bts->last_rate can change ONLY on creation */
+ bts->last_ts = BLK_TS_STAMP(&bts->line.payld->blk_tsl);
+ bts->line.last_magic = magic;
+
+ return bts->last_ts;
+}
+
+static void scmi_telemetry_blkts_update(struct telemetry_info *ti, u32 magic,
+ struct telemetry_block_ts *bts)
+{
+ guard(mutex)(&bts->line.mtx);
+
+ if (bts->line.last_magic != magic) {
+ bts->last_ts = BLK_TS_STAMP(&bts->line.payld->blk_tsl);
+ bts->last_rate = BLK_TS_RATE(bts->line.payld);
+ /* BLK_TS clock rate value can change ONLY here on creation */
+ if (!bts->last_rate)
+ bts->last_rate = ti->default_blk_ts_rate;
+ bts->line.last_magic = magic;
+ }
+}
+
+static void scmi_telemetry_line_put(struct telemetry_line *line, void *blob)
+{
+ if (refcount_dec_and_test(&line->users)) {
+ scoped_guard(mutex, &line->mtx)
+ xa_erase(line->xa_lines, (unsigned long)line->payld);
+ kfree(blob);
+ }
+}
+
+static void scmi_telemetry_blkts_unlink(struct telemetry_de *tde)
+{
+ scmi_telemetry_line_put(&tde->bts->line, tde->bts);
+ tde->bts = NULL;
+}
+
+static void scmi_telemetry_uuid_unlink(struct telemetry_de *tde)
+{
+ scmi_telemetry_line_put(&tde->uuid->line, tde->uuid);
+ tde->uuid = NULL;
+}
+
+static void scmi_telemetry_de_unlink(struct scmi_telemetry_de *de)
+{
+ struct telemetry_de *tde = to_tde(de);
+
+ /* Unlink all related lines triggering their deallocation */
+ if (tde->bts)
+ scmi_telemetry_blkts_unlink(tde);
+ if (tde->uuid)
+ scmi_telemetry_uuid_unlink(tde);
+}
+
+static struct telemetry_line *
+scmi_telemetry_line_get(struct xarray *xa_lines, struct payload *payld)
+{
+ struct telemetry_line *line;
+
+ line = xa_load(xa_lines, (unsigned long)payld);
+ if (!line)
+ return NULL;
+
+ refcount_inc(&line->users);
+
+ return line;
+}
+
+static int
+scmi_telemetry_line_init(struct telemetry_line *line, struct xarray *xa_lines,
+ struct payload __iomem *payld)
+{
+ refcount_set(&line->users, 1);
+ line->payld = payld;
+ line->xa_lines = xa_lines;
+ mutex_init(&line->mtx);
+
+ return xa_insert(xa_lines, (unsigned long)payld, line, GFP_KERNEL);
+}
+
+static struct telemetry_block_ts *
+scmi_telemetry_blkts_create(struct device *dev, struct xarray *xa_lines,
+ struct payload *payld)
+{
+ struct telemetry_block_ts *bts;
+ int ret;
+
+ bts = kzalloc(sizeof(*bts), GFP_KERNEL);
+ if (!bts)
+ return NULL;
+
+ ret = scmi_telemetry_line_init(&bts->line, xa_lines, payld);
+ if (ret) {
+ kfree(bts);
+ return NULL;
+ }
+
+ trace_scmi_tlm_collect(0, (u64)payld, 0, "SHMTI_NEW_BLKTS");
+
+ return bts;
+}
+
+static struct telemetry_block_ts *
+scmi_telemetry_blkts_get_or_create(struct device *dev, struct xarray *xa_lines,
+ struct payload *payld)
+{
+ struct telemetry_line *line;
+
+ line = scmi_telemetry_line_get(xa_lines, payld);
+ if (line)
+ return to_blkts(line);
+
+ return scmi_telemetry_blkts_create(dev, xa_lines, payld);
+}
+
+static struct telemetry_uuid *
+scmi_telemetry_uuid_create(struct device *dev, struct xarray *xa_lines,
+ struct payload *payld)
+{
+ struct telemetry_uuid *uuid;
+ int ret;
+
+ uuid = kzalloc(sizeof(*uuid), GFP_KERNEL);
+ if (!uuid)
+ return NULL;
+
+ for (int i = 0; i < SCMI_TLM_DE_IMPL_MAX_DWORDS; i++)
+ uuid->de_impl_version[i] = le32_to_cpu(payld->uuid_l.dwords[i]);
+
+ ret = scmi_telemetry_line_init(&uuid->line, xa_lines, payld);
+ if (ret) {
+ kfree(uuid);
+ return NULL;
+ }
+
+ trace_scmi_tlm_collect(0, (u64)payld, 0, "SHMTI_NEW_UUID");
+
+ return uuid;
+}
+
+static struct telemetry_uuid *
+scmi_telemetry_uuid_get_or_create(struct device *dev, struct xarray *xa_lines,
+ struct payload *payld)
+{
+ struct telemetry_line *line;
+
+ line = scmi_telemetry_line_get(xa_lines, payld);
+ if (line)
+ return to_uuid(line);
+
+ return scmi_telemetry_uuid_create(dev, xa_lines, payld);
+}
+
+static void scmi_telemetry_tdcf_uuid_parse(struct telemetry_info *ti,
+ struct payload __iomem *payld,
+ struct telemetry_shmti *shmti,
+ void **active_uuid)
+{
+ struct telemetry_uuid *uuid;
+
+ if (UUID_INVALID(payld)) {
+ trace_scmi_tlm_access(0, "UUID_INVALID", 0, 0);
+ return;
+ }
+
+ /* A UUID descriptor MUST be returned: it is found or it is created */
+ uuid = scmi_telemetry_uuid_get_or_create(ti->ph->dev, &ti->xa_lines,
+ payld);
+ if (WARN_ON(!uuid))
+ return;
+
+ *active_uuid = uuid;
+}
+
+static struct payload *
+scmi_telemetry_nearest_line_by_type(struct telemetry_shmti *shmti,
+ void *last, enum tdcf_line_types ltype)
+{
+ struct tdcf __iomem *tdcf = shmti->base;
+ void *next, *found = NULL;
+
+ /* Scan from start of TDCF payloads up to last_payld */
+ next = tdcf->payld;
+ while (next < last) {
+ if (LINE_TYPE((struct payload *)next) == ltype)
+ found = next;
+
+ next += LINE_LENGTH_WORDS((struct payload *)next);
+ }
+
+ return found;
+}
+
+static struct telemetry_block_ts *
+scmi_telemetry_blkts_bind(struct device *dev, struct telemetry_shmti *shmti,
+ struct payload *payld, struct xarray *xa_lines,
+ struct payload *bts_payld)
+{
+ /* Trigger a manual search when no BLK_TS payload offset was provided */
+ if (!bts_payld) {
+ /* Find the BLK_TS immediately preceding this DE payld */
+ bts_payld = scmi_telemetry_nearest_line_by_type(shmti, payld,
+ TDCF_BLK_TS_LINE);
+ if (!bts_payld)
+ return NULL;
+ }
+
+ return scmi_telemetry_blkts_get_or_create(dev, xa_lines, bts_payld);
+}
+
+/**
+ * scmi_telemetry_tdcf_blkts_parse - A BLK_TS line parser
+ *
+ * @ti: A reference to the telemetry_info descriptor
+ * @payld: TDCF payld line to process
+ * @shmti: SHMTI descriptor inside which the scan is happening
+ * @active_bts: Input/output reference to keep track of the last blk_ts found
+ *
+ * Process a valid TDCF BLK_TS line and, after having looked up or created a
+ * blk_ts descriptor, update the related data and return it as the currently
+ * active blk_ts, given that it is effectively the last found during this
+ * scan.
+ */
+static void scmi_telemetry_tdcf_blkts_parse(struct telemetry_info *ti,
+ struct payload __iomem *payld,
+ struct telemetry_shmti *shmti,
+ void **active_bts)
+{
+ struct telemetry_block_ts *bts;
+
+ /* Check for spec compliance */
+ if (BLK_TS_INVALID(payld)) {
+ trace_scmi_tlm_access(0, "BLK_TS_INVALID", 0, 0);
+ return;
+ }
+
+ /* A BLK_TS descriptor MUST be returned: it is found or it is created */
+ bts = scmi_telemetry_blkts_get_or_create(ti->ph->dev,
+ &ti->xa_lines, payld);
+ if (WARN_ON(!bts))
+ return;
+
+ /* Update the descriptor with the lastest TS */
+ scmi_telemetry_blkts_update(ti, shmti->last_magic, bts);
+ *active_bts = bts;
+}
+
+static inline struct telemetry_de *
+scmi_telemetry_tde_allocate(struct telemetry_info *ti, u32 de_id,
+ struct payload __iomem *payld)
+{
+ struct telemetry_de *tde;
+
+ tde = scmi_telemetry_tde_get(ti, de_id);
+ if (IS_ERR(tde))
+ return NULL;
+
+ tde->de.info->id = de_id;
+ tde->de.enabled = true;
+ tde->de.tstamp_enabled = LINE_TS_VALID(payld) || USE_BLK_TS(payld);
+
+ if (scmi_telemetry_tde_register(ti, tde)) {
+ scmi_telemetry_free_tde_put(ti, tde);
+ return NULL;
+ }
+
+ return tde;
+}
+
+static inline void
+scmi_telemetry_line_data_parse(struct telemetry_de *tde, u64 *val, u64 *tstamp,
+ struct payload __iomem *payld, u32 magic)
+{
+ /* Data is always valid since we are NOT handling BLK TS lines here */
+ *val = LINE_DATA_GET(&payld->l);
+ if (tstamp) {
+ if (USE_BLK_TS(payld)) {
+ /* Read out the actual BLK_TS */
+ *tstamp = scmi_telemetry_blkts_read(magic, tde->bts);
+ } else if (LINE_TS_VALID(payld)) {
+ /*
+ * Note that LINE_TS_VALID implies HAS_LINE_EXT and that
+ * the per DE line_ts_rate is advertised in the DE
+ * descriptor.
+ */
+ *tstamp = LINE_TSTAMP_GET(&payld->tsl);
+ } else {
+ *tstamp = 0;
+ }
+ }
+
+ trace_scmi_tlm_collect(tstamp ? *tstamp : 0, tde->de.info->id,
+ *val, "SHMTI_DE_READ");
+}
+
+static inline void scmi_telemetry_bts_link(struct telemetry_de *tde,
+ struct telemetry_block_ts *bts)
+{
+ refcount_inc(&bts->line.users);
+ tde->bts = bts;
+ /* Update TS clock rate if provided by the BLK_TS */
+ if (tde->bts->last_rate)
+ tde->de.info->ts_rate = tde->bts->last_rate;
+}
+
+static inline void scmi_telemetry_uuid_link(struct telemetry_de *tde,
+ struct telemetry_uuid *uuid)
+{
+ refcount_inc(&uuid->line.users);
+ tde->uuid = uuid;
+}
+
+/**
+ * scmi_telemetry_tdcf_data_parse - TDCF DataLine parsing
+ * @ti: A reference to the telemetry info descriptor
+ * @payld: Line payload to parse
+ * @shmti: A reference to the containing SHMTI area
+ * @mode: A flag to determine the behaviour of the scan
+ * @active_bts: A pointer to keep track and report any found BLK timestamp line
+ * @active_uuid: A pointer to keep track and report any found UUID line
+ *
+ * This routine takes care to:
+ * - verify line consistency in relation to the used flags and the current
+ * context: e.g. is there an active preceding BLK_TS line if the DataLine
+ * sports a USE_BLKTS flag ?
+ * - verify the related Data Event ID exists OR create a brand new DE
+ * (depending on the @mode of operation)
+ * - links any active BLK_TS or UUID line to the current DE
+ * - read and save value/tstamp for the DE ONLY if anything has changed (by
+ * tracking the last TDCF magic) and update related magic: this allows to
+ * minimize future needs of single-DE reads
+ *
+ * Modes of operation.
+ *
+ * The scan behaviour depends on the chosen @mode:
+ * - SCAN_LOOKUP: the basic scan which aims to update value associated to
+ * existing DEs. Any discovered DataLine that could NOT be
+ * matched to an existing, previously discovered, DE is
+ * discarded. This is the normal scan behaviour.
+ * - SCAN_UPDATE: a more advanced scan which provides all the SCAN_LOOKUP
+ * features plus takes care to update the DEs location
+ * coordinates inside the SHMTI: note that the related DEs are
+ * still supposed to have been previously discovered when
+ * this scan runs. This is used to update location
+ * coordinates for DEs contained in a Group when such group
+ * is enabled.
+ * - SCAN_DISCOVERY: the most advanced scan available which provides all
+ * the SCAN_LOOKUP features plus discovery capabilities:
+ * any DataLine referring to a previously unknown DE leads
+ * to the allocation of a new DE descriptor.
+ * This mode is used on the first scan at init time, ONLY
+ * if Telemetry was found to be already enabled at boot on
+ * the platform side: this helps to maximize gathered
+ * information when dealing with out of spec firmwares.
+ * Any usage of this discovery mode other than in a boot-on
+ * enabled scenario is discouraged since it can easily
+ * lead to spurious DE discoveries.
+ */
+static void scmi_telemetry_tdcf_data_parse(struct telemetry_info *ti,
+ struct payload __iomem *payld,
+ struct telemetry_shmti *shmti,
+ enum scan_mode mode,
+ void *active_bts, void *active_uuid)
+{
+ bool use_blk_ts = USE_BLK_TS(payld);
+ struct telemetry_de *tde;
+ u64 val, tstamp = 0;
+ u32 de_id;
+
+ de_id = PAYLD_ID(payld);
+ /* Discard malformed lines...a preceding BLK_TS must exist */
+ if (use_blk_ts && !active_bts) {
+ trace_scmi_tlm_access(de_id, "BAD_USE_BLK_TS", 0, 0);
+ return;
+ }
+
+ /* Is this DE ID known ? */
+ tde = scmi_telemetry_tde_lookup(ti, de_id);
+ if (!tde) {
+ if (mode != SCAN_DISCOVERY) {
+ trace_scmi_tlm_access(de_id, "DE_INVALID", 0, 0);
+ return;
+ }
+
+ /* In SCAN_DISCOVERY mode we allocate new DEs for unknown IDs */
+ tde = scmi_telemetry_tde_allocate(ti, de_id, payld);
+ if (!tde)
+ return;
+ }
+
+ /* Update DE location refs if requested: normally done only on enable */
+ if (mode >= SCAN_UPDATE) {
+ tde->base = shmti->base;
+ tde->eplg = SHMTI_EPLG(shmti);
+ tde->offset = (void *)payld - (void *)shmti->base;
+
+ dev_dbg(ti->ph->dev,
+ "TDCF-updated DE_ID:0x%08X - shmti:%pK offset:%u\n",
+ tde->de.info->id, tde->base, tde->offset);
+ }
+
+ /* Has any value/tstamp really changed ?*/
+ scoped_guard(mutex, &tde->mtx) {
+ if (tde->last_magic == shmti->last_magic)
+ return;
+ }
+
+ /* Link the related BTS when needed, it's unlinked on disable */
+ if (use_blk_ts && !tde->bts)
+ scmi_telemetry_bts_link(tde, active_bts);
+
+ /* Link the active UUID when existent, it's unlinked on disable */
+ if (active_uuid)
+ scmi_telemetry_uuid_link(tde, active_uuid);
+
+ /* Parse data words */
+ scmi_telemetry_line_data_parse(tde, &val, &tstamp, payld,
+ shmti->last_magic);
+
+ guard(mutex)(&tde->mtx);
+ tde->last_magic = shmti->last_magic;
+ tde->last_val = val;
+ tde->last_ts = tde->de.tstamp_enabled ? tstamp : 0;
+}
+
+static int scmi_telemetry_tdcf_line_parse(struct telemetry_info *ti,
+ struct payload __iomem *payld,
+ struct telemetry_shmti *shmti,
+ enum scan_mode mode,
+ void **active_bts, void **active_uuid)
+{
+ int used_qwords;
+
+ used_qwords = LINE_LENGTH_QWORDS(payld);
+ /* Invalid lines are not an error, could simply be disabled DEs */
+ if (DATA_INVALID(payld)) {
+ trace_scmi_tlm_access(PAYLD_ID(payld), "TDCF_INVALID", 0, 0);
+ return used_qwords;
+ }
+
+ switch (LINE_TYPE(payld)) {
+ case TDCF_DATA_LINE:
+ scmi_telemetry_tdcf_data_parse(ti, payld, shmti, mode,
+ *active_bts, *active_uuid);
+ break;
+ case TDCF_BLK_TS_LINE:
+ scmi_telemetry_tdcf_blkts_parse(ti, payld, shmti, active_bts);
+ break;
+ case TDCF_UUID_LINE:
+ scmi_telemetry_tdcf_uuid_parse(ti, payld, shmti, active_uuid);
+ break;
+ default:
+ trace_scmi_tlm_access(PAYLD_ID(payld), "TDCF_UNKNOWN", 0, 0);
+ break;
+ }
+
+ return used_qwords;
+}
+
+/**
+ * scmi_telemetry_shmti_scan - Full SHMTI scan
+ * @ti: A reference to the telemetry info descriptor
+ * @shmti_id: ID of the SHMTI area that has to be scanned
+ * @mode: A flag to determine the behaviour of the scan
+ *
+ * Return: 0 on Success
+ */
+static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
+ unsigned int shmti_id, enum scan_mode mode)
+{
+ struct telemetry_shmti *shmti = &ti->shmti[shmti_id];
+ struct tdcf __iomem *tdcf = shmti->base;
+ int retries = SCMI_TLM_TDCF_MAX_RETRIES;
+ u32 startm = 0, endm = TDCF_BAD_END_SEQ;
+
+ if (!tdcf)
+ return -ENODEV;
+
+ do {
+ void *active_bts = NULL, *active_uuid = NULL;
+ unsigned int qwords;
+ void __iomem *next;
+
+ /* A bit of exponential backoff between retries */
+ fsleep((SCMI_TLM_TDCF_MAX_RETRIES - retries) * 1000);
+
+ /*
+ * Note that during a full SHMTI scan the magic seq numbers are
+ * checked only at the start and at the end of the scan, NOT
+ * between each parsed line and this has these consequences:
+ * - TDCF magic numbers accesses are reduced to 2 reads
+ * - the set of values obtained from a full scan belong all
+ * to the same platform update (same magic number)
+ * - a SHMTI full scan is an all or nothing operation: when
+ * a potentially corrupted read is detected along the way
+ * (MSEQ_MISMATCH) another full scan is triggered.
+ */
+ startm = TDCF_START_SEQ_GET(tdcf);
+ if (IS_BAD_START_SEQ(startm)) {
+ trace_scmi_tlm_access(0, "MSEQ_BADSTART", startm, 0);
+ continue;
+ }
+
+ /* On a BAD_SEQ this will be updated on the next attempt */
+ shmti->last_magic = startm;
+
+ qwords = QWORDS(tdcf);
+ next = tdcf->payld;
+ while (qwords) {
+ int used_qwords;
+
+ used_qwords = scmi_telemetry_tdcf_line_parse(ti, next,
+ shmti, mode,
+ &active_bts,
+ &active_uuid);
+ if (qwords < used_qwords) {
+ trace_scmi_tlm_access(PAYLD_ID(next),
+ "BAD_QWORDS", startm, 0);
+ return -EINVAL;
+ }
+
+ next += used_qwords * 8;
+ qwords -= used_qwords;
+ }
+
+ endm = TDCF_END_SEQ_GET(SHMTI_EPLG(shmti));
+ if (startm != endm)
+ trace_scmi_tlm_access(0, "MSEQ_MISMATCH", startm, endm);
+ } while (startm != endm && --retries);
+
+ if (startm != endm) {
+ trace_scmi_tlm_access(0, "TDCF_SCAN_FAIL", startm, endm);
+ return -EPROTO;
+ }
+
+ return 0;
+}
+
static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.info_get = scmi_telemetry_info_get,
.de_lookup = scmi_telemetry_de_lookup,
@@ -1208,6 +1776,13 @@ static void scmi_telemetry_resources_free(void *arg)
struct telemetry_info *ti = arg;
struct scmi_telemetry_res_info *rinfo = ACCESS_PRIVATE(ti, rinfo);
+ /*
+ * Unlinking all the BLK_TS/UUID lines related to a DE triggers also
+ * the deallocation of such lines when the embedded refcount hits zero.
+ */
+ for (int i = 0; i < rinfo->num_des; i++)
+ scmi_telemetry_de_unlink(rinfo->des[i]);
+
kfree(ti->tdes);
kfree(rinfo->des);
kfree(rinfo->dei_store);
@@ -1313,6 +1888,7 @@ static int scmi_telemetry_instance_init(struct telemetry_info *ti)
return ret;
xa_init(&ti->xa_des);
+ xa_init(&ti->xa_lines);
/* Setup resources lazy initialization */
atomic_set(&ti->rinfo_initializing, 0);
init_completion(&ti->rinfo_initdone);
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 10/24] firmware: arm_scmi: Add Telemetry configuration operations
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
` (8 preceding siblings ...)
2026-03-29 16:33 ` [PATCH v3 09/24] firmware: arm_scmi: Add support to parse SHMTIs areas Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 11/24] firmware: arm_scmi: Add Telemetry DataEvent read capabilities Cristian Marussi
` (13 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, souvik.chakravarty, Cristian Marussi
Add support for basic Telemetry configuration operations to selectively
enable or disable DataEvents monitoring.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2 --> v3
- split from monolithic Telemetry patch
- simplify clenaup with scmi_telemetry_de_unlink
---
drivers/firmware/arm_scmi/telemetry.c | 348 ++++++++++++++++++++++++++
include/linux/scmi_protocol.h | 16 ++
2 files changed, 364 insertions(+)
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
index 6c5a988b2aac..9536262bbdf7 100644
--- a/drivers/firmware/arm_scmi/telemetry.c
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -457,6 +457,9 @@ struct telemetry_info {
static struct scmi_telemetry_res_info *
__scmi_telemetry_resources_get(struct telemetry_info *ti);
+static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
+ unsigned int shmti_id, enum scan_mode mode);
+
static struct telemetry_de *
scmi_telemetry_free_tde_get(struct telemetry_info *ti)
{
@@ -1687,10 +1690,355 @@ static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
return 0;
}
+static int scmi_telemetry_group_state_update(struct telemetry_info *ti,
+ struct scmi_telemetry_group *grp,
+ bool *enable, bool *tstamp)
+{
+ struct scmi_telemetry_res_info *rinfo;
+
+ rinfo = ti->res_get(ti);
+ for (int i = 0; i < grp->info->num_des; i++) {
+ struct scmi_telemetry_de *de = rinfo->des[grp->des[i]];
+
+ if (enable)
+ 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 de_offs;
+
+ de_offs = le32_to_cpu(resp->shmti_de_offset);
+ if (de_offs >= ti->shmti[sid].len - de->info->data_sz)
+ return -EPROTO;
+
+ tde = to_tde(de);
+ tde->base = ti->shmti[sid].base;
+ tde->offset = de_offs;
+ /* A handy reference to the Epilogue updated */
+ tde->eplg = SHMTI_EPLG(&ti->shmti[sid]);
+
+ payld = tde->base + tde->offset;
+ if (USE_BLK_TS(payld) && !tde->bts) {
+ struct payload *bts_payld;
+ u32 bts_offs;
+
+ bts_offs = le32_to_cpu(resp->blk_ts_offset);
+ bts_payld = (bts_offs) ? tde->base + bts_offs : NULL;
+ tde->bts = scmi_telemetry_blkts_bind(ti->ph->dev,
+ &ti->shmti[sid],
+ payld,
+ &ti->xa_lines,
+ bts_payld);
+ if (WARN_ON(!tde->bts))
+ return -EPROTO;
+ }
+ } else {
+ int ret;
+
+ /*
+ * A full SHMTI scan is needed when enabling a
+ * group or its timestamps in order to retrieve
+ * offsets: note that when group-timestamp is
+ * enabled for composing DEs a re-scan is needed
+ * since some DEs could have been relocated due
+ * to lack of space in the TDCF.
+ */
+ ret = scmi_telemetry_shmti_scan(ti, sid, SCAN_UPDATE);
+ if (ret)
+ dev_warn(ti->ph->dev,
+ "Failed group-scan of SHMTI ID:%d - ret:%d\n",
+ sid, ret);
+ }
+ } else if (!is_group) {
+ /* Unlink the related BLK_TS/UUID lines on disable */
+ scmi_telemetry_de_unlink(de);
+ }
+
+ return 0;
+}
+
+static int __scmi_telemetry_state_set(const struct scmi_protocol_handle *ph,
+ bool is_group, bool *enable,
+ bool *enabled_state, bool *tstamp,
+ bool *tstamp_enabled_state, void *obj)
+{
+ struct scmi_msg_resp_telemetry_de_configure *resp;
+ struct scmi_msg_telemetry_de_configure *msg;
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_telemetry_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 const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.info_get = scmi_telemetry_info_get,
.de_lookup = scmi_telemetry_de_lookup,
.res_get = scmi_telemetry_resources_get,
+ .state_get = scmi_telemetry_state_get,
+ .state_set = scmi_telemetry_state_set,
+ .all_disable = scmi_telemetry_all_disable,
+ .collection_configure = scmi_telemetry_collection_configure,
};
/**
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index fcb45bd4b44c..03aa6b3dbd6b 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -942,6 +942,12 @@ struct scmi_telemetry_info {
* @info_get: get the general Telemetry information.
* @de_lookup: get a specific DE descriptor from the DE id.
* @res_get: get a reference to the Telemetry resources descriptor.
+ * @state_get: retrieve the specific DE or GROUP state.
+ * @state_set: enable/disable the specific DE or GROUP with or without timestamps.
+ * @all_disable: disable ALL DEs or GROUPs.
+ * @collection_configure: choose a sampling rate and enable SHMTI/FC sampling
+ * for on demand collection via @de_data_read or async
+ * notificatioins for all the enabled DEs.
*/
struct scmi_telemetry_proto_ops {
const struct scmi_telemetry_info __must_check *(*info_get)
@@ -950,6 +956,16 @@ struct scmi_telemetry_proto_ops {
(const struct scmi_protocol_handle *ph, u32 id);
const struct scmi_telemetry_res_info __must_check *(*res_get)
(const struct scmi_protocol_handle *ph);
+ int (*state_get)(const struct scmi_protocol_handle *ph,
+ u32 id, bool *enabled, bool *tstamp_enabled);
+ int (*state_set)(const struct scmi_protocol_handle *ph,
+ bool is_group, u32 id, bool *enable, bool *tstamp);
+ int (*all_disable)(const struct scmi_protocol_handle *ph, bool group);
+ int (*collection_configure)(const struct scmi_protocol_handle *ph,
+ unsigned int res_id, bool grp_ignore,
+ bool *enable,
+ unsigned int *update_interval_ms,
+ enum scmi_telemetry_collection *mode);
};
/**
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 11/24] firmware: arm_scmi: Add Telemetry DataEvent read capabilities
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
` (9 preceding siblings ...)
2026-03-29 16:33 ` [PATCH v3 10/24] firmware: arm_scmi: Add Telemetry configuration operations Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 12/24] firmware: arm_scmi: Add support for Telemetry reset Cristian Marussi
` (12 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, souvik.chakravarty, Cristian Marussi
Add support for Telemetry operations needed to read DataEvent values and
timestamps as single entities or all together in a single bulk buffer.
The returned values are effectiely retrieved from the platform only
when strictly needed, i.e. when no fresh recent cached value was already
available.
The DataEvent values are fetched transparently from the platform origins
using the proper synchronization and consistency primitives, directly from
the SHMTIs areas or the FastChannels memory depending on the configuration.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2 --> v3
- split from monolithic Telemetry patch
- simplity using a few assignement using ternary ops
- remove useless ts param from scanning function
- use a compound literal to simplify samples init
- add a missing __must_check on telemetry_ops
- changed errno on DE read troubles:
- ENODEV/ENOENT: DE is UNKNOWN
- EINVAL: DE is marked as DATA_INVALID
- ENODATA: TLM susbsystem or the specific DE is OFF
---
drivers/firmware/arm_scmi/telemetry.c | 428 ++++++++++++++++++++++++++
include/linux/scmi_protocol.h | 23 ++
2 files changed, 451 insertions(+)
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
index 9536262bbdf7..61eaad817db4 100644
--- a/drivers/firmware/arm_scmi/telemetry.c
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -2031,6 +2031,431 @@ scmi_telemetry_collection_configure(const struct scmi_protocol_handle *ph,
return ret;
}
+static inline void scmi_telemetry_de_data_fc_read(struct telemetry_de *tde,
+ u64 *tstamp, u64 *val)
+{
+ struct fc_tsline __iomem *fc = tde->base + tde->offset;
+
+ *val = LINE_DATA_GET(fc);
+ if (tstamp)
+ *tstamp = tde->de.tstamp_support ? LINE_TSTAMP_GET(fc) : 0;
+
+ trace_scmi_tlm_collect(tstamp ? *tstamp : 0,
+ tde->de.info->id, *val, "FC_READ");
+}
+
+static void scmi_telemetry_scan_update(struct telemetry_info *ti)
+{
+ struct telemetry_de *tde;
+
+ /* Scan all SHMTIs ... */
+ for (int id = 0; id < ti->num_shmti; id++) {
+ int ret;
+
+ ret = scmi_telemetry_shmti_scan(ti, id, SCAN_LOOKUP);
+ if (ret)
+ dev_warn(ti->ph->dev,
+ "Failed update-scan of SHMTI ID:%d - ret:%d\n",
+ id, ret);
+ }
+
+ if (!ti->info.fc_support)
+ return;
+
+ /* Need to enumerate resources to access fastchannels */
+ ti->res_get(ti);
+ list_for_each_entry(tde, &ti->fcs_des, item) {
+ 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;
+ tde->last_ts = tde->de.tstamp_enabled ? tstamp : 0;
+ }
+}
+
+/*
+ * TDCF and TS Line Management Notes
+ * ---------------------------------
+ *
+ * TCDF Payload Metadata notable bits:
+ * - Bit[3]: USE BLK Tstamp
+ * - Bit[2]: Line-Extension Field present (LineTstamp)
+ * - Bit[1]: Tstamp VALID
+ * - Bit[0]: Data INVALID
+ *
+ * CASE_1:
+ * -------
+ * + A DE is enabled with timestamp disabled, so the TS fields are
+ * NOT present
+ * -> BIT[3:0] = 0000b
+ *
+ * - 1/A LINE_TSTAMP
+ * ------------------
+ * + that DE is then 're-enabled' with TS: so it was ON, it remains
+ * ON but using DE_CONFIGURE I now enabled also TS, so the
+ * platform relocates it at the end of the SHMTI and return the
+ * new offset
+ * -> BIT[3:0] = 0110b
+ *
+ * - 1/B BLK_TSTAMP
+ * ------------------
+ * + that DE is then 're-enabled' with BLK TS: so it was ON, it
+ * remains ON but using DE_CONFIGURE, we now also enabled the TS,
+ * so the platform will:
+ * - IF a preceding BLK_TS line exist (with same clk rate)
+ * it relocates the DE at the end of the SHMTI and return the
+ * new offset (if there is enough room, if not in another SHMTI)
+ * - IF a preceding BLK_TS line DOES NOT exist (with same clk rate)
+ * it creates a new BLK_TS line at the end of the SHMTI and then
+ * relocates the DE after the new BLK_TS and return the
+ * new offset (if there is enough room, if not in another SHMTI)
+ * -> BIT[3:1] = 1010b
+ *
+ * + the hole left from the relocated DE can be reused by the platform
+ * to fit another equally sized DE. (i.e. without shuffling around any
+ * other enabled DE, since that would cause a change of the known offset)
+ * anyway it will be marked as:
+ * -> BIT[3:0] = 0101b iff it was a LINE_TSTAMP
+ * -> BIT[3:0] = 0001b iff it was a BLK_TSTAMP
+ *
+ * CASE_2:
+ * -------
+ * + A DE is enabled with LINE timestamp enabled, so the TS_Line is there
+ * -> BIT[3:0] = 0110b
+ * + that DE has its timestamp disabled: again, you can do this without
+ * disabling it fully but just disabling the TS, so now that TS_line
+ * fields are still physically there but NOT valid
+ * -> BIT[3:0] = 0100b
+ * + the hole from the timestamp remain there unused until
+ * - you enable again the TS so the hole is used again
+ * -> BIT[3:0] = 0110b
+ * OR
+ * - you disable fully the DE and then re-enable it with the TS
+ * -> potentially CASE_1 the DE is relocated on enable
+ * + same kind of dynamic applies if the DE had a BLK_TS line
+ */
+static struct payload __iomem *
+scmi_telemetry_tdcf_de_payld_get(struct telemetry_de *tde)
+{
+ struct payload __iomem *payld;
+
+ payld = tde->base + tde->offset;
+ if (DATA_INVALID(payld)) {
+ trace_scmi_tlm_access(PAYLD_ID(payld), "DE_DATA_INVALID", 0, 0);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (IS_BLK_TS_LINE(payld)) {
+ trace_scmi_tlm_access(tde->de.info->id, "BAD_DE_META", 0, 0);
+ return ERR_PTR(-EPROTO);
+ }
+
+ if (PAYLD_ID(payld) != tde->de.info->id) {
+ trace_scmi_tlm_access(tde->de.info->id, "DE_ID_MISMATCH", 0, 0);
+ return ERR_PTR(-ENODEV);
+ }
+
+ /*
+ * A valid line using BLK_TS should have been initialized with the
+ * related BLK_TS when enabled.
+ */
+ if (WARN_ON((USE_BLK_TS(payld) && !tde->bts))) {
+ trace_scmi_tlm_access(tde->de.info->id, "BAD_USE_BLK_TS", 0, 0);
+ return ERR_PTR(-EPROTO);
+ }
+
+ return payld;
+}
+
+static int
+scmi_telemetry_tdcf_de_parse(struct telemetry_de *tde, u64 *tstamp, u64 *val)
+{
+ int retries = SCMI_TLM_TDCF_MAX_RETRIES;
+ struct tdcf __iomem *tdcf = tde->base;
+ u32 startm, endm;
+
+ if (!tdcf)
+ return -ENODEV;
+
+ do {
+ struct payload __iomem *payld;
+
+ /* A bit of exponential backoff between retries */
+ fsleep((SCMI_TLM_TDCF_MAX_RETRIES - retries) * 1000);
+
+ startm = TDCF_START_SEQ_GET(tdcf);
+ if (IS_BAD_START_SEQ(startm)) {
+ trace_scmi_tlm_access(tde->de.info->id, "MSEQ_BADSTART",
+ startm, 0);
+ continue;
+ }
+
+ /* 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 = scmi_telemetry_tdcf_de_payld_get(tde);
+ if (IS_ERR(payld))
+ return PTR_ERR(payld);
+
+ /* Parse data words */
+ scmi_telemetry_line_data_parse(tde, val, tstamp, payld, startm);
+
+ endm = TDCF_END_SEQ_GET(tde->eplg);
+ if (startm != endm)
+ trace_scmi_tlm_access(tde->de.info->id, "MSEQ_MISMATCH",
+ startm, endm);
+ } while (startm != endm && --retries);
+
+ if (startm != endm) {
+ trace_scmi_tlm_access(tde->de.info->id, "TDCF_DE_FAIL",
+ startm, endm);
+ return -EPROTO;
+ }
+
+ 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_access(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 -ENODATA;
+
+ /*
+ * 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_access(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 -ENODATA;
+
+ 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 -ENODATA;
+
+ de = xa_load(&ti->xa_des, sample->id);
+ if (!de)
+ return -ENOENT;
+
+ 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)++] = (struct scmi_telemetry_de_sample) {
+ .tstamp = tstamp,
+ .val = val,
+ .id = de->info->id,
+ };
+ }
+
+ return 0;
+}
+
+static int scmi_telemetry_des_bulk_read(const struct scmi_protocol_handle *ph,
+ int grp_id, int *num_samples,
+ struct scmi_telemetry_de_sample *samples)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ if (!ti->info.enabled || !num_samples || !samples)
+ return -EINVAL;
+
+ /* Trigger a full SHMTIs & FCs scan */
+ scmi_telemetry_scan_update(ti);
+
+ /* Collect all last cached values */
+ return scmi_telemetry_samples_collect(ti, grp_id, num_samples, samples);
+}
+
+static void
+scmi_telemetry_msg_payld_process(struct telemetry_info *ti,
+ unsigned int num_dwords, u32 *dwords)
+{
+ struct scmi_telemetry_res_info *rinfo;
+ u32 next = 0;
+
+ rinfo = ti->res_get(ti);
+ if (!rinfo->fully_enumerated) {
+ dev_warn_once(ti->ph->dev,
+ "Cannot process DEs in message payload. Drop.\n");
+ return;
+ }
+
+ while (next < num_dwords) {
+ struct payload *payld = (struct payload *)&dwords[next];
+ struct scmi_telemetry_de *de;
+ struct telemetry_de *tde;
+ u32 de_id;
+
+ next += LINE_LENGTH_WORDS(payld);
+
+ if (DATA_INVALID(payld)) {
+ dev_err(ti->ph->dev, "MSG - Received INVALID DATA line\n");
+ continue;
+ }
+
+ de_id = le32_to_cpu(payld->id);
+ de = xa_load(&ti->xa_des, de_id);
+ if (!de || !de->enabled) {
+ dev_err(ti->ph->dev,
+ "MSG - Received INVALID DE - ID:%u enabled:%c\n",
+ de_id, de ? (de->enabled ? 'Y' : 'N') : 'X');
+ continue;
+ }
+
+ tde = to_tde(de);
+ guard(mutex)(&tde->mtx);
+ tde->cached = true;
+ tde->last_val = LINE_DATA_GET(&payld->tsl);
+ /* TODO BLK_TS in notification payloads */
+ tde->last_ts = HAS_LINE_EXT(payld) && LINE_TS_VALID(payld) ?
+ LINE_TSTAMP_GET(&payld->tsl) : 0;
+
+ trace_scmi_tlm_collect(tde->last_ts, tde->de.info->id,
+ tde->last_val, "MESSAGE");
+ }
+}
+
+static int scmi_telemetry_des_sample_get(const struct scmi_protocol_handle *ph,
+ int grp_id, int *num_samples,
+ struct scmi_telemetry_de_sample *samples)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_msg_telemetry_config_set *msg;
+ struct scmi_xfer *t;
+ bool 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);
+ /* Scan and update SMHTIs and FCs */
+ scmi_telemetry_scan_update(ti);
+
+ /* Collect all last cached values */
+ ret = scmi_telemetry_samples_collect(ti, grp_id, num_samples,
+ samples);
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.info_get = scmi_telemetry_info_get,
.de_lookup = scmi_telemetry_de_lookup,
@@ -2039,6 +2464,9 @@ static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.state_set = scmi_telemetry_state_set,
.all_disable = scmi_telemetry_all_disable,
.collection_configure = scmi_telemetry_collection_configure,
+ .de_data_read = scmi_telemetry_de_data_read,
+ .des_bulk_read = scmi_telemetry_des_bulk_read,
+ .des_sample_get = scmi_telemetry_des_sample_get,
};
/**
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index 03aa6b3dbd6b..5f05df297064 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -935,6 +935,12 @@ struct scmi_telemetry_info {
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
@@ -948,6 +954,15 @@ struct scmi_telemetry_info {
* @collection_configure: choose a sampling rate and enable SHMTI/FC sampling
* for on demand collection via @de_data_read or async
* notificatioins for all the enabled DEs.
+ * @de_data_read: on-demand read of a single DE and related optional timestamp:
+ * the value will be retrieved at the proper SHMTI offset OR
+ * from the dedicated FC area (if supported by that DE).
+ * @des_bulk_read: on-demand read of all the currently enabled DEs, or just
+ * the ones belonging to a specific group when provided.
+ * @des_sample_get: on-demand read of all the currently enabled DEs, or just
+ * the ones belonging to a specific group when provided.
+ * This causes an immediate update platform-side of all the
+ * enabled DEs.
*/
struct scmi_telemetry_proto_ops {
const struct scmi_telemetry_info __must_check *(*info_get)
@@ -966,6 +981,14 @@ struct scmi_telemetry_proto_ops {
bool *enable,
unsigned int *update_interval_ms,
enum scmi_telemetry_collection *mode);
+ int __must_check (*de_data_read)(const struct scmi_protocol_handle *ph,
+ struct scmi_telemetry_de_sample *sample);
+ int __must_check (*des_bulk_read)(const struct scmi_protocol_handle *ph,
+ int grp_id, int *num_samples,
+ struct scmi_telemetry_de_sample *samples);
+ int __must_check (*des_sample_get)(const struct scmi_protocol_handle *ph,
+ int grp_id, int *num_samples,
+ struct scmi_telemetry_de_sample *samples);
};
/**
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 12/24] firmware: arm_scmi: Add support for Telemetry reset
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
` (10 preceding siblings ...)
2026-03-29 16:33 ` [PATCH v3 11/24] firmware: arm_scmi: Add Telemetry DataEvent read capabilities Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 13/24] firmware: arm_scmi: Add Telemetry notification support Cristian Marussi
` (11 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, souvik.chakravarty, Cristian Marussi
Add support for Telemetry operations needed to request platform to
reset telemetry current configuration and data events values.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2 --> v3
- split from monolithic Telemetry patch
- use scmi_telemetry_de_unlink
---
drivers/firmware/arm_scmi/telemetry.c | 44 +++++++++++++++++++++++++++
include/linux/scmi_protocol.h | 2 ++
2 files changed, 46 insertions(+)
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
index 61eaad817db4..ff0a5a8f6f57 100644
--- a/drivers/firmware/arm_scmi/telemetry.c
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -2456,6 +2456,49 @@ static int scmi_telemetry_des_sample_get(const struct scmi_protocol_handle *ph,
return ret;
}
+static void scmi_telemetry_local_resources_reset(struct telemetry_info *ti)
+{
+ struct scmi_telemetry_res_info *rinfo;
+
+ /* Get rinfo as it is...without triggering an enumeration */
+ rinfo = __scmi_telemetry_resources_get(ti);
+ /* Clear all local state...*/
+ for (int i = 0; i < rinfo->num_des; i++) {
+ rinfo->des[i]->enabled = false;
+ rinfo->des[i]->tstamp_enabled = false;
+
+ scmi_telemetry_de_unlink(rinfo->des[i]);
+ }
+ for (int i = 0; i < rinfo->num_groups; i++) {
+ rinfo->grps[i].enabled = false;
+ rinfo->grps[i].tstamp_enabled = false;
+ rinfo->grps[i].current_mode = SCMI_TLM_ONDEMAND;
+ rinfo->grps[i].active_update_interval = 0;
+ }
+}
+
+static int scmi_telemetry_reset(const struct scmi_protocol_handle *ph)
+{
+ struct scmi_xfer *t;
+ int ret;
+
+ ret = ph->xops->xfer_get_init(ph, TELEMETRY_RESET, sizeof(u32), 0, &t);
+ if (ret)
+ return ret;
+
+ put_unaligned_le32(0, t->tx.buf);
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret) {
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ scmi_telemetry_local_resources_reset(ti);
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.info_get = scmi_telemetry_info_get,
.de_lookup = scmi_telemetry_de_lookup,
@@ -2467,6 +2510,7 @@ static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.de_data_read = scmi_telemetry_de_data_read,
.des_bulk_read = scmi_telemetry_des_bulk_read,
.des_sample_get = scmi_telemetry_des_sample_get,
+ .reset = scmi_telemetry_reset,
};
/**
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index 5f05df297064..fc3b5493dc1a 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -963,6 +963,7 @@ struct scmi_telemetry_de_sample {
* the ones belonging to a specific group when provided.
* This causes an immediate update platform-side of all the
* enabled DEs.
+ * @reset: reset configuration and telemetry data.
*/
struct scmi_telemetry_proto_ops {
const struct scmi_telemetry_info __must_check *(*info_get)
@@ -989,6 +990,7 @@ struct scmi_telemetry_proto_ops {
int __must_check (*des_sample_get)(const struct scmi_protocol_handle *ph,
int grp_id, int *num_samples,
struct scmi_telemetry_de_sample *samples);
+ int (*reset)(const struct scmi_protocol_handle *ph);
};
/**
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 13/24] firmware: arm_scmi: Add Telemetry notification support
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
` (11 preceding siblings ...)
2026-03-29 16:33 ` [PATCH v3 12/24] firmware: arm_scmi: Add support for Telemetry reset Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 14/24] firmware: arm_scmi: Add support for boot-on Telemetry Cristian Marussi
` (10 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, souvik.chakravarty, Cristian Marussi
Add support for notifications to Telemetry protocol and register an
internal notifier during protocol initialization: any DE value received
inside a notification payload will be cached for future user consumption.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2 --> v3
- changed a few dev_err into traces
- split from monolithic telemetry protocol patch
- use memcpy_from_le32
---
drivers/firmware/arm_scmi/telemetry.c | 124 ++++++++++++++++++++++++--
include/linux/scmi_protocol.h | 9 ++
2 files changed, 128 insertions(+), 5 deletions(-)
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
index ff0a5a8f6f57..c793ac616a2a 100644
--- a/drivers/firmware/arm_scmi/telemetry.c
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -448,12 +448,16 @@ struct telemetry_info {
struct list_head free_des;
struct list_head fcs_des;
struct scmi_telemetry_info info;
+ struct notifier_block telemetry_nb;
atomic_t rinfo_initializing;
struct completion rinfo_initdone;
struct scmi_telemetry_res_info __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);
@@ -2379,16 +2383,15 @@ scmi_telemetry_msg_payld_process(struct telemetry_info *ti,
next += LINE_LENGTH_WORDS(payld);
if (DATA_INVALID(payld)) {
- dev_err(ti->ph->dev, "MSG - Received INVALID DATA line\n");
+ trace_scmi_tlm_access(PAYLD_ID(payld), "MSG_INVALID", 0, 0);
continue;
}
de_id = le32_to_cpu(payld->id);
de = xa_load(&ti->xa_des, de_id);
if (!de || !de->enabled) {
- dev_err(ti->ph->dev,
- "MSG - Received INVALID DE - ID:%u enabled:%c\n",
- de_id, de ? (de->enabled ? 'Y' : 'N') : 'X');
+ trace_scmi_tlm_access(de_id, de ? "MSG_DE_DISABLED" :
+ "MSG_DE_UNKNOWN", 0, 0);
continue;
}
@@ -2513,6 +2516,98 @@ static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.reset = scmi_telemetry_reset,
};
+static bool
+scmi_telemetry_notify_supported(const struct scmi_protocol_handle *ph,
+ u8 evt_id, u32 src_id)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ return ti->info.continuos_update_support;
+}
+
+static int
+scmi_telemetry_set_notify_enabled(const struct scmi_protocol_handle *ph,
+ u8 evt_id, u32 src_id, bool enable)
+{
+ return 0;
+}
+
+static void *
+scmi_telemetry_fill_custom_report(const struct scmi_protocol_handle *ph,
+ u8 evt_id, ktime_t timestamp,
+ const void *payld, size_t payld_sz,
+ void *report, u32 *src_id)
+{
+ const struct scmi_telemetry_update_notify_payld *p = payld;
+ struct scmi_telemetry_update_report *r = report;
+
+ /* At least sized as an empty notification */
+ if (payld_sz < sizeof(*p))
+ return NULL;
+
+ r->timestamp = timestamp;
+ r->agent_id = le32_to_cpu(p->agent_id);
+ r->status = le32_to_cpu(p->status);
+ r->num_dwords = le32_to_cpu(p->num_dwords);
+ /*
+ * Allocated dwords and report are sized as max_msg_size, so as
+ * to allow for the maximum payload permitted by the configured
+ * transport. Overflow is not possible since out-of-size messages
+ * are dropped at the transport layer.
+ */
+ if (r->num_dwords)
+ memcpy_from_le32(r->dwords, p->array, r->num_dwords);
+
+ *src_id = 0;
+
+ return r;
+}
+
+static const struct scmi_event tlm_events[] = {
+ {
+ .id = SCMI_EVENT_TELEMETRY_UPDATE,
+ .max_payld_sz = 0,
+ .max_report_sz = 0,
+ },
+};
+
+static const struct scmi_event_ops tlm_event_ops = {
+ .is_notify_supported = scmi_telemetry_notify_supported,
+ .set_notify_enabled = scmi_telemetry_set_notify_enabled,
+ .fill_custom_report = scmi_telemetry_fill_custom_report,
+};
+
+static const struct scmi_protocol_events tlm_protocol_events = {
+ .queue_sz = SCMI_PROTO_QUEUE_SZ,
+ .ops = &tlm_event_ops,
+ .evts = tlm_events,
+ .num_events = ARRAY_SIZE(tlm_events),
+ .num_sources = 1,
+};
+
+static int scmi_telemetry_notifier(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct scmi_telemetry_update_report *er = data;
+ struct telemetry_info *ti = telemetry_nb_to_info(nb);
+
+ if (er->status) {
+ trace_scmi_tlm_access(0, "BAD_NOTIF_MSG", 0, 0);
+ return NOTIFY_DONE;
+ }
+
+ trace_scmi_tlm_access(0, "TLM_UPDATE_MSG", 0, 0);
+ /* Lookup the embedded DEs in the notification payload ... */
+ if (er->num_dwords)
+ scmi_telemetry_msg_payld_process(ti, er->num_dwords, er->dwords);
+
+ /* ...scan the SHMTI/FCs for any other DE updates. */
+ if (ti->streaming_mode)
+ scmi_telemetry_scan_update(ti);
+
+ return NOTIFY_OK;
+}
+
/**
* scmi_telemetry_resources_alloc - Resources allocation
* @ti: A reference to the telemetry info descriptor for this instance
@@ -2757,7 +2852,25 @@ static int scmi_telemetry_protocol_init(const struct scmi_protocol_handle *ph)
ti->info.base.version = ph->version;
- return ph->set_priv(ph, ti);
+ ret = ph->set_priv(ph, ti);
+ if (ret)
+ return ret;
+
+ /*
+ * Register a notifier anyway straight upon protocol initialization
+ * since there could be some DEs that are ONLY reported by notifications
+ * even though the chosen collection method was SHMTI/FCs.
+ */
+ if (ti->info.continuos_update_support) {
+ ti->telemetry_nb.notifier_call = &scmi_telemetry_notifier;
+ ret = ph->notifier_register(ph, SCMI_EVENT_TELEMETRY_UPDATE,
+ NULL, &ti->telemetry_nb);
+ if (ret)
+ dev_warn(ph->dev,
+ "Could NOT register Telemetry notifications\n");
+ }
+
+ return ret;
}
static const struct scmi_protocol scmi_telemetry = {
@@ -2765,6 +2878,7 @@ static const struct scmi_protocol scmi_telemetry = {
.owner = THIS_MODULE,
.instance_init = &scmi_telemetry_protocol_init,
.ops = &tlm_proto_ops,
+ .events = &tlm_protocol_events,
.supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
};
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index fc3b5493dc1a..9d8b12b2160e 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -1198,6 +1198,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 {
@@ -1285,4 +1286,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.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 14/24] firmware: arm_scmi: Add support for boot-on Telemetry
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
` (12 preceding siblings ...)
2026-03-29 16:33 ` [PATCH v3 13/24] firmware: arm_scmi: Add Telemetry notification support Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 15/24] firmware: arm_scmi: Add System Telemetry filesystem driver Cristian Marussi
` (9 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, souvik.chakravarty, Cristian Marussi
Add the initialization and discovery logic needed to detect when the
platform SCMI server is configured with telemetry enabled at boot and
perform all the needed resource enumerations to keep the kernel telemetry
subsystem state aligned with the platform boot-on configurations.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2 --> v3
- split from monolithic telemetry protocol patch
- swap logic in scmi_telemetry_initial_state_lookup
---
drivers/firmware/arm_scmi/telemetry.c | 196 ++++++++++++++++++++++++++
1 file changed, 196 insertions(+)
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
index c793ac616a2a..5526447a8a93 100644
--- a/drivers/firmware/arm_scmi/telemetry.c
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -751,6 +751,190 @@ static int iter_de_descr_process_response(const struct scmi_protocol_handle *ph,
return ret;
}
+static int scmi_telemetry_config_lookup(struct telemetry_info *ti,
+ unsigned int grp_id, bool *enabled,
+ unsigned int *active_update_interval)
+{
+ const struct scmi_protocol_handle *ph = ti->ph;
+ struct scmi_msg_telemetry_config_get *msg;
+ struct scmi_msg_resp_telemetry_config_get *resp;
+ struct scmi_xfer *t;
+ 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));
+
+ if (st->rx_len < (sizeof(*r) + sizeof(r->entry[0]) * st->num_returned))
+ return -EINVAL;
+
+ /*
+ * total enabled is not declared previously anywhere so we
+ * assume it's returned+remaining on first call.
+ */
+ if (!st->max_resources)
+ st->max_resources = st->num_returned + st->num_remaining;
+
+ return 0;
+}
+
+static int
+iter_enabled_list_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_de_enabled_list *r = response;
+ const struct scmi_enabled_de_desc *desc;
+ struct telemetry_info *ti = priv;
+ struct telemetry_de *tde;
+ u32 de_id;
+ int ret;
+
+ desc = &r->entry[st->loop_idx];
+ de_id = le32_to_cpu(desc->id);
+ if (scmi_telemetry_tde_lookup(ti, de_id)) {
+ dev_err(ph->dev,
+ "Found INVALID DE with DUPLICATED ID:0x%08X\n", de_id);
+ return -EINVAL;
+ }
+
+ tde = scmi_telemetry_tde_get(ti, de_id);
+ if (IS_ERR(tde))
+ return PTR_ERR(tde);
+
+ tde->de.info->id = de_id;
+ tde->de.enabled = true;
+ tde->de.tstamp_enabled = 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)
+{
+ struct scmi_telemetry_res_info *rinfo = ACCESS_PRIVATE(ti, rinfo);
+ const struct scmi_protocol_handle *ph = ti->ph;
+ struct scmi_iterator_ops ops = {
+ .prepare_message = iter_enabled_list_prepare_message,
+ .update_state = iter_enabled_list_update_state,
+ .process_response = iter_enabled_list_process_response,
+ };
+ void *iter;
+ int ret;
+
+ iter = ph->hops->iter_response_init(ph, &ops, 0,
+ TELEMETRY_DE_ENABLED_LIST,
+ sizeof(u32) * 2, ti);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+
+ ret = ph->hops->iter_response_run(iter);
+ if (ret)
+ return ret;
+
+ dev_info(ti->ph->dev, "Found %u enabled DEs.\n", rinfo->num_des);
+
+ return 0;
+}
+
+static int scmi_telemetry_initial_state_lookup(struct telemetry_info *ti)
+{
+ struct device *dev = ti->ph->dev;
+ int ret;
+
+ ret = scmi_telemetry_config_lookup(ti, SCMI_TLM_GRP_INVALID,
+ &ti->info.enabled,
+ &ti->info.active_update_interval);
+ if (ret)
+ return ret;
+
+ if (!ti->info.enabled)
+ return 0;
+
+ /*
+ * When Telemetry is found already enabled on the platform, proceed with
+ * passive discovery using DE_ENABLED_LIST and 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, SCAN_DISCOVERY);
+ if (ret)
+ dev_warn(dev,
+ "Failed discovery-scan of SHMTI ID:%d - ret:%d\n",
+ id, ret);
+ }
+
+ return 0;
+}
+
static int
scmi_telemetry_de_groups_init(struct device *dev, struct telemetry_info *ti)
{
@@ -815,6 +999,9 @@ scmi_telemetry_de_groups_init(struct device *dev, struct telemetry_info *ti)
}
}
+ for (int i = 0; i < ti->info.base.num_groups; i++)
+ scmi_telemetry_group_config_lookup(ti, &rinfo->grps[i]);
+
rinfo->num_groups = ti->info.base.num_groups;
return 0;
@@ -2495,6 +2682,11 @@ static int scmi_telemetry_reset(const struct scmi_protocol_handle *ph)
struct telemetry_info *ti = ph->get_priv(ph);
scmi_telemetry_local_resources_reset(ti);
+ /* Fetch again the states from platform. */
+ ret = scmi_telemetry_initial_state_lookup(ti);
+ if (ret)
+ dev_warn(ph->dev,
+ FW_BUG "Cannot retrieve initial state after reset.\n");
}
ph->xops->xfer_put(ph, t);
@@ -2850,6 +3042,10 @@ static int scmi_telemetry_protocol_init(const struct scmi_protocol_handle *ph)
return ret;
}
+ 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 = ph->version;
ret = ph->set_priv(ph, ti);
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 15/24] firmware: arm_scmi: Add System Telemetry filesystem driver
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
` (13 preceding siblings ...)
2026-03-29 16:33 ` [PATCH v3 14/24] firmware: arm_scmi: Add support for boot-on Telemetry Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 16/24] fs/stlmfs: Document ARM SCMI Telemetry filesystem Cristian Marussi
` (8 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, 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>
---
v2 --> v3
- change from tstamp_exp to tstamp_rate entry
- use new interval.num_intervals
- addded a few more comments
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 | 1456 +++++++++++++++++
3 files changed, 1467 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..e850fc57c609
--- /dev/null
+++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
@@ -0,0 +1,1456 @@
+// 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;
+
+ /*
+ * Bail-out when called on a bad tree, so that there is NO need to
+ * check upfront for errors at call-site. (like debugfs)
+ */
+ 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;
+}
+
+/**
+ * scmi_telemetry_res_info_get - Resources info getter
+ * @tsp: A reference to the telemetry instance setup
+ *
+ * On first call this helper takes care to retrieve and cache all the resources
+ * descriptor from the platform, then, on the following invocations it will
+ * always return the cached value.
+ */
+static inline const struct scmi_telemetry_res_info *
+scmi_telemetry_res_info_get(struct scmi_tlm_setup *tsp)
+{
+ const struct scmi_telemetry_res_info *rinfo;
+
+ if (tsp->rinfo)
+ return 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;
+
+ /*
+ * Accepting interval specified as:
+ *
+ * - a single value, interpreted as milliseconds
+ * - a coma separated tuple, with interleaving spaces removed,
+ * interpreted as <secs>,<exp> so that the interval is calculated as:
+ * <secs> x 10 ^ <exp>
+ */
+ 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_intervals * 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_intervals; 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, ¤t_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_rate_tlmo, "tstamp_rate", 0,
+ S_IFREG | S_IRUSR, &sa_u32_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_rate_tlmo,
+ &dei->ts_rate);
+ }
+
+ 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,
+ ¤t_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);
+
+ /*
+ * In the rare case that the file system had already been 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.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 16/24] fs/stlmfs: Document ARM SCMI Telemetry filesystem
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
` (14 preceding siblings ...)
2026-03-29 16:33 ` [PATCH v3 15/24] firmware: arm_scmi: Add System Telemetry filesystem driver Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 17/24] firmware: arm_scmi: Add System Telemetry ioctls support Cristian Marussi
` (7 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, souvik.chakravarty, Cristian Marussi, Jonathan Corbet,
Shuah Khan
Introduce initial ARM SCMI Telemetry filesystem documentation.
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Shuah Khan <skhan@linuxfoundation.org>
Cc: linux-doc@vger.kernel.org
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2 --> v3
- changed tstamp_exp to tstamp_rate
---
Documentation/filesystems/stlmfs.rst | 199 +++++++++++++++++++++++++++
1 file changed, 199 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..2b49beb5b960
--- /dev/null
+++ b/Documentation/filesystems/stlmfs.rst
@@ -0,0 +1,199 @@
+.. 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 debugFS is NOT an 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_tree_single(), 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_rate
+ |-- 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.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 17/24] firmware: arm_scmi: Add System Telemetry ioctls support
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
` (15 preceding siblings ...)
2026-03-29 16:33 ` [PATCH v3 16/24] fs/stlmfs: Document ARM SCMI Telemetry filesystem Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 18/24] fs/stlmfs: Document alternative ioctl based binary interface Cristian Marussi
` (6 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, 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
- use new interval.num_intervals
---
.../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 e850fc57c609..9ac9138e5e30 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"
@@ -1076,6 +1078,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_intervals != tlm_ivs->num_intervals)
+ return -EINVAL;
+
+ if (copy_to_user(uptr, tlm_ivs,
+ sizeof(*tlm_ivs) + sizeof(u32) * ivs.num_intervals))
+ 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;
@@ -1115,6 +1517,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);
@@ -1299,6 +1702,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.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 18/24] fs/stlmfs: Document alternative ioctl based binary interface
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
` (16 preceding siblings ...)
2026-03-29 16:33 ` [PATCH v3 17/24] firmware: arm_scmi: Add System Telemetry ioctls support Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 19/24] firmware: arm_scmi: Add Telemetry components view Cristian Marussi
` (5 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, 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 2b49beb5b960..e4f07a4f930d 100644
--- a/Documentation/filesystems/stlmfs.rst
+++ b/Documentation/filesystems/stlmfs.rst
@@ -113,6 +113,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/
@@ -130,6 +131,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/
@@ -190,6 +195,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
@@ -197,3 +203,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/scmi.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.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 19/24] firmware: arm_scmi: Add Telemetry components view
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
` (17 preceding siblings ...)
2026-03-29 16:33 ` [PATCH v3 18/24] fs/stlmfs: Document alternative ioctl based binary interface Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 20/24] fs/stlmfs: Document alternative topological view Cristian Marussi
` (4 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, 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>
---
v2 --> v3
- renamed components to by_components
- fixed uninitialized variable in scmi_telemetry_de_subdir_symlink()
as reported by Elif
v1 --> v2
- Use new FS API
- Introduce new stlmfs_lookup_by_name helper
---
.../firmware/arm_scmi/scmi_system_telemetry.c | 683 ++++++++++++++++++
1 file changed, 683 insertions(+)
diff --git a/drivers/firmware/arm_scmi/scmi_system_telemetry.c b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
index 9ac9138e5e30..d415a0ee1bc4 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);
@@ -835,6 +1357,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,
@@ -1679,6 +2213,149 @@ 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;
+
+ if (IS_ERR(parent))
+ return 0;
+
+ char *name __free(kfree) = kasprintf(GFP_KERNEL, "0x%08X[%s]",
+ de->info->id, (const char *)de->info->name);
+ 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_make_persistent(dentry, inode);
+
+ simple_done_creating(dentry);
+
+ return 0;
+}
+
+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, "by_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;
@@ -1732,6 +2409,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.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 20/24] fs/stlmfs: Document alternative topological view
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
` (18 preceding siblings ...)
2026-03-29 16:33 ` [PATCH v3 19/24] firmware: arm_scmi: Add Telemetry components view Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 21/24] [RFC] docs: stlmfs: Document ARM SCMI Telemetry FS ABI Cristian Marussi
` (3 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, 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>
---
v2 --> v3
- completed FS tree description
- renamed components to by_components
---
Documentation/filesystems/stlmfs.rst | 72 ++++++++++++++++++++++++++++
1 file changed, 72 insertions(+)
diff --git a/Documentation/filesystems/stlmfs.rst b/Documentation/filesystems/stlmfs.rst
index e4f07a4f930d..a775da24f320 100644
--- a/Documentation/filesystems/stlmfs.rst
+++ b/Documentation/filesystems/stlmfs.rst
@@ -113,6 +113,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
@@ -203,6 +204,77 @@ values, as in::
|-- intervals_discrete
`-- tstamp_enable
+by_components/
+-----------
+
+An alternative topological view of the des/ directory based on the topology
+relationship information described in des/ ::
+
+ by_components/
+ ├── cpu
+ │ ├── 0
+ │ │ ├── celsius
+ │ │ │ └── 0
+ │ │ │ └── 0x00000001[pe_0] -> ../../../../../des/0x00000001
+ │ │ └── cycles
+ │ │ ├── 0
+ │ │ │ └── 0x00001010[] -> ../../../../../des/0x00001010
+ │ │ └── 1
+ │ │ └── 0x00002020[] -> ../../../../../des/0x00002020
+ │ ├── 1
+ │ │ └── celsius
+ │ │ └── 0
+ │ │ └── 0x00000002[pe_1] -> ../../../../../des/0x00000002
+ │ └── 2
+ │ └── celsius
+ │ └── 0
+ │ └── 0x00000003[pe_2] -> ../../../../../des/0x00000003
+ ├── interconnnect
+ │ └── 0
+ │ └── hertz
+ │ └── 0
+ │ ├── 0x0000A008[A008_de] -> ../../../../../des/0x0000A008
+ │ └── 0x0000A00B[] -> ../../../../../des/0x0000A00B
+ ├── mem_cntrl
+ │ └── 0
+ │ ├── bps
+ │ │ └── 0
+ │ │ └── 0x0000A00A[] -> ../../../../../des/0x0000A00A
+ │ ├── celsius
+ │ │ └── 0
+ │ │ └── 0x0000A007[DRAM_temp] -> ../../../../../des/0x0000A007
+ │ └── joules
+ │ └── 0
+ │ └── 0x0000A002[DRAM_energy] -> ../../../../../des/0x0000A002
+ ├── periph
+ │ ├── 0
+ │ │ └── messages
+ │ │ └── 0
+ │ │ └── 0x00000016[device_16] -> ../../../../../des/0x00000016
+ │ ├── 1
+ │ │ └── messages
+ │ │ └── 0
+ │ │ └── 0x00000017[device_17] -> ../../../../../des/0x00000017
+ │ └── 2
+ │ └── messages
+ │ └── 0
+ │ └── 0x00000018[device_18] -> ../../../../../des/0x00000018
+ └── unspec
+ └── 0
+ ├── celsius
+ │ └── 0
+ │ └── 0x0000A005[] -> ../../../../../des/0x0000A005
+ ├── counts
+ │ └── 0
+ │ └── 0x0000A00C[] -> ../../../../../des/0x0000A00C
+ ├── joules
+ │ └── 0
+ │ ├── 0x0000A000[SOC_Energy] -> ../../../../../des/0x0000A000
+ │ └── 0x0000A001[] -> ../../../../../des/0x0000A001
+ └── state
+ └── 0
+ └── 0x0000A010[] -> ../../../../../des/0x0000A010
+
Alternative Binary Interfaces - Special files
=============================================
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 21/24] [RFC] docs: stlmfs: Document ARM SCMI Telemetry FS ABI
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
` (19 preceding siblings ...)
2026-03-29 16:33 ` [PATCH v3 20/24] fs/stlmfs: Document alternative topological view Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 22/24] firmware: arm_scmi: Add lazy population support to Telemetry FS Cristian Marussi
` (2 subsequent siblings)
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, souvik.chakravarty, Cristian Marussi
Add full ABI dcoumentation for stlmfs under testing/
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2-->v3
- complete ABI entries docs
RFC since the documentation is still NOT complete and unsure if place
this into stable/ or testing/
---
Documentation/ABI/testing/stlmfs | 297 +++++++++++++++++++++++++++++++
1 file changed, 297 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..0d9b1c1885a2
--- /dev/null
+++ b/Documentation/ABI/testing/stlmfs
@@ -0,0 +1,297 @@
+What: /sys/fs/arm_telemetry/tlm_<N>/all_des_enable
+Date: May 2026
+KernelVersion: 7.2
+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: May 2026
+KernelVersion: 7.2
+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: May 2026
+KernelVersion: 7.2
+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>/by_components/
+Date: May 2026
+KernelVersion: 7.2
+Contact: cristian.marussi@arm.com
+Description: A subdirectory that exposes an alternative topological view of
+ the same set of discovered DEs that can be already found under
+ the des/ branch.
+ This topology subtree is built following this structure:
+ by_components/
+ ├── <COMPO_TYPE_STR>
+ │ ├── <COMPO_ISTANCE_ID>
+ │ │ ├── <DE_UNIT_TYPE_STR>
+ │ │ │ └── <DE_INSTANCE_ID>
+ │ │ │ └── 0x<DE_ID>[<DE_NAME>] -> ../../../../../des/0x<DE_ID>
+
+ The leaves are actual symlinks to an existing des/0x<DE_ID>
+ subdirectory, while the naming of the subdirectories composing
+ the inner nodes of the subtree are derived from the DataEvent
+ Descriptor in SCMI v4.0 3.12.4.6.
+Users: Any userspace telemetry tool
+
+What: /sys/fs/arm_telemetry/tlm_<N>/control
+Date: May 2026
+KernelVersion: 7.2
+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>/current_update_intervals_ms
+Date: May 2026
+KernelVersion: 7.2
+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, i.e. milliseconds.
+Users: Any userspace telemetry tool
+
+What: /sys/fs/arm_telemetry/tlm_<N>/de_implementation_version
+Date: May 2026
+KernelVersion: 7.2
+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: May 2026
+KernelVersion: 7.2
+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, formatted
+ 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 will have been collected transparently in a number
+ of different ways: on-demand SHMTI lookup, notifications,
+ fastchannels.
+ Any disabled or unavailable DE is simply NOT included.
+Users: Any userspace telemetry tool
+
+What: /sys/fs/arm_telemetry/tlm_<N>/des_single_sample_read
+Date: May 2026
+KernelVersion: 7.2
+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.
+ Any disabled or unavailable DE is simply NOT included.
+Users: Any userspace telemetry tool
+
+What: /sys/fs/arm_telemetry/tlm_<N>/intervals_discrete
+Date: May 2026
+KernelVersion: 7.2
+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: May 2026
+KernelVersion: 7.2
+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: May 2026
+KernelVersion: 7.2
+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 configured DEs updates platform side.
+Users: Any userspace telemetry tool
+
+What: /sys/fs/arm_telemetry/tlm_<N>/version
+Date: May 2026
+KernelVersion: 7.2
+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>/compo_instance_id
+Date: May 2026
+KernelVersion: 7.2
+Contact: cristian.marussi@arm.com
+Description: A RO entry to report the component instance to which this DE
+ belongs, as described by the DataEvent Descriptor in SCMI v4.0
+ 3.12.4.6.
+Users: Any userspace telemetry tool
+
+What: /sys/fs/arm_telemetry/tlm_<N>/des/0x<NNNNNNNN>/compo_type
+Date: May 2026
+KernelVersion: 7.2
+Contact: cristian.marussi@arm.com
+Description: A RO entry to report the component type to which this DE is
+ associated, as described by the DataEvent Descriptor in SCMI v4.0
+ 3.12.4.6.
+Users: Any userspace telemetry tool
+
+What: /sys/fs/arm_telemetry/tlm_<N>/des/0x<NNNNNNNN>/instance_id
+Date: May 2026
+KernelVersion: 7.2
+Contact: cristian.marussi@arm.com
+Description: A RO entry to report the DE instance ID that identifies this DE
+ within the component instance to which it belongs, as described
+ by the DataEvent Descriptor in SCMI v4.0 3.12.4.6.
+Users: Any userspace telemetry tool
+
+What: /sys/fs/arm_telemetry/tlm_<N>/des/0x<NNNNNNNN>/name
+Date: May 2026
+KernelVersion: 7.2
+Contact: cristian.marussi@arm.com
+Description: A optional RO entry to report the name associated with this DE,
+ as described by the DataEvent Descriptor in SCMI v4.0 3.12.4.6.
+Users: Any userspace telemetry tool
+
+What: /sys/fs/arm_telemetry/tlm_<N>/des/0x<NNNNNNNN>/persistent
+Date: May 2026
+KernelVersion: 7.2
+Contact: cristian.marussi@arm.com
+Description: A RO boolean to report that the DE data values are persistent
+ across all reboot cycles, except cold reboot, as described by
+ the DataEvent Descriptor in SCMI v4.0 3.12.4.6.
+Users: Any userspace telemetry tool
+
+What: /sys/fs/arm_telemetry/tlm_<N>/des/0x<NNNNNNNN>/tstamp_rate
+Date: May 2026
+KernelVersion: 7.2
+Contact: cristian.marussi@arm.com
+Description: An optional RO entry to report the clock rate in KHZ used to
+ generate the timestamps associated to this DE, as described by
+ the DataEvent Descriptor in SCMI v4.0 3.12.4.6.
+Users: Any userspace telemetry tool
+
+What: /sys/fs/arm_telemetry/tlm_<N>/des/0x<NNNNNNNN>/type
+Date: May 2026
+KernelVersion: 7.2
+Contact: cristian.marussi@arm.com
+Description: A RO entry to report the type of DataEvent as described by the
+ DataEvent Descriptor in SCMI v4.0 3.12.4.6.
+Users: Any userspace telemetry tool
+
+What: /sys/fs/arm_telemetry/tlm_<N>/des/0x<NNNNNNNN>/unit
+Date: May 2026
+KernelVersion: 7.2
+Contact: cristian.marussi@arm.com
+Description: A RO entry to report the unit of measurements used by this DE,
+ as described by the DataEvent Descriptor in SCMI v4.0 3.12.4.6.
+Users: Any userspace telemetry tool
+
+What: /sys/fs/arm_telemetry/tlm_<N>/des/0x<NNNNNNNN>/unit_exp
+Date: May 2026
+KernelVersion: 7.2
+Contact: cristian.marussi@arm.com
+Description: A RO entry to report the power-of-10 multiplier in two's
+ complement format that is applied to the unit specified by the
+ DE unit field, as described by the DataEvent Descriptor in SCMI
+ v4.0 3.12.4.6.
+Users: Any userspace telemetry tool
+
+What: /sys/fs/arm_telemetry/tlm_<N>/des/0x<NNNNNNNN>/value
+Date: May 2026
+KernelVersion: 7.2
+Contact: cristian.marussi@arm.com
+Description: A RO entry used to read the last value and timestamp collected
+ for Data Event with id 0x<NNNNNNNN> for SCMI instance <N>.
+ The output is formatted as: <TIMESTAMP>: <DATA_VALUE>
+ Reading from this entry can fail with:
+ -ENODATA: the DE itself, or the whole telemetry subsystem,
+ was in a disabled state at the time of the read.
+ -EINVAL: the data value associated to this DE is NOT usable
+ since it was found to have been internally marked as
+ DATA_INVALID; this could be due to a temporary or
+ permanent error condition of the underlying hardware
+ in charge of this DE data collection.
+Users: Any userspace telemetry tool
+
+What: /sys/fs/arm_telemetry/tlm_<N>/des/0x<NNNNNNNN>/enable
+Date: May 2026
+KernelVersion: 7.2
+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: May 2026
+KernelVersion: 7.2
+Contact: cristian.marussi@arm.com
+Description: An RW boolean entry used to enable or disable timestamping for
+ Data Event with id 0x<NNNNNNNN> for SCMI instance <N>.
+ This entry is optional and present only if the DE supports
+ timestamping.
+Users: Any userspace telemetry tool
+
+What: /sys/fs/arm_telemetry/tlm_<N>/groups/<M>/
+Date: May 2026
+KernelVersion: 7.2
+Contact: cristian.marussi@arm.com
+Description: A subdirectory containing entries that describe configurations
+ and values related to group <M> of SCMI instance <N>.
+ Most of the contained entries share the same names with some
+ other, already defined entries, elsewhere:
+
+ groups/0/
+ ├── available_update_intervals_ms
+ ├── control
+ ├── current_update_interval_ms
+ ├── des_bulk_read
+ ├── des_single_sample_read
+ ├── enable
+ ├── intervals_discrete
+ └── tstamp_enable
+
+ These homonyms carry the same syntax and semantic as the other
+ but they are usually restricted in their definitions to the
+ specific group <M>.
+ These common entries won't be described further again here.
+Users: Any userspace telemetry tool
+
+What: /sys/fs/arm_telemetry/tlm_<N>/groups/<M>/composing_des
+Date: May 2026
+KernelVersion: 7.2
+Contact: cristian.marussi@arm.com
+Description: A RO entry that reports the space separated list of DataEvents
+ belonging to group <M> for SCMI instance <N>
+Users: Any userspace telemetry tool
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 22/24] firmware: arm_scmi: Add lazy population support to Telemetry FS
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
` (20 preceding siblings ...)
2026-03-29 16:33 ` [PATCH v3 21/24] [RFC] docs: stlmfs: Document ARM SCMI Telemetry FS ABI Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 23/24] fs/stlmfs: Document lazy mode and related mount option Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 24/24] [RFC] tools/scmi: Add SCMI Telemetry testing tool Cristian Marussi
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, 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>
---
---
.../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 d415a0ee1bc4..f2eb18932aaf 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,12 +778,10 @@ 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;
/*
* Bail-out when called on a bad tree, so that there is NO need to
@@ -756,7 +790,15 @@ stlmfs_create_dentry(struct super_block *sb, struct scmi_tlm_setup *tsp,
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;
@@ -781,14 +823,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;
}
@@ -1330,8 +1382,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,
@@ -1403,48 +1453,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;
@@ -1452,9 +1526,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);
@@ -1594,8 +1855,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);
@@ -2012,66 +2271,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,
@@ -2128,6 +2403,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);
@@ -2171,6 +2447,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;
}
@@ -2262,6 +2541,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);
@@ -2277,6 +2557,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);
@@ -2288,7 +2570,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;
@@ -2329,21 +2611,19 @@ static int scmi_telemetry_topology_add_node(struct super_block *sb,
return ret;
}
-DEFINE_TLM_CLASS(compo_dir_cls, "by_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;
@@ -2353,13 +2633,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);
@@ -2380,10 +2698,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,
+ "by_components",
+ ti->top_dentry,
+ &lazy_compo_fops,
+ &lazy_compo_dir_iops,
+ ti);
return 0;
}
@@ -2391,6 +2724,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;
@@ -2398,6 +2732,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;
@@ -2409,21 +2746,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;
@@ -2433,6 +2786,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);
@@ -2450,17 +2804,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;
@@ -2468,7 +2866,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 = {
@@ -2476,6 +2878,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.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 23/24] fs/stlmfs: Document lazy mode and related mount option
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
` (21 preceding siblings ...)
2026-03-29 16:33 ` [PATCH v3 22/24] firmware: arm_scmi: Add lazy population support to Telemetry FS Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 24/24] [RFC] tools/scmi: Add SCMI Telemetry testing tool Cristian Marussi
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, souvik.chakravarty, Cristian Marussi, Jonathan Corbet,
Shuah Khan
Document optional lazy enumeration behaviour and related mount option.
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Shuah Khan <skhan@linuxfoundation.org>
Cc: linux-doc@vger.kernel.org
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
Documentation/filesystems/stlmfs.rst | 21 +++++++++++++++++++--
1 file changed, 19 insertions(+), 2 deletions(-)
diff --git a/Documentation/filesystems/stlmfs.rst b/Documentation/filesystems/stlmfs.rst
index a775da24f320..9a5c8bf588c8 100644
--- a/Documentation/filesystems/stlmfs.rst
+++ b/Documentation/filesystems/stlmfs.rst
@@ -74,8 +74,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
@@ -105,6 +109,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.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v3 24/24] [RFC] tools/scmi: Add SCMI Telemetry testing tool
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
` (22 preceding siblings ...)
2026-03-29 16:33 ` [PATCH v3 23/24] fs/stlmfs: Document lazy mode and related mount option Cristian Marussi
@ 2026-03-29 16:33 ` Cristian Marussi
23 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
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,
brauner, 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..0c1ad6ad7afe
--- /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_intervals; 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_intervals = 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.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* Re: [PATCH v3 06/24] dt-bindings: firmware: arm,scmi: Add support for telemetry protocol
2026-03-29 16:33 ` [PATCH v3 06/24] dt-bindings: firmware: arm,scmi: Add support for telemetry protocol Cristian Marussi
@ 2026-03-29 17:31 ` Rob Herring (Arm)
2026-03-29 18:00 ` Cristian Marussi
0 siblings, 1 reply; 27+ messages in thread
From: Rob Herring (Arm) @ 2026-03-29 17:31 UTC (permalink / raw)
To: Cristian Marussi
Cc: etienne.carriere, Krzysztof Kozlowski, d-gole, linux-fsdevel,
Conor Dooley, linux-doc, f.fainelli, vincent.guittot,
philip.radford, souvik.chakravarty, peng.fan, dan.carpenter,
lukasz.luba, arm-scmi, sudeep.holla, michal.simek, linux-kernel,
jonathan.cameron, elif.topuz, linux-arm-kernel, james.quinlan,
devicetree, brauner
On Sun, 29 Mar 2026 17:33:17 +0100, Cristian Marussi wrote:
> Add new SCMI v4.0 Telemetry protocol bindings definitions.
>
> Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
> ---
> Cc: Rob Herring <robh@kernel.org>
> Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
> Cc: Conor Dooley <conor+dt@kernel.org>
> Cc: devicetree@vger.kernel.org
> ---
> Documentation/devicetree/bindings/firmware/arm,scmi.yaml | 8 ++++++++
> 1 file changed, 8 insertions(+)
>
My bot found errors running 'make dt_binding_check' on your patch:
yamllint warnings/errors:
dtschema/dtc warnings/errors:
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/firmware/arm,scmi.example.dtb: scmi (arm,scmi): protocol@19: Unevaluated properties are not allowed ('i2c2-pins', 'keys-pins', 'mdio-pins' were unexpected)
from schema $id: http://devicetree.org/schemas/firmware/arm,scmi.yaml
doc reference errors (make refcheckdocs):
See https://patchwork.kernel.org/project/devicetree/patch/20260329163337.637393-7-cristian.marussi@arm.com
The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.
If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:
pip3 install dtschema --upgrade
Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 06/24] dt-bindings: firmware: arm,scmi: Add support for telemetry protocol
2026-03-29 17:31 ` Rob Herring (Arm)
@ 2026-03-29 18:00 ` Cristian Marussi
0 siblings, 0 replies; 27+ messages in thread
From: Cristian Marussi @ 2026-03-29 18:00 UTC (permalink / raw)
To: Rob Herring (Arm)
Cc: Cristian Marussi, etienne.carriere, Krzysztof Kozlowski, d-gole,
linux-fsdevel, Conor Dooley, linux-doc, f.fainelli,
vincent.guittot, philip.radford, souvik.chakravarty, peng.fan,
dan.carpenter, lukasz.luba, arm-scmi, sudeep.holla, michal.simek,
linux-kernel, jonathan.cameron, elif.topuz, linux-arm-kernel,
james.quinlan, devicetree, brauner
On Sun, Mar 29, 2026 at 12:31:33PM -0500, Rob Herring (Arm) wrote:
>
> On Sun, 29 Mar 2026 17:33:17 +0100, Cristian Marussi wrote:
> > Add new SCMI v4.0 Telemetry protocol bindings definitions.
> >
> > Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
> > ---
> > Cc: Rob Herring <robh@kernel.org>
> > Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
> > Cc: Conor Dooley <conor+dt@kernel.org>
> > Cc: devicetree@vger.kernel.org
> > ---
> > Documentation/devicetree/bindings/firmware/arm,scmi.yaml | 8 ++++++++
> > 1 file changed, 8 insertions(+)
> >
>
> My bot found errors running 'make dt_binding_check' on your patch:
>
> yamllint warnings/errors:
>
> dtschema/dtc warnings/errors:
> /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/firmware/arm,scmi.example.dtb: scmi (arm,scmi): protocol@19: Unevaluated properties are not allowed ('i2c2-pins', 'keys-pins', 'mdio-pins' were unexpected)
> from schema $id: http://devicetree.org/schemas/firmware/arm,scmi.yaml
>
> doc reference errors (make refcheckdocs):
>
> See https://patchwork.kernel.org/project/devicetree/patch/20260329163337.637393-7-cristian.marussi@arm.com
>
> The base for the series is generally the latest rc1. A different dependency
> should be noted in *this* patch.
>
> If you already ran 'make dt_binding_check' and didn't see the above
> error(s), then make sure 'yamllint' is installed and dt-schema is up to
> date:
>
> pip3 install dtschema --upgrade
>
Yes...the new protocol block definition ended up intermixed with the
previous protocol block...totally wrong.
My bad.
I will fix in V4.
Thanks,
Cristian
^ permalink raw reply [flat|nested] 27+ messages in thread
end of thread, other threads:[~2026-03-29 18:01 UTC | newest]
Thread overview: 27+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-29 16:33 [PATCH v3 00/24] Introduce SCMI Telemetry FS support Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 01/24] firmware: arm_scmi: Add new SCMIv4.0 error codes definitions Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 02/24] firmware: arm_scmi: Reduce the scope of protocols mutex Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 03/24] firmware: arm_scmi: Allow registration of unknown-size events/reports Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 04/24] firmware: arm_scmi: Allow protocols to register for notifications Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 05/24] uapi: Add ARM SCMI definitions Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 06/24] dt-bindings: firmware: arm,scmi: Add support for telemetry protocol Cristian Marussi
2026-03-29 17:31 ` Rob Herring (Arm)
2026-03-29 18:00 ` Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 07/24] include: trace: Add Telemetry trace events Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 08/24] firmware: arm_scmi: Add basic Telemetry support Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 09/24] firmware: arm_scmi: Add support to parse SHMTIs areas Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 10/24] firmware: arm_scmi: Add Telemetry configuration operations Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 11/24] firmware: arm_scmi: Add Telemetry DataEvent read capabilities Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 12/24] firmware: arm_scmi: Add support for Telemetry reset Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 13/24] firmware: arm_scmi: Add Telemetry notification support Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 14/24] firmware: arm_scmi: Add support for boot-on Telemetry Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 15/24] firmware: arm_scmi: Add System Telemetry filesystem driver Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 16/24] fs/stlmfs: Document ARM SCMI Telemetry filesystem Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 17/24] firmware: arm_scmi: Add System Telemetry ioctls support Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 18/24] fs/stlmfs: Document alternative ioctl based binary interface Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 19/24] firmware: arm_scmi: Add Telemetry components view Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 20/24] fs/stlmfs: Document alternative topological view Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 21/24] [RFC] docs: stlmfs: Document ARM SCMI Telemetry FS ABI Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 22/24] firmware: arm_scmi: Add lazy population support to Telemetry FS Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 23/24] fs/stlmfs: Document lazy mode and related mount option Cristian Marussi
2026-03-29 16:33 ` [PATCH v3 24/24] [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