lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<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

Powered by Openwall GNU/*/Linux Powered by OpenVZ