Add SCMIv4.0 Telemetry basic support to enable initialization and resources
enumeration: add all the telemetry messages definitions and parsing logic
but only a few simple state gathering protocol operations.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2 --> v3
- split from monolithic Telemetry patch
- fix checkpatch macros complaints
- fix ACCESS_PRIVATE usage
- add a few comments on allocation/enumeration lifetime
- use interval.num_intervals
- removed needless cleanup handler usage
- simply return from scmi_telemetry_de_lookup()
- fixed composing_des name length to 08X
---
drivers/firmware/arm_scmi/Makefile | 2 +-
drivers/firmware/arm_scmi/driver.c | 2 +
drivers/firmware/arm_scmi/protocols.h | 1 +
drivers/firmware/arm_scmi/telemetry.c | 1375 +++++++++++++++++++++++++
include/linux/scmi_protocol.h | 135 ++-
5 files changed, 1513 insertions(+), 2 deletions(-)
create mode 100644 drivers/firmware/arm_scmi/telemetry.c
diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
index 780cd62b2f78..fe55b7aa0707 100644
--- a/drivers/firmware/arm_scmi/Makefile
+++ b/drivers/firmware/arm_scmi/Makefile
@@ -8,7 +8,7 @@ scmi-driver-$(CONFIG_ARM_SCMI_RAW_MODE_SUPPORT) += raw_mode.o
scmi-transport-$(CONFIG_ARM_SCMI_HAVE_SHMEM) = shmem.o
scmi-transport-$(CONFIG_ARM_SCMI_HAVE_MSG) += msg.o
scmi-protocols-y := base.o clock.o perf.o power.o reset.o sensors.o system.o voltage.o powercap.o
-scmi-protocols-y += pinctrl.o
+scmi-protocols-y += pinctrl.o telemetry.o
scmi-module-objs := $(scmi-driver-y) $(scmi-protocols-y) $(scmi-transport-y)
obj-$(CONFIG_ARM_SCMI_PROTOCOL) += transports/
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index c4aefbeead62..a4a2e52e1f3d 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -3508,6 +3508,7 @@ static int __init scmi_driver_init(void)
scmi_system_register();
scmi_powercap_register();
scmi_pinctrl_register();
+ scmi_telemetry_register();
return platform_driver_register(&scmi_driver);
}
@@ -3526,6 +3527,7 @@ static void __exit scmi_driver_exit(void)
scmi_system_unregister();
scmi_powercap_unregister();
scmi_pinctrl_unregister();
+ scmi_telemetry_unregister();
platform_driver_unregister(&scmi_driver);
diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h
index 3e7b6f8aa72c..3250d981664b 100644
--- a/drivers/firmware/arm_scmi/protocols.h
+++ b/drivers/firmware/arm_scmi/protocols.h
@@ -386,5 +386,6 @@ DECLARE_SCMI_REGISTER_UNREGISTER(sensors);
DECLARE_SCMI_REGISTER_UNREGISTER(voltage);
DECLARE_SCMI_REGISTER_UNREGISTER(system);
DECLARE_SCMI_REGISTER_UNREGISTER(powercap);
+DECLARE_SCMI_REGISTER_UNREGISTER(telemetry);
#endif /* _SCMI_PROTOCOLS_H */
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
new file mode 100644
index 000000000000..7e5af7bd9fdc
--- /dev/null
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -0,0 +1,1375 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * System Control and Management Interface (SCMI) Telemetry Protocol
+ *
+ * Copyright (C) 2026 ARM Ltd.
+ */
+
+#include <linux/atomic.h>
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/compiler_types.h>
+#include <linux/completion.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/refcount.h>
+#include <linux/slab.h>
+#include <linux/sprintf.h>
+#include <linux/string.h>
+#include <linux/xarray.h>
+
+#include "protocols.h"
+#include "notify.h"
+
+#include <trace/events/scmi.h>
+
+/* Updated only after ALL the mandatory features for that version are merged */
+#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x10000
+
+#define SCMI_TLM_TDCF_MAX_RETRIES 5
+
+enum scmi_telemetry_protocol_cmd {
+ TELEMETRY_LIST_SHMTI = 0x3,
+ TELEMETRY_DE_DESCRIPTION = 0x4,
+ TELEMETRY_LIST_UPDATE_INTERVALS = 0x5,
+ TELEMETRY_DE_CONFIGURE = 0x6,
+ TELEMETRY_DE_ENABLED_LIST = 0x7,
+ TELEMETRY_CONFIG_SET = 0x8,
+ TELEMETRY_READING_COMPLETE = TELEMETRY_CONFIG_SET,
+ TELEMETRY_CONFIG_GET = 0x9,
+ TELEMETRY_RESET = 0xA,
+};
+
+struct scmi_msg_resp_telemetry_protocol_attributes {
+ __le32 de_num;
+ __le32 groups_num;
+ __le32 de_implementation_rev_dword[SCMI_TLM_DE_IMPL_MAX_DWORDS];
+ __le32 attributes;
+#define SUPPORTS_SINGLE_READ(x) ((x) & BIT(31))
+#define SUPPORTS_CONTINUOS_UPDATE(x) ((x) & BIT(30))
+#define SUPPORTS_PER_GROUP_CONFIG(x) ((x) & BIT(18))
+#define SUPPORTS_RESET(x) ((x) & BIT(17))
+#define SUPPORTS_FC(x) ((x) & BIT(16))
+ __le32 default_blk_ts_rate;
+};
+
+struct scmi_telemetry_update_notify_payld {
+ __le32 agent_id;
+ __le32 status;
+ __le32 num_dwords;
+ __le32 array[] __counted_by(num_dwords);
+};
+
+struct scmi_shmti_desc {
+ __le32 id;
+ __le32 addr_low;
+ __le32 addr_high;
+ __le32 length;
+ __le32 flags;
+};
+
+struct scmi_msg_resp_telemetry_shmti_list {
+ __le32 num_shmti;
+ struct scmi_shmti_desc desc[] __counted_by(num_shmti);
+};
+
+struct de_desc_fc {
+ __le32 addr_low;
+ __le32 addr_high;
+ __le32 size;
+};
+
+struct scmi_de_desc {
+ __le32 id;
+ __le32 grp_id;
+ __le32 data_sz;
+ __le32 attr_1;
+#define IS_NAME_SUPPORTED(d) ((d)->attr_1 & BIT(31))
+#define IS_FC_SUPPORTED(d) ((d)->attr_1 & BIT(30))
+#define GET_DE_TYPE(d) (le32_get_bits((d)->attr_1, GENMASK(29, 22)))
+#define IS_PERSISTENT(d) ((d)->attr_1 & BIT(21))
+#define GET_DE_UNIT_EXP(d) \
+ ({ \
+ __u32 __signed_exp = \
+ le32_get_bits((d)->attr_1, GENMASK(20, 13)); \
+ \
+ sign_extend32(__signed_exp, 7); \
+ })
+#define GET_DE_UNIT(d) (le32_get_bits((d)->attr_1, GENMASK(12, 5)))
+#define TSTAMP_SUPPORT(d) (le32_get_bits((d)->attr_1, GENMASK(1, 0)))
+ __le32 attr_2;
+#define GET_DE_INSTA_ID(d) (le32_get_bits((d)->attr_2, GENMASK(31, 24)))
+#define GET_COMPO_INSTA_ID(d) (le32_get_bits((d)->attr_2, GENMASK(23, 8)))
+#define GET_COMPO_TYPE(d) (le32_get_bits((d)->attr_2, GENMASK(7, 0)))
+ __le32 reserved;
+};
+
+struct scmi_msg_resp_telemetry_de_description {
+ __le32 num_desc;
+ struct scmi_de_desc desc[] __counted_by(num_desc);
+};
+
+struct scmi_msg_telemetry_update_intervals {
+ __le32 index;
+ __le32 group_identifier;
+#define ALL_DES_NO_GROUP 0x0
+#define SPECIFIC_GROUP_DES 0x1
+#define ALL_DES_ANY_GROUP 0x2
+ __le32 flags;
+};
+
+struct scmi_msg_resp_telemetry_update_intervals {
+ __le32 flags;
+#define INTERVALS_DISCRETE(x) (!((x) & BIT(12)))
+ __le32 intervals[];
+};
+
+struct scmi_msg_telemetry_de_enabled_list {
+ __le32 index;
+ __le32 flags;
+};
+
+struct scmi_enabled_de_desc {
+ __le32 id;
+ __le32 mode;
+};
+
+struct scmi_msg_resp_telemetry_de_enabled_list {
+ __le32 flags;
+ struct scmi_enabled_de_desc entry[];
+};
+
+struct scmi_msg_telemetry_de_configure {
+ __le32 id;
+ __le32 flags;
+#define DE_ENABLE_NO_TSTAMP BIT(0)
+#define DE_ENABLE_WTH_TSTAMP BIT(1)
+#define DE_DISABLE_ALL BIT(2)
+#define GROUP_SELECTOR BIT(3)
+#define EVENT_DE 0
+#define EVENT_GROUP 1
+#define DE_DISABLE_ONE 0x0
+};
+
+struct scmi_msg_resp_telemetry_de_configure {
+ __le32 shmti_id;
+#define IS_SHMTI_ID_VALID(x) ((x) != 0xFFFFFFFF)
+ __le32 shmti_de_offset;
+ __le32 blk_ts_offset;
+};
+
+struct scmi_msg_telemetry_config_set {
+ __le32 grp_id;
+ __le32 control;
+#define TELEMETRY_ENABLE (BIT(0))
+
+#define TELEMETRY_MODE_SET(x) (FIELD_PREP(GENMASK(4, 1), (x)))
+#define TLM_ONDEMAND (0)
+#define TLM_NOTIFS (1)
+#define TLM_SINGLE (2)
+#define TELEMETRY_MODE_ONDEMAND TELEMETRY_MODE_SET(TLM_ONDEMAND)
+#define TELEMETRY_MODE_NOTIFS TELEMETRY_MODE_SET(TLM_NOTIFS)
+#define TELEMETRY_MODE_SINGLE TELEMETRY_MODE_SET(TLM_SINGLE)
+
+#define TLM_ORPHANS (0)
+#define TLM_GROUP (1)
+#define TLM_ALL (2)
+#define TELEMETRY_SET_SELECTOR(x) (FIELD_PREP(GENMASK(8, 5), (x)))
+#define TELEMETRY_SET_SELECTOR_ORPHANS TELEMETRY_SET_SELECTOR(TLM_ORPHANS)
+#define TELEMETRY_SET_SELECTOR_GROUP TELEMETRY_SET_SELECTOR(TLM_GROUP)
+#define TELEMETRY_SET_SELECTOR_ALL TELEMETRY_SET_SELECTOR(TLM_ALL)
+ __le32 sampling_rate;
+};
+
+struct scmi_msg_resp_telemetry_reading_complete {
+ __le32 num_dwords;
+ __le32 dwords[] __counted_by(num_dwords);
+};
+
+struct scmi_msg_telemetry_config_get {
+ __le32 grp_id;
+ __le32 flags;
+#define TELEMETRY_GET_SELECTOR(x) (FIELD_PREP(GENMASK(3, 0), (x)))
+#define TELEMETRY_GET_SELECTOR_ORPHANS TELEMETRY_GET_SELECTOR(TLM_ORPHANS)
+#define TELEMETRY_GET_SELECTOR_GROUP TELEMETRY_GET_SELECTOR(TLM_GROUP)
+#define TELEMETRY_GET_SELECTOR_ALL TELEMETRY_GET_SELECTOR(TLM_ALL)
+};
+
+struct scmi_msg_resp_telemetry_config_get {
+ __le32 control;
+#define TELEMETRY_MODE_GET (FIELD_GET(GENMASK(4, 1)))
+ __le32 sampling_rate;
+};
+
+/* TDCF */
+
+#define _I(__a) (ioread32((void __iomem *)(__a)))
+
+#define TO_CPU_64(h, l) ((((u64)(h)) << 32) | (l))
+
+/*
+ * Define the behaviour of a SHMTI scan defining what information will
+ * be gathered and which Telemetry items can be updated.
+ */
+enum scan_mode {
+ SCAN_LOOKUP, /* Update only value/tstamp */
+ SCAN_UPDATE, /* Update also location offset */
+ SCAN_DISCOVERY /* Update xa_des: allows for new DEs to be discovered */
+};
+
+struct fc_line {
+ u32 data_low;
+ u32 data_high;
+};
+
+struct fc_tsline {
+ u32 data_low;
+ u32 data_high;
+ u32 ts_low;
+ u32 ts_high;
+};
+
+struct line {
+ u32 data_low;
+ u32 data_high;
+};
+
+struct blk_tsline {
+ u32 ts_low;
+ u32 ts_high;
+};
+
+struct tsline {
+ u32 data_low;
+ u32 data_high;
+ u32 ts_low;
+ u32 ts_high;
+};
+
+struct uuid_line {
+ u32 dwords[SCMI_TLM_DE_IMPL_MAX_DWORDS];
+};
+
+enum tdcf_line_types {
+ TDCF_DATA_LINE,
+ TDCF_BLK_TS_LINE,
+ TDCF_UUID_LINE,
+};
+
+struct payload {
+ u32 meta;
+#define LINE_TYPE(x) (le32_get_bits(_I(&((x)->meta)), GENMASK(7, 4)))
+#define IS_DATA_LINE(x) (LINE_TYPE(x) == TDCF_DATA_LINE)
+#define IS_BLK_TS_LINE(x) (LINE_TYPE(x) == TDCF_BLK_TS_LINE)
+#define IS_UUID_LINE(x) (LINE_TYPE(x) == TDCF_UUID_LINE)
+#define USE_BLK_TS(x) (_I(&((x)->meta)) & BIT(3))
+#define HAS_LINE_EXT(x) (_I(&((x)->meta)) & BIT(2))
+#define LINE_TS_VALID(x) (_I(&((x)->meta)) & BIT(1))
+#define DATA_INVALID(x) (_I(&((x)->meta)) & BIT(0))
+#define BLK_TS_INVALID(p) \
+({ \
+ typeof(p) _p = (p); \
+ bool invalid; \
+ \
+ invalid = LINE_TS_VALID(_p) || HAS_LINE_EXT(_p) || \
+ USE_BLK_TS(_p) || DATA_INVALID(_p); \
+ invalid; \
+})
+
+#define UUID_INVALID(p) \
+({ \
+ typeof(p) _p = (p); \
+ bool invalid; \
+ \
+ invalid = LINE_TS_VALID(_p) || USE_BLK_TS(_p) || \
+ DATA_INVALID(_p) || !HAS_LINE_EXT(_p); \
+ invalid; \
+})
+ u32 id;
+ union {
+ struct line l;
+ struct tsline tsl;
+ struct blk_tsline blk_tsl;
+ struct uuid_line uuid_l;
+ };
+};
+
+#define PAYLD_ID(x) (_I(&(((struct payload *)(x))->id)))
+
+#define LINE_DATA_PAYLD_WORDS \
+ ((sizeof(u32) + sizeof(u32) + sizeof(struct line)) / sizeof(u32))
+#define EXT_LINE_DATA_PAYLD_WORDS \
+ ((sizeof(u32) + sizeof(u32) + sizeof(struct tsline)) / sizeof(u32))
+
+#define LINE_LENGTH_WORDS(x) \
+ (HAS_LINE_EXT((x)) ? EXT_LINE_DATA_PAYLD_WORDS : LINE_DATA_PAYLD_WORDS)
+
+#define LINE_LENGTH_QWORDS(x) ((LINE_LENGTH_WORDS(x)) / 2)
+
+struct prlg {
+ u32 sign_start;
+#define SIGNATURE_START 0x5442474E /* TBGN */
+ u32 match_start;
+ u32 num_qwords;
+ u32 hdr_meta_1;
+#define TDCF_REVISION_GET(x) (le32_get_bits((x)->hdr_meta_1, GENMASK(7, 0)))
+};
+
+struct eplg {
+ u32 match_end;
+ u32 sign_end;
+#define SIGNATURE_END 0x54454E44 /* TEND */
+};
+
+#define TDCF_EPLG_SZ (sizeof(struct eplg))
+
+struct tdcf {
+ struct prlg prlg;
+ unsigned char payld[];
+};
+
+#define QWORDS(_t) (_I(&(_t)->prlg.num_qwords))
+
+#define SHMTI_MIN_SIZE (sizeof(struct tdcf) + TDCF_EPLG_SZ)
+
+#define TDCF_START_SIGNATURE(x) (_I(&((x)->prlg.sign_start)))
+#define TDCF_START_SEQ_GET(x) (_I(&((x)->prlg.match_start)))
+#define IS_BAD_START_SEQ(s) ((s) & 0x1)
+
+#define TDCF_END_SEQ_GET(e) (_I(&((e)->match_end)))
+#define TDCF_END_SIGNATURE(e) (_I(&((e)->sign_end)))
+#define TDCF_BAD_END_SEQ GENMASK(31, 0)
+
+struct telemetry_shmti {
+ int id;
+ u32 flags;
+ void __iomem *base;
+ u32 len;
+ u32 last_magic;
+};
+
+#define SHMTI_EPLG(s) \
+ ({ \
+ struct telemetry_shmti *_s = (s); \
+ struct eplg *_eplg; \
+ \
+ _eplg = _s->base + _s->len - TDCF_EPLG_SZ; \
+ (_eplg); \
+ })
+
+struct telemetry_line {
+ refcount_t users;
+ u32 last_magic;
+ struct payload __iomem *payld;
+ /* Protect line accesses */
+ struct mutex mtx;
+};
+
+struct telemetry_block_ts {
+ u64 last_ts;
+ u32 last_rate;
+ struct telemetry_line line;
+};
+
+#define to_blkts(l) container_of(l, struct telemetry_block_ts, line)
+
+struct telemetry_uuid {
+ u32 de_impl_version[SCMI_TLM_DE_IMPL_MAX_DWORDS];
+ struct telemetry_line line;
+};
+
+#define to_uuid(l) container_of(l, struct telemetry_uuid, line)
+
+enum timestamps {
+ TSTAMP_NONE,
+ TSTAMP_LINE,
+ TSTAMP_BLK
+};
+
+struct telemetry_de {
+ enum timestamps ts_type;
+ u32 ts_rate;
+ bool enumerated;
+ bool cached;
+ void __iomem *base;
+ struct eplg __iomem *eplg;
+ u32 offset;
+ /* NOTE THAT DE data_sz is registered in scmi_telemetry_de */
+ u32 fc_size;
+ /* Protect last_val/ts/magic accesses */
+ struct mutex mtx;
+ u64 last_val;
+ u64 last_ts;
+ u32 last_magic;
+ struct list_head item;
+ struct telemetry_block_ts *bts;
+ struct telemetry_uuid *uuid;
+ struct scmi_telemetry_de de;
+};
+
+#define to_tde(d) container_of(d, struct telemetry_de, de)
+
+#define DE_ENABLED_WITH_TSTAMP 2
+
+struct telemetry_info {
+ bool streaming_mode;
+ unsigned int num_shmti;
+ unsigned int default_blk_ts_rate;
+ const struct scmi_protocol_handle *ph;
+ struct telemetry_shmti *shmti;
+ struct telemetry_de *tdes;
+ struct scmi_telemetry_group *grps;
+ struct xarray xa_des;
+ /* Mutex to protect access to @free_des */
+ struct mutex free_mtx;
+ struct list_head free_des;
+ struct list_head fcs_des;
+ struct scmi_telemetry_info info;
+ atomic_t rinfo_initializing;
+ struct completion rinfo_initdone;
+ struct scmi_telemetry_res_info __private *rinfo;
+ struct scmi_telemetry_res_info *(*res_get)(struct telemetry_info *ti);
+};
+
+static struct scmi_telemetry_res_info *
+__scmi_telemetry_resources_get(struct telemetry_info *ti);
+
+static struct telemetry_de *
+scmi_telemetry_free_tde_get(struct telemetry_info *ti)
+{
+ struct telemetry_de *tde;
+
+ guard(mutex)(&ti->free_mtx);
+
+ tde = list_first_entry_or_null(&ti->free_des, struct telemetry_de, item);
+ if (!tde)
+ return tde;
+
+ list_del(&tde->item);
+
+ return tde;
+}
+
+static void scmi_telemetry_free_tde_put(struct telemetry_info *ti,
+ struct telemetry_de *tde)
+{
+ guard(mutex)(&ti->free_mtx);
+
+ list_add_tail(&tde->item, &ti->free_des);
+}
+
+static struct telemetry_de *scmi_telemetry_tde_lookup(struct telemetry_info *ti,
+ unsigned int de_id)
+{
+ struct scmi_telemetry_de *de;
+
+ de = xa_load(&ti->xa_des, de_id);
+ if (!de)
+ return NULL;
+
+ return to_tde(de);
+}
+
+static struct telemetry_de *scmi_telemetry_tde_get(struct telemetry_info *ti,
+ unsigned int de_id)
+{
+ static struct telemetry_de *tde;
+
+ /* Pick a new tde */
+ tde = scmi_telemetry_free_tde_get(ti);
+ if (!tde) {
+ dev_err(ti->ph->dev, "Cannot allocate DE for ID:0x%08X\n", de_id);
+ return ERR_PTR(-ENOSPC);
+ }
+
+ return tde;
+}
+
+static int scmi_telemetry_tde_register(struct telemetry_info *ti,
+ struct telemetry_de *tde)
+{
+ struct scmi_telemetry_res_info *rinfo = ACCESS_PRIVATE(ti, rinfo);
+ int ret;
+
+ if (rinfo->num_des >= ti->info.base.num_des) {
+ ret = -ENOSPC;
+ goto err;
+ }
+
+ /* Store DE pointer by de_id ... */
+ ret = xa_insert(&ti->xa_des, tde->de.info->id, &tde->de, GFP_KERNEL);
+ if (ret)
+ goto err;
+
+ /* ... and in the general array */
+ rinfo->des[rinfo->num_des++] = &tde->de;
+
+ return 0;
+
+err:
+ dev_err(ti->ph->dev, "Cannot register DE for ID:0x%08X\n",
+ tde->de.info->id);
+
+ return ret;
+}
+
+struct scmi_tlm_de_priv {
+ struct telemetry_info *ti;
+ void *next;
+};
+
+static int
+scmi_telemetry_protocol_attributes_get(struct telemetry_info *ti)
+{
+ struct scmi_msg_resp_telemetry_protocol_attributes *resp;
+ const struct scmi_protocol_handle *ph = ti->ph;
+ struct scmi_xfer *t;
+ int ret;
+
+ ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0,
+ sizeof(*resp), &t);
+ if (ret)
+ return ret;
+
+ resp = t->rx.buf;
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret) {
+ __le32 attr = resp->attributes;
+
+ ti->info.base.num_des = le32_to_cpu(resp->de_num);
+ ti->info.base.num_groups = le32_to_cpu(resp->groups_num);
+ for (int i = 0; i < SCMI_TLM_DE_IMPL_MAX_DWORDS; i++)
+ ti->info.base.de_impl_version[i] =
+ le32_to_cpu(resp->de_implementation_rev_dword[i]);
+ ti->info.single_read_support = SUPPORTS_SINGLE_READ(attr);
+ ti->info.continuos_update_support = SUPPORTS_CONTINUOS_UPDATE(attr);
+ ti->info.per_group_config_support = SUPPORTS_PER_GROUP_CONFIG(attr);
+ ti->info.reset_support = SUPPORTS_RESET(attr);
+ ti->info.fc_support = SUPPORTS_FC(attr);
+ ti->num_shmti = le32_get_bits(attr, GENMASK(15, 0));
+ ti->default_blk_ts_rate = le32_to_cpu(resp->default_blk_ts_rate);
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static void iter_tlm_prepare_message(void *message,
+ unsigned int desc_index, const void *priv)
+{
+ put_unaligned_le32(desc_index, message);
+}
+
+static int iter_de_descr_update_state(struct scmi_iterator_state *st,
+ const void *response, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_de_description *r = response;
+ struct scmi_tlm_de_priv *p = priv;
+
+ st->num_returned = le32_get_bits(r->num_desc, GENMASK(15, 0));
+ st->num_remaining = le32_get_bits(r->num_desc, GENMASK(31, 16));
+
+ if (st->rx_len < (sizeof(*r) + sizeof(r->desc[0]) * st->num_returned))
+ return -EINVAL;
+
+ /* Initialized to first descriptor */
+ p->next = (void *)r->desc;
+
+ return 0;
+}
+
+static int scmi_telemetry_de_descriptor_parse(struct telemetry_info *ti,
+ struct telemetry_de *tde,
+ void **next)
+{
+ struct scmi_telemetry_res_info *rinfo = ACCESS_PRIVATE(ti, rinfo);
+ const struct scmi_de_desc *desc = *next;
+ unsigned int grp_id;
+
+ tde->de.info->id = le32_to_cpu(desc->id);
+ grp_id = le32_to_cpu(desc->grp_id);
+ if (grp_id != SCMI_TLM_GRP_INVALID) {
+ /* Group descriptors are empty but allocated at this point */
+ if (grp_id >= ti->info.base.num_groups)
+ return -EINVAL;
+
+ /* Link to parent group */
+ tde->de.info->grp_id = grp_id;
+ tde->de.grp = &rinfo->grps[grp_id];
+ }
+
+ tde->de.info->data_sz = le32_to_cpu(desc->data_sz);
+ tde->de.info->type = GET_DE_TYPE(desc);
+ tde->de.info->unit = GET_DE_UNIT(desc);
+ tde->de.info->unit_exp = GET_DE_UNIT_EXP(desc);
+ tde->de.info->instance_id = GET_DE_INSTA_ID(desc);
+ tde->de.info->compo_instance_id = GET_COMPO_INSTA_ID(desc);
+ tde->de.info->compo_type = GET_COMPO_TYPE(desc);
+ tde->de.info->persistent = IS_PERSISTENT(desc);
+ tde->ts_type = TSTAMP_SUPPORT(desc);
+ tde->de.tstamp_support = !!tde->ts_type;
+ tde->de.fc_support = IS_FC_SUPPORTED(desc);
+ tde->de.name_support = IS_NAME_SUPPORTED(desc);
+ /* Update DE_DESCRIPTOR size for the next iteration */
+ *next += sizeof(*desc);
+
+ if (tde->ts_type == TSTAMP_LINE) {
+ u32 *line_ts_rate = *next;
+
+ tde->de.info->ts_rate = *line_ts_rate;
+
+ /* Variably sized depending on TS support */
+ *next += sizeof(*line_ts_rate);
+ } else if (tde->ts_type == TSTAMP_BLK) {
+ /* Setup default BLK TS value at first */
+ tde->de.info->ts_rate = ti->default_blk_ts_rate;
+ }
+
+ if (tde->de.fc_support) {
+ u32 size;
+ u64 phys_addr;
+ void __iomem *addr;
+ struct de_desc_fc *dfc;
+
+ dfc = *next;
+ phys_addr = le32_to_cpu(dfc->addr_low);
+ phys_addr |= (u64)le32_to_cpu(dfc->addr_high) << 32;
+
+ size = le32_to_cpu(dfc->size);
+ addr = devm_ioremap(ti->ph->dev, phys_addr, size);
+ if (!addr)
+ return -EADDRNOTAVAIL;
+
+ tde->base = addr;
+ tde->offset = 0;
+ tde->fc_size = size;
+
+ /* Add to FastChannels list */
+ list_add(&tde->item, &ti->fcs_des);
+
+ /* Variably sized depending on FC support */
+ *next += sizeof(*dfc);
+ }
+
+ if (tde->de.name_support) {
+ const char *de_name = *next;
+
+ strscpy(tde->de.info->name, de_name, SCMI_SHORT_NAME_MAX_SIZE);
+ /* Variably sized depending on name support */
+ *next += SCMI_SHORT_NAME_MAX_SIZE;
+ }
+
+ return 0;
+}
+
+static int iter_de_descr_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st,
+ void *priv)
+{
+ struct scmi_tlm_de_priv *p = priv;
+ struct telemetry_info *ti = p->ti;
+ const struct scmi_de_desc *desc = p->next;
+ struct telemetry_de *tde;
+ bool discovered = false;
+ unsigned int de_id;
+ int ret;
+
+ de_id = le32_to_cpu(desc->id);
+ /* Check if this DE has already been discovered by other means... */
+ tde = scmi_telemetry_tde_lookup(ti, de_id);
+ if (!tde) {
+ /* Create a new one */
+ tde = scmi_telemetry_tde_get(ti, de_id);
+ if (IS_ERR(tde))
+ return PTR_ERR(tde);
+
+ discovered = true;
+ } else if (tde->enumerated) {
+ /* Cannot be a duplicate of a DE already created by enumeration */
+ dev_err(ph->dev,
+ "Discovered INVALID DE with DUPLICATED ID:0x%08X\n",
+ de_id);
+ return -EINVAL;
+ }
+
+ ret = scmi_telemetry_de_descriptor_parse(ti, tde, &p->next);
+ if (ret)
+ goto err;
+
+ if (discovered) {
+ /* Register if it was not already ... */
+ ret = scmi_telemetry_tde_register(ti, tde);
+ if (ret)
+ goto err;
+
+ tde->enumerated = true;
+ }
+
+ /* Account for this DE in group num_de counter */
+ if (tde->de.grp)
+ tde->de.grp->info->num_des++;
+
+ return 0;
+
+err:
+ /* DE not enumerated at this point were created in this call */
+ if (!tde->enumerated)
+ scmi_telemetry_free_tde_put(ti, tde);
+
+ return ret;
+}
+
+static int
+scmi_telemetry_de_groups_init(struct device *dev, struct telemetry_info *ti)
+{
+ struct scmi_telemetry_res_info *rinfo = ACCESS_PRIVATE(ti, rinfo);
+
+ /* Allocate all groups DEs IDs arrays at first ... */
+ for (int i = 0; i < ti->info.base.num_groups; i++) {
+ struct scmi_telemetry_group *grp = &rinfo->grps[i];
+ size_t des_str_sz;
+
+ unsigned int *des __free(kfree) = kcalloc(grp->info->num_des,
+ sizeof(unsigned int),
+ GFP_KERNEL);
+ if (!des)
+ return -ENOMEM;
+
+ /*
+ * Max size 32bit ID string in Hex: 0xCAFECAFE
+ * - 10 digits + ' '/'\n' = 11 bytes per number
+ * - terminating NUL character
+ */
+ des_str_sz = grp->info->num_des * 11 + 1;
+ char *des_str __free(kfree) = kzalloc(des_str_sz, GFP_KERNEL);
+ if (!des_str)
+ return -ENOMEM;
+
+ grp->des = no_free_ptr(des);
+ grp->des_str = no_free_ptr(des_str);
+ /* Reset group DE counter */
+ grp->info->num_des = 0;
+ }
+
+ /* Scan DEs and populate DE IDs arrays for all groups */
+ for (int i = 0; i < rinfo->num_des; i++) {
+ struct scmi_telemetry_group *grp = rinfo->des[i]->grp;
+
+ if (!grp)
+ continue;
+
+ /*
+ * Note that, at this point, num_des is guaranteed to be
+ * sane (in-bounds) by construction.
+ */
+ grp->des[grp->info->num_des++] = i;
+ }
+
+ /* Build composing DES string */
+ for (int i = 0; i < ti->info.base.num_groups; i++) {
+ struct scmi_telemetry_group *grp = &rinfo->grps[i];
+ size_t bufsize = grp->info->num_des * 11 + 1;
+ char *buf = grp->des_str;
+
+ for (int j = 0; j < grp->info->num_des; j++) {
+ char term = j != (grp->info->num_des - 1) ? ' ' : '\0';
+ int len;
+
+ len = scnprintf(buf, bufsize, "0x%08X%c",
+ rinfo->des[grp->des[j]]->info->id, term);
+
+ buf += len;
+ bufsize -= len;
+ }
+ }
+
+ rinfo->num_groups = ti->info.base.num_groups;
+
+ return 0;
+}
+
+static int scmi_telemetry_de_descriptors_get(struct telemetry_info *ti)
+{
+ const struct scmi_protocol_handle *ph = ti->ph;
+
+ struct scmi_iterator_ops ops = {
+ .prepare_message = iter_tlm_prepare_message,
+ .update_state = iter_de_descr_update_state,
+ .process_response = iter_de_descr_process_response,
+ };
+ struct scmi_tlm_de_priv tpriv = {
+ .ti = ti,
+ .next = NULL,
+ };
+ void *iter;
+ int ret;
+
+ if (!ti->info.base.num_des)
+ return 0;
+
+ iter = ph->hops->iter_response_init(ph, &ops, ti->info.base.num_des,
+ TELEMETRY_DE_DESCRIPTION,
+ sizeof(u32), &tpriv);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+
+ ret = ph->hops->iter_response_run(iter);
+ if (ret)
+ return ret;
+
+ return scmi_telemetry_de_groups_init(ph->dev, ti);
+}
+
+struct scmi_tlm_ivl_priv {
+ struct device *dev;
+ struct scmi_tlm_intervals **intrvs;
+ unsigned int grp_id;
+ unsigned int flags;
+};
+
+static void iter_intervals_prepare_message(void *message,
+ unsigned int desc_index,
+ const void *priv)
+{
+ struct scmi_msg_telemetry_update_intervals *msg = message;
+ const struct scmi_tlm_ivl_priv *p = priv;
+
+ msg->index = cpu_to_le32(desc_index);
+ msg->group_identifier = cpu_to_le32(p->grp_id);
+ msg->flags = FIELD_PREP(GENMASK(3, 0), p->flags);
+}
+
+static int iter_intervals_update_state(struct scmi_iterator_state *st,
+ const void *response, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_update_intervals *r = response;
+
+ st->num_returned = le32_get_bits(r->flags, GENMASK(11, 0));
+ st->num_remaining = le32_get_bits(r->flags, GENMASK(31, 16));
+
+ if (st->rx_len < (sizeof(*r) + sizeof(r->intervals[0]) * st->num_returned))
+ return -EINVAL;
+
+ /*
+ * total intervals is not declared previously anywhere so we
+ * assume it's returned+remaining on first call.
+ */
+ if (!st->max_resources) {
+ struct scmi_tlm_ivl_priv *p = priv;
+ struct scmi_tlm_intervals *intrvs;
+ bool discrete;
+ int inum;
+
+ discrete = INTERVALS_DISCRETE(r->flags);
+ /* Check consistency on first call */
+ if (!discrete && (st->num_returned != 3 || st->num_remaining != 0))
+ return -EINVAL;
+
+ inum = st->num_returned + st->num_remaining;
+ intrvs = kzalloc(sizeof(*intrvs) + inum * sizeof(__u32), GFP_KERNEL);
+ if (!intrvs)
+ return -ENOMEM;
+
+ intrvs->num_intervals = inum;
+ intrvs->discrete = discrete;
+ st->max_resources = intrvs->num_intervals;
+
+ *p->intrvs = intrvs;
+ }
+
+ return 0;
+}
+
+static int
+iter_intervals_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_update_intervals *r = response;
+ struct scmi_tlm_ivl_priv *p = priv;
+ struct scmi_tlm_intervals *intrvs = *p->intrvs;
+ unsigned int idx = st->loop_idx;
+
+ intrvs->update_intervals[st->desc_index + idx] = r->intervals[idx];
+
+ return 0;
+}
+
+static int
+scmi_tlm_enumerate_update_intervals(struct telemetry_info *ti,
+ struct scmi_tlm_intervals **intervals,
+ int grp_id, unsigned int flags)
+{
+ struct scmi_iterator_ops ops = {
+ .prepare_message = iter_intervals_prepare_message,
+ .update_state = iter_intervals_update_state,
+ .process_response = iter_intervals_process_response,
+ };
+ const struct scmi_protocol_handle *ph = ti->ph;
+ struct scmi_tlm_ivl_priv ipriv = {
+ .dev = ph->dev,
+ .grp_id = grp_id,
+ .intrvs = intervals,
+ .flags = flags,
+ };
+ void *iter;
+
+ iter = ph->hops->iter_response_init(ph, &ops, 0,
+ TELEMETRY_LIST_UPDATE_INTERVALS,
+ sizeof(struct scmi_msg_telemetry_update_intervals),
+ &ipriv);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+
+ return ph->hops->iter_response_run(iter);
+}
+
+static int
+scmi_telemetry_enumerate_groups_intervals(struct telemetry_info *ti)
+{
+ struct scmi_telemetry_res_info *rinfo = ACCESS_PRIVATE(ti, rinfo);
+
+ if (!ti->info.per_group_config_support)
+ return 0;
+
+ for (int id = 0; id < rinfo->num_groups; id++) {
+ int ret;
+
+ ret = scmi_tlm_enumerate_update_intervals(ti,
+ &rinfo->grps[id].intervals,
+ id, SPECIFIC_GROUP_DES);
+ if (ret)
+ return ret;
+
+ rinfo->grps_store[id].num_intervals =
+ rinfo->grps[id].intervals->num_intervals;
+ }
+
+ return 0;
+}
+
+static void scmi_telemetry_intervals_free(void *interval)
+{
+ kfree(interval);
+}
+
+static int
+scmi_telemetry_enumerate_common_intervals(struct telemetry_info *ti)
+{
+ unsigned int flags;
+ int ret;
+
+ flags = !ti->info.per_group_config_support ?
+ ALL_DES_ANY_GROUP : ALL_DES_NO_GROUP;
+
+ ret = scmi_tlm_enumerate_update_intervals(ti, &ti->info.intervals,
+ SCMI_TLM_GRP_INVALID, flags);
+ if (ret)
+ return ret;
+
+ /* A copy for UAPI access... */
+ ti->info.base.num_intervals = ti->info.intervals->num_intervals;
+
+ /* Delegate freeing of allocated intervals to unbind time */
+ return devm_add_action_or_reset(ti->ph->dev,
+ scmi_telemetry_intervals_free,
+ ti->info.intervals);
+}
+
+static int iter_shmti_update_state(struct scmi_iterator_state *st,
+ const void *response, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_shmti_list *r = response;
+
+ st->num_returned = le32_get_bits(r->num_shmti, GENMASK(15, 0));
+ st->num_remaining = le32_get_bits(r->num_shmti, GENMASK(31, 16));
+
+ if (st->rx_len < (sizeof(*r) + sizeof(r->desc[0]) * st->num_returned))
+ return -EINVAL;
+
+ return 0;
+}
+
+static inline int
+scmi_telemetry_shmti_validate(struct device *dev, struct telemetry_shmti *shmti)
+{
+ struct tdcf __iomem *tdcf = shmti->base;
+ u32 sign_start, sign_end;
+
+ sign_start = TDCF_START_SIGNATURE(tdcf);
+ sign_end = TDCF_END_SIGNATURE(SHMTI_EPLG(shmti));
+
+ if (sign_start != SIGNATURE_START || sign_end != SIGNATURE_END) {
+ dev_err(dev,
+ "BAD signature for SHMTI ID:%u @phys:%pK - START:0x%04X END:0x%04X\n",
+ shmti->id, shmti->base, sign_start, sign_end);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int iter_shmti_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st,
+ void *priv)
+{
+ const struct scmi_msg_resp_telemetry_shmti_list *r = response;
+ struct telemetry_info *ti = priv;
+ struct telemetry_shmti *shmti;
+ const struct scmi_shmti_desc *desc;
+ void __iomem *addr;
+ u64 phys_addr;
+ u32 len;
+
+ desc = &r->desc[st->loop_idx];
+ shmti = &ti->shmti[st->desc_index + st->loop_idx];
+
+ shmti->id = le32_to_cpu(desc->id);
+ shmti->flags = le32_to_cpu(desc->flags);
+ phys_addr = le32_to_cpu(desc->addr_low);
+ phys_addr |= (u64)le32_to_cpu(desc->addr_high) << 32;
+
+ len = le32_to_cpu(desc->length);
+ if (len < SHMTI_MIN_SIZE) {
+ dev_err(ph->dev, "Invalid length for SHMTI ID:%u len:%u\n",
+ shmti->id, len);
+ return -EINVAL;
+ }
+
+ addr = devm_ioremap(ph->dev, phys_addr, len);
+ if (!addr)
+ return -EADDRNOTAVAIL;
+
+ shmti->base = addr;
+ shmti->len = len;
+
+ return scmi_telemetry_shmti_validate(ph->dev, shmti);
+}
+
+static int scmi_telemetry_shmti_list(const struct scmi_protocol_handle *ph,
+ struct telemetry_info *ti)
+{
+ struct scmi_iterator_ops ops = {
+ .prepare_message = iter_tlm_prepare_message,
+ .update_state = iter_shmti_update_state,
+ .process_response = iter_shmti_process_response,
+ };
+ void *iter;
+
+ iter = ph->hops->iter_response_init(ph, &ops, ti->info.base.num_des,
+ TELEMETRY_LIST_SHMTI,
+ sizeof(u32), ti);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+
+ return ph->hops->iter_response_run(iter);
+}
+
+static int scmi_telemetry_enumerate_shmti(struct telemetry_info *ti)
+{
+ const struct scmi_protocol_handle *ph = ti->ph;
+ int ret;
+
+ if (!ti->num_shmti)
+ return 0;
+
+ ti->shmti = devm_kcalloc(ph->dev, ti->num_shmti, sizeof(*ti->shmti),
+ GFP_KERNEL);
+ if (!ti->shmti)
+ return -ENOMEM;
+
+ ret = scmi_telemetry_shmti_list(ph, ti);
+ if (ret) {
+ dev_err(ph->dev, "Cannot get SHMTI list descriptors");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct scmi_telemetry_info *
+scmi_telemetry_info_get(const struct scmi_protocol_handle *ph)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ return &ti->info;
+}
+
+static const struct scmi_telemetry_de *
+scmi_telemetry_de_lookup(const struct scmi_protocol_handle *ph, u32 id)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ ti->res_get(ti);
+ return xa_load(&ti->xa_des, id);
+}
+
+static const struct scmi_telemetry_res_info *
+scmi_telemetry_resources_get(const struct scmi_protocol_handle *ph)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ return ti->res_get(ti);
+}
+
+static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
+ .info_get = scmi_telemetry_info_get,
+ .de_lookup = scmi_telemetry_de_lookup,
+ .res_get = scmi_telemetry_resources_get,
+};
+
+/**
+ * scmi_telemetry_resources_alloc - Resources allocation
+ * @ti: A reference to the telemetry info descriptor for this instance
+ *
+ * This allocates and initializes dedicated resources for the maximum possible
+ * number of needed telemetry resources, based on information gathered from
+ * the initial enumeration: these allocations represent an upper bound on
+ * the number of discoverable telemetry resources and they will be later
+ * populated during late deferred further discovery phases.
+ *
+ * Return: 0 on Success, errno otherwise
+ */
+static int scmi_telemetry_resources_alloc(struct telemetry_info *ti)
+{
+ /* Array to hold pointers to discovered DEs */
+ struct scmi_telemetry_de **des __free(kfree) =
+ kcalloc(ti->info.base.num_des, sizeof(*des), GFP_KERNEL);
+ if (!des)
+ return -ENOMEM;
+
+ /* The allocated DE descriptors */
+ struct telemetry_de *tdes __free(kfree) =
+ kcalloc(ti->info.base.num_des, sizeof(*tdes), GFP_KERNEL);
+ if (!tdes)
+ return -ENOMEM;
+
+ /* Allocate a set of contiguous DE info descriptors. */
+ struct scmi_tlm_de_info *dei_store __free(kfree) =
+ kcalloc(ti->info.base.num_des, sizeof(*dei_store), GFP_KERNEL);
+ if (!dei_store)
+ return -ENOMEM;
+
+ /* Array to hold descriptors of discovered GROUPs */
+ struct scmi_telemetry_group *grps __free(kfree) =
+ kcalloc(ti->info.base.num_groups, sizeof(*grps), GFP_KERNEL);
+ if (!grps)
+ return -ENOMEM;
+
+ /* Allocate a set of contiguous Group info descriptors. */
+ struct scmi_tlm_grp_info *grps_store __free(kfree) =
+ kcalloc(ti->info.base.num_groups, sizeof(*grps_store), GFP_KERNEL);
+ if (!grps_store)
+ return -ENOMEM;
+
+ struct scmi_telemetry_res_info *rinfo __free(kfree) =
+ kzalloc(sizeof(*rinfo), GFP_KERNEL);
+ if (!rinfo)
+ return -ENOMEM;
+
+ mutex_init(&ti->free_mtx);
+ INIT_LIST_HEAD(&ti->free_des);
+ for (int i = 0; i < ti->info.base.num_des; i++) {
+ mutex_init(&tdes[i].mtx);
+ /* Bind contiguous DE info structures */
+ tdes[i].de.info = &dei_store[i];
+ list_add_tail(&tdes[i].item, &ti->free_des);
+ }
+
+ for (int i = 0; i < ti->info.base.num_groups; i++) {
+ grps_store[i].id = i;
+ /* Bind contiguous Group info struct */
+ grps[i].info = &grps_store[i];
+ }
+
+ INIT_LIST_HEAD(&ti->fcs_des);
+
+ ti->tdes = no_free_ptr(tdes);
+
+ rinfo->des = no_free_ptr(des);
+ rinfo->dei_store = no_free_ptr(dei_store);
+ rinfo->grps = no_free_ptr(grps);
+ rinfo->grps_store = no_free_ptr(grps_store);
+
+ ACCESS_PRIVATE(ti, rinfo) = no_free_ptr(rinfo);
+
+ return 0;
+}
+
+static void scmi_telemetry_resources_free(void *arg)
+{
+ struct telemetry_info *ti = arg;
+ struct scmi_telemetry_res_info *rinfo = ACCESS_PRIVATE(ti, rinfo);
+
+ kfree(ti->tdes);
+ kfree(rinfo->des);
+ kfree(rinfo->dei_store);
+ kfree(rinfo->grps);
+ kfree(rinfo->grps_store);
+
+ kfree(rinfo);
+
+ ACCESS_PRIVATE(ti, rinfo) = NULL;
+}
+
+static struct scmi_telemetry_res_info *
+__scmi_telemetry_resources_get(struct telemetry_info *ti)
+{
+ return ACCESS_PRIVATE(ti, rinfo);
+}
+
+/**
+ * scmi_telemetry_resources_enumerate - Enumeration helper
+ * @ti: A reference to the telemetry info descriptor for this instance
+ *
+ * This helper is configured to be called once on the first enumeration
+ * attempt, when triggered by invoking ti->res_get() from somewhere else.
+ * Once run it substitues itself in ti->res_get() with the simple accessor
+ * __scmi_telemetry_resources_get, which returns a descriptor to the resources
+ * that were possibly discovered.
+ *
+ * Note that, while it attempts to fully enumerate Data Events and Groups, it
+ * does NOT fail when such enumerations fail, instead it simply gives up with
+ * the end result that only a partially populated, but consistent, resources
+ * descriptor will be returned; in such a case the incomplete descriptor will
+ * be marked as NOT fully_enumerated: this design enables the kernel to deal
+ * with badly implemented out-of-spec firmware support while keep on providing
+ * a minimal sane, albeit possibly incomplete, set of telemetry respources.
+ *
+ * Return: A reference to a fully or partially populated resources descriptor
+ */
+static struct scmi_telemetry_res_info *
+scmi_telemetry_resources_enumerate(struct telemetry_info *ti)
+{
+ struct scmi_telemetry_res_info *rinfo = ACCESS_PRIVATE(ti, rinfo);
+ struct device *dev = ti->ph->dev;
+ int ret;
+
+ /*
+ * Ensure this init function can be called only once and
+ * handles properly concurrent calls.
+ */
+ if (atomic_cmpxchg(&ti->rinfo_initializing, 0, 1)) {
+ if (!completion_done(&ti->rinfo_initdone))
+ wait_for_completion(&ti->rinfo_initdone);
+ goto out;
+ }
+
+ ret = scmi_telemetry_de_descriptors_get(ti);
+ if (ret) {
+ dev_err(dev, FW_BUG "Cannot enumerate DEs resources. Carry-on.\n");
+ goto done;
+ }
+
+ ret = scmi_telemetry_enumerate_groups_intervals(ti);
+ if (ret) {
+ dev_err(dev, FW_BUG "Cannot enumerate group intervals. Carry-on.\n");
+ goto done;
+ }
+
+ /* If we got here, the enumeration was fully successful */
+ rinfo->fully_enumerated = true;
+done:
+ /* Disable initialization permanently */
+ smp_store_mb(ti->res_get, __scmi_telemetry_resources_get);
+ complete_all(&ti->rinfo_initdone);
+
+out:
+ return rinfo;
+}
+
+/**
+ * scmi_telemetry_instance_init - Instance initializer
+ * @ti: A reference to the telemetry info descriptor for this instance
+ *
+ * Note that this allocates and initialize all the resources possibly needed
+ * and then setups the @scmi_telemetry_resources_enumerate helper as the
+ * default method for the first call to ti->res_get(): this mechanism enables
+ * the possibility of optionally implementing deferred enumeration policies
+ * which optionally delay the discovery phase and related SCMI message exchanges
+ * to a later point in time.
+ *
+ * Return: 0 on Success, errno otherwise
+ */
+static int scmi_telemetry_instance_init(struct telemetry_info *ti)
+{
+ int ret;
+
+ /* Allocate and Initialize on first call... */
+ ret = scmi_telemetry_resources_alloc(ti);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(ti->ph->dev,
+ scmi_telemetry_resources_free, ti);
+ if (ret)
+ return ret;
+
+ xa_init(&ti->xa_des);
+ /* Setup resources lazy initialization */
+ atomic_set(&ti->rinfo_initializing, 0);
+ init_completion(&ti->rinfo_initdone);
+ /* Ensure the new res_get() operation is visible after this point */
+ smp_store_mb(ti->res_get, scmi_telemetry_resources_enumerate);
+
+ return 0;
+}
+
+static int scmi_telemetry_protocol_init(const struct scmi_protocol_handle *ph)
+{
+ struct device *dev = ph->dev;
+ struct telemetry_info *ti;
+ int ret;
+
+ dev_dbg(dev, "Telemetry Version %d.%d\n",
+ PROTOCOL_REV_MAJOR(ph->version), PROTOCOL_REV_MINOR(ph->version));
+
+ ti = devm_kzalloc(dev, sizeof(*ti), GFP_KERNEL);
+ if (!ti)
+ return -ENOMEM;
+
+ ti->ph = ph;
+
+ ret = scmi_telemetry_protocol_attributes_get(ti);
+ if (ret) {
+ dev_err(dev, FW_BUG "Cannot retrieve protocol attributes. Abort.\n");
+ return ret;
+ }
+
+ ret = scmi_telemetry_instance_init(ti);
+ if (ret) {
+ dev_err(dev, "Cannot initialize instance. Abort.\n");
+ return ret;
+ }
+
+ ret = scmi_telemetry_enumerate_common_intervals(ti);
+ if (ret)
+ dev_warn(dev, FW_BUG "Cannot enumerate update intervals. Carry-on.\n");
+
+ ret = scmi_telemetry_enumerate_shmti(ti);
+ if (ret) {
+ dev_err(dev, FW_BUG "Cannot enumerate SHMTIs. Abort.\n");
+ return ret;
+ }
+
+ ti->info.base.version = ph->version;
+
+ return ph->set_priv(ph, ti);
+}
+
+static const struct scmi_protocol scmi_telemetry = {
+ .id = SCMI_PROTOCOL_TELEMETRY,
+ .owner = THIS_MODULE,
+ .instance_init = &scmi_telemetry_protocol_init,
+ .ops = &tlm_proto_ops,
+ .supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
+};
+
+DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(telemetry, scmi_telemetry)
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index aafaac1496b0..fcb45bd4b44c 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -2,17 +2,21 @@
/*
* SCMI Message Protocol driver header
*
- * Copyright (C) 2018-2021 ARM Ltd.
+ * Copyright (C) 2018-2026 ARM Ltd.
*/
#ifndef _LINUX_SCMI_PROTOCOL_H
#define _LINUX_SCMI_PROTOCOL_H
#include <linux/bitfield.h>
+#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/notifier.h>
#include <linux/types.h>
+#include <uapi/linux/limits.h>
+#include <uapi/linux/scmi.h>
+
#define SCMI_MAX_STR_SIZE 64
#define SCMI_SHORT_NAME_MAX_SIZE 16
#define SCMI_MAX_NUM_RATES 16
@@ -820,6 +824,134 @@ struct scmi_pinctrl_proto_ops {
int (*pin_free)(const struct scmi_protocol_handle *ph, u32 pin);
};
+enum scmi_telemetry_de_type {
+ SCMI_TLM_DE_TYPE_USPECIFIED,
+ SCMI_TLM_DE_TYPE_ACCUMUL_IDLE_RESIDENCY,
+ SCMI_TLM_DE_TYPE_ACCUMUL_IDLE_COUNTS,
+ SCMI_TLM_DE_TYPE_ACCUMUL_OTHERS,
+ SCMI_TLM_DE_TYPE_INSTA_IDLE_STATE,
+ SCMI_TLM_DE_TYPE_INSTA_OTHERS,
+ SCMI_TLM_DE_TYPE_AVERAGE,
+ SCMI_TLM_DE_TYPE_STATUS,
+ SCMI_TLM_DE_TYPE_RESERVED_START,
+ SCMI_TLM_DE_TYPE_RESERVED_END = 0xef,
+ SCMI_TLM_DE_TYPE_OEM_START = 0xf0,
+ SCMI_TLM_DE_TYPE_OEM_END = 0xff,
+};
+
+enum scmi_telemetry_compo_type {
+ SCMI_TLM_COMPO_TYPE_USPECIFIED,
+ SCMI_TLM_COMPO_TYPE_CPU,
+ SCMI_TLM_COMPO_TYPE_CLUSTER,
+ SCMI_TLM_COMPO_TYPE_GPU,
+ SCMI_TLM_COMPO_TYPE_NPU,
+ SCMI_TLM_COMPO_TYPE_INTERCONNECT,
+ SCMI_TLM_COMPO_TYPE_MEM_CNTRL,
+ SCMI_TLM_COMPO_TYPE_L1_CACHE,
+ SCMI_TLM_COMPO_TYPE_L2_CACHE,
+ SCMI_TLM_COMPO_TYPE_L3_CACHE,
+ SCMI_TLM_COMPO_TYPE_LL_CACHE,
+ SCMI_TLM_COMPO_TYPE_SYS_CACHE,
+ SCMI_TLM_COMPO_TYPE_DISP_CNTRL,
+ SCMI_TLM_COMPO_TYPE_IPU,
+ SCMI_TLM_COMPO_TYPE_CHIPLET,
+ SCMI_TLM_COMPO_TYPE_PACKAGE,
+ SCMI_TLM_COMPO_TYPE_SOC,
+ SCMI_TLM_COMPO_TYPE_SYSTEM,
+ SCMI_TLM_COMPO_TYPE_SMCU,
+ SCMI_TLM_COMPO_TYPE_ACCEL,
+ SCMI_TLM_COMPO_TYPE_BATTERY,
+ SCMI_TLM_COMPO_TYPE_CHARGER,
+ SCMI_TLM_COMPO_TYPE_PMIC,
+ SCMI_TLM_COMPO_TYPE_BOARD,
+ SCMI_TLM_COMPO_TYPE_MEMORY,
+ SCMI_TLM_COMPO_TYPE_PERIPH,
+ SCMI_TLM_COMPO_TYPE_PERIPH_SUBC,
+ SCMI_TLM_COMPO_TYPE_LID,
+ SCMI_TLM_COMPO_TYPE_DISPLAY,
+ SCMI_TLM_COMPO_TYPE_RESERVED_START = 0x1d,
+ SCMI_TLM_COMPO_TYPE_RESERVED_END = 0xdf,
+ SCMI_TLM_COMPO_TYPE_OEM_START = 0xe0,
+ SCMI_TLM_COMPO_TYPE_OEM_END = 0xff,
+};
+
+#define SCMI_TLM_GET_UPDATE_INTERVAL_SECS(x) \
+ (le32_get_bits((x), GENMASK(20, 5)))
+#define SCMI_TLM_GET_UPDATE_INTERVAL_EXP(x) (sign_extend32((x), 4))
+
+#define SCMI_TLM_GET_UPDATE_INTERVAL(x) (FIELD_GET(GENMASK(20, 0), (x)))
+#define SCMI_TLM_BUILD_UPDATE_INTERVAL(s, e) \
+ (FIELD_PREP(GENMASK(20, 5), (s)) | FIELD_PREP(GENMASK(4, 0), (e)))
+
+enum scmi_telemetry_collection {
+ SCMI_TLM_ONDEMAND,
+ SCMI_TLM_NOTIFICATION,
+ SCMI_TLM_SINGLE_READ,
+};
+
+#define SCMI_TLM_GRP_INVALID 0xFFFFFFFF
+struct scmi_telemetry_group {
+ bool enabled;
+ bool tstamp_enabled;
+ unsigned int *des;
+ char *des_str;
+ struct scmi_tlm_grp_info *info;
+ unsigned int active_update_interval;
+ struct scmi_tlm_intervals *intervals;
+ enum scmi_telemetry_collection current_mode;
+};
+
+struct scmi_telemetry_de {
+ bool tstamp_support;
+ bool fc_support;
+ bool name_support;
+ struct scmi_tlm_de_info *info;
+ struct scmi_telemetry_group *grp;
+ bool enabled;
+ bool tstamp_enabled;
+};
+
+struct scmi_telemetry_res_info {
+ bool fully_enumerated;
+ unsigned int num_des;
+ struct scmi_telemetry_de **des;
+ struct scmi_tlm_de_info *dei_store;
+ unsigned int num_groups;
+ struct scmi_telemetry_group *grps;
+ struct scmi_tlm_grp_info *grps_store;
+};
+
+struct scmi_telemetry_info {
+ bool single_read_support;
+ bool continuos_update_support;
+ bool per_group_config_support;
+ bool reset_support;
+ bool fc_support;
+ struct scmi_tlm_base_info base;
+ unsigned int active_update_interval;
+ struct scmi_tlm_intervals *intervals;
+ bool enabled;
+ bool notif_enabled;
+ enum scmi_telemetry_collection current_mode;
+};
+
+/**
+ * struct scmi_telemetry_proto_ops - represents the various operations provided
+ * by SCMI Telemetry Protocol
+ *
+ * @info_get: get the general Telemetry information.
+ * @de_lookup: get a specific DE descriptor from the DE id.
+ * @res_get: get a reference to the Telemetry resources descriptor.
+ */
+struct scmi_telemetry_proto_ops {
+ const struct scmi_telemetry_info __must_check *(*info_get)
+ (const struct scmi_protocol_handle *ph);
+ const struct scmi_telemetry_de __must_check *(*de_lookup)
+ (const struct scmi_protocol_handle *ph, u32 id);
+ const struct scmi_telemetry_res_info __must_check *(*res_get)
+ (const struct scmi_protocol_handle *ph);
+};
+
/**
* struct scmi_notify_ops - represents notifications' operations provided by
* SCMI core
@@ -926,6 +1058,7 @@ enum scmi_std_protocol {
SCMI_PROTOCOL_VOLTAGE = 0x17,
SCMI_PROTOCOL_POWERCAP = 0x18,
SCMI_PROTOCOL_PINCTRL = 0x19,
+ SCMI_PROTOCOL_TELEMETRY = 0x1b,
};
enum scmi_system_events {
--
2.53.0
© 2016 - 2026 Red Hat, Inc.