* [RFC PATCH 0/7] Introduce SCMI Telemetry support
@ 2025-06-20 19:28 Cristian Marussi
2025-06-20 19:28 ` [RFC PATCH 1/7] firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value Cristian Marussi
` (7 more replies)
0 siblings, 8 replies; 21+ messages in thread
From: Cristian Marussi @ 2025-06-20 19:28 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, 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 read them back using the collection method that is
deeemd more suitable for the usecase at hand.
Without delving into the gory details of the whole SCMI Telemetry protocol
let's just say that the SCMI platform firmware advertises a number of
Telemetry Data Events, each one identified by a 32bit unique ID, and a user
can read back at will the associated data value in a number of ways.
Anyway, the set of well-known architected DE IDs defined by the spec is
limited to a dozen IDs, which means that the vast majority of DE IDs are
customizable per-platform: as a consequence the same ID, say '0x1234',
could represent completely different things on different systems.
Data Event IDs definitions and their semantic are supposed to be described
using some sort of JSON-like description file consumed by a userspace tool,
which would be finally in charge of making sense of the exact meaning of
the set of DEs specifically defined as available on a specific platform.
IOW, in turn, this means that even though the DEs enumerated via SCMI come
with some sort of topological and qualitative description (like unit of
measurements), kernel-wise we CANNOT be sure of "what is what" without
being fed-back some sort of information about the DEs semantic by the afore
mentioned userspace tool.
For these reasons, currently this series does NOT attempt to register
any of the discovered DEs with any of the usual in-kernel subsystems (like
HWMON, IIO, POWERCAP,PERF etc), simply because we cannot be sure if a DE is
suitable or not for a given subsystem.
This also means there are NO in-kernel user of these Telemetry data, as of
now.
So, while we do not exclude, for the future, to feed/register some of the
discovered DEs to 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 users.
As of now, really, this series explores 2 main alternative
userspace APIs:
1. a SysFS based human-readable API tree
This API present the discovered DEs and DEs-groups rooted under a
structrure like this:
/sys/class/scmi_telemetry/scmi_tlm_0/
|-- all_des_enable
|-- all_des_tstamp_enable
|-- available_update_intervals_ms
|-- current_update_interval_ms
|-- de_implementation_version
|-- des
| |-- 0x0000
| |-- 0x0016
| |-- 0x1010
| |-- 0xA000
| |-- 0xA001
| |-- 0xA002
| |-- 0xA005
| |-- 0xA007
| |-- 0xA008
| |-- 0xA00A
| |-- 0xA00B
| |-- 0xA00C
| `-- 0xA010
|-- 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 oe rest the whole stack
- read all the enabled DEs in a buffer one-per-line
<DE_ID> <TIMESTAMP> <DATA_VALUE>
with each DE in turn is represented by a subtree like:
scmi_tlm_0/des/0xA001/
|-- compo_instance_id
|-- compo_type
|-- enable
|-- instance_id
|-- 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/value as in:
<TIMESTAMP> <DATA_VALUE>
then for each discovered group of DEs:
scmi_tlm_0/groups/0/
|-- available_update_intervals_ms
|-- composing_des
|-- current_update_interval_ms
|-- des_bulk_read
|-- des_single_sample_read
|-- enable
|-- intervals_discrete
`-- tstamp_enable
you can find the knobs to:
- enable/disable the group as a whole
- lookup group composition
- set a per-group update interval (if supported)
- read all the enabled DEs in a buffer one-per-line
<DE_ID> <TIMESTAMP> <DATA_VALUE>
The problem with this, beside being based on SysFS, is that while it is
easily accessible and human-readable/scriptable does not scale well when
the number of DEs ramps up...
2. an alternative and surely more performant API based on chardev file_ops
and IOCTLs as described fully in:
include/uapi/linux/scmi.h
This, in a nutshell, creates one char-device /dec/scmi_tlm_0 for-each
SCMI Telemetry instance found on the system and then:
- uses some IOCTLs to configure a set of properties equivalent to the
ones above in SysFS
- uses some other IOCTLs for direct access to data in binary format
- uses a .read file_operations to read back a human readable buffer
containing all the enabled DEs using the same format as above
<DE_ID> <TIMESTAMP> <DATA_VALUE>
- (TBD) uses .mmap file_operation to allow for the raw unfiltered access
to the SCMI Telemetry binary data as provided by the platform
This initial RFC aims at first to explore and experiment to find the best
possible userspace API (or mix of APIs) that can provide simplicity of use
while also ensuring high performance from the user-space point of view.
IOW, nothing is set in stone as of now (clearly) some of the alternative
options going ahead are:
A. shrinking the gigantic SysFS above to keep only a few of those knobs
while keeping and extending the chardev API
B. keeping the gigantic FS for readability, but moving to a real
standalone Telemetry-FS to overcome the limitations/constraints of
SysFS, while keeping the chardev/IOCTL API for performance
(not sure anyway the gigantic FS would be acceptable or makes sense
anyway)
C. keeping the gigantic FS but move it to debugfs so as to provide it
only for test/debug/devel, while keeping only the chardev/IOCTLs as
the production interface
... moreover we could also additionally:
D. generalize enough one of the above choices to make it abstract enough
that other non-SCMI based telemetry can plug into some sort of geenric
Telemetry subsystem
E. explore completely different APIs to userspace (netlink ?)
F. additionally serve some of the DEs in some existent Kernel subsystem
(like HWMON/IIO/PERF...) under the constraint discussed above (i.e.
userspace has to tell me which DEs can fit into which subsys)
NOTE THAT, this latter solution CANNOT be the only solution, because
all of the above subsystem (beside PERF) expose a SysFS-based userspace
interface (AFAIK), so, using their standard well-known interfaces WON'T
solve the performance and scalability problem we have in our SysFS.
Beside all of the above, the specification is still in ALPHA_0 and some
features are still NOT supported by this series...
...and of course any form of documentation is still missing :D
Based on V6.16-rc2.
Any feedback welcome,
For whoever had the gut to read till here :P ...
Thanks,
Cristian
[0]: https://developer.arm.com/documentation/den0056/f/?lang=en
Cristian Marussi (7):
firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value
firmware: arm_scmi: Allow protocols to register for notifications
firmware: arm_scmi: Add Telemetry protocol support
firmware: arm_scmi: Add System Telemetry driver
firmware: arm_scmi: Add System Telemetry chardev/ioctls API
include: trace: Add Telemetry trace events
firmware: arm_scmi: Use new Telemetry traces
drivers/firmware/arm_scmi/Kconfig | 10 +
drivers/firmware/arm_scmi/Makefile | 3 +-
drivers/firmware/arm_scmi/common.h | 4 +
drivers/firmware/arm_scmi/driver.c | 14 +
drivers/firmware/arm_scmi/notify.c | 31 +-
drivers/firmware/arm_scmi/notify.h | 8 +-
drivers/firmware/arm_scmi/protocols.h | 9 +
.../firmware/arm_scmi/scmi_system_telemetry.c | 1459 ++++++++++++++
drivers/firmware/arm_scmi/telemetry.c | 1744 +++++++++++++++++
include/linux/scmi_protocol.h | 201 +-
include/trace/events/scmi.h | 48 +-
include/uapi/linux/scmi.h | 253 +++
12 files changed, 3769 insertions(+), 15 deletions(-)
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
--
2.47.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [RFC PATCH 1/7] firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value
2025-06-20 19:28 [RFC PATCH 0/7] Introduce SCMI Telemetry support Cristian Marussi
@ 2025-06-20 19:28 ` Cristian Marussi
2025-06-24 3:13 ` Peng Fan
2025-06-20 19:28 ` [RFC PATCH 2/7] firmware: arm_scmi: Allow protocols to register for notifications Cristian Marussi
` (6 subsequent siblings)
7 siblings, 1 reply; 21+ messages in thread
From: Cristian Marussi @ 2025-06-20 19:28 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi
Add a common definition of SCMI_MAX_PROTOCOLS and use it all over the
SCMI stack.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
drivers/firmware/arm_scmi/notify.c | 4 +---
include/linux/scmi_protocol.h | 3 +++
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/drivers/firmware/arm_scmi/notify.c b/drivers/firmware/arm_scmi/notify.c
index e160ecb22948..27a53a6729dd 100644
--- a/drivers/firmware/arm_scmi/notify.c
+++ b/drivers/firmware/arm_scmi/notify.c
@@ -94,8 +94,6 @@
#include "common.h"
#include "notify.h"
-#define SCMI_MAX_PROTO 256
-
#define PROTO_ID_MASK GENMASK(31, 24)
#define EVT_ID_MASK GENMASK(23, 16)
#define SRC_ID_MASK GENMASK(15, 0)
@@ -1652,7 +1650,7 @@ int scmi_notification_init(struct scmi_handle *handle)
ni->gid = gid;
ni->handle = handle;
- ni->registered_protocols = devm_kcalloc(handle->dev, SCMI_MAX_PROTO,
+ ni->registered_protocols = devm_kcalloc(handle->dev, SCMI_MAX_PROTOCOLS,
sizeof(char *), GFP_KERNEL);
if (!ni->registered_protocols)
goto err;
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index 688466a0e816..6f8d36e1f8fc 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -926,8 +926,11 @@ enum scmi_std_protocol {
SCMI_PROTOCOL_VOLTAGE = 0x17,
SCMI_PROTOCOL_POWERCAP = 0x18,
SCMI_PROTOCOL_PINCTRL = 0x19,
+ SCMI_PROTOCOL_LAST = 0xff,
};
+#define SCMI_MAX_PROTOCOLS (SCMI_PROTOCOL_LAST + 1)
+
enum scmi_system_events {
SCMI_SYSTEM_SHUTDOWN,
SCMI_SYSTEM_COLDRESET,
--
2.47.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [RFC PATCH 2/7] firmware: arm_scmi: Allow protocols to register for notifications
2025-06-20 19:28 [RFC PATCH 0/7] Introduce SCMI Telemetry support Cristian Marussi
2025-06-20 19:28 ` [RFC PATCH 1/7] firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value Cristian Marussi
@ 2025-06-20 19:28 ` Cristian Marussi
2025-06-20 19:28 ` [RFC PATCH 3/7] firmware: arm_scmi: Add Telemetry protocol support Cristian Marussi
` (5 subsequent siblings)
7 siblings, 0 replies; 21+ messages in thread
From: Cristian Marussi @ 2025-06-20 19:28 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi
Allow protocols themselves to register for their own notifications and
providing their own notifier callbacks. While at that, allow for a protocol
to register events with compilation-time unknown report/event sizes: such
events will use the maximum transport size.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
drivers/firmware/arm_scmi/common.h | 4 ++++
drivers/firmware/arm_scmi/driver.c | 12 ++++++++++++
drivers/firmware/arm_scmi/notify.c | 27 ++++++++++++++++++++-------
drivers/firmware/arm_scmi/notify.h | 8 ++++++--
drivers/firmware/arm_scmi/protocols.h | 8 ++++++++
5 files changed, 50 insertions(+), 9 deletions(-)
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index dab758c5fdea..daf500503166 100644
--- a/drivers/firmware/arm_scmi/common.h
+++ b/drivers/firmware/arm_scmi/common.h
@@ -17,6 +17,7 @@
#include <linux/hashtable.h>
#include <linux/list.h>
#include <linux/module.h>
+#include <linux/notifier.h>
#include <linux/refcount.h>
#include <linux/scmi_protocol.h>
#include <linux/spinlock.h>
@@ -498,4 +499,7 @@ 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);
#endif /* _SCMI_COMMON_H */
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index 395fe9289035..1b9404175098 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -1661,6 +1661,17 @@ static void *scmi_get_protocol_priv(const struct scmi_protocol_handle *ph)
return pi->priv;
}
+static int
+scmi_register_instance_notifier(const struct scmi_protocol_handle *ph,
+ u8 evt_id, const u32 *src_id,
+ struct notifier_block *nb)
+{
+ const struct scmi_protocol_instance *pi = ph_to_pi(ph);
+
+ return scmi_notifier_register(pi->handle, pi->proto->id,
+ evt_id, src_id, nb);
+}
+
static const struct scmi_xfer_ops xfer_ops = {
.version_get = version_get,
.xfer_get_init = xfer_get_init,
@@ -2161,6 +2172,7 @@ scmi_alloc_init_protocol_instance(struct scmi_info *info,
pi->ph.hops = &helpers_ops;
pi->ph.set_priv = scmi_set_protocol_priv;
pi->ph.get_priv = scmi_get_protocol_priv;
+ pi->ph.notifier_register = scmi_register_instance_notifier;
refcount_set(&pi->users, 1);
/* proto->init is assured NON NULL by scmi_protocol_register */
ret = pi->proto->instance_init(&pi->ph);
diff --git a/drivers/firmware/arm_scmi/notify.c b/drivers/firmware/arm_scmi/notify.c
index 27a53a6729dd..2ae705dab2fd 100644
--- a/drivers/firmware/arm_scmi/notify.c
+++ b/drivers/firmware/arm_scmi/notify.c
@@ -589,7 +589,12 @@ 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;
}
@@ -748,7 +753,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;
@@ -763,6 +768,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;
@@ -775,8 +782,13 @@ int scmi_register_protocol_events(const struct scmi_handle *handle, u8 proto_id,
}
evt = ee->evts;
- for (i = 0; i < ee->num_events; i++)
+ for (i = 0; i < ee->num_events; i++) {
+ if (evt[i].max_payld_sz == 0) {
+ payld_sz = max_msg_sz;
+ break;
+ }
payld_sz = max_t(size_t, payld_sz, evt[i].max_payld_sz);
+ }
payld_sz += sizeof(struct scmi_event_header);
pd = scmi_allocate_registered_events_desc(ni, proto_id, ee->queue_sz,
@@ -805,7 +817,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;
@@ -1352,9 +1365,9 @@ static int scmi_event_handler_enable_events(struct scmi_event_handler *hndl)
*
* Return: 0 on Success
*/
-static int scmi_notifier_register(const struct scmi_handle *handle,
- u8 proto_id, u8 evt_id, const u32 *src_id,
- struct notifier_block *nb)
+int scmi_notifier_register(const struct scmi_handle *handle,
+ u8 proto_id, u8 evt_id, const u32 *src_id,
+ struct notifier_block *nb)
{
int ret = 0;
u32 evt_key;
diff --git a/drivers/firmware/arm_scmi/notify.h b/drivers/firmware/arm_scmi/notify.h
index 76758a736cf4..ecfa4b746487 100644
--- a/drivers/firmware/arm_scmi/notify.h
+++ b/drivers/firmware/arm_scmi/notify.h
@@ -18,8 +18,12 @@
/**
* struct scmi_event - Describes an event to be supported
* @id: Event ID
- * @max_payld_sz: Max possible size for the payload of a notification message
- * @max_report_sz: Max possible size for the report of a notification message
+ * @max_payld_sz: Max possible size for the payload of a notification message.
+ * Set to zero to use the maximum payload size allowed by the
+ * transport.
+ * @max_report_sz: Max possible size for the report of a notification message.
+ * Set to zero to use the maximum payload size allowed by the
+ * transport.
*
* Each SCMI protocol, during its initialization phase, can describe the events
* it wishes to support in a few struct scmi_event and pass them to the core
diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h
index d62c4469d1fd..2e40a7bb5b01 100644
--- a/drivers/firmware/arm_scmi/protocols.h
+++ b/drivers/firmware/arm_scmi/protocols.h
@@ -161,8 +161,13 @@ struct scmi_proto_helpers_ops;
* @dev: A reference to the associated SCMI instance device (handle->dev).
* @xops: A reference to a struct holding refs to the core xfer operations that
* can be used by the protocol implementation to generate SCMI messages.
+ * @hops: A reference to a struct holding refs to the common helper operations
+ * that can be used by the protocol implementation.
* @set_priv: A method to set protocol private data for this instance.
* @get_priv: A method to get protocol private data previously set.
+ * @notifier_register: A method to register interest for notifications from
+ * within a protocol implementation unit: notifiers can
+ * be registered only for the same protocol.
*
* This structure represents a protocol initialized against specific SCMI
* instance and it will be used as follows:
@@ -182,6 +187,9 @@ struct scmi_protocol_handle {
int (*set_priv)(const struct scmi_protocol_handle *ph, void *priv,
u32 version);
void *(*get_priv)(const struct scmi_protocol_handle *ph);
+ int (*notifier_register)(const struct scmi_protocol_handle *ph,
+ u8 evt_id, const u32 *src_id,
+ struct notifier_block *nb);
};
/**
--
2.47.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [RFC PATCH 3/7] firmware: arm_scmi: Add Telemetry protocol support
2025-06-20 19:28 [RFC PATCH 0/7] Introduce SCMI Telemetry support Cristian Marussi
2025-06-20 19:28 ` [RFC PATCH 1/7] firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value Cristian Marussi
2025-06-20 19:28 ` [RFC PATCH 2/7] firmware: arm_scmi: Allow protocols to register for notifications Cristian Marussi
@ 2025-06-20 19:28 ` Cristian Marussi
2025-06-20 20:46 ` Dan Carpenter
2025-06-20 21:01 ` Dan Carpenter
2025-06-20 19:28 ` [RFC PATCH 4/7] firmware: arm_scmi: Add System Telemetry driver Cristian Marussi
` (4 subsequent siblings)
7 siblings, 2 replies; 21+ messages in thread
From: Cristian Marussi @ 2025-06-20 19:28 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi
Add basic support for SCMI V4.0-alpha_0 Telemetry protocol including SHMTI,
FastChannels, Notifications and Single Sample Reads collection methods.
Still lacking:
- Block timestamp lines
- Handling of already-on-at-boot Telemetry setup
- Complete groups support (offset on enable and ungrouped DEs)
- mmap access operations for RAW access
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
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 | 1719 +++++++++++++++++++++++++
include/linux/scmi_protocol.h | 198 ++-
5 files changed, 1920 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 1b9404175098..b235fc7f1a65 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -3445,6 +3445,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);
}
@@ -3463,6 +3464,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 2e40a7bb5b01..edd83a02e272 100644
--- a/drivers/firmware/arm_scmi/protocols.h
+++ b/drivers/firmware/arm_scmi/protocols.h
@@ -387,5 +387,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..3cbad06251a9
--- /dev/null
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -0,0 +1,1719 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * System Control and Management Interface (SCMI) Telemetry Protocol
+ *
+ * Copyright (C) 2025 ARM Ltd.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/xarray.h>
+
+#include "protocols.h"
+#include "notify.h"
+
+/* Updated only after ALL the mandatory features for that version are merged */
+#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x10000
+
+#define SCMI_TLM_TDCF_MAX_RETRIES 5
+
+enum scmi_telemetry_protocol_cmd {
+ TELEMETRY_LIST_SHMTI = 0x3,
+ TELEMETRY_DE_DESCRIPTION = 0x4,
+ TELEMETRY_LIST_UPDATE_INTERVALS = 0x5,
+ TELEMETRY_DE_CONFIGURE = 0x6,
+ TELEMETRY_DE_ENABLED_LIST = 0x7, //TODO IMPLEMENT
+ TELEMETRY_CONFIG_SET = 0x8,
+ TELEMETRY_READING_COMPLETE = TELEMETRY_CONFIG_SET,
+ TELEMETRY_CONFIG_GET = 0x9, //TODO IMPLEMENT !
+ TELEMETRY_RESET = 0xA,
+};
+
+struct scmi_msg_resp_telemetry_protocol_attributes {
+ __le32 de_num;
+ __le32 groups_num;
+ __le32 de_implementation_rev_dword[SCMI_TLM_MAX_DWORD];
+ __le32 attributes;
+#define SUPPORTS_SINGLE_READ(x) ((x) & BIT(31))
+#define SUPPORTS_CONTINUOS_UPDATE(x) ((x) & BIT(30))
+#define SUPPORTS_PER_GROUP_CONFIG(x) ((x) & BIT(18))
+#define SUPPORTS_RESET(x) ((x) & BIT(17))
+#define SUPPORTS_FC(x) ((x) & BIT(16))
+};
+
+struct scmi_telemetry_update_notify_payld {
+ __le32 agent_id;
+ __le32 status;
+ __le32 num_dwords;
+ __le32 array[];
+};
+
+struct scmi_shmti_desc {
+ __le32 id;
+ __le32 addr_low;
+ __le32 addr_high;
+ __le32 length;
+};
+
+struct scmi_msg_resp_telemetry_shmti_list {
+ __le32 num_shmti;
+ struct scmi_shmti_desc desc[];
+};
+
+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) \
+ ({ \
+ int __signed_exp = \
+ le32_get_bits((d)->attr_1, GENMASK(20, 13)); \
+ \
+ if (__signed_exp & BIT(7)) \
+ __signed_exp |= GENMASK(31, 8); \
+ __signed_exp; \
+ })
+#define GET_DE_UNIT(d) (le32_get_bits((d)->attr_1, GENMASK(12, 5)))
+
+#define GET_DE_TSTAMP_EXP(d) \
+ ({ \
+ int __signed_exp = \
+ FIELD_GET(GENMASK(4, 1), (d)->attr_1); \
+ \
+ if (__signed_exp & BIT(3)) \
+ __signed_exp |= GENMASK(31, 4); \
+ __signed_exp; \
+ })
+#define IS_TSTAMP_SUPPORTED(d) ((d)->attr_1 & BIT(0))
+ __le32 attr_2;
+#define GET_DE_INSTA_ID(d) (le32_get_bits((d)->attr_2, GENMASK(31, 24)))
+#define GET_COMPO_INSTA_ID(d) (le32_get_bits((d)->attr_2, GENMASK(23, 8)))
+#define GET_COMPO_TYPE(d) (le32_get_bits((d)->attr_2, GENMASK(7, 0)))
+ __le32 reserved;
+};
+
+struct scmi_msg_resp_telemetry_de_description {
+ __le32 num_desc;
+ struct scmi_de_desc desc[];
+};
+
+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_configure {
+ __le32 id;
+ __le32 flags;
+#define DE_ENABLE_NO_TSTAMP BIT(0)
+#define DE_ENABLE_WTH_TSTAMP BIT(1)
+#define DE_DISABLE_ALL BIT(2)
+#define GROUP_SELECTOR BIT(3)
+#define EVENT_DE 0
+#define EVENT_GROUP 1
+#define DE_DISABLE_ONE 0x0
+};
+
+struct scmi_msg_resp_telemetry_de_configure {
+ __le32 shmti_id;
+#define IS_SHMTI_ID_VALID(x) ((x) != 0xFFFFFFFF)
+ __le32 tdcf_de_offset;
+};
+
+struct scmi_msg_telemetry_config_set {
+ __le32 grp_id;
+ __le32 control;
+#define TELEMETRY_ENABLE (BIT(0))
+
+#define TELEMETRY_MODE(x) (FIELD_PREP(GENMASK(4, 1), (x)))
+#define TELEMETRY_MODE_ONDEMAND TELEMETRY_MODE(0)
+#define TELEMETRY_MODE_NOTIFS TELEMETRY_MODE(1)
+#define TELEMETRY_MODE_SINGLE TELEMETRY_MODE(2)
+
+#define TELEMETRY_SELECTOR(x) (FIELD_PREP(GENMASK(8, 5), (x)))
+#define TELEMETRY_SELECTOR_ORPHANS TELEMETRY_SELECTOR(0)
+#define TELEMETRY_SELECTOR_GROUP TELEMETRY_SELECTOR(1)
+#define TELEMETRY_SELECTOR_ALL TELEMETRY_SELECTOR(2)
+ __le32 sampling_rate;
+};
+
+struct scmi_msg_resp_telemetry_reading_complete {
+ __le32 num_dwords;
+ __le32 dwords[];
+};
+
+/* TDCF */
+
+#define TO_CPU_64(h, l) (((u64)le32_to_cpu((h)) << 32) | le32_to_cpu((l)))
+
+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;
+};
+
+#define BLK_TSTAMP(l) ((((u64)(l)->data_high) << 32) | (l)->data_low)
+
+struct blk_tsline {
+ u32 ts_low;
+ u32 ts_high;
+};
+
+struct tsline {
+ u32 data_low;
+ u32 data_high;
+ u32 ts_low;
+ u32 ts_high;
+};
+
+#define LINE_DATA_GET(f) (TO_CPU_64((f)->data_high, (f)->data_low))
+#define LINE_TSTAMP_GET(f) (TO_CPU_64((f)->ts_high, (f)->ts_low))
+
+struct payload {
+ u32 meta;
+#define IS_BLK_TS(x) ((x)->meta & BIT(4))
+#define USE_BLK_TS(x) ((x)->meta & BIT(3))
+#define USE_LINE_TS(x) ((x)->meta & BIT(2))
+#define TS_VALID(x) ((x)->meta & BIT(1))
+#define DATA_INVALID(x) ((x)->meta & BIT(0))
+ u32 id;
+ union {
+ struct line l;
+ struct tsline tsl;
+ struct blk_tsline blk_tsl;
+ };
+};
+
+#define LINE_DATA_PAYLD_WORDS \
+ ((sizeof(u32) + sizeof(u32) + sizeof(struct line)) / sizeof(u32))
+#define TS_LINE_DATA_PAYLD_WORDS \
+ ((sizeof(u32) + sizeof(u32) + sizeof(struct tsline)) / sizeof(u32))
+
+struct prlg {
+ u32 seq_low;
+ u32 seq_high;
+ u32 num_qwords;
+ u32 _meta_header_high;
+};
+
+struct eplg {
+ u32 seq_low;
+ u32 seq_high;
+};
+
+#define TDCF_EPLG_SZ (sizeof(struct eplg))
+
+struct tdcf {
+ struct prlg prlg;
+ unsigned char payld[];
+};
+
+#define TDCF_START_SEQ_GET(x) \
+ ({ \
+ u64 _val; \
+ struct prlg *_p = &((x)->prlg); \
+ \
+ _val = TO_CPU_64(_p->seq_high, _p->seq_low); \
+ (_val); \
+ })
+
+#define IS_BAD_START_SEQ(s) ((s) & 0x1)
+
+#define TDCF_END_SEQ_GET(e) \
+ ({ \
+ u64 _val; \
+ struct eplg *_e = (e); \
+ \
+ _val = TO_CPU_64(_e->seq_high, _e->seq_low); \
+ (_val); \
+ })
+
+struct telemetry_shmti {
+ int id;
+ void __iomem *base;
+ u32 len;
+};
+
+#define SHMTI_EPLG(s) \
+ ({ \
+ struct telemetry_shmti *_s = (s); \
+ void *_eplg; \
+ \
+ _eplg = _s->base + _s->len; \
+ (_eplg); \
+ })
+
+struct telemetry_info {
+ bool streaming_mode;
+ int num_shmti;
+ struct device *dev;
+ struct telemetry_shmti *shmti;
+ struct xarray xa_des;
+ struct scmi_telemetry_info info;
+ struct notifier_block telemetry_nb;
+};
+
+#define telemetry_nb_to_info(x) \
+ container_of(x, struct telemetry_info, telemetry_nb)
+
+struct telemetry_de {
+ bool cached;
+ char name[SCMI_SHORT_NAME_MAX_SIZE];
+ void __iomem *base;
+ void __iomem *eplg;
+ u32 offset;
+ /* NOTE THAT DE data_sz is registered in scmi_telemetry_de */
+ u32 fc_size;
+ struct scmi_telemetry_de de;
+ u64 last_val;
+ u64 last_ts;
+ /* Protect last_val/ts accesses */
+ struct mutex mtx;
+};
+
+#define to_tde(d) container_of(d, struct telemetry_de, de)
+
+struct scmi_tlm_de_priv {
+ struct telemetry_info *ti;
+ void *next;
+};
+
+static int
+scmi_telemetry_protocol_attributes_get(const struct scmi_protocol_handle *ph,
+ struct telemetry_info *ti)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_telemetry_protocol_attributes *resp;
+
+ 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.num_de = le32_to_cpu(resp->de_num);
+ ti->info.num_groups = le32_to_cpu(resp->groups_num);
+ for (int i = 0; i < SCMI_TLM_MAX_DWORD; i++)
+ ti->info.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));
+ /* Allocate DEs descriptors */
+ ti->info.des = devm_kcalloc(ph->dev, ti->info.num_de,
+ sizeof(*ti->info.des), GFP_KERNEL);
+ if (!ti->info.des)
+ ret = -ENOMEM;
+
+ /* Allocate DE GROUPS descriptors */
+ ti->info.des_groups = devm_kcalloc(ph->dev, ti->info.num_groups,
+ sizeof(*ti->info.des_groups),
+ GFP_KERNEL);
+ if (!ti->info.des_groups)
+ ret = -ENOMEM;
+
+ for (int i = 0; i < ti->info.num_groups; i++)
+ ti->info.des_groups[i].id = i;
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static void iter_tlm_prepare_message(void *message,
+ unsigned int desc_index, const void *priv)
+{
+ put_unaligned_le32(desc_index, message);
+}
+
+static int iter_de_descr_update_state(struct scmi_iterator_state *st,
+ const void *response, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_de_description *r = response;
+ struct scmi_tlm_de_priv *p = priv;
+
+ st->num_returned = le32_get_bits(r->num_desc, GENMASK(15, 0));
+ st->num_remaining = le32_get_bits(r->num_desc, GENMASK(31, 16));
+
+ /* Initialized to first descriptor */
+ p->next = (void *)r->desc;
+
+ return 0;
+}
+
+static int iter_de_descr_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st,
+ void *priv)
+{
+ struct telemetry_de *tde;
+ struct scmi_tlm_de_priv *p = priv;
+ const struct scmi_de_desc *desc = p->next;
+ unsigned int grp_id;
+ int ret;
+
+ tde = to_tde(p->ti->info.des[st->desc_index + st->loop_idx]);
+
+ tde->de.id = le32_to_cpu(desc->id);
+ grp_id = le32_to_cpu(desc->grp_id);
+ if (grp_id != SCMI_TLM_GRP_INVALID) {
+ if (grp_id >= p->ti->info.num_groups)
+ return -EINVAL;
+
+ /* Link to parent group */
+ tde->de.grp = &p->ti->info.des_groups[grp_id];
+ }
+ tde->de.data_sz = le32_to_cpu(desc->data_sz);
+ tde->de.type = GET_DE_TYPE(desc);
+ tde->de.unit = GET_DE_UNIT(desc);
+ tde->de.unit_exp = GET_DE_UNIT_EXP(desc);
+ tde->de.tstamp_exp = GET_DE_TSTAMP_EXP(desc);
+ tde->de.instance_id = GET_DE_INSTA_ID(desc);
+ tde->de.compo_instance_id = GET_COMPO_INSTA_ID(desc);
+ tde->de.compo_type = GET_COMPO_TYPE(desc);
+ tde->de.persistent = IS_PERSISTENT(desc);
+ tde->de.tstamp_support = IS_TSTAMP_SUPPORTED(desc);
+ tde->de.fc_support = IS_FC_SUPPORTED(desc);
+ tde->de.name_support = IS_NAME_SUPPORTED(desc);
+ p->next += sizeof(*desc);
+ if (tde->de.fc_support) {
+ u32 size;
+ u64 phys_addr;
+ void __iomem *addr;
+ struct de_desc_fc *dfc;
+
+ dfc = p->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(ph->dev, phys_addr, size);
+ if (!addr)
+ return -EADDRNOTAVAIL;
+
+ tde->base = addr;
+ tde->offset = 0;
+ tde->fc_size = size;
+
+ /* Variably sized depending on FC support */
+ p->next += sizeof(*dfc);
+ }
+
+ if (tde->de.name_support) {
+ const char *de_name = p->next;
+
+ strscpy(tde->name, de_name, SCMI_SHORT_NAME_MAX_SIZE);
+ tde->de.name = tde->name;
+
+ /* Variably sized depending on name support */
+ p->next += SCMI_SHORT_NAME_MAX_SIZE;
+ }
+
+ /* Store DE pointer by de_id */
+ ret = xa_insert(&p->ti->xa_des, tde->de.id, &tde->de, GFP_KERNEL);
+ if (ret)
+ return ret;
+
+ /* Account for this DE in group num_de counter */
+ if (tde->de.grp)
+ tde->de.grp->num_de++;
+
+ return 0;
+}
+
+static int
+scmi_telemetry_de_groups_init(struct device *dev, struct telemetry_info *ti)
+{
+ /* Allocate all groups DEs IDs arrays at first ... */
+ for (int i = 0; i < ti->info.num_groups; i++) {
+ struct scmi_telemetry_group *grp = &ti->info.des_groups[i];
+
+ grp->des = devm_kcalloc(dev, grp->num_de, sizeof(unsigned int),
+ GFP_KERNEL);
+ if (!grp->des)
+ return -ENOMEM;
+
+ /*
+ * Max size 32bit ID string in Hex: 0xCAFECAFE
+ * - 10 digits + ' '/'\n' = 11 bytes per number
+ * - terminating NUL character
+ */
+ grp->des_str_sz = grp->num_de * 11 + 1;
+ grp->des_str = devm_kzalloc(dev, grp->des_str_sz, GFP_KERNEL);
+ if (!grp->des_str)
+ return -ENOMEM;
+
+ /* Reset group DE counter */
+ grp->num_de = 0;
+ }
+
+ /* Scan DEs and populate group DE IDs arrays */
+ for (int i = 0; i < ti->info.num_de; i++) {
+ struct scmi_telemetry_group *grp = ti->info.des[i]->grp;
+
+ if (!grp)
+ continue;
+
+ /* Note that, at this point, num_de is guaranteed to be
+ * sane (in-bounds) by construction.
+ */
+ grp->des[grp->num_de++] = i;
+ }
+
+ /* Build compsing DES string */
+ for (int i = 0; i < ti->info.num_groups; i++) {
+ struct scmi_telemetry_group *grp = &ti->info.des_groups[i];
+ char *buf = grp->des_str;
+ size_t bufsize = grp->des_str_sz;
+
+ for (int j = 0; j < grp->num_de; j++) {
+ char term = j != (grp->num_de - 1) ? ' ' : '\0';
+ int len;
+
+ len = snprintf(buf, bufsize, "0x%04X%c",
+ ti->info.des[grp->des[j]]->id, term);
+
+ buf += len;
+ bufsize -= len;
+ }
+ }
+
+ return 0;
+}
+
+static int
+scmi_telemetry_de_descriptors_get(const struct scmi_protocol_handle *ph,
+ struct telemetry_info *ti)
+{
+ 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;
+
+ xa_init(&ti->xa_des);
+ iter = ph->hops->iter_response_init(ph, &ops, ti->info.num_de,
+ 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);
+}
+
+static int scmi_telemetry_enumerate_de(const struct scmi_protocol_handle *ph,
+ struct telemetry_info *ti)
+{
+ int ret;
+
+ if (!ti->info.num_de)
+ return 0;
+
+ for (int i = 0; i < ti->info.num_de; i++) {
+ struct telemetry_de *tde;
+
+ tde = devm_kzalloc(ph->dev, sizeof(*tde), GFP_KERNEL);
+ if (!tde)
+ return -ENOMEM;
+
+ mutex_init(&tde->mtx);
+ ti->info.des[i] = &tde->de;
+ }
+
+ ret = scmi_telemetry_de_descriptors_get(ph, ti);
+ if (ret) {
+ dev_err(ph->dev, "Cannot get DE descriptors");
+ return ret;
+ }
+
+ return 0;
+}
+
+struct scmi_tlm_ivl_priv {
+ struct device *dev;
+ struct scmi_telemetry_update_interval *intrvs;
+ unsigned int grp_id;
+ unsigned int flags;
+};
+
+static void iter_intervals_prepare_message(void *message,
+ unsigned int desc_index,
+ const void *priv)
+{
+ struct scmi_msg_telemetry_update_intervals *msg = message;
+ const struct scmi_tlm_ivl_priv *p = priv;
+
+ msg->index = cpu_to_le32(desc_index);
+ msg->group_identifier = cpu_to_le32(p->grp_id);
+ msg->flags = FIELD_PREP(GENMASK(3, 0), p->flags);
+}
+
+static int iter_intervals_update_state(struct scmi_iterator_state *st,
+ const void *response, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_update_intervals *r = response;
+
+ st->num_returned = le32_get_bits(r->flags, GENMASK(11, 0));
+ st->num_remaining = le32_get_bits(r->flags, GENMASK(31, 16));
+
+ /*
+ * total intervals is not declared previously anywhere so we
+ * assume it's returned+remaining on first call.
+ */
+ if (!st->max_resources) {
+ struct scmi_tlm_ivl_priv *p = priv;
+
+ p->intrvs->discrete = INTERVALS_DISCRETE(r->flags);
+ /* Check consistency on first call */
+ if (!p->intrvs->discrete &&
+ (st->num_returned != 3 || st->num_remaining != 0))
+ return -EINVAL;
+
+ p->intrvs->num = st->num_returned + st->num_remaining;
+ p->intrvs->update_intervals =
+ devm_kcalloc(p->dev, p->intrvs->num,
+ sizeof(*p->intrvs->update_intervals),
+ GFP_KERNEL);
+ if (!p->intrvs->update_intervals) {
+ p->intrvs->num = 0;
+ return -ENOMEM;
+ }
+
+ st->max_resources = p->intrvs->num;
+ }
+
+ 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;
+ unsigned int idx = st->loop_idx;
+
+ p->intrvs->update_intervals[st->desc_index + idx] = r->intervals[idx];
+
+ return 0;
+}
+
+static int
+scmi_tlm_enumerate_update_intervals(const struct scmi_protocol_handle *ph,
+ struct telemetry_info *ti, 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,
+ };
+ struct scmi_tlm_ivl_priv ipriv = {
+ .dev = ph->dev,
+ .grp_id = grp_id,
+ .intrvs = (grp_id == SCMI_TLM_GRP_INVALID) ?
+ &ti->info.intervals :
+ &ti->info.des_groups[grp_id].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_update_intervals(const struct scmi_protocol_handle *ph,
+ struct telemetry_info *ti)
+{
+ int ret;
+ unsigned int flags;
+
+ flags = !ti->info.per_group_config_support ?
+ ALL_DES_ANY_GROUP : ALL_DES_NO_GROUP;
+
+ ret = scmi_tlm_enumerate_update_intervals(ph, ti, SCMI_TLM_GRP_INVALID,
+ flags);
+ if (ret)
+ return ret;
+
+ if (ti->info.num_groups && ti->info.per_group_config_support) {
+ flags = SPECIFIC_GROUP_DES;
+ for (int grp_id = 0; grp_id < ti->info.num_groups; grp_id++) {
+ ret = scmi_tlm_enumerate_update_intervals(ph, ti, grp_id,
+ flags);
+ if (ret)
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int iter_shmti_update_state(struct scmi_iterator_state *st,
+ const void *response, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_shmti_list *r = response;
+
+ st->num_returned = le32_get_bits(r->num_shmti, GENMASK(15, 0));
+ st->num_remaining = le32_get_bits(r->num_shmti, GENMASK(31, 16));
+
+ return 0;
+}
+
+static int iter_shmti_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st,
+ void *priv)
+{
+ const struct scmi_msg_resp_telemetry_shmti_list *r = response;
+ struct telemetry_info *ti = priv;
+ struct telemetry_shmti *shmti;
+ const struct scmi_shmti_desc *desc;
+ void __iomem *addr;
+ u64 phys_addr;
+ u32 len;
+
+ desc = &r->desc[st->loop_idx];
+ shmti = &ti->shmti[st->desc_index + st->loop_idx];
+
+ shmti->id = le32_to_cpu(desc->id);
+ phys_addr = le32_to_cpu(desc->addr_low);
+ phys_addr |= (u64)le32_to_cpu(desc->addr_high) << 32;
+
+ len = le32_to_cpu(desc->length);
+ addr = devm_ioremap(ph->dev, phys_addr, len);
+ if (!addr)
+ return -EADDRNOTAVAIL;
+
+ shmti->base = addr;
+ shmti->len = len;
+
+ return 0;
+}
+
+static int scmi_telemetry_shmti_list(const struct scmi_protocol_handle *ph,
+ struct telemetry_info *ti)
+{
+ struct scmi_iterator_ops ops = {
+ .prepare_message = iter_tlm_prepare_message,
+ .update_state = iter_shmti_update_state,
+ .process_response = iter_shmti_process_response,
+ };
+ void *iter;
+
+ iter = ph->hops->iter_response_init(ph, &ops, ti->info.num_de,
+ 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(const struct scmi_protocol_handle *ph,
+ struct telemetry_info *ti)
+{
+ 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 int scmi_telemetry_tdcf_parse_one(struct telemetry_info *ti,
+ struct payload __iomem *payld,
+ struct telemetry_shmti *shmti)
+{
+ struct scmi_telemetry_de *de;
+ struct telemetry_de *tde;
+ u64 val, tstamp;
+ int used_qwords;
+
+ de = xa_load(&ti->xa_des, le32_to_cpu(payld->id));
+ if (!de || DATA_INVALID(payld))
+ return -EINVAL;
+
+ used_qwords = 4;
+
+ tde = to_tde(de);
+ if (shmti) {
+ tde->base = shmti->base;
+ tde->eplg = shmti->base + shmti->len - TDCF_EPLG_SZ;
+ tde->offset = (void *)payld - (void *)shmti->base;
+ }
+
+ //TODO BLK_TS
+ if (USE_LINE_TS(payld)) {
+ used_qwords += 2;
+ if (TS_VALID(payld))
+ tstamp = LINE_TSTAMP_GET(&payld->tsl);
+ }
+ val = LINE_DATA_GET(&payld->tsl);
+
+ guard(mutex)(&tde->mtx);
+ tde->last_val = val;
+ if (de->tstamp_enabled)
+ tde->last_ts = tstamp;
+ else
+ tde->last_ts = 0;
+
+ return used_qwords;
+}
+
+static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
+ unsigned int shmti_id, u64 ts,
+ bool update)
+{
+ struct telemetry_shmti *shmti = &ti->shmti[shmti_id];
+ struct tdcf __iomem *tdcf = shmti->base;
+ int retries = SCMI_TLM_TDCF_MAX_RETRIES;
+ u64 startm = 0, endm = 0xffffffffffffffff;
+ void *eplg = SHMTI_EPLG(shmti);
+
+ if (!tdcf)
+ return -ENODEV;
+
+ do {
+ unsigned int qwords;
+ void __iomem *next;
+
+ /* A bit of exponential backoff between retries */
+ fsleep((SCMI_TLM_TDCF_MAX_RETRIES - retries) * 1000);
+
+ startm = TDCF_START_SEQ_GET(tdcf);
+ if (IS_BAD_START_SEQ(startm))
+ continue;
+
+ qwords = tdcf->prlg.num_qwords;
+ next = tdcf->payld;
+ while (qwords) {
+ int used_qwords;
+
+ used_qwords = scmi_telemetry_tdcf_parse_one(ti, next,
+ update ? shmti : NULL);
+ if (qwords < used_qwords)
+ return -EINVAL;
+
+ next += used_qwords * 4;
+ qwords -= used_qwords;
+ }
+
+ endm = TDCF_END_SEQ_GET(eplg);
+ } while (startm != endm && --retries);
+
+ if (startm != endm)
+ return -EPROTO;
+
+ return 0;
+}
+
+static int scmi_telemetry_group_state_update(struct telemetry_info *ti,
+ struct scmi_telemetry_group *grp,
+ bool *enable, bool *tstamp)
+{
+ struct scmi_telemetry_de *de;
+
+ for (int i = 0; i < grp->num_de; i++) {
+ de = ti->info.des[grp->des[i]];
+
+ if (enable)
+ de->enabled = *enable;
+ if (tstamp)
+ de->tstamp_enabled = *tstamp;
+ }
+
+ 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 *priv)
+{
+ 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 = priv;
+ 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;
+ }
+
+ 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(grp->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) {
+ u32 id = le32_to_cpu(resp->shmti_id);
+
+ /* Update DE SHMTI and offset, if applicable */
+ if (enable && IS_SHMTI_ID_VALID(id)) {
+ if (id >= ti->num_shmti) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ /*
+ * Update SHMTI/offset while skipping non-SHMTI-DEs like
+ * FCs and notif-only.
+ */
+ if (!is_group) {
+ struct scmi_telemetry_de *de = priv;
+ struct telemetry_de *tde;
+ u32 offs;
+
+ offs = le32_to_cpu(resp->tdcf_de_offset);
+ if (offs >= ti->shmti[id].len - de->data_sz) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ tde = to_tde(de);
+ tde->base = ti->shmti[id].base;
+ tde->offset = offs;
+ /* A handy reference to the Epilogue updated */
+ tde->eplg = tde->base + ti->shmti[id].len -
+ TDCF_EPLG_SZ;
+ } else {
+ /*
+ * A full SHMTI scan is needed when enabling a
+ * group in order to retrieve offsets.
+ */
+ scmi_telemetry_shmti_scan(ti, id, 0, true);
+ }
+ }
+
+ /* 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);
+ }
+
+out:
+ 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)
+{
+ void *obj;
+ bool *enabled_state, *tstamp_enabled_state;
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ 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.num_groups)
+ return -EINVAL;
+
+ grp = &ti->info.des_groups[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 scmi_msg_telemetry_de_configure *msg;
+ struct scmi_xfer *t;
+ int ret;
+
+ 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) {
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ for (int i = 0; i < ti->info.num_de; i++)
+ ti->info.des[i]->enabled = false;
+
+ if (is_group) {
+ for (int i = 0; i < ti->info.num_groups; i++)
+ ti->info.des_groups[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)
+{
+ struct scmi_telemetry_update_interval *intervals;
+ struct telemetry_info *ti = ph->get_priv(ph);
+ enum scmi_telemetry_collection *current_mode, next_mode;
+ struct scmi_msg_telemetry_config_set *msg;
+ 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.num_groups)
+ return -EINVAL;
+
+ if (res_id == SCMI_TLM_GRP_INVALID || grp_ignore) {
+ intervals = &ti->info.intervals;
+ current_mode = &ti->info.current_mode;
+ } else {
+ intervals = &ti->info.des_groups[res_id].intervals;
+ current_mode = &ti->info.des_groups[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(intervals->active_update_interval);
+ } else {
+ //TODO Fix .. not only ms...review API
+ interval = FIELD_PREP(GENMASK(20, 5),
+ cpu_to_le32(*update_interval_ms));
+ interval |= FIELD_PREP(GENMASK(4, 0), (-3 & 0x1f));
+ }
+
+ 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_SELECTOR_ALL :
+ TELEMETRY_SELECTOR_GROUP;
+ msg->control |= TELEMETRY_MODE(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)
+ intervals->active_update_interval = interval;
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static int scmi_telemetry_de_data_fc_read(struct telemetry_de *tde,
+ u64 *tstamp, u64 *val)
+{
+ struct fc_tsline __iomem *fc = tde->base + tde->offset;
+
+ *val = LINE_DATA_GET(fc);
+ if (tstamp) {
+ if (tde->de.tstamp_support)
+ *tstamp = LINE_TSTAMP_GET(fc);
+ else
+ *tstamp = 0;
+ }
+
+ return 0;
+}
+
+/*
+ * TDCF and TS Line Management Notes
+ * ---------------------------------
+ * (from a chat with ATG)
+ *
+ * TCDF Payload Metadata notable bits:
+ * - Bit[2]: USE Tstamp
+ * - Bit[1]: Tstamp VALID
+ *
+ * CASE_1:
+ * -------
+ * + A DE is enabled with timestamp disabled, so the TS_Line is NOT present
+ * -> BIT[2:1] = 00b
+ * + that DE is then 're-enabled' with TS_line: 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[2:1]: 11b
+ * + the hole left from the relocated DE can be reused by the platform to
+ * fit another equally sized DE. (i.e. without shuffling around any
+ * other enabled DE, since that would cause a change of the known
+ * offset)
+ *
+ * CASE_2:
+ * -------
+ * + A DE is enabled with timestamp enabled, so the TS_Line is there
+ * -> BIT[2:1]:11b
+ * + that DE has its timestamp disabled: again, you can do this without
+ * disabling it fully but just disabling the TS, so now that TS_line
+ * fields are still physiclly there but NOT valid
+ * -> BIT[2:1]= 10b
+ * + the hole from the timestamp remain there unused until
+ * - you enable again the TS so the hole is used again
+ * -> BIT[2:1]=11b
+ * OR
+ * - you disable fully the DE and then re-enable it with the TS
+ * -> potentially CASE_1 the DE is relocated on enable
+ */
+static int scmi_telemetry_de_tdcf_parse(struct telemetry_de *tde,
+ u64 *tstamp, u64 *val)
+{
+ struct tdcf __iomem *tdcf = tde->base;
+ u64 startm, endm;
+ int retries = SCMI_TLM_TDCF_MAX_RETRIES;
+
+ if (!tdcf)
+ return -ENODEV;
+
+ do {
+ struct payload __iomem *payld;
+
+ /* A bit of exponential backoff between retries */
+ fsleep((SCMI_TLM_TDCF_MAX_RETRIES - retries) * 1000);
+
+ startm = TDCF_START_SEQ_GET(tdcf);
+ if (IS_BAD_START_SEQ(startm))
+ continue;
+
+ payld = tde->base + tde->offset;
+ if (le32_to_cpu(payld->id) != tde->de.id || DATA_INVALID(payld))
+ return -EINVAL;
+
+ //TODO BLK_TS
+ if (tstamp && USE_LINE_TS(payld) && TS_VALID(payld))
+ *tstamp = LINE_TSTAMP_GET(&payld->tsl);
+
+ *val = LINE_DATA_GET(&payld->tsl);
+
+ endm = TDCF_END_SEQ_GET(tde->eplg);
+ } while (startm != endm && --retries);
+
+ if (startm != endm)
+ return -EPROTO;
+
+ return 0;
+}
+
+static int scmi_telemetry_de_lookup(struct telemetry_de *tde,
+ u64 *tstamp, u64 *val)
+{
+ if (!tde->de.fc_support)
+ return scmi_telemetry_de_tdcf_parse(tde, tstamp, val);
+
+ return scmi_telemetry_de_data_fc_read(tde, tstamp, val);
+}
+
+static int scmi_telemetry_de_collect(struct telemetry_info *ti,
+ struct scmi_telemetry_de *de,
+ u64 *tstamp, u64 *val)
+{
+ struct telemetry_de *tde = to_tde(de);
+
+ if (!de->enabled)
+ return -EINVAL;
+
+ /*
+ * DE readings returns cached values when:
+ * - DE data value was retrieved via notification
+ */
+ scoped_guard(mutex, &tde->mtx) {
+ if (tde->cached) {
+ *val = tde->last_val;
+ if (tstamp)
+ *tstamp = tde->last_ts;
+ return 0;
+ }
+ }
+
+ return scmi_telemetry_de_lookup(tde, tstamp, val);
+}
+
+static int scmi_telemetry_de_data_read(const struct scmi_protocol_handle *ph,
+ struct scmi_telemetry_de_sample *sample)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_telemetry_de *de;
+
+ if (!ti->info.enabled || !sample)
+ return -EINVAL;
+
+ de = xa_load(&ti->xa_des, sample->id);
+ if (!de)
+ return -ENODEV;
+
+ return scmi_telemetry_de_collect(ti, de, &sample->tstamp, &sample->val);
+}
+
+static int
+scmi_telemetry_samples_collect(struct telemetry_info *ti, int grp_id,
+ int *num_samples,
+ struct scmi_telemetry_de_sample *samples)
+{
+ int max_samples;
+
+ max_samples = *num_samples;
+ *num_samples = 0;
+
+ for (int i = 0; i < ti->info.num_de; i++) {
+ struct scmi_telemetry_de *de;
+ u64 val, tstamp;
+ int ret;
+
+ de = ti->info.des[i];
+ if (grp_id != SCMI_TLM_GRP_INVALID &&
+ (!de->grp || de->grp->id != grp_id))
+ continue;
+
+ ret = scmi_telemetry_de_collect(ti, de, &tstamp, &val);
+ if (ret)
+ continue;
+
+ if (*num_samples == max_samples)
+ return -ENOSPC;
+
+ samples[*num_samples].tstamp = tstamp;
+ samples[*num_samples].val = val;
+ samples[*num_samples].id = de->id;
+
+ (*num_samples)++;
+ }
+
+ return 0;
+}
+
+static int scmi_telemetry_des_bulk_read(const struct scmi_protocol_handle *ph,
+ int grp_id, int *num_samples,
+ struct scmi_telemetry_de_sample *samples)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ if (!ti->info.enabled || !num_samples || !samples)
+ return -EINVAL;
+
+ 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, unsigned int *dwords,
+ ktime_t timestamp)
+{
+ u32 next = 0;
+
+ while (next < num_dwords) {
+ struct payload *payld = (struct payload *)&dwords[next];
+ struct scmi_telemetry_de *de;
+ struct telemetry_de *tde;
+ u32 de_id;
+
+ next += USE_LINE_TS(payld) ?
+ TS_LINE_DATA_PAYLD_WORDS : LINE_DATA_PAYLD_WORDS;
+
+ if (DATA_INVALID(payld)) {
+ dev_err(ti->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->dev,
+ "MSG - Received INVALID DE - ID:%u enabled:%d\n",
+ de_id, de ? (de->enabled ? 'Y' : 'N') : 'X');
+ continue;
+ }
+
+ tde = to_tde(de);
+ guard(mutex)(&tde->mtx);
+ tde->cached = true;
+ tde->last_val = LINE_DATA_GET(&payld->tsl);
+ //TODO BLK_TS ?
+ if (USE_LINE_TS(payld) && TS_VALID(payld))
+ tde->last_ts = LINE_TSTAMP_GET(&payld->tsl);
+ else
+ tde->last_ts = 0;
+ }
+}
+
+static int scmi_telemetry_des_sample_get(const struct scmi_protocol_handle *ph,
+ int grp_id, int *num_samples,
+ struct scmi_telemetry_de_sample *samples)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_msg_telemetry_config_set *msg;
+ struct scmi_xfer *t;
+ bool grp_ignore;
+ int ret;
+
+ if (!ti->info.enabled || !num_samples || !samples)
+ return -EINVAL;
+
+ grp_ignore = grp_id == SCMI_TLM_GRP_INVALID ? true : false;
+ if (!grp_ignore && grp_id >= ti->info.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_SELECTOR_ALL :
+ TELEMETRY_SELECTOR_GROUP;
+ msg->control |= TELEMETRY_MODE_SINGLE;
+ msg->sampling_rate = 0;
+
+ ret = ph->xops->do_xfer_with_response(ph, t);
+ if (!ret) {
+ struct scmi_msg_resp_telemetry_reading_complete *r = t->rx.buf;
+
+ /* Update cached DEs values from payload */
+ if (r->num_dwords)
+ scmi_telemetry_msg_payld_process(ti, r->num_dwords,
+ r->dwords, 0);
+ ret = scmi_telemetry_samples_collect(ti, grp_id, num_samples,
+ samples);
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static int scmi_telemetry_config_get(const struct scmi_protocol_handle *ph,
+ bool *enabled, int *mode,
+ u32 *update_interval)
+{
+ ///TODO
+ return 0;
+}
+
+static int scmi_telemetry_reset(const struct scmi_protocol_handle *ph)
+{
+ int ret;
+ struct scmi_xfer *t;
+
+ 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);
+
+ //XXX Better would be to read back from platform
+ // CONFIG_GET + DE_ENABLED_LIST
+ ti->info.enabled = false;
+ ti->info.notif_enabled = false;
+ ti->info.current_mode = SCMI_TLM_ONDEMAND;
+ ti->info.intervals.active_update_interval = 0;
+
+ for (int i = 0; i < ti->info.num_de; i++) {
+ ti->info.des[i]->enabled = false;
+ ti->info.des[i]->tstamp_enabled = false;
+ }
+
+ for (int i = 0; i < ti->info.num_groups; i++) {
+ ti->info.des_groups[i].enabled = false;
+ ti->info.des_groups[i].tstamp_enabled = false;
+ ti->info.des_groups[i].current_mode = SCMI_TLM_ONDEMAND;
+ ti->info.des_groups[i].intervals.active_update_interval = 0;
+ }
+ }
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
+ .info_get = scmi_telemetry_info_get,
+ .state_get = scmi_telemetry_state_get,
+ .state_set = scmi_telemetry_state_set,
+ .all_disable = scmi_telemetry_all_disable,
+ .collection_configure = scmi_telemetry_collection_configure,
+ .de_data_read = scmi_telemetry_de_data_read,
+ .des_bulk_read = scmi_telemetry_des_bulk_read,
+ .des_sample_get = scmi_telemetry_des_sample_get,
+ .config_get = scmi_telemetry_config_get,
+ .reset = scmi_telemetry_reset,
+};
+
+static bool
+scmi_telemetry_notify_supported(const struct scmi_protocol_handle *ph,
+ u8 evt_id, u32 src_id)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ return ti->info.continuos_update_support;
+}
+
+static int
+scmi_telemetry_set_notify_enabled(const struct scmi_protocol_handle *ph,
+ u8 evt_id, u32 src_id, bool enable)
+{
+ return 0;
+}
+
+static void *
+scmi_telemetry_fill_custom_report(const struct scmi_protocol_handle *ph,
+ u8 evt_id, ktime_t timestamp,
+ const void *payld, size_t payld_sz,
+ void *report, u32 *src_id)
+{
+ const struct scmi_telemetry_update_notify_payld *p = payld;
+ struct scmi_telemetry_update_report *r = report;
+
+ /* At least sized as an empty notification */
+ if (payld_sz < sizeof(*p))
+ return NULL;
+
+ r->timestamp = timestamp;
+ r->agent_id = le32_to_cpu(p->agent_id);
+ r->status = le32_to_cpu(p->status);
+ r->num_dwords = le32_to_cpu(p->num_dwords);
+ /*
+ * Allocated dwords and report are sized as max_msg_size, so as
+ * to allow for the maximum payload permitted by the configured
+ * transport. Overflow is not possible since out-of-size messages
+ * are dropped at the transport layer.
+ */
+ if (r->num_dwords)
+ memcpy(r->dwords, p->array, r->num_dwords * sizeof(u32));
+
+ *src_id = 0;
+
+ return r;
+}
+
+static const struct scmi_event tlm_events[] = {
+ {
+ .id = SCMI_EVENT_TELEMETRY_UPDATE,
+ .max_payld_sz = 0,
+ .max_report_sz = 0,
+ },
+};
+
+static const struct scmi_event_ops tlm_event_ops = {
+ .is_notify_supported = scmi_telemetry_notify_supported,
+ .set_notify_enabled = scmi_telemetry_set_notify_enabled,
+ .fill_custom_report = scmi_telemetry_fill_custom_report,
+};
+
+static const struct scmi_protocol_events tlm_protocol_events = {
+ .queue_sz = SCMI_PROTO_QUEUE_SZ,
+ .ops = &tlm_event_ops,
+ .evts = tlm_events,
+ .num_events = ARRAY_SIZE(tlm_events),
+ .num_sources = 1,
+};
+
+static void scmi_telemetry_scan_update(struct telemetry_info *ti, u64 ts)
+{
+ /* Scan all SHMTIs ... */
+ for (int id = 0; id < ti->num_shmti; id++)
+ scmi_telemetry_shmti_scan(ti, id, ts, false);
+
+ /* ... then scan all FCs ... XXX Use a list */
+ for (int i = 0; i < ti->info.num_de; i++) {
+ struct scmi_telemetry_de *de;
+ struct telemetry_de *tde;
+ u64 val, tstamp;
+ int ret;
+
+ de = ti->info.des[i];
+ if (!de->enabled)
+ continue;
+
+ tde = to_tde(de);
+ if (!tde->de.fc_support)
+ continue;
+
+ //TODO Repoet errors
+ ret = scmi_telemetry_de_data_fc_read(tde, &tstamp, &val);
+ if (ret)
+ return;
+
+ guard(mutex)(&tde->mtx);
+ tde->last_val = val;
+ if (de->tstamp_enabled)
+ tde->last_ts = tstamp;
+ else
+ tde->last_ts = 0;
+ }
+}
+
+static int scmi_telemetry_notifier(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct scmi_telemetry_update_report *er = data;
+ struct telemetry_info *ti = telemetry_nb_to_info(nb);
+
+ if (er->status) {
+ dev_err(ti->dev, "Bad Telemetry update notification - ret: %dn",
+ er->status);
+ return NOTIFY_DONE;
+ }
+
+ /* Lookup the embedded DEs in the notification payload ... */
+ if (er->num_dwords)
+ scmi_telemetry_msg_payld_process(ti, er->num_dwords,
+ er->dwords, er->timestamp);
+
+ /* ...scan the SHMTI/FCs for any other DE updates. */
+ if (ti->streaming_mode)
+ scmi_telemetry_scan_update(ti, er->timestamp);
+
+ return NOTIFY_OK;
+}
+
+static int scmi_telemetry_protocol_init(const struct scmi_protocol_handle *ph)
+{
+ struct telemetry_info *ti;
+ u32 version;
+ int ret;
+
+ ret = ph->xops->version_get(ph, &version);
+ if (ret)
+ return ret;
+
+ dev_dbg(ph->dev, "Telemetry Version %d.%d\n",
+ PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version));
+
+ ti = devm_kzalloc(ph->dev, sizeof(*ti), GFP_KERNEL);
+ if (!ti)
+ return -ENOMEM;
+
+ ti->dev = ph->dev;
+
+ ret = scmi_telemetry_protocol_attributes_get(ph, ti);
+ if (ret)
+ return ret;
+
+ ret = scmi_telemetry_enumerate_de(ph, ti);
+ if (ret)
+ return ret;
+
+ ret = scmi_telemetry_enumerate_update_intervals(ph, ti);
+ if (ret)
+ return ret;
+
+ ret = scmi_telemetry_enumerate_shmti(ph, ti);
+ if (ret)
+ return ret;
+
+ ti->info.version = version;
+
+ ret = ph->set_priv(ph, ti, version);
+ if (ret)
+ return ret;
+
+ /*
+ * Register a notifier anyway straight upon protocol initialization
+ * since there could be some DEs that are ONLY reported by notifications
+ * even though the chosen collection method was SHMTI/FCs.
+ */
+ if (ti->info.continuos_update_support) {
+ ti->telemetry_nb.notifier_call = &scmi_telemetry_notifier;
+ ret = ph->notifier_register(ph, SCMI_EVENT_TELEMETRY_UPDATE,
+ NULL, &ti->telemetry_nb);
+ if (ret)
+ dev_warn(ph->dev,
+ "Could NOT register Telemetry notifications\n");
+ }
+
+ return ret;
+}
+
+static const struct scmi_protocol scmi_telemetry = {
+ .id = SCMI_PROTOCOL_TELEMETRY,
+ .owner = THIS_MODULE,
+ .instance_init = &scmi_telemetry_protocol_init,
+ .ops = &tlm_proto_ops,
+ .events = &tlm_protocol_events,
+ .supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
+};
+
+DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(telemetry, scmi_telemetry)
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index 6f8d36e1f8fc..f02f8f353d30 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -2,7 +2,7 @@
/*
* SCMI Message Protocol driver header
*
- * Copyright (C) 2018-2021 ARM Ltd.
+ * Copyright (C) 2018-2025 ARM Ltd.
*/
#ifndef _LINUX_SCMI_PROTOCOL_H
@@ -820,6 +820,192 @@ 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,
+};
+
+struct scmi_telemetry_update_interval {
+ bool discrete;
+#define SCMI_TLM_UPDATE_INTVL_SEGMENT_LOW 0
+#define SCMI_TLM_UPDATE_INTVL_SEGMENT_HIGH 1
+#define SCMI_TLM_UPDATE_INTVL_SEGMENT_STEP 2
+ int num;
+ unsigned int *update_intervals;
+ unsigned int active_update_interval;
+#define SCMI_GET_UPDATE_INTERVAL_SECS(x) \
+ (le32_get_bits((x), GENMASK(20, 5)))
+#define SCM_IGET_UPDATE_INTERVAL_EXP(x) \
+ ({ \
+ int __signed_exp = FIELD_GET(GENMASK(4, 0), (x)); \
+ \
+ if (__signed_exp & BIT(4)) \
+ __signed_exp |= GENMASK(31, 5); \
+ __signed_exp; \
+ })
+};
+
+enum scmi_telemetry_collection {
+ SCMI_TLM_ONDEMAND,
+ SCMI_TLM_NOTIFICATION,
+ SCMI_TLM_SINGLE_READ,
+};
+
+struct scmi_telemetry_group {
+#define SCMI_TLM_GRP_INVALID 0xFFFFFFFF
+ int id;
+ bool enabled;
+ bool tstamp_enabled;
+ unsigned int num_de;
+ unsigned int *des;
+ size_t des_str_sz;
+ char *des_str;
+ struct scmi_telemetry_update_interval intervals;
+ enum scmi_telemetry_collection current_mode;
+};
+
+//XXX Check what to hide
+struct scmi_telemetry_de {
+ int id;
+ unsigned int data_sz;
+ enum scmi_telemetry_de_type type;
+ int unit;
+ int unit_exp;
+ int tstamp_exp;
+ int instance_id;
+ int compo_instance_id;
+ enum scmi_telemetry_compo_type compo_type;
+ bool persistent;
+ bool tstamp_support;
+ bool fc_support;
+ bool name_support;
+ char *name;
+ struct scmi_telemetry_group *grp;
+ bool enabled;
+ bool tstamp_enabled;
+};
+
+//XXX Check what to hide
+struct scmi_telemetry_info {
+ unsigned int version;
+#define SCMI_TLM_MAX_DWORD 4
+ unsigned int de_impl_version[SCMI_TLM_MAX_DWORD];
+ bool single_read_support;
+ bool continuos_update_support;
+ bool per_group_config_support;
+ bool reset_support;
+ bool fc_support;
+ int num_de;
+ struct scmi_telemetry_de **des;
+ struct scmi_telemetry_update_interval intervals;
+ int num_groups;
+ struct scmi_telemetry_group *des_groups;
+ bool enabled;
+ bool notif_enabled;
+ enum scmi_telemetry_collection current_mode;
+};
+
+struct scmi_telemetry_de_sample {
+ u32 id;
+ u64 tstamp;
+ u64 val;
+};
+
+/**
+ * struct scmi_telemetry_proto_ops - represents the various operations provided
+ * by SCMI Telemetry Protocol
+ *
+ * @info_get: get the general Telemetry information.
+ * @state_get: retrieve the specific DE or GROUP state.
+ * @state_set: enable/disable the specific DE or GROUP with or without timestamps.
+ * @all_disable: disable ALL DEs or GROUPs.
+ * @collection_configure: choose a sampling rate and enable SHMTI/FC sampling
+ * for on demand collection via @de_data_read or async
+ * notificatioins for all the enabled DEs.
+ * @de_data_read: on-demand read of a single DE and related optional timestamp:
+ * the value will be retrieved at the proper SHMTI offset OR
+ * from the dedicated FC area (if supported by that DE).
+ * @des_bulk_read: on-demand read of all the currently enabled DEs, or just
+ * the ones belonging to a specific group when provided.
+ * @des_sample_get: on-demand read of all the currently enabled DEs, or just
+ * the ones belonging to a specific group when provided.
+ * This causes an immediate update platform-side of all the
+ * enabled DEs.
+ * @config_get: retrieve current telemetry configuration.
+ * @reset: reset configuration and telemetry data.
+ */
+struct scmi_telemetry_proto_ops {
+ const struct scmi_telemetry_info __must_check *(*info_get)
+ (const struct scmi_protocol_handle *ph);
+ int (*state_get)(const struct scmi_protocol_handle *ph,
+ u32 id, bool *enabled, bool *tstamp_enabled);
+ int (*state_set)(const struct scmi_protocol_handle *ph,
+ bool is_group, u32 id, bool *enable, bool *tstamp);
+ int (*all_disable)(const struct scmi_protocol_handle *ph, bool group);
+ int (*collection_configure)(const struct scmi_protocol_handle *ph,
+ unsigned int res_id, bool grp_ignore,
+ bool *enable,
+ unsigned int *update_interval_ms,
+ enum scmi_telemetry_collection *mode);
+ int (*de_data_read)(const struct scmi_protocol_handle *ph,
+ struct scmi_telemetry_de_sample *sample);
+ int __must_check (*des_bulk_read)(const struct scmi_protocol_handle *ph,
+ int grp_id, int *num_samples,
+ struct scmi_telemetry_de_sample *samples);
+ int __must_check (*des_sample_get)(const struct scmi_protocol_handle *ph,
+ int grp_id, int *num_samples,
+ struct scmi_telemetry_de_sample *samples);
+ int (*config_get)(const struct scmi_protocol_handle *ph, bool *enabled,
+ int *mode, u32 *update_interval);
+ int (*reset)(const struct scmi_protocol_handle *ph);
+};
+
/**
* struct scmi_notify_ops - represents notifications' operations provided by
* SCMI core
@@ -926,6 +1112,7 @@ enum scmi_std_protocol {
SCMI_PROTOCOL_VOLTAGE = 0x17,
SCMI_PROTOCOL_POWERCAP = 0x18,
SCMI_PROTOCOL_PINCTRL = 0x19,
+ SCMI_PROTOCOL_TELEMETRY = 0x1b,
SCMI_PROTOCOL_LAST = 0xff,
};
@@ -1027,6 +1214,7 @@ enum scmi_notification_events {
SCMI_EVENT_SYSTEM_POWER_STATE_NOTIFIER = 0x0,
SCMI_EVENT_POWERCAP_CAP_CHANGED = 0x0,
SCMI_EVENT_POWERCAP_MEASUREMENTS_CHANGED = 0x1,
+ SCMI_EVENT_TELEMETRY_UPDATE = 0x0,
};
struct scmi_power_state_changed_report {
@@ -1114,4 +1302,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.47.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [RFC PATCH 4/7] firmware: arm_scmi: Add System Telemetry driver
2025-06-20 19:28 [RFC PATCH 0/7] Introduce SCMI Telemetry support Cristian Marussi
` (2 preceding siblings ...)
2025-06-20 19:28 ` [RFC PATCH 3/7] firmware: arm_scmi: Add Telemetry protocol support Cristian Marussi
@ 2025-06-20 19:28 ` Cristian Marussi
2025-06-20 21:27 ` Dan Carpenter
2025-06-20 19:28 ` [RFC PATCH 5/7] firmware: arm_scmi: Add System Telemetry chardev/ioctls API Cristian Marussi
` (3 subsequent siblings)
7 siblings, 1 reply; 21+ messages in thread
From: Cristian Marussi @ 2025-06-20 19:28 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi
Add an SCMI System Telemetry driver which exposes a sysfs custom API to
configure and consume Telemetry data from userspace.
Still lacking:
- complete groups handling
- ungrouped DEs handling
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
drivers/firmware/arm_scmi/Kconfig | 10 +
drivers/firmware/arm_scmi/Makefile | 1 +
.../firmware/arm_scmi/scmi_system_telemetry.c | 967 ++++++++++++++++++
3 files changed, 978 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..a2f59001747d
--- /dev/null
+++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
@@ -0,0 +1,967 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SCMI - System Telemetry Driver
+ *
+ * Copyright (C) 2025 ARM Ltd.
+ */
+
+#include <linux/atomic.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/kstrtox.h>
+#include <linux/module.h>
+#include <linux/scmi_protocol.h>
+#include <linux/sysfs.h>
+#include <linux/slab.h>
+
+#define MAX_BULK_LINE_CHAR_LENGTH 64
+
+struct scmi_tlm_setup;
+
+static atomic_t scmi_tlm_instance_count = ATOMIC_INIT(0);
+
+struct scmi_tlm_grp_dev {
+ struct device dev;
+ struct scmi_tlm_setup *tsp;
+ const struct scmi_telemetry_group *grp;
+};
+
+#define to_tlm_grp_dev(d) \
+ (container_of((d), struct scmi_tlm_grp_dev, dev))
+
+struct scmi_tlm_de_dev {
+ struct device dev;
+ struct scmi_tlm_setup *tsp;
+ const struct scmi_telemetry_de *de;
+};
+
+#define to_tlm_de_dev(d) \
+ (container_of((d), struct scmi_tlm_de_dev, dev))
+
+struct scmi_tlm_instance {
+ struct device dev;
+ struct device des_dev;
+ struct device groups_dev;
+ struct scmi_tlm_de_dev **des;
+ struct scmi_tlm_setup *tsp;
+ const struct scmi_telemetry_info *info;
+};
+
+#define dev_to_tlm_instance(d) \
+ (container_of((d), struct scmi_tlm_instance, dev))
+
+#define des_dev_to_tlm_instance(e) \
+ (container_of((e), struct scmi_tlm_instance, des_dev))
+
+#define groups_dev_to_tlm_instance(e) \
+ (container_of((e), struct scmi_tlm_instance, groups_dev))
+
+/**
+ * struct scmi_tlm_setup - Telemetry setup descriptor
+ * @sdev: A reference to the related SCMI device
+ * @ops: A reference to the protocol ops
+ * @ph: A reference to the protocol handle to be used with the ops
+ * @priv: A reference to optional driver-specific data
+ */
+struct scmi_tlm_setup {
+ struct scmi_device *sdev;
+ const struct scmi_telemetry_proto_ops *ops;
+ struct scmi_protocol_handle *ph;
+ const void *priv;
+};
+
+static void scmi_telemetry_release(struct device *dev)
+{
+}
+
+static ssize_t __all_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len,
+ bool is_enable_entry)
+{
+ struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ bool enable;
+ int ret;
+
+ if (kstrtobool(buf, &enable))
+ return -EINVAL;
+
+ if (is_enable_entry && !enable) {
+ ret = tsp->ops->all_disable(tsp->ph, false);
+ if (ret)
+ return ret;
+ } else {
+ for (int i = 0; i < ti->info->num_de; i++) {
+ const struct scmi_telemetry_de *de = ti->info->des[i];
+
+ ret = tsp->ops->state_set(tsp->ph, false, de->id,
+ is_enable_entry ? &enable : NULL,
+ !is_enable_entry ? &enable : NULL);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return len;
+}
+
+static ssize_t all_des_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ return __all_enable_store(dev, attr, buf, len, true);
+}
+
+static ssize_t all_des_tstamp_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ return __all_enable_store(dev, attr, buf, len, false);
+}
+
+static inline ssize_t __current_update_show(char *buf,
+ unsigned int active_update_interval)
+{
+ return sysfs_emit(buf, "%u\n",
+ SCMI_GET_UPDATE_INTERVAL_SECS(active_update_interval));
+}
+
+static inline ssize_t __current_update_store(struct scmi_tlm_setup *tsp,
+ const char *buf, size_t len,
+ unsigned int grp_id)
+{
+ bool grp_ignore = grp_id == SCMI_TLM_GRP_INVALID ? true : false;
+ unsigned int update_interval_ms = 0;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &update_interval_ms);
+ if (ret)
+ return ret;
+
+ ret = tsp->ops->collection_configure(tsp->ph, grp_id, grp_ignore, NULL,
+ &update_interval_ms, NULL);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static ssize_t current_update_interval_ms_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+
+ return __current_update_show(buf,
+ ti->info->intervals.active_update_interval);
+}
+
+static ssize_t current_update_interval_ms_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+ struct scmi_tlm_setup *tsp = ti->tsp;
+
+ return __current_update_store(tsp, buf, len, SCMI_TLM_GRP_INVALID);
+}
+
+static ssize_t tlm_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+
+ return sysfs_emit(buf, "%c\n", ti->info->enabled ? 'Y' : 'N');
+}
+
+static ssize_t tlm_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ enum scmi_telemetry_collection mode = SCMI_TLM_ONDEMAND;
+ bool enabled;
+ int ret;
+
+ ret = kstrtobool(buf, &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 len;
+}
+
+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 += snprintf(buf + bytes, size - bytes,
+ "0x%04X %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 inline ssize_t __des_bulk_read_show(struct scmi_tlm_instance *ti,
+ unsigned int grp_id, char *buf,
+ int size)
+{
+ struct scmi_telemetry_de_sample *samples;
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ int ret, num;
+
+ num = ti->info->num_de;
+ samples = kcalloc(num, sizeof(*samples), GFP_KERNEL);
+ if (!samples)
+ return -ENOMEM;
+
+ ret = tsp->ops->des_bulk_read(tsp->ph, grp_id, &num, samples);
+ if (ret) {
+ kfree(samples);
+ return ret;
+ }
+
+ ret = scmi_tlm_buffer_fill(&ti->dev, buf, size, NULL, num, samples);
+ kfree(samples);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%s", buf);
+}
+
+static ssize_t des_bulk_read_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+
+ return __des_bulk_read_show(ti, SCMI_TLM_GRP_INVALID, buf, PAGE_SIZE);
+}
+
+static inline ssize_t __des_single_sample_read_show(struct scmi_tlm_instance *ti,
+ unsigned int grp_id,
+ char *buf, int len)
+{
+ struct scmi_telemetry_de_sample *samples;
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ int ret, num, bytes = 0;
+
+ num = ti->info->num_de;
+ samples = kcalloc(num, sizeof(*samples), GFP_KERNEL);
+ if (!samples)
+ return -ENOMEM;
+
+ ret = tsp->ops->des_sample_get(tsp->ph, grp_id, &num, samples);
+ if (ret) {
+ kfree(samples);
+ return ret;
+ }
+
+ for (int i = 0; i < num; i++) {
+ bytes += snprintf(buf + bytes, len - bytes,
+ "0x%04X %llu %016llX\n", samples[i].id,
+ samples[i].tstamp, samples[i].val);
+
+ if (bytes >= len) {
+ dev_err(&ti->dev, "==>> BULK BUFFER OVERFLOW !\n");
+ kfree(samples);
+ return -ENOSPC;
+ }
+ }
+
+ kfree(samples);
+
+ return sysfs_emit(buf, "%s", buf);
+}
+
+static ssize_t des_single_sample_read_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+
+ return __des_single_sample_read_show(ti, SCMI_TLM_GRP_INVALID,
+ buf, PAGE_SIZE);
+}
+
+static ssize_t de_implementation_version_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+
+ return sysfs_emit(buf, "%pUL\n", ti->info->de_impl_version);
+}
+
+static inline ssize_t __intervals_discrete_show(char *buf, const bool discrete)
+{
+ return sysfs_emit(buf, "%c\n", discrete ? 'Y' : 'N');
+}
+
+static ssize_t intervals_discrete_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+
+ return __intervals_discrete_show(buf, ti->info->intervals.discrete);
+}
+
+//TODO Review available interval show
+#define BUF_SZ 1024
+static inline ssize_t
+__available_update_show(char *buf,
+ const struct scmi_telemetry_update_interval *intervals)
+{
+ int len = 0, num_intervals = intervals->num;
+ char available[BUF_SZ];
+
+ for (int i = 0; i < num_intervals; i++) {
+ len += scnprintf(available + len, BUF_SZ - len, "%u ",
+ intervals->update_intervals[i]);
+ }
+
+ available[len - 1] = '\0';
+
+ return sysfs_emit(buf, "%s\n", available);
+}
+
+static ssize_t available_update_intervals_ms_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+
+ return __available_update_show(buf, &ti->info->intervals);
+}
+
+static ssize_t version_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+
+ return sysfs_emit(buf, "0x%08x\n", ti->info->version);
+}
+
+static DEVICE_ATTR_WO(all_des_enable);
+static DEVICE_ATTR_WO(all_des_tstamp_enable);
+static DEVICE_ATTR_RW(current_update_interval_ms);
+static DEVICE_ATTR_RW(tlm_enable);
+static DEVICE_ATTR_RO(des_bulk_read);
+static DEVICE_ATTR_RO(des_single_sample_read);
+static DEVICE_ATTR_RO(de_implementation_version);
+static DEVICE_ATTR_RO(intervals_discrete);
+static DEVICE_ATTR_RO(available_update_intervals_ms);
+static DEVICE_ATTR_RO(version);
+
+static ssize_t reset_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct scmi_tlm_instance *ti = dev_to_tlm_instance(dev);
+ int ret;
+
+ ret = ti->tsp->ops->reset(ti->tsp->ph);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static struct device_attribute dev_attr_reset = {
+ .attr = { .name = "reset", .mode = 0200 },
+ .store = reset_store,
+};
+
+static struct attribute *scmi_telemetry_attrs[] = {
+ &dev_attr_all_des_enable.attr,
+ &dev_attr_all_des_tstamp_enable.attr,
+ &dev_attr_current_update_interval_ms.attr,
+ &dev_attr_tlm_enable.attr,
+ &dev_attr_des_bulk_read.attr,
+ &dev_attr_des_single_sample_read.attr,
+ &dev_attr_de_implementation_version.attr,
+ &dev_attr_intervals_discrete.attr,
+ &dev_attr_available_update_intervals_ms.attr,
+ &dev_attr_version.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(scmi_telemetry);
+
+static struct class scmi_telemetry_class = {
+ .name = "scmi_telemetry",
+ .dev_release = scmi_telemetry_release,
+};
+
+static ssize_t value_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct scmi_tlm_de_dev *tde = to_tlm_de_dev(dev);
+ struct scmi_tlm_setup *tsp = tde->tsp;
+ struct scmi_telemetry_de_sample sample;
+ int ret;
+
+ sample.id = tde->de->id;
+ ret = tsp->ops->de_data_read(tsp->ph, &sample);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%llu: %016llX\n", sample.tstamp, sample.val);
+}
+static DEVICE_ATTR_RO(value);
+
+#define DEFINE_DE_ATTR_INT_RO(_attr, _fmt) \
+static ssize_t _attr##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct scmi_tlm_de_dev *tde = to_tlm_de_dev(dev); \
+ \
+ return sysfs_emit(buf, _fmt "\n", tde->de->_attr); \
+} \
+static DEVICE_ATTR_RO(_attr)
+
+#define DEFINE_DE_ATTR_BOOL_RO(_attr) \
+static ssize_t _attr##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct scmi_tlm_de_dev *tde = to_tlm_de_dev(dev); \
+ \
+ return sysfs_emit(buf, "%c\n", tde->de->_attr ? 'Y' : 'N'); \
+} \
+static DEVICE_ATTR_RO(_attr)
+
+DEFINE_DE_ATTR_INT_RO(type, "%u");
+DEFINE_DE_ATTR_INT_RO(unit, "%u");
+DEFINE_DE_ATTR_INT_RO(unit_exp, "%d");
+DEFINE_DE_ATTR_INT_RO(instance_id, "%u");
+DEFINE_DE_ATTR_INT_RO(compo_type, "%u");
+DEFINE_DE_ATTR_INT_RO(compo_instance_id, "%u");
+DEFINE_DE_ATTR_BOOL_RO(persistent);
+DEFINE_DE_ATTR_INT_RO(name, "%s");
+DEFINE_DE_ATTR_INT_RO(tstamp_exp, "%d");
+
+#define DEFINE_DE_ATTR_STATE_RW(_name, _is_enable) \
+static ssize_t _name##_store(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t len) \
+{ \
+ struct scmi_tlm_de_dev *tde = to_tlm_de_dev(dev); \
+ struct scmi_tlm_setup *tsp = tde->tsp; \
+ typeof(_is_enable) _is_ena = _is_enable; \
+ bool enabled; \
+ int ret; \
+ \
+ ret = kstrtobool(buf, &enabled); \
+ if (ret) \
+ return ret; \
+ \
+ ret = tsp->ops->state_set(tsp->ph, false, tde->de->id, \
+ _is_ena ? &enabled : NULL, \
+ !_is_ena ? &enabled : NULL); \
+ if (ret) \
+ return ret; \
+ \
+ return len; \
+} \
+ \
+static ssize_t _name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ struct scmi_tlm_de_dev *tde = to_tlm_de_dev(dev); \
+ \
+ return sysfs_emit(buf, "%c\n", tde->de->_name ## d ? 'Y' : 'N');\
+} \
+static DEVICE_ATTR_RW(_name)
+
+DEFINE_DE_ATTR_STATE_RW(enable, true);
+DEFINE_DE_ATTR_STATE_RW(tstamp_enable, false);
+
+static struct attribute *scmi_des_attrs[] = {
+ &dev_attr_value.attr,
+ &dev_attr_type.attr,
+ &dev_attr_unit.attr,
+ &dev_attr_unit_exp.attr,
+ &dev_attr_instance_id.attr,
+ &dev_attr_compo_type.attr,
+ &dev_attr_compo_instance_id.attr,
+ &dev_attr_persistent.attr,
+ &dev_attr_enable.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(scmi_des);
+
+static void scmi_tlm_dev_release(struct device *dev)
+{
+}
+
+static int
+scmi_telemetry_dev_register(struct device *dev, struct device *parent,
+ const char *name)
+{
+ int ret;
+
+ dev->parent = parent;
+ dev->release = scmi_tlm_dev_release;
+ dev_set_name(dev, "%s", name);
+ device_set_pm_not_required(dev);
+ dev_set_uevent_suppress(dev, true);
+ ret = device_register(dev);
+ if (ret)
+ put_device(dev);
+
+ return ret;
+}
+
+static int scmi_des_iter(struct device *dev, void *data)
+{
+ device_unregister(dev);
+
+ return 0;
+}
+
+static void scmi_telemetry_dev_unregister(struct device *parent)
+{
+ device_for_each_child(parent, NULL, scmi_des_iter);
+ device_unregister(parent);
+}
+
+static ssize_t grp_obj_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct scmi_tlm_grp_dev *gde = to_tlm_grp_dev(dev);
+ struct scmi_tlm_setup *tsp = gde->tsp;
+ bool enabled, is_ena_entry;
+ int ret;
+
+ ret = kstrtobool(buf, &enabled);
+ if (ret)
+ return ret;
+
+ is_ena_entry = !strncmp(attr->attr.name, "enable", 6);
+ ret = tsp->ops->state_set(tsp->ph, true, gde->grp->id,
+ is_ena_entry ? &enabled : NULL,
+ !is_ena_entry ? &enabled : NULL);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static ssize_t grp_obj_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct scmi_tlm_grp_dev *gde = to_tlm_grp_dev(dev);
+ bool enabled, is_ena_entry;
+
+ is_ena_entry = !strncmp(attr->attr.name, "enable", 6);
+ enabled = is_ena_entry ? gde->grp->enabled : gde->grp->tstamp_enabled;
+
+ return sysfs_emit(buf, "%c\n", enabled ? 'Y' : 'N');
+}
+
+static struct device_attribute dev_attr_grp_enable = {
+ .attr = { .name = "enable", .mode = 0600 },
+ .show = grp_obj_show,
+ .store = grp_obj_store,
+};
+
+static struct device_attribute dev_attr_grp_tstamp_enable = {
+ .attr = { .name = "tstamp_enable", .mode = 0600 },
+ .show = grp_obj_show,
+ .store = grp_obj_store,
+};
+
+static ssize_t composing_des_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct scmi_tlm_grp_dev *gde = to_tlm_grp_dev(dev);
+
+ return sysfs_emit(buf, "%s\n", gde->grp->des_str);
+}
+static DEVICE_ATTR_RO(composing_des);
+
+static ssize_t grp_current_update_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct scmi_tlm_grp_dev *gde = to_tlm_grp_dev(dev);
+
+ return __current_update_show(buf,
+ gde->grp->intervals.active_update_interval);
+}
+
+static ssize_t grp_current_update_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct scmi_tlm_grp_dev *gde = to_tlm_grp_dev(dev);
+ struct scmi_tlm_setup *tsp = gde->tsp;
+
+ return __current_update_store(tsp, buf, len, gde->grp->id);
+}
+
+static struct device_attribute dev_attr_grp_current_update = {
+ .attr = { .name = "current_update_interval_ms", .mode = 0600 },
+ .show = grp_current_update_show,
+ .store = grp_current_update_store,
+};
+
+static ssize_t grp_intervals_discrete_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct scmi_tlm_grp_dev *gde = to_tlm_grp_dev(dev);
+
+ return __intervals_discrete_show(buf, gde->grp->intervals.discrete);
+}
+
+static struct device_attribute dev_attr_grp_intervals_discrete = {
+ .attr = { .name = "intervals_discrete", .mode = 0400 },
+ .show = grp_intervals_discrete_show,
+};
+
+static ssize_t grp_available_intervals_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct scmi_tlm_grp_dev *gde = to_tlm_grp_dev(dev);
+
+ return __available_update_show(buf, &gde->grp->intervals);
+}
+
+static struct device_attribute dev_attr_grp_available_intervals = {
+ .attr = { .name = "available_update_intervals_ms", .mode = 0400 },
+ .show = grp_available_intervals_show,
+};
+
+static ssize_t grp_des_bulk_read_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct scmi_tlm_grp_dev *gde = to_tlm_grp_dev(dev);
+ struct scmi_tlm_instance *ti =
+ groups_dev_to_tlm_instance(gde->dev.parent);
+
+ return __des_bulk_read_show(ti, gde->grp->id, buf, PAGE_SIZE);
+}
+
+static ssize_t grp_des_single_sample_read_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct scmi_tlm_grp_dev *gde = to_tlm_grp_dev(dev);
+ struct scmi_tlm_instance *ti =
+ groups_dev_to_tlm_instance(gde->dev.parent);
+
+ return __des_single_sample_read_show(ti, gde->grp->id, buf, PAGE_SIZE);
+}
+
+static struct device_attribute dev_attr_grp_des_bulk_read = {
+ .attr = { .name = "des_bulk_read", .mode = 0400 },
+ .show = grp_des_bulk_read_show,
+};
+
+static struct device_attribute dev_attr_grp_des_single_sample_read = {
+ .attr = { .name = "des_single_sample_read", .mode = 0400 },
+ .show = grp_des_single_sample_read_show,
+};
+
+static struct attribute *scmi_grp_attrs[] = {
+ &dev_attr_grp_enable.attr,
+ &dev_attr_grp_tstamp_enable.attr,
+ &dev_attr_grp_des_bulk_read.attr,
+ &dev_attr_grp_des_single_sample_read.attr,
+ &dev_attr_composing_des.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(scmi_grp);
+
+static int scmi_telemetry_groups_initialize(struct device *dev,
+ struct scmi_tlm_instance *ti)
+{
+ int ret;
+
+ if (ti->info->num_groups == 0)
+ return 0;
+
+ ret = scmi_telemetry_dev_register(&ti->groups_dev, &ti->dev, "groups");
+ if (ret)
+ return ret;
+
+ for (int i = 0; i < ti->info->num_groups; i++) {
+ const struct scmi_telemetry_group *grp = &ti->info->des_groups[i];
+ struct scmi_tlm_grp_dev *gdev;
+ char name[16];
+
+ gdev = devm_kzalloc(dev, sizeof(*gdev), GFP_KERNEL);
+ if (!gdev) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ gdev->tsp = ti->tsp;
+ gdev->grp = grp;
+ gdev->dev.groups = scmi_grp_groups;
+
+ snprintf(name, 8, "%d", grp->id);
+ ret = scmi_telemetry_dev_register(&gdev->dev,
+ &ti->groups_dev, name);
+ if (ret)
+ goto err;
+
+ if (ti->info->per_group_config_support) {
+ sysfs_add_file_to_group(&gdev->dev.kobj,
+ &dev_attr_grp_current_update.attr,
+ NULL);
+ sysfs_add_file_to_group(&gdev->dev.kobj,
+ &dev_attr_grp_intervals_discrete.attr,
+ NULL);
+ sysfs_add_file_to_group(&gdev->dev.kobj,
+ &dev_attr_grp_available_intervals.attr,
+ NULL);
+ }
+ }
+
+ dev_info(dev, "Found %d Telemetry GROUPS resources.\n",
+ ti->info->num_groups);
+
+ return 0;
+
+err:
+ scmi_telemetry_dev_unregister(&ti->groups_dev);
+
+ return ret;
+}
+
+static int scmi_telemetry_des_initialize(struct device *dev,
+ struct scmi_tlm_instance *ti)
+{
+ int ret;
+
+ ret = scmi_telemetry_dev_register(&ti->des_dev, &ti->dev, "des");
+ if (ret)
+ return ret;
+
+ for (int i = 0; i < ti->info->num_de; i++) {
+ const struct scmi_telemetry_de *de = ti->info->des[i];
+ struct scmi_tlm_de_dev *tdev;
+ char name[16];
+
+ tdev = devm_kzalloc(dev, sizeof(*tdev), GFP_KERNEL);
+ if (!tdev) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ tdev->tsp = ti->tsp;
+ tdev->de = de;
+ tdev->dev.groups = scmi_des_groups;
+
+ /*XXX What about of ID/name digits-length used ? */
+ snprintf(name, 8, "0x%04X", de->id);
+ ret = scmi_telemetry_dev_register(&tdev->dev,
+ &ti->des_dev, name);
+ if (ret)
+ goto err;
+
+ if (de->name)
+ sysfs_add_file_to_group(&tdev->dev.kobj,
+ &dev_attr_name.attr, NULL);
+ if (de->tstamp_support) {
+ sysfs_add_file_to_group(&tdev->dev.kobj,
+ &dev_attr_tstamp_exp.attr,
+ NULL);
+ sysfs_add_file_to_group(&tdev->dev.kobj,
+ &dev_attr_tstamp_enable.attr,
+ NULL);
+ }
+ }
+
+ dev_info(dev, "Found %d Telemetry DE resources.\n",
+ ti->info->num_de);
+
+ return 0;
+
+err:
+ scmi_telemetry_dev_unregister(&ti->des_dev);
+
+ return ret;
+}
+
+static int
+scmi_tlm_root_instance_initialize(struct device *dev,
+ struct scmi_tlm_instance *ti, int instance_id)
+{
+ char name[16];
+ int ret;
+
+ ti->dev.class = &scmi_telemetry_class;
+ ti->dev.groups = scmi_telemetry_groups;
+
+ snprintf(name, 16, "scmi_tlm_%d", instance_id);
+ ret = scmi_telemetry_dev_register(&ti->dev, NULL, name);
+ if (ret)
+ return ret;
+
+ if (ti->info->reset_support)
+ ret = sysfs_add_file_to_group(&ti->dev.kobj,
+ &dev_attr_reset.attr, NULL);
+
+ return ret;
+}
+
+static struct scmi_tlm_instance *scmi_tlm_init(struct scmi_tlm_setup *tsp,
+ int instance_id)
+{
+ const struct scmi_telemetry_proto_ops *tlm_ops = tsp->ops;
+ struct device *dev = &tsp->sdev->dev;
+ struct scmi_tlm_instance *ti;
+ int ret;
+
+ ti = devm_kzalloc(dev, sizeof(*ti), GFP_KERNEL);
+ if (!ti)
+ return ERR_PTR(-ENOMEM);
+
+ ti->info = tlm_ops->info_get(tsp->ph);
+ if (!ti->info) {
+ dev_err(dev, "invalid Telemetry info !\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ ti->tsp = tsp;
+
+ ret = scmi_tlm_root_instance_initialize(dev, ti, instance_id);
+ if (ret)
+ return ERR_PTR(ret);
+
+ ret = scmi_telemetry_des_initialize(dev, ti);
+ if (ret) {
+ device_unregister(&ti->dev);
+ return ERR_PTR(ret);
+ }
+
+ ret = scmi_telemetry_groups_initialize(dev, ti);
+ if (ret) {
+ scmi_telemetry_dev_unregister(&ti->des_dev);
+ device_unregister(&ti->dev);
+ return ERR_PTR(ret);
+ }
+
+ 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;
+ 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(&sdev->dev, sizeof(*tsp), GFP_KERNEL);
+ if (!tsp)
+ return -ENOMEM;
+
+ tsp->sdev = sdev;
+ tsp->ops = ops;
+ tsp->ph = ph;
+
+ //TODO Better to get info->id from SCMI/core
+ ti = scmi_tlm_init(tsp, atomic_fetch_inc(&scmi_tlm_instance_count));
+ if (IS_ERR(ti))
+ return PTR_ERR(ti);
+
+ dev_set_drvdata(&sdev->dev, ti);
+
+ return 0;
+}
+
+static void scmi_telemetry_remove(struct scmi_device *sdev)
+{
+ struct device *dev = &sdev->dev;
+ struct scmi_tlm_instance *ti;
+ bool enabled = false;
+ int ret;
+
+ ti = dev_get_drvdata(&sdev->dev);
+
+ ret = ti->tsp->ops->collection_configure(ti->tsp->ph,
+ SCMI_TLM_GRP_INVALID, true,
+ &enabled, NULL, NULL);
+ if (ret)
+ dev_warn(dev, "Failed to stop Telemetry collection\n");
+
+ scmi_telemetry_dev_unregister(&ti->groups_dev);
+ scmi_telemetry_dev_unregister(&ti->des_dev);
+ device_unregister(&ti->dev);
+}
+
+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,
+ .remove = scmi_telemetry_remove,
+ .id_table = scmi_id_table,
+};
+
+static int __init scmi_telemetry_init(void)
+{
+ int ret;
+
+ ret = class_register(&scmi_telemetry_class);
+ if (ret)
+ return ret;
+
+ ret = scmi_register(&scmi_telemetry_driver);
+ if (ret)
+ class_unregister(&scmi_telemetry_class);
+
+ return ret;
+}
+module_init(scmi_telemetry_init);
+
+static void __exit scmi_telemetry_exit(void)
+{
+ scmi_unregister(&scmi_telemetry_driver);
+
+ class_unregister(&scmi_telemetry_class);
+}
+module_exit(scmi_telemetry_exit);
+
+MODULE_AUTHOR("Cristian Marussi <cristian.marussi@arm.com>");
+MODULE_DESCRIPTION("ARM SCMI Telemetry Driver");
+MODULE_LICENSE("GPL v2");
--
2.47.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [RFC PATCH 5/7] firmware: arm_scmi: Add System Telemetry chardev/ioctls API
2025-06-20 19:28 [RFC PATCH 0/7] Introduce SCMI Telemetry support Cristian Marussi
` (3 preceding siblings ...)
2025-06-20 19:28 ` [RFC PATCH 4/7] firmware: arm_scmi: Add System Telemetry driver Cristian Marussi
@ 2025-06-20 19:28 ` Cristian Marussi
2025-06-20 21:51 ` Dan Carpenter
2025-06-20 19:28 ` [RFC PATCH 6/7] include: trace: Add Telemetry trace events Cristian Marussi
` (2 subsequent siblings)
7 siblings, 1 reply; 21+ messages in thread
From: Cristian Marussi @ 2025-06-20 19:28 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi
Add to the SCMI Telemetry driver an alternative custom userspace API based
on a character device and ioctls.
Still lacking:
- a few groups related ioctls
- a telemetry-reset ioctl
- mmap support for raw telenetry data access
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
.../firmware/arm_scmi/scmi_system_telemetry.c | 496 +++++++++++++++++-
include/uapi/linux/scmi.h | 253 +++++++++
2 files changed, 747 insertions(+), 2 deletions(-)
create mode 100644 include/uapi/linux/scmi.h
diff --git a/drivers/firmware/arm_scmi/scmi_system_telemetry.c b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
index a2f59001747d..c354dbe8a0f7 100644
--- a/drivers/firmware/arm_scmi/scmi_system_telemetry.c
+++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
@@ -6,6 +6,7 @@
*/
#include <linux/atomic.h>
+#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/fs.h>
@@ -15,6 +16,8 @@
#include <linux/sysfs.h>
#include <linux/slab.h>
+#include <uapi/linux/scmi.h>
+
#define MAX_BULK_LINE_CHAR_LENGTH 64
struct scmi_tlm_setup;
@@ -39,13 +42,22 @@ struct scmi_tlm_de_dev {
#define to_tlm_de_dev(d) \
(container_of((d), struct scmi_tlm_de_dev, dev))
+struct scmi_tlm_ioctls_db {
+ struct scmi_tlm_info tlm_info;
+ struct scmi_tlm_intervals *tlm_intervals;
+ struct scmi_tlm_intervals **tlm_grp_intervals;
+ struct scmi_tlm_des_list *tlm_des_list;
+};
+
struct scmi_tlm_instance {
struct device dev;
+ struct cdev cdev;
struct device des_dev;
struct device groups_dev;
struct scmi_tlm_de_dev **des;
struct scmi_tlm_setup *tsp;
const struct scmi_telemetry_info *info;
+ struct scmi_tlm_ioctls_db io_db;
};
#define dev_to_tlm_instance(d) \
@@ -71,6 +83,8 @@ struct scmi_tlm_setup {
const void *priv;
};
+static int scmi_tlm_major;
+
static void scmi_telemetry_release(struct device *dev)
{
}
@@ -326,7 +340,6 @@ static ssize_t intervals_discrete_show(struct device *dev,
return __intervals_discrete_show(buf, ti->info->intervals.discrete);
}
-//TODO Review available interval show
#define BUF_SZ 1024
static inline ssize_t
__available_update_show(char *buf,
@@ -532,6 +545,31 @@ scmi_telemetry_dev_register(struct device *dev, struct device *parent,
return ret;
}
+static int
+scmi_telemetry_cdev_register(struct device *dev, struct device *parent,
+ struct cdev *cdev, const struct file_operations *fops,
+ const char *name, unsigned int minor)
+{
+ int ret;
+
+ dev->parent = parent;
+ dev->release = scmi_tlm_dev_release;
+ dev_set_name(dev, "%s", name);
+ device_set_pm_not_required(dev);
+ dev_set_uevent_suppress(dev, true);
+
+ device_initialize(dev);
+
+ dev->devt = MKDEV(scmi_tlm_major, minor);
+ cdev_init(cdev, fops);
+
+ ret = cdev_device_add(cdev, dev);
+ if (ret)
+ put_device(dev);
+
+ return ret;
+}
+
static int scmi_des_iter(struct device *dev, void *data)
{
device_unregister(dev);
@@ -807,6 +845,448 @@ static int scmi_telemetry_des_initialize(struct device *dev,
return ret;
}
+struct scmi_tlm_priv {
+ char *buf;
+ size_t buf_sz;
+ int buf_len;
+ struct scmi_tlm_instance *ti;
+};
+
+static int scmi_tlm_open(struct inode *ino, struct file *filp)
+{
+ struct scmi_tlm_instance *ti;
+ struct scmi_tlm_priv *tp;
+
+ tp = kzalloc(sizeof(*tp), GFP_KERNEL);
+ if (!tp)
+ return -ENOMEM;
+
+ ti = container_of(ino->i_cdev, struct scmi_tlm_instance, cdev);
+ tp->ti = ti;
+
+ filp->private_data = tp;
+
+ return 0;
+}
+
+static int scmi_tlm_bulk_buffer_allocate_and_fill(struct scmi_tlm_priv *tp)
+{
+ struct scmi_tlm_instance *ti = tp->ti;
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ struct scmi_telemetry_de_sample *samples;
+ int ret, num_samples;
+
+ tp->buf_sz = ti->info->num_de * MAX_BULK_LINE_CHAR_LENGTH;
+ tp->buf = kzalloc(tp->buf_sz, GFP_KERNEL);
+ if (!tp->buf)
+ return -ENOMEM;
+
+ num_samples = ti->info->num_de;
+ samples = kcalloc(num_samples, sizeof(*samples), GFP_KERNEL);
+ if (!samples) {
+ kfree(tp->buf);
+ return -ENOMEM;
+ }
+
+ ret = tsp->ops->des_bulk_read(tsp->ph, SCMI_TLM_GRP_INVALID,
+ &num_samples, samples);
+ if (ret) {
+ kfree(samples);
+ kfree(tp->buf);
+ return ret;
+ }
+
+ ret = scmi_tlm_buffer_fill(&ti->dev, tp->buf, tp->buf_sz, &tp->buf_len,
+ num_samples, samples);
+ kfree(samples);
+
+ return ret;
+}
+
+static ssize_t scmi_tlm_read(struct file *filp, char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ struct scmi_tlm_priv *tp = filp->private_data;
+ int ret;
+
+ if (!tp->buf) {
+ ret = scmi_tlm_bulk_buffer_allocate_and_fill(tp);
+ if (ret)
+ return ret;
+ }
+
+ return simple_read_from_buffer(buf, count, ppos, tp->buf, tp->buf_len);
+}
+
+static __poll_t scmi_tlm_poll(struct file *, struct poll_table_struct *)
+{
+ return 0;
+}
+
+static long
+scmi_tlm_info_get_ioctl(struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ void * __user uptr = (void * __user)arg;
+
+ if (copy_to_user(uptr, &ti->io_db.tlm_info,
+ sizeof(ti->io_db.tlm_info)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long
+scmi_tlm_intervals_ioctl(struct scmi_tlm_instance *ti, unsigned long arg,
+ bool group)
+{
+ void * __user uptr = (void * __user)arg;
+ struct scmi_tlm_intervals ivs, *tlm_ivs;
+
+ if (copy_from_user(&ivs, uptr, sizeof(ivs)))
+ return -EFAULT;
+
+ if (!group) {
+ tlm_ivs = ti->io_db.tlm_intervals;
+ } else {
+ if (ivs.grp_id >= ti->info->num_groups)
+ return -EINVAL;
+
+ tlm_ivs = ti->io_db.tlm_grp_intervals[ivs.grp_id];
+ }
+
+ if (ivs.num != tlm_ivs->num)
+ return -EINVAL;
+
+ if (copy_to_user(uptr, tlm_ivs,
+ sizeof(*tlm_ivs) + sizeof(u32) * ivs.num))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long
+scmi_tlm_de_config_set_ioctl(struct scmi_tlm_instance *ti, unsigned long arg,
+ bool all)
+{
+ void * __user uptr = (void * __user)arg;
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ const struct scmi_telemetry_de *de;
+ 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);
+
+ for (int i = 0; i < ti->info->num_de; i++) {
+ de = ti->info->des[i];
+
+ ret = tsp->ops->state_set(tsp->ph, false, de->id,
+ (bool *)&tcfg.enable,
+ (bool *)&tcfg.t_enable);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static long
+scmi_tlm_de_config_get_ioctl(struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ void * __user uptr = (void * __user)arg;
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ struct scmi_tlm_de_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(struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ void * __user uptr = (void * __user)arg;
+ struct scmi_tlm_config cfg;
+
+ cfg.enable = !!ti->info->enabled;
+ cfg.current_update_interval =
+ ti->info->intervals.active_update_interval;
+
+ if (copy_to_user(uptr, &cfg, sizeof(cfg)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long
+scmi_tlm_config_set_ioctl(struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ void * __user uptr = (void * __user)arg;
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ struct scmi_tlm_config cfg = {};
+
+ if (copy_from_user(&cfg, uptr, sizeof(cfg)))
+ return -EFAULT;
+
+ return tsp->ops->collection_configure(tsp->ph, SCMI_TLM_GRP_INVALID,
+ true, (bool *)&cfg.enable,
+ &cfg.current_update_interval,
+ NULL);
+}
+
+static long
+scmi_tlm_des_list_get_ioctl(struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ void * __user uptr = (void * __user)arg;
+ struct scmi_tlm_des_list dsl;
+
+ if (copy_from_user(&dsl, uptr, sizeof(dsl)))
+ return -EFAULT;
+
+ if (dsl.num_des < ti->io_db.tlm_des_list->num_des)
+ return -EFAULT;
+
+ if (copy_to_user(uptr, ti->io_db.tlm_des_list,
+ sizeof(*ti->io_db.tlm_des_list) +
+ ti->io_db.tlm_des_list->num_des * sizeof(ti->io_db.tlm_des_list->des[0])))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long
+scmi_tlm_de_value_get_ioctl(struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ void * __user uptr = (void * __user)arg;
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ struct scmi_tlm_de_sample sample;
+ int ret;
+
+ 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_des_read_ioctl(struct scmi_tlm_instance *ti,
+ unsigned long arg, bool single)
+{
+ void * __user uptr = (void * __user)arg;
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ struct scmi_tlm_bulk_read bulk, *bulk_ptr;
+ int ret;
+
+ if (copy_from_user(&bulk, uptr, sizeof(bulk)))
+ return -EFAULT;
+
+ bulk_ptr = kzalloc(sizeof(*bulk_ptr) +
+ bulk.num_samples * sizeof(bulk_ptr->samples[0]),
+ GFP_KERNEL);
+ if (!bulk_ptr)
+ return -ENOMEM;
+
+ bulk_ptr->grp_id = bulk.grp_id;
+ bulk_ptr->num_samples = bulk.num_samples;
+ if (!single)
+ ret = tsp->ops->des_bulk_read(tsp->ph, bulk_ptr->grp_id,
+ &bulk_ptr->num_samples,
+ (struct scmi_telemetry_de_sample *)bulk_ptr->samples);
+ else
+ ret = tsp->ops->des_sample_get(tsp->ph, bulk_ptr->grp_id,
+ &bulk_ptr->num_samples,
+ (struct scmi_telemetry_de_sample *)bulk_ptr->samples);
+ if (ret)
+ goto out;
+
+ if (copy_to_user(uptr, bulk_ptr, sizeof(*bulk_ptr) +
+ bulk_ptr->num_samples * sizeof(bulk_ptr->samples[0])))
+ ret = -EFAULT;
+
+out:
+ kfree(bulk_ptr);
+
+ return ret;
+}
+
+static long scmi_tlm_unlocked_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct scmi_tlm_priv *tp = filp->private_data;
+ struct scmi_tlm_instance *ti = tp->ti;
+
+ switch (cmd) {
+ case SCMI_TLM_GET_INFO:
+ return scmi_tlm_info_get_ioctl(ti, arg);
+ case SCMI_TLM_GET_CFG:
+ return scmi_tlm_config_get_ioctl(ti, arg);
+ case SCMI_TLM_SET_CFG:
+ return scmi_tlm_config_set_ioctl(ti, arg);
+ case SCMI_TLM_GET_INTRVS:
+ return scmi_tlm_intervals_ioctl(ti, arg, false);
+ case SCMI_TLM_GET_DE_CFG:
+ return scmi_tlm_de_config_get_ioctl(ti, arg);
+ case SCMI_TLM_SET_DE_CFG:
+ return scmi_tlm_de_config_set_ioctl(ti, arg, false);
+ case SCMI_TLM_GET_DE_INFO:
+ return -EOPNOTSUPP;
+ case SCMI_TLM_GET_DE_LIST:
+ return scmi_tlm_des_list_get_ioctl(ti, arg);
+ case SCMI_TLM_GET_DE_VALUE:
+ return scmi_tlm_de_value_get_ioctl(ti, arg);
+ case SCMI_TLM_GET_GRP_CFG:
+ return -EOPNOTSUPP;
+ case SCMI_TLM_SET_GRP_CFG:
+ return -EOPNOTSUPP;
+ case SCMI_TLM_GET_GRP_INTRVS:
+ return scmi_tlm_intervals_ioctl(ti, arg, true);
+ case SCMI_TLM_GET_GRP_INFO:
+ return -EOPNOTSUPP;
+ case SCMI_TLM_GET_GRP_LIST:
+ return -EOPNOTSUPP;
+ case SCMI_TLM_SINGLE_SAMPLE:
+ return scmi_tlm_des_read_ioctl(ti, arg, true);
+ case SCMI_TLM_BULK_READ:
+ return scmi_tlm_des_read_ioctl(ti, arg, false);
+ case SCMI_TLM_SET_ALL_CFG:
+ return scmi_tlm_de_config_set_ioctl(ti, arg, true);
+ default:
+ return -ENOTTY;
+ }
+}
+
+static long scmi_tlm_compat_ioctl(struct file *, unsigned int, unsigned long)
+{
+ return 0;
+}
+
+static int scmi_tlm_mmap(struct file *, struct vm_area_struct *)
+{
+ return 0;
+}
+
+static int scmi_tlm_release(struct inode *ino, struct file *filp)
+{
+ struct scmi_tlm_priv *tp = filp->private_data;
+
+ kfree(tp->buf);
+ kfree(tp);
+
+ return 0;
+}
+
+static const struct file_operations scmi_tlm_fops = {
+ .owner = THIS_MODULE,
+ .open = scmi_tlm_open,
+ .read = scmi_tlm_read,
+ .poll = scmi_tlm_poll,
+ .unlocked_ioctl = scmi_tlm_unlocked_ioctl,
+ .compat_ioctl = scmi_tlm_compat_ioctl,
+ .mmap = scmi_tlm_mmap,
+ .release = scmi_tlm_release,
+};
+
+static int scmi_tlm_setup_ioctl_data(struct device *dev,
+ struct scmi_tlm_instance *ti)
+{
+ ti->io_db.tlm_info.version = ti->info->version;
+ for (int i = 0; i < SCMI_TLM_DE_IMPL_VERS; i++)
+ ti->io_db.tlm_info.de_impl_version[i] = ti->info->de_impl_version[i];
+ ti->io_db.tlm_info.num_des = ti->info->num_de;
+ ti->io_db.tlm_info.num_groups = ti->info->num_groups;
+ ti->io_db.tlm_info.num_intervals = ti->info->intervals.num;
+ if (ti->info->reset_support)
+ ti->io_db.tlm_info.flags = SCMI_TLM_CAN_RESET;
+
+ ti->io_db.tlm_intervals = devm_kzalloc(dev, sizeof(*ti->io_db.tlm_intervals) +
+ ti->info->intervals.num * sizeof(__u32),
+ GFP_KERNEL);
+ if (!ti->io_db.tlm_intervals)
+ return -ENOMEM;
+
+ ti->io_db.tlm_intervals->grp_id = 0;
+ ti->io_db.tlm_intervals->discrete = ti->info->intervals.discrete;
+ ti->io_db.tlm_intervals->num = ti->info->intervals.num;
+ for (int i = 0; i < ti->info->intervals.num; i++)
+ ti->io_db.tlm_intervals->available[i] =
+ ti->info->intervals.update_intervals[i];
+
+ ti->io_db.tlm_grp_intervals = devm_kcalloc(dev, ti->info->num_groups,
+ sizeof(ti->io_db.tlm_grp_intervals),
+ GFP_KERNEL);
+ if (!ti->io_db.tlm_grp_intervals)
+ return -ENOMEM;
+
+ for (int i = 0; i < ti->info->num_groups; i++) {
+ struct scmi_tlm_intervals *ivs;
+ struct scmi_telemetry_group *grp = &ti->info->des_groups[i];
+
+ ivs = devm_kzalloc(dev, sizeof(*ivs) +
+ grp->intervals.num * sizeof(__u32),
+ GFP_KERNEL);
+ if (!ivs)
+ return -ENOMEM;
+
+ ivs->grp_id = i;
+ ivs->discrete = grp->intervals.discrete;
+ ivs->num = grp->intervals.num;
+ for (int j = 0; j < ivs->num; j++)
+ ivs->available[i] = grp->intervals.update_intervals[i];
+
+ ti->io_db.tlm_grp_intervals[i] = ivs;
+ }
+
+ ti->io_db.tlm_des_list = devm_kzalloc(dev, sizeof(*ti->io_db.tlm_des_list) +
+ ti->info->num_de * sizeof(ti->io_db.tlm_des_list->des[0]),
+ GFP_KERNEL);
+ if (!ti->io_db.tlm_des_list)
+ return -ENOMEM;
+
+ ti->io_db.tlm_des_list->num_des = ti->info->num_de;
+ for (int i = 0; i < ti->info->num_de; i++) {
+ ti->io_db.tlm_des_list->des[i].id = ti->info->des[i]->id;
+ ti->io_db.tlm_des_list->des[i].grp_id =
+ ti->info->des[i]->grp ? ti->info->des[i]->grp->id : SCMI_TLM_GRP_INVALID;
+ ti->io_db.tlm_des_list->des[i].data_sz = ti->info->des[i]->data_sz;
+ ti->io_db.tlm_des_list->des[i].type = ti->info->des[i]->type;
+ ti->io_db.tlm_des_list->des[i].unit = ti->info->des[i]->unit;
+ ti->io_db.tlm_des_list->des[i].unit_exp = ti->info->des[i]->unit_exp;
+ ti->io_db.tlm_des_list->des[i].tstamp_exp = ti->info->des[i]->tstamp_exp;
+ ti->io_db.tlm_des_list->des[i].instance_id = ti->info->des[i]->instance_id;
+ ti->io_db.tlm_des_list->des[i].compo_instance_id =
+ ti->info->des[i]->compo_instance_id;
+ ti->io_db.tlm_des_list->des[i].compo_type = ti->info->des[i]->compo_type;
+ ti->io_db.tlm_des_list->des[i].persistent = ti->info->des[i]->persistent;
+ if (ti->info->des[i]->name)
+ strscpy(ti->io_db.tlm_des_list->des[i].name, ti->info->des[i]->name,
+ SCMI_SHORT_NAME_MAX_SIZE);
+ }
+
+ return 0;
+}
+
static int
scmi_tlm_root_instance_initialize(struct device *dev,
struct scmi_tlm_instance *ti, int instance_id)
@@ -814,11 +1294,16 @@ scmi_tlm_root_instance_initialize(struct device *dev,
char name[16];
int ret;
+ ret = scmi_tlm_setup_ioctl_data(dev, ti);
+ if (ret)
+ return ret;
+
ti->dev.class = &scmi_telemetry_class;
ti->dev.groups = scmi_telemetry_groups;
snprintf(name, 16, "scmi_tlm_%d", instance_id);
- ret = scmi_telemetry_dev_register(&ti->dev, NULL, name);
+ ret = scmi_telemetry_cdev_register(&ti->dev, NULL, &ti->cdev,
+ &scmi_tlm_fops, name, instance_id);
if (ret)
return ret;
@@ -940,8 +1425,15 @@ static struct scmi_driver scmi_telemetry_driver = {
static int __init scmi_telemetry_init(void)
{
+ dev_t devt;
int ret;
+ ret = alloc_chrdev_region(&devt, 0, 1024, "scmi-tlm");
+ if (ret)
+ return ret;
+
+ scmi_tlm_major = MAJOR(devt);
+
ret = class_register(&scmi_telemetry_class);
if (ret)
return ret;
diff --git a/include/uapi/linux/scmi.h b/include/uapi/linux/scmi.h
new file mode 100644
index 000000000000..8a0f365fca52
--- /dev/null
+++ b/include/uapi/linux/scmi.h
@@ -0,0 +1,253 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Copyright (C) 2025 ARM Ltd.
+ */
+#ifndef _UAPI_LINUX_SCMI_H
+#define _UAPI_LINUX_SCMI_H
+
+/*
+ * Userspace interface SCMI Telemetry
+ */
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+#define SCMI_TLM_DE_IMPL_VERS 4
+#define SCMI_TLM_GRP_INVALID 0xFFFFFFFF
+
+/**
+ * scmi_tlm_info - Basic info about an instance
+ *
+ * @version: SCMI Telemetry protocol version
+ * @de_impl_version: SCMI Telemetry DE implementation revision
+ * @num_desi: 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
+ */
+struct scmi_tlm_info {
+ __u32 version;
+ __u32 de_impl_version[SCMI_TLM_DE_IMPL_VERS];
+ __u32 num_des;
+ __u32 num_groups;
+ __u32 num_intervals;
+ __u32 flags;
+#define SCMI_TLM_CAN_RESET (1 << 0)
+};
+
+/**
+ * scmi_tlm_config - Basic instance configuration
+ *
+ * @enable: Enable/Disable Telemetry for the whole instance
+ * @current_update_interval: Get/Set currently active update interval
+ * (periodic tick for SHMTIs and Notifications)
+ *
+ * Used by:
+ * RO - SCMI_TLM_GET_CFG
+ * WO - SCMI_TLM_SET_CFG
+ */
+struct scmi_tlm_config {
+ __u32 enable;
+ __u32 current_update_interval;
+};
+
+/**
+ * scmi_tlm_intervals - Update intervals descriptor
+ *
+ * @grp_id: Group identifier (ignored by SCMI_TLM_GET_INTRVS)
+ * @discrete: Flag to indicate the nature of the intervals described in
+ * @available. When 'false' @available is a triplet: min/max/step
+ * @num: Number of entries of @available
+ * @available: A variably-sized array containing the update intervals
+ *
+ * Used by:
+ * RO - SCMI_TLM_GET_INTRVS
+ * RW - SCMI_TLM_GET_GRP_INTRVS
+ */
+struct scmi_tlm_intervals {
+ __u32 grp_id;
+ __u32 discrete;
+ __u32 num;
+ __u32 available[];
+};
+
+/**
+ * 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
+ */
+struct scmi_tlm_de_config {
+ __u32 id;
+ __u32 enable;
+ __u32 t_enable;
+};
+
+/**
+ * scmi_tlm_de_info - DE Descriptor
+ *
+ * @id: DE identifier
+ * @grp_id: Identifier of the group which this DE belongs to; reported as
+ * SCMI_TLM_GRP_INVALID when not part of any group
+ * @data_sz: DE data size in bytes
+ * @type: DE type
+ * @unit: DE unit of measurements
+ * @unit_exp: Power-of-10 multiplier for DE unit
+ * @tstamp_exp: Power-of-10 multiplier for DE timestamp (if supported)
+ * @instance_id: DE instance ID
+ * @compo_instance_id: DE component instance ID
+ * @compo_type: Type of component which is associated to this DE
+ * @peristent: 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
+ * RO - SCMI_TLM_GET_DE_LIST
+ */
+struct scmi_tlm_de_info {
+ __u32 id;
+ __u32 grp_id;
+ __u32 data_sz;
+ __u32 type;
+ __u32 unit;
+ __s32 unit_exp;
+ __s32 tstamp_exp;
+ __u32 instance_id;
+ __u32 compo_instance_id;
+ __u32 compo_type;
+ __u32 persistent;
+ __u8 name[16];
+};
+
+/**
+ * scmi_tlm_des_list - List of all defined DEs
+ *
+ * @num_des: Number of entries in @des
+ * @des: An array containing descriptors for all defined DEs
+ *
+ * Used by:
+ * RO - SCMI_TLM_GET_DE_LIST
+ */
+struct scmi_tlm_des_list {
+ __u32 num_des;
+ struct scmi_tlm_de_info des[];
+};
+
+/**
+ * scmi_tlm_de_sample - A DE reading
+ *
+ * @id: DE identifier
+ * @tstamp: DE reading timestamp (equal 0 is NOT supported)
+ * @val: Reading of the DE data value
+ *
+ * Used by:
+ * RW - SCMI_TLM_GET_DE_VALUE
+ * RO - SCMI_TLM_SINGLE_READ
+ */
+struct scmi_tlm_de_sample {
+ __u32 id;
+ __u64 tstamp;
+ __u64 val;
+};
+
+/**
+ * scmi_tlm_bulk_read - Bulk read of multiple DEs
+ *
+ * @grp_id: The identifier of the group to query with a single asynchronous
+ * sample read. Set to SCMI_TLM_GRP_INVALID to ignore.
+ * @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
+ */
+struct scmi_tlm_bulk_read {
+ __u32 grp_id;
+ __u32 num_samples;
+ struct scmi_tlm_de_sample samples[];
+};
+
+/**
+ * scmi_tlm_grp_info - DE-group descriptor
+ *
+ * @id: Group identifier
+ * @flags: Group capabilities
+ * @num_intervals: Number of update intervals supported
+ * @num_des: Number of DEs part of this group
+ *
+ * Used by:
+ * WR - SCMI_TLM_GET_GRP_INFO
+ */
+struct scmi_tlm_grp_info {
+ __u32 id;
+ __u32 flags;
+#define SCMI_TLM_GRP_HAS_UPDATE (1 << 0)
+ __u32 num_intervals;
+ __u32 num_des;
+};
+
+/**
+ * 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
+ */
+struct scmi_tlm_grps_list {
+ __u32 num_grps;
+ struct scmi_tlm_grp_info grps[];
+};
+
+/**
+ * scmi_tlm_grp_config - Group config
+ *
+ * @id: Identifier of the DEs-group to act upon
+ * @enable: A boolean to enable/disable the group
+ * @t_enable: A boolean to enable/disable the timestamp for this group
+ *
+ * Used by:
+ * RW - SCMI_TLM_GET_GRP_CFG
+ * WO - SCMI_TLM_SET_GRP_CFG
+ */
+struct scmi_tlm_grp_config {
+ __u32 id;
+ __u32 enable;
+ __u32 t_enable;
+ __u32 current_update_interval;
+};
+
+#define SCMI 0xF1
+
+#define SCMI_TLM_GET_INFO _IOR(SCMI, 0x00, struct scmi_tlm_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 _IOR(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 _IOW(SCMI, 0x05, struct scmi_tlm_de_config)
+#define SCMI_TLM_GET_DE_INFO _IOWR(SCMI, 0x06, struct scmi_tlm_de_info)
+#define SCMI_TLM_GET_DE_LIST _IOWR(SCMI, 0x07, struct scmi_tlm_des_list)
+#define SCMI_TLM_GET_DE_VALUE _IOWR(SCMI, 0x08, struct scmi_tlm_de_sample)
+#define SCMI_TLM_GET_GRP_CFG _IOWR(SCMI, 0x09, struct scmi_tlm_grp_config)
+#define SCMI_TLM_SET_GRP_CFG _IOW(SCMI, 0x0A, struct scmi_tlm_grp_config)
+#define SCMI_TLM_GET_GRP_INTRVS _IOWR(SCMI, 0x0B, struct scmi_tlm_intervals)
+#define SCMI_TLM_GET_GRP_INFO _IOWR(SCMI, 0x0C, struct scmi_tlm_grp_info)
+#define SCMI_TLM_GET_GRP_LIST _IOR(SCMI, 0x0D, struct scmi_tlm_grps_list)
+#define SCMI_TLM_SINGLE_SAMPLE _IOWR(SCMI, 0x0E, struct scmi_tlm_bulk_read)
+#define SCMI_TLM_BULK_READ _IOWR(SCMI, 0x0F, struct scmi_tlm_bulk_read)
+#define SCMI_TLM_SET_ALL_CFG _IOW(SCMI, 0x10, struct scmi_tlm_de_config)
+
+#endif /* _UAPI_LINUX_SCMI_H */
--
2.47.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [RFC PATCH 6/7] include: trace: Add Telemetry trace events
2025-06-20 19:28 [RFC PATCH 0/7] Introduce SCMI Telemetry support Cristian Marussi
` (4 preceding siblings ...)
2025-06-20 19:28 ` [RFC PATCH 5/7] firmware: arm_scmi: Add System Telemetry chardev/ioctls API Cristian Marussi
@ 2025-06-20 19:28 ` Cristian Marussi
2025-06-20 19:28 ` [RFC PATCH 7/7] firmware: arm_scmi: Use new Telemetry traces Cristian Marussi
2025-06-24 10:22 ` [RFC PATCH 0/7] Introduce SCMI Telemetry support Dhruva Gole
7 siblings, 0 replies; 21+ messages in thread
From: Cristian Marussi @ 2025-06-20 19:28 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, 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 127300481123..471f028f36db 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),
@@ -176,6 +177,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.47.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [RFC PATCH 7/7] firmware: arm_scmi: Use new Telemetry traces
2025-06-20 19:28 [RFC PATCH 0/7] Introduce SCMI Telemetry support Cristian Marussi
` (5 preceding siblings ...)
2025-06-20 19:28 ` [RFC PATCH 6/7] include: trace: Add Telemetry trace events Cristian Marussi
@ 2025-06-20 19:28 ` Cristian Marussi
2025-06-24 10:22 ` [RFC PATCH 0/7] Introduce SCMI Telemetry support Dhruva Gole
7 siblings, 0 replies; 21+ messages in thread
From: Cristian Marussi @ 2025-06-20 19:28 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, d-gole, souvik.chakravarty, Cristian Marussi
Track failed SHMTI accesses and notification updates.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
drivers/firmware/arm_scmi/telemetry.c | 35 +++++++++++++++++++++++----
1 file changed, 30 insertions(+), 5 deletions(-)
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
index 3cbad06251a9..7843ff802bd0 100644
--- a/drivers/firmware/arm_scmi/telemetry.c
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -16,6 +16,8 @@
#include "protocols.h"
#include "notify.h"
+#include <trace/events/scmi.h>
+
/* Updated only after ALL the mandatory features for that version are merged */
#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x10000
@@ -813,8 +815,10 @@ static int scmi_telemetry_tdcf_parse_one(struct telemetry_info *ti,
int used_qwords;
de = xa_load(&ti->xa_des, le32_to_cpu(payld->id));
- if (!de || DATA_INVALID(payld))
+ if (!de || DATA_INVALID(payld)) {
+ trace_scmi_tlm_access(de->id, "DE_INVALID", 0, 0);
return -EINVAL;
+ }
used_qwords = 4;
@@ -840,6 +844,8 @@ static int scmi_telemetry_tdcf_parse_one(struct telemetry_info *ti,
else
tde->last_ts = 0;
+ trace_scmi_tlm_collect(0, de->id, tde->last_val, "SHMTI_UPDATE");
+
return used_qwords;
}
@@ -864,8 +870,10 @@ static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
fsleep((SCMI_TLM_TDCF_MAX_RETRIES - retries) * 1000);
startm = TDCF_START_SEQ_GET(tdcf);
- if (IS_BAD_START_SEQ(startm))
+ if (IS_BAD_START_SEQ(startm)) {
+ trace_scmi_tlm_access(0, "MSEQ_BADSTART", startm, 0);
continue;
+ }
qwords = tdcf->prlg.num_qwords;
next = tdcf->payld;
@@ -874,14 +882,18 @@ static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
used_qwords = scmi_telemetry_tdcf_parse_one(ti, next,
update ? shmti : NULL);
- if (qwords < used_qwords)
+ if (qwords < used_qwords) {
+ trace_scmi_tlm_access(0, "BAD_QWORDS", 0, 0);
return -EINVAL;
+ }
next += used_qwords * 4;
qwords -= used_qwords;
}
endm = TDCF_END_SEQ_GET(eplg);
+ if (startm != endm)
+ trace_scmi_tlm_access(0, "MSEQ_MISMATCH", startm, endm);
} while (startm != endm && --retries);
if (startm != endm)
@@ -1252,12 +1264,17 @@ static int scmi_telemetry_de_tdcf_parse(struct telemetry_de *tde,
fsleep((SCMI_TLM_TDCF_MAX_RETRIES - retries) * 1000);
startm = TDCF_START_SEQ_GET(tdcf);
- if (IS_BAD_START_SEQ(startm))
+ if (IS_BAD_START_SEQ(startm)) {
+ trace_scmi_tlm_access(tde->de.id, "MSEQ_BADSTART",
+ startm, 0);
continue;
+ }
payld = tde->base + tde->offset;
- if (le32_to_cpu(payld->id) != tde->de.id || DATA_INVALID(payld))
+ if (le32_to_cpu(payld->id) != tde->de.id || DATA_INVALID(payld)) {
+ trace_scmi_tlm_access(tde->de.id, "DE_INVALID", 0, 0);
return -EINVAL;
+ }
//TODO BLK_TS
if (tstamp && USE_LINE_TS(payld) && TS_VALID(payld))
@@ -1266,6 +1283,9 @@ static int scmi_telemetry_de_tdcf_parse(struct telemetry_de *tde,
*val = LINE_DATA_GET(&payld->tsl);
endm = TDCF_END_SEQ_GET(tde->eplg);
+ if (startm != endm)
+ trace_scmi_tlm_access(tde->de.id, "MSEQ_MISMATCH",
+ startm, endm);
} while (startm != endm && --retries);
if (startm != endm)
@@ -1412,6 +1432,9 @@ scmi_telemetry_msg_payld_process(struct telemetry_info *ti,
tde->last_ts = LINE_TSTAMP_GET(&payld->tsl);
else
tde->last_ts = 0;
+
+ trace_scmi_tlm_collect(timestamp, tde->de.id, tde->last_val,
+ "MESSAGE");
}
}
@@ -1622,6 +1645,8 @@ static void scmi_telemetry_scan_update(struct telemetry_info *ti, u64 ts)
tde->last_ts = tstamp;
else
tde->last_ts = 0;
+
+ trace_scmi_tlm_collect(ts, de->id, tde->last_val, "FC_UPDATE");
}
}
--
2.47.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [RFC PATCH 3/7] firmware: arm_scmi: Add Telemetry protocol support
2025-06-20 19:28 ` [RFC PATCH 3/7] firmware: arm_scmi: Add Telemetry protocol support Cristian Marussi
@ 2025-06-20 20:46 ` Dan Carpenter
2025-06-25 14:02 ` Cristian Marussi
2025-06-25 14:04 ` Cristian Marussi
2025-06-20 21:01 ` Dan Carpenter
1 sibling, 2 replies; 21+ messages in thread
From: Dan Carpenter @ 2025-06-20 20:46 UTC (permalink / raw)
To: Cristian Marussi
Cc: linux-kernel, linux-arm-kernel, arm-scmi, sudeep.holla,
james.quinlan, f.fainelli, vincent.guittot, etienne.carriere,
peng.fan, michal.simek, quic_sibis, d-gole, souvik.chakravarty
On Fri, Jun 20, 2025 at 08:28:09PM +0100, Cristian Marussi wrote:
> +static int
> +scmi_telemetry_protocol_attributes_get(const struct scmi_protocol_handle *ph,
> + struct telemetry_info *ti)
> +{
> + int ret;
> + struct scmi_xfer *t;
> + struct scmi_msg_resp_telemetry_protocol_attributes *resp;
> +
> + 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.num_de = le32_to_cpu(resp->de_num);
> + ti->info.num_groups = le32_to_cpu(resp->groups_num);
> + for (int i = 0; i < SCMI_TLM_MAX_DWORD; i++)
> + ti->info.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));
> + /* Allocate DEs descriptors */
> + ti->info.des = devm_kcalloc(ph->dev, ti->info.num_de,
> + sizeof(*ti->info.des), GFP_KERNEL);
> + if (!ti->info.des)
> + ret = -ENOMEM;
> +
> + /* Allocate DE GROUPS descriptors */
> + ti->info.des_groups = devm_kcalloc(ph->dev, ti->info.num_groups,
> + sizeof(*ti->info.des_groups),
> + GFP_KERNEL);
> + if (!ti->info.des_groups)
> + ret = -ENOMEM;
It the allocation fails we need to jump to the ->xfer_put
> +
> + for (int i = 0; i < ti->info.num_groups; i++)
> + ti->info.des_groups[i].id = i;
otherwise it leads to a NULL dereference.
> + }
> +
> + ph->xops->xfer_put(ph, t);
> +
> + return ret;
> +}
[ snip ]
> +static int iter_shmti_process_response(const struct scmi_protocol_handle *ph,
> + const void *response,
> + struct scmi_iterator_state *st,
> + void *priv)
> +{
> + const struct scmi_msg_resp_telemetry_shmti_list *r = response;
> + struct telemetry_info *ti = priv;
> + struct telemetry_shmti *shmti;
> + const struct scmi_shmti_desc *desc;
> + void __iomem *addr;
> + u64 phys_addr;
> + u32 len;
> +
> + desc = &r->desc[st->loop_idx];
> + shmti = &ti->shmti[st->desc_index + st->loop_idx];
> +
> + shmti->id = le32_to_cpu(desc->id);
> + phys_addr = le32_to_cpu(desc->addr_low);
> + phys_addr |= (u64)le32_to_cpu(desc->addr_high) << 32;
> +
> + len = le32_to_cpu(desc->length);
> + addr = devm_ioremap(ph->dev, phys_addr, len);
> + if (!addr)
> + return -EADDRNOTAVAIL;
> +
> + shmti->base = addr;
> + shmti->len = len;
There is some code later which assumes ->len is at least
TDCF_EPLG_SZ and de->data_sz. This is probably where we should
check if (len < TDCF_EPLG_SZ) return -EINVAL; and the de->data_sz
would be checked later.
> +
> + return 0;
> +}
regards,
dan carpenter
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC PATCH 3/7] firmware: arm_scmi: Add Telemetry protocol support
2025-06-20 19:28 ` [RFC PATCH 3/7] firmware: arm_scmi: Add Telemetry protocol support Cristian Marussi
2025-06-20 20:46 ` Dan Carpenter
@ 2025-06-20 21:01 ` Dan Carpenter
2025-06-25 14:04 ` Cristian Marussi
1 sibling, 1 reply; 21+ messages in thread
From: Dan Carpenter @ 2025-06-20 21:01 UTC (permalink / raw)
To: Cristian Marussi
Cc: linux-kernel, linux-arm-kernel, arm-scmi, sudeep.holla,
james.quinlan, f.fainelli, vincent.guittot, etienne.carriere,
peng.fan, michal.simek, quic_sibis, d-gole, souvik.chakravarty
On Fri, Jun 20, 2025 at 08:28:09PM +0100, Cristian Marussi wrote:
> + /* Build compsing DES string */
> + for (int i = 0; i < ti->info.num_groups; i++) {
> + struct scmi_telemetry_group *grp = &ti->info.des_groups[i];
> + char *buf = grp->des_str;
> + size_t bufsize = grp->des_str_sz;
> +
> + for (int j = 0; j < grp->num_de; j++) {
> + char term = j != (grp->num_de - 1) ? ' ' : '\0';
> + int len;
> +
> + len = snprintf(buf, bufsize, "0x%04X%c",
> + ti->info.des[grp->des[j]]->id, term);
Please use scnprintf() btw. Otherwise if there is an overflow the
next iteration will complain that bufsize is negative.
> +
> + buf += len;
> + bufsize -= len;
> + }
> + }
> +
> + return 0;
> +}
regards,
dan carpenter
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC PATCH 4/7] firmware: arm_scmi: Add System Telemetry driver
2025-06-20 19:28 ` [RFC PATCH 4/7] firmware: arm_scmi: Add System Telemetry driver Cristian Marussi
@ 2025-06-20 21:27 ` Dan Carpenter
2025-06-25 14:11 ` Cristian Marussi
0 siblings, 1 reply; 21+ messages in thread
From: Dan Carpenter @ 2025-06-20 21:27 UTC (permalink / raw)
To: Cristian Marussi
Cc: linux-kernel, linux-arm-kernel, arm-scmi, sudeep.holla,
james.quinlan, f.fainelli, vincent.guittot, etienne.carriere,
peng.fan, michal.simek, quic_sibis, d-gole, souvik.chakravarty
On Fri, Jun 20, 2025 at 08:28:10PM +0100, Cristian Marussi wrote:
> +//TODO Review available interval show
> +#define BUF_SZ 1024
> +static inline ssize_t
> +__available_update_show(char *buf,
> + const struct scmi_telemetry_update_interval *intervals)
> +{
> + int len = 0, num_intervals = intervals->num;
> + char available[BUF_SZ];
> +
> + for (int i = 0; i < num_intervals; i++) {
> + len += scnprintf(available + len, BUF_SZ - len, "%u ",
> + intervals->update_intervals[i]);
> + }
> +
> + available[len - 1] = '\0';
No need. scnprintf() will already have put a NUL terminator there.
Unless num_intervals <= 0 in which case this will corrupt memory.
> +
> + return sysfs_emit(buf, "%s\n", available);
> +}
[ snip ]
> +static int scmi_telemetry_groups_initialize(struct device *dev,
> + struct scmi_tlm_instance *ti)
> +{
> + int ret;
> +
> + if (ti->info->num_groups == 0)
> + return 0;
> +
> + ret = scmi_telemetry_dev_register(&ti->groups_dev, &ti->dev, "groups");
> + if (ret)
> + return ret;
> +
> + for (int i = 0; i < ti->info->num_groups; i++) {
> + const struct scmi_telemetry_group *grp = &ti->info->des_groups[i];
> + struct scmi_tlm_grp_dev *gdev;
> + char name[16];
> +
> + gdev = devm_kzalloc(dev, sizeof(*gdev), GFP_KERNEL);
> + if (!gdev) {
> + ret = -ENOMEM;
> + goto err;
> + }
> +
> + gdev->tsp = ti->tsp;
> + gdev->grp = grp;
> + gdev->dev.groups = scmi_grp_groups;
> +
> + snprintf(name, 8, "%d", grp->id);
s/8/sizeof(name)/?
> + ret = scmi_telemetry_dev_register(&gdev->dev,
> + &ti->groups_dev, name);
> + if (ret)
> + goto err;
> +
> + if (ti->info->per_group_config_support) {
> + sysfs_add_file_to_group(&gdev->dev.kobj,
> + &dev_attr_grp_current_update.attr,
> + NULL);
> + sysfs_add_file_to_group(&gdev->dev.kobj,
> + &dev_attr_grp_intervals_discrete.attr,
> + NULL);
> + sysfs_add_file_to_group(&gdev->dev.kobj,
> + &dev_attr_grp_available_intervals.attr,
> + NULL);
> + }
> + }
> +
> + dev_info(dev, "Found %d Telemetry GROUPS resources.\n",
> + ti->info->num_groups);
> +
> + return 0;
> +
> +err:
> + scmi_telemetry_dev_unregister(&ti->groups_dev);
> +
> + return ret;
> +}
> +
> +static int scmi_telemetry_des_initialize(struct device *dev,
> + struct scmi_tlm_instance *ti)
> +{
> + int ret;
> +
> + ret = scmi_telemetry_dev_register(&ti->des_dev, &ti->dev, "des");
> + if (ret)
> + return ret;
> +
> + for (int i = 0; i < ti->info->num_de; i++) {
> + const struct scmi_telemetry_de *de = ti->info->des[i];
> + struct scmi_tlm_de_dev *tdev;
> + char name[16];
> +
> + tdev = devm_kzalloc(dev, sizeof(*tdev), GFP_KERNEL);
> + if (!tdev) {
> + ret = -ENOMEM;
> + goto err;
> + }
> +
> + tdev->tsp = ti->tsp;
> + tdev->de = de;
> + tdev->dev.groups = scmi_des_groups;
> +
> + /*XXX What about of ID/name digits-length used ? */
> + snprintf(name, 8, "0x%04X", de->id);
s/8/sizeof(name)/?
regards,
dan carpenter
> + ret = scmi_telemetry_dev_register(&tdev->dev,
> + &ti->des_dev, name);
> + if (ret)
> + goto err;
> +
> + if (de->name)
> + sysfs_add_file_to_group(&tdev->dev.kobj,
> + &dev_attr_name.attr, NULL);
> + if (de->tstamp_support) {
> + sysfs_add_file_to_group(&tdev->dev.kobj,
> + &dev_attr_tstamp_exp.attr,
> + NULL);
> + sysfs_add_file_to_group(&tdev->dev.kobj,
> + &dev_attr_tstamp_enable.attr,
> + NULL);
> + }
> + }
> +
> + dev_info(dev, "Found %d Telemetry DE resources.\n",
> + ti->info->num_de);
> +
> + return 0;
> +
> +err:
> + scmi_telemetry_dev_unregister(&ti->des_dev);
> +
> + return ret;
> +}
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC PATCH 5/7] firmware: arm_scmi: Add System Telemetry chardev/ioctls API
2025-06-20 19:28 ` [RFC PATCH 5/7] firmware: arm_scmi: Add System Telemetry chardev/ioctls API Cristian Marussi
@ 2025-06-20 21:51 ` Dan Carpenter
2025-06-25 14:14 ` Cristian Marussi
0 siblings, 1 reply; 21+ messages in thread
From: Dan Carpenter @ 2025-06-20 21:51 UTC (permalink / raw)
To: Cristian Marussi
Cc: linux-kernel, linux-arm-kernel, arm-scmi, sudeep.holla,
james.quinlan, f.fainelli, vincent.guittot, etienne.carriere,
peng.fan, michal.simek, quic_sibis, d-gole, souvik.chakravarty
On Fri, Jun 20, 2025 at 08:28:11PM +0100, Cristian Marussi wrote:
> +static long scmi_tlm_des_read_ioctl(struct scmi_tlm_instance *ti,
> + unsigned long arg, bool single)
> +{
> + void * __user uptr = (void * __user)arg;
> + struct scmi_tlm_setup *tsp = ti->tsp;
> + struct scmi_tlm_bulk_read bulk, *bulk_ptr;
> + int ret;
> +
> + if (copy_from_user(&bulk, uptr, sizeof(bulk)))
> + return -EFAULT;
> +
> + bulk_ptr = kzalloc(sizeof(*bulk_ptr) +
> + bulk.num_samples * sizeof(bulk_ptr->samples[0]),
This should be struct_size(bulk_ptr, samples, bulk.num_samples) to
avoid an integer overflow on 32bit systems.
regards,
dan carpenter
> + GFP_KERNEL);
> + if (!bulk_ptr)
> + return -ENOMEM;
> +
> + bulk_ptr->grp_id = bulk.grp_id;
> + bulk_ptr->num_samples = bulk.num_samples;
> + if (!single)
> + ret = tsp->ops->des_bulk_read(tsp->ph, bulk_ptr->grp_id,
> + &bulk_ptr->num_samples,
> + (struct scmi_telemetry_de_sample *)bulk_ptr->samples);
> + else
> + ret = tsp->ops->des_sample_get(tsp->ph, bulk_ptr->grp_id,
> + &bulk_ptr->num_samples,
> + (struct scmi_telemetry_de_sample *)bulk_ptr->samples);
> + if (ret)
> + goto out;
> +
> + if (copy_to_user(uptr, bulk_ptr, sizeof(*bulk_ptr) +
> + bulk_ptr->num_samples * sizeof(bulk_ptr->samples[0])))
> + ret = -EFAULT;
> +
> +out:
> + kfree(bulk_ptr);
> +
> + return ret;
> +}
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC PATCH 1/7] firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value
2025-06-20 19:28 ` [RFC PATCH 1/7] firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value Cristian Marussi
@ 2025-06-24 3:13 ` Peng Fan
2025-06-25 13:59 ` Cristian Marussi
0 siblings, 1 reply; 21+ messages in thread
From: Peng Fan @ 2025-06-24 3:13 UTC (permalink / raw)
To: Cristian Marussi
Cc: linux-kernel, linux-arm-kernel, arm-scmi, sudeep.holla,
james.quinlan, f.fainelli, vincent.guittot, etienne.carriere,
michal.simek, quic_sibis, dan.carpenter, d-gole,
souvik.chakravarty
On Fri, Jun 20, 2025 at 08:28:07PM +0100, Cristian Marussi wrote:
>Add a common definition of SCMI_MAX_PROTOCOLS and use it all over the
>SCMI stack.
>
>Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
>---
> drivers/firmware/arm_scmi/notify.c | 4 +---
> include/linux/scmi_protocol.h | 3 +++
> 2 files changed, 4 insertions(+), 3 deletions(-)
>
>diff --git a/drivers/firmware/arm_scmi/notify.c b/drivers/firmware/arm_scmi/notify.c
>index e160ecb22948..27a53a6729dd 100644
>--- a/drivers/firmware/arm_scmi/notify.c
>+++ b/drivers/firmware/arm_scmi/notify.c
>@@ -94,8 +94,6 @@
> #include "common.h"
> #include "notify.h"
>
>-#define SCMI_MAX_PROTO 256
>-
> #define PROTO_ID_MASK GENMASK(31, 24)
> #define EVT_ID_MASK GENMASK(23, 16)
> #define SRC_ID_MASK GENMASK(15, 0)
>@@ -1652,7 +1650,7 @@ int scmi_notification_init(struct scmi_handle *handle)
> ni->gid = gid;
> ni->handle = handle;
>
>- ni->registered_protocols = devm_kcalloc(handle->dev, SCMI_MAX_PROTO,
>+ ni->registered_protocols = devm_kcalloc(handle->dev, SCMI_MAX_PROTOCOLS,
> sizeof(char *), GFP_KERNEL);
> if (!ni->registered_protocols)
> goto err;
>diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
>index 688466a0e816..6f8d36e1f8fc 100644
>--- a/include/linux/scmi_protocol.h
>+++ b/include/linux/scmi_protocol.h
>@@ -926,8 +926,11 @@ enum scmi_std_protocol {
> SCMI_PROTOCOL_VOLTAGE = 0x17,
> SCMI_PROTOCOL_POWERCAP = 0x18,
> SCMI_PROTOCOL_PINCTRL = 0x19,
>+ SCMI_PROTOCOL_LAST = 0xff,
The enum says this is std protocol, but 0x80~0xff is for vendor extension.
It might confuse others to keep "SCMI_PROTOCOL_LAST" here.
> };
>
>+#define SCMI_MAX_PROTOCOLS (SCMI_PROTOCOL_LAST + 1)
How about "#define SCMI_MAX_PROTOCOLS 256" as the line you removed in notify.c.
Regards
Peng
>+
> enum scmi_system_events {
> SCMI_SYSTEM_SHUTDOWN,
> SCMI_SYSTEM_COLDRESET,
>--
>2.47.0
>
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC PATCH 0/7] Introduce SCMI Telemetry support
2025-06-20 19:28 [RFC PATCH 0/7] Introduce SCMI Telemetry support Cristian Marussi
` (6 preceding siblings ...)
2025-06-20 19:28 ` [RFC PATCH 7/7] firmware: arm_scmi: Use new Telemetry traces Cristian Marussi
@ 2025-06-24 10:22 ` Dhruva Gole
2025-06-25 14:53 ` Cristian Marussi
7 siblings, 1 reply; 21+ messages in thread
From: Dhruva Gole @ 2025-06-24 10:22 UTC (permalink / raw)
To: Cristian Marussi
Cc: linux-kernel, linux-arm-kernel, arm-scmi, sudeep.holla,
james.quinlan, f.fainelli, vincent.guittot, etienne.carriere,
peng.fan, michal.simek, quic_sibis, dan.carpenter,
souvik.chakravarty
Hey Cristian,
On Jun 20, 2025 at 20:28:06 +0100, Cristian Marussi wrote:
> 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 read them back using the collection method that is
> deeemd more suitable for the usecase at hand.
>
> Without delving into the gory details of the whole SCMI Telemetry protocol
> let's just say that the SCMI platform firmware advertises a number of
> Telemetry Data Events, each one identified by a 32bit unique ID, and a user
> can read back at will the associated data value in a number of ways.
>
> Anyway, the set of well-known architected DE IDs defined by the spec is
> limited to a dozen IDs, which means that the vast majority of DE IDs are
> customizable per-platform: as a consequence the same ID, say '0x1234',
> could represent completely different things on different systems.
>
> Data Event IDs definitions and their semantic are supposed to be described
> using some sort of JSON-like description file consumed by a userspace tool,
> which would be finally in charge of making sense of the exact meaning of
> the set of DEs specifically defined as available on a specific platform.
>
> IOW, in turn, this means that even though the DEs enumerated via SCMI come
> with some sort of topological and qualitative description (like unit of
> measurements), kernel-wise we CANNOT be sure of "what is what" without
> being fed-back some sort of information about the DEs semantic by the afore
> mentioned userspace tool.
>
> For these reasons, currently this series does NOT attempt to register
> any of the discovered DEs with any of the usual in-kernel subsystems (like
> HWMON, IIO, POWERCAP,PERF etc), simply because we cannot be sure if a DE is
> suitable or not for a given subsystem.
> This also means there are NO in-kernel user of these Telemetry data, as of
> now.
>
> So, while we do not exclude, for the future, to feed/register some of the
> discovered DEs to 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 users.
>
> As of now, really, this series explores 2 main alternative
> userspace APIs:
>
> 1. a SysFS based human-readable API tree
>
> This API present the discovered DEs and DEs-groups rooted under a
> structrure like this:
>
> /sys/class/scmi_telemetry/scmi_tlm_0/
> |-- all_des_enable
> |-- all_des_tstamp_enable
> |-- available_update_intervals_ms
> |-- current_update_interval_ms
> |-- de_implementation_version
> |-- des
> | |-- 0x0000
> | |-- 0x0016
> | |-- 0x1010
> | |-- 0xA000
> | |-- 0xA001
> | |-- 0xA002
> | |-- 0xA005
> | |-- 0xA007
> | |-- 0xA008
> | |-- 0xA00A
> | |-- 0xA00B
> | |-- 0xA00C
> | `-- 0xA010
> |-- 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 oe rest the whole stack
> - read all the enabled DEs in a buffer one-per-line
> <DE_ID> <TIMESTAMP> <DATA_VALUE>
>
> with each DE in turn is represented by a subtree like:
>
> scmi_tlm_0/des/0xA001/
> |-- compo_instance_id
> |-- compo_type
> |-- enable
> |-- instance_id
> |-- 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/value as in:
> <TIMESTAMP> <DATA_VALUE>
>
> then for each discovered group of DEs:
>
> scmi_tlm_0/groups/0/
> |-- available_update_intervals_ms
> |-- composing_des
> |-- current_update_interval_ms
> |-- des_bulk_read
> |-- des_single_sample_read
> |-- enable
> |-- intervals_discrete
> `-- tstamp_enable
>
> you can find the knobs to:
>
> - enable/disable the group as a whole
> - lookup group composition
> - set a per-group update interval (if supported)
> - read all the enabled DEs in a buffer one-per-line
> <DE_ID> <TIMESTAMP> <DATA_VALUE>
>
> The problem with this, beside being based on SysFS, is that while it is
> easily accessible and human-readable/scriptable does not scale well when
> the number of DEs ramps up...
>
> 2. an alternative and surely more performant API based on chardev file_ops
> and IOCTLs as described fully in:
>
> include/uapi/linux/scmi.h
>
> This, in a nutshell, creates one char-device /dec/scmi_tlm_0 for-each
> SCMI Telemetry instance found on the system and then:
>
> - uses some IOCTLs to configure a set of properties equivalent to the
> ones above in SysFS
> - uses some other IOCTLs for direct access to data in binary format
> - uses a .read file_operations to read back a human readable buffer
> containing all the enabled DEs using the same format as above
> <DE_ID> <TIMESTAMP> <DATA_VALUE>
> - (TBD) uses .mmap file_operation to allow for the raw unfiltered access
> to the SCMI Telemetry binary data as provided by the platform
>
> This initial RFC aims at first to explore and experiment to find the best
> possible userspace API (or mix of APIs) that can provide simplicity of use
> while also ensuring high performance from the user-space point of view.
I think the IOCTL based API and then a userspace tool that can use these
sounds good for now.
>
> IOW, nothing is set in stone as of now (clearly) some of the alternative
> options going ahead are:
>
> A. shrinking the gigantic SysFS above to keep only a few of those knobs
> while keeping and extending the chardev API
>
> B. keeping the gigantic FS for readability, but moving to a real
> standalone Telemetry-FS to overcome the limitations/constraints of
> SysFS, while keeping the chardev/IOCTL API for performance
> (not sure anyway the gigantic FS would be acceptable or makes sense
> anyway)
>
> C. keeping the gigantic FS but move it to debugfs so as to provide it
> only for test/debug/devel, while keeping only the chardev/IOCTLs as
> the production interface
As for this series, I would support the motion to move this to debugFS.
Similar to how we have /sys/kernel/debug/scmi/0/raw ...
I think grouping telemetry too under the same debug/ interface makes more
sense to me.
>
> ... moreover we could also additionally:
>
> D. generalize enough one of the above choices to make it abstract enough
> that other non-SCMI based telemetry can plug into some sort of geenric
> Telemetry subsystem
To my knowledge, I don't see that many users of firmware based telemetry similar to how
SCMI telemetry is being proposed. So maybe at the moment a whole new
telemetry subsystem might be an overkill.
>
> E. explore completely different APIs to userspace (netlink ?)
>
> F. additionally serve some of the DEs in some existent Kernel subsystem
> (like HWMON/IIO/PERF...) under the constraint discussed above (i.e.
> userspace has to tell me which DEs can fit into which subsys)
Perhaps in the future...
As a user, having used hwmon in the past, and also looking at the SCMI spec example
of capturing the output of a sensor which measures the temperature of a PE
Here's some points that support that:
* HWMON is a well-established interface for exposing sensor data (temperature,
voltage, current, power, etc.) to userspace via sysfs.
* Many userspace tools (e.g., lm-sensors, monitoring dashboards) already
understand HWMON.
* Well-known/architected SCMI DEs (like temperature, voltage, power)
directly map to HWMON sensor types.
However I can see that we may hit a limitation with that with the amount
of flexibility in SCMI telemetry, it may not always fit well in hwmon.
But, I think we can still leverage hwmon for telemetry related to power/
sensor related info.
The question about how do we differentiate between the above subsystems
is still open. Do we expect telemetry to purely come from the firmware
once the kernel is booted up already and is limited in the scope of what
it knows about the system its running on?
Or, can we somehow use DT to specify the subsystem we are interested in
based on the telemetry "number" and some compatible?
>
> NOTE THAT, this latter solution CANNOT be the only solution, because
> all of the above subsystem (beside PERF) expose a SysFS-based userspace
> interface (AFAIK), so, using their standard well-known interfaces WON'T
> solve the performance and scalability problem we have in our SysFS.
>
> Beside all of the above, the specification is still in ALPHA_0 and some
> features are still NOT supported by this series...
>
> ...and of course any form of documentation is still missing :D
>
> Based on V6.16-rc2.
>
> Any feedback welcome,
>
> For whoever had the gut to read till here :P ...
Hehe.. somehow managed to read it all :P
>
> Thanks,
> Cristian
>
> [0]: https://developer.arm.com/documentation/den0056/f/?lang=en
>
>
> Cristian Marussi (7):
> firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value
> firmware: arm_scmi: Allow protocols to register for notifications
> firmware: arm_scmi: Add Telemetry protocol support
> firmware: arm_scmi: Add System Telemetry driver
> firmware: arm_scmi: Add System Telemetry chardev/ioctls API
> include: trace: Add Telemetry trace events
> firmware: arm_scmi: Use new Telemetry traces
>
> drivers/firmware/arm_scmi/Kconfig | 10 +
> drivers/firmware/arm_scmi/Makefile | 3 +-
> drivers/firmware/arm_scmi/common.h | 4 +
> drivers/firmware/arm_scmi/driver.c | 14 +
> drivers/firmware/arm_scmi/notify.c | 31 +-
> drivers/firmware/arm_scmi/notify.h | 8 +-
> drivers/firmware/arm_scmi/protocols.h | 9 +
> .../firmware/arm_scmi/scmi_system_telemetry.c | 1459 ++++++++++++++
> drivers/firmware/arm_scmi/telemetry.c | 1744 +++++++++++++++++
> include/linux/scmi_protocol.h | 201 +-
> include/trace/events/scmi.h | 48 +-
> include/uapi/linux/scmi.h | 253 +++
> 12 files changed, 3769 insertions(+), 15 deletions(-)
> 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
>
> --
> 2.47.0
>
--
Best regards,
Dhruva Gole
Texas Instruments Incorporated
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC PATCH 1/7] firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value
2025-06-24 3:13 ` Peng Fan
@ 2025-06-25 13:59 ` Cristian Marussi
0 siblings, 0 replies; 21+ messages in thread
From: Cristian Marussi @ 2025-06-25 13:59 UTC (permalink / raw)
To: Peng Fan
Cc: Cristian Marussi, linux-kernel, linux-arm-kernel, arm-scmi,
sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, michal.simek, quic_sibis, dan.carpenter, d-gole,
souvik.chakravarty
On Tue, Jun 24, 2025 at 11:13:51AM +0800, Peng Fan wrote:
> On Fri, Jun 20, 2025 at 08:28:07PM +0100, Cristian Marussi wrote:
> >Add a common definition of SCMI_MAX_PROTOCOLS and use it all over the
> >SCMI stack.
> >
Hi Peng,
thanks for having a look.
> >Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
> >---
> > drivers/firmware/arm_scmi/notify.c | 4 +---
> > include/linux/scmi_protocol.h | 3 +++
> > 2 files changed, 4 insertions(+), 3 deletions(-)
> >
> >diff --git a/drivers/firmware/arm_scmi/notify.c b/drivers/firmware/arm_scmi/notify.c
> >index e160ecb22948..27a53a6729dd 100644
> >--- a/drivers/firmware/arm_scmi/notify.c
> >+++ b/drivers/firmware/arm_scmi/notify.c
> >@@ -94,8 +94,6 @@
> > #include "common.h"
> > #include "notify.h"
> >
> >-#define SCMI_MAX_PROTO 256
> >-
> > #define PROTO_ID_MASK GENMASK(31, 24)
> > #define EVT_ID_MASK GENMASK(23, 16)
> > #define SRC_ID_MASK GENMASK(15, 0)
> >@@ -1652,7 +1650,7 @@ int scmi_notification_init(struct scmi_handle *handle)
> > ni->gid = gid;
> > ni->handle = handle;
> >
> >- ni->registered_protocols = devm_kcalloc(handle->dev, SCMI_MAX_PROTO,
> >+ ni->registered_protocols = devm_kcalloc(handle->dev, SCMI_MAX_PROTOCOLS,
> > sizeof(char *), GFP_KERNEL);
> > if (!ni->registered_protocols)
> > goto err;
> >diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
> >index 688466a0e816..6f8d36e1f8fc 100644
> >--- a/include/linux/scmi_protocol.h
> >+++ b/include/linux/scmi_protocol.h
> >@@ -926,8 +926,11 @@ enum scmi_std_protocol {
> > SCMI_PROTOCOL_VOLTAGE = 0x17,
> > SCMI_PROTOCOL_POWERCAP = 0x18,
> > SCMI_PROTOCOL_PINCTRL = 0x19,
> >+ SCMI_PROTOCOL_LAST = 0xff,
>
> The enum says this is std protocol, but 0x80~0xff is for vendor extension.
> It might confuse others to keep "SCMI_PROTOCOL_LAST" here.
>
Yes indeed. This patch was lingering around since ages in my trees and
it is probably to review in itself.
> > };
> >
> >+#define SCMI_MAX_PROTOCOLS (SCMI_PROTOCOL_LAST + 1)
>
> How about "#define SCMI_MAX_PROTOCOLS 256" as the line you removed in notify.c.
>
Yes, probably the easiest thing to do.
Thanks,
Cristian
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC PATCH 3/7] firmware: arm_scmi: Add Telemetry protocol support
2025-06-20 20:46 ` Dan Carpenter
@ 2025-06-25 14:02 ` Cristian Marussi
2025-06-25 14:04 ` Cristian Marussi
1 sibling, 0 replies; 21+ messages in thread
From: Cristian Marussi @ 2025-06-25 14:02 UTC (permalink / raw)
To: Dan Carpenter
Cc: Cristian Marussi, linux-kernel, linux-arm-kernel, arm-scmi,
sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis, d-gole,
souvik.chakravarty
On Fri, Jun 20, 2025 at 11:46:15PM +0300, Dan Carpenter wrote:
> On Fri, Jun 20, 2025 at 08:28:09PM +0100, Cristian Marussi wrote:
> > +static int
> > +scmi_telemetry_protocol_attributes_get(const struct scmi_protocol_handle *ph,
> > + struct telemetry_info *ti)
> > +{
Hi Dan,
thanks for having a look.
> > + int ret;
> > + struct scmi_xfer *t;
> > + struct scmi_msg_resp_telemetry_protocol_attributes *resp;
> > +
> > + 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.num_de = le32_to_cpu(resp->de_num);
> > + ti->info.num_groups = le32_to_cpu(resp->groups_num);
> > + for (int i = 0; i < SCMI_TLM_MAX_DWORD; i++)
> > + ti->info.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));
> > + /* Allocate DEs descriptors */
> > + ti->info.des = devm_kcalloc(ph->dev, ti->info.num_de,
> > + sizeof(*ti->info.des), GFP_KERNEL);
> > + if (!ti->info.des)
> > + ret = -ENOMEM;
> > +
> > + /* Allocate DE GROUPS descriptors */
> > + ti->info.des_groups = devm_kcalloc(ph->dev, ti->info.num_groups,
> > + sizeof(*ti->info.des_groups),
> > + GFP_KERNEL);
> > + if (!ti->info.des_groups)
> > + ret = -ENOMEM;
>
> It the allocation fails we need to jump to the ->xfer_put
>
> > +
> > + for (int i = 0; i < ti->info.num_groups; i++)
> > + ti->info.des_groups[i].id = i;
>
> otherwise it leads to a NULL dereference.
>
Indeed...I will fix.
There are a few more of this bugs around probably being an RFC...bots also are
complaining a lot at this series indeed :D
Thanks,
Cristian
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC PATCH 3/7] firmware: arm_scmi: Add Telemetry protocol support
2025-06-20 20:46 ` Dan Carpenter
2025-06-25 14:02 ` Cristian Marussi
@ 2025-06-25 14:04 ` Cristian Marussi
1 sibling, 0 replies; 21+ messages in thread
From: Cristian Marussi @ 2025-06-25 14:04 UTC (permalink / raw)
To: Dan Carpenter
Cc: Cristian Marussi, linux-kernel, linux-arm-kernel, arm-scmi,
sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis, d-gole,
souvik.chakravarty
On Fri, Jun 20, 2025 at 11:46:15PM +0300, Dan Carpenter wrote:
> On Fri, Jun 20, 2025 at 08:28:09PM +0100, Cristian Marussi wrote:
> > +static int
> > +scmi_telemetry_protocol_attributes_get(const struct scmi_protocol_handle *ph,
> > + struct telemetry_info *ti)
> > +{
... and also...
> > + int ret;
> > + struct scmi_xfer *t;
> > + struct scmi_msg_resp_telemetry_protocol_attributes *resp;
> > +
> > + 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.num_de = le32_to_cpu(resp->de_num);
> > + ti->info.num_groups = le32_to_cpu(resp->groups_num);
> > + for (int i = 0; i < SCMI_TLM_MAX_DWORD; i++)
> > + ti->info.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));
> > + /* Allocate DEs descriptors */
> > + ti->info.des = devm_kcalloc(ph->dev, ti->info.num_de,
> > + sizeof(*ti->info.des), GFP_KERNEL);
> > + if (!ti->info.des)
> > + ret = -ENOMEM;
> > +
> > + /* Allocate DE GROUPS descriptors */
> > + ti->info.des_groups = devm_kcalloc(ph->dev, ti->info.num_groups,
> > + sizeof(*ti->info.des_groups),
> > + GFP_KERNEL);
> > + if (!ti->info.des_groups)
> > + ret = -ENOMEM;
>
> It the allocation fails we need to jump to the ->xfer_put
>
> > +
> > + for (int i = 0; i < ti->info.num_groups; i++)
> > + ti->info.des_groups[i].id = i;
>
> otherwise it leads to a NULL dereference.
>
> > + }
> > +
> > + ph->xops->xfer_put(ph, t);
> > +
> > + return ret;
> > +}
>
> [ snip ]
>
> > +static int iter_shmti_process_response(const struct scmi_protocol_handle *ph,
> > + const void *response,
> > + struct scmi_iterator_state *st,
> > + void *priv)
> > +{
> > + const struct scmi_msg_resp_telemetry_shmti_list *r = response;
> > + struct telemetry_info *ti = priv;
> > + struct telemetry_shmti *shmti;
> > + const struct scmi_shmti_desc *desc;
> > + void __iomem *addr;
> > + u64 phys_addr;
> > + u32 len;
> > +
> > + desc = &r->desc[st->loop_idx];
> > + shmti = &ti->shmti[st->desc_index + st->loop_idx];
> > +
> > + shmti->id = le32_to_cpu(desc->id);
> > + phys_addr = le32_to_cpu(desc->addr_low);
> > + phys_addr |= (u64)le32_to_cpu(desc->addr_high) << 32;
> > +
> > + len = le32_to_cpu(desc->length);
> > + addr = devm_ioremap(ph->dev, phys_addr, len);
> > + if (!addr)
> > + return -EADDRNOTAVAIL;
> > +
> > + shmti->base = addr;
> > + shmti->len = len;
>
> There is some code later which assumes ->len is at least
> TDCF_EPLG_SZ and de->data_sz. This is probably where we should
> check if (len < TDCF_EPLG_SZ) return -EINVAL; and the de->data_sz
> would be checked later.
I will add proper checks
Thanks,
Cristian
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC PATCH 3/7] firmware: arm_scmi: Add Telemetry protocol support
2025-06-20 21:01 ` Dan Carpenter
@ 2025-06-25 14:04 ` Cristian Marussi
0 siblings, 0 replies; 21+ messages in thread
From: Cristian Marussi @ 2025-06-25 14:04 UTC (permalink / raw)
To: Dan Carpenter
Cc: Cristian Marussi, linux-kernel, linux-arm-kernel, arm-scmi,
sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis, d-gole,
souvik.chakravarty
On Sat, Jun 21, 2025 at 12:01:23AM +0300, Dan Carpenter wrote:
> On Fri, Jun 20, 2025 at 08:28:09PM +0100, Cristian Marussi wrote:
> > + /* Build compsing DES string */
> > + for (int i = 0; i < ti->info.num_groups; i++) {
> > + struct scmi_telemetry_group *grp = &ti->info.des_groups[i];
> > + char *buf = grp->des_str;
> > + size_t bufsize = grp->des_str_sz;
> > +
> > + for (int j = 0; j < grp->num_de; j++) {
> > + char term = j != (grp->num_de - 1) ? ' ' : '\0';
> > + int len;
> > +
> > + len = snprintf(buf, bufsize, "0x%04X%c",
> > + ti->info.des[grp->des[j]]->id, term);
>
> Please use scnprintf() btw. Otherwise if there is an overflow the
> next iteration will complain that bufsize is negative.
Yes, I will review all of this.
Thanks,
Cristian
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC PATCH 4/7] firmware: arm_scmi: Add System Telemetry driver
2025-06-20 21:27 ` Dan Carpenter
@ 2025-06-25 14:11 ` Cristian Marussi
0 siblings, 0 replies; 21+ messages in thread
From: Cristian Marussi @ 2025-06-25 14:11 UTC (permalink / raw)
To: Dan Carpenter
Cc: Cristian Marussi, linux-kernel, linux-arm-kernel, arm-scmi,
sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis, d-gole,
souvik.chakravarty
On Sat, Jun 21, 2025 at 12:27:03AM +0300, Dan Carpenter wrote:
> On Fri, Jun 20, 2025 at 08:28:10PM +0100, Cristian Marussi wrote:
> > +//TODO Review available interval show
> > +#define BUF_SZ 1024
> > +static inline ssize_t
> > +__available_update_show(char *buf,
> > + const struct scmi_telemetry_update_interval *intervals)
> > +{
> > + int len = 0, num_intervals = intervals->num;
> > + char available[BUF_SZ];
> > +
> > + for (int i = 0; i < num_intervals; i++) {
> > + len += scnprintf(available + len, BUF_SZ - len, "%u ",
> > + intervals->update_intervals[i]);
> > + }
> > +
> > + available[len - 1] = '\0';
>
> No need. scnprintf() will already have put a NUL terminator there.
> Unless num_intervals <= 0 in which case this will corrupt memory.
>
Yes, all of this routine has to be really reworked to avoid on the stack
allocation too...
> > +
> > + return sysfs_emit(buf, "%s\n", available);
> > +}
>
> [ snip ]
>
> > +static int scmi_telemetry_groups_initialize(struct device *dev,
> > + struct scmi_tlm_instance *ti)
> > +{
> > + int ret;
> > +
> > + if (ti->info->num_groups == 0)
> > + return 0;
> > +
> > + ret = scmi_telemetry_dev_register(&ti->groups_dev, &ti->dev, "groups");
> > + if (ret)
> > + return ret;
> > +
> > + for (int i = 0; i < ti->info->num_groups; i++) {
> > + const struct scmi_telemetry_group *grp = &ti->info->des_groups[i];
> > + struct scmi_tlm_grp_dev *gdev;
> > + char name[16];
> > +
> > + gdev = devm_kzalloc(dev, sizeof(*gdev), GFP_KERNEL);
> > + if (!gdev) {
> > + ret = -ENOMEM;
> > + goto err;
> > + }
> > +
> > + gdev->tsp = ti->tsp;
> > + gdev->grp = grp;
> > + gdev->dev.groups = scmi_grp_groups;
> > +
> > + snprintf(name, 8, "%d", grp->id);
>
> s/8/sizeof(name)/?
indeed...
>
> > + ret = scmi_telemetry_dev_register(&gdev->dev,
> > + &ti->groups_dev, name);
> > + if (ret)
> > + goto err;
> > +
> > + if (ti->info->per_group_config_support) {
> > + sysfs_add_file_to_group(&gdev->dev.kobj,
> > + &dev_attr_grp_current_update.attr,
> > + NULL);
> > + sysfs_add_file_to_group(&gdev->dev.kobj,
> > + &dev_attr_grp_intervals_discrete.attr,
> > + NULL);
> > + sysfs_add_file_to_group(&gdev->dev.kobj,
> > + &dev_attr_grp_available_intervals.attr,
> > + NULL);
> > + }
> > + }
> > +
> > + dev_info(dev, "Found %d Telemetry GROUPS resources.\n",
> > + ti->info->num_groups);
> > +
> > + return 0;
> > +
> > +err:
> > + scmi_telemetry_dev_unregister(&ti->groups_dev);
> > +
> > + return ret;
> > +}
> > +
> > +static int scmi_telemetry_des_initialize(struct device *dev,
> > + struct scmi_tlm_instance *ti)
> > +{
> > + int ret;
> > +
> > + ret = scmi_telemetry_dev_register(&ti->des_dev, &ti->dev, "des");
> > + if (ret)
> > + return ret;
> > +
> > + for (int i = 0; i < ti->info->num_de; i++) {
> > + const struct scmi_telemetry_de *de = ti->info->des[i];
> > + struct scmi_tlm_de_dev *tdev;
> > + char name[16];
> > +
> > + tdev = devm_kzalloc(dev, sizeof(*tdev), GFP_KERNEL);
> > + if (!tdev) {
> > + ret = -ENOMEM;
> > + goto err;
> > + }
> > +
> > + tdev->tsp = ti->tsp;
> > + tdev->de = de;
> > + tdev->dev.groups = scmi_des_groups;
> > +
> > + /*XXX What about of ID/name digits-length used ? */
> > + snprintf(name, 8, "0x%04X", de->id);
>
> s/8/sizeof(name)/?
>
yes.
> regards,
> dan carpenter
>
Thanks,
Cristian
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC PATCH 5/7] firmware: arm_scmi: Add System Telemetry chardev/ioctls API
2025-06-20 21:51 ` Dan Carpenter
@ 2025-06-25 14:14 ` Cristian Marussi
0 siblings, 0 replies; 21+ messages in thread
From: Cristian Marussi @ 2025-06-25 14:14 UTC (permalink / raw)
To: Dan Carpenter
Cc: Cristian Marussi, linux-kernel, linux-arm-kernel, arm-scmi,
sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis, d-gole,
souvik.chakravarty
On Sat, Jun 21, 2025 at 12:51:12AM +0300, Dan Carpenter wrote:
> On Fri, Jun 20, 2025 at 08:28:11PM +0100, Cristian Marussi wrote:
> > +static long scmi_tlm_des_read_ioctl(struct scmi_tlm_instance *ti,
> > + unsigned long arg, bool single)
> > +{
> > + void * __user uptr = (void * __user)arg;
> > + struct scmi_tlm_setup *tsp = ti->tsp;
> > + struct scmi_tlm_bulk_read bulk, *bulk_ptr;
> > + int ret;
> > +
> > + if (copy_from_user(&bulk, uptr, sizeof(bulk)))
> > + return -EFAULT;
> > +
> > + bulk_ptr = kzalloc(sizeof(*bulk_ptr) +
> > + bulk.num_samples * sizeof(bulk_ptr->samples[0]),
>
> This should be struct_size(bulk_ptr, samples, bulk.num_samples) to
> avoid an integer overflow on 32bit systems.
>
I will fix.
Thanks,
Cristian
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC PATCH 0/7] Introduce SCMI Telemetry support
2025-06-24 10:22 ` [RFC PATCH 0/7] Introduce SCMI Telemetry support Dhruva Gole
@ 2025-06-25 14:53 ` Cristian Marussi
0 siblings, 0 replies; 21+ messages in thread
From: Cristian Marussi @ 2025-06-25 14:53 UTC (permalink / raw)
To: Dhruva Gole
Cc: Cristian Marussi, linux-kernel, linux-arm-kernel, arm-scmi,
sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, quic_sibis,
dan.carpenter, souvik.chakravarty
On Tue, Jun 24, 2025 at 03:52:33PM +0530, Dhruva Gole wrote:
> Hey Cristian,
>
Hi Dhruva,
thanks for leaving your feedback.
> On Jun 20, 2025 at 20:28:06 +0100, Cristian Marussi wrote:
> > Hi all,
> >
[snip]
> >
> > 2. an alternative and surely more performant API based on chardev file_ops
> > and IOCTLs as described fully in:
> >
> > include/uapi/linux/scmi.h
> >
> > This, in a nutshell, creates one char-device /dec/scmi_tlm_0 for-each
> > SCMI Telemetry instance found on the system and then:
> >
> > - uses some IOCTLs to configure a set of properties equivalent to the
> > ones above in SysFS
> > - uses some other IOCTLs for direct access to data in binary format
> > - uses a .read file_operations to read back a human readable buffer
> > containing all the enabled DEs using the same format as above
> > <DE_ID> <TIMESTAMP> <DATA_VALUE>
> > - (TBD) uses .mmap file_operation to allow for the raw unfiltered access
> > to the SCMI Telemetry binary data as provided by the platform
> >
> > This initial RFC aims at first to explore and experiment to find the best
> > possible userspace API (or mix of APIs) that can provide simplicity of use
> > while also ensuring high performance from the user-space point of view.
>
> I think the IOCTL based API and then a userspace tool that can use these
> sounds good for now.
>
Definitely better for performance...
> >
> > IOW, nothing is set in stone as of now (clearly) some of the alternative
> > options going ahead are:
> >
> > A. shrinking the gigantic SysFS above to keep only a few of those knobs
> > while keeping and extending the chardev API
> >
> > B. keeping the gigantic FS for readability, but moving to a real
> > standalone Telemetry-FS to overcome the limitations/constraints of
> > SysFS, while keeping the chardev/IOCTL API for performance
> > (not sure anyway the gigantic FS would be acceptable or makes sense
> > anyway)
> >
> > C. keeping the gigantic FS but move it to debugfs so as to provide it
> > only for test/debug/devel, while keeping only the chardev/IOCTLs as
> > the production interface
>
>
> As for this series, I would support the motion to move this to debugFS.
> Similar to how we have /sys/kernel/debug/scmi/0/raw ...
> I think grouping telemetry too under the same debug/ interface makes more
> sense to me.
>
... indeed all of this was initially prototyped under debugfs in
/sys/kernel/debug/scmi/0/telemetry
The thing is, of course, anything there is doomed to be axed in a
production system....so you would loose the human-readable and easily
scriptable API...while moving to sysfs is probably less of acceptable
for a number of reasons (like multi value files...)
... so for these reasons I would also be tempted by a variation of B:
- a standlone full-fledged filesystem to overcome sysfs limitations,
but stripped down a bit to avoid the full-depth of the above sysfs
tree (say dropping the DEs dedicated subdirectories), and augmented
with some dedicated file and related IOCTL/file_operations based binary
interface as described in the current uapi, so as to avoid additional
dedicated char-devices to cope with
> >
> > ... moreover we could also additionally:
> >
> > D. generalize enough one of the above choices to make it abstract enough
> > that other non-SCMI based telemetry can plug into some sort of geenric
> > Telemetry subsystem
>
> To my knowledge, I don't see that many users of firmware based telemetry similar to how
> SCMI telemetry is being proposed. So maybe at the moment a whole new
> telemetry subsystem might be an overkill.
>
Yes...and indee this would be a nice to have, BUT at the moment being SCMI
the one and only user of this new subsystem, I would NOT even have
enough non-SCMI use-cases to look at in order to generalize and abstract
some common features...
> >
> > E. explore completely different APIs to userspace (netlink ?)
> >
> > F. additionally serve some of the DEs in some existent Kernel subsystem
> > (like HWMON/IIO/PERF...) under the constraint discussed above (i.e.
> > userspace has to tell me which DEs can fit into which subsys)
>
> Perhaps in the future...
>
> As a user, having used hwmon in the past, and also looking at the SCMI spec example
> of capturing the output of a sensor which measures the temperature of a PE
>
> Here's some points that support that:
>
> * HWMON is a well-established interface for exposing sensor data (temperature,
> voltage, current, power, etc.) to userspace via sysfs.
>
> * Many userspace tools (e.g., lm-sensors, monitoring dashboards) already
> understand HWMON.
>
Absolutely, but, just to be clear, here I am talking about adding some of
the discovered Telemetry DEs also as additional sensor devices to HWMON,
BUT any existing sensor discovered via the SCMI Sensor protocol (0x15)
which is currently fed into HWMON or IIO will STILL be handled by those
subsystems....
> * Well-known/architected SCMI DEs (like temperature, voltage, power)
> directly map to HWMON sensor types.
>
...well the thing is, while you can be sure that a DE is a temperature
you cannot automatically be sure of WHAT it is really measuring (name is
also optional) OR if it fits (or you want it to fit) into HWMON or IIO
subsystems...IOW there are NO well-known/architected DE beside a few
general events in the 0xA000-0xA013 range...
> However I can see that we may hit a limitation with that with the amount
> of flexibility in SCMI telemetry, it may not always fit well in hwmon.
>
> But, I think we can still leverage hwmon for telemetry related to power/
> sensor related info.
>
Absolutely, but as of now I think Kernel needs some sort of feedback/config
info from userspace to identify WHAT is fine to be registered with HWMON/IIO/PERF
etc etc...since userspace is where the precise semantic decription of what-the-hell
0x1234 ID represents on the specific platform...
(so that is the reason for still NOT having this mechanism in this series...)
> The question about how do we differentiate between the above subsystems
> is still open. Do we expect telemetry to purely come from the firmware
> once the kernel is booted up already and is limited in the scope of what
> it knows about the system its running on?
> Or, can we somehow use DT to specify the subsystem we are interested in
> based on the telemetry "number" and some compatible?
>
I dont think DT is a viable option for this kind of descriptions...I was
more thinking about an extension of (whatever we choose as) API which
a userspace app can use to select which DEs is approriate for a specific
kernel subsystem (f anything)
> >
> > NOTE THAT, this latter solution CANNOT be the only solution, because
> > all of the above subsystem (beside PERF) expose a SysFS-based userspace
> > interface (AFAIK), so, using their standard well-known interfaces WON'T
> > solve the performance and scalability problem we have in our SysFS.
> >
> > Beside all of the above, the specification is still in ALPHA_0 and some
> > features are still NOT supported by this series...
> >
> > ...and of course any form of documentation is still missing :D
> >
> > Based on V6.16-rc2.
> >
> > Any feedback welcome,
> >
> > For whoever had the gut to read till here :P ...
>
> Hehe.. somehow managed to read it all :P
>
...congratulations ! (I have no prizes to distribute, though :P)
Thanks
Cristian,
^ permalink raw reply [flat|nested] 21+ messages in thread
end of thread, other threads:[~2025-06-25 18:29 UTC | newest]
Thread overview: 21+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-20 19:28 [RFC PATCH 0/7] Introduce SCMI Telemetry support Cristian Marussi
2025-06-20 19:28 ` [RFC PATCH 1/7] firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value Cristian Marussi
2025-06-24 3:13 ` Peng Fan
2025-06-25 13:59 ` Cristian Marussi
2025-06-20 19:28 ` [RFC PATCH 2/7] firmware: arm_scmi: Allow protocols to register for notifications Cristian Marussi
2025-06-20 19:28 ` [RFC PATCH 3/7] firmware: arm_scmi: Add Telemetry protocol support Cristian Marussi
2025-06-20 20:46 ` Dan Carpenter
2025-06-25 14:02 ` Cristian Marussi
2025-06-25 14:04 ` Cristian Marussi
2025-06-20 21:01 ` Dan Carpenter
2025-06-25 14:04 ` Cristian Marussi
2025-06-20 19:28 ` [RFC PATCH 4/7] firmware: arm_scmi: Add System Telemetry driver Cristian Marussi
2025-06-20 21:27 ` Dan Carpenter
2025-06-25 14:11 ` Cristian Marussi
2025-06-20 19:28 ` [RFC PATCH 5/7] firmware: arm_scmi: Add System Telemetry chardev/ioctls API Cristian Marussi
2025-06-20 21:51 ` Dan Carpenter
2025-06-25 14:14 ` Cristian Marussi
2025-06-20 19:28 ` [RFC PATCH 6/7] include: trace: Add Telemetry trace events Cristian Marussi
2025-06-20 19:28 ` [RFC PATCH 7/7] firmware: arm_scmi: Use new Telemetry traces Cristian Marussi
2025-06-24 10:22 ` [RFC PATCH 0/7] Introduce SCMI Telemetry support Dhruva Gole
2025-06-25 14:53 ` Cristian Marussi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).