[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <02534a48-6795-4948-9348-fe507e8810fd@arm.com>
Date: Fri, 23 Jan 2026 11:00:46 +0000
From: Elif Topuz <elif.topuz@....com>
To: Cristian Marussi <cristian.marussi@....com>,
linux-kernel@...r.kernel.org, linux-arm-kernel@...ts.infradead.org,
arm-scmi@...r.kernel.org, linux-fsdevel@...r.kernel.org
Cc: sudeep.holla@....com, james.quinlan@...adcom.com, f.fainelli@...il.com,
vincent.guittot@...aro.org, etienne.carriere@...com, peng.fan@....nxp.com,
michal.simek@....com, dan.carpenter@...aro.org, d-gole@...com,
jonathan.cameron@...wei.com, lukasz.luba@....com, philip.radford@....com,
souvik.chakravarty@....com, elif.topuz@....com
Subject: Re: [PATCH v2 05/17] firmware: arm_scmi: Add Telemetry protocol
support
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@....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
Powered by blists - more mailing lists