Add basic support for SCMI V4.0 Telemetry protocol including SHMTI,
FastChannels, Notifications and Single Sample Reads collection methods.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v1 --> v2
- Add proper ioread accessors for TDCF areas
- Rework resource allocation logic and lifecycle
- Introduce new resources accessors and res_get() operation to
implement lazy enumeration
- Support boot-on telemetry:
+ Add DE_ENABLED_LIST cmd support
+ Add CONFIG_GET cmd support
+ Add TDCF_SCAN for best effort enumeration
+ Harden driver against out-of-spec FW with out of spec cmds
- Use FCs list
- Rework de_info_lookup to a moer general de_lookup
- Fixed reset to use CONFIG_GET and DE_ENABLED_LIST to gather initial
state
- Using sign_extend32 helper
- Added counted_by marker where appropriate
---
drivers/firmware/arm_scmi/Makefile | 2 +-
drivers/firmware/arm_scmi/driver.c | 2 +
drivers/firmware/arm_scmi/protocols.h | 1 +
drivers/firmware/arm_scmi/telemetry.c | 2671 +++++++++++++++++++++++++
include/linux/scmi_protocol.h | 188 +-
5 files changed, 2862 insertions(+), 2 deletions(-)
create mode 100644 drivers/firmware/arm_scmi/telemetry.c
diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
index 780cd62b2f78..fe55b7aa0707 100644
--- a/drivers/firmware/arm_scmi/Makefile
+++ b/drivers/firmware/arm_scmi/Makefile
@@ -8,7 +8,7 @@ scmi-driver-$(CONFIG_ARM_SCMI_RAW_MODE_SUPPORT) += raw_mode.o
scmi-transport-$(CONFIG_ARM_SCMI_HAVE_SHMEM) = shmem.o
scmi-transport-$(CONFIG_ARM_SCMI_HAVE_MSG) += msg.o
scmi-protocols-y := base.o clock.o perf.o power.o reset.o sensors.o system.o voltage.o powercap.o
-scmi-protocols-y += pinctrl.o
+scmi-protocols-y += pinctrl.o telemetry.o
scmi-module-objs := $(scmi-driver-y) $(scmi-protocols-y) $(scmi-transport-y)
obj-$(CONFIG_ARM_SCMI_PROTOCOL) += transports/
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index 1085c70ca457..dd5da75a19b0 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -3450,6 +3450,7 @@ static int __init scmi_driver_init(void)
scmi_system_register();
scmi_powercap_register();
scmi_pinctrl_register();
+ scmi_telemetry_register();
return platform_driver_register(&scmi_driver);
}
@@ -3468,6 +3469,7 @@ static void __exit scmi_driver_exit(void)
scmi_system_unregister();
scmi_powercap_unregister();
scmi_pinctrl_unregister();
+ scmi_telemetry_unregister();
platform_driver_unregister(&scmi_driver);
diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h
index afca1336267b..766b68a3084e 100644
--- a/drivers/firmware/arm_scmi/protocols.h
+++ b/drivers/firmware/arm_scmi/protocols.h
@@ -385,5 +385,6 @@ DECLARE_SCMI_REGISTER_UNREGISTER(sensors);
DECLARE_SCMI_REGISTER_UNREGISTER(voltage);
DECLARE_SCMI_REGISTER_UNREGISTER(system);
DECLARE_SCMI_REGISTER_UNREGISTER(powercap);
+DECLARE_SCMI_REGISTER_UNREGISTER(telemetry);
#endif /* _SCMI_PROTOCOLS_H */
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
new file mode 100644
index 000000000000..16bcdcdc1dc3
--- /dev/null
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -0,0 +1,2671 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * System Control and Management Interface (SCMI) Telemetry Protocol
+ *
+ * Copyright (C) 2026 ARM Ltd.
+ */
+
+#include <linux/atomic.h>
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/compiler_types.h>
+#include <linux/completion.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/refcount.h>
+#include <linux/slab.h>
+#include <linux/sprintf.h>
+#include <linux/string.h>
+#include <linux/xarray.h>
+
+#include "protocols.h"
+#include "notify.h"
+
+/* Updated only after ALL the mandatory features for that version are merged */
+#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x10000
+
+#define SCMI_TLM_TDCF_MAX_RETRIES 5
+
+enum scmi_telemetry_protocol_cmd {
+ TELEMETRY_LIST_SHMTI = 0x3,
+ TELEMETRY_DE_DESCRIPTION = 0x4,
+ TELEMETRY_LIST_UPDATE_INTERVALS = 0x5,
+ TELEMETRY_DE_CONFIGURE = 0x6,
+ TELEMETRY_DE_ENABLED_LIST = 0x7,
+ TELEMETRY_CONFIG_SET = 0x8,
+ TELEMETRY_READING_COMPLETE = TELEMETRY_CONFIG_SET,
+ TELEMETRY_CONFIG_GET = 0x9,
+ TELEMETRY_RESET = 0xA,
+};
+
+struct scmi_msg_resp_telemetry_protocol_attributes {
+ __le32 de_num;
+ __le32 groups_num;
+ __le32 de_implementation_rev_dword[SCMI_TLM_DE_IMPL_MAX_DWORDS];
+ __le32 attributes;
+#define SUPPORTS_SINGLE_READ(x) ((x) & BIT(31))
+#define SUPPORTS_CONTINUOS_UPDATE(x) ((x) & BIT(30))
+#define SUPPORTS_PER_GROUP_CONFIG(x) ((x) & BIT(18))
+#define SUPPORTS_RESET(x) ((x) & BIT(17))
+#define SUPPORTS_FC(x) ((x) & BIT(16))
+};
+
+struct scmi_telemetry_update_notify_payld {
+ __le32 agent_id;
+ __le32 status;
+ __le32 num_dwords;
+ __le32 array[] __counted_by(num_dwords);
+};
+
+struct scmi_shmti_desc {
+ __le32 id;
+ __le32 addr_low;
+ __le32 addr_high;
+ __le32 length;
+};
+
+struct scmi_msg_resp_telemetry_shmti_list {
+ __le32 num_shmti;
+ struct scmi_shmti_desc desc[] __counted_by(num_shmti);
+};
+
+struct de_desc_fc {
+ __le32 addr_low;
+ __le32 addr_high;
+ __le32 size;
+};
+
+struct scmi_de_desc {
+ __le32 id;
+ __le32 grp_id;
+ __le32 data_sz;
+ __le32 attr_1;
+#define IS_NAME_SUPPORTED(d) ((d)->attr_1 & BIT(31))
+#define IS_FC_SUPPORTED(d) ((d)->attr_1 & BIT(30))
+#define GET_DE_TYPE(d) (le32_get_bits((d)->attr_1, GENMASK(29, 22)))
+#define IS_PERSISTENT(d) ((d)->attr_1 & BIT(21))
+#define GET_DE_UNIT_EXP(d) \
+ ({ \
+ __u32 __signed_exp = \
+ le32_get_bits((d)->attr_1, GENMASK(20, 13)); \
+ \
+ sign_extend32(__signed_exp, 7); \
+ })
+
+#define GET_DE_UNIT(d) (le32_get_bits((d)->attr_1, GENMASK(12, 5)))
+
+#define GET_DE_TSTAMP_EXP(d) \
+ ({ \
+ __u32 __signed_exp = \
+ FIELD_GET(GENMASK(4, 1), (d)->attr_1); \
+ \
+ sign_extend32(__signed_exp, 3); \
+ })
+#define IS_TSTAMP_SUPPORTED(d) ((d)->attr_1 & BIT(0))
+ __le32 attr_2;
+#define GET_DE_INSTA_ID(d) (le32_get_bits((d)->attr_2, GENMASK(31, 24)))
+#define GET_COMPO_INSTA_ID(d) (le32_get_bits((d)->attr_2, GENMASK(23, 8)))
+#define GET_COMPO_TYPE(d) (le32_get_bits((d)->attr_2, GENMASK(7, 0)))
+ __le32 reserved;
+};
+
+struct scmi_msg_resp_telemetry_de_description {
+ __le32 num_desc;
+ struct scmi_de_desc desc[] __counted_by(num_desc);
+};
+
+struct scmi_msg_telemetry_update_intervals {
+ __le32 index;
+ __le32 group_identifier;
+#define ALL_DES_NO_GROUP 0x0
+#define SPECIFIC_GROUP_DES 0x1
+#define ALL_DES_ANY_GROUP 0x2
+ __le32 flags;
+};
+
+struct scmi_msg_resp_telemetry_update_intervals {
+ __le32 flags;
+#define INTERVALS_DISCRETE(x) (!((x) & BIT(12)))
+ __le32 intervals[];
+};
+
+struct scmi_msg_telemetry_de_enabled_list {
+ __le32 index;
+ __le32 flags;
+};
+
+struct scmi_enabled_de_desc {
+ __le32 id;
+ __le32 mode;
+};
+
+struct scmi_msg_resp_telemetry_de_enabled_list {
+ __le32 flags;
+ struct scmi_enabled_de_desc entry[];
+};
+
+struct scmi_msg_telemetry_de_configure {
+ __le32 id;
+ __le32 flags;
+#define DE_ENABLE_NO_TSTAMP BIT(0)
+#define DE_ENABLE_WTH_TSTAMP BIT(1)
+#define DE_DISABLE_ALL BIT(2)
+#define GROUP_SELECTOR BIT(3)
+#define EVENT_DE 0
+#define EVENT_GROUP 1
+#define DE_DISABLE_ONE 0x0
+};
+
+struct scmi_msg_resp_telemetry_de_configure {
+ __le32 shmti_id;
+#define IS_SHMTI_ID_VALID(x) ((x) != 0xFFFFFFFF)
+ __le32 tdcf_de_offset;
+};
+
+struct scmi_msg_telemetry_config_set {
+ __le32 grp_id;
+ __le32 control;
+#define TELEMETRY_ENABLE (BIT(0))
+
+#define TELEMETRY_MODE_SET(x) (FIELD_PREP(GENMASK(4, 1), (x)))
+#define TLM_ONDEMAND (0)
+#define TLM_NOTIFS (1)
+#define TLM_SINGLE (2)
+#define TELEMETRY_MODE_ONDEMAND TELEMETRY_MODE_SET(TLM_ONDEMAND)
+#define TELEMETRY_MODE_NOTIFS TELEMETRY_MODE_SET(TLM_NOTIFS)
+#define TELEMETRY_MODE_SINGLE TELEMETRY_MODE_SET(TLM_SINGLE)
+
+#define TLM_ORPHANS (0)
+#define TLM_GROUP (1)
+#define TLM_ALL (2)
+#define TELEMETRY_SET_SELECTOR(x) (FIELD_PREP(GENMASK(8, 5), (x)))
+#define TELEMETRY_SET_SELECTOR_ORPHANS TELEMETRY_SET_SELECTOR(TLM_ORPHANS)
+#define TELEMETRY_SET_SELECTOR_GROUP TELEMETRY_SET_SELECTOR(TLM_GROUP)
+#define TELEMETRY_SET_SELECTOR_ALL TELEMETRY_SET_SELECTOR(TLM_ALL)
+ __le32 sampling_rate;
+};
+
+struct scmi_msg_resp_telemetry_reading_complete {
+ __le32 num_dwords;
+ __le32 dwords[] __counted_by(num_dwords);
+};
+
+struct scmi_msg_telemetry_config_get {
+ __le32 grp_id;
+ __le32 flags;
+#define TELEMETRY_GET_SELECTOR(x) (FIELD_PREP(GENMASK(3, 0), (x)))
+#define TELEMETRY_GET_SELECTOR_ORPHANS TELEMETRY_GET_SELECTOR(TLM_ORPHANS)
+#define TELEMETRY_GET_SELECTOR_GROUP TELEMETRY_GET_SELECTOR(TLM_GROUP)
+#define TELEMETRY_GET_SELECTOR_ALL TELEMETRY_GET_SELECTOR(TLM_ALL)
+};
+
+struct scmi_msg_resp_telemetry_config_get {
+ __le32 control;
+#define TELEMETRY_MODE_GET (FIELD_GET(GENMASK(4, 1)))
+ __le32 sampling_rate;
+};
+
+/* TDCF */
+
+#define _I(__a) (ioread32((void __iomem *)(__a)))
+
+#define TO_CPU_64(h, l) ((((u64)(h)) << 32) | (l))
+
+enum scan_mode {
+ SCAN_LOOKUP,
+ SCAN_UPDATE,
+ SCAN_DISCOVERY
+};
+
+struct fc_line {
+ u32 data_low;
+ u32 data_high;
+};
+
+struct fc_tsline {
+ u32 data_low;
+ u32 data_high;
+ u32 ts_low;
+ u32 ts_high;
+};
+
+struct line {
+ u32 data_low;
+ u32 data_high;
+};
+
+struct blk_tsline {
+ u32 ts_low;
+ u32 ts_high;
+};
+
+struct tsline {
+ u32 data_low;
+ u32 data_high;
+ u32 ts_low;
+ u32 ts_high;
+};
+
+#define LINE_DATA_GET(f) \
+({ \
+ typeof(f) _f = (f); \
+ \
+ (TO_CPU_64(_I(&_f->data_high), _I(&_f->data_low))); \
+})
+
+#define LINE_TSTAMP_GET(f) \
+({ \
+ typeof(f) _f = (f); \
+ \
+ (TO_CPU_64(_I(&_f->ts_high), _I(&_f->ts_low))); \
+})
+
+#define BLK_TSTAMP_GET(f) LINE_TSTAMP_GET(f)
+
+struct payload {
+ u32 meta;
+#define IS_BLK_TS(x) (_I(&((x)->meta)) & BIT(4))
+#define USE_BLK_TS(x) (_I(&((x)->meta)) & BIT(3))
+#define USE_LINE_TS(x) (_I(&((x)->meta)) & BIT(2))
+#define TS_VALID(x) (_I(&((x)->meta)) & BIT(1))
+#define DATA_INVALID(x) (_I(&((x)->meta)) & BIT(0))
+ u32 id;
+ union {
+ struct line l;
+ struct tsline tsl;
+ struct blk_tsline blk_tsl;
+ };
+};
+
+#define PAYLD_ID(x) (_I(&(((struct payload *)(x))->id)))
+
+#define LINE_DATA_PAYLD_WORDS \
+ ((sizeof(u32) + sizeof(u32) + sizeof(struct line)) / sizeof(u32))
+#define TS_LINE_DATA_PAYLD_WORDS \
+ ((sizeof(u32) + sizeof(u32) + sizeof(struct tsline)) / sizeof(u32))
+
+#define QWORDS_LINE_DATA_PAYLD (LINE_DATA_PAYLD_WORDS / 2)
+#define QWORDS_TS_LINE_DATA_PAYLD (TS_LINE_DATA_PAYLD_WORDS / 2)
+
+struct prlg {
+ u32 seq_low;
+ u32 seq_high;
+ u32 num_qwords;
+ u32 _meta_header_high;
+};
+
+struct eplg {
+ u32 seq_low;
+ u32 seq_high;
+};
+
+#define TDCF_EPLG_SZ (sizeof(struct eplg))
+
+struct tdcf {
+ struct prlg prlg;
+ unsigned char payld[];
+};
+
+#define QWORDS(_t) (_I(&(_t)->prlg.num_qwords))
+
+#define SHMTI_MIN_SIZE (sizeof(struct tdcf) + TDCF_EPLG_SZ)
+
+#define TDCF_BAD_END_SEQ GENMASK_U64(63, 0)
+#define TDCF_START_SEQ_GET(x) \
+ ({ \
+ u64 _val; \
+ struct prlg *_p = &((x)->prlg); \
+ \
+ _val = TO_CPU_64(_I(&_p->seq_high), _I(&_p->seq_low)); \
+ (_val); \
+ })
+
+#define IS_BAD_START_SEQ(s) ((s) & 0x1)
+
+#define TDCF_END_SEQ_GET(e) \
+ ({ \
+ u64 _val; \
+ struct eplg *_e = (e); \
+ \
+ _val = TO_CPU_64(_I(&_e->seq_high), _I(&_e->seq_low)); \
+ (_val); \
+ })
+
+struct telemetry_shmti {
+ int id;
+ void __iomem *base;
+ u32 len;
+ u64 last_magic;
+};
+
+#define SHMTI_EPLG(s) \
+ ({ \
+ struct telemetry_shmti *_s = (s); \
+ void *_eplg; \
+ \
+ _eplg = _s->base + _s->len - TDCF_EPLG_SZ; \
+ (_eplg); \
+ })
+
+struct telemetry_block_ts {
+ refcount_t users;
+ /* Protect block_ts accesses */
+ struct mutex mtx;
+ u64 last_ts;
+ u64 last_magic;
+ struct payload __iomem *payld;
+ struct xarray *xa_bts;
+};
+
+struct telemetry_de {
+ bool enumerated;
+ bool cached;
+ void __iomem *base;
+ void __iomem *eplg;
+ u32 offset;
+ /* NOTE THAT DE data_sz is registered in scmi_telemetry_de */
+ u32 fc_size;
+ /* Protect last_val/ts/magic accesses */
+ struct mutex mtx;
+ u64 last_val;
+ u64 last_ts;
+ u64 last_magic;
+ struct list_head item;
+ struct telemetry_block_ts *bts;
+ struct scmi_telemetry_de de;
+};
+
+#define to_tde(d) container_of(d, struct telemetry_de, de)
+
+#define DE_ENABLED_WITH_TSTAMP 2
+
+struct telemetry_info {
+ bool streaming_mode;
+ int num_shmti;
+ const struct scmi_protocol_handle *ph;
+ struct telemetry_shmti *shmti;
+ struct telemetry_de *tdes;
+ struct scmi_telemetry_group *grps;
+ struct xarray xa_des;
+ struct xarray xa_bts;
+ /* Mutex to protect access to @free_des */
+ struct mutex free_mtx;
+ struct list_head free_des;
+ struct list_head fcs_des;
+ struct scmi_telemetry_info info;
+ struct notifier_block telemetry_nb;
+ atomic_t rinfo_initializing;
+ struct completion rinfo_initdone;
+ struct scmi_telemetry_res_info __private *rinfo;
+ struct scmi_telemetry_res_info *(*res_get)(struct telemetry_info *ti);
+};
+
+#define telemetry_nb_to_info(x) \
+ container_of(x, struct telemetry_info, telemetry_nb)
+
+static struct scmi_telemetry_res_info *
+__scmi_telemetry_resources_get(struct telemetry_info *ti);
+
+static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
+ unsigned int shmti_id, u64 ts,
+ enum scan_mode mode);
+
+static struct telemetry_de *
+scmi_telemetry_free_tde_get(struct telemetry_info *ti)
+{
+ struct telemetry_de *tde;
+
+ guard(mutex)(&ti->free_mtx);
+
+ tde = list_first_entry_or_null(&ti->free_des, struct telemetry_de, item);
+ if (!tde)
+ return tde;
+
+ list_del(&tde->item);
+
+ return tde;
+}
+
+static void scmi_telemetry_free_tde_put(struct telemetry_info *ti,
+ struct telemetry_de *tde)
+{
+ guard(mutex)(&ti->free_mtx);
+
+ list_add_tail(&tde->item, &ti->free_des);
+}
+
+static struct telemetry_de *scmi_telemetry_tde_lookup(struct telemetry_info *ti,
+ unsigned int de_id)
+{
+ struct scmi_telemetry_de *de;
+
+ de = xa_load(&ti->xa_des, de_id);
+ if (!de)
+ return NULL;
+
+ return to_tde(de);
+}
+
+static struct telemetry_de *scmi_telemetry_tde_get(struct telemetry_info *ti,
+ unsigned int de_id)
+{
+ static struct telemetry_de *tde;
+
+ /* Pick a new tde */
+ tde = scmi_telemetry_free_tde_get(ti);
+ if (!tde) {
+ dev_err(ti->ph->dev, "Cannot allocate DE for ID:0x%08X\n", de_id);
+ return ERR_PTR(-ENOSPC);
+ }
+
+ return tde;
+}
+
+static int scmi_telemetry_tde_register(struct telemetry_info *ti,
+ struct telemetry_de *tde)
+{
+ int ret;
+
+ if (ti->rinfo->num_des >= ti->info.base.num_des) {
+ ret = -ENOSPC;
+ goto err;
+ }
+
+ /* Store DE pointer by de_id ... */
+ ret = xa_insert(&ti->xa_des, tde->de.info->id, &tde->de, GFP_KERNEL);
+ if (ret)
+ goto err;
+
+ /* ... and in the general array */
+ ti->rinfo->des[ti->rinfo->num_des++] = &tde->de;
+
+ return 0;
+
+err:
+ dev_err(ti->ph->dev, "Cannot register DE for ID:0x%08X\n",
+ tde->de.info->id);
+
+ return ret;
+}
+
+struct scmi_tlm_de_priv {
+ struct telemetry_info *ti;
+ void *next;
+};
+
+static int
+scmi_telemetry_protocol_attributes_get(struct telemetry_info *ti)
+{
+ struct scmi_msg_resp_telemetry_protocol_attributes *resp;
+ const struct scmi_protocol_handle *ph = ti->ph;
+ struct scmi_xfer *t;
+ int ret;
+
+ ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0,
+ sizeof(*resp), &t);
+ if (ret)
+ return ret;
+
+ resp = t->rx.buf;
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret) {
+ __le32 attr = resp->attributes;
+
+ ti->info.base.num_des = le32_to_cpu(resp->de_num);
+ ti->info.base.num_groups = le32_to_cpu(resp->groups_num);
+ for (int i = 0; i < SCMI_TLM_DE_IMPL_MAX_DWORDS; i++)
+ ti->info.base.de_impl_version[i] =
+ le32_to_cpu(resp->de_implementation_rev_dword[i]);
+ ti->info.single_read_support = SUPPORTS_SINGLE_READ(attr);
+ ti->info.continuos_update_support = SUPPORTS_CONTINUOS_UPDATE(attr);
+ ti->info.per_group_config_support = SUPPORTS_PER_GROUP_CONFIG(attr);
+ ti->info.reset_support = SUPPORTS_RESET(attr);
+ ti->info.fc_support = SUPPORTS_FC(attr);
+ ti->num_shmti = le32_get_bits(attr, GENMASK(15, 0));
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static void iter_tlm_prepare_message(void *message,
+ unsigned int desc_index, const void *priv)
+{
+ put_unaligned_le32(desc_index, message);
+}
+
+static int iter_de_descr_update_state(struct scmi_iterator_state *st,
+ const void *response, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_de_description *r = response;
+ struct scmi_tlm_de_priv *p = priv;
+
+ st->num_returned = le32_get_bits(r->num_desc, GENMASK(15, 0));
+ st->num_remaining = le32_get_bits(r->num_desc, GENMASK(31, 16));
+
+ /* Initialized to first descriptor */
+ p->next = (void *)r->desc;
+
+ return 0;
+}
+
+static int scmi_telemetry_de_descriptor_parse(struct telemetry_info *ti,
+ struct telemetry_de *tde,
+ void **next)
+{
+ struct scmi_telemetry_res_info *rinfo = ti->rinfo;
+ const struct scmi_de_desc *desc = *next;
+ unsigned int grp_id;
+
+ tde->de.info->id = le32_to_cpu(desc->id);
+ grp_id = le32_to_cpu(desc->grp_id);
+ if (grp_id != SCMI_TLM_GRP_INVALID) {
+ /* Group descriptors are empty but allocated at this point */
+ if (grp_id >= ti->info.base.num_groups)
+ return -EINVAL;
+
+ /* Link to parent group */
+ tde->de.info->grp_id = grp_id;
+ tde->de.grp = &rinfo->grps[grp_id];
+ }
+
+ tde->de.info->data_sz = le32_to_cpu(desc->data_sz);
+ tde->de.info->type = GET_DE_TYPE(desc);
+ tde->de.info->unit = GET_DE_UNIT(desc);
+ tde->de.info->unit_exp = GET_DE_UNIT_EXP(desc);
+ tde->de.info->tstamp_exp = GET_DE_TSTAMP_EXP(desc);
+ tde->de.info->instance_id = GET_DE_INSTA_ID(desc);
+ tde->de.info->compo_instance_id = GET_COMPO_INSTA_ID(desc);
+ tde->de.info->compo_type = GET_COMPO_TYPE(desc);
+ tde->de.info->persistent = IS_PERSISTENT(desc);
+ tde->de.tstamp_support = IS_TSTAMP_SUPPORTED(desc);
+ tde->de.fc_support = IS_FC_SUPPORTED(desc);
+ tde->de.name_support = IS_NAME_SUPPORTED(desc);
+ /* Update DE_DESCRIPTOR size for the next iteration */
+ *next += sizeof(*desc);
+ if (tde->de.fc_support) {
+ u32 size;
+ u64 phys_addr;
+ void __iomem *addr;
+ struct de_desc_fc *dfc;
+
+ dfc = *next;
+ phys_addr = le32_to_cpu(dfc->addr_low);
+ phys_addr |= (u64)le32_to_cpu(dfc->addr_high) << 32;
+
+ size = le32_to_cpu(dfc->size);
+ addr = devm_ioremap(ti->ph->dev, phys_addr, size);
+ if (!addr)
+ return -EADDRNOTAVAIL;
+
+ tde->base = addr;
+ tde->offset = 0;
+ tde->fc_size = size;
+
+ /* Add to FastChannels list */
+ list_add(&tde->item, &ti->fcs_des);
+
+ /* Variably sized depending on FC support */
+ *next += sizeof(*dfc);
+ }
+
+ if (tde->de.name_support) {
+ const char *de_name = *next;
+
+ strscpy(tde->de.info->name, de_name, SCMI_SHORT_NAME_MAX_SIZE);
+ /* Variably sized depending on name support */
+ *next += SCMI_SHORT_NAME_MAX_SIZE;
+ }
+
+ return 0;
+}
+
+static int iter_de_descr_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st,
+ void *priv)
+{
+ struct scmi_tlm_de_priv *p = priv;
+ struct telemetry_info *ti = p->ti;
+ const struct scmi_de_desc *desc = p->next;
+ struct telemetry_de *tde;
+ bool discovered = false;
+ unsigned int de_id;
+ int ret;
+
+ de_id = le32_to_cpu(desc->id);
+ /* Check if this DE has already been discovered by other means... */
+ tde = scmi_telemetry_tde_lookup(ti, de_id);
+ if (!tde) {
+ /* Create a new one */
+ tde = scmi_telemetry_tde_get(ti, de_id);
+ if (IS_ERR(tde))
+ return PTR_ERR(tde);
+
+ discovered = true;
+ } else if (tde->enumerated) {
+ /* Cannot be a duplicate of a DE already created by enumeration */
+ dev_err(ph->dev,
+ "Discovered INVALID DE with DUPLICATED ID:0x%08X\n",
+ de_id);
+ return -EINVAL;
+ }
+
+ ret = scmi_telemetry_de_descriptor_parse(ti, tde, &p->next);
+ if (ret)
+ goto err;
+
+ if (discovered) {
+ /* Register if it was not already ... */
+ ret = scmi_telemetry_tde_register(ti, tde);
+ if (ret)
+ goto err;
+
+ tde->enumerated = true;
+ }
+
+ /* Account for this DE in group num_de counter */
+ if (tde->de.grp)
+ tde->de.grp->info->num_des++;
+
+ return 0;
+
+err:
+ /* DE not enumerated at this point were created in this call */
+ if (!tde->enumerated)
+ scmi_telemetry_free_tde_put(ti, tde);
+
+ return ret;
+}
+
+static int scmi_telemetry_config_lookup(struct telemetry_info *ti,
+ unsigned int grp_id, bool *enabled,
+ unsigned int *active_update_interval)
+{
+ const struct scmi_protocol_handle *ph = ti->ph;
+ struct scmi_msg_telemetry_config_get *msg;
+ struct scmi_msg_resp_telemetry_config_get *resp;
+ struct scmi_xfer *t;
+ int ret;
+
+ ret = ph->xops->xfer_get_init(ph, TELEMETRY_CONFIG_GET,
+ sizeof(*msg), sizeof(*resp), &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ msg->grp_id = grp_id;
+ msg->flags = grp_id == SCMI_TLM_GRP_INVALID ?
+ TELEMETRY_GET_SELECTOR_ORPHANS : TELEMETRY_GET_SELECTOR_GROUP;
+
+ resp = t->rx.buf;
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret) {
+ *enabled = resp->control & TELEMETRY_ENABLE;
+ *active_update_interval =
+ SCMI_TLM_GET_UPDATE_INTERVAL(resp->sampling_rate);
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return 0;
+}
+
+static int scmi_telemetry_group_config_lookup(struct telemetry_info *ti,
+ struct scmi_telemetry_group *grp)
+{
+ return scmi_telemetry_config_lookup(ti, grp->info->id, &grp->enabled,
+ &grp->active_update_interval);
+}
+
+static void iter_enabled_list_prepare_message(void *message,
+ unsigned int desc_index,
+ const void *priv)
+{
+ struct scmi_msg_telemetry_de_enabled_list *msg = message;
+
+ msg->index = cpu_to_le32(desc_index);
+ msg->flags = 0;
+}
+
+static int iter_enabled_list_update_state(struct scmi_iterator_state *st,
+ const void *response, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_de_enabled_list *r = response;
+
+ st->num_returned = le32_get_bits(r->flags, GENMASK(15, 0));
+ st->num_remaining = le32_get_bits(r->flags, GENMASK(31, 16));
+
+ /*
+ * total enabled is not declared previously anywhere so we
+ * assume it's returned+remaining on first call.
+ */
+ if (!st->max_resources)
+ st->max_resources = st->num_returned + st->num_remaining;
+
+ return 0;
+}
+
+static int
+iter_enabled_list_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_de_enabled_list *r = response;
+ const struct scmi_enabled_de_desc *desc;
+ struct telemetry_info *ti = priv;
+ struct telemetry_de *tde;
+ u32 de_id;
+ int ret;
+
+ desc = &r->entry[st->loop_idx];
+ de_id = le32_to_cpu(desc->id);
+ if (scmi_telemetry_tde_lookup(ti, de_id)) {
+ dev_err(ph->dev,
+ "Found INVALID DE with DUPLICATED ID:0x%08X\n", de_id);
+ return -EINVAL;
+ }
+
+ tde = scmi_telemetry_tde_get(ti, de_id);
+ if (IS_ERR(tde))
+ return PTR_ERR(tde);
+
+ tde->de.info->id = de_id;
+ tde->de.enabled = true;
+ tde->de.tstamp_enabled = desc->mode == DE_ENABLED_WITH_TSTAMP;
+
+ ret = scmi_telemetry_tde_register(ti, tde);
+ if (ret) {
+ scmi_telemetry_free_tde_put(ti, tde);
+ return ret;
+ }
+
+ dev_dbg(ph->dev, "Registered new ENABLED DE with ID:0x%08X\n",
+ tde->de.info->id);
+
+ return 0;
+}
+
+static int scmi_telemetry_enumerate_des_enabled_list(struct telemetry_info *ti)
+{
+ const struct scmi_protocol_handle *ph = ti->ph;
+ struct scmi_iterator_ops ops = {
+ .prepare_message = iter_enabled_list_prepare_message,
+ .update_state = iter_enabled_list_update_state,
+ .process_response = iter_enabled_list_process_response,
+ };
+ void *iter;
+ int ret;
+
+ iter = ph->hops->iter_response_init(ph, &ops, 0,
+ TELEMETRY_DE_ENABLED_LIST,
+ sizeof(u32) * 2, ti);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+
+ ret = ph->hops->iter_response_run(iter);
+ if (ret)
+ return ret;
+
+ dev_info(ti->ph->dev, "Found %u enabled DEs.\n", ti->rinfo->num_des);
+
+ return 0;
+}
+
+static int scmi_telemetry_initial_state_lookup(struct telemetry_info *ti)
+{
+ struct device *dev = ti->ph->dev;
+ int ret;
+
+ ret = scmi_telemetry_config_lookup(ti, SCMI_TLM_GRP_INVALID,
+ &ti->info.enabled,
+ &ti->info.active_update_interval);
+ if (ret)
+ return ret;
+
+ if (ti->info.enabled) {
+ /*
+ * When Telemetry is found already enabled on the platform,
+ * proceed with passive discovery using DE_ENABLED_LIST and
+ * TCDF scanning: note that this CAN only discover DEs exposed
+ * via SHMTIs.
+ * FastChannel DEs need a proper DE_DESCRIPTION enumeration,
+ * while, even though incoming Notifications could be used for
+ * passive discovery too, it would carry a considerable risk
+ * of assimilating trash as DEs.
+ */
+ dev_info(dev,
+ "Telemetry found enabled with update interval %ux10^%d\n",
+ SCMI_TLM_GET_UPDATE_INTERVAL_SECS(ti->info.active_update_interval),
+ SCMI_TLM_GET_UPDATE_INTERVAL_EXP(ti->info.active_update_interval));
+ /*
+ * Query enabled DEs list: collect states.
+ * It will include DEs from any interface.
+ * Enabled groups still NOT enumerated.
+ */
+ ret = scmi_telemetry_enumerate_des_enabled_list(ti);
+ if (ret)
+ dev_warn(dev, FW_BUG "Cannot query enabled DE list. Carry-on.\n");
+
+ /* Discover DEs on SHMTis: collect states/offsets/values */
+ for (int id = 0; id < ti->num_shmti; id++) {
+ ret = scmi_telemetry_shmti_scan(ti, id, 0, SCAN_DISCOVERY);
+ if (ret)
+ dev_warn(dev, "Failed discovery-scan of SHMTI ID:%d\n", id);
+ }
+ }
+
+ return 0;
+}
+
+static int
+scmi_telemetry_de_groups_init(struct device *dev, struct telemetry_info *ti)
+{
+ struct scmi_telemetry_res_info *rinfo = ti->rinfo;
+
+ /* Allocate all groups DEs IDs arrays at first ... */
+ for (int i = 0; i < ti->info.base.num_groups; i++) {
+ struct scmi_telemetry_group *grp = &rinfo->grps[i];
+ size_t des_str_sz;
+
+ unsigned int *des __free(kfree) = kcalloc(grp->info->num_des,
+ sizeof(unsigned int),
+ GFP_KERNEL);
+ if (!des)
+ return -ENOMEM;
+
+ /*
+ * Max size 32bit ID string in Hex: 0xCAFECAFE
+ * - 10 digits + ' '/'\n' = 11 bytes per number
+ * - terminating NUL character
+ */
+ des_str_sz = grp->info->num_des * 11 + 1;
+ char *des_str __free(kfree) = kzalloc(des_str_sz, GFP_KERNEL);
+ if (!des_str)
+ return -ENOMEM;
+
+ grp->des = no_free_ptr(des);
+ grp->des_str = no_free_ptr(des_str);
+ /* Reset group DE counter */
+ grp->info->num_des = 0;
+ }
+
+ /* Scan DEs and populate DE IDs arrays for all groups */
+ for (int i = 0; i < rinfo->num_des; i++) {
+ struct scmi_telemetry_group *grp = rinfo->des[i]->grp;
+
+ if (!grp)
+ continue;
+
+ /*
+ * Note that, at this point, num_des is guaranteed to be
+ * sane (in-bounds) by construction.
+ */
+ grp->des[grp->info->num_des++] = i;
+ }
+
+ /* Build composing DES string */
+ for (int i = 0; i < ti->info.base.num_groups; i++) {
+ struct scmi_telemetry_group *grp = &rinfo->grps[i];
+ size_t bufsize = grp->info->num_des * 11 + 1;
+ char *buf = grp->des_str;
+
+ for (int j = 0; j < grp->info->num_des; j++) {
+ char term = j != (grp->info->num_des - 1) ? ' ' : '\0';
+ int len;
+
+ len = scnprintf(buf, bufsize, "0x%04X%c",
+ rinfo->des[grp->des[j]]->info->id, term);
+
+ buf += len;
+ bufsize -= len;
+ }
+ }
+
+ for (int i = 0; i < ti->info.base.num_groups; i++)
+ scmi_telemetry_group_config_lookup(ti, &rinfo->grps[i]);
+
+ rinfo->num_groups = ti->info.base.num_groups;
+
+ return 0;
+}
+
+static int scmi_telemetry_de_descriptors_get(struct telemetry_info *ti)
+{
+ const struct scmi_protocol_handle *ph = ti->ph;
+
+ struct scmi_iterator_ops ops = {
+ .prepare_message = iter_tlm_prepare_message,
+ .update_state = iter_de_descr_update_state,
+ .process_response = iter_de_descr_process_response,
+ };
+ struct scmi_tlm_de_priv tpriv = {
+ .ti = ti,
+ .next = NULL,
+ };
+ void *iter;
+ int ret;
+
+ if (!ti->info.base.num_des)
+ return 0;
+
+ iter = ph->hops->iter_response_init(ph, &ops, ti->info.base.num_des,
+ TELEMETRY_DE_DESCRIPTION,
+ sizeof(u32), &tpriv);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+
+ ret = ph->hops->iter_response_run(iter);
+ if (ret)
+ return ret;
+
+ return scmi_telemetry_de_groups_init(ph->dev, ti);
+}
+
+struct scmi_tlm_ivl_priv {
+ struct device *dev;
+ struct scmi_tlm_intervals **intrvs;
+ unsigned int grp_id;
+ unsigned int flags;
+};
+
+static void iter_intervals_prepare_message(void *message,
+ unsigned int desc_index,
+ const void *priv)
+{
+ struct scmi_msg_telemetry_update_intervals *msg = message;
+ const struct scmi_tlm_ivl_priv *p = priv;
+
+ msg->index = cpu_to_le32(desc_index);
+ msg->group_identifier = cpu_to_le32(p->grp_id);
+ msg->flags = FIELD_PREP(GENMASK(3, 0), p->flags);
+}
+
+static int iter_intervals_update_state(struct scmi_iterator_state *st,
+ const void *response, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_update_intervals *r = response;
+
+ st->num_returned = le32_get_bits(r->flags, GENMASK(11, 0));
+ st->num_remaining = le32_get_bits(r->flags, GENMASK(31, 16));
+
+ /*
+ * total intervals is not declared previously anywhere so we
+ * assume it's returned+remaining on first call.
+ */
+ if (!st->max_resources) {
+ struct scmi_tlm_ivl_priv *p = priv;
+ bool discrete;
+ int inum;
+
+ discrete = INTERVALS_DISCRETE(r->flags);
+ /* Check consistency on first call */
+ if (!discrete && (st->num_returned != 3 || st->num_remaining != 0))
+ return -EINVAL;
+
+ inum = st->num_returned + st->num_remaining;
+ struct scmi_tlm_intervals *intrvs __free(kfree) =
+ kzalloc(sizeof(*intrvs) + inum * sizeof(__u32), GFP_KERNEL);
+ if (!intrvs)
+ return -ENOMEM;
+
+ intrvs->num = inum;
+ intrvs->discrete = discrete;
+ st->max_resources = intrvs->num;
+
+ *p->intrvs = no_free_ptr(intrvs);
+ }
+
+ return 0;
+}
+
+static int
+iter_intervals_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_update_intervals *r = response;
+ struct scmi_tlm_ivl_priv *p = priv;
+ struct scmi_tlm_intervals *intrvs = *p->intrvs;
+ unsigned int idx = st->loop_idx;
+
+ intrvs->update_intervals[st->desc_index + idx] = r->intervals[idx];
+
+ return 0;
+}
+
+static int
+scmi_tlm_enumerate_update_intervals(struct telemetry_info *ti,
+ struct scmi_tlm_intervals **intervals,
+ int grp_id, unsigned int flags)
+{
+ struct scmi_iterator_ops ops = {
+ .prepare_message = iter_intervals_prepare_message,
+ .update_state = iter_intervals_update_state,
+ .process_response = iter_intervals_process_response,
+ };
+ const struct scmi_protocol_handle *ph = ti->ph;
+ struct scmi_tlm_ivl_priv ipriv = {
+ .dev = ph->dev,
+ .grp_id = grp_id,
+ .intrvs = intervals,
+ .flags = flags,
+ };
+ void *iter;
+
+ iter = ph->hops->iter_response_init(ph, &ops, 0,
+ TELEMETRY_LIST_UPDATE_INTERVALS,
+ sizeof(struct scmi_msg_telemetry_update_intervals),
+ &ipriv);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+
+ return ph->hops->iter_response_run(iter);
+}
+
+static int
+scmi_telemetry_enumerate_groups_intervals(struct telemetry_info *ti)
+{
+ struct scmi_telemetry_res_info *rinfo = ti->rinfo;
+
+ if (!ti->info.per_group_config_support)
+ return 0;
+
+ for (int id = 0; id < rinfo->num_groups; id++) {
+ int ret;
+
+ ret = scmi_tlm_enumerate_update_intervals(ti,
+ &rinfo->grps[id].intervals,
+ id, SPECIFIC_GROUP_DES);
+ if (ret)
+ return ret;
+
+ rinfo->grps_store[id].num_intervals =
+ rinfo->grps[id].intervals->num;
+ }
+
+ return 0;
+}
+
+static void scmi_telemetry_intervals_free(void *interval)
+{
+ kfree(interval);
+}
+
+static int
+scmi_telemetry_enumerate_common_intervals(struct telemetry_info *ti)
+{
+ unsigned int flags;
+ int ret;
+
+ flags = !ti->info.per_group_config_support ?
+ ALL_DES_ANY_GROUP : ALL_DES_NO_GROUP;
+
+ ret = scmi_tlm_enumerate_update_intervals(ti, &ti->info.intervals,
+ SCMI_TLM_GRP_INVALID, flags);
+ if (ret)
+ return ret;
+
+ /* A copy for UAPI access... */
+ ti->info.base.num_intervals = ti->info.intervals->num;
+
+ /* Delegate freeing of allocated intervals to unbind time */
+ return devm_add_action_or_reset(ti->ph->dev,
+ scmi_telemetry_intervals_free,
+ ti->info.intervals);
+}
+
+static int iter_shmti_update_state(struct scmi_iterator_state *st,
+ const void *response, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_shmti_list *r = response;
+
+ st->num_returned = le32_get_bits(r->num_shmti, GENMASK(15, 0));
+ st->num_remaining = le32_get_bits(r->num_shmti, GENMASK(31, 16));
+
+ return 0;
+}
+
+static int iter_shmti_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st,
+ void *priv)
+{
+ const struct scmi_msg_resp_telemetry_shmti_list *r = response;
+ struct telemetry_info *ti = priv;
+ struct telemetry_shmti *shmti;
+ const struct scmi_shmti_desc *desc;
+ void __iomem *addr;
+ u64 phys_addr;
+ u32 len;
+
+ desc = &r->desc[st->loop_idx];
+ shmti = &ti->shmti[st->desc_index + st->loop_idx];
+
+ shmti->id = le32_to_cpu(desc->id);
+ phys_addr = le32_to_cpu(desc->addr_low);
+ phys_addr |= (u64)le32_to_cpu(desc->addr_high) << 32;
+
+ len = le32_to_cpu(desc->length);
+ if (len < SHMTI_MIN_SIZE)
+ return -EINVAL;
+
+ addr = devm_ioremap(ph->dev, phys_addr, len);
+ if (!addr)
+ return -EADDRNOTAVAIL;
+
+ shmti->base = addr;
+ shmti->len = len;
+
+ return 0;
+}
+
+static int scmi_telemetry_shmti_list(const struct scmi_protocol_handle *ph,
+ struct telemetry_info *ti)
+{
+ struct scmi_iterator_ops ops = {
+ .prepare_message = iter_tlm_prepare_message,
+ .update_state = iter_shmti_update_state,
+ .process_response = iter_shmti_process_response,
+ };
+ void *iter;
+
+ iter = ph->hops->iter_response_init(ph, &ops, ti->info.base.num_des,
+ TELEMETRY_LIST_SHMTI,
+ sizeof(u32), ti);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+
+ return ph->hops->iter_response_run(iter);
+}
+
+static int scmi_telemetry_enumerate_shmti(struct telemetry_info *ti)
+{
+ const struct scmi_protocol_handle *ph = ti->ph;
+ int ret;
+
+ if (!ti->num_shmti)
+ return 0;
+
+ ti->shmti = devm_kcalloc(ph->dev, ti->num_shmti, sizeof(*ti->shmti),
+ GFP_KERNEL);
+ if (!ti->shmti)
+ return -ENOMEM;
+
+ ret = scmi_telemetry_shmti_list(ph, ti);
+ if (ret) {
+ dev_err(ph->dev, "Cannot get SHMTI list descriptors");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct scmi_telemetry_info *
+scmi_telemetry_info_get(const struct scmi_protocol_handle *ph)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ return &ti->info;
+}
+
+static const struct scmi_telemetry_de *
+scmi_telemetry_de_lookup(const struct scmi_protocol_handle *ph, u32 id)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_telemetry_de *de;
+
+ ti->res_get(ti);
+ de = xa_load(&ti->xa_des, id);
+ if (!de)
+ return NULL;
+
+ return de;
+}
+
+static const struct scmi_telemetry_res_info *
+scmi_telemetry_resources_get(const struct scmi_protocol_handle *ph)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ return ti->res_get(ti);
+}
+
+static u64
+scmi_telemetry_blkts_read(u64 magic, struct telemetry_block_ts *bts)
+{
+ if (WARN_ON(!bts || !refcount_read(&bts->users)))
+ return 0;
+
+ guard(mutex)(&bts->mtx);
+
+ if (bts->last_magic == magic)
+ return bts->last_ts;
+
+ bts->last_ts = BLK_TSTAMP_GET(&bts->payld->blk_tsl);
+ bts->last_magic = magic;
+
+ return bts->last_ts;
+}
+
+static void scmi_telemetry_blkts_update(u64 magic,
+ struct telemetry_block_ts *bts)
+{
+ guard(mutex)(&bts->mtx);
+
+ if (bts->last_magic != magic) {
+ bts->last_ts = BLK_TSTAMP_GET(&bts->payld->blk_tsl);
+ bts->last_magic = magic;
+ }
+}
+
+static void scmi_telemetry_blkts_put(struct device *dev,
+ struct telemetry_block_ts *bts)
+{
+ if (refcount_dec_and_test(&bts->users)) {
+ scoped_guard(mutex, &bts->mtx)
+ xa_erase(bts->xa_bts, (unsigned long)bts->payld);
+ devm_kfree(dev, bts);
+ }
+}
+
+static struct telemetry_block_ts *
+scmi_telemetry_blkts_get(struct xarray *xa_bts, struct payload *payld)
+{
+ struct telemetry_block_ts *bts;
+
+ bts = xa_load(xa_bts, (unsigned long)payld);
+ if (!bts)
+ return NULL;
+
+ refcount_inc(&bts->users);
+
+ return bts;
+}
+
+static struct payload *
+scmi_telemetry_nearest_blk_ts(struct telemetry_shmti *shmti,
+ struct payload *last_payld)
+{
+ struct payload *payld, *bts_payld = NULL;
+ struct tdcf __iomem *tdcf = shmti->base;
+ u32 *next;
+
+ /* Scan from start of TDCF payloads up to last_payld */
+ payld = (struct payload *)tdcf->payld;
+ next = (u32 *)payld;
+ while (payld < last_payld) {
+ if (IS_BLK_TS(payld))
+ bts_payld = payld;
+
+ next += USE_LINE_TS(payld) ?
+ TS_LINE_DATA_PAYLD_WORDS : LINE_DATA_PAYLD_WORDS;
+ payld = (struct payload *)next;
+ }
+
+ return bts_payld;
+}
+
+static struct telemetry_block_ts *
+scmi_telemetry_blkts_lookup(struct device *dev, struct xarray *xa_bts,
+ struct payload *payld)
+{
+ struct telemetry_block_ts *bts;
+
+ bts = xa_load(xa_bts, (unsigned long)payld);
+ if (!bts) {
+ int ret;
+
+ bts = devm_kzalloc(dev, sizeof(*bts), GFP_KERNEL);
+ if (!bts)
+ return NULL;
+
+ refcount_set(&bts->users, 1);
+ bts->payld = payld;
+ bts->xa_bts = xa_bts;
+ mutex_init(&bts->mtx);
+ ret = xa_insert(xa_bts, (unsigned long)payld, bts, GFP_KERNEL);
+ if (ret) {
+ devm_kfree(dev, bts);
+ return NULL;
+ }
+ }
+
+ return bts;
+}
+
+static struct telemetry_block_ts *
+scmi_telemetry_blkts_bind(struct device *dev, struct telemetry_shmti *shmti,
+ struct payload *payld, struct xarray *xa_bts)
+{
+ struct telemetry_block_ts *bts;
+ struct payload *bts_payld;
+
+ /* Find the BLK_TS immediately preceding this DE payld */
+ bts_payld = scmi_telemetry_nearest_blk_ts(shmti, payld);
+ if (!bts_payld)
+ return NULL;
+
+ bts = scmi_telemetry_blkts_get(xa_bts, bts_payld);
+ if (bts)
+ return bts;
+
+ return scmi_telemetry_blkts_lookup(dev, xa_bts, payld);
+}
+
+static void scmi_telemetry_tdcf_blkts_parse(struct telemetry_info *ti,
+ struct payload __iomem *payld,
+ struct telemetry_shmti *shmti)
+{
+ struct telemetry_block_ts *bts;
+
+ /* Check for spec compliance */
+ if (USE_LINE_TS(payld) || USE_BLK_TS(payld) ||
+ DATA_INVALID(payld) || (PAYLD_ID(payld) != 0))
+ return;
+
+ /* A BLK_TS descriptor MUST be returned: it is found or it is crated */
+ bts = scmi_telemetry_blkts_lookup(ti->ph->dev, &ti->xa_bts, payld);
+ if (WARN_ON(!bts))
+ return;
+
+ /* Update the descriptor with the lastest TS*/
+ scmi_telemetry_blkts_update(shmti->last_magic, bts);
+}
+
+static void scmi_telemetry_tdcf_data_parse(struct telemetry_info *ti,
+ struct payload __iomem *payld,
+ struct telemetry_shmti *shmti,
+ enum scan_mode mode)
+{
+ bool ts_valid = TS_VALID(payld);
+ struct telemetry_de *tde;
+ bool discovered = false;
+ u64 val, tstamp = 0;
+ u32 de_id;
+
+ de_id = PAYLD_ID(payld);
+ /* Is thi DE ID know ? */
+ tde = scmi_telemetry_tde_lookup(ti, de_id);
+ if (!tde) {
+ if (mode != SCAN_DISCOVERY)
+ return;
+
+ /* In SCAN_DISCOVERY mode we allocate new DEs for unknown IDs */
+ tde = scmi_telemetry_tde_get(ti, de_id);
+ if (IS_ERR(tde))
+ return;
+
+ tde->de.info->id = de_id;
+ tde->de.enabled = true;
+ tde->de.tstamp_enabled = ts_valid;
+ discovered = true;
+ }
+
+ /* Update DE location refs if requested: normally done only on enable */
+ if (mode >= SCAN_UPDATE) {
+ tde->base = shmti->base;
+ tde->eplg = SHMTI_EPLG(shmti);
+ tde->offset = (void *)payld - (void *)shmti->base;
+
+ dev_dbg(ti->ph->dev,
+ "TDCF-updated DE_ID:0x%08X - shmti:%pX offset:%u\n",
+ tde->de.info->id, tde->base, tde->offset);
+ }
+
+ if (discovered) {
+ if (scmi_telemetry_tde_register(ti, tde)) {
+ scmi_telemetry_free_tde_put(ti, tde);
+ return;
+ }
+ }
+
+ scoped_guard(mutex, &tde->mtx) {
+ if (tde->last_magic == shmti->last_magic)
+ return;
+ }
+
+ /* Data is always valid since we are NOT handling BLK TS lines here */
+ val = LINE_DATA_GET(&payld->l);
+ /* Collect the right TS */
+ if (ts_valid) {
+ if (USE_LINE_TS(payld)) {
+ tstamp = LINE_TSTAMP_GET(&payld->tsl);
+ } else if (USE_BLK_TS(payld)) {
+ if (!tde->bts) {
+ /*
+ * Scanning a TDCF looking for the nearest
+ * previous valid BLK_TS, after having found a
+ * USE_BLK_TS() payload, MUST succeed.
+ */
+ tde->bts = scmi_telemetry_blkts_bind(ti->ph->dev,
+ shmti, payld,
+ &ti->xa_bts);
+ if (WARN_ON(!tde->bts))
+ return;
+ }
+
+ tstamp = scmi_telemetry_blkts_read(tde->last_magic,
+ tde->bts);
+ }
+ }
+
+ guard(mutex)(&tde->mtx);
+ tde->last_magic = shmti->last_magic;
+ tde->last_val = val;
+ if (tde->de.tstamp_enabled)
+ tde->last_ts = tstamp;
+ else
+ tde->last_ts = 0;
+}
+
+static int scmi_telemetry_tdcf_line_parse(struct telemetry_info *ti,
+ struct payload __iomem *payld,
+ struct telemetry_shmti *shmti,
+ enum scan_mode mode)
+{
+ int used_qwords;
+
+ used_qwords = (USE_LINE_TS(payld) && TS_VALID(payld)) ?
+ QWORDS_TS_LINE_DATA_PAYLD : QWORDS_LINE_DATA_PAYLD;
+
+ /* Invalid lines are not an error, could simply be disabled DEs */
+ if (DATA_INVALID(payld))
+ return used_qwords;
+
+ if (!IS_BLK_TS(payld))
+ scmi_telemetry_tdcf_data_parse(ti, payld, shmti, mode);
+ else
+ scmi_telemetry_tdcf_blkts_parse(ti, payld, shmti);
+
+ return used_qwords;
+}
+
+static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
+ unsigned int shmti_id, u64 ts,
+ enum scan_mode mode)
+{
+ struct telemetry_shmti *shmti = &ti->shmti[shmti_id];
+ struct tdcf __iomem *tdcf = shmti->base;
+ int retries = SCMI_TLM_TDCF_MAX_RETRIES;
+ u64 startm = 0, endm = TDCF_BAD_END_SEQ;
+ void *eplg = SHMTI_EPLG(shmti);
+
+ if (!tdcf)
+ return -ENODEV;
+
+ do {
+ unsigned int qwords;
+ void __iomem *next;
+
+ /* A bit of exponential backoff between retries */
+ fsleep((SCMI_TLM_TDCF_MAX_RETRIES - retries) * 1000);
+
+ startm = TDCF_START_SEQ_GET(tdcf);
+ if (IS_BAD_START_SEQ(startm))
+ continue;
+
+ /* On a BAD_SEQ this will be updated on the next attempt */
+ shmti->last_magic = startm;
+
+ qwords = QWORDS(tdcf);
+ next = tdcf->payld;
+ while (qwords) {
+ int used_qwords;
+
+ used_qwords = scmi_telemetry_tdcf_line_parse(ti, next,
+ shmti, mode);
+ if (qwords < used_qwords)
+ return -EINVAL;
+
+ next += used_qwords * 8;
+ qwords -= used_qwords;
+ }
+
+ endm = TDCF_END_SEQ_GET(eplg);
+ } while (startm != endm && --retries);
+
+ if (startm != endm)
+ return -EPROTO;
+
+ return 0;
+}
+
+static int scmi_telemetry_group_state_update(struct telemetry_info *ti,
+ struct scmi_telemetry_group *grp,
+ bool *enable, bool *tstamp)
+{
+ struct scmi_telemetry_res_info *rinfo;
+
+ rinfo = ti->res_get(ti);
+ for (int i = 0; i < grp->info->num_des; i++) {
+ struct scmi_telemetry_de *de = rinfo->des[grp->des[i]];
+
+ if (enable)
+ de->enabled = *enable;
+ if (tstamp)
+ de->tstamp_enabled = *tstamp;
+ }
+
+ return 0;
+}
+
+static int
+scmi_telemetry_state_set_resp_process(struct telemetry_info *ti,
+ struct scmi_telemetry_de *de,
+ void *r, bool is_group)
+{
+ struct scmi_msg_resp_telemetry_de_configure *resp = r;
+ u32 sid = le32_to_cpu(resp->shmti_id);
+
+ /* Update DE SHMTI and offset, if applicable */
+ if (IS_SHMTI_ID_VALID(sid)) {
+ if (sid >= ti->num_shmti)
+ return -EPROTO;
+
+ /*
+ * Update SHMTI/offset while skipping non-SHMTI-DEs like
+ * FCs and notif-only.
+ */
+ if (!is_group) {
+ struct telemetry_de *tde;
+ struct payload *payld;
+ u32 offs;
+
+ offs = le32_to_cpu(resp->tdcf_de_offset);
+ if (offs >= ti->shmti[sid].len - de->info->data_sz)
+ return -EPROTO;
+
+ tde = to_tde(de);
+ tde->base = ti->shmti[sid].base;
+ tde->offset = offs;
+ /* A handy reference to the Epilogue updated */
+ tde->eplg = SHMTI_EPLG(&ti->shmti[sid]);
+
+ payld = tde->base + tde->offset;
+ if (USE_BLK_TS(payld) && !tde->bts) {
+ tde->bts = scmi_telemetry_blkts_bind(ti->ph->dev,
+ &ti->shmti[sid],
+ payld,
+ &ti->xa_bts);
+ if (WARN_ON(!tde->bts))
+ return -EPROTO;
+ }
+ } else {
+ int ret;
+
+ /*
+ * A full SHMTI scan is needed when enabling a
+ * group or its timestamps in order to retrieve
+ * offsets: node that when group-timestamp is
+ * enabled for composing DEs a re-scan is needed
+ * since some DEs could have been relocated due
+ * to lack of space in the TDCF.
+ */
+ ret = scmi_telemetry_shmti_scan(ti, sid, 0, SCAN_UPDATE);
+ if (ret)
+ dev_warn(ti->ph->dev,
+ "Failed group-scan of SHMTI ID:%d\n", sid);
+ }
+ } else if (!is_group) {
+ struct telemetry_de *tde;
+
+ tde = to_tde(de);
+ if (tde->bts) {
+ /* Unlink the related BLK_TS on disable */
+ scmi_telemetry_blkts_put(ti->ph->dev, tde->bts);
+ tde->bts = NULL;
+ }
+ }
+
+ return 0;
+}
+
+static int __scmi_telemetry_state_set(const struct scmi_protocol_handle *ph,
+ bool is_group, bool *enable,
+ bool *enabled_state, bool *tstamp,
+ bool *tstamp_enabled_state, void *obj)
+{
+ struct scmi_msg_resp_telemetry_de_configure *resp;
+ struct scmi_msg_telemetry_de_configure *msg;
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_telemetry_group *grp;
+ struct scmi_telemetry_de *de;
+ unsigned int obj_id;
+ struct scmi_xfer *t;
+ int ret;
+
+ if (!enabled_state || !tstamp_enabled_state)
+ return -EINVAL;
+
+ /* Is anything to do at all on this DE ? */
+ if (!is_group && (!enable || *enable == *enabled_state) &&
+ (!tstamp || *tstamp == *tstamp_enabled_state))
+ return 0;
+
+ /*
+ * DE is currently disabled AND no enable state change was requested,
+ * while timestamp is being changed: update only local state...no need
+ * to send a message.
+ */
+ if (!is_group && !enable && !*enabled_state) {
+ *tstamp_enabled_state = *tstamp;
+ return 0;
+ }
+
+ if (!is_group) {
+ de = obj;
+ obj_id = de->info->id;
+ } else {
+ grp = obj;
+ obj_id = grp->info->id;
+ }
+
+ ret = ph->xops->xfer_get_init(ph, TELEMETRY_DE_CONFIGURE,
+ sizeof(*msg), sizeof(*resp), &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ /* Note that BOTH DE and GROUPS have a first ID field.. */
+ msg->id = cpu_to_le32(obj_id);
+ /* Default to disable mode for one DE */
+ msg->flags = DE_DISABLE_ONE;
+ msg->flags |= FIELD_PREP(GENMASK(3, 3),
+ is_group ? EVENT_GROUP : EVENT_DE);
+
+ if ((!enable && *enabled_state) || (enable && *enable)) {
+ /* Already enabled but tstamp_enabled state changed */
+ if (tstamp) {
+ /* Here, tstamp cannot be NULL too */
+ msg->flags |= *tstamp ? DE_ENABLE_WTH_TSTAMP :
+ DE_ENABLE_NO_TSTAMP;
+ } else {
+ msg->flags |= *tstamp_enabled_state ?
+ DE_ENABLE_WTH_TSTAMP : DE_ENABLE_NO_TSTAMP;
+ }
+ }
+
+ resp = t->rx.buf;
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret) {
+ ret = scmi_telemetry_state_set_resp_process(ti, de, resp, is_group);
+ if (!ret) {
+ /* Update cached state on success */
+ if (enable)
+ *enabled_state = *enable;
+ if (tstamp)
+ *tstamp_enabled_state = *tstamp;
+
+ if (is_group)
+ scmi_telemetry_group_state_update(ti, grp, enable,
+ tstamp);
+ }
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static int scmi_telemetry_state_get(const struct scmi_protocol_handle *ph,
+ u32 id, bool *enabled, bool *tstamp_enabled)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_telemetry_de *de;
+
+ if (!enabled || !tstamp_enabled)
+ return -EINVAL;
+
+ de = xa_load(&ti->xa_des, id);
+ if (!de)
+ return -ENODEV;
+
+ *enabled = de->enabled;
+ *tstamp_enabled = de->tstamp_enabled;
+
+ return 0;
+}
+
+static int scmi_telemetry_state_set(const struct scmi_protocol_handle *ph,
+ bool is_group, u32 id, bool *enable,
+ bool *tstamp)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+ bool *enabled_state, *tstamp_enabled_state;
+ struct scmi_telemetry_res_info *rinfo;
+ void *obj;
+
+ rinfo = ti->res_get(ti);
+ if (!is_group) {
+ struct scmi_telemetry_de *de;
+
+ de = xa_load(&ti->xa_des, id);
+ if (!de)
+ return -ENODEV;
+
+ enabled_state = &de->enabled;
+ tstamp_enabled_state = &de->tstamp_enabled;
+ obj = de;
+ } else {
+ struct scmi_telemetry_group *grp;
+
+ if (id >= ti->info.base.num_groups)
+ return -EINVAL;
+
+ grp = &rinfo->grps[id];
+
+ enabled_state = &grp->enabled;
+ tstamp_enabled_state = &grp->tstamp_enabled;
+ obj = grp;
+ }
+
+ return __scmi_telemetry_state_set(ph, is_group, enable, enabled_state,
+ tstamp, tstamp_enabled_state, obj);
+}
+
+static int scmi_telemetry_all_disable(const struct scmi_protocol_handle *ph,
+ bool is_group)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_msg_telemetry_de_configure *msg;
+ struct scmi_telemetry_res_info *rinfo;
+ struct scmi_xfer *t;
+ int ret;
+
+ rinfo = ti->res_get(ti);
+ ret = ph->xops->xfer_get_init(ph, TELEMETRY_DE_CONFIGURE,
+ sizeof(*msg), 0, &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ msg->flags = DE_DISABLE_ALL;
+ if (is_group)
+ msg->flags |= GROUP_SELECTOR;
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret) {
+ for (int i = 0; i < ti->info.base.num_des; i++)
+ rinfo->des[i]->enabled = false;
+
+ if (is_group) {
+ for (int i = 0; i < ti->info.base.num_groups; i++)
+ rinfo->grps[i].enabled = false;
+ }
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static int
+scmi_telemetry_collection_configure(const struct scmi_protocol_handle *ph,
+ unsigned int res_id, bool grp_ignore,
+ bool *enable,
+ unsigned int *update_interval_ms,
+ enum scmi_telemetry_collection *mode)
+{
+ enum scmi_telemetry_collection *current_mode, next_mode;
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_msg_telemetry_config_set *msg;
+ unsigned int *active_update_interval;
+ struct scmi_xfer *t;
+ bool tlm_enable;
+ u32 interval;
+ int ret;
+
+ if (mode && *mode == SCMI_TLM_NOTIFICATION &&
+ !ti->info.continuos_update_support)
+ return -EINVAL;
+
+ if (res_id != SCMI_TLM_GRP_INVALID && res_id >= ti->info.base.num_groups)
+ return -EINVAL;
+
+ if (res_id == SCMI_TLM_GRP_INVALID || grp_ignore) {
+ active_update_interval = &ti->info.active_update_interval;
+ current_mode = &ti->info.current_mode;
+ } else {
+ struct scmi_telemetry_res_info *rinfo;
+
+ rinfo = ti->res_get(ti);
+ active_update_interval =
+ &rinfo->grps[res_id].active_update_interval;
+ current_mode = &rinfo->grps[res_id].current_mode;
+ }
+
+ if (!enable && !update_interval_ms && (!mode || *mode == *current_mode))
+ return 0;
+
+ ret = ph->xops->xfer_get_init(ph, TELEMETRY_CONFIG_SET,
+ sizeof(*msg), 0, &t);
+ if (ret)
+ return ret;
+
+ if (!update_interval_ms)
+ interval = cpu_to_le32(*active_update_interval);
+ else
+ interval = *update_interval_ms;
+
+ tlm_enable = enable ? *enable : ti->info.enabled;
+ next_mode = mode ? *mode : *current_mode;
+
+ msg = t->tx.buf;
+ msg->grp_id = res_id;
+ msg->control = tlm_enable ? TELEMETRY_ENABLE : 0;
+ msg->control |= grp_ignore ? TELEMETRY_SET_SELECTOR_ALL :
+ TELEMETRY_SET_SELECTOR_GROUP;
+ msg->control |= TELEMETRY_MODE_SET(next_mode);
+ msg->sampling_rate = interval;
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret) {
+ ti->info.enabled = tlm_enable;
+ *current_mode = next_mode;
+ ti->info.notif_enabled = *current_mode == SCMI_TLM_NOTIFICATION;
+ if (update_interval_ms)
+ *active_update_interval = interval;
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static inline void scmi_telemetry_de_data_fc_read(struct telemetry_de *tde,
+ u64 *tstamp, u64 *val)
+{
+ struct fc_tsline __iomem *fc = tde->base + tde->offset;
+
+ *val = LINE_DATA_GET(fc);
+ if (tstamp) {
+ if (tde->de.tstamp_support)
+ *tstamp = LINE_TSTAMP_GET(fc);
+ else
+ *tstamp = 0;
+ }
+}
+
+static void scmi_telemetry_scan_update(struct telemetry_info *ti, u64 ts)
+{
+ struct telemetry_de *tde;
+
+ /* Scan all SHMTIs ... */
+ for (int id = 0; id < ti->num_shmti; id++) {
+ int ret;
+
+ ret = scmi_telemetry_shmti_scan(ti, id, ts, SCAN_LOOKUP);
+ if (ret)
+ dev_warn(ti->ph->dev,
+ "Failed update-scan of SHMTI ID:%d\n", id);
+ }
+
+ if (!ti->info.fc_support)
+ return;
+
+ /* Need to enumerate resources to access fastchannels */
+ ti->res_get(ti);
+ list_for_each_entry(tde, &ti->fcs_des, item) {
+ u64 val, tstamp;
+
+ if (!tde->de.enabled)
+ continue;
+
+ scmi_telemetry_de_data_fc_read(tde, &tstamp, &val);
+
+ guard(mutex)(&tde->mtx);
+ tde->last_val = val;
+ if (tde->de.tstamp_enabled)
+ tde->last_ts = tstamp;
+ else
+ tde->last_ts = 0;
+ }
+}
+
+/*
+ * TDCF and TS Line Management Notes
+ * ---------------------------------
+ * (from a chat with ATG)
+ *
+ * TCDF Payload Metadata notable bits:
+ * - Bit[3]: USE BLK Tstamp
+ * - Bit[2]: USE LINE Tstamp
+ * - Bit[1]: Tstamp VALID
+ *
+ * CASE_1:
+ * -------
+ * + A DE is enabled with timestamp disabled, so the TS fields are
+ * NOT present
+ * -> BIT[3:1] = 000b
+ *
+ * - 1/A LINE_TSTAMP
+ * ------------------
+ * + that DE is then 're-enabled' with TS: so it was ON, it remains
+ * ON but using DE_CONFIGURE I now enabled also TS, so the
+ * platform relocates it at the end of the SHMTI and return the
+ * new offset
+ * -> BIT[3:1] = 011b
+ *
+ * - 1/B BLK_TSTAMP
+ * ------------------
+ * + that DE is then 're-enabled' with BLK TS: so it was ON, it
+ * remains ON but using DE_CONFIGURE, we now also enabled the TS,
+ * so the platform will:
+ * - IF a preceding BLK_TS line exist (with same clock freq)
+ * it relocates the DE at the end of the SHMTI and return the
+ * new offset (if there is enough room, if not in another SHMTI)
+ * - IF a preceding BLK_TS line DOES NOT exist (same clock freq)
+ * it creates a new BLK_TS line at the end of the SHMTI and then
+ * relocates the DE after the new BLK_TS and return the
+ * new offset (if there is enough room, if not in another SHMTI)
+ * -> BIT[3:1] = 101b
+ *
+ * + the hole left from the relocated DE can be reused by the platform
+ * to fit another equally sized DE. (i.e. without shuffling around any
+ * other enabled DE, since that would cause a change of the known offset)
+ *
+ * CASE_2:
+ * -------
+ * + A DE is enabled with LINE timestamp enabled, so the TS_Line is there
+ * -> BIT[3:1] = 011b
+ * + that DE has its timestamp disabled: again, you can do this without
+ * disabling it fully but just disabling the TS, so now that TS_line
+ * fields are still physiclly there but NOT valid
+ * -> BIT[3:1] = 010b
+ * + the hole from the timestamp remain there unused until
+ * - you enable again the TS so the hole is used again
+ * -> BIT[3:1] = 011b
+ * OR
+ * - you disable fully the DE and then re-enable it with the TS
+ * -> potentially CASE_1 the DE is relocated on enable
+ * + same kind of dynamic applies if the DE had a BLK_TS line
+ */
+static int scmi_telemetry_tdcf_de_parse(struct telemetry_de *tde,
+ u64 *tstamp, u64 *val)
+{
+ struct tdcf __iomem *tdcf = tde->base;
+ u64 startm, endm;
+ int retries = SCMI_TLM_TDCF_MAX_RETRIES;
+
+ if (!tdcf)
+ return -ENODEV;
+
+ do {
+ struct payload __iomem *payld;
+
+ /* A bit of exponential backoff between retries */
+ fsleep((SCMI_TLM_TDCF_MAX_RETRIES - retries) * 1000);
+
+ startm = TDCF_START_SEQ_GET(tdcf);
+ if (IS_BAD_START_SEQ(startm))
+ continue;
+
+ /* Has anything changed at all at the SHMTI level ? */
+ scoped_guard(mutex, &tde->mtx) {
+ if (tde->last_magic == startm) {
+ *val = tde->last_val;
+ if (tstamp)
+ *tstamp = tde->last_ts;
+ return 0;
+ }
+ }
+
+ payld = tde->base + tde->offset;
+ if (DATA_INVALID(payld))
+ return -EINVAL;
+
+ if (IS_BLK_TS(payld))
+ return -EINVAL;
+
+ if (PAYLD_ID(payld) != tde->de.info->id)
+ return -EINVAL;
+
+ /* Data is always valid since NOT handling BLK TS lines here */
+ *val = LINE_DATA_GET(&payld->l);
+ /* Collect the right TS */
+ if (tstamp) {
+ if (!TS_VALID(payld)) {
+ *tstamp = 0;
+ } else if (USE_LINE_TS(payld)) {
+ *tstamp = LINE_TSTAMP_GET(&payld->tsl);
+ } else if (USE_BLK_TS(payld)) {
+ /*
+ * A valid line using BLK_TS should have been
+ * initialized with the related BLK_TS when
+ * enabled.
+ */
+ if (WARN_ON(!tde->bts))
+ return -EPROTO;
+ *tstamp = scmi_telemetry_blkts_read(startm,
+ tde->bts);
+ }
+ }
+
+ endm = TDCF_END_SEQ_GET(tde->eplg);
+ } while (startm != endm && --retries);
+
+ if (startm != endm)
+ return -EPROTO;
+
+ guard(mutex)(&tde->mtx);
+ tde->last_magic = startm;
+ tde->last_val = *val;
+ if (tstamp)
+ tde->last_ts = *tstamp;
+
+ return 0;
+}
+
+static int scmi_telemetry_de_read(struct telemetry_de *tde, u64 *tstamp, u64 *val)
+{
+ if (!tde->de.fc_support)
+ return scmi_telemetry_tdcf_de_parse(tde, tstamp, val);
+
+ scmi_telemetry_de_data_fc_read(tde, tstamp, val);
+
+ return 0;
+}
+
+static int scmi_telemetry_de_collect(struct telemetry_info *ti,
+ struct scmi_telemetry_de *de,
+ u64 *tstamp, u64 *val)
+{
+ struct telemetry_de *tde = to_tde(de);
+
+ if (!de->enabled)
+ return -EINVAL;
+
+ /*
+ * DE readings returns cached values when:
+ * - DE data value was retrieved via notification
+ */
+ scoped_guard(mutex, &tde->mtx) {
+ if (tde->cached) {
+ *val = tde->last_val;
+ if (tstamp)
+ *tstamp = tde->last_ts;
+ return 0;
+ }
+ }
+
+ return scmi_telemetry_de_read(tde, tstamp, val);
+}
+
+static int scmi_telemetry_de_cached_read(struct telemetry_info *ti,
+ struct scmi_telemetry_de *de,
+ u64 *tstamp, u64 *val)
+{
+ struct telemetry_de *tde = to_tde(de);
+
+ if (!de->enabled)
+ return -EINVAL;
+
+ guard(mutex)(&tde->mtx);
+ *val = tde->last_val;
+ if (tstamp)
+ *tstamp = tde->last_ts;
+
+ return 0;
+}
+
+static int scmi_telemetry_de_data_read(const struct scmi_protocol_handle *ph,
+ struct scmi_telemetry_de_sample *sample)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_telemetry_de *de;
+
+ if (!ti->info.enabled || !sample)
+ return -EINVAL;
+
+ de = xa_load(&ti->xa_des, sample->id);
+ if (!de)
+ return -ENODEV;
+
+ return scmi_telemetry_de_collect(ti, de, &sample->tstamp, &sample->val);
+}
+
+static int
+scmi_telemetry_samples_collect(struct telemetry_info *ti, int grp_id,
+ int *num_samples,
+ struct scmi_telemetry_de_sample *samples)
+{
+ struct scmi_telemetry_res_info *rinfo;
+ int max_samples;
+
+ max_samples = *num_samples;
+ *num_samples = 0;
+
+ rinfo = ti->res_get(ti);
+ for (int i = 0; i < rinfo->num_des; i++) {
+ struct scmi_telemetry_de *de;
+ u64 val, tstamp;
+ int ret;
+
+ de = rinfo->des[i];
+ if (grp_id != SCMI_TLM_GRP_INVALID &&
+ (!de->grp || de->grp->info->id != grp_id))
+ continue;
+
+ ret = scmi_telemetry_de_cached_read(ti, de, &tstamp, &val);
+ if (ret)
+ continue;
+
+ if (*num_samples == max_samples)
+ return -ENOSPC;
+
+ samples[*num_samples].tstamp = tstamp;
+ samples[*num_samples].val = val;
+ samples[*num_samples].id = de->info->id;
+
+ (*num_samples)++;
+ }
+
+ return 0;
+}
+
+static int scmi_telemetry_des_bulk_read(const struct scmi_protocol_handle *ph,
+ int grp_id, int *num_samples,
+ struct scmi_telemetry_de_sample *samples)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ if (!ti->info.enabled || !num_samples || !samples)
+ return -EINVAL;
+
+ /* Trigger a full SHMTIs & FCs scan */
+ scmi_telemetry_scan_update(ti, 0);
+
+ /* Collect all last cached values */
+ return scmi_telemetry_samples_collect(ti, grp_id, num_samples, samples);
+}
+
+static void
+scmi_telemetry_msg_payld_process(struct telemetry_info *ti,
+ unsigned int num_dwords, u32 *dwords,
+ ktime_t timestamp)
+{
+ struct scmi_telemetry_res_info *rinfo;
+ u32 next = 0;
+
+ rinfo = ti->res_get(ti);
+ if (!rinfo->fully_enumerated) {
+ dev_warn_once(ti->ph->dev,
+ "Cannot process DEs in message payload. Drop.\n");
+ return;
+ }
+
+ while (next < num_dwords) {
+ struct payload *payld = (struct payload *)&dwords[next];
+ struct scmi_telemetry_de *de;
+ struct telemetry_de *tde;
+ u32 de_id;
+
+ next += USE_LINE_TS(payld) ?
+ TS_LINE_DATA_PAYLD_WORDS : LINE_DATA_PAYLD_WORDS;
+
+ if (DATA_INVALID(payld)) {
+ dev_err(ti->ph->dev, "MSG - Received INVALID DATA line\n");
+ continue;
+ }
+
+ de_id = le32_to_cpu(payld->id);
+ de = xa_load(&ti->xa_des, de_id);
+ if (!de || !de->enabled) {
+ dev_err(ti->ph->dev,
+ "MSG - Received INVALID DE - ID:%u enabled:%d\n",
+ de_id, de ? (de->enabled ? 'Y' : 'N') : 'X');
+ continue;
+ }
+
+ tde = to_tde(de);
+ guard(mutex)(&tde->mtx);
+ tde->cached = true;
+ tde->last_val = LINE_DATA_GET(&payld->tsl);
+ /* TODO BLK_TS in notification payloads */
+ if (USE_LINE_TS(payld) && TS_VALID(payld))
+ tde->last_ts = LINE_TSTAMP_GET(&payld->tsl);
+ else
+ tde->last_ts = 0;
+ }
+}
+
+static int scmi_telemetry_des_sample_get(const struct scmi_protocol_handle *ph,
+ int grp_id, int *num_samples,
+ struct scmi_telemetry_de_sample *samples)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_msg_telemetry_config_set *msg;
+ struct scmi_xfer *t;
+ bool grp_ignore;
+ int ret;
+
+ if (!ti->info.enabled || !num_samples || !samples)
+ return -EINVAL;
+
+ grp_ignore = grp_id == SCMI_TLM_GRP_INVALID ? true : false;
+ if (!grp_ignore && grp_id >= ti->info.base.num_groups)
+ return -EINVAL;
+
+ ret = ph->xops->xfer_get_init(ph, TELEMETRY_CONFIG_SET,
+ sizeof(*msg), 0, &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ msg->grp_id = grp_id;
+ msg->control = TELEMETRY_ENABLE;
+ msg->control |= grp_ignore ? TELEMETRY_SET_SELECTOR_ALL :
+ TELEMETRY_SET_SELECTOR_GROUP;
+ msg->control |= TELEMETRY_MODE_SINGLE;
+ msg->sampling_rate = 0;
+
+ ret = ph->xops->do_xfer_with_response(ph, t);
+ if (!ret) {
+ struct scmi_msg_resp_telemetry_reading_complete *r = t->rx.buf;
+
+ /* Update cached DEs values from payload */
+ if (r->num_dwords)
+ scmi_telemetry_msg_payld_process(ti, r->num_dwords,
+ r->dwords, 0);
+ /* Scan and update SMHTIs and FCs */
+ scmi_telemetry_scan_update(ti, 0);
+
+ /* Collect all last cached values */
+ ret = scmi_telemetry_samples_collect(ti, grp_id, num_samples,
+ samples);
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static int scmi_telemetry_config_get(const struct scmi_protocol_handle *ph,
+ bool *enabled, int *mode,
+ u32 *update_interval)
+{
+ return -EOPNOTSUPP;
+}
+
+static void scmi_telemetry_local_resources_reset(struct telemetry_info *ti)
+{
+ struct scmi_telemetry_res_info *rinfo;
+
+ /* Get rinfo as it is...without triggering an enumeration */
+ rinfo = __scmi_telemetry_resources_get(ti);
+ /* Clear all local state...*/
+ for (int i = 0; i < rinfo->num_des; i++) {
+ rinfo->des[i]->enabled = false;
+ rinfo->des[i]->tstamp_enabled = false;
+ }
+ for (int i = 0; i < rinfo->num_groups; i++) {
+ rinfo->grps[i].enabled = false;
+ rinfo->grps[i].tstamp_enabled = false;
+ rinfo->grps[i].current_mode = SCMI_TLM_ONDEMAND;
+ rinfo->grps[i].active_update_interval = 0;
+ }
+}
+
+static int scmi_telemetry_reset(const struct scmi_protocol_handle *ph)
+{
+ struct scmi_xfer *t;
+ int ret;
+
+ ret = ph->xops->xfer_get_init(ph, TELEMETRY_RESET, sizeof(u32), 0, &t);
+ if (ret)
+ return ret;
+
+ put_unaligned_le32(0, t->tx.buf);
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret) {
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ scmi_telemetry_local_resources_reset(ti);
+ /* Fetch agaon states state from platform.*/
+ ret = scmi_telemetry_initial_state_lookup(ti);
+ if (ret)
+ dev_warn(ph->dev,
+ FW_BUG "Cannot retrieve initial state after reset.\n");
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
+ .info_get = scmi_telemetry_info_get,
+ .de_lookup = scmi_telemetry_de_lookup,
+ .res_get = scmi_telemetry_resources_get,
+ .state_get = scmi_telemetry_state_get,
+ .state_set = scmi_telemetry_state_set,
+ .all_disable = scmi_telemetry_all_disable,
+ .collection_configure = scmi_telemetry_collection_configure,
+ .de_data_read = scmi_telemetry_de_data_read,
+ .des_bulk_read = scmi_telemetry_des_bulk_read,
+ .des_sample_get = scmi_telemetry_des_sample_get,
+ .config_get = scmi_telemetry_config_get,
+ .reset = scmi_telemetry_reset,
+};
+
+static bool
+scmi_telemetry_notify_supported(const struct scmi_protocol_handle *ph,
+ u8 evt_id, u32 src_id)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ return ti->info.continuos_update_support;
+}
+
+static int
+scmi_telemetry_set_notify_enabled(const struct scmi_protocol_handle *ph,
+ u8 evt_id, u32 src_id, bool enable)
+{
+ return 0;
+}
+
+static void *
+scmi_telemetry_fill_custom_report(const struct scmi_protocol_handle *ph,
+ u8 evt_id, ktime_t timestamp,
+ const void *payld, size_t payld_sz,
+ void *report, u32 *src_id)
+{
+ const struct scmi_telemetry_update_notify_payld *p = payld;
+ struct scmi_telemetry_update_report *r = report;
+
+ /* At least sized as an empty notification */
+ if (payld_sz < sizeof(*p))
+ return NULL;
+
+ r->timestamp = timestamp;
+ r->agent_id = le32_to_cpu(p->agent_id);
+ r->status = le32_to_cpu(p->status);
+ r->num_dwords = le32_to_cpu(p->num_dwords);
+ /*
+ * Allocated dwords and report are sized as max_msg_size, so as
+ * to allow for the maximum payload permitted by the configured
+ * transport. Overflow is not possible since out-of-size messages
+ * are dropped at the transport layer.
+ */
+ if (r->num_dwords)
+ memcpy(r->dwords, p->array, r->num_dwords * sizeof(u32));
+
+ *src_id = 0;
+
+ return r;
+}
+
+static const struct scmi_event tlm_events[] = {
+ {
+ .id = SCMI_EVENT_TELEMETRY_UPDATE,
+ .max_payld_sz = 0,
+ .max_report_sz = 0,
+ },
+};
+
+static const struct scmi_event_ops tlm_event_ops = {
+ .is_notify_supported = scmi_telemetry_notify_supported,
+ .set_notify_enabled = scmi_telemetry_set_notify_enabled,
+ .fill_custom_report = scmi_telemetry_fill_custom_report,
+};
+
+static const struct scmi_protocol_events tlm_protocol_events = {
+ .queue_sz = SCMI_PROTO_QUEUE_SZ,
+ .ops = &tlm_event_ops,
+ .evts = tlm_events,
+ .num_events = ARRAY_SIZE(tlm_events),
+ .num_sources = 1,
+};
+
+static int scmi_telemetry_notifier(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct scmi_telemetry_update_report *er = data;
+ struct telemetry_info *ti = telemetry_nb_to_info(nb);
+
+ if (er->status) {
+ dev_err(ti->ph->dev, "Bad Telemetry update notification - ret: %dn",
+ er->status);
+ return NOTIFY_DONE;
+ }
+
+ /* Lookup the embedded DEs in the notification payload ... */
+ if (er->num_dwords)
+ scmi_telemetry_msg_payld_process(ti, er->num_dwords,
+ er->dwords, er->timestamp);
+
+ /* ...scan the SHMTI/FCs for any other DE updates. */
+ if (ti->streaming_mode)
+ scmi_telemetry_scan_update(ti, er->timestamp);
+
+ return NOTIFY_OK;
+}
+
+static int scmi_telemetry_resources_alloc(struct telemetry_info *ti)
+{
+ /* Array to hold pointers to discovered DEs */
+ struct scmi_telemetry_de **des __free(kfree) =
+ kcalloc(ti->info.base.num_des, sizeof(*des), GFP_KERNEL);
+ if (!des)
+ return -ENOMEM;
+
+ /* The allocated DE descriptors */
+ struct telemetry_de *tdes __free(kfree) =
+ kcalloc(ti->info.base.num_des, sizeof(*tdes), GFP_KERNEL);
+ if (!tdes)
+ return -ENOMEM;
+
+ /* Allocate a set of contiguous DE info descriptors. */
+ struct scmi_tlm_de_info *dei_store __free(kfree) =
+ kcalloc(ti->info.base.num_des, sizeof(*dei_store), GFP_KERNEL);
+ if (!dei_store)
+ return -ENOMEM;
+
+ /* Array to hold descriptors of discovered GROUPs */
+ struct scmi_telemetry_group *grps __free(kfree) =
+ kcalloc(ti->info.base.num_groups, sizeof(*grps), GFP_KERNEL);
+ if (!grps)
+ return -ENOMEM;
+
+ /* Allocate a set of contiguous Group info descriptors. */
+ struct scmi_tlm_grp_info *grps_store __free(kfree) =
+ kcalloc(ti->info.base.num_groups, sizeof(*grps_store), GFP_KERNEL);
+ if (!grps_store)
+ return -ENOMEM;
+
+ struct scmi_telemetry_res_info *rinfo __free(kfree) =
+ kzalloc(sizeof(*rinfo), GFP_KERNEL);
+ if (!rinfo)
+ return -ENOMEM;
+
+ mutex_init(&ti->free_mtx);
+ INIT_LIST_HEAD(&ti->free_des);
+ for (int i = 0; i < ti->info.base.num_des; i++) {
+ mutex_init(&tdes[i].mtx);
+ /* Bind contiguous DE info structures */
+ tdes[i].de.info = &dei_store[i];
+ list_add_tail(&tdes[i].item, &ti->free_des);
+ }
+
+ for (int i = 0; i < ti->info.base.num_groups; i++) {
+ grps_store[i].id = i;
+ /* Bind contiguous Group info struct */
+ grps[i].info = &grps_store[i];
+ }
+
+ INIT_LIST_HEAD(&ti->fcs_des);
+
+ ti->tdes = no_free_ptr(tdes);
+
+ rinfo->des = no_free_ptr(des);
+ rinfo->dei_store = no_free_ptr(dei_store);
+ rinfo->grps = no_free_ptr(grps);
+ rinfo->grps_store = no_free_ptr(grps_store);
+
+ ti->rinfo = no_free_ptr(rinfo);
+
+ return 0;
+}
+
+static void scmi_telemetry_resources_free(void *arg)
+{
+ struct telemetry_info *ti = arg;
+
+ kfree(ti->tdes);
+ kfree(ti->rinfo->des);
+ kfree(ti->rinfo->dei_store);
+ kfree(ti->rinfo->grps);
+ kfree(ti->rinfo->grps_store);
+
+ kfree(ti->rinfo);
+}
+
+static struct scmi_telemetry_res_info *
+__scmi_telemetry_resources_get(struct telemetry_info *ti)
+{
+ return ACCESS_PRIVATE(ti, rinfo);
+}
+
+static struct scmi_telemetry_res_info *
+scmi_telemetry_resources_enumerate(struct telemetry_info *ti)
+{
+ struct device *dev = ti->ph->dev;
+ int ret;
+
+ /*
+ * Ensure this init function can be called only once and
+ * handles properly concurrent calls.
+ */
+ if (atomic_cmpxchg(&ti->rinfo_initializing, 0, 1)) {
+ if (!completion_done(&ti->rinfo_initdone))
+ wait_for_completion(&ti->rinfo_initdone);
+ goto out;
+ }
+
+ ret = scmi_telemetry_de_descriptors_get(ti);
+ if (ret) {
+ dev_err(dev, FW_BUG "Cannot enumerate DEs resources. Carry-on.\n");
+ goto done;
+ }
+
+ ret = scmi_telemetry_enumerate_groups_intervals(ti);
+ if (ret) {
+ dev_err(dev, FW_BUG "Cannot enumerate group intervals. Carry-on.\n");
+ goto done;
+ }
+
+ ti->rinfo->fully_enumerated = true;
+done:
+ /* Disable initialization permanently */
+ smp_store_mb(ti->res_get, __scmi_telemetry_resources_get);
+ complete_all(&ti->rinfo_initdone);
+
+out:
+ return ti->rinfo;
+}
+
+static int scmi_telemetry_instance_init(struct telemetry_info *ti)
+{
+ int ret;
+
+ /* Allocate and Initialize on first call... */
+ ret = scmi_telemetry_resources_alloc(ti);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(ti->ph->dev,
+ scmi_telemetry_resources_free, ti);
+ if (ret)
+ return ret;
+
+ xa_init(&ti->xa_des);
+ /* Setup resources lazy initialization */
+ atomic_set(&ti->rinfo_initializing, 0);
+ init_completion(&ti->rinfo_initdone);
+ /* Ensure the new res_get() operation is visible after this point */
+ smp_store_mb(ti->res_get, scmi_telemetry_resources_enumerate);
+
+ return 0;
+}
+
+static int scmi_telemetry_protocol_init(const struct scmi_protocol_handle *ph)
+{
+ struct device *dev = ph->dev;
+ struct telemetry_info *ti;
+ u32 version;
+ int ret;
+
+ ret = ph->xops->version_get(ph, &version);
+ if (ret)
+ return ret;
+
+ dev_dbg(dev, "Telemetry Version %d.%d\n",
+ PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version));
+
+ ti = devm_kzalloc(dev, sizeof(*ti), GFP_KERNEL);
+ if (!ti)
+ return -ENOMEM;
+
+ ti->ph = ph;
+
+ ret = scmi_telemetry_protocol_attributes_get(ti);
+ if (ret) {
+ dev_err(dev, FW_BUG "Cannot retrieve protocol attributes. Abort\n");
+ return ret;
+ }
+
+ ret = scmi_telemetry_instance_init(ti);
+ if (ret) {
+ dev_err(dev, "Cannot initialize instance. Abort.\n");
+ return ret;
+ }
+
+ ret = scmi_telemetry_enumerate_common_intervals(ti);
+ if (ret)
+ dev_warn(dev, FW_BUG "Cannot enumerate update intervals. Carry-on.\n");
+
+ ret = scmi_telemetry_enumerate_shmti(ti);
+ if (ret)
+ dev_warn(dev, FW_BUG "Cannot enumerate SHMTIs. Carry-on.\n");
+
+ ret = scmi_telemetry_initial_state_lookup(ti);
+ if (ret)
+ dev_warn(dev, FW_BUG "Cannot retrieve initial state. Carry-on.\n");
+
+ ti->info.base.version = version;
+
+ ret = ph->set_priv(ph, ti, version);
+ if (ret)
+ return ret;
+
+ /*
+ * Register a notifier anyway straight upon protocol initialization
+ * since there could be some DEs that are ONLY reported by notifications
+ * even though the chosen collection method was SHMTI/FCs.
+ */
+ if (ti->info.continuos_update_support) {
+ ti->telemetry_nb.notifier_call = &scmi_telemetry_notifier;
+ ret = ph->notifier_register(ph, SCMI_EVENT_TELEMETRY_UPDATE,
+ NULL, &ti->telemetry_nb);
+ if (ret)
+ dev_warn(ph->dev,
+ "Could NOT register Telemetry notifications\n");
+ }
+
+ return ret;
+}
+
+static const struct scmi_protocol scmi_telemetry = {
+ .id = SCMI_PROTOCOL_TELEMETRY,
+ .owner = THIS_MODULE,
+ .instance_init = &scmi_telemetry_protocol_init,
+ .ops = &tlm_proto_ops,
+ .events = &tlm_protocol_events,
+ .supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
+};
+
+DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(telemetry, scmi_telemetry)
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index c6efe4f371ac..d58b81ffd81e 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -2,17 +2,21 @@
/*
* SCMI Message Protocol driver header
*
- * Copyright (C) 2018-2021 ARM Ltd.
+ * Copyright (C) 2018-2026 ARM Ltd.
*/
#ifndef _LINUX_SCMI_PROTOCOL_H
#define _LINUX_SCMI_PROTOCOL_H
#include <linux/bitfield.h>
+#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/notifier.h>
#include <linux/types.h>
+#include <uapi/linux/limits.h>
+#include <uapi/linux/scmi.h>
+
#define SCMI_MAX_STR_SIZE 64
#define SCMI_SHORT_NAME_MAX_SIZE 16
#define SCMI_MAX_NUM_RATES 16
@@ -820,6 +824,178 @@ struct scmi_pinctrl_proto_ops {
int (*pin_free)(const struct scmi_protocol_handle *ph, u32 pin);
};
+enum scmi_telemetry_de_type {
+ SCMI_TLM_DE_TYPE_USPECIFIED,
+ SCMI_TLM_DE_TYPE_ACCUMUL_IDLE_RESIDENCY,
+ SCMI_TLM_DE_TYPE_ACCUMUL_IDLE_COUNTS,
+ SCMI_TLM_DE_TYPE_ACCUMUL_OTHERS,
+ SCMI_TLM_DE_TYPE_INSTA_IDLE_STATE,
+ SCMI_TLM_DE_TYPE_INSTA_OTHERS,
+ SCMI_TLM_DE_TYPE_AVERAGE,
+ SCMI_TLM_DE_TYPE_STATUS,
+ SCMI_TLM_DE_TYPE_RESERVED_START,
+ SCMI_TLM_DE_TYPE_RESERVED_END = 0xef,
+ SCMI_TLM_DE_TYPE_OEM_START = 0xf0,
+ SCMI_TLM_DE_TYPE_OEM_END = 0xff,
+};
+
+enum scmi_telemetry_compo_type {
+ SCMI_TLM_COMPO_TYPE_USPECIFIED,
+ SCMI_TLM_COMPO_TYPE_CPU,
+ SCMI_TLM_COMPO_TYPE_CLUSTER,
+ SCMI_TLM_COMPO_TYPE_GPU,
+ SCMI_TLM_COMPO_TYPE_NPU,
+ SCMI_TLM_COMPO_TYPE_INTERCONNECT,
+ SCMI_TLM_COMPO_TYPE_MEM_CNTRL,
+ SCMI_TLM_COMPO_TYPE_L1_CACHE,
+ SCMI_TLM_COMPO_TYPE_L2_CACHE,
+ SCMI_TLM_COMPO_TYPE_L3_CACHE,
+ SCMI_TLM_COMPO_TYPE_LL_CACHE,
+ SCMI_TLM_COMPO_TYPE_SYS_CACHE,
+ SCMI_TLM_COMPO_TYPE_DISP_CNTRL,
+ SCMI_TLM_COMPO_TYPE_IPU,
+ SCMI_TLM_COMPO_TYPE_CHIPLET,
+ SCMI_TLM_COMPO_TYPE_PACKAGE,
+ SCMI_TLM_COMPO_TYPE_SOC,
+ SCMI_TLM_COMPO_TYPE_SYSTEM,
+ SCMI_TLM_COMPO_TYPE_SMCU,
+ SCMI_TLM_COMPO_TYPE_ACCEL,
+ SCMI_TLM_COMPO_TYPE_BATTERY,
+ SCMI_TLM_COMPO_TYPE_CHARGER,
+ SCMI_TLM_COMPO_TYPE_PMIC,
+ SCMI_TLM_COMPO_TYPE_BOARD,
+ SCMI_TLM_COMPO_TYPE_MEMORY,
+ SCMI_TLM_COMPO_TYPE_PERIPH,
+ SCMI_TLM_COMPO_TYPE_PERIPH_SUBC,
+ SCMI_TLM_COMPO_TYPE_LID,
+ SCMI_TLM_COMPO_TYPE_DISPLAY,
+ SCMI_TLM_COMPO_TYPE_RESERVED_START = 0x1d,
+ SCMI_TLM_COMPO_TYPE_RESERVED_END = 0xdf,
+ SCMI_TLM_COMPO_TYPE_OEM_START = 0xe0,
+ SCMI_TLM_COMPO_TYPE_OEM_END = 0xff,
+};
+
+#define SCMI_TLM_GET_UPDATE_INTERVAL_SECS(x) \
+ (le32_get_bits((x), GENMASK(20, 5)))
+#define SCMI_TLM_GET_UPDATE_INTERVAL_EXP(x) (sign_extend32((x), 4))
+
+#define SCMI_TLM_GET_UPDATE_INTERVAL(x) (FIELD_GET(GENMASK(20, 0), (x)))
+#define SCMI_TLM_BUILD_UPDATE_INTERVAL(s, e) \
+ (FIELD_PREP(GENMASK(20, 5), (s)) | FIELD_PREP(GENMASK(4, 0), (e)))
+
+enum scmi_telemetry_collection {
+ SCMI_TLM_ONDEMAND,
+ SCMI_TLM_NOTIFICATION,
+ SCMI_TLM_SINGLE_READ,
+};
+
+#define SCMI_TLM_GRP_INVALID 0xFFFFFFFF
+struct scmi_telemetry_group {
+ bool enabled;
+ bool tstamp_enabled;
+ unsigned int *des;
+ char *des_str;
+ struct scmi_tlm_grp_info *info;
+ unsigned int active_update_interval;
+ struct scmi_tlm_intervals *intervals;
+ enum scmi_telemetry_collection current_mode;
+};
+
+struct scmi_telemetry_de {
+ bool tstamp_support;
+ bool fc_support;
+ bool name_support;
+ struct scmi_tlm_de_info *info;
+ struct scmi_telemetry_group *grp;
+ bool enabled;
+ bool tstamp_enabled;
+};
+
+struct scmi_telemetry_res_info {
+ bool fully_enumerated;
+ unsigned int num_des;
+ struct scmi_telemetry_de **des;
+ struct scmi_tlm_de_info *dei_store;
+ unsigned int num_groups;
+ struct scmi_telemetry_group *grps;
+ struct scmi_tlm_grp_info *grps_store;
+};
+
+struct scmi_telemetry_info {
+ bool single_read_support;
+ bool continuos_update_support;
+ bool per_group_config_support;
+ bool reset_support;
+ bool fc_support;
+ struct scmi_tlm_base_info base;
+ unsigned int active_update_interval;
+ struct scmi_tlm_intervals *intervals;
+ bool enabled;
+ bool notif_enabled;
+ enum scmi_telemetry_collection current_mode;
+};
+
+struct scmi_telemetry_de_sample {
+ u32 id;
+ u64 tstamp;
+ u64 val;
+};
+
+/**
+ * struct scmi_telemetry_proto_ops - represents the various operations provided
+ * by SCMI Telemetry Protocol
+ *
+ * @info_get: get the general Telemetry information.
+ * @de_lookup: get a specific DE descriptor from the DE id.
+ * @res_get: get a reference to the Telemetry resources descriptor.
+ * @state_get: retrieve the specific DE or GROUP state.
+ * @state_set: enable/disable the specific DE or GROUP with or without timestamps.
+ * @all_disable: disable ALL DEs or GROUPs.
+ * @collection_configure: choose a sampling rate and enable SHMTI/FC sampling
+ * for on demand collection via @de_data_read or async
+ * notificatioins for all the enabled DEs.
+ * @de_data_read: on-demand read of a single DE and related optional timestamp:
+ * the value will be retrieved at the proper SHMTI offset OR
+ * from the dedicated FC area (if supported by that DE).
+ * @des_bulk_read: on-demand read of all the currently enabled DEs, or just
+ * the ones belonging to a specific group when provided.
+ * @des_sample_get: on-demand read of all the currently enabled DEs, or just
+ * the ones belonging to a specific group when provided.
+ * This causes an immediate update platform-side of all the
+ * enabled DEs.
+ * @config_get: retrieve current telemetry configuration.
+ * @reset: reset configuration and telemetry data.
+ */
+struct scmi_telemetry_proto_ops {
+ const struct scmi_telemetry_info __must_check *(*info_get)
+ (const struct scmi_protocol_handle *ph);
+ const struct scmi_telemetry_de __must_check *(*de_lookup)
+ (const struct scmi_protocol_handle *ph, u32 id);
+ const struct scmi_telemetry_res_info __must_check *(*res_get)
+ (const struct scmi_protocol_handle *ph);
+ int (*state_get)(const struct scmi_protocol_handle *ph,
+ u32 id, bool *enabled, bool *tstamp_enabled);
+ int (*state_set)(const struct scmi_protocol_handle *ph,
+ bool is_group, u32 id, bool *enable, bool *tstamp);
+ int (*all_disable)(const struct scmi_protocol_handle *ph, bool group);
+ int (*collection_configure)(const struct scmi_protocol_handle *ph,
+ unsigned int res_id, bool grp_ignore,
+ bool *enable,
+ unsigned int *update_interval_ms,
+ enum scmi_telemetry_collection *mode);
+ int (*de_data_read)(const struct scmi_protocol_handle *ph,
+ struct scmi_telemetry_de_sample *sample);
+ int __must_check (*des_bulk_read)(const struct scmi_protocol_handle *ph,
+ int grp_id, int *num_samples,
+ struct scmi_telemetry_de_sample *samples);
+ int __must_check (*des_sample_get)(const struct scmi_protocol_handle *ph,
+ int grp_id, int *num_samples,
+ struct scmi_telemetry_de_sample *samples);
+ int (*config_get)(const struct scmi_protocol_handle *ph, bool *enabled,
+ int *mode, u32 *update_interval);
+ int (*reset)(const struct scmi_protocol_handle *ph);
+};
+
/**
* struct scmi_notify_ops - represents notifications' operations provided by
* SCMI core
@@ -926,6 +1102,7 @@ enum scmi_std_protocol {
SCMI_PROTOCOL_VOLTAGE = 0x17,
SCMI_PROTOCOL_POWERCAP = 0x18,
SCMI_PROTOCOL_PINCTRL = 0x19,
+ SCMI_PROTOCOL_TELEMETRY = 0x1b,
SCMI_PROTOCOL_LAST = 0x7f,
};
@@ -1027,6 +1204,7 @@ enum scmi_notification_events {
SCMI_EVENT_SYSTEM_POWER_STATE_NOTIFIER = 0x0,
SCMI_EVENT_POWERCAP_CAP_CHANGED = 0x0,
SCMI_EVENT_POWERCAP_MEASUREMENTS_CHANGED = 0x1,
+ SCMI_EVENT_TELEMETRY_UPDATE = 0x0,
};
struct scmi_power_state_changed_report {
@@ -1114,4 +1292,12 @@ struct scmi_powercap_meas_changed_report {
unsigned int domain_id;
unsigned int power;
};
+
+struct scmi_telemetry_update_report {
+ ktime_t timestamp;
+ unsigned int agent_id;
+ int status;
+ unsigned int num_dwords;
+ unsigned int dwords[];
+};
#endif /* _LINUX_SCMI_PROTOCOL_H */
--
2.52.0
Hi Cristian,
On 14/01/2026 11:46, Cristian Marussi wrote:
> Add basic support for SCMI V4.0 Telemetry protocol including SHMTI,
> FastChannels, Notifications and Single Sample Reads collection methods.
>
> Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
> ---
> v1 --> v2
> - Add proper ioread accessors for TDCF areas
> - Rework resource allocation logic and lifecycle
> - Introduce new resources accessors and res_get() operation to
> implement lazy enumeration
> - Support boot-on telemetry:
> + Add DE_ENABLED_LIST cmd support
> + Add CONFIG_GET cmd support
> + Add TDCF_SCAN for best effort enumeration
> + Harden driver against out-of-spec FW with out of spec cmds
> - Use FCs list
> - Rework de_info_lookup to a moer general de_lookup
> - Fixed reset to use CONFIG_GET and DE_ENABLED_LIST to gather initial
> state
> - Using sign_extend32 helper
> - Added counted_by marker where appropriate
> ---
> drivers/firmware/arm_scmi/Makefile | 2 +-
> drivers/firmware/arm_scmi/driver.c | 2 +
> drivers/firmware/arm_scmi/protocols.h | 1 +
> drivers/firmware/arm_scmi/telemetry.c | 2671 +++++++++++++++++++++++++
> include/linux/scmi_protocol.h | 188 +-
> 5 files changed, 2862 insertions(+), 2 deletions(-)
> create mode 100644 drivers/firmware/arm_scmi/telemetry.c
>
> diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
> index 780cd62b2f78..fe55b7aa0707 100644
> --- a/drivers/firmware/arm_scmi/Makefile
> +++ b/drivers/firmware/arm_scmi/Makefile
> @@ -8,7 +8,7 @@ scmi-driver-$(CONFIG_ARM_SCMI_RAW_MODE_SUPPORT) += raw_mode.o
> scmi-transport-$(CONFIG_ARM_SCMI_HAVE_SHMEM) = shmem.o
> scmi-transport-$(CONFIG_ARM_SCMI_HAVE_MSG) += msg.o
> scmi-protocols-y := base.o clock.o perf.o power.o reset.o sensors.o system.o voltage.o powercap.o
> -scmi-protocols-y += pinctrl.o
> +scmi-protocols-y += pinctrl.o telemetry.o
> scmi-module-objs := $(scmi-driver-y) $(scmi-protocols-y) $(scmi-transport-y)
>
> obj-$(CONFIG_ARM_SCMI_PROTOCOL) += transports/
> diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
> index 1085c70ca457..dd5da75a19b0 100644
> --- a/drivers/firmware/arm_scmi/driver.c
> +++ b/drivers/firmware/arm_scmi/driver.c
> @@ -3450,6 +3450,7 @@ static int __init scmi_driver_init(void)
> scmi_system_register();
> scmi_powercap_register();
> scmi_pinctrl_register();
> + scmi_telemetry_register();
>
> return platform_driver_register(&scmi_driver);
> }
> @@ -3468,6 +3469,7 @@ static void __exit scmi_driver_exit(void)
> scmi_system_unregister();
> scmi_powercap_unregister();
> scmi_pinctrl_unregister();
> + scmi_telemetry_unregister();
>
> platform_driver_unregister(&scmi_driver);
>
> diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h
> index afca1336267b..766b68a3084e 100644
> --- a/drivers/firmware/arm_scmi/protocols.h
> +++ b/drivers/firmware/arm_scmi/protocols.h
> @@ -385,5 +385,6 @@ DECLARE_SCMI_REGISTER_UNREGISTER(sensors);
> DECLARE_SCMI_REGISTER_UNREGISTER(voltage);
> DECLARE_SCMI_REGISTER_UNREGISTER(system);
> DECLARE_SCMI_REGISTER_UNREGISTER(powercap);
> +DECLARE_SCMI_REGISTER_UNREGISTER(telemetry);
>
> #endif /* _SCMI_PROTOCOLS_H */
> diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
> new file mode 100644
> index 000000000000..16bcdcdc1dc3
> --- /dev/null
> +++ b/drivers/firmware/arm_scmi/telemetry.c
> @@ -0,0 +1,2671 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * System Control and Management Interface (SCMI) Telemetry Protocol
> + *
> + * Copyright (C) 2026 ARM Ltd.
> + */
> +
> +#include <linux/atomic.h>
> +#include <linux/bitfield.h>
> +#include <linux/device.h>
> +#include <linux/compiler_types.h>
> +#include <linux/completion.h>
> +#include <linux/err.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +#include <linux/limits.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/refcount.h>
> +#include <linux/slab.h>
> +#include <linux/sprintf.h>
> +#include <linux/string.h>
> +#include <linux/xarray.h>
> +
> +#include "protocols.h"
> +#include "notify.h"
> +
> +/* Updated only after ALL the mandatory features for that version are merged */
> +#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x10000
> +
> +#define SCMI_TLM_TDCF_MAX_RETRIES 5
> +
> +enum scmi_telemetry_protocol_cmd {
> + TELEMETRY_LIST_SHMTI = 0x3,
> + TELEMETRY_DE_DESCRIPTION = 0x4,
> + TELEMETRY_LIST_UPDATE_INTERVALS = 0x5,
> + TELEMETRY_DE_CONFIGURE = 0x6,
> + TELEMETRY_DE_ENABLED_LIST = 0x7,
> + TELEMETRY_CONFIG_SET = 0x8,
> + TELEMETRY_READING_COMPLETE = TELEMETRY_CONFIG_SET,
> + TELEMETRY_CONFIG_GET = 0x9,
> + TELEMETRY_RESET = 0xA,
> +};
> +
> +struct scmi_msg_resp_telemetry_protocol_attributes {
> + __le32 de_num;
> + __le32 groups_num;
> + __le32 de_implementation_rev_dword[SCMI_TLM_DE_IMPL_MAX_DWORDS];
> + __le32 attributes;
> +#define SUPPORTS_SINGLE_READ(x) ((x) & BIT(31))
> +#define SUPPORTS_CONTINUOS_UPDATE(x) ((x) & BIT(30))
> +#define SUPPORTS_PER_GROUP_CONFIG(x) ((x) & BIT(18))
> +#define SUPPORTS_RESET(x) ((x) & BIT(17))
> +#define SUPPORTS_FC(x) ((x) & BIT(16))
> +};
> +
> +struct scmi_telemetry_update_notify_payld {
> + __le32 agent_id;
> + __le32 status;
> + __le32 num_dwords;
> + __le32 array[] __counted_by(num_dwords);
> +};
> +
> +struct scmi_shmti_desc {
> + __le32 id;
> + __le32 addr_low;
> + __le32 addr_high;
> + __le32 length;
> +};
> +
> +struct scmi_msg_resp_telemetry_shmti_list {
> + __le32 num_shmti;
> + struct scmi_shmti_desc desc[] __counted_by(num_shmti);
> +};
> +
> +struct de_desc_fc {
> + __le32 addr_low;
> + __le32 addr_high;
> + __le32 size;
> +};
> +
> +struct scmi_de_desc {
> + __le32 id;
> + __le32 grp_id;
> + __le32 data_sz;
> + __le32 attr_1;
> +#define IS_NAME_SUPPORTED(d) ((d)->attr_1 & BIT(31))
> +#define IS_FC_SUPPORTED(d) ((d)->attr_1 & BIT(30))
> +#define GET_DE_TYPE(d) (le32_get_bits((d)->attr_1, GENMASK(29, 22)))
> +#define IS_PERSISTENT(d) ((d)->attr_1 & BIT(21))
> +#define GET_DE_UNIT_EXP(d) \
> + ({ \
> + __u32 __signed_exp = \
> + le32_get_bits((d)->attr_1, GENMASK(20, 13)); \
> + \
> + sign_extend32(__signed_exp, 7); \
> + })
> +
> +#define GET_DE_UNIT(d) (le32_get_bits((d)->attr_1, GENMASK(12, 5)))
> +
> +#define GET_DE_TSTAMP_EXP(d) \
> + ({ \
> + __u32 __signed_exp = \
> + FIELD_GET(GENMASK(4, 1), (d)->attr_1); \
> + \
> + sign_extend32(__signed_exp, 3); \
> + })
> +#define IS_TSTAMP_SUPPORTED(d) ((d)->attr_1 & BIT(0))
> + __le32 attr_2;
> +#define GET_DE_INSTA_ID(d) (le32_get_bits((d)->attr_2, GENMASK(31, 24)))
> +#define GET_COMPO_INSTA_ID(d) (le32_get_bits((d)->attr_2, GENMASK(23, 8)))
> +#define GET_COMPO_TYPE(d) (le32_get_bits((d)->attr_2, GENMASK(7, 0)))
> + __le32 reserved;
> +};
> +
> +struct scmi_msg_resp_telemetry_de_description {
> + __le32 num_desc;
> + struct scmi_de_desc desc[] __counted_by(num_desc);
> +};
> +
> +struct scmi_msg_telemetry_update_intervals {
> + __le32 index;
> + __le32 group_identifier;
> +#define ALL_DES_NO_GROUP 0x0
> +#define SPECIFIC_GROUP_DES 0x1
> +#define ALL_DES_ANY_GROUP 0x2
> + __le32 flags;
> +};
> +
> +struct scmi_msg_resp_telemetry_update_intervals {
> + __le32 flags;
> +#define INTERVALS_DISCRETE(x) (!((x) & BIT(12)))
> + __le32 intervals[];
> +};
> +
> +struct scmi_msg_telemetry_de_enabled_list {
> + __le32 index;
> + __le32 flags;
> +};
> +
> +struct scmi_enabled_de_desc {
> + __le32 id;
> + __le32 mode;
> +};
> +
> +struct scmi_msg_resp_telemetry_de_enabled_list {
> + __le32 flags;
> + struct scmi_enabled_de_desc entry[];
> +};
> +
> +struct scmi_msg_telemetry_de_configure {
> + __le32 id;
> + __le32 flags;
> +#define DE_ENABLE_NO_TSTAMP BIT(0)
> +#define DE_ENABLE_WTH_TSTAMP BIT(1)
> +#define DE_DISABLE_ALL BIT(2)
> +#define GROUP_SELECTOR BIT(3)
> +#define EVENT_DE 0
> +#define EVENT_GROUP 1
> +#define DE_DISABLE_ONE 0x0
> +};
> +
> +struct scmi_msg_resp_telemetry_de_configure {
> + __le32 shmti_id;
> +#define IS_SHMTI_ID_VALID(x) ((x) != 0xFFFFFFFF)
> + __le32 tdcf_de_offset;
> +};
> +
> +struct scmi_msg_telemetry_config_set {
> + __le32 grp_id;
> + __le32 control;
> +#define TELEMETRY_ENABLE (BIT(0))
> +
> +#define TELEMETRY_MODE_SET(x) (FIELD_PREP(GENMASK(4, 1), (x)))
> +#define TLM_ONDEMAND (0)
> +#define TLM_NOTIFS (1)
> +#define TLM_SINGLE (2)
> +#define TELEMETRY_MODE_ONDEMAND TELEMETRY_MODE_SET(TLM_ONDEMAND)
> +#define TELEMETRY_MODE_NOTIFS TELEMETRY_MODE_SET(TLM_NOTIFS)
> +#define TELEMETRY_MODE_SINGLE TELEMETRY_MODE_SET(TLM_SINGLE)
> +
> +#define TLM_ORPHANS (0)
> +#define TLM_GROUP (1)
> +#define TLM_ALL (2)
> +#define TELEMETRY_SET_SELECTOR(x) (FIELD_PREP(GENMASK(8, 5), (x)))
> +#define TELEMETRY_SET_SELECTOR_ORPHANS TELEMETRY_SET_SELECTOR(TLM_ORPHANS)
> +#define TELEMETRY_SET_SELECTOR_GROUP TELEMETRY_SET_SELECTOR(TLM_GROUP)
> +#define TELEMETRY_SET_SELECTOR_ALL TELEMETRY_SET_SELECTOR(TLM_ALL)
> + __le32 sampling_rate;
> +};
> +
> +struct scmi_msg_resp_telemetry_reading_complete {
> + __le32 num_dwords;
> + __le32 dwords[] __counted_by(num_dwords);
> +};
> +
> +struct scmi_msg_telemetry_config_get {
> + __le32 grp_id;
> + __le32 flags;
> +#define TELEMETRY_GET_SELECTOR(x) (FIELD_PREP(GENMASK(3, 0), (x)))
> +#define TELEMETRY_GET_SELECTOR_ORPHANS TELEMETRY_GET_SELECTOR(TLM_ORPHANS)
> +#define TELEMETRY_GET_SELECTOR_GROUP TELEMETRY_GET_SELECTOR(TLM_GROUP)
> +#define TELEMETRY_GET_SELECTOR_ALL TELEMETRY_GET_SELECTOR(TLM_ALL)
> +};
> +
> +struct scmi_msg_resp_telemetry_config_get {
> + __le32 control;
> +#define TELEMETRY_MODE_GET (FIELD_GET(GENMASK(4, 1)))
> + __le32 sampling_rate;
> +};
> +
> +/* TDCF */
> +
> +#define _I(__a) (ioread32((void __iomem *)(__a)))
> +
> +#define TO_CPU_64(h, l) ((((u64)(h)) << 32) | (l))
> +
> +enum scan_mode {
> + SCAN_LOOKUP,
> + SCAN_UPDATE,
> + SCAN_DISCOVERY
> +};
> +
> +struct fc_line {
> + u32 data_low;
> + u32 data_high;
> +};
> +
> +struct fc_tsline {
> + u32 data_low;
> + u32 data_high;
> + u32 ts_low;
> + u32 ts_high;
> +};
> +
> +struct line {
> + u32 data_low;
> + u32 data_high;
> +};
> +
> +struct blk_tsline {
> + u32 ts_low;
> + u32 ts_high;
> +};
> +
> +struct tsline {
> + u32 data_low;
> + u32 data_high;
> + u32 ts_low;
> + u32 ts_high;
> +};
> +
> +#define LINE_DATA_GET(f) \
> +({ \
> + typeof(f) _f = (f); \
> + \
> + (TO_CPU_64(_I(&_f->data_high), _I(&_f->data_low))); \
> +})
> +
> +#define LINE_TSTAMP_GET(f) \
> +({ \
> + typeof(f) _f = (f); \
> + \
> + (TO_CPU_64(_I(&_f->ts_high), _I(&_f->ts_low))); \
> +})
> +
> +#define BLK_TSTAMP_GET(f) LINE_TSTAMP_GET(f)
> +
> +struct payload {
> + u32 meta;
> +#define IS_BLK_TS(x) (_I(&((x)->meta)) & BIT(4))
> +#define USE_BLK_TS(x) (_I(&((x)->meta)) & BIT(3))
> +#define USE_LINE_TS(x) (_I(&((x)->meta)) & BIT(2))
> +#define TS_VALID(x) (_I(&((x)->meta)) & BIT(1))
> +#define DATA_INVALID(x) (_I(&((x)->meta)) & BIT(0))
> + u32 id;
> + union {
> + struct line l;
> + struct tsline tsl;
> + struct blk_tsline blk_tsl;
> + };
> +};
> +
> +#define PAYLD_ID(x) (_I(&(((struct payload *)(x))->id)))
> +
> +#define LINE_DATA_PAYLD_WORDS \
> + ((sizeof(u32) + sizeof(u32) + sizeof(struct line)) / sizeof(u32))
> +#define TS_LINE_DATA_PAYLD_WORDS \
> + ((sizeof(u32) + sizeof(u32) + sizeof(struct tsline)) / sizeof(u32))
> +
> +#define QWORDS_LINE_DATA_PAYLD (LINE_DATA_PAYLD_WORDS / 2)
> +#define QWORDS_TS_LINE_DATA_PAYLD (TS_LINE_DATA_PAYLD_WORDS / 2)
> +
> +struct prlg {
> + u32 seq_low;
> + u32 seq_high;
> + u32 num_qwords;
> + u32 _meta_header_high;
> +};
> +
> +struct eplg {
> + u32 seq_low;
> + u32 seq_high;
> +};
> +
> +#define TDCF_EPLG_SZ (sizeof(struct eplg))
> +
> +struct tdcf {
> + struct prlg prlg;
> + unsigned char payld[];
> +};
> +
> +#define QWORDS(_t) (_I(&(_t)->prlg.num_qwords))
> +
> +#define SHMTI_MIN_SIZE (sizeof(struct tdcf) + TDCF_EPLG_SZ)
> +
> +#define TDCF_BAD_END_SEQ GENMASK_U64(63, 0)
> +#define TDCF_START_SEQ_GET(x) \
> + ({ \
> + u64 _val; \
> + struct prlg *_p = &((x)->prlg); \
> + \
> + _val = TO_CPU_64(_I(&_p->seq_high), _I(&_p->seq_low)); \
> + (_val); \
> + })
> +
> +#define IS_BAD_START_SEQ(s) ((s) & 0x1)
> +
> +#define TDCF_END_SEQ_GET(e) \
> + ({ \
> + u64 _val; \
> + struct eplg *_e = (e); \
> + \
> + _val = TO_CPU_64(_I(&_e->seq_high), _I(&_e->seq_low)); \
> + (_val); \
> + })
> +
> +struct telemetry_shmti {
> + int id;
> + void __iomem *base;
> + u32 len;
> + u64 last_magic;
> +};
> +
> +#define SHMTI_EPLG(s) \
> + ({ \
> + struct telemetry_shmti *_s = (s); \
> + void *_eplg; \
> + \
> + _eplg = _s->base + _s->len - TDCF_EPLG_SZ; \
> + (_eplg); \
> + })
> +
> +struct telemetry_block_ts {
> + refcount_t users;
> + /* Protect block_ts accesses */
> + struct mutex mtx;
> + u64 last_ts;
> + u64 last_magic;
> + struct payload __iomem *payld;
> + struct xarray *xa_bts;
> +};
> +
> +struct telemetry_de {
> + bool enumerated;
> + bool cached;
> + void __iomem *base;
> + void __iomem *eplg;
> + u32 offset;
> + /* NOTE THAT DE data_sz is registered in scmi_telemetry_de */
> + u32 fc_size;
> + /* Protect last_val/ts/magic accesses */
> + struct mutex mtx;
> + u64 last_val;
> + u64 last_ts;
> + u64 last_magic;
> + struct list_head item;
> + struct telemetry_block_ts *bts;
> + struct scmi_telemetry_de de;
> +};
> +
> +#define to_tde(d) container_of(d, struct telemetry_de, de)
> +
> +#define DE_ENABLED_WITH_TSTAMP 2
> +
> +struct telemetry_info {
> + bool streaming_mode;
> + int num_shmti;
> + const struct scmi_protocol_handle *ph;
> + struct telemetry_shmti *shmti;
> + struct telemetry_de *tdes;
> + struct scmi_telemetry_group *grps;
> + struct xarray xa_des;
> + struct xarray xa_bts;
> + /* Mutex to protect access to @free_des */
> + struct mutex free_mtx;
> + struct list_head free_des;
> + struct list_head fcs_des;
> + struct scmi_telemetry_info info;
> + struct notifier_block telemetry_nb;
> + atomic_t rinfo_initializing;
> + struct completion rinfo_initdone;
> + struct scmi_telemetry_res_info __private *rinfo;
> + struct scmi_telemetry_res_info *(*res_get)(struct telemetry_info *ti);
> +};
> +
> +#define telemetry_nb_to_info(x) \
> + container_of(x, struct telemetry_info, telemetry_nb)
> +
> +static struct scmi_telemetry_res_info *
> +__scmi_telemetry_resources_get(struct telemetry_info *ti);
> +
> +static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
> + unsigned int shmti_id, u64 ts,
> + enum scan_mode mode);
> +
> +static struct telemetry_de *
> +scmi_telemetry_free_tde_get(struct telemetry_info *ti)
> +{
> + struct telemetry_de *tde;
> +
> + guard(mutex)(&ti->free_mtx);
> +
> + tde = list_first_entry_or_null(&ti->free_des, struct telemetry_de, item);
> + if (!tde)
> + return tde;
> +
> + list_del(&tde->item);
> +
> + return tde;
> +}
> +
> +static void scmi_telemetry_free_tde_put(struct telemetry_info *ti,
> + struct telemetry_de *tde)
> +{
> + guard(mutex)(&ti->free_mtx);
> +
> + list_add_tail(&tde->item, &ti->free_des);
> +}
> +
> +static struct telemetry_de *scmi_telemetry_tde_lookup(struct telemetry_info *ti,
> + unsigned int de_id)
> +{
> + struct scmi_telemetry_de *de;
> +
> + de = xa_load(&ti->xa_des, de_id);
> + if (!de)
> + return NULL;
> +
> + return to_tde(de);
> +}
> +
> +static struct telemetry_de *scmi_telemetry_tde_get(struct telemetry_info *ti,
> + unsigned int de_id)
> +{
> + static struct telemetry_de *tde;
> +
> + /* Pick a new tde */
> + tde = scmi_telemetry_free_tde_get(ti);
> + if (!tde) {
> + dev_err(ti->ph->dev, "Cannot allocate DE for ID:0x%08X\n", de_id);
> + return ERR_PTR(-ENOSPC);
> + }
> +
> + return tde;
> +}
> +
> +static int scmi_telemetry_tde_register(struct telemetry_info *ti,
> + struct telemetry_de *tde)
> +{
> + int ret;
> +
> + if (ti->rinfo->num_des >= ti->info.base.num_des) {
> + ret = -ENOSPC;
> + goto err;
> + }
> +
> + /* Store DE pointer by de_id ... */
> + ret = xa_insert(&ti->xa_des, tde->de.info->id, &tde->de, GFP_KERNEL);
> + if (ret)
> + goto err;
> +
> + /* ... and in the general array */
> + ti->rinfo->des[ti->rinfo->num_des++] = &tde->de;
> +
> + return 0;
> +
> +err:
> + dev_err(ti->ph->dev, "Cannot register DE for ID:0x%08X\n",
> + tde->de.info->id);
> +
> + return ret;
> +}
> +
> +struct scmi_tlm_de_priv {
> + struct telemetry_info *ti;
> + void *next;
> +};
> +
> +static int
> +scmi_telemetry_protocol_attributes_get(struct telemetry_info *ti)
> +{
> + struct scmi_msg_resp_telemetry_protocol_attributes *resp;
> + const struct scmi_protocol_handle *ph = ti->ph;
> + struct scmi_xfer *t;
> + int ret;
> +
> + ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0,
> + sizeof(*resp), &t);
> + if (ret)
> + return ret;
> +
> + resp = t->rx.buf;
> + ret = ph->xops->do_xfer(ph, t);
> + if (!ret) {
> + __le32 attr = resp->attributes;
> +
> + ti->info.base.num_des = le32_to_cpu(resp->de_num);
> + ti->info.base.num_groups = le32_to_cpu(resp->groups_num);
> + for (int i = 0; i < SCMI_TLM_DE_IMPL_MAX_DWORDS; i++)
> + ti->info.base.de_impl_version[i] =
> + le32_to_cpu(resp->de_implementation_rev_dword[i]);
> + ti->info.single_read_support = SUPPORTS_SINGLE_READ(attr);
> + ti->info.continuos_update_support = SUPPORTS_CONTINUOS_UPDATE(attr);
> + ti->info.per_group_config_support = SUPPORTS_PER_GROUP_CONFIG(attr);
> + ti->info.reset_support = SUPPORTS_RESET(attr);
> + ti->info.fc_support = SUPPORTS_FC(attr);
> + ti->num_shmti = le32_get_bits(attr, GENMASK(15, 0));
> + }
> +
> + ph->xops->xfer_put(ph, t);
> +
> + return ret;
> +}
> +
> +static void iter_tlm_prepare_message(void *message,
> + unsigned int desc_index, const void *priv)
> +{
> + put_unaligned_le32(desc_index, message);
> +}
> +
> +static int iter_de_descr_update_state(struct scmi_iterator_state *st,
> + const void *response, void *priv)
> +{
> + const struct scmi_msg_resp_telemetry_de_description *r = response;
> + struct scmi_tlm_de_priv *p = priv;
> +
> + st->num_returned = le32_get_bits(r->num_desc, GENMASK(15, 0));
> + st->num_remaining = le32_get_bits(r->num_desc, GENMASK(31, 16));
> +
> + /* Initialized to first descriptor */
> + p->next = (void *)r->desc;
> +
> + return 0;
> +}
> +
> +static int scmi_telemetry_de_descriptor_parse(struct telemetry_info *ti,
> + struct telemetry_de *tde,
> + void **next)
> +{
> + struct scmi_telemetry_res_info *rinfo = ti->rinfo;
> + const struct scmi_de_desc *desc = *next;
> + unsigned int grp_id;
> +
> + tde->de.info->id = le32_to_cpu(desc->id);
> + grp_id = le32_to_cpu(desc->grp_id);
> + if (grp_id != SCMI_TLM_GRP_INVALID) {
> + /* Group descriptors are empty but allocated at this point */
> + if (grp_id >= ti->info.base.num_groups)
> + return -EINVAL;
> +
> + /* Link to parent group */
> + tde->de.info->grp_id = grp_id;
> + tde->de.grp = &rinfo->grps[grp_id];
> + }
> +
> + tde->de.info->data_sz = le32_to_cpu(desc->data_sz);
> + tde->de.info->type = GET_DE_TYPE(desc);
> + tde->de.info->unit = GET_DE_UNIT(desc);
> + tde->de.info->unit_exp = GET_DE_UNIT_EXP(desc);
> + tde->de.info->tstamp_exp = GET_DE_TSTAMP_EXP(desc);
> + tde->de.info->instance_id = GET_DE_INSTA_ID(desc);
> + tde->de.info->compo_instance_id = GET_COMPO_INSTA_ID(desc);
> + tde->de.info->compo_type = GET_COMPO_TYPE(desc);
> + tde->de.info->persistent = IS_PERSISTENT(desc);
> + tde->de.tstamp_support = IS_TSTAMP_SUPPORTED(desc);
> + tde->de.fc_support = IS_FC_SUPPORTED(desc);
> + tde->de.name_support = IS_NAME_SUPPORTED(desc);
> + /* Update DE_DESCRIPTOR size for the next iteration */
> + *next += sizeof(*desc);
> + if (tde->de.fc_support) {
> + u32 size;
> + u64 phys_addr;
> + void __iomem *addr;
> + struct de_desc_fc *dfc;
> +
> + dfc = *next;
> + phys_addr = le32_to_cpu(dfc->addr_low);
> + phys_addr |= (u64)le32_to_cpu(dfc->addr_high) << 32;
> +
> + size = le32_to_cpu(dfc->size);
> + addr = devm_ioremap(ti->ph->dev, phys_addr, size);
> + if (!addr)
> + return -EADDRNOTAVAIL;
> +
> + tde->base = addr;
> + tde->offset = 0;
> + tde->fc_size = size;
> +
> + /* Add to FastChannels list */
> + list_add(&tde->item, &ti->fcs_des);
> +
> + /* Variably sized depending on FC support */
> + *next += sizeof(*dfc);
> + }
> +
> + if (tde->de.name_support) {
> + const char *de_name = *next;
> +
> + strscpy(tde->de.info->name, de_name, SCMI_SHORT_NAME_MAX_SIZE);
> + /* Variably sized depending on name support */
> + *next += SCMI_SHORT_NAME_MAX_SIZE;
> + }
> +
> + return 0;
> +}
> +
> +static int iter_de_descr_process_response(const struct scmi_protocol_handle *ph,
> + const void *response,
> + struct scmi_iterator_state *st,
> + void *priv)
> +{
> + struct scmi_tlm_de_priv *p = priv;
> + struct telemetry_info *ti = p->ti;
> + const struct scmi_de_desc *desc = p->next;
> + struct telemetry_de *tde;
> + bool discovered = false;
> + unsigned int de_id;
> + int ret;
> +
> + de_id = le32_to_cpu(desc->id);
> + /* Check if this DE has already been discovered by other means... */
> + tde = scmi_telemetry_tde_lookup(ti, de_id);
> + if (!tde) {
> + /* Create a new one */
> + tde = scmi_telemetry_tde_get(ti, de_id);
> + if (IS_ERR(tde))
> + return PTR_ERR(tde);
> +
> + discovered = true;
> + } else if (tde->enumerated) {
> + /* Cannot be a duplicate of a DE already created by enumeration */
> + dev_err(ph->dev,
> + "Discovered INVALID DE with DUPLICATED ID:0x%08X\n",
> + de_id);
> + return -EINVAL;
> + }
> +
> + ret = scmi_telemetry_de_descriptor_parse(ti, tde, &p->next);
> + if (ret)
> + goto err;
> +
> + if (discovered) {
> + /* Register if it was not already ... */
> + ret = scmi_telemetry_tde_register(ti, tde);
> + if (ret)
> + goto err;
> +
> + tde->enumerated = true;
> + }
> +
> + /* Account for this DE in group num_de counter */
> + if (tde->de.grp)
> + tde->de.grp->info->num_des++;
> +
> + return 0;
> +
> +err:
> + /* DE not enumerated at this point were created in this call */
> + if (!tde->enumerated)
> + scmi_telemetry_free_tde_put(ti, tde);
> +
> + return ret;
> +}
> +
> +static int scmi_telemetry_config_lookup(struct telemetry_info *ti,
> + unsigned int grp_id, bool *enabled,
> + unsigned int *active_update_interval)
> +{
> + const struct scmi_protocol_handle *ph = ti->ph;
> + struct scmi_msg_telemetry_config_get *msg;
> + struct scmi_msg_resp_telemetry_config_get *resp;
> + struct scmi_xfer *t;
> + int ret;
> +
> + ret = ph->xops->xfer_get_init(ph, TELEMETRY_CONFIG_GET,
> + sizeof(*msg), sizeof(*resp), &t);
> + if (ret)
> + return ret;
> +
> + msg = t->tx.buf;
> + msg->grp_id = grp_id;
> + msg->flags = grp_id == SCMI_TLM_GRP_INVALID ?
> + TELEMETRY_GET_SELECTOR_ORPHANS : TELEMETRY_GET_SELECTOR_GROUP;
> +
> + resp = t->rx.buf;
> + ret = ph->xops->do_xfer(ph, t);
> + if (!ret) {
> + *enabled = resp->control & TELEMETRY_ENABLE;
> + *active_update_interval =
> + SCMI_TLM_GET_UPDATE_INTERVAL(resp->sampling_rate);
> + }
> +
> + ph->xops->xfer_put(ph, t);
> +
> + return 0;
> +}
> +
> +static int scmi_telemetry_group_config_lookup(struct telemetry_info *ti,
> + struct scmi_telemetry_group *grp)
> +{
> + return scmi_telemetry_config_lookup(ti, grp->info->id, &grp->enabled,
> + &grp->active_update_interval);
> +}
> +
> +static void iter_enabled_list_prepare_message(void *message,
> + unsigned int desc_index,
> + const void *priv)
> +{
> + struct scmi_msg_telemetry_de_enabled_list *msg = message;
> +
> + msg->index = cpu_to_le32(desc_index);
> + msg->flags = 0;
> +}
> +
> +static int iter_enabled_list_update_state(struct scmi_iterator_state *st,
> + const void *response, void *priv)
> +{
> + const struct scmi_msg_resp_telemetry_de_enabled_list *r = response;
> +
> + st->num_returned = le32_get_bits(r->flags, GENMASK(15, 0));
> + st->num_remaining = le32_get_bits(r->flags, GENMASK(31, 16));
> +
> + /*
> + * total enabled is not declared previously anywhere so we
> + * assume it's returned+remaining on first call.
> + */
> + if (!st->max_resources)
> + st->max_resources = st->num_returned + st->num_remaining;
> +
> + return 0;
> +}
> +
> +static int
> +iter_enabled_list_process_response(const struct scmi_protocol_handle *ph,
> + const void *response,
> + struct scmi_iterator_state *st, void *priv)
> +{
> + const struct scmi_msg_resp_telemetry_de_enabled_list *r = response;
> + const struct scmi_enabled_de_desc *desc;
> + struct telemetry_info *ti = priv;
> + struct telemetry_de *tde;
> + u32 de_id;
> + int ret;
> +
> + desc = &r->entry[st->loop_idx];
> + de_id = le32_to_cpu(desc->id);
> + if (scmi_telemetry_tde_lookup(ti, de_id)) {
> + dev_err(ph->dev,
> + "Found INVALID DE with DUPLICATED ID:0x%08X\n", de_id);
> + return -EINVAL;
> + }
> +
> + tde = scmi_telemetry_tde_get(ti, de_id);
> + if (IS_ERR(tde))
> + return PTR_ERR(tde);
> +
> + tde->de.info->id = de_id;
> + tde->de.enabled = true;
> + tde->de.tstamp_enabled = desc->mode == DE_ENABLED_WITH_TSTAMP;
> +
> + ret = scmi_telemetry_tde_register(ti, tde);
> + if (ret) {
> + scmi_telemetry_free_tde_put(ti, tde);
> + return ret;
> + }
> +
> + dev_dbg(ph->dev, "Registered new ENABLED DE with ID:0x%08X\n",
> + tde->de.info->id);
> +
> + return 0;
> +}
> +
> +static int scmi_telemetry_enumerate_des_enabled_list(struct telemetry_info *ti)
> +{
> + const struct scmi_protocol_handle *ph = ti->ph;
> + struct scmi_iterator_ops ops = {
> + .prepare_message = iter_enabled_list_prepare_message,
> + .update_state = iter_enabled_list_update_state,
> + .process_response = iter_enabled_list_process_response,
> + };
> + void *iter;
> + int ret;
> +
> + iter = ph->hops->iter_response_init(ph, &ops, 0,
> + TELEMETRY_DE_ENABLED_LIST,
> + sizeof(u32) * 2, ti);
> + if (IS_ERR(iter))
> + return PTR_ERR(iter);
> +
> + ret = ph->hops->iter_response_run(iter);
> + if (ret)
> + return ret;
> +
> + dev_info(ti->ph->dev, "Found %u enabled DEs.\n", ti->rinfo->num_des);
> +
> + return 0;
> +}
> +
> +static int scmi_telemetry_initial_state_lookup(struct telemetry_info *ti)
> +{
> + struct device *dev = ti->ph->dev;
> + int ret;
> +
> + ret = scmi_telemetry_config_lookup(ti, SCMI_TLM_GRP_INVALID,
> + &ti->info.enabled,
> + &ti->info.active_update_interval);
> + if (ret)
> + return ret;
> +
> + if (ti->info.enabled) {
> + /*
> + * When Telemetry is found already enabled on the platform,
> + * proceed with passive discovery using DE_ENABLED_LIST and
> + * TCDF scanning: note that this CAN only discover DEs exposed
> + * via SHMTIs.
> + * FastChannel DEs need a proper DE_DESCRIPTION enumeration,
> + * while, even though incoming Notifications could be used for
> + * passive discovery too, it would carry a considerable risk
> + * of assimilating trash as DEs.
> + */
> + dev_info(dev,
> + "Telemetry found enabled with update interval %ux10^%d\n",
> + SCMI_TLM_GET_UPDATE_INTERVAL_SECS(ti->info.active_update_interval),
> + SCMI_TLM_GET_UPDATE_INTERVAL_EXP(ti->info.active_update_interval));
> + /*
> + * Query enabled DEs list: collect states.
> + * It will include DEs from any interface.
> + * Enabled groups still NOT enumerated.
> + */
> + ret = scmi_telemetry_enumerate_des_enabled_list(ti);
> + if (ret)
> + dev_warn(dev, FW_BUG "Cannot query enabled DE list. Carry-on.\n");
> +
> + /* Discover DEs on SHMTis: collect states/offsets/values */
> + for (int id = 0; id < ti->num_shmti; id++) {
> + ret = scmi_telemetry_shmti_scan(ti, id, 0, SCAN_DISCOVERY);
> + if (ret)
> + dev_warn(dev, "Failed discovery-scan of SHMTI ID:%d\n", id);
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int
> +scmi_telemetry_de_groups_init(struct device *dev, struct telemetry_info *ti)
> +{
> + struct scmi_telemetry_res_info *rinfo = ti->rinfo;
> +
> + /* Allocate all groups DEs IDs arrays at first ... */
> + for (int i = 0; i < ti->info.base.num_groups; i++) {
> + struct scmi_telemetry_group *grp = &rinfo->grps[i];
> + size_t des_str_sz;
> +
> + unsigned int *des __free(kfree) = kcalloc(grp->info->num_des,
> + sizeof(unsigned int),
> + GFP_KERNEL);
> + if (!des)
> + return -ENOMEM;
> +
> + /*
> + * Max size 32bit ID string in Hex: 0xCAFECAFE
> + * - 10 digits + ' '/'\n' = 11 bytes per number
> + * - terminating NUL character
> + */
> + des_str_sz = grp->info->num_des * 11 + 1;
> + char *des_str __free(kfree) = kzalloc(des_str_sz, GFP_KERNEL);
> + if (!des_str)
> + return -ENOMEM;
> +
> + grp->des = no_free_ptr(des);
> + grp->des_str = no_free_ptr(des_str);
> + /* Reset group DE counter */
> + grp->info->num_des = 0;
> + }
> +
> + /* Scan DEs and populate DE IDs arrays for all groups */
> + for (int i = 0; i < rinfo->num_des; i++) {
> + struct scmi_telemetry_group *grp = rinfo->des[i]->grp;
> +
> + if (!grp)
> + continue;
> +
> + /*
> + * Note that, at this point, num_des is guaranteed to be
> + * sane (in-bounds) by construction.
> + */
> + grp->des[grp->info->num_des++] = i;
> + }
> +
> + /* Build composing DES string */
> + for (int i = 0; i < ti->info.base.num_groups; i++) {
> + struct scmi_telemetry_group *grp = &rinfo->grps[i];
> + size_t bufsize = grp->info->num_des * 11 + 1;
> + char *buf = grp->des_str;
> +
> + for (int j = 0; j < grp->info->num_des; j++) {
> + char term = j != (grp->info->num_des - 1) ? ' ' : '\0';
> + int len;
> +
> + len = scnprintf(buf, bufsize, "0x%04X%c",
I think it should be 0x%08X%c to print 8 bytes.
> + rinfo->des[grp->des[j]]->info->id, term);
> +
> + buf += len;
> + bufsize -= len;
> + }
> + }
> +
> + for (int i = 0; i < ti->info.base.num_groups; i++)
> + scmi_telemetry_group_config_lookup(ti, &rinfo->grps[i]);
> +
> + rinfo->num_groups = ti->info.base.num_groups;
> +
> + return 0;
> +}
> +
> +static int scmi_telemetry_de_descriptors_get(struct telemetry_info *ti)
> +{
> + const struct scmi_protocol_handle *ph = ti->ph;
> +
> + struct scmi_iterator_ops ops = {
> + .prepare_message = iter_tlm_prepare_message,
> + .update_state = iter_de_descr_update_state,
> + .process_response = iter_de_descr_process_response,
> + };
> + struct scmi_tlm_de_priv tpriv = {
> + .ti = ti,
> + .next = NULL,
> + };
> + void *iter;
> + int ret;
> +
> + if (!ti->info.base.num_des)
> + return 0;
> +
> + iter = ph->hops->iter_response_init(ph, &ops, ti->info.base.num_des,
> + TELEMETRY_DE_DESCRIPTION,
> + sizeof(u32), &tpriv);
> + if (IS_ERR(iter))
> + return PTR_ERR(iter);
> +
> + ret = ph->hops->iter_response_run(iter);
> + if (ret)
> + return ret;
> +
> + return scmi_telemetry_de_groups_init(ph->dev, ti);
> +}
> +
> +struct scmi_tlm_ivl_priv {
> + struct device *dev;
> + struct scmi_tlm_intervals **intrvs;
> + unsigned int grp_id;
> + unsigned int flags;
> +};
> +
> +static void iter_intervals_prepare_message(void *message,
> + unsigned int desc_index,
> + const void *priv)
> +{
> + struct scmi_msg_telemetry_update_intervals *msg = message;
> + const struct scmi_tlm_ivl_priv *p = priv;
> +
> + msg->index = cpu_to_le32(desc_index);
> + msg->group_identifier = cpu_to_le32(p->grp_id);
> + msg->flags = FIELD_PREP(GENMASK(3, 0), p->flags);
> +}
> +
> +static int iter_intervals_update_state(struct scmi_iterator_state *st,
> + const void *response, void *priv)
> +{
> + const struct scmi_msg_resp_telemetry_update_intervals *r = response;
> +
> + st->num_returned = le32_get_bits(r->flags, GENMASK(11, 0));
> + st->num_remaining = le32_get_bits(r->flags, GENMASK(31, 16));
> +
> + /*
> + * total intervals is not declared previously anywhere so we
> + * assume it's returned+remaining on first call.
> + */
> + if (!st->max_resources) {
> + struct scmi_tlm_ivl_priv *p = priv;
> + bool discrete;
> + int inum;
> +
> + discrete = INTERVALS_DISCRETE(r->flags);
> + /* Check consistency on first call */
> + if (!discrete && (st->num_returned != 3 || st->num_remaining != 0))
> + return -EINVAL;
> +
> + inum = st->num_returned + st->num_remaining;
> + struct scmi_tlm_intervals *intrvs __free(kfree) =
> + kzalloc(sizeof(*intrvs) + inum * sizeof(__u32), GFP_KERNEL);
> + if (!intrvs)
> + return -ENOMEM;
> +
> + intrvs->num = inum;
> + intrvs->discrete = discrete;
> + st->max_resources = intrvs->num;
> +
> + *p->intrvs = no_free_ptr(intrvs);
> + }
> +
> + return 0;
> +}
> +
> +static int
> +iter_intervals_process_response(const struct scmi_protocol_handle *ph,
> + const void *response,
> + struct scmi_iterator_state *st, void *priv)
> +{
> + const struct scmi_msg_resp_telemetry_update_intervals *r = response;
> + struct scmi_tlm_ivl_priv *p = priv;
> + struct scmi_tlm_intervals *intrvs = *p->intrvs;
> + unsigned int idx = st->loop_idx;
> +
> + intrvs->update_intervals[st->desc_index + idx] = r->intervals[idx];
> +
> + return 0;
> +}
> +
> +static int
> +scmi_tlm_enumerate_update_intervals(struct telemetry_info *ti,
> + struct scmi_tlm_intervals **intervals,
> + int grp_id, unsigned int flags)
> +{
> + struct scmi_iterator_ops ops = {
> + .prepare_message = iter_intervals_prepare_message,
> + .update_state = iter_intervals_update_state,
> + .process_response = iter_intervals_process_response,
> + };
> + const struct scmi_protocol_handle *ph = ti->ph;
> + struct scmi_tlm_ivl_priv ipriv = {
> + .dev = ph->dev,
> + .grp_id = grp_id,
> + .intrvs = intervals,
> + .flags = flags,
> + };
> + void *iter;
> +
> + iter = ph->hops->iter_response_init(ph, &ops, 0,
> + TELEMETRY_LIST_UPDATE_INTERVALS,
> + sizeof(struct scmi_msg_telemetry_update_intervals),
> + &ipriv);
> + if (IS_ERR(iter))
> + return PTR_ERR(iter);
> +
> + return ph->hops->iter_response_run(iter);
> +}
> +
> +static int
> +scmi_telemetry_enumerate_groups_intervals(struct telemetry_info *ti)
> +{
> + struct scmi_telemetry_res_info *rinfo = ti->rinfo;
> +
> + if (!ti->info.per_group_config_support)
> + return 0;
> +
> + for (int id = 0; id < rinfo->num_groups; id++) {
> + int ret;
> +
> + ret = scmi_tlm_enumerate_update_intervals(ti,
> + &rinfo->grps[id].intervals,
> + id, SPECIFIC_GROUP_DES);
> + if (ret)
> + return ret;
> +
> + rinfo->grps_store[id].num_intervals =
> + rinfo->grps[id].intervals->num;
> + }
> +
> + return 0;
> +}
> +
> +static void scmi_telemetry_intervals_free(void *interval)
> +{
> + kfree(interval);
> +}
> +
> +static int
> +scmi_telemetry_enumerate_common_intervals(struct telemetry_info *ti)
> +{
> + unsigned int flags;
> + int ret;
> +
> + flags = !ti->info.per_group_config_support ?
> + ALL_DES_ANY_GROUP : ALL_DES_NO_GROUP;
> +
> + ret = scmi_tlm_enumerate_update_intervals(ti, &ti->info.intervals,
> + SCMI_TLM_GRP_INVALID, flags);
> + if (ret)
> + return ret;
> +
> + /* A copy for UAPI access... */
> + ti->info.base.num_intervals = ti->info.intervals->num;
> +
> + /* Delegate freeing of allocated intervals to unbind time */
> + return devm_add_action_or_reset(ti->ph->dev,
> + scmi_telemetry_intervals_free,
> + ti->info.intervals);
> +}
> +
> +static int iter_shmti_update_state(struct scmi_iterator_state *st,
> + const void *response, void *priv)
> +{
> + const struct scmi_msg_resp_telemetry_shmti_list *r = response;
> +
> + st->num_returned = le32_get_bits(r->num_shmti, GENMASK(15, 0));
> + st->num_remaining = le32_get_bits(r->num_shmti, GENMASK(31, 16));
> +
> + return 0;
> +}
> +
> +static int iter_shmti_process_response(const struct scmi_protocol_handle *ph,
> + const void *response,
> + struct scmi_iterator_state *st,
> + void *priv)
> +{
> + const struct scmi_msg_resp_telemetry_shmti_list *r = response;
> + struct telemetry_info *ti = priv;
> + struct telemetry_shmti *shmti;
> + const struct scmi_shmti_desc *desc;
> + void __iomem *addr;
> + u64 phys_addr;
> + u32 len;
> +
> + desc = &r->desc[st->loop_idx];
> + shmti = &ti->shmti[st->desc_index + st->loop_idx];
> +
> + shmti->id = le32_to_cpu(desc->id);
> + phys_addr = le32_to_cpu(desc->addr_low);
> + phys_addr |= (u64)le32_to_cpu(desc->addr_high) << 32;
> +
> + len = le32_to_cpu(desc->length);
> + if (len < SHMTI_MIN_SIZE)
> + return -EINVAL;
> +
> + addr = devm_ioremap(ph->dev, phys_addr, len);
> + if (!addr)
> + return -EADDRNOTAVAIL;
> +
> + shmti->base = addr;
> + shmti->len = len;
> +
> + return 0;
> +}
> +
> +static int scmi_telemetry_shmti_list(const struct scmi_protocol_handle *ph,
> + struct telemetry_info *ti)
> +{
> + struct scmi_iterator_ops ops = {
> + .prepare_message = iter_tlm_prepare_message,
> + .update_state = iter_shmti_update_state,
> + .process_response = iter_shmti_process_response,
> + };
> + void *iter;
> +
> + iter = ph->hops->iter_response_init(ph, &ops, ti->info.base.num_des,
> + TELEMETRY_LIST_SHMTI,
> + sizeof(u32), ti);
> + if (IS_ERR(iter))
> + return PTR_ERR(iter);
> +
> + return ph->hops->iter_response_run(iter);
> +}
> +
> +static int scmi_telemetry_enumerate_shmti(struct telemetry_info *ti)
> +{
> + const struct scmi_protocol_handle *ph = ti->ph;
> + int ret;
> +
> + if (!ti->num_shmti)
> + return 0;
> +
> + ti->shmti = devm_kcalloc(ph->dev, ti->num_shmti, sizeof(*ti->shmti),
> + GFP_KERNEL);
> + if (!ti->shmti)
> + return -ENOMEM;
> +
> + ret = scmi_telemetry_shmti_list(ph, ti);
> + if (ret) {
> + dev_err(ph->dev, "Cannot get SHMTI list descriptors");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static const struct scmi_telemetry_info *
> +scmi_telemetry_info_get(const struct scmi_protocol_handle *ph)
> +{
> + struct telemetry_info *ti = ph->get_priv(ph);
> +
> + return &ti->info;
> +}
> +
> +static const struct scmi_telemetry_de *
> +scmi_telemetry_de_lookup(const struct scmi_protocol_handle *ph, u32 id)
> +{
> + struct telemetry_info *ti = ph->get_priv(ph);
> + struct scmi_telemetry_de *de;
> +
> + ti->res_get(ti);
> + de = xa_load(&ti->xa_des, id);
> + if (!de)
> + return NULL;
> +
> + return de;
> +}
> +
> +static const struct scmi_telemetry_res_info *
> +scmi_telemetry_resources_get(const struct scmi_protocol_handle *ph)
> +{
> + struct telemetry_info *ti = ph->get_priv(ph);
> +
> + return ti->res_get(ti);
> +}
> +
> +static u64
> +scmi_telemetry_blkts_read(u64 magic, struct telemetry_block_ts *bts)
> +{
> + if (WARN_ON(!bts || !refcount_read(&bts->users)))
> + return 0;
> +
> + guard(mutex)(&bts->mtx);
> +
> + if (bts->last_magic == magic)
> + return bts->last_ts;
> +
> + bts->last_ts = BLK_TSTAMP_GET(&bts->payld->blk_tsl);
> + bts->last_magic = magic;
> +
> + return bts->last_ts;
> +}
> +
> +static void scmi_telemetry_blkts_update(u64 magic,
> + struct telemetry_block_ts *bts)
> +{
> + guard(mutex)(&bts->mtx);
> +
> + if (bts->last_magic != magic) {
> + bts->last_ts = BLK_TSTAMP_GET(&bts->payld->blk_tsl);
> + bts->last_magic = magic;
> + }
> +}
> +
> +static void scmi_telemetry_blkts_put(struct device *dev,
> + struct telemetry_block_ts *bts)
> +{
> + if (refcount_dec_and_test(&bts->users)) {
> + scoped_guard(mutex, &bts->mtx)
> + xa_erase(bts->xa_bts, (unsigned long)bts->payld);
> + devm_kfree(dev, bts);
> + }
> +}
> +
> +static struct telemetry_block_ts *
> +scmi_telemetry_blkts_get(struct xarray *xa_bts, struct payload *payld)
> +{
> + struct telemetry_block_ts *bts;
> +
> + bts = xa_load(xa_bts, (unsigned long)payld);
> + if (!bts)
> + return NULL;
> +
> + refcount_inc(&bts->users);
> +
> + return bts;
> +}
> +
> +static struct payload *
> +scmi_telemetry_nearest_blk_ts(struct telemetry_shmti *shmti,
> + struct payload *last_payld)
> +{
> + struct payload *payld, *bts_payld = NULL;
> + struct tdcf __iomem *tdcf = shmti->base;
> + u32 *next;
> +
> + /* Scan from start of TDCF payloads up to last_payld */
> + payld = (struct payload *)tdcf->payld;
> + next = (u32 *)payld;
> + while (payld < last_payld) {
> + if (IS_BLK_TS(payld))
> + bts_payld = payld;
> +
> + next += USE_LINE_TS(payld) ?
> + TS_LINE_DATA_PAYLD_WORDS : LINE_DATA_PAYLD_WORDS;
> + payld = (struct payload *)next;
> + }
> +
> + return bts_payld;
> +}
> +
> +static struct telemetry_block_ts *
> +scmi_telemetry_blkts_lookup(struct device *dev, struct xarray *xa_bts,
> + struct payload *payld)
> +{
> + struct telemetry_block_ts *bts;
> +
> + bts = xa_load(xa_bts, (unsigned long)payld);
> + if (!bts) {
> + int ret;
> +
> + bts = devm_kzalloc(dev, sizeof(*bts), GFP_KERNEL);
> + if (!bts)
> + return NULL;
> +
> + refcount_set(&bts->users, 1);
> + bts->payld = payld;
> + bts->xa_bts = xa_bts;
> + mutex_init(&bts->mtx);
> + ret = xa_insert(xa_bts, (unsigned long)payld, bts, GFP_KERNEL);
> + if (ret) {
> + devm_kfree(dev, bts);
> + return NULL;
> + }
> + }
> +
> + return bts;
> +}
> +
> +static struct telemetry_block_ts *
> +scmi_telemetry_blkts_bind(struct device *dev, struct telemetry_shmti *shmti,
> + struct payload *payld, struct xarray *xa_bts)
> +{
> + struct telemetry_block_ts *bts;
> + struct payload *bts_payld;
> +
> + /* Find the BLK_TS immediately preceding this DE payld */
> + bts_payld = scmi_telemetry_nearest_blk_ts(shmti, payld);
> + if (!bts_payld)
> + return NULL;
> +
> + bts = scmi_telemetry_blkts_get(xa_bts, bts_payld);
> + if (bts)
> + return bts;
> +
> + return scmi_telemetry_blkts_lookup(dev, xa_bts, payld);
> +}
> +
> +static void scmi_telemetry_tdcf_blkts_parse(struct telemetry_info *ti,
> + struct payload __iomem *payld,
> + struct telemetry_shmti *shmti)
> +{
> + struct telemetry_block_ts *bts;
> +
> + /* Check for spec compliance */
> + if (USE_LINE_TS(payld) || USE_BLK_TS(payld) ||
> + DATA_INVALID(payld) || (PAYLD_ID(payld) != 0))
> + return;
> +
> + /* A BLK_TS descriptor MUST be returned: it is found or it is crated */
> + bts = scmi_telemetry_blkts_lookup(ti->ph->dev, &ti->xa_bts, payld);
> + if (WARN_ON(!bts))
> + return;
> +
> + /* Update the descriptor with the lastest TS*/
> + scmi_telemetry_blkts_update(shmti->last_magic, bts);
> +}
> +
> +static void scmi_telemetry_tdcf_data_parse(struct telemetry_info *ti,
> + struct payload __iomem *payld,
> + struct telemetry_shmti *shmti,
> + enum scan_mode mode)
> +{
> + bool ts_valid = TS_VALID(payld);
> + struct telemetry_de *tde;
> + bool discovered = false;
> + u64 val, tstamp = 0;
> + u32 de_id;
> +
> + de_id = PAYLD_ID(payld);
> + /* Is thi DE ID know ? */
> + tde = scmi_telemetry_tde_lookup(ti, de_id);
> + if (!tde) {
> + if (mode != SCAN_DISCOVERY)
> + return;
> +
> + /* In SCAN_DISCOVERY mode we allocate new DEs for unknown IDs */
> + tde = scmi_telemetry_tde_get(ti, de_id);
> + if (IS_ERR(tde))
> + return;
> +
> + tde->de.info->id = de_id;
> + tde->de.enabled = true;
> + tde->de.tstamp_enabled = ts_valid;
> + discovered = true;
> + }
> +
> + /* Update DE location refs if requested: normally done only on enable */
> + if (mode >= SCAN_UPDATE) {
> + tde->base = shmti->base;
> + tde->eplg = SHMTI_EPLG(shmti);
> + tde->offset = (void *)payld - (void *)shmti->base;
> +
> + dev_dbg(ti->ph->dev,
> + "TDCF-updated DE_ID:0x%08X - shmti:%pX offset:%u\n",
> + tde->de.info->id, tde->base, tde->offset);
> + }
> +
> + if (discovered) {
> + if (scmi_telemetry_tde_register(ti, tde)) {
> + scmi_telemetry_free_tde_put(ti, tde);
> + return;
> + }
> + }
> +
> + scoped_guard(mutex, &tde->mtx) {
> + if (tde->last_magic == shmti->last_magic)
> + return;
> + }
> +
> + /* Data is always valid since we are NOT handling BLK TS lines here */
> + val = LINE_DATA_GET(&payld->l);
> + /* Collect the right TS */
> + if (ts_valid) {
> + if (USE_LINE_TS(payld)) {
> + tstamp = LINE_TSTAMP_GET(&payld->tsl);
> + } else if (USE_BLK_TS(payld)) {
> + if (!tde->bts) {
> + /*
> + * Scanning a TDCF looking for the nearest
> + * previous valid BLK_TS, after having found a
> + * USE_BLK_TS() payload, MUST succeed.
> + */
> + tde->bts = scmi_telemetry_blkts_bind(ti->ph->dev,
> + shmti, payld,
> + &ti->xa_bts);
> + if (WARN_ON(!tde->bts))
> + return;
> + }
> +
> + tstamp = scmi_telemetry_blkts_read(tde->last_magic,
> + tde->bts);
> + }
> + }
> +
> + guard(mutex)(&tde->mtx);
> + tde->last_magic = shmti->last_magic;
> + tde->last_val = val;
> + if (tde->de.tstamp_enabled)
> + tde->last_ts = tstamp;
> + else
> + tde->last_ts = 0;
> +}
> +
> +static int scmi_telemetry_tdcf_line_parse(struct telemetry_info *ti,
> + struct payload __iomem *payld,
> + struct telemetry_shmti *shmti,
> + enum scan_mode mode)
> +{
> + int used_qwords;
> +
> + used_qwords = (USE_LINE_TS(payld) && TS_VALID(payld)) ?
> + QWORDS_TS_LINE_DATA_PAYLD : QWORDS_LINE_DATA_PAYLD;
If I understand correctly from the "chat with ATG" comments below, when
timestamp is disabled (after DE is enabled with timestamp), physically ts line
is still there (use_line_ts = 1 and ts_valid = 0). That's why I think we need to
drop TS_VALID(payld) check. Otherwise qwords will be QWORDS_LINE_DATA_PAYLD
although ts line exists.
Also, for the alpha version of the spec, the combination of use_line_ts = 1 and
ts_valid = 0 doesn't seem to be possible as I understand. But I think beta
version allows us?
> +
> + /* Invalid lines are not an error, could simply be disabled DEs */
> + if (DATA_INVALID(payld))
> + return used_qwords;
> +
> + if (!IS_BLK_TS(payld))
> + scmi_telemetry_tdcf_data_parse(ti, payld, shmti, mode);
> + else
> + scmi_telemetry_tdcf_blkts_parse(ti, payld, shmti);
> +
> + return used_qwords;
> +}
> +
> +static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
> + unsigned int shmti_id, u64 ts,
> + enum scan_mode mode)
ts is not used in the function.
> +{
> + struct telemetry_shmti *shmti = &ti->shmti[shmti_id];
> + struct tdcf __iomem *tdcf = shmti->base;
> + int retries = SCMI_TLM_TDCF_MAX_RETRIES;
> + u64 startm = 0, endm = TDCF_BAD_END_SEQ;
> + void *eplg = SHMTI_EPLG(shmti);
> +
> + if (!tdcf)
> + return -ENODEV;
> +
> + do {
> + unsigned int qwords;
> + void __iomem *next;
> +
> + /* A bit of exponential backoff between retries */
> + fsleep((SCMI_TLM_TDCF_MAX_RETRIES - retries) * 1000);
> +
> + startm = TDCF_START_SEQ_GET(tdcf);
> + if (IS_BAD_START_SEQ(startm))
> + continue;
> +
> + /* On a BAD_SEQ this will be updated on the next attempt */
> + shmti->last_magic = startm;
> +
> + qwords = QWORDS(tdcf);
> + next = tdcf->payld;
> + while (qwords) {
> + int used_qwords;
> +
> + used_qwords = scmi_telemetry_tdcf_line_parse(ti, next,
> + shmti, mode);
> + if (qwords < used_qwords)
> + return -EINVAL;
> +
> + next += used_qwords * 8;
> + qwords -= used_qwords;
> + }
> +
> + endm = TDCF_END_SEQ_GET(eplg);
> + } while (startm != endm && --retries);
> +
> + if (startm != endm)
> + return -EPROTO;
> +
> + return 0;
> +}
> +
> +static int scmi_telemetry_group_state_update(struct telemetry_info *ti,
> + struct scmi_telemetry_group *grp,
> + bool *enable, bool *tstamp)
> +{
> + struct scmi_telemetry_res_info *rinfo;
> +
> + rinfo = ti->res_get(ti);
> + for (int i = 0; i < grp->info->num_des; i++) {
> + struct scmi_telemetry_de *de = rinfo->des[grp->des[i]];
> +
> + if (enable)
> + de->enabled = *enable;
> + if (tstamp)
> + de->tstamp_enabled = *tstamp;
> + }
> +
> + return 0;
> +}
> +
> +static int
> +scmi_telemetry_state_set_resp_process(struct telemetry_info *ti,
> + struct scmi_telemetry_de *de,
> + void *r, bool is_group)
> +{
> + struct scmi_msg_resp_telemetry_de_configure *resp = r;
> + u32 sid = le32_to_cpu(resp->shmti_id);
> +
> + /* Update DE SHMTI and offset, if applicable */
> + if (IS_SHMTI_ID_VALID(sid)) {
> + if (sid >= ti->num_shmti)
> + return -EPROTO;
> +
> + /*
> + * Update SHMTI/offset while skipping non-SHMTI-DEs like
> + * FCs and notif-only.
> + */
> + if (!is_group) {
> + struct telemetry_de *tde;
> + struct payload *payld;
> + u32 offs;
> +
> + offs = le32_to_cpu(resp->tdcf_de_offset);
> + if (offs >= ti->shmti[sid].len - de->info->data_sz)
> + return -EPROTO;
> +
> + tde = to_tde(de);
> + tde->base = ti->shmti[sid].base;
> + tde->offset = offs;
> + /* A handy reference to the Epilogue updated */
> + tde->eplg = SHMTI_EPLG(&ti->shmti[sid]);
> +
> + payld = tde->base + tde->offset;
> + if (USE_BLK_TS(payld) && !tde->bts) {
> + tde->bts = scmi_telemetry_blkts_bind(ti->ph->dev,
> + &ti->shmti[sid],
> + payld,
> + &ti->xa_bts);
> + if (WARN_ON(!tde->bts))
> + return -EPROTO;
> + }
> + } else {
> + int ret;
> +
> + /*
> + * A full SHMTI scan is needed when enabling a
> + * group or its timestamps in order to retrieve
> + * offsets: node that when group-timestamp is
> + * enabled for composing DEs a re-scan is needed
> + * since some DEs could have been relocated due
> + * to lack of space in the TDCF.
> + */
> + ret = scmi_telemetry_shmti_scan(ti, sid, 0, SCAN_UPDATE);
> + if (ret)
> + dev_warn(ti->ph->dev,
> + "Failed group-scan of SHMTI ID:%d\n", sid);
> + }
> + } else if (!is_group) {
> + struct telemetry_de *tde;
> +
> + tde = to_tde(de);
> + if (tde->bts) {
> + /* Unlink the related BLK_TS on disable */
> + scmi_telemetry_blkts_put(ti->ph->dev, tde->bts);
> + tde->bts = NULL;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int __scmi_telemetry_state_set(const struct scmi_protocol_handle *ph,
> + bool is_group, bool *enable,
> + bool *enabled_state, bool *tstamp,
> + bool *tstamp_enabled_state, void *obj)
> +{
> + struct scmi_msg_resp_telemetry_de_configure *resp;
> + struct scmi_msg_telemetry_de_configure *msg;
> + struct telemetry_info *ti = ph->get_priv(ph);
> + struct scmi_telemetry_group *grp;
> + struct scmi_telemetry_de *de;
> + unsigned int obj_id;
> + struct scmi_xfer *t;
> + int ret;
> +
> + if (!enabled_state || !tstamp_enabled_state)
> + return -EINVAL;
> +
> + /* Is anything to do at all on this DE ? */
> + if (!is_group && (!enable || *enable == *enabled_state) &&
> + (!tstamp || *tstamp == *tstamp_enabled_state))
> + return 0;
> +
> + /*
> + * DE is currently disabled AND no enable state change was requested,
> + * while timestamp is being changed: update only local state...no need
> + * to send a message.
> + */
> + if (!is_group && !enable && !*enabled_state) {
> + *tstamp_enabled_state = *tstamp;
> + return 0;
> + }
> +
> + if (!is_group) {
> + de = obj;
> + obj_id = de->info->id;
> + } else {
> + grp = obj;
> + obj_id = grp->info->id;
> + }
> +
> + ret = ph->xops->xfer_get_init(ph, TELEMETRY_DE_CONFIGURE,
> + sizeof(*msg), sizeof(*resp), &t);
> + if (ret)
> + return ret;
> +
> + msg = t->tx.buf;
> + /* Note that BOTH DE and GROUPS have a first ID field.. */
> + msg->id = cpu_to_le32(obj_id);
> + /* Default to disable mode for one DE */
> + msg->flags = DE_DISABLE_ONE;
> + msg->flags |= FIELD_PREP(GENMASK(3, 3),
> + is_group ? EVENT_GROUP : EVENT_DE);
> +
> + if ((!enable && *enabled_state) || (enable && *enable)) {
> + /* Already enabled but tstamp_enabled state changed */
> + if (tstamp) {
> + /* Here, tstamp cannot be NULL too */
> + msg->flags |= *tstamp ? DE_ENABLE_WTH_TSTAMP :
> + DE_ENABLE_NO_TSTAMP;
> + } else {
> + msg->flags |= *tstamp_enabled_state ?
> + DE_ENABLE_WTH_TSTAMP : DE_ENABLE_NO_TSTAMP;
> + }
> + }
> +
> + resp = t->rx.buf;
> + ret = ph->xops->do_xfer(ph, t);
> + if (!ret) {
> + ret = scmi_telemetry_state_set_resp_process(ti, de, resp, is_group);
> + if (!ret) {
> + /* Update cached state on success */
> + if (enable)
> + *enabled_state = *enable;
> + if (tstamp)
> + *tstamp_enabled_state = *tstamp;
> +
> + if (is_group)
> + scmi_telemetry_group_state_update(ti, grp, enable,
> + tstamp);
> + }
> + }
> +
> + ph->xops->xfer_put(ph, t);
> +
> + return ret;
> +}
> +
> +static int scmi_telemetry_state_get(const struct scmi_protocol_handle *ph,
> + u32 id, bool *enabled, bool *tstamp_enabled)
> +{
> + struct telemetry_info *ti = ph->get_priv(ph);
> + struct scmi_telemetry_de *de;
> +
> + if (!enabled || !tstamp_enabled)
> + return -EINVAL;
> +
> + de = xa_load(&ti->xa_des, id);
> + if (!de)
> + return -ENODEV;
> +
> + *enabled = de->enabled;
> + *tstamp_enabled = de->tstamp_enabled;
> +
> + return 0;
> +}
> +
> +static int scmi_telemetry_state_set(const struct scmi_protocol_handle *ph,
> + bool is_group, u32 id, bool *enable,
> + bool *tstamp)
> +{
> + struct telemetry_info *ti = ph->get_priv(ph);
> + bool *enabled_state, *tstamp_enabled_state;
> + struct scmi_telemetry_res_info *rinfo;
> + void *obj;
> +
> + rinfo = ti->res_get(ti);
> + if (!is_group) {
> + struct scmi_telemetry_de *de;
> +
> + de = xa_load(&ti->xa_des, id);
> + if (!de)
> + return -ENODEV;
> +
> + enabled_state = &de->enabled;
> + tstamp_enabled_state = &de->tstamp_enabled;
> + obj = de;
> + } else {
> + struct scmi_telemetry_group *grp;
> +
> + if (id >= ti->info.base.num_groups)
> + return -EINVAL;
> +
> + grp = &rinfo->grps[id];
> +
> + enabled_state = &grp->enabled;
> + tstamp_enabled_state = &grp->tstamp_enabled;
> + obj = grp;
> + }
> +
> + return __scmi_telemetry_state_set(ph, is_group, enable, enabled_state,
> + tstamp, tstamp_enabled_state, obj);
> +}
> +
> +static int scmi_telemetry_all_disable(const struct scmi_protocol_handle *ph,
> + bool is_group)
> +{
> + struct telemetry_info *ti = ph->get_priv(ph);
> + struct scmi_msg_telemetry_de_configure *msg;
> + struct scmi_telemetry_res_info *rinfo;
> + struct scmi_xfer *t;
> + int ret;
> +
> + rinfo = ti->res_get(ti);
> + ret = ph->xops->xfer_get_init(ph, TELEMETRY_DE_CONFIGURE,
> + sizeof(*msg), 0, &t);
> + if (ret)
> + return ret;
> +
> + msg = t->tx.buf;
> + msg->flags = DE_DISABLE_ALL;
> + if (is_group)
> + msg->flags |= GROUP_SELECTOR;
> + ret = ph->xops->do_xfer(ph, t);
> + if (!ret) {
> + for (int i = 0; i < ti->info.base.num_des; i++)
> + rinfo->des[i]->enabled = false;
> +
> + if (is_group) {
> + for (int i = 0; i < ti->info.base.num_groups; i++)
> + rinfo->grps[i].enabled = false;
> + }
> + }
> +
> + ph->xops->xfer_put(ph, t);
> +
> + return ret;
> +}
> +
> +static int
> +scmi_telemetry_collection_configure(const struct scmi_protocol_handle *ph,
> + unsigned int res_id, bool grp_ignore,
> + bool *enable,
> + unsigned int *update_interval_ms,
> + enum scmi_telemetry_collection *mode)
> +{
> + enum scmi_telemetry_collection *current_mode, next_mode;
> + struct telemetry_info *ti = ph->get_priv(ph);
> + struct scmi_msg_telemetry_config_set *msg;
> + unsigned int *active_update_interval;
> + struct scmi_xfer *t;
> + bool tlm_enable;
> + u32 interval;
> + int ret;
> +
> + if (mode && *mode == SCMI_TLM_NOTIFICATION &&
> + !ti->info.continuos_update_support)
> + return -EINVAL;
> +
> + if (res_id != SCMI_TLM_GRP_INVALID && res_id >= ti->info.base.num_groups)
> + return -EINVAL;
> +
> + if (res_id == SCMI_TLM_GRP_INVALID || grp_ignore) {
> + active_update_interval = &ti->info.active_update_interval;
> + current_mode = &ti->info.current_mode;
> + } else {
> + struct scmi_telemetry_res_info *rinfo;
> +
> + rinfo = ti->res_get(ti);
> + active_update_interval =
> + &rinfo->grps[res_id].active_update_interval;
> + current_mode = &rinfo->grps[res_id].current_mode;
> + }
> +
> + if (!enable && !update_interval_ms && (!mode || *mode == *current_mode))
> + return 0;
> +
> + ret = ph->xops->xfer_get_init(ph, TELEMETRY_CONFIG_SET,
> + sizeof(*msg), 0, &t);
> + if (ret)
> + return ret;
> +
> + if (!update_interval_ms)
> + interval = cpu_to_le32(*active_update_interval);
> + else
> + interval = *update_interval_ms;
> +
> + tlm_enable = enable ? *enable : ti->info.enabled;
> + next_mode = mode ? *mode : *current_mode;
> +
> + msg = t->tx.buf;
> + msg->grp_id = res_id;
> + msg->control = tlm_enable ? TELEMETRY_ENABLE : 0;
> + msg->control |= grp_ignore ? TELEMETRY_SET_SELECTOR_ALL :
> + TELEMETRY_SET_SELECTOR_GROUP;
> + msg->control |= TELEMETRY_MODE_SET(next_mode);
> + msg->sampling_rate = interval;
> + ret = ph->xops->do_xfer(ph, t);
> + if (!ret) {
> + ti->info.enabled = tlm_enable;
> + *current_mode = next_mode;
> + ti->info.notif_enabled = *current_mode == SCMI_TLM_NOTIFICATION;
> + if (update_interval_ms)
> + *active_update_interval = interval;
> + }
> +
> + ph->xops->xfer_put(ph, t);
> +
> + return ret;
> +}
> +
> +static inline void scmi_telemetry_de_data_fc_read(struct telemetry_de *tde,
> + u64 *tstamp, u64 *val)
> +{
> + struct fc_tsline __iomem *fc = tde->base + tde->offset;
> +
> + *val = LINE_DATA_GET(fc);
> + if (tstamp) {
> + if (tde->de.tstamp_support)
> + *tstamp = LINE_TSTAMP_GET(fc);
> + else
> + *tstamp = 0;
> + }
> +}
> +
> +static void scmi_telemetry_scan_update(struct telemetry_info *ti, u64 ts)
> +{
> + struct telemetry_de *tde;
> +
> + /* Scan all SHMTIs ... */
> + for (int id = 0; id < ti->num_shmti; id++) {
> + int ret;
> +
> + ret = scmi_telemetry_shmti_scan(ti, id, ts, SCAN_LOOKUP);
> + if (ret)
> + dev_warn(ti->ph->dev,
> + "Failed update-scan of SHMTI ID:%d\n", id);
> + }
> +
> + if (!ti->info.fc_support)
> + return;
> +
> + /* Need to enumerate resources to access fastchannels */
> + ti->res_get(ti);
> + list_for_each_entry(tde, &ti->fcs_des, item) {
> + u64 val, tstamp;
> +
> + if (!tde->de.enabled)
> + continue;
> +
> + scmi_telemetry_de_data_fc_read(tde, &tstamp, &val);
> +
> + guard(mutex)(&tde->mtx);
> + tde->last_val = val;
> + if (tde->de.tstamp_enabled)
> + tde->last_ts = tstamp;
> + else
> + tde->last_ts = 0;
> + }
> +}
> +
> +/*
> + * TDCF and TS Line Management Notes
> + * ---------------------------------
> + * (from a chat with ATG)
> + *
> + * TCDF Payload Metadata notable bits:
> + * - Bit[3]: USE BLK Tstamp
> + * - Bit[2]: USE LINE Tstamp
> + * - Bit[1]: Tstamp VALID
> + *
> + * CASE_1:
> + * -------
> + * + A DE is enabled with timestamp disabled, so the TS fields are
> + * NOT present
> + * -> BIT[3:1] = 000b
> + *
> + * - 1/A LINE_TSTAMP
> + * ------------------
> + * + that DE is then 're-enabled' with TS: so it was ON, it remains
> + * ON but using DE_CONFIGURE I now enabled also TS, so the
> + * platform relocates it at the end of the SHMTI and return the
> + * new offset
> + * -> BIT[3:1] = 011b
> + *
> + * - 1/B BLK_TSTAMP
> + * ------------------
> + * + that DE is then 're-enabled' with BLK TS: so it was ON, it
> + * remains ON but using DE_CONFIGURE, we now also enabled the TS,
> + * so the platform will:
> + * - IF a preceding BLK_TS line exist (with same clock freq)
> + * it relocates the DE at the end of the SHMTI and return the
> + * new offset (if there is enough room, if not in another SHMTI)
> + * - IF a preceding BLK_TS line DOES NOT exist (same clock freq)
> + * it creates a new BLK_TS line at the end of the SHMTI and then
> + * relocates the DE after the new BLK_TS and return the
> + * new offset (if there is enough room, if not in another SHMTI)
> + * -> BIT[3:1] = 101b
> + *
> + * + the hole left from the relocated DE can be reused by the platform
> + * to fit another equally sized DE. (i.e. without shuffling around any
> + * other enabled DE, since that would cause a change of the known offset)
> + *
> + * CASE_2:
> + * -------
> + * + A DE is enabled with LINE timestamp enabled, so the TS_Line is there
> + * -> BIT[3:1] = 011b
> + * + that DE has its timestamp disabled: again, you can do this without
> + * disabling it fully but just disabling the TS, so now that TS_line
> + * fields are still physiclly there but NOT valid
> + * -> BIT[3:1] = 010b
> + * + the hole from the timestamp remain there unused until
> + * - you enable again the TS so the hole is used again
> + * -> BIT[3:1] = 011b
> + * OR
> + * - you disable fully the DE and then re-enable it with the TS
> + * -> potentially CASE_1 the DE is relocated on enable
> + * + same kind of dynamic applies if the DE had a BLK_TS line
> + */
> +static int scmi_telemetry_tdcf_de_parse(struct telemetry_de *tde,
> + u64 *tstamp, u64 *val)
> +{
> + struct tdcf __iomem *tdcf = tde->base;
> + u64 startm, endm;
> + int retries = SCMI_TLM_TDCF_MAX_RETRIES;
> +
> + if (!tdcf)
> + return -ENODEV;
> +
> + do {
> + struct payload __iomem *payld;
> +
> + /* A bit of exponential backoff between retries */
> + fsleep((SCMI_TLM_TDCF_MAX_RETRIES - retries) * 1000);
> +
> + startm = TDCF_START_SEQ_GET(tdcf);
> + if (IS_BAD_START_SEQ(startm))
> + continue;
> +
> + /* Has anything changed at all at the SHMTI level ? */
> + scoped_guard(mutex, &tde->mtx) {
> + if (tde->last_magic == startm) {
> + *val = tde->last_val;
> + if (tstamp)
> + *tstamp = tde->last_ts;
> + return 0;
> + }
> + }
> +
> + payld = tde->base + tde->offset;
> + if (DATA_INVALID(payld))
> + return -EINVAL;
> +
> + if (IS_BLK_TS(payld))
> + return -EINVAL;
> +
> + if (PAYLD_ID(payld) != tde->de.info->id)
> + return -EINVAL;
> +
> + /* Data is always valid since NOT handling BLK TS lines here */
> + *val = LINE_DATA_GET(&payld->l);
> + /* Collect the right TS */
> + if (tstamp) {
> + if (!TS_VALID(payld)) {
> + *tstamp = 0;
> + } else if (USE_LINE_TS(payld)) {
> + *tstamp = LINE_TSTAMP_GET(&payld->tsl);
> + } else if (USE_BLK_TS(payld)) {
> + /*
> + * A valid line using BLK_TS should have been
> + * initialized with the related BLK_TS when
> + * enabled.
> + */
> + if (WARN_ON(!tde->bts))
> + return -EPROTO;
> + *tstamp = scmi_telemetry_blkts_read(startm,
> + tde->bts);
> + }
> + }
> +
> + endm = TDCF_END_SEQ_GET(tde->eplg);
> + } while (startm != endm && --retries);
> +
> + if (startm != endm)
> + return -EPROTO;
> +
> + guard(mutex)(&tde->mtx);
> + tde->last_magic = startm;
> + tde->last_val = *val;
> + if (tstamp)
> + tde->last_ts = *tstamp;
> +
> + return 0;
> +}
> +
> +static int scmi_telemetry_de_read(struct telemetry_de *tde, u64 *tstamp, u64 *val)
> +{
> + if (!tde->de.fc_support)
> + return scmi_telemetry_tdcf_de_parse(tde, tstamp, val);
> +
> + scmi_telemetry_de_data_fc_read(tde, tstamp, val);
> +
> + return 0;
> +}
> +
> +static int scmi_telemetry_de_collect(struct telemetry_info *ti,
> + struct scmi_telemetry_de *de,
> + u64 *tstamp, u64 *val)
> +{
> + struct telemetry_de *tde = to_tde(de);
> +
> + if (!de->enabled)
> + return -EINVAL;
> +
> + /*
> + * DE readings returns cached values when:
> + * - DE data value was retrieved via notification
> + */
> + scoped_guard(mutex, &tde->mtx) {
> + if (tde->cached) {
> + *val = tde->last_val;
> + if (tstamp)
> + *tstamp = tde->last_ts;
> + return 0;
> + }
> + }
> +
> + return scmi_telemetry_de_read(tde, tstamp, val);
> +}
> +
> +static int scmi_telemetry_de_cached_read(struct telemetry_info *ti,
> + struct scmi_telemetry_de *de,
> + u64 *tstamp, u64 *val)
> +{
> + struct telemetry_de *tde = to_tde(de);
> +
> + if (!de->enabled)
> + return -EINVAL;
> +
> + guard(mutex)(&tde->mtx);
> + *val = tde->last_val;
> + if (tstamp)
> + *tstamp = tde->last_ts;
> +
> + return 0;
> +}
> +
> +static int scmi_telemetry_de_data_read(const struct scmi_protocol_handle *ph,
> + struct scmi_telemetry_de_sample *sample)
> +{
> + struct telemetry_info *ti = ph->get_priv(ph);
> + struct scmi_telemetry_de *de;
> +
> + if (!ti->info.enabled || !sample)
> + return -EINVAL;
> +
> + de = xa_load(&ti->xa_des, sample->id);
> + if (!de)
> + return -ENODEV;
> +
> + return scmi_telemetry_de_collect(ti, de, &sample->tstamp, &sample->val);
> +}
> +
> +static int
> +scmi_telemetry_samples_collect(struct telemetry_info *ti, int grp_id,
> + int *num_samples,
> + struct scmi_telemetry_de_sample *samples)
> +{
> + struct scmi_telemetry_res_info *rinfo;
> + int max_samples;
> +
> + max_samples = *num_samples;
> + *num_samples = 0;
> +
> + rinfo = ti->res_get(ti);
> + for (int i = 0; i < rinfo->num_des; i++) {
> + struct scmi_telemetry_de *de;
> + u64 val, tstamp;
> + int ret;
> +
> + de = rinfo->des[i];
> + if (grp_id != SCMI_TLM_GRP_INVALID &&
> + (!de->grp || de->grp->info->id != grp_id))
> + continue;
> +
> + ret = scmi_telemetry_de_cached_read(ti, de, &tstamp, &val);
> + if (ret)
> + continue;
> +
> + if (*num_samples == max_samples)
> + return -ENOSPC;
> +
> + samples[*num_samples].tstamp = tstamp;
> + samples[*num_samples].val = val;
> + samples[*num_samples].id = de->info->id;
> +
> + (*num_samples)++;
> + }
> +
> + return 0;
> +}
> +
> +static int scmi_telemetry_des_bulk_read(const struct scmi_protocol_handle *ph,
> + int grp_id, int *num_samples,
> + struct scmi_telemetry_de_sample *samples)
> +{
> + struct telemetry_info *ti = ph->get_priv(ph);
> +
> + if (!ti->info.enabled || !num_samples || !samples)
> + return -EINVAL;
> +
> + /* Trigger a full SHMTIs & FCs scan */
> + scmi_telemetry_scan_update(ti, 0);
> +
> + /* Collect all last cached values */
> + return scmi_telemetry_samples_collect(ti, grp_id, num_samples, samples);
> +}
> +
> +static void
> +scmi_telemetry_msg_payld_process(struct telemetry_info *ti,
> + unsigned int num_dwords, u32 *dwords,
> + ktime_t timestamp)
> +{
> + struct scmi_telemetry_res_info *rinfo;
> + u32 next = 0;
> +
> + rinfo = ti->res_get(ti);
> + if (!rinfo->fully_enumerated) {
> + dev_warn_once(ti->ph->dev,
> + "Cannot process DEs in message payload. Drop.\n");
> + return;
> + }
> +
> + while (next < num_dwords) {
> + struct payload *payld = (struct payload *)&dwords[next];
> + struct scmi_telemetry_de *de;
> + struct telemetry_de *tde;
> + u32 de_id;
> +
> + next += USE_LINE_TS(payld) ?
> + TS_LINE_DATA_PAYLD_WORDS : LINE_DATA_PAYLD_WORDS;
> +
> + if (DATA_INVALID(payld)) {
> + dev_err(ti->ph->dev, "MSG - Received INVALID DATA line\n");
> + continue;
> + }
> +
> + de_id = le32_to_cpu(payld->id);
> + de = xa_load(&ti->xa_des, de_id);
> + if (!de || !de->enabled) {
> + dev_err(ti->ph->dev,
> + "MSG - Received INVALID DE - ID:%u enabled:%d\n",
enabled:%c?
> + de_id, de ? (de->enabled ? 'Y' : 'N') : 'X');
> + continue;
> + }
> +
> + tde = to_tde(de);
> + guard(mutex)(&tde->mtx);
> + tde->cached = true;
> + tde->last_val = LINE_DATA_GET(&payld->tsl);
> + /* TODO BLK_TS in notification payloads */
> + if (USE_LINE_TS(payld) && TS_VALID(payld))
> + tde->last_ts = LINE_TSTAMP_GET(&payld->tsl);
> + else
> + tde->last_ts = 0;
> + }
> +}
> +
> +static int scmi_telemetry_des_sample_get(const struct scmi_protocol_handle *ph,
> + int grp_id, int *num_samples,
> + struct scmi_telemetry_de_sample *samples)
> +{
> + struct telemetry_info *ti = ph->get_priv(ph);
> + struct scmi_msg_telemetry_config_set *msg;
> + struct scmi_xfer *t;
> + bool grp_ignore;
> + int ret;
> +
> + if (!ti->info.enabled || !num_samples || !samples)
> + return -EINVAL;
> +
> + grp_ignore = grp_id == SCMI_TLM_GRP_INVALID ? true : false;
> + if (!grp_ignore && grp_id >= ti->info.base.num_groups)
> + return -EINVAL;
> +
> + ret = ph->xops->xfer_get_init(ph, TELEMETRY_CONFIG_SET,
> + sizeof(*msg), 0, &t);
> + if (ret)
> + return ret;
> +
> + msg = t->tx.buf;
> + msg->grp_id = grp_id;
> + msg->control = TELEMETRY_ENABLE;
> + msg->control |= grp_ignore ? TELEMETRY_SET_SELECTOR_ALL :
> + TELEMETRY_SET_SELECTOR_GROUP;
> + msg->control |= TELEMETRY_MODE_SINGLE;
> + msg->sampling_rate = 0;
> +
> + ret = ph->xops->do_xfer_with_response(ph, t);
> + if (!ret) {
> + struct scmi_msg_resp_telemetry_reading_complete *r = t->rx.buf;
> +
> + /* Update cached DEs values from payload */
> + if (r->num_dwords)
> + scmi_telemetry_msg_payld_process(ti, r->num_dwords,
> + r->dwords, 0);
> + /* Scan and update SMHTIs and FCs */
> + scmi_telemetry_scan_update(ti, 0);
> +
> + /* Collect all last cached values */
> + ret = scmi_telemetry_samples_collect(ti, grp_id, num_samples,
> + samples);
> + }
> +
> + ph->xops->xfer_put(ph, t);
> +
> + return ret;
> +}
> +
> +static int scmi_telemetry_config_get(const struct scmi_protocol_handle *ph,
> + bool *enabled, int *mode,
> + u32 *update_interval)
> +{
> + return -EOPNOTSUPP;
> +}
> +
> +static void scmi_telemetry_local_resources_reset(struct telemetry_info *ti)
> +{
> + struct scmi_telemetry_res_info *rinfo;
> +
> + /* Get rinfo as it is...without triggering an enumeration */
> + rinfo = __scmi_telemetry_resources_get(ti);
> + /* Clear all local state...*/
> + for (int i = 0; i < rinfo->num_des; i++) {
> + rinfo->des[i]->enabled = false;
> + rinfo->des[i]->tstamp_enabled = false;
> + }
> + for (int i = 0; i < rinfo->num_groups; i++) {
> + rinfo->grps[i].enabled = false;
> + rinfo->grps[i].tstamp_enabled = false;
> + rinfo->grps[i].current_mode = SCMI_TLM_ONDEMAND;
> + rinfo->grps[i].active_update_interval = 0;
> + }
> +}
> +
> +static int scmi_telemetry_reset(const struct scmi_protocol_handle *ph)
> +{
> + struct scmi_xfer *t;
> + int ret;
> +
> + ret = ph->xops->xfer_get_init(ph, TELEMETRY_RESET, sizeof(u32), 0, &t);
> + if (ret)
> + return ret;
> +
> + put_unaligned_le32(0, t->tx.buf);
> + ret = ph->xops->do_xfer(ph, t);
> + if (!ret) {
> + struct telemetry_info *ti = ph->get_priv(ph);
> +
> + scmi_telemetry_local_resources_reset(ti);
> + /* Fetch agaon states state from platform.*/
> + ret = scmi_telemetry_initial_state_lookup(ti);
> + if (ret)
> + dev_warn(ph->dev,
> + FW_BUG "Cannot retrieve initial state after reset.\n");
> + }
> +
> + ph->xops->xfer_put(ph, t);
> +
> + return ret;
> +}
> +
> +static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
> + .info_get = scmi_telemetry_info_get,
> + .de_lookup = scmi_telemetry_de_lookup,
> + .res_get = scmi_telemetry_resources_get,
> + .state_get = scmi_telemetry_state_get,
> + .state_set = scmi_telemetry_state_set,
> + .all_disable = scmi_telemetry_all_disable,
> + .collection_configure = scmi_telemetry_collection_configure,
> + .de_data_read = scmi_telemetry_de_data_read,
> + .des_bulk_read = scmi_telemetry_des_bulk_read,
> + .des_sample_get = scmi_telemetry_des_sample_get,
> + .config_get = scmi_telemetry_config_get,
> + .reset = scmi_telemetry_reset,
> +};
> +
> +static bool
> +scmi_telemetry_notify_supported(const struct scmi_protocol_handle *ph,
> + u8 evt_id, u32 src_id)
> +{
> + struct telemetry_info *ti = ph->get_priv(ph);
> +
> + return ti->info.continuos_update_support;
> +}
> +
> +static int
> +scmi_telemetry_set_notify_enabled(const struct scmi_protocol_handle *ph,
> + u8 evt_id, u32 src_id, bool enable)
> +{
> + return 0;
> +}
> +
> +static void *
> +scmi_telemetry_fill_custom_report(const struct scmi_protocol_handle *ph,
> + u8 evt_id, ktime_t timestamp,
> + const void *payld, size_t payld_sz,
> + void *report, u32 *src_id)
> +{
> + const struct scmi_telemetry_update_notify_payld *p = payld;
> + struct scmi_telemetry_update_report *r = report;
> +
> + /* At least sized as an empty notification */
> + if (payld_sz < sizeof(*p))
> + return NULL;
> +
> + r->timestamp = timestamp;
> + r->agent_id = le32_to_cpu(p->agent_id);
> + r->status = le32_to_cpu(p->status);
> + r->num_dwords = le32_to_cpu(p->num_dwords);
> + /*
> + * Allocated dwords and report are sized as max_msg_size, so as
> + * to allow for the maximum payload permitted by the configured
> + * transport. Overflow is not possible since out-of-size messages
> + * are dropped at the transport layer.
> + */
> + if (r->num_dwords)
> + memcpy(r->dwords, p->array, r->num_dwords * sizeof(u32));
> +
> + *src_id = 0;
> +
> + return r;
> +}
> +
> +static const struct scmi_event tlm_events[] = {
> + {
> + .id = SCMI_EVENT_TELEMETRY_UPDATE,
> + .max_payld_sz = 0,
> + .max_report_sz = 0,
> + },
> +};
> +
> +static const struct scmi_event_ops tlm_event_ops = {
> + .is_notify_supported = scmi_telemetry_notify_supported,
> + .set_notify_enabled = scmi_telemetry_set_notify_enabled,
> + .fill_custom_report = scmi_telemetry_fill_custom_report,
> +};
> +
> +static const struct scmi_protocol_events tlm_protocol_events = {
> + .queue_sz = SCMI_PROTO_QUEUE_SZ,
> + .ops = &tlm_event_ops,
> + .evts = tlm_events,
> + .num_events = ARRAY_SIZE(tlm_events),
> + .num_sources = 1,
> +};
> +
> +static int scmi_telemetry_notifier(struct notifier_block *nb,
> + unsigned long event, void *data)
> +{
> + struct scmi_telemetry_update_report *er = data;
> + struct telemetry_info *ti = telemetry_nb_to_info(nb);
> +
> + if (er->status) {
> + dev_err(ti->ph->dev, "Bad Telemetry update notification - ret: %dn",
> + er->status);
> + return NOTIFY_DONE;
> + }
> +
> + /* Lookup the embedded DEs in the notification payload ... */
> + if (er->num_dwords)
> + scmi_telemetry_msg_payld_process(ti, er->num_dwords,
> + er->dwords, er->timestamp);
> +
> + /* ...scan the SHMTI/FCs for any other DE updates. */
> + if (ti->streaming_mode)
> + scmi_telemetry_scan_update(ti, er->timestamp);
> +
> + return NOTIFY_OK;
> +}
> +
> +static int scmi_telemetry_resources_alloc(struct telemetry_info *ti)
> +{
> + /* Array to hold pointers to discovered DEs */
> + struct scmi_telemetry_de **des __free(kfree) =
> + kcalloc(ti->info.base.num_des, sizeof(*des), GFP_KERNEL);
> + if (!des)
> + return -ENOMEM;
> +
> + /* The allocated DE descriptors */
> + struct telemetry_de *tdes __free(kfree) =
> + kcalloc(ti->info.base.num_des, sizeof(*tdes), GFP_KERNEL);
> + if (!tdes)
> + return -ENOMEM;
> +
> + /* Allocate a set of contiguous DE info descriptors. */
> + struct scmi_tlm_de_info *dei_store __free(kfree) =
> + kcalloc(ti->info.base.num_des, sizeof(*dei_store), GFP_KERNEL);
> + if (!dei_store)
> + return -ENOMEM;
> +
> + /* Array to hold descriptors of discovered GROUPs */
> + struct scmi_telemetry_group *grps __free(kfree) =
> + kcalloc(ti->info.base.num_groups, sizeof(*grps), GFP_KERNEL);
> + if (!grps)
> + return -ENOMEM;
> +
> + /* Allocate a set of contiguous Group info descriptors. */
> + struct scmi_tlm_grp_info *grps_store __free(kfree) =
> + kcalloc(ti->info.base.num_groups, sizeof(*grps_store), GFP_KERNEL);
> + if (!grps_store)
> + return -ENOMEM;
> +
> + struct scmi_telemetry_res_info *rinfo __free(kfree) =
> + kzalloc(sizeof(*rinfo), GFP_KERNEL);
> + if (!rinfo)
> + return -ENOMEM;
> +
> + mutex_init(&ti->free_mtx);
> + INIT_LIST_HEAD(&ti->free_des);
> + for (int i = 0; i < ti->info.base.num_des; i++) {
> + mutex_init(&tdes[i].mtx);
> + /* Bind contiguous DE info structures */
> + tdes[i].de.info = &dei_store[i];
> + list_add_tail(&tdes[i].item, &ti->free_des);
> + }
> +
> + for (int i = 0; i < ti->info.base.num_groups; i++) {
> + grps_store[i].id = i;
> + /* Bind contiguous Group info struct */
> + grps[i].info = &grps_store[i];
> + }
> +
> + INIT_LIST_HEAD(&ti->fcs_des);
> +
> + ti->tdes = no_free_ptr(tdes);
> +
> + rinfo->des = no_free_ptr(des);
> + rinfo->dei_store = no_free_ptr(dei_store);
> + rinfo->grps = no_free_ptr(grps);
> + rinfo->grps_store = no_free_ptr(grps_store);
> +
> + ti->rinfo = no_free_ptr(rinfo);
> +
> + return 0;
> +}
> +
> +static void scmi_telemetry_resources_free(void *arg)
> +{
> + struct telemetry_info *ti = arg;
> +
> + kfree(ti->tdes);
> + kfree(ti->rinfo->des);
> + kfree(ti->rinfo->dei_store);
> + kfree(ti->rinfo->grps);
> + kfree(ti->rinfo->grps_store);
> +
> + kfree(ti->rinfo);
> +}
> +
> +static struct scmi_telemetry_res_info *
> +__scmi_telemetry_resources_get(struct telemetry_info *ti)
> +{
> + return ACCESS_PRIVATE(ti, rinfo);
> +}
> +
> +static struct scmi_telemetry_res_info *
> +scmi_telemetry_resources_enumerate(struct telemetry_info *ti)
> +{
> + struct device *dev = ti->ph->dev;
> + int ret;
> +
> + /*
> + * Ensure this init function can be called only once and
> + * handles properly concurrent calls.
> + */
> + if (atomic_cmpxchg(&ti->rinfo_initializing, 0, 1)) {
> + if (!completion_done(&ti->rinfo_initdone))
> + wait_for_completion(&ti->rinfo_initdone);
> + goto out;
> + }
> +
> + ret = scmi_telemetry_de_descriptors_get(ti);
> + if (ret) {
> + dev_err(dev, FW_BUG "Cannot enumerate DEs resources. Carry-on.\n");
> + goto done;
> + }
> +
> + ret = scmi_telemetry_enumerate_groups_intervals(ti);
> + if (ret) {
> + dev_err(dev, FW_BUG "Cannot enumerate group intervals. Carry-on.\n");
> + goto done;
> + }
> +
> + ti->rinfo->fully_enumerated = true;
> +done:
> + /* Disable initialization permanently */
> + smp_store_mb(ti->res_get, __scmi_telemetry_resources_get);
> + complete_all(&ti->rinfo_initdone);
> +
> +out:
> + return ti->rinfo;
> +}
> +
> +static int scmi_telemetry_instance_init(struct telemetry_info *ti)
> +{
> + int ret;
> +
> + /* Allocate and Initialize on first call... */
> + ret = scmi_telemetry_resources_alloc(ti);
> + if (ret)
> + return ret;
> +
> + ret = devm_add_action_or_reset(ti->ph->dev,
> + scmi_telemetry_resources_free, ti);
> + if (ret)
> + return ret;
> +
> + xa_init(&ti->xa_des);
> + /* Setup resources lazy initialization */
> + atomic_set(&ti->rinfo_initializing, 0);
> + init_completion(&ti->rinfo_initdone);
> + /* Ensure the new res_get() operation is visible after this point */
> + smp_store_mb(ti->res_get, scmi_telemetry_resources_enumerate);
> +
> + return 0;
> +}
> +
> +static int scmi_telemetry_protocol_init(const struct scmi_protocol_handle *ph)
> +{
> + struct device *dev = ph->dev;
> + struct telemetry_info *ti;
> + u32 version;
> + int ret;
> +
> + ret = ph->xops->version_get(ph, &version);
> + if (ret)
> + return ret;
> +
> + dev_dbg(dev, "Telemetry Version %d.%d\n",
> + PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version));
> +
> + ti = devm_kzalloc(dev, sizeof(*ti), GFP_KERNEL);
> + if (!ti)
> + return -ENOMEM;
> +
> + ti->ph = ph;
> +
> + ret = scmi_telemetry_protocol_attributes_get(ti);
> + if (ret) {
> + dev_err(dev, FW_BUG "Cannot retrieve protocol attributes. Abort\n");
> + return ret;
> + }
> +
> + ret = scmi_telemetry_instance_init(ti);
> + if (ret) {
> + dev_err(dev, "Cannot initialize instance. Abort.\n");
> + return ret;
> + }
> +
> + ret = scmi_telemetry_enumerate_common_intervals(ti);
> + if (ret)
> + dev_warn(dev, FW_BUG "Cannot enumerate update intervals. Carry-on.\n");
> +
> + ret = scmi_telemetry_enumerate_shmti(ti);
> + if (ret)
> + dev_warn(dev, FW_BUG "Cannot enumerate SHMTIs. Carry-on.\n");
> +
> + ret = scmi_telemetry_initial_state_lookup(ti);
> + if (ret)
> + dev_warn(dev, FW_BUG "Cannot retrieve initial state. Carry-on.\n");
> +
> + ti->info.base.version = version;
> +
> + ret = ph->set_priv(ph, ti, version);
> + if (ret)
> + return ret;
> +
> + /*
> + * Register a notifier anyway straight upon protocol initialization
> + * since there could be some DEs that are ONLY reported by notifications
> + * even though the chosen collection method was SHMTI/FCs.
> + */
> + if (ti->info.continuos_update_support) {
> + ti->telemetry_nb.notifier_call = &scmi_telemetry_notifier;
> + ret = ph->notifier_register(ph, SCMI_EVENT_TELEMETRY_UPDATE,
> + NULL, &ti->telemetry_nb);
> + if (ret)
> + dev_warn(ph->dev,
> + "Could NOT register Telemetry notifications\n");
> + }
> +
> + return ret;
> +}
> +
> +static const struct scmi_protocol scmi_telemetry = {
> + .id = SCMI_PROTOCOL_TELEMETRY,
> + .owner = THIS_MODULE,
> + .instance_init = &scmi_telemetry_protocol_init,
> + .ops = &tlm_proto_ops,
> + .events = &tlm_protocol_events,
> + .supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
> +};
> +
> +DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(telemetry, scmi_telemetry)
> diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
> index c6efe4f371ac..d58b81ffd81e 100644
> --- a/include/linux/scmi_protocol.h
> +++ b/include/linux/scmi_protocol.h
> @@ -2,17 +2,21 @@
> /*
> * SCMI Message Protocol driver header
> *
> - * Copyright (C) 2018-2021 ARM Ltd.
> + * Copyright (C) 2018-2026 ARM Ltd.
> */
>
> #ifndef _LINUX_SCMI_PROTOCOL_H
> #define _LINUX_SCMI_PROTOCOL_H
>
> #include <linux/bitfield.h>
> +#include <linux/bitops.h>
> #include <linux/device.h>
> #include <linux/notifier.h>
> #include <linux/types.h>
>
> +#include <uapi/linux/limits.h>
> +#include <uapi/linux/scmi.h>
> +
> #define SCMI_MAX_STR_SIZE 64
> #define SCMI_SHORT_NAME_MAX_SIZE 16
> #define SCMI_MAX_NUM_RATES 16
> @@ -820,6 +824,178 @@ struct scmi_pinctrl_proto_ops {
> int (*pin_free)(const struct scmi_protocol_handle *ph, u32 pin);
> };
>
> +enum scmi_telemetry_de_type {
> + SCMI_TLM_DE_TYPE_USPECIFIED,
> + SCMI_TLM_DE_TYPE_ACCUMUL_IDLE_RESIDENCY,
> + SCMI_TLM_DE_TYPE_ACCUMUL_IDLE_COUNTS,
> + SCMI_TLM_DE_TYPE_ACCUMUL_OTHERS,
> + SCMI_TLM_DE_TYPE_INSTA_IDLE_STATE,
> + SCMI_TLM_DE_TYPE_INSTA_OTHERS,
> + SCMI_TLM_DE_TYPE_AVERAGE,
> + SCMI_TLM_DE_TYPE_STATUS,
> + SCMI_TLM_DE_TYPE_RESERVED_START,
> + SCMI_TLM_DE_TYPE_RESERVED_END = 0xef,
> + SCMI_TLM_DE_TYPE_OEM_START = 0xf0,
> + SCMI_TLM_DE_TYPE_OEM_END = 0xff,
> +};
> +
> +enum scmi_telemetry_compo_type {
> + SCMI_TLM_COMPO_TYPE_USPECIFIED,
> + SCMI_TLM_COMPO_TYPE_CPU,
> + SCMI_TLM_COMPO_TYPE_CLUSTER,
> + SCMI_TLM_COMPO_TYPE_GPU,
> + SCMI_TLM_COMPO_TYPE_NPU,
> + SCMI_TLM_COMPO_TYPE_INTERCONNECT,
> + SCMI_TLM_COMPO_TYPE_MEM_CNTRL,
> + SCMI_TLM_COMPO_TYPE_L1_CACHE,
> + SCMI_TLM_COMPO_TYPE_L2_CACHE,
> + SCMI_TLM_COMPO_TYPE_L3_CACHE,
> + SCMI_TLM_COMPO_TYPE_LL_CACHE,
> + SCMI_TLM_COMPO_TYPE_SYS_CACHE,
> + SCMI_TLM_COMPO_TYPE_DISP_CNTRL,
> + SCMI_TLM_COMPO_TYPE_IPU,
> + SCMI_TLM_COMPO_TYPE_CHIPLET,
> + SCMI_TLM_COMPO_TYPE_PACKAGE,
> + SCMI_TLM_COMPO_TYPE_SOC,
> + SCMI_TLM_COMPO_TYPE_SYSTEM,
> + SCMI_TLM_COMPO_TYPE_SMCU,
> + SCMI_TLM_COMPO_TYPE_ACCEL,
> + SCMI_TLM_COMPO_TYPE_BATTERY,
> + SCMI_TLM_COMPO_TYPE_CHARGER,
> + SCMI_TLM_COMPO_TYPE_PMIC,
> + SCMI_TLM_COMPO_TYPE_BOARD,
> + SCMI_TLM_COMPO_TYPE_MEMORY,
> + SCMI_TLM_COMPO_TYPE_PERIPH,
> + SCMI_TLM_COMPO_TYPE_PERIPH_SUBC,
> + SCMI_TLM_COMPO_TYPE_LID,
> + SCMI_TLM_COMPO_TYPE_DISPLAY,
> + SCMI_TLM_COMPO_TYPE_RESERVED_START = 0x1d,
> + SCMI_TLM_COMPO_TYPE_RESERVED_END = 0xdf,
> + SCMI_TLM_COMPO_TYPE_OEM_START = 0xe0,
> + SCMI_TLM_COMPO_TYPE_OEM_END = 0xff,
> +};
> +
> +#define SCMI_TLM_GET_UPDATE_INTERVAL_SECS(x) \
> + (le32_get_bits((x), GENMASK(20, 5)))
> +#define SCMI_TLM_GET_UPDATE_INTERVAL_EXP(x) (sign_extend32((x), 4))
> +
> +#define SCMI_TLM_GET_UPDATE_INTERVAL(x) (FIELD_GET(GENMASK(20, 0), (x)))
> +#define SCMI_TLM_BUILD_UPDATE_INTERVAL(s, e) \
> + (FIELD_PREP(GENMASK(20, 5), (s)) | FIELD_PREP(GENMASK(4, 0), (e)))
> +
> +enum scmi_telemetry_collection {
> + SCMI_TLM_ONDEMAND,
> + SCMI_TLM_NOTIFICATION,
> + SCMI_TLM_SINGLE_READ,
> +};
> +
> +#define SCMI_TLM_GRP_INVALID 0xFFFFFFFF
> +struct scmi_telemetry_group {
> + bool enabled;
> + bool tstamp_enabled;
> + unsigned int *des;
> + char *des_str;
> + struct scmi_tlm_grp_info *info;
> + unsigned int active_update_interval;
> + struct scmi_tlm_intervals *intervals;
> + enum scmi_telemetry_collection current_mode;
> +};
> +
> +struct scmi_telemetry_de {
> + bool tstamp_support;
> + bool fc_support;
> + bool name_support;
> + struct scmi_tlm_de_info *info;
> + struct scmi_telemetry_group *grp;
> + bool enabled;
> + bool tstamp_enabled;
> +};
> +
> +struct scmi_telemetry_res_info {
> + bool fully_enumerated;
> + unsigned int num_des;
> + struct scmi_telemetry_de **des;
> + struct scmi_tlm_de_info *dei_store;
> + unsigned int num_groups;
> + struct scmi_telemetry_group *grps;
> + struct scmi_tlm_grp_info *grps_store;
> +};
> +
> +struct scmi_telemetry_info {
> + bool single_read_support;
> + bool continuos_update_support;
> + bool per_group_config_support;
> + bool reset_support;
> + bool fc_support;
> + struct scmi_tlm_base_info base;
> + unsigned int active_update_interval;
> + struct scmi_tlm_intervals *intervals;
> + bool enabled;
> + bool notif_enabled;
> + enum scmi_telemetry_collection current_mode;
> +};
> +
> +struct scmi_telemetry_de_sample {
> + u32 id;
> + u64 tstamp;
> + u64 val;
> +};
> +
> +/**
> + * struct scmi_telemetry_proto_ops - represents the various operations provided
> + * by SCMI Telemetry Protocol
> + *
> + * @info_get: get the general Telemetry information.
> + * @de_lookup: get a specific DE descriptor from the DE id.
> + * @res_get: get a reference to the Telemetry resources descriptor.
> + * @state_get: retrieve the specific DE or GROUP state.
> + * @state_set: enable/disable the specific DE or GROUP with or without timestamps.
> + * @all_disable: disable ALL DEs or GROUPs.
> + * @collection_configure: choose a sampling rate and enable SHMTI/FC sampling
> + * for on demand collection via @de_data_read or async
> + * notificatioins for all the enabled DEs.
> + * @de_data_read: on-demand read of a single DE and related optional timestamp:
> + * the value will be retrieved at the proper SHMTI offset OR
> + * from the dedicated FC area (if supported by that DE).
> + * @des_bulk_read: on-demand read of all the currently enabled DEs, or just
> + * the ones belonging to a specific group when provided.
> + * @des_sample_get: on-demand read of all the currently enabled DEs, or just
> + * the ones belonging to a specific group when provided.
> + * This causes an immediate update platform-side of all the
> + * enabled DEs.
> + * @config_get: retrieve current telemetry configuration.
> + * @reset: reset configuration and telemetry data.
> + */
> +struct scmi_telemetry_proto_ops {
> + const struct scmi_telemetry_info __must_check *(*info_get)
> + (const struct scmi_protocol_handle *ph);
> + const struct scmi_telemetry_de __must_check *(*de_lookup)
> + (const struct scmi_protocol_handle *ph, u32 id);
> + const struct scmi_telemetry_res_info __must_check *(*res_get)
> + (const struct scmi_protocol_handle *ph);
> + int (*state_get)(const struct scmi_protocol_handle *ph,
> + u32 id, bool *enabled, bool *tstamp_enabled);
> + int (*state_set)(const struct scmi_protocol_handle *ph,
> + bool is_group, u32 id, bool *enable, bool *tstamp);
> + int (*all_disable)(const struct scmi_protocol_handle *ph, bool group);
> + int (*collection_configure)(const struct scmi_protocol_handle *ph,
> + unsigned int res_id, bool grp_ignore,
> + bool *enable,
> + unsigned int *update_interval_ms,
> + enum scmi_telemetry_collection *mode);
> + int (*de_data_read)(const struct scmi_protocol_handle *ph,
> + struct scmi_telemetry_de_sample *sample);
> + int __must_check (*des_bulk_read)(const struct scmi_protocol_handle *ph,
> + int grp_id, int *num_samples,
> + struct scmi_telemetry_de_sample *samples);
> + int __must_check (*des_sample_get)(const struct scmi_protocol_handle *ph,
> + int grp_id, int *num_samples,
> + struct scmi_telemetry_de_sample *samples);
> + int (*config_get)(const struct scmi_protocol_handle *ph, bool *enabled,
> + int *mode, u32 *update_interval);
> + int (*reset)(const struct scmi_protocol_handle *ph);
> +};
> +
> /**
> * struct scmi_notify_ops - represents notifications' operations provided by
> * SCMI core
> @@ -926,6 +1102,7 @@ enum scmi_std_protocol {
> SCMI_PROTOCOL_VOLTAGE = 0x17,
> SCMI_PROTOCOL_POWERCAP = 0x18,
> SCMI_PROTOCOL_PINCTRL = 0x19,
> + SCMI_PROTOCOL_TELEMETRY = 0x1b,
> SCMI_PROTOCOL_LAST = 0x7f,
> };
>
> @@ -1027,6 +1204,7 @@ enum scmi_notification_events {
> SCMI_EVENT_SYSTEM_POWER_STATE_NOTIFIER = 0x0,
> SCMI_EVENT_POWERCAP_CAP_CHANGED = 0x0,
> SCMI_EVENT_POWERCAP_MEASUREMENTS_CHANGED = 0x1,
> + SCMI_EVENT_TELEMETRY_UPDATE = 0x0,
> };
>
> struct scmi_power_state_changed_report {
> @@ -1114,4 +1292,12 @@ struct scmi_powercap_meas_changed_report {
> unsigned int domain_id;
> unsigned int power;
> };
> +
> +struct scmi_telemetry_update_report {
> + ktime_t timestamp;
> + unsigned int agent_id;
> + int status;
> + unsigned int num_dwords;
> + unsigned int dwords[];
> +};
> #endif /* _LINUX_SCMI_PROTOCOL_H */
Thanks,
Elif
On Wed, 14 Jan 2026 11:46:09 +0000
Cristian Marussi <cristian.marussi@arm.com> wrote:
> Add basic support for SCMI V4.0 Telemetry protocol including SHMTI,
> FastChannels, Notifications and Single Sample Reads collection methods.
>
> Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
> ---
> v1 --> v2
> - Add proper ioread accessors for TDCF areas
> - Rework resource allocation logic and lifecycle
> - Introduce new resources accessors and res_get() operation to
> implement lazy enumeration
> - Support boot-on telemetry:
> + Add DE_ENABLED_LIST cmd support
> + Add CONFIG_GET cmd support
> + Add TDCF_SCAN for best effort enumeration
> + Harden driver against out-of-spec FW with out of spec cmds
> - Use FCs list
> - Rework de_info_lookup to a moer general de_lookup
> - Fixed reset to use CONFIG_GET and DE_ENABLED_LIST to gather initial
> state
> - Using sign_extend32 helper
> - Added counted_by marker where appropriate
> ---
> drivers/firmware/arm_scmi/Makefile | 2 +-
> drivers/firmware/arm_scmi/driver.c | 2 +
> drivers/firmware/arm_scmi/protocols.h | 1 +
> drivers/firmware/arm_scmi/telemetry.c | 2671 +++++++++++++++++++++++++
Ouch. Might be worth splitting this up into more bite sized pieces.
It's a bit of a take a deep breath before diving in patch at the moment.
So the following is rather superficial.
> include/linux/scmi_protocol.h | 188 +-
> 5 files changed, 2862 insertions(+), 2 deletions(-)
> create mode 100644 drivers/firmware/arm_scmi/telemetry.c
>
> diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
> new file mode 100644
> index 000000000000..16bcdcdc1dc3
> --- /dev/null
> +++ b/drivers/firmware/arm_scmi/telemetry.c
> @@ -0,0 +1,2671 @@
> +static void scmi_telemetry_free_tde_put(struct telemetry_info *ti,
> + struct telemetry_de *tde)
> +{
> + guard(mutex)(&ti->free_mtx);
> +
> + list_add_tail(&tde->item, &ti->free_des);
> +}
> +static int iter_de_descr_update_state(struct scmi_iterator_state *st,
> + const void *response, void *priv)
> +{
> + const struct scmi_msg_resp_telemetry_de_description *r = response;
> + struct scmi_tlm_de_priv *p = priv;
> +
> + st->num_returned = le32_get_bits(r->num_desc, GENMASK(15, 0));
> + st->num_remaining = le32_get_bits(r->num_desc, GENMASK(31, 16));
> +
> + /* Initialized to first descriptor */
> + p->next = (void *)r->desc;
No need to cast to a void *
C always lets you do this implicitly if the target type is a void *.
> +
> + return 0;
> +}
> +
> +static int scmi_telemetry_initial_state_lookup(struct telemetry_info *ti)
> +{
> + struct device *dev = ti->ph->dev;
> + int ret;
> +
> + ret = scmi_telemetry_config_lookup(ti, SCMI_TLM_GRP_INVALID,
> + &ti->info.enabled,
> + &ti->info.active_update_interval);
> + if (ret)
> + return ret;
> +
> + if (ti->info.enabled) {
I'd flip logic to reduce indent
if (!ti->info.enabled)
return 0;
/*
*...
> + /*
> + * When Telemetry is found already enabled on the platform,
> + * proceed with passive discovery using DE_ENABLED_LIST and
> + * TCDF scanning: note that this CAN only discover DEs exposed
> + * via SHMTIs.
> + * FastChannel DEs need a proper DE_DESCRIPTION enumeration,
> + * while, even though incoming Notifications could be used for
> + * passive discovery too, it would carry a considerable risk
> + * of assimilating trash as DEs.
> + */
> + dev_info(dev,
> + "Telemetry found enabled with update interval %ux10^%d\n",
> + SCMI_TLM_GET_UPDATE_INTERVAL_SECS(ti->info.active_update_interval),
> + SCMI_TLM_GET_UPDATE_INTERVAL_EXP(ti->info.active_update_interval));
> + /*
> + * Query enabled DEs list: collect states.
> + * It will include DEs from any interface.
> + * Enabled groups still NOT enumerated.
> + */
> + ret = scmi_telemetry_enumerate_des_enabled_list(ti);
> + if (ret)
> + dev_warn(dev, FW_BUG "Cannot query enabled DE list. Carry-on.\n");
> +
> + /* Discover DEs on SHMTis: collect states/offsets/values */
> + for (int id = 0; id < ti->num_shmti; id++) {
> + ret = scmi_telemetry_shmti_scan(ti, id, 0, SCAN_DISCOVERY);
> + if (ret)
> + dev_warn(dev, "Failed discovery-scan of SHMTI ID:%d\n", id);
> + }
> + }
> +
> + return 0;
> +}
}
> +static int iter_intervals_update_state(struct scmi_iterator_state *st,
> + const void *response, void *priv)
> +{
> + const struct scmi_msg_resp_telemetry_update_intervals *r = response;
> +
> + st->num_returned = le32_get_bits(r->flags, GENMASK(11, 0));
> + st->num_remaining = le32_get_bits(r->flags, GENMASK(31, 16));
> +
> + /*
> + * total intervals is not declared previously anywhere so we
> + * assume it's returned+remaining on first call.
> + */
> + if (!st->max_resources) {
> + struct scmi_tlm_ivl_priv *p = priv;
> + bool discrete;
> + int inum;
> +
> + discrete = INTERVALS_DISCRETE(r->flags);
> + /* Check consistency on first call */
> + if (!discrete && (st->num_returned != 3 || st->num_remaining != 0))
> + return -EINVAL;
> +
> + inum = st->num_returned + st->num_remaining;
> + struct scmi_tlm_intervals *intrvs __free(kfree) =
> + kzalloc(sizeof(*intrvs) + inum * sizeof(__u32), GFP_KERNEL);
Unless this is going to get more complex, the __free() isn't doing anything useful
as there are no other error paths before the no_free_ptr().
> + if (!intrvs)
> + return -ENOMEM;
> +
> + intrvs->num = inum;
> + intrvs->discrete = discrete;
> + st->max_resources = intrvs->num;
> +
> + *p->intrvs = no_free_ptr(intrvs);
> + }
> +
> + return 0;
> +static int
> +scmi_tlm_enumerate_update_intervals(struct telemetry_info *ti,
> + struct scmi_tlm_intervals **intervals,
> + int grp_id, unsigned int flags)
> +{
> + struct scmi_iterator_ops ops = {
> + .prepare_message = iter_intervals_prepare_message,
> + .update_state = iter_intervals_update_state,
> + .process_response = iter_intervals_process_response,
> + };
> + const struct scmi_protocol_handle *ph = ti->ph;
> + struct scmi_tlm_ivl_priv ipriv = {
> + .dev = ph->dev,
> + .grp_id = grp_id,
> + .intrvs = intervals,
> + .flags = flags,
> + };
> + void *iter;
> +
> + iter = ph->hops->iter_response_init(ph, &ops, 0,
> + TELEMETRY_LIST_UPDATE_INTERVALS,
> + sizeof(struct scmi_msg_telemetry_update_intervals),
This alignment is unusual. Given the length of that type name I'd do this as:
iter = ph->hops->iter_response_init(ph, &ops, 0,
TELEMETRY_LIST_UPDATE_INTERVALS,
sizeof(struct scmi_msg_telemetry_update_intervals),
&ipriv);
> + &ipriv);
> + if (IS_ERR(iter))
> + return PTR_ERR(iter);
> +
> + return ph->hops->iter_response_run(iter);
> +}
> +static const struct scmi_telemetry_de *
> +scmi_telemetry_de_lookup(const struct scmi_protocol_handle *ph, u32 id)
> +{
> + struct telemetry_info *ti = ph->get_priv(ph);
> + struct scmi_telemetry_de *de;
> +
> + ti->res_get(ti);
> + de = xa_load(&ti->xa_des, id);
> + if (!de)
> + return NULL;
> +
> + return de;
return xa_load(&ti->xa_des, id);
> +}
> +static struct payload *
> +scmi_telemetry_nearest_blk_ts(struct telemetry_shmti *shmti,
> + struct payload *last_payld)
> +{
> + struct payload *payld, *bts_payld = NULL;
> + struct tdcf __iomem *tdcf = shmti->base;
> + u32 *next;
> +
> + /* Scan from start of TDCF payloads up to last_payld */
> + payld = (struct payload *)tdcf->payld;
casting away the __iomem is usualy a bad idea.
Shouldn't a readl or similar be used to get next below.
> + next = (u32 *)payld;
> + while (payld < last_payld) {
> + if (IS_BLK_TS(payld))
> + bts_payld = payld;
> +
> + next += USE_LINE_TS(payld) ?
> + TS_LINE_DATA_PAYLD_WORDS : LINE_DATA_PAYLD_WORDS;
> + payld = (struct payload *)next;
> + }
> +
> + return bts_payld;
> +}
> +
> +static struct telemetry_block_ts *
> +scmi_telemetry_blkts_lookup(struct device *dev, struct xarray *xa_bts,
> + struct payload *payld)
> +{
> + struct telemetry_block_ts *bts;
> +
> + bts = xa_load(xa_bts, (unsigned long)payld);
> + if (!bts) {
> + int ret;
> +
> + bts = devm_kzalloc(dev, sizeof(*bts), GFP_KERNEL);
I'd not normally expect to see xa_insert using devm allocated memory
because you sort of hand ownership to the xa with that call so I'd expect
on exist we'd see a walk of the xa clearing out everything it is tracking.
> + if (!bts)
> + return NULL;
> +
> + refcount_set(&bts->users, 1);
> + bts->payld = payld;
> + bts->xa_bts = xa_bts;
> + mutex_init(&bts->mtx);
> + ret = xa_insert(xa_bts, (unsigned long)payld, bts, GFP_KERNEL);
> + if (ret) {
> + devm_kfree(dev, bts);
> + return NULL;
> + }
> + }
> +
> + return bts;
> +}
> +
> +static void scmi_telemetry_tdcf_data_parse(struct telemetry_info *ti,
> + struct payload __iomem *payld,
> + struct telemetry_shmti *shmti,
> + enum scan_mode mode)
> +{
> + bool ts_valid = TS_VALID(payld);
> + struct telemetry_de *tde;
> + bool discovered = false;
> + u64 val, tstamp = 0;
> + u32 de_id;
> +
> + de_id = PAYLD_ID(payld);
> + /* Is thi DE ID know ? */
That comment needs a rewrite.
> + tde = scmi_telemetry_tde_lookup(ti, de_id);
> + if (!tde) {
> + if (mode != SCAN_DISCOVERY)
> + return;
> +
> + /* In SCAN_DISCOVERY mode we allocate new DEs for unknown IDs */
> + tde = scmi_telemetry_tde_get(ti, de_id);
> + if (IS_ERR(tde))
> + return;
> +
> + tde->de.info->id = de_id;
> + tde->de.enabled = true;
> + tde->de.tstamp_enabled = ts_valid;
> + discovered = true;
> + }
> +
> + /* Update DE location refs if requested: normally done only on enable */
> + if (mode >= SCAN_UPDATE) {
> + tde->base = shmti->base;
> + tde->eplg = SHMTI_EPLG(shmti);
> + tde->offset = (void *)payld - (void *)shmti->base;
> +
> + dev_dbg(ti->ph->dev,
> + "TDCF-updated DE_ID:0x%08X - shmti:%pX offset:%u\n",
> + tde->de.info->id, tde->base, tde->offset);
> + }
> +
> + if (discovered) {
> + if (scmi_telemetry_tde_register(ti, tde)) {
> + scmi_telemetry_free_tde_put(ti, tde);
> + return;
> + }
> + }
> +
> + scoped_guard(mutex, &tde->mtx) {
> + if (tde->last_magic == shmti->last_magic)
> + return;
> + }
> +
> + /* Data is always valid since we are NOT handling BLK TS lines here */
> + val = LINE_DATA_GET(&payld->l);
> + /* Collect the right TS */
> + if (ts_valid) {
> + if (USE_LINE_TS(payld)) {
> + tstamp = LINE_TSTAMP_GET(&payld->tsl);
> + } else if (USE_BLK_TS(payld)) {
> + if (!tde->bts) {
> + /*
> + * Scanning a TDCF looking for the nearest
> + * previous valid BLK_TS, after having found a
> + * USE_BLK_TS() payload, MUST succeed.
> + */
> + tde->bts = scmi_telemetry_blkts_bind(ti->ph->dev,
> + shmti, payld,
> + &ti->xa_bts);
> + if (WARN_ON(!tde->bts))
> + return;
> + }
> +
> + tstamp = scmi_telemetry_blkts_read(tde->last_magic,
> + tde->bts);
> + }
> + }
> +
> + guard(mutex)(&tde->mtx);
> + tde->last_magic = shmti->last_magic;
> + tde->last_val = val;
> + if (tde->de.tstamp_enabled)
ternary perhaps
tde->last_ts = tde->de.tstamp_enabled ? tstamp : 0;
> + tde->last_ts = tstamp;
> + else
> + tde->last_ts = 0;
> +}
> +static inline void scmi_telemetry_de_data_fc_read(struct telemetry_de *tde,
> + u64 *tstamp, u64 *val)
> +{
> + struct fc_tsline __iomem *fc = tde->base + tde->offset;
> +
> + *val = LINE_DATA_GET(fc);
> + if (tstamp) {
> + if (tde->de.tstamp_support)
> + *tstamp = LINE_TSTAMP_GET(fc);
> + else
> + *tstamp = 0;
*tstamp = tde->de.tstam_support ? LINE_TIMESTAMP_GET(fc) : 0;
> + }
> +}
> +
> +static void scmi_telemetry_scan_update(struct telemetry_info *ti, u64 ts)
> +{
> + struct telemetry_de *tde;
> +
> + /* Scan all SHMTIs ... */
> + for (int id = 0; id < ti->num_shmti; id++) {
> + int ret;
> +
> + ret = scmi_telemetry_shmti_scan(ti, id, ts, SCAN_LOOKUP);
> + if (ret)
Might as well simplify given value of ret only used here.
if (scmi_telemetry_shmti_scan(ti, id, ts, SCAN_LOOKUP)) {
dev_warn();
> + dev_warn(ti->ph->dev,
> + "Failed update-scan of SHMTI ID:%d\n", id);
> + }
> +
> + if (!ti->info.fc_support)
> + return;
> +
> + /* Need to enumerate resources to access fastchannels */
> + ti->res_get(ti);
> + list_for_each_entry(tde, &ti->fcs_des, item) {
> + u64 val, tstamp;
> +
> + if (!tde->de.enabled)
> + continue;
> +
> + scmi_telemetry_de_data_fc_read(tde, &tstamp, &val);
> +
> + guard(mutex)(&tde->mtx);
> + tde->last_val = val;
> + if (tde->de.tstamp_enabled)
> + tde->last_ts = tstamp;
> + else
> + tde->last_ts = 0;
> + }
> +}
> +
> +/*
> + * TDCF and TS Line Management Notes
> + * ---------------------------------
> + * (from a chat with ATG)
That's probably not a detail we want in the long term record, nice
and helpful as they are :)
> + *
> + * TCDF Payload Metadata notable bits:
> + * - Bit[3]: USE BLK Tstamp
> + * - Bit[2]: USE LINE Tstamp
> + * - Bit[1]: Tstamp VALID
> + *
> + * CASE_1:
...
> +static int
> +scmi_telemetry_samples_collect(struct telemetry_info *ti, int grp_id,
> + int *num_samples,
> + struct scmi_telemetry_de_sample *samples)
> +{
> + struct scmi_telemetry_res_info *rinfo;
> + int max_samples;
> +
> + max_samples = *num_samples;
> + *num_samples = 0;
> +
> + rinfo = ti->res_get(ti);
> + for (int i = 0; i < rinfo->num_des; i++) {
> + struct scmi_telemetry_de *de;
> + u64 val, tstamp;
> + int ret;
> +
> + de = rinfo->des[i];
> + if (grp_id != SCMI_TLM_GRP_INVALID &&
> + (!de->grp || de->grp->info->id != grp_id))
> + continue;
> +
> + ret = scmi_telemetry_de_cached_read(ti, de, &tstamp, &val);
> + if (ret)
> + continue;
> +
> + if (*num_samples == max_samples)
> + return -ENOSPC;
> +
> + samples[*num_samples].tstamp = tstamp;
> + samples[*num_samples].val = val;
> + samples[*num_samples].id = de->info->id;
Maybe worth doing
samples[(*num_samples)++] = (struct scmi_telemetry_de_sample) {
.tstamp = tstamp,
.val = val,
.id = de->info->id,
};
so that it is immediately obvious you are filling whole record in (or zeroing
any other fields though not relevant here)
> +
> + (*num_samples)++;
> + }
> +
> + return 0;
> +}
> +
> +static int scmi_telemetry_des_sample_get(const struct scmi_protocol_handle *ph,
> + int grp_id, int *num_samples,
> + struct scmi_telemetry_de_sample *samples)
> +{
> + struct telemetry_info *ti = ph->get_priv(ph);
> + struct scmi_msg_telemetry_config_set *msg;
> + struct scmi_xfer *t;
> + bool grp_ignore;
> + int ret;
> +
> + if (!ti->info.enabled || !num_samples || !samples)
> + return -EINVAL;
> +
> + grp_ignore = grp_id == SCMI_TLM_GRP_INVALID ? true : false;
> + if (!grp_ignore && grp_id >= ti->info.base.num_groups)
> + return -EINVAL;
> +
> + ret = ph->xops->xfer_get_init(ph, TELEMETRY_CONFIG_SET,
> + sizeof(*msg), 0, &t);
> + if (ret)
> + return ret;
> +
> + msg = t->tx.buf;
> + msg->grp_id = grp_id;
> + msg->control = TELEMETRY_ENABLE;
> + msg->control |= grp_ignore ? TELEMETRY_SET_SELECTOR_ALL :
> + TELEMETRY_SET_SELECTOR_GROUP;
> + msg->control |= TELEMETRY_MODE_SINGLE;
> + msg->sampling_rate = 0;
> +
> + ret = ph->xops->do_xfer_with_response(ph, t);
> + if (!ret) {
> + struct scmi_msg_resp_telemetry_reading_complete *r = t->rx.buf;
Feels like that type might benefit form a shorter name
struct scmi_msg_resp_telemetry_rd_comp
maybe?
> +
> + /* Update cached DEs values from payload */
> + if (r->num_dwords)
> + scmi_telemetry_msg_payld_process(ti, r->num_dwords,
> + r->dwords, 0);
> + /* Scan and update SMHTIs and FCs */
> + scmi_telemetry_scan_update(ti, 0);
> +
> + /* Collect all last cached values */
> + ret = scmi_telemetry_samples_collect(ti, grp_id, num_samples,
> + samples);
> + }
> +
> + ph->xops->xfer_put(ph, t);
> +
> + return ret;
> +}
> +
> +static int scmi_telemetry_reset(const struct scmi_protocol_handle *ph)
> +{
> + struct scmi_xfer *t;
> + int ret;
> +
> + ret = ph->xops->xfer_get_init(ph, TELEMETRY_RESET, sizeof(u32), 0, &t);
> + if (ret)
> + return ret;
> +
> + put_unaligned_le32(0, t->tx.buf);
> + ret = ph->xops->do_xfer(ph, t);
> + if (!ret) {
> + struct telemetry_info *ti = ph->get_priv(ph);
> +
> + scmi_telemetry_local_resources_reset(ti);
> + /* Fetch agaon states state from platform.*/
Not sure what that comment means.
> + ret = scmi_telemetry_initial_state_lookup(ti);
> + if (ret)
> + dev_warn(ph->dev,
> + FW_BUG "Cannot retrieve initial state after reset.\n");
> + }
> +
> + ph->xops->xfer_put(ph, t);
> +
> + return ret;
> +}
> +static void *
> +scmi_telemetry_fill_custom_report(const struct scmi_protocol_handle *ph,
> + u8 evt_id, ktime_t timestamp,
> + const void *payld, size_t payld_sz,
> + void *report, u32 *src_id)
> +{
> + const struct scmi_telemetry_update_notify_payld *p = payld;
> + struct scmi_telemetry_update_report *r = report;
> +
> + /* At least sized as an empty notification */
> + if (payld_sz < sizeof(*p))
> + return NULL;
> +
> + r->timestamp = timestamp;
> + r->agent_id = le32_to_cpu(p->agent_id);
> + r->status = le32_to_cpu(p->status);
> + r->num_dwords = le32_to_cpu(p->num_dwords);
> + /*
> + * Allocated dwords and report are sized as max_msg_size, so as
> + * to allow for the maximum payload permitted by the configured
> + * transport. Overflow is not possible since out-of-size messages
> + * are dropped at the transport layer.
> + */
> + if (r->num_dwords)
> + memcpy(r->dwords, p->array, r->num_dwords * sizeof(u32));
This needs le32 magic as you are copying from an array of those to an array
of unsigned int (if you do this as a memcpy that should be u32 to make the
size explicit).
memcpy_from_le32() should do what you need here.
> +
> + *src_id = 0;
> +
> + return r;
> +}
> diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
> index c6efe4f371ac..d58b81ffd81e 100644
> --- a/include/linux/scmi_protocol.h
> +++ b/include/linux/scmi_protocol.h
> +
> +/**
> + * struct scmi_telemetry_proto_ops - represents the various operations provided
> + * by SCMI Telemetry Protocol
> + *
> + * @info_get: get the general Telemetry information.
> + * @de_lookup: get a specific DE descriptor from the DE id.
> + * @res_get: get a reference to the Telemetry resources descriptor.
> + * @state_get: retrieve the specific DE or GROUP state.
> + * @state_set: enable/disable the specific DE or GROUP with or without timestamps.
> + * @all_disable: disable ALL DEs or GROUPs.
> + * @collection_configure: choose a sampling rate and enable SHMTI/FC sampling
> + * for on demand collection via @de_data_read or async
> + * notificatioins for all the enabled DEs.
> + * @de_data_read: on-demand read of a single DE and related optional timestamp:
> + * the value will be retrieved at the proper SHMTI offset OR
> + * from the dedicated FC area (if supported by that DE).
> + * @des_bulk_read: on-demand read of all the currently enabled DEs, or just
> + * the ones belonging to a specific group when provided.
> + * @des_sample_get: on-demand read of all the currently enabled DEs, or just
> + * the ones belonging to a specific group when provided.
> + * This causes an immediate update platform-side of all the
> + * enabled DEs.
> + * @config_get: retrieve current telemetry configuration.
> + * @reset: reset configuration and telemetry data.
> + */
> +struct scmi_telemetry_proto_ops {
> + const struct scmi_telemetry_info __must_check *(*info_get)
> + (const struct scmi_protocol_handle *ph);
> + const struct scmi_telemetry_de __must_check *(*de_lookup)
> + (const struct scmi_protocol_handle *ph, u32 id);
> + const struct scmi_telemetry_res_info __must_check *(*res_get)
> + (const struct scmi_protocol_handle *ph);
> + int (*state_get)(const struct scmi_protocol_handle *ph,
> + u32 id, bool *enabled, bool *tstamp_enabled);
> + int (*state_set)(const struct scmi_protocol_handle *ph,
> + bool is_group, u32 id, bool *enable, bool *tstamp);
> + int (*all_disable)(const struct scmi_protocol_handle *ph, bool group);
> + int (*collection_configure)(const struct scmi_protocol_handle *ph,
> + unsigned int res_id, bool grp_ignore,
> + bool *enable,
> + unsigned int *update_interval_ms,
> + enum scmi_telemetry_collection *mode);
> + int (*de_data_read)(const struct scmi_protocol_handle *ph,
> + struct scmi_telemetry_de_sample *sample);
> + int __must_check (*des_bulk_read)(const struct scmi_protocol_handle *ph,
I'm curious. What about this one makes it suitable for a __must_check?
Seems a bit random.
> + int grp_id, int *num_samples,
> + struct scmi_telemetry_de_sample *samples);
> + int __must_check (*des_sample_get)(const struct scmi_protocol_handle *ph,
> + int grp_id, int *num_samples,
> + struct scmi_telemetry_de_sample *samples);
> + int (*config_get)(const struct scmi_protocol_handle *ph, bool *enabled,
> + int *mode, u32 *update_interval);
> + int (*reset)(const struct scmi_protocol_handle *ph);
> +};
> +
> +struct scmi_telemetry_update_report {
> + ktime_t timestamp;
> + unsigned int agent_id;
> + int status;
> + unsigned int num_dwords;
> + unsigned int dwords[];
__counted_by not appropriate?
> +};
> #endif /* _LINUX_SCMI_PROTOCOL_H */
> + put_unaligned_le32(0, t->tx.buf);
> + ret = ph->xops->do_xfer(ph, t);
On Mon, Jan 19, 2026 at 04:29:32PM +0000, Jonathan Cameron wrote:
> On Wed, 14 Jan 2026 11:46:09 +0000
> Cristian Marussi <cristian.marussi@arm.com> wrote:
>
> > Add basic support for SCMI V4.0 Telemetry protocol including SHMTI,
> > FastChannels, Notifications and Single Sample Reads collection methods.
> >
Hi Jonathan,
> > Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
> > ---
> > v1 --> v2
> > - Add proper ioread accessors for TDCF areas
> > - Rework resource allocation logic and lifecycle
> > - Introduce new resources accessors and res_get() operation to
> > implement lazy enumeration
> > - Support boot-on telemetry:
> > + Add DE_ENABLED_LIST cmd support
> > + Add CONFIG_GET cmd support
> > + Add TDCF_SCAN for best effort enumeration
> > + Harden driver against out-of-spec FW with out of spec cmds
> > - Use FCs list
> > - Rework de_info_lookup to a moer general de_lookup
> > - Fixed reset to use CONFIG_GET and DE_ENABLED_LIST to gather initial
> > state
> > - Using sign_extend32 helper
> > - Added counted_by marker where appropriate
> > ---
> > drivers/firmware/arm_scmi/Makefile | 2 +-
> > drivers/firmware/arm_scmi/driver.c | 2 +
> > drivers/firmware/arm_scmi/protocols.h | 1 +
> > drivers/firmware/arm_scmi/telemetry.c | 2671 +++++++++++++++++++++++++
>
> Ouch. Might be worth splitting this up into more bite sized pieces.
>
> It's a bit of a take a deep breath before diving in patch at the moment.
> So the following is rather superficial.
Yes, indeed this has to be split..usually I post the protocol unit in
one patch...but telemetry is rather big...I will split this on V3 once I
had some mofre feedback especially on the FS part and its supposed
location in the siource tree (now lives all in drivers and I suppose/guess
is frowned upon...also FS is a bit more split..but it can be further
split)
>
> > include/linux/scmi_protocol.h | 188 +-
> > 5 files changed, 2862 insertions(+), 2 deletions(-)
> > create mode 100644 drivers/firmware/arm_scmi/telemetry.c
> >
>
>
>
> > diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
> > new file mode 100644
> > index 000000000000..16bcdcdc1dc3
> > --- /dev/null
> > +++ b/drivers/firmware/arm_scmi/telemetry.c
> > @@ -0,0 +1,2671 @@
>
> > +static void scmi_telemetry_free_tde_put(struct telemetry_info *ti,
> > + struct telemetry_de *tde)
> > +{
> > + guard(mutex)(&ti->free_mtx);
> > +
> > + list_add_tail(&tde->item, &ti->free_des);
> > +}
>
> > +static int iter_de_descr_update_state(struct scmi_iterator_state *st,
> > + const void *response, void *priv)
> > +{
> > + const struct scmi_msg_resp_telemetry_de_description *r = response;
> > + struct scmi_tlm_de_priv *p = priv;
> > +
> > + st->num_returned = le32_get_bits(r->num_desc, GENMASK(15, 0));
> > + st->num_remaining = le32_get_bits(r->num_desc, GENMASK(31, 16));
> > +
> > + /* Initialized to first descriptor */
> > + p->next = (void *)r->desc;
>
> No need to cast to a void *
> C always lets you do this implicitly if the target type is a void *.
Indeed...I think I recall that (as usual) is because I have to drop the
const part from r without upsetting the compiler...and next is just a
reference to iterate the payload so it is fine to be non const
>
> > +
> > + return 0;
> > +}
> > +
>
>
> > +static int scmi_telemetry_initial_state_lookup(struct telemetry_info *ti)
> > +{
> > + struct device *dev = ti->ph->dev;
> > + int ret;
> > +
> > + ret = scmi_telemetry_config_lookup(ti, SCMI_TLM_GRP_INVALID,
> > + &ti->info.enabled,
> > + &ti->info.active_update_interval);
> > + if (ret)
> > + return ret;
> > +
> > + if (ti->info.enabled) {
>
> I'd flip logic to reduce indent
> if (!ti->info.enabled)
> return 0;
Yep, I will do.
>
> /*
> *...
>
> > + /*
> > + * When Telemetry is found already enabled on the platform,
> > + * proceed with passive discovery using DE_ENABLED_LIST and
> > + * TCDF scanning: note that this CAN only discover DEs exposed
> > + * via SHMTIs.
> > + * FastChannel DEs need a proper DE_DESCRIPTION enumeration,
> > + * while, even though incoming Notifications could be used for
> > + * passive discovery too, it would carry a considerable risk
> > + * of assimilating trash as DEs.
> > + */
> > + dev_info(dev,
> > + "Telemetry found enabled with update interval %ux10^%d\n",
> > + SCMI_TLM_GET_UPDATE_INTERVAL_SECS(ti->info.active_update_interval),
> > + SCMI_TLM_GET_UPDATE_INTERVAL_EXP(ti->info.active_update_interval));
> > + /*
> > + * Query enabled DEs list: collect states.
> > + * It will include DEs from any interface.
> > + * Enabled groups still NOT enumerated.
> > + */
> > + ret = scmi_telemetry_enumerate_des_enabled_list(ti);
> > + if (ret)
> > + dev_warn(dev, FW_BUG "Cannot query enabled DE list. Carry-on.\n");
> > +
> > + /* Discover DEs on SHMTis: collect states/offsets/values */
> > + for (int id = 0; id < ti->num_shmti; id++) {
> > + ret = scmi_telemetry_shmti_scan(ti, id, 0, SCAN_DISCOVERY);
> > + if (ret)
> > + dev_warn(dev, "Failed discovery-scan of SHMTI ID:%d\n", id);
> > + }
> > + }
> > +
> > + return 0;
> > +}
> }
>
> > +static int iter_intervals_update_state(struct scmi_iterator_state *st,
> > + const void *response, void *priv)
> > +{
> > + const struct scmi_msg_resp_telemetry_update_intervals *r = response;
> > +
> > + st->num_returned = le32_get_bits(r->flags, GENMASK(11, 0));
> > + st->num_remaining = le32_get_bits(r->flags, GENMASK(31, 16));
> > +
> > + /*
> > + * total intervals is not declared previously anywhere so we
> > + * assume it's returned+remaining on first call.
> > + */
> > + if (!st->max_resources) {
> > + struct scmi_tlm_ivl_priv *p = priv;
> > + bool discrete;
> > + int inum;
> > +
> > + discrete = INTERVALS_DISCRETE(r->flags);
> > + /* Check consistency on first call */
> > + if (!discrete && (st->num_returned != 3 || st->num_remaining != 0))
> > + return -EINVAL;
> > +
> > + inum = st->num_returned + st->num_remaining;
> > + struct scmi_tlm_intervals *intrvs __free(kfree) =
> > + kzalloc(sizeof(*intrvs) + inum * sizeof(__u32), GFP_KERNEL);
>
> Unless this is going to get more complex, the __free() isn't doing anything useful
> as there are no other error paths before the no_free_ptr().
Yes indeed...I was trying to be consistent...but here makes no sense.
>
> > + if (!intrvs)
> > + return -ENOMEM;
> > +
> > + intrvs->num = inum;
> > + intrvs->discrete = discrete;
> > + st->max_resources = intrvs->num;
> > +
> > + *p->intrvs = no_free_ptr(intrvs);
> > + }
> > +
> > + return 0;
>
> > +static int
> > +scmi_tlm_enumerate_update_intervals(struct telemetry_info *ti,
> > + struct scmi_tlm_intervals **intervals,
> > + int grp_id, unsigned int flags)
> > +{
> > + struct scmi_iterator_ops ops = {
> > + .prepare_message = iter_intervals_prepare_message,
> > + .update_state = iter_intervals_update_state,
> > + .process_response = iter_intervals_process_response,
> > + };
> > + const struct scmi_protocol_handle *ph = ti->ph;
> > + struct scmi_tlm_ivl_priv ipriv = {
> > + .dev = ph->dev,
> > + .grp_id = grp_id,
> > + .intrvs = intervals,
> > + .flags = flags,
> > + };
> > + void *iter;
> > +
> > + iter = ph->hops->iter_response_init(ph, &ops, 0,
> > + TELEMETRY_LIST_UPDATE_INTERVALS,
> > + sizeof(struct scmi_msg_telemetry_update_intervals),
> This alignment is unusual. Given the length of that type name I'd do this as:
>
> iter = ph->hops->iter_response_init(ph, &ops, 0,
> TELEMETRY_LIST_UPDATE_INTERVALS,
> sizeof(struct scmi_msg_telemetry_update_intervals),
> &ipriv);
>
Ok.
> > + &ipriv);
> > + if (IS_ERR(iter))
> > + return PTR_ERR(iter);
> > +
> > + return ph->hops->iter_response_run(iter);
> > +}
>
> > +static const struct scmi_telemetry_de *
> > +scmi_telemetry_de_lookup(const struct scmi_protocol_handle *ph, u32 id)
> > +{
> > + struct telemetry_info *ti = ph->get_priv(ph);
> > + struct scmi_telemetry_de *de;
> > +
> > + ti->res_get(ti);
> > + de = xa_load(&ti->xa_des, id);
> > + if (!de)
> > + return NULL;
> > +
> > + return de;
>
> return xa_load(&ti->xa_des, id);
Agreed.
>
> > +}
>
>
> > +static struct payload *
> > +scmi_telemetry_nearest_blk_ts(struct telemetry_shmti *shmti,
> > + struct payload *last_payld)
> > +{
> > + struct payload *payld, *bts_payld = NULL;
> > + struct tdcf __iomem *tdcf = shmti->base;
> > + u32 *next;
> > +
> > + /* Scan from start of TDCF payloads up to last_payld */
> > + payld = (struct payload *)tdcf->payld;
>
> casting away the __iomem is usualy a bad idea.
> Shouldn't a readl or similar be used to get next below.
Yes I still have to properly rework a bit of these __iomem access paths
in a more consistent way (sparse/smatch still screams a lot)...
..all the below accessors macros are defined to use transparently an
ioread32() or similar...next is just a reference to track the payld
is only payload that is effectly accessed and needs proper iomem
accessors...
>
>
> > + next = (u32 *)payld;
> > + while (payld < last_payld) {
> > + if (IS_BLK_TS(payld))
> > + bts_payld = payld;
> > +
> > + next += USE_LINE_TS(payld) ?
> > + TS_LINE_DATA_PAYLD_WORDS : LINE_DATA_PAYLD_WORDS;
> > + payld = (struct payload *)next;
> > + }
> > +
> > + return bts_payld;
> > +}
> > +
> > +static struct telemetry_block_ts *
> > +scmi_telemetry_blkts_lookup(struct device *dev, struct xarray *xa_bts,
> > + struct payload *payld)
> > +{
> > + struct telemetry_block_ts *bts;
> > +
> > + bts = xa_load(xa_bts, (unsigned long)payld);
> > + if (!bts) {
> > + int ret;
> > +
> > + bts = devm_kzalloc(dev, sizeof(*bts), GFP_KERNEL);
>
> I'd not normally expect to see xa_insert using devm allocated memory
> because you sort of hand ownership to the xa with that call so I'd expect
> on exist we'd see a walk of the xa clearing out everything it is tracking.
Good point...I have to say, though, to my excuse, that this part of the protocol
code related to Block Timestamp (_blt_ts_) is knowingly very poorly curated and
reworked since V1 given that it has been deeply changed at the spec level in the
latest BETA spec (a few weks ago) so it will be changed substantially in the
next V3 when BETA will be supported...
...anyway...my bad I should have warned about this in the inline comments...now it
is only generically mentioned in the cover letter that the series is still implementing
ALPA_0 spec.
>
> > + if (!bts)
> > + return NULL;
> > +
> > + refcount_set(&bts->users, 1);
> > + bts->payld = payld;
> > + bts->xa_bts = xa_bts;
> > + mutex_init(&bts->mtx);
> > + ret = xa_insert(xa_bts, (unsigned long)payld, bts, GFP_KERNEL);
> > + if (ret) {
> > + devm_kfree(dev, bts);
> > + return NULL;
> > + }
> > + }
> > +
> > + return bts;
> > +}
>
> > +
> > +static void scmi_telemetry_tdcf_data_parse(struct telemetry_info *ti,
> > + struct payload __iomem *payld,
> > + struct telemetry_shmti *shmti,
> > + enum scan_mode mode)
> > +{
> > + bool ts_valid = TS_VALID(payld);
> > + struct telemetry_de *tde;
> > + bool discovered = false;
> > + u64 val, tstamp = 0;
> > + u32 de_id;
> > +
> > + de_id = PAYLD_ID(payld);
> > + /* Is thi DE ID know ? */
>
> That comment needs a rewrite.
>
Yes.
> > + tde = scmi_telemetry_tde_lookup(ti, de_id);
> > + if (!tde) {
> > + if (mode != SCAN_DISCOVERY)
> > + return;
> > +
> > + /* In SCAN_DISCOVERY mode we allocate new DEs for unknown IDs */
> > + tde = scmi_telemetry_tde_get(ti, de_id);
> > + if (IS_ERR(tde))
> > + return;
> > +
> > + tde->de.info->id = de_id;
> > + tde->de.enabled = true;
> > + tde->de.tstamp_enabled = ts_valid;
> > + discovered = true;
> > + }
> > +
> > + /* Update DE location refs if requested: normally done only on enable */
> > + if (mode >= SCAN_UPDATE) {
> > + tde->base = shmti->base;
> > + tde->eplg = SHMTI_EPLG(shmti);
> > + tde->offset = (void *)payld - (void *)shmti->base;
> > +
> > + dev_dbg(ti->ph->dev,
> > + "TDCF-updated DE_ID:0x%08X - shmti:%pX offset:%u\n",
> > + tde->de.info->id, tde->base, tde->offset);
> > + }
> > +
> > + if (discovered) {
> > + if (scmi_telemetry_tde_register(ti, tde)) {
> > + scmi_telemetry_free_tde_put(ti, tde);
> > + return;
> > + }
> > + }
> > +
> > + scoped_guard(mutex, &tde->mtx) {
> > + if (tde->last_magic == shmti->last_magic)
> > + return;
> > + }
> > +
> > + /* Data is always valid since we are NOT handling BLK TS lines here */
> > + val = LINE_DATA_GET(&payld->l);
> > + /* Collect the right TS */
> > + if (ts_valid) {
> > + if (USE_LINE_TS(payld)) {
> > + tstamp = LINE_TSTAMP_GET(&payld->tsl);
> > + } else if (USE_BLK_TS(payld)) {
> > + if (!tde->bts) {
> > + /*
> > + * Scanning a TDCF looking for the nearest
> > + * previous valid BLK_TS, after having found a
> > + * USE_BLK_TS() payload, MUST succeed.
> > + */
> > + tde->bts = scmi_telemetry_blkts_bind(ti->ph->dev,
> > + shmti, payld,
> > + &ti->xa_bts);
> > + if (WARN_ON(!tde->bts))
> > + return;
> > + }
> > +
> > + tstamp = scmi_telemetry_blkts_read(tde->last_magic,
> > + tde->bts);
> > + }
> > + }
> > +
> > + guard(mutex)(&tde->mtx);
> > + tde->last_magic = shmti->last_magic;
> > + tde->last_val = val;
> > + if (tde->de.tstamp_enabled)
>
> ternary perhaps
> tde->last_ts = tde->de.tstamp_enabled ? tstamp : 0;
>
Yes much better.
>
> > + tde->last_ts = tstamp;
> > + else
> > + tde->last_ts = 0;
> > +}
>
>
>
>
> > +static inline void scmi_telemetry_de_data_fc_read(struct telemetry_de *tde,
> > + u64 *tstamp, u64 *val)
> > +{
> > + struct fc_tsline __iomem *fc = tde->base + tde->offset;
> > +
> > + *val = LINE_DATA_GET(fc);
> > + if (tstamp) {
> > + if (tde->de.tstamp_support)
> > + *tstamp = LINE_TSTAMP_GET(fc);
> > + else
> > + *tstamp = 0;
>
> *tstamp = tde->de.tstam_support ? LINE_TIMESTAMP_GET(fc) : 0;
Indeed.
>
> > + }
> > +}
> > +
> > +static void scmi_telemetry_scan_update(struct telemetry_info *ti, u64 ts)
> > +{
> > + struct telemetry_de *tde;
> > +
> > + /* Scan all SHMTIs ... */
> > + for (int id = 0; id < ti->num_shmti; id++) {
> > + int ret;
> > +
> > + ret = scmi_telemetry_shmti_scan(ti, id, ts, SCAN_LOOKUP);
> > + if (ret)
>
> Might as well simplify given value of ret only used here.
>
> if (scmi_telemetry_shmti_scan(ti, id, ts, SCAN_LOOKUP)) {
> dev_warn();
I'll do.
>
> > + dev_warn(ti->ph->dev,
> > + "Failed update-scan of SHMTI ID:%d\n", id);
> > + }
> > +
> > + if (!ti->info.fc_support)
> > + return;
> > +
> > + /* Need to enumerate resources to access fastchannels */
> > + ti->res_get(ti);
> > + list_for_each_entry(tde, &ti->fcs_des, item) {
> > + u64 val, tstamp;
> > +
> > + if (!tde->de.enabled)
> > + continue;
> > +
> > + scmi_telemetry_de_data_fc_read(tde, &tstamp, &val);
> > +
> > + guard(mutex)(&tde->mtx);
> > + tde->last_val = val;
> > + if (tde->de.tstamp_enabled)
> > + tde->last_ts = tstamp;
> > + else
> > + tde->last_ts = 0;
> > + }
> > +}
> > +
> > +/*
> > + * TDCF and TS Line Management Notes
> > + * ---------------------------------
> > + * (from a chat with ATG)
>
> That's probably not a detail we want in the long term record, nice
> and helpful as they are :)
Mmmm....it was to keep track of somehow of these discussions, the
reasons and the origin of the (supposed) truth...but yes I can strip
down the details :P ... especially because some of these clarifications
indeed have now been merged into the BETA spec so the wording around
this should be less ammbiguos in BETA...
>
>
> > + *
> > + * TCDF Payload Metadata notable bits:
> > + * - Bit[3]: USE BLK Tstamp
> > + * - Bit[2]: USE LINE Tstamp
> > + * - Bit[1]: Tstamp VALID
> > + *
> > + * CASE_1:
> ...
>
>
> > +static int
> > +scmi_telemetry_samples_collect(struct telemetry_info *ti, int grp_id,
> > + int *num_samples,
> > + struct scmi_telemetry_de_sample *samples)
> > +{
> > + struct scmi_telemetry_res_info *rinfo;
> > + int max_samples;
> > +
> > + max_samples = *num_samples;
> > + *num_samples = 0;
> > +
> > + rinfo = ti->res_get(ti);
> > + for (int i = 0; i < rinfo->num_des; i++) {
> > + struct scmi_telemetry_de *de;
> > + u64 val, tstamp;
> > + int ret;
> > +
> > + de = rinfo->des[i];
> > + if (grp_id != SCMI_TLM_GRP_INVALID &&
> > + (!de->grp || de->grp->info->id != grp_id))
> > + continue;
> > +
> > + ret = scmi_telemetry_de_cached_read(ti, de, &tstamp, &val);
> > + if (ret)
> > + continue;
> > +
> > + if (*num_samples == max_samples)
> > + return -ENOSPC;
> > +
> > + samples[*num_samples].tstamp = tstamp;
> > + samples[*num_samples].val = val;
> > + samples[*num_samples].id = de->info->id;
> Maybe worth doing
> samples[(*num_samples)++] = (struct scmi_telemetry_de_sample) {
> .tstamp = tstamp,
> .val = val,
> .id = de->info->id,
> };
> so that it is immediately obvious you are filling whole record in (or zeroing
> any other fields though not relevant here)
wow...that is defintely a construct I would not have come up with :P
..thanks for the hint..
>
> > +
> > + (*num_samples)++;
> > + }
> > +
> > + return 0;
> > +}
>
> > +
> > +static int scmi_telemetry_des_sample_get(const struct scmi_protocol_handle *ph,
> > + int grp_id, int *num_samples,
> > + struct scmi_telemetry_de_sample *samples)
> > +{
> > + struct telemetry_info *ti = ph->get_priv(ph);
> > + struct scmi_msg_telemetry_config_set *msg;
> > + struct scmi_xfer *t;
> > + bool grp_ignore;
> > + int ret;
> > +
> > + if (!ti->info.enabled || !num_samples || !samples)
> > + return -EINVAL;
> > +
> > + grp_ignore = grp_id == SCMI_TLM_GRP_INVALID ? true : false;
> > + if (!grp_ignore && grp_id >= ti->info.base.num_groups)
> > + return -EINVAL;
> > +
> > + ret = ph->xops->xfer_get_init(ph, TELEMETRY_CONFIG_SET,
> > + sizeof(*msg), 0, &t);
> > + if (ret)
> > + return ret;
> > +
> > + msg = t->tx.buf;
> > + msg->grp_id = grp_id;
> > + msg->control = TELEMETRY_ENABLE;
> > + msg->control |= grp_ignore ? TELEMETRY_SET_SELECTOR_ALL :
> > + TELEMETRY_SET_SELECTOR_GROUP;
> > + msg->control |= TELEMETRY_MODE_SINGLE;
> > + msg->sampling_rate = 0;
> > +
> > + ret = ph->xops->do_xfer_with_response(ph, t);
> > + if (!ret) {
> > + struct scmi_msg_resp_telemetry_reading_complete *r = t->rx.buf;
>
> Feels like that type might benefit form a shorter name
> struct scmi_msg_resp_telemetry_rd_comp
> maybe?
Yes...we try to stick to some standard msg/reply struct naming across protocols
definition ... but this leads to awfully long names...
>
> > +
> > + /* Update cached DEs values from payload */
> > + if (r->num_dwords)
> > + scmi_telemetry_msg_payld_process(ti, r->num_dwords,
> > + r->dwords, 0);
> > + /* Scan and update SMHTIs and FCs */
> > + scmi_telemetry_scan_update(ti, 0);
> > +
> > + /* Collect all last cached values */
> > + ret = scmi_telemetry_samples_collect(ti, grp_id, num_samples,
> > + samples);
> > + }
> > +
> > + ph->xops->xfer_put(ph, t);
> > +
> > + return ret;
> > +}
>
> > +
> > +static int scmi_telemetry_reset(const struct scmi_protocol_handle *ph)
> > +{
> > + struct scmi_xfer *t;
> > + int ret;
> > +
> > + ret = ph->xops->xfer_get_init(ph, TELEMETRY_RESET, sizeof(u32), 0, &t);
> > + if (ret)
> > + return ret;
> > +
> > + put_unaligned_le32(0, t->tx.buf);
> > + ret = ph->xops->do_xfer(ph, t);
> > + if (!ret) {
> > + struct telemetry_info *ti = ph->get_priv(ph);
> > +
> > + scmi_telemetry_local_resources_reset(ti);
> > + /* Fetch agaon states state from platform.*/
>
> Not sure what that comment means.
That there was a problem between the screen and the keyboard :D ...
..I will fix...the meaning was simply...
Fetch again the states from the platform.
... but seemed more gaelic from my mis-spelling :P
>
> > + ret = scmi_telemetry_initial_state_lookup(ti);
> > + if (ret)
> > + dev_warn(ph->dev,
> > + FW_BUG "Cannot retrieve initial state after reset.\n");
> > + }
> > +
> > + ph->xops->xfer_put(ph, t);
> > +
> > + return ret;
> > +}
>
> > +static void *
> > +scmi_telemetry_fill_custom_report(const struct scmi_protocol_handle *ph,
> > + u8 evt_id, ktime_t timestamp,
> > + const void *payld, size_t payld_sz,
> > + void *report, u32 *src_id)
> > +{
> > + const struct scmi_telemetry_update_notify_payld *p = payld;
> > + struct scmi_telemetry_update_report *r = report;
> > +
> > + /* At least sized as an empty notification */
> > + if (payld_sz < sizeof(*p))
> > + return NULL;
> > +
> > + r->timestamp = timestamp;
> > + r->agent_id = le32_to_cpu(p->agent_id);
> > + r->status = le32_to_cpu(p->status);
> > + r->num_dwords = le32_to_cpu(p->num_dwords);
> > + /*
> > + * Allocated dwords and report are sized as max_msg_size, so as
> > + * to allow for the maximum payload permitted by the configured
> > + * transport. Overflow is not possible since out-of-size messages
> > + * are dropped at the transport layer.
> > + */
> > + if (r->num_dwords)
> > + memcpy(r->dwords, p->array, r->num_dwords * sizeof(u32));
>
> This needs le32 magic as you are copying from an array of those to an array
> of unsigned int (if you do this as a memcpy that should be u32 to make the
> size explicit).
>
> memcpy_from_le32() should do what you need here.
So...this is the usual per-protocol events handler that is used to parse
the notification payload and build a notification report for the users
interested in this notification to use...
...in this series this report is really used by this protocol itself in
scmi_telemetry_msg_payld_process() to cache the received DE data...and
in this last routine all teh LE32 handling happens ...
...this is the only protocol indeed that makes use and process notifcation
reports from within...
...so it works properly at the end ...BUT is wrong as you pointed out since
any other user interested in these generic notification support will not
enjoy these post-processing conversion and also the report itself uses
u32 fields already so....no excuses..
I will fix...and sparse/smatch will scream a little less..
>
>
> > +
> > + *src_id = 0;
> > +
> > + return r;
> > +}
>
>
> > diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
> > index c6efe4f371ac..d58b81ffd81e 100644
> > --- a/include/linux/scmi_protocol.h
> > +++ b/include/linux/scmi_protocol.h
>
> > +
> > +/**
> > + * struct scmi_telemetry_proto_ops - represents the various operations provided
> > + * by SCMI Telemetry Protocol
> > + *
> > + * @info_get: get the general Telemetry information.
> > + * @de_lookup: get a specific DE descriptor from the DE id.
> > + * @res_get: get a reference to the Telemetry resources descriptor.
> > + * @state_get: retrieve the specific DE or GROUP state.
> > + * @state_set: enable/disable the specific DE or GROUP with or without timestamps.
> > + * @all_disable: disable ALL DEs or GROUPs.
> > + * @collection_configure: choose a sampling rate and enable SHMTI/FC sampling
> > + * for on demand collection via @de_data_read or async
> > + * notificatioins for all the enabled DEs.
> > + * @de_data_read: on-demand read of a single DE and related optional timestamp:
> > + * the value will be retrieved at the proper SHMTI offset OR
> > + * from the dedicated FC area (if supported by that DE).
> > + * @des_bulk_read: on-demand read of all the currently enabled DEs, or just
> > + * the ones belonging to a specific group when provided.
> > + * @des_sample_get: on-demand read of all the currently enabled DEs, or just
> > + * the ones belonging to a specific group when provided.
> > + * This causes an immediate update platform-side of all the
> > + * enabled DEs.
> > + * @config_get: retrieve current telemetry configuration.
> > + * @reset: reset configuration and telemetry data.
> > + */
> > +struct scmi_telemetry_proto_ops {
> > + const struct scmi_telemetry_info __must_check *(*info_get)
> > + (const struct scmi_protocol_handle *ph);
> > + const struct scmi_telemetry_de __must_check *(*de_lookup)
> > + (const struct scmi_protocol_handle *ph, u32 id);
> > + const struct scmi_telemetry_res_info __must_check *(*res_get)
> > + (const struct scmi_protocol_handle *ph);
> > + int (*state_get)(const struct scmi_protocol_handle *ph,
> > + u32 id, bool *enabled, bool *tstamp_enabled);
> > + int (*state_set)(const struct scmi_protocol_handle *ph,
> > + bool is_group, u32 id, bool *enable, bool *tstamp);
> > + int (*all_disable)(const struct scmi_protocol_handle *ph, bool group);
> > + int (*collection_configure)(const struct scmi_protocol_handle *ph,
> > + unsigned int res_id, bool grp_ignore,
> > + bool *enable,
> > + unsigned int *update_interval_ms,
> > + enum scmi_telemetry_collection *mode);
> > + int (*de_data_read)(const struct scmi_protocol_handle *ph,
> > + struct scmi_telemetry_de_sample *sample);
> > + int __must_check (*des_bulk_read)(const struct scmi_protocol_handle *ph,
>
> I'm curious. What about this one makes it suitable for a __must_check?
> Seems a bit random.
So some of these read functions will return -EINVAL when called with an
invalid setup while in some other cases could return simply an empty buffer
e.g.:
de_data_read () -> a single DE read...-EINVAL if the DE is NOT
enabled
des_bulk_read() - > returns only the enabled DEs and so it can
return an empty buffer when NO DEs are enabled
BUT it returns -EINVAL if called when Telemetry
is disabled as a whole
des_sample_get() -> same logic as des_bulk_read() BUT with async
messages
..so I would say..it is NOT random...but needs to be reviewed when the
__must_check is applied...since as an example is probably missing in
de_data_read()
>
> > + int grp_id, int *num_samples,
> > + struct scmi_telemetry_de_sample *samples);
> > + int __must_check (*des_sample_get)(const struct scmi_protocol_handle *ph,
> > + int grp_id, int *num_samples,
> > + struct scmi_telemetry_de_sample *samples);
> > + int (*config_get)(const struct scmi_protocol_handle *ph, bool *enabled,
> > + int *mode, u32 *update_interval);
> > + int (*reset)(const struct scmi_protocol_handle *ph);
> > +};
>
Thanks a lot Jonathan for having a look at this series.
It still needs some work to cleanup and split as you could see.
Thanks,
Cristian
© 2016 - 2026 Red Hat, Inc.