From nobody Thu Oct 9 04:14:04 2025 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 693812EBBA9; Fri, 20 Jun 2025 19:29:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750447745; cv=none; b=I26XxbiKiTBm5VmerjwGv8e94ILXMgqlOnpSPFumDGRGZsGu4cYNvsuA7fzPLnYOyBaGSBcT3qKKYtVMH1SQ+k24xapZfONAGH/rMP/CtfwfgppWT1Chky0NQDmJMPlbKzNiUj5oz9XPgg29NUwuNKCVOnfOBVQ6DvXt3O+ahDE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750447745; c=relaxed/simple; bh=1mBMa0j54XAs8/pcKATd4UKU8viPI5T6UeJ1s7MotVA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=YqYe2ctLmKdbD+iRYSTkiLfiMWnW4/fU0ZMOi55NEMEda0/z1Ce36ZxdhUt0sgzUtldGiPNtjSebqM/ywBXnzMUewe+hWO2RX996ybjhEGyrFxQDVx/5+MWUd3dl1BjKgNPcQ8vwujJq7LXsJeiudFoMIaxVz+8EQVL8ajn3v7k= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 2EAEC16A3; Fri, 20 Jun 2025 12:28:41 -0700 (PDT) Received: from pluto.fritz.box (usa-sjc-mx-foss1.foss.arm.com [172.31.20.19]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id 29BDE3F673; Fri, 20 Jun 2025 12:28:58 -0700 (PDT) From: Cristian Marussi To: linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, arm-scmi@vger.kernel.org Cc: sudeep.holla@arm.com, james.quinlan@broadcom.com, f.fainelli@gmail.com, vincent.guittot@linaro.org, etienne.carriere@st.com, peng.fan@oss.nxp.com, michal.simek@amd.com, quic_sibis@quicinc.com, dan.carpenter@linaro.org, d-gole@ti.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [RFC PATCH 3/7] firmware: arm_scmi: Add Telemetry protocol support Date: Fri, 20 Jun 2025 20:28:09 +0100 Message-ID: <20250620192813.2463367-4-cristian.marussi@arm.com> X-Mailer: git-send-email 2.47.0 In-Reply-To: <20250620192813.2463367-1-cristian.marussi@arm.com> References: <20250620192813.2463367-1-cristian.marussi@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add basic support for SCMI V4.0-alpha_0 Telemetry protocol including SHMTI, FastChannels, Notifications and Single Sample Reads collection methods. Still lacking: - Block timestamp lines - Handling of already-on-at-boot Telemetry setup - Complete groups support (offset on enable and ungrouped DEs) - mmap access operations for RAW access Signed-off-by: Cristian Marussi --- drivers/firmware/arm_scmi/Makefile | 2 +- drivers/firmware/arm_scmi/driver.c | 2 + drivers/firmware/arm_scmi/protocols.h | 1 + drivers/firmware/arm_scmi/telemetry.c | 1719 +++++++++++++++++++++++++ include/linux/scmi_protocol.h | 198 ++- 5 files changed, 1920 insertions(+), 2 deletions(-) create mode 100644 drivers/firmware/arm_scmi/telemetry.c diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi= /Makefile index 780cd62b2f78..fe55b7aa0707 100644 --- a/drivers/firmware/arm_scmi/Makefile +++ b/drivers/firmware/arm_scmi/Makefile @@ -8,7 +8,7 @@ scmi-driver-$(CONFIG_ARM_SCMI_RAW_MODE_SUPPORT) +=3D raw_mo= de.o scmi-transport-$(CONFIG_ARM_SCMI_HAVE_SHMEM) =3D shmem.o scmi-transport-$(CONFIG_ARM_SCMI_HAVE_MSG) +=3D msg.o scmi-protocols-y :=3D base.o clock.o perf.o power.o reset.o sensors.o syst= em.o voltage.o powercap.o -scmi-protocols-y +=3D pinctrl.o +scmi-protocols-y +=3D pinctrl.o telemetry.o scmi-module-objs :=3D $(scmi-driver-y) $(scmi-protocols-y) $(scmi-transpor= t-y) =20 obj-$(CONFIG_ARM_SCMI_PROTOCOL) +=3D transports/ diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi= /driver.c index 1b9404175098..b235fc7f1a65 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -3445,6 +3445,7 @@ static int __init scmi_driver_init(void) scmi_system_register(); scmi_powercap_register(); scmi_pinctrl_register(); + scmi_telemetry_register(); =20 return platform_driver_register(&scmi_driver); } @@ -3463,6 +3464,7 @@ static void __exit scmi_driver_exit(void) scmi_system_unregister(); scmi_powercap_unregister(); scmi_pinctrl_unregister(); + scmi_telemetry_unregister(); =20 platform_driver_unregister(&scmi_driver); =20 diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_s= cmi/protocols.h index 2e40a7bb5b01..edd83a02e272 100644 --- a/drivers/firmware/arm_scmi/protocols.h +++ b/drivers/firmware/arm_scmi/protocols.h @@ -387,5 +387,6 @@ DECLARE_SCMI_REGISTER_UNREGISTER(sensors); DECLARE_SCMI_REGISTER_UNREGISTER(voltage); DECLARE_SCMI_REGISTER_UNREGISTER(system); DECLARE_SCMI_REGISTER_UNREGISTER(powercap); +DECLARE_SCMI_REGISTER_UNREGISTER(telemetry); =20 #endif /* _SCMI_PROTOCOLS_H */ diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_s= cmi/telemetry.c new file mode 100644 index 000000000000..3cbad06251a9 --- /dev/null +++ b/drivers/firmware/arm_scmi/telemetry.c @@ -0,0 +1,1719 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Management Interface (SCMI) Telemetry Protocol + * + * Copyright (C) 2025 ARM Ltd. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "protocols.h" +#include "notify.h" + +/* Updated only after ALL the mandatory features for that version are merg= ed */ +#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x10000 + +#define SCMI_TLM_TDCF_MAX_RETRIES 5 + +enum scmi_telemetry_protocol_cmd { + TELEMETRY_LIST_SHMTI =3D 0x3, + TELEMETRY_DE_DESCRIPTION =3D 0x4, + TELEMETRY_LIST_UPDATE_INTERVALS =3D 0x5, + TELEMETRY_DE_CONFIGURE =3D 0x6, + TELEMETRY_DE_ENABLED_LIST =3D 0x7, //TODO IMPLEMENT + TELEMETRY_CONFIG_SET =3D 0x8, + TELEMETRY_READING_COMPLETE =3D TELEMETRY_CONFIG_SET, + TELEMETRY_CONFIG_GET =3D 0x9, //TODO IMPLEMENT ! + TELEMETRY_RESET =3D 0xA, +}; + +struct scmi_msg_resp_telemetry_protocol_attributes { + __le32 de_num; + __le32 groups_num; + __le32 de_implementation_rev_dword[SCMI_TLM_MAX_DWORD]; + __le32 attributes; +#define SUPPORTS_SINGLE_READ(x) ((x) & BIT(31)) +#define SUPPORTS_CONTINUOS_UPDATE(x) ((x) & BIT(30)) +#define SUPPORTS_PER_GROUP_CONFIG(x) ((x) & BIT(18)) +#define SUPPORTS_RESET(x) ((x) & BIT(17)) +#define SUPPORTS_FC(x) ((x) & BIT(16)) +}; + +struct scmi_telemetry_update_notify_payld { + __le32 agent_id; + __le32 status; + __le32 num_dwords; + __le32 array[]; +}; + +struct scmi_shmti_desc { + __le32 id; + __le32 addr_low; + __le32 addr_high; + __le32 length; +}; + +struct scmi_msg_resp_telemetry_shmti_list { + __le32 num_shmti; + struct scmi_shmti_desc desc[]; +}; + +struct de_desc_fc { + __le32 addr_low; + __le32 addr_high; + __le32 size; +}; + +struct scmi_de_desc { + __le32 id; + __le32 grp_id; + __le32 data_sz; + __le32 attr_1; +#define IS_NAME_SUPPORTED(d) ((d)->attr_1 & BIT(31)) +#define IS_FC_SUPPORTED(d) ((d)->attr_1 & BIT(30)) +#define GET_DE_TYPE(d) (le32_get_bits((d)->attr_1, GENMASK(29, 22))) +#define IS_PERSISTENT(d) ((d)->attr_1 & BIT(21)) +#define GET_DE_UNIT_EXP(d) \ + ({ \ + int __signed_exp =3D \ + le32_get_bits((d)->attr_1, GENMASK(20, 13)); \ + \ + if (__signed_exp & BIT(7)) \ + __signed_exp |=3D GENMASK(31, 8); \ + __signed_exp; \ + }) +#define GET_DE_UNIT(d) (le32_get_bits((d)->attr_1, GENMASK(12, 5))) + +#define GET_DE_TSTAMP_EXP(d) \ + ({ \ + int __signed_exp =3D \ + FIELD_GET(GENMASK(4, 1), (d)->attr_1); \ + \ + if (__signed_exp & BIT(3)) \ + __signed_exp |=3D GENMASK(31, 4); \ + __signed_exp; \ + }) +#define IS_TSTAMP_SUPPORTED(d) ((d)->attr_1 & BIT(0)) + __le32 attr_2; +#define GET_DE_INSTA_ID(d) (le32_get_bits((d)->attr_2, GENMASK(31, 24))) +#define GET_COMPO_INSTA_ID(d) (le32_get_bits((d)->attr_2, GENMASK(23, 8))) +#define GET_COMPO_TYPE(d) (le32_get_bits((d)->attr_2, GENMASK(7, 0))) + __le32 reserved; +}; + +struct scmi_msg_resp_telemetry_de_description { + __le32 num_desc; + struct scmi_de_desc desc[]; +}; + +struct scmi_msg_telemetry_update_intervals { + __le32 index; + __le32 group_identifier; +#define ALL_DES_NO_GROUP 0x0 +#define SPECIFIC_GROUP_DES 0x1 +#define ALL_DES_ANY_GROUP 0x2 + __le32 flags; +}; + +struct scmi_msg_resp_telemetry_update_intervals { + __le32 flags; +#define INTERVALS_DISCRETE(x) (!((x) & BIT(12))) + __le32 intervals[]; +}; + +struct scmi_msg_telemetry_de_configure { + __le32 id; + __le32 flags; +#define DE_ENABLE_NO_TSTAMP BIT(0) +#define DE_ENABLE_WTH_TSTAMP BIT(1) +#define DE_DISABLE_ALL BIT(2) +#define GROUP_SELECTOR BIT(3) +#define EVENT_DE 0 +#define EVENT_GROUP 1 +#define DE_DISABLE_ONE 0x0 +}; + +struct scmi_msg_resp_telemetry_de_configure { + __le32 shmti_id; +#define IS_SHMTI_ID_VALID(x) ((x) !=3D 0xFFFFFFFF) + __le32 tdcf_de_offset; +}; + +struct scmi_msg_telemetry_config_set { + __le32 grp_id; + __le32 control; +#define TELEMETRY_ENABLE (BIT(0)) + +#define TELEMETRY_MODE(x) (FIELD_PREP(GENMASK(4, 1), (x))) +#define TELEMETRY_MODE_ONDEMAND TELEMETRY_MODE(0) +#define TELEMETRY_MODE_NOTIFS TELEMETRY_MODE(1) +#define TELEMETRY_MODE_SINGLE TELEMETRY_MODE(2) + +#define TELEMETRY_SELECTOR(x) (FIELD_PREP(GENMASK(8, 5), (x))) +#define TELEMETRY_SELECTOR_ORPHANS TELEMETRY_SELECTOR(0) +#define TELEMETRY_SELECTOR_GROUP TELEMETRY_SELECTOR(1) +#define TELEMETRY_SELECTOR_ALL TELEMETRY_SELECTOR(2) + __le32 sampling_rate; +}; + +struct scmi_msg_resp_telemetry_reading_complete { + __le32 num_dwords; + __le32 dwords[]; +}; + +/* TDCF */ + +#define TO_CPU_64(h, l) (((u64)le32_to_cpu((h)) << 32) | le32_to_cpu((l))) + +struct fc_line { + u32 data_low; + u32 data_high; +}; + +struct fc_tsline { + u32 data_low; + u32 data_high; + u32 ts_low; + u32 ts_high; +}; + +struct line { + u32 data_low; + u32 data_high; +}; + +#define BLK_TSTAMP(l) ((((u64)(l)->data_high) << 32) | (l)->data_low) + +struct blk_tsline { + u32 ts_low; + u32 ts_high; +}; + +struct tsline { + u32 data_low; + u32 data_high; + u32 ts_low; + u32 ts_high; +}; + +#define LINE_DATA_GET(f) (TO_CPU_64((f)->data_high, (f)->data_low)) +#define LINE_TSTAMP_GET(f) (TO_CPU_64((f)->ts_high, (f)->ts_low)) + +struct payload { + u32 meta; +#define IS_BLK_TS(x) ((x)->meta & BIT(4)) +#define USE_BLK_TS(x) ((x)->meta & BIT(3)) +#define USE_LINE_TS(x) ((x)->meta & BIT(2)) +#define TS_VALID(x) ((x)->meta & BIT(1)) +#define DATA_INVALID(x) ((x)->meta & BIT(0)) + u32 id; + union { + struct line l; + struct tsline tsl; + struct blk_tsline blk_tsl; + }; +}; + +#define LINE_DATA_PAYLD_WORDS \ + ((sizeof(u32) + sizeof(u32) + sizeof(struct line)) / sizeof(u32)) +#define TS_LINE_DATA_PAYLD_WORDS \ + ((sizeof(u32) + sizeof(u32) + sizeof(struct tsline)) / sizeof(u32)) + +struct prlg { + u32 seq_low; + u32 seq_high; + u32 num_qwords; + u32 _meta_header_high; +}; + +struct eplg { + u32 seq_low; + u32 seq_high; +}; + +#define TDCF_EPLG_SZ (sizeof(struct eplg)) + +struct tdcf { + struct prlg prlg; + unsigned char payld[]; +}; + +#define TDCF_START_SEQ_GET(x) \ + ({ \ + u64 _val; \ + struct prlg *_p =3D &((x)->prlg); \ + \ + _val =3D TO_CPU_64(_p->seq_high, _p->seq_low); \ + (_val); \ + }) + +#define IS_BAD_START_SEQ(s) ((s) & 0x1) + +#define TDCF_END_SEQ_GET(e) \ + ({ \ + u64 _val; \ + struct eplg *_e =3D (e); \ + \ + _val =3D TO_CPU_64(_e->seq_high, _e->seq_low); \ + (_val); \ + }) + +struct telemetry_shmti { + int id; + void __iomem *base; + u32 len; +}; + +#define SHMTI_EPLG(s) \ + ({ \ + struct telemetry_shmti *_s =3D (s); \ + void *_eplg; \ + \ + _eplg =3D _s->base + _s->len; \ + (_eplg); \ + }) + +struct telemetry_info { + bool streaming_mode; + int num_shmti; + struct device *dev; + struct telemetry_shmti *shmti; + struct xarray xa_des; + struct scmi_telemetry_info info; + struct notifier_block telemetry_nb; +}; + +#define telemetry_nb_to_info(x) \ + container_of(x, struct telemetry_info, telemetry_nb) + +struct telemetry_de { + bool cached; + char name[SCMI_SHORT_NAME_MAX_SIZE]; + void __iomem *base; + void __iomem *eplg; + u32 offset; + /* NOTE THAT DE data_sz is registered in scmi_telemetry_de */ + u32 fc_size; + struct scmi_telemetry_de de; + u64 last_val; + u64 last_ts; + /* Protect last_val/ts accesses */ + struct mutex mtx; +}; + +#define to_tde(d) container_of(d, struct telemetry_de, de) + +struct scmi_tlm_de_priv { + struct telemetry_info *ti; + void *next; +}; + +static int +scmi_telemetry_protocol_attributes_get(const struct scmi_protocol_handle *= ph, + struct telemetry_info *ti) +{ + int ret; + struct scmi_xfer *t; + struct scmi_msg_resp_telemetry_protocol_attributes *resp; + + ret =3D ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, + 0, sizeof(*resp), &t); + if (ret) + return ret; + + resp =3D t->rx.buf; + ret =3D ph->xops->do_xfer(ph, t); + if (!ret) { + __le32 attr =3D resp->attributes; + + ti->info.num_de =3D le32_to_cpu(resp->de_num); + ti->info.num_groups =3D le32_to_cpu(resp->groups_num); + for (int i =3D 0; i < SCMI_TLM_MAX_DWORD; i++) + ti->info.de_impl_version[i] =3D + le32_to_cpu(resp->de_implementation_rev_dword[i]); + ti->info.single_read_support =3D SUPPORTS_SINGLE_READ(attr); + ti->info.continuos_update_support =3D SUPPORTS_CONTINUOS_UPDATE(attr); + ti->info.per_group_config_support =3D SUPPORTS_PER_GROUP_CONFIG(attr); + ti->info.reset_support =3D SUPPORTS_RESET(attr); + ti->info.fc_support =3D SUPPORTS_FC(attr); + ti->num_shmti =3D le32_get_bits(attr, GENMASK(15, 0)); + /* Allocate DEs descriptors */ + ti->info.des =3D devm_kcalloc(ph->dev, ti->info.num_de, + sizeof(*ti->info.des), GFP_KERNEL); + if (!ti->info.des) + ret =3D -ENOMEM; + + /* Allocate DE GROUPS descriptors */ + ti->info.des_groups =3D devm_kcalloc(ph->dev, ti->info.num_groups, + sizeof(*ti->info.des_groups), + GFP_KERNEL); + if (!ti->info.des_groups) + ret =3D -ENOMEM; + + for (int i =3D 0; i < ti->info.num_groups; i++) + ti->info.des_groups[i].id =3D i; + } + + ph->xops->xfer_put(ph, t); + + return ret; +} + +static void iter_tlm_prepare_message(void *message, + unsigned int desc_index, const void *priv) +{ + put_unaligned_le32(desc_index, message); +} + +static int iter_de_descr_update_state(struct scmi_iterator_state *st, + const void *response, void *priv) +{ + const struct scmi_msg_resp_telemetry_de_description *r =3D response; + struct scmi_tlm_de_priv *p =3D priv; + + st->num_returned =3D le32_get_bits(r->num_desc, GENMASK(15, 0)); + st->num_remaining =3D le32_get_bits(r->num_desc, GENMASK(31, 16)); + + /* Initialized to first descriptor */ + p->next =3D (void *)r->desc; + + return 0; +} + +static int iter_de_descr_process_response(const struct scmi_protocol_handl= e *ph, + const void *response, + struct scmi_iterator_state *st, + void *priv) +{ + struct telemetry_de *tde; + struct scmi_tlm_de_priv *p =3D priv; + const struct scmi_de_desc *desc =3D p->next; + unsigned int grp_id; + int ret; + + tde =3D to_tde(p->ti->info.des[st->desc_index + st->loop_idx]); + + tde->de.id =3D le32_to_cpu(desc->id); + grp_id =3D le32_to_cpu(desc->grp_id); + if (grp_id !=3D SCMI_TLM_GRP_INVALID) { + if (grp_id >=3D p->ti->info.num_groups) + return -EINVAL; + + /* Link to parent group */ + tde->de.grp =3D &p->ti->info.des_groups[grp_id]; + } + tde->de.data_sz =3D le32_to_cpu(desc->data_sz); + tde->de.type =3D GET_DE_TYPE(desc); + tde->de.unit =3D GET_DE_UNIT(desc); + tde->de.unit_exp =3D GET_DE_UNIT_EXP(desc); + tde->de.tstamp_exp =3D GET_DE_TSTAMP_EXP(desc); + tde->de.instance_id =3D GET_DE_INSTA_ID(desc); + tde->de.compo_instance_id =3D GET_COMPO_INSTA_ID(desc); + tde->de.compo_type =3D GET_COMPO_TYPE(desc); + tde->de.persistent =3D IS_PERSISTENT(desc); + tde->de.tstamp_support =3D IS_TSTAMP_SUPPORTED(desc); + tde->de.fc_support =3D IS_FC_SUPPORTED(desc); + tde->de.name_support =3D IS_NAME_SUPPORTED(desc); + p->next +=3D sizeof(*desc); + if (tde->de.fc_support) { + u32 size; + u64 phys_addr; + void __iomem *addr; + struct de_desc_fc *dfc; + + dfc =3D p->next; + phys_addr =3D le32_to_cpu(dfc->addr_low); + phys_addr |=3D (u64)le32_to_cpu(dfc->addr_high) << 32; + + size =3D le32_to_cpu(dfc->size); + addr =3D devm_ioremap(ph->dev, phys_addr, size); + if (!addr) + return -EADDRNOTAVAIL; + + tde->base =3D addr; + tde->offset =3D 0; + tde->fc_size =3D size; + + /* Variably sized depending on FC support */ + p->next +=3D sizeof(*dfc); + } + + if (tde->de.name_support) { + const char *de_name =3D p->next; + + strscpy(tde->name, de_name, SCMI_SHORT_NAME_MAX_SIZE); + tde->de.name =3D tde->name; + + /* Variably sized depending on name support */ + p->next +=3D SCMI_SHORT_NAME_MAX_SIZE; + } + + /* Store DE pointer by de_id */ + ret =3D xa_insert(&p->ti->xa_des, tde->de.id, &tde->de, GFP_KERNEL); + if (ret) + return ret; + + /* Account for this DE in group num_de counter */ + if (tde->de.grp) + tde->de.grp->num_de++; + + return 0; +} + +static int +scmi_telemetry_de_groups_init(struct device *dev, struct telemetry_info *t= i) +{ + /* Allocate all groups DEs IDs arrays at first ... */ + for (int i =3D 0; i < ti->info.num_groups; i++) { + struct scmi_telemetry_group *grp =3D &ti->info.des_groups[i]; + + grp->des =3D devm_kcalloc(dev, grp->num_de, sizeof(unsigned int), + GFP_KERNEL); + if (!grp->des) + return -ENOMEM; + + /* + * Max size 32bit ID string in Hex: 0xCAFECAFE + * - 10 digits + ' '/'\n' =3D 11 bytes per number + * - terminating NUL character + */ + grp->des_str_sz =3D grp->num_de * 11 + 1; + grp->des_str =3D devm_kzalloc(dev, grp->des_str_sz, GFP_KERNEL); + if (!grp->des_str) + return -ENOMEM; + + /* Reset group DE counter */ + grp->num_de =3D 0; + } + + /* Scan DEs and populate group DE IDs arrays */ + for (int i =3D 0; i < ti->info.num_de; i++) { + struct scmi_telemetry_group *grp =3D ti->info.des[i]->grp; + + if (!grp) + continue; + + /* Note that, at this point, num_de is guaranteed to be + * sane (in-bounds) by construction. + */ + grp->des[grp->num_de++] =3D i; + } + + /* Build compsing DES string */ + for (int i =3D 0; i < ti->info.num_groups; i++) { + struct scmi_telemetry_group *grp =3D &ti->info.des_groups[i]; + char *buf =3D grp->des_str; + size_t bufsize =3D grp->des_str_sz; + + for (int j =3D 0; j < grp->num_de; j++) { + char term =3D j !=3D (grp->num_de - 1) ? ' ' : '\0'; + int len; + + len =3D snprintf(buf, bufsize, "0x%04X%c", + ti->info.des[grp->des[j]]->id, term); + + buf +=3D len; + bufsize -=3D len; + } + } + + return 0; +} + +static int +scmi_telemetry_de_descriptors_get(const struct scmi_protocol_handle *ph, + struct telemetry_info *ti) +{ + struct scmi_iterator_ops ops =3D { + .prepare_message =3D iter_tlm_prepare_message, + .update_state =3D iter_de_descr_update_state, + .process_response =3D iter_de_descr_process_response, + }; + struct scmi_tlm_de_priv tpriv =3D { + .ti =3D ti, + .next =3D NULL, + }; + void *iter; + int ret; + + xa_init(&ti->xa_des); + iter =3D ph->hops->iter_response_init(ph, &ops, ti->info.num_de, + TELEMETRY_DE_DESCRIPTION, + sizeof(u32), &tpriv); + if (IS_ERR(iter)) + return PTR_ERR(iter); + + ret =3D ph->hops->iter_response_run(iter); + if (ret) + return ret; + + return scmi_telemetry_de_groups_init(ph->dev, ti); +} + +static int scmi_telemetry_enumerate_de(const struct scmi_protocol_handle *= ph, + struct telemetry_info *ti) +{ + int ret; + + if (!ti->info.num_de) + return 0; + + for (int i =3D 0; i < ti->info.num_de; i++) { + struct telemetry_de *tde; + + tde =3D devm_kzalloc(ph->dev, sizeof(*tde), GFP_KERNEL); + if (!tde) + return -ENOMEM; + + mutex_init(&tde->mtx); + ti->info.des[i] =3D &tde->de; + } + + ret =3D scmi_telemetry_de_descriptors_get(ph, ti); + if (ret) { + dev_err(ph->dev, "Cannot get DE descriptors"); + return ret; + } + + return 0; +} + +struct scmi_tlm_ivl_priv { + struct device *dev; + struct scmi_telemetry_update_interval *intrvs; + unsigned int grp_id; + unsigned int flags; +}; + +static void iter_intervals_prepare_message(void *message, + unsigned int desc_index, + const void *priv) +{ + struct scmi_msg_telemetry_update_intervals *msg =3D message; + const struct scmi_tlm_ivl_priv *p =3D priv; + + msg->index =3D cpu_to_le32(desc_index); + msg->group_identifier =3D cpu_to_le32(p->grp_id); + msg->flags =3D 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 =3D response; + + st->num_returned =3D le32_get_bits(r->flags, GENMASK(11, 0)); + st->num_remaining =3D 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 =3D priv; + + p->intrvs->discrete =3D INTERVALS_DISCRETE(r->flags); + /* Check consistency on first call */ + if (!p->intrvs->discrete && + (st->num_returned !=3D 3 || st->num_remaining !=3D 0)) + return -EINVAL; + + p->intrvs->num =3D st->num_returned + st->num_remaining; + p->intrvs->update_intervals =3D + devm_kcalloc(p->dev, p->intrvs->num, + sizeof(*p->intrvs->update_intervals), + GFP_KERNEL); + if (!p->intrvs->update_intervals) { + p->intrvs->num =3D 0; + return -ENOMEM; + } + + st->max_resources =3D p->intrvs->num; + } + + return 0; +} + +static int +iter_intervals_process_response(const struct scmi_protocol_handle *ph, + const void *response, + struct scmi_iterator_state *st, void *priv) +{ + const struct scmi_msg_resp_telemetry_update_intervals *r =3D response; + struct scmi_tlm_ivl_priv *p =3D priv; + unsigned int idx =3D st->loop_idx; + + p->intrvs->update_intervals[st->desc_index + idx] =3D r->intervals[idx]; + + return 0; +} + +static int +scmi_tlm_enumerate_update_intervals(const struct scmi_protocol_handle *ph, + struct telemetry_info *ti, int grp_id, + unsigned int flags) +{ + struct scmi_iterator_ops ops =3D { + .prepare_message =3D iter_intervals_prepare_message, + .update_state =3D iter_intervals_update_state, + .process_response =3D iter_intervals_process_response, + }; + struct scmi_tlm_ivl_priv ipriv =3D { + .dev =3D ph->dev, + .grp_id =3D grp_id, + .intrvs =3D (grp_id =3D=3D SCMI_TLM_GRP_INVALID) ? + &ti->info.intervals : + &ti->info.des_groups[grp_id].intervals, + .flags =3D flags, + }; + void *iter; + + iter =3D ph->hops->iter_response_init(ph, &ops, 0, + TELEMETRY_LIST_UPDATE_INTERVALS, + sizeof(struct scmi_msg_telemetry_update_intervals), + &ipriv); + if (IS_ERR(iter)) + return PTR_ERR(iter); + + return ph->hops->iter_response_run(iter); +} + +static int +scmi_telemetry_enumerate_update_intervals(const struct scmi_protocol_handl= e *ph, + struct telemetry_info *ti) +{ + int ret; + unsigned int flags; + + flags =3D !ti->info.per_group_config_support ? + ALL_DES_ANY_GROUP : ALL_DES_NO_GROUP; + + ret =3D scmi_tlm_enumerate_update_intervals(ph, ti, SCMI_TLM_GRP_INVALID, + flags); + if (ret) + return ret; + + if (ti->info.num_groups && ti->info.per_group_config_support) { + flags =3D SPECIFIC_GROUP_DES; + for (int grp_id =3D 0; grp_id < ti->info.num_groups; grp_id++) { + ret =3D scmi_tlm_enumerate_update_intervals(ph, ti, grp_id, + flags); + if (ret) + break; + } + } + + return ret; +} + +static int iter_shmti_update_state(struct scmi_iterator_state *st, + const void *response, void *priv) +{ + const struct scmi_msg_resp_telemetry_shmti_list *r =3D response; + + st->num_returned =3D le32_get_bits(r->num_shmti, GENMASK(15, 0)); + st->num_remaining =3D 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 =3D response; + struct telemetry_info *ti =3D priv; + struct telemetry_shmti *shmti; + const struct scmi_shmti_desc *desc; + void __iomem *addr; + u64 phys_addr; + u32 len; + + desc =3D &r->desc[st->loop_idx]; + shmti =3D &ti->shmti[st->desc_index + st->loop_idx]; + + shmti->id =3D le32_to_cpu(desc->id); + phys_addr =3D le32_to_cpu(desc->addr_low); + phys_addr |=3D (u64)le32_to_cpu(desc->addr_high) << 32; + + len =3D le32_to_cpu(desc->length); + addr =3D devm_ioremap(ph->dev, phys_addr, len); + if (!addr) + return -EADDRNOTAVAIL; + + shmti->base =3D addr; + shmti->len =3D len; + + return 0; +} + +static int scmi_telemetry_shmti_list(const struct scmi_protocol_handle *ph, + struct telemetry_info *ti) +{ + struct scmi_iterator_ops ops =3D { + .prepare_message =3D iter_tlm_prepare_message, + .update_state =3D iter_shmti_update_state, + .process_response =3D iter_shmti_process_response, + }; + void *iter; + + iter =3D ph->hops->iter_response_init(ph, &ops, ti->info.num_de, + TELEMETRY_LIST_SHMTI, + sizeof(u32), ti); + if (IS_ERR(iter)) + return PTR_ERR(iter); + + return ph->hops->iter_response_run(iter); +} + +static int scmi_telemetry_enumerate_shmti(const struct scmi_protocol_handl= e *ph, + struct telemetry_info *ti) +{ + int ret; + + if (!ti->num_shmti) + return 0; + + ti->shmti =3D devm_kcalloc(ph->dev, ti->num_shmti, sizeof(*ti->shmti), + GFP_KERNEL); + if (!ti->shmti) + return -ENOMEM; + + ret =3D 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 =3D ph->get_priv(ph); + + return &ti->info; +} + +static int scmi_telemetry_tdcf_parse_one(struct telemetry_info *ti, + struct payload __iomem *payld, + struct telemetry_shmti *shmti) +{ + struct scmi_telemetry_de *de; + struct telemetry_de *tde; + u64 val, tstamp; + int used_qwords; + + de =3D xa_load(&ti->xa_des, le32_to_cpu(payld->id)); + if (!de || DATA_INVALID(payld)) + return -EINVAL; + + used_qwords =3D 4; + + tde =3D to_tde(de); + if (shmti) { + tde->base =3D shmti->base; + tde->eplg =3D shmti->base + shmti->len - TDCF_EPLG_SZ; + tde->offset =3D (void *)payld - (void *)shmti->base; + } + + //TODO BLK_TS + if (USE_LINE_TS(payld)) { + used_qwords +=3D 2; + if (TS_VALID(payld)) + tstamp =3D LINE_TSTAMP_GET(&payld->tsl); + } + val =3D LINE_DATA_GET(&payld->tsl); + + guard(mutex)(&tde->mtx); + tde->last_val =3D val; + if (de->tstamp_enabled) + tde->last_ts =3D tstamp; + else + tde->last_ts =3D 0; + + return used_qwords; +} + +static int scmi_telemetry_shmti_scan(struct telemetry_info *ti, + unsigned int shmti_id, u64 ts, + bool update) +{ + struct telemetry_shmti *shmti =3D &ti->shmti[shmti_id]; + struct tdcf __iomem *tdcf =3D shmti->base; + int retries =3D SCMI_TLM_TDCF_MAX_RETRIES; + u64 startm =3D 0, endm =3D 0xffffffffffffffff; + void *eplg =3D 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 =3D TDCF_START_SEQ_GET(tdcf); + if (IS_BAD_START_SEQ(startm)) + continue; + + qwords =3D tdcf->prlg.num_qwords; + next =3D tdcf->payld; + while (qwords) { + int used_qwords; + + used_qwords =3D scmi_telemetry_tdcf_parse_one(ti, next, + update ? shmti : NULL); + if (qwords < used_qwords) + return -EINVAL; + + next +=3D used_qwords * 4; + qwords -=3D used_qwords; + } + + endm =3D TDCF_END_SEQ_GET(eplg); + } while (startm !=3D endm && --retries); + + if (startm !=3D endm) + return -EPROTO; + + return 0; +} + +static int scmi_telemetry_group_state_update(struct telemetry_info *ti, + struct scmi_telemetry_group *grp, + bool *enable, bool *tstamp) +{ + struct scmi_telemetry_de *de; + + for (int i =3D 0; i < grp->num_de; i++) { + de =3D ti->info.des[grp->des[i]]; + + if (enable) + de->enabled =3D *enable; + if (tstamp) + de->tstamp_enabled =3D *tstamp; + } + + return 0; +} + +static int __scmi_telemetry_state_set(const struct scmi_protocol_handle *p= h, + bool is_group, bool *enable, + bool *enabled_state, bool *tstamp, + bool *tstamp_enabled_state, void *priv) +{ + struct scmi_msg_resp_telemetry_de_configure *resp; + struct scmi_msg_telemetry_de_configure *msg; + struct telemetry_info *ti =3D ph->get_priv(ph); + struct scmi_telemetry_group *grp =3D priv; + struct scmi_xfer *t; + int ret; + + if (!enabled_state || !tstamp_enabled_state) + return -EINVAL; + + /* Is anything to do at all on this DE ? */ + if (!is_group && (!enable || *enable =3D=3D *enabled_state) && + (!tstamp || *tstamp =3D=3D *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 =3D *tstamp; + return 0; + } + + ret =3D ph->xops->xfer_get_init(ph, TELEMETRY_DE_CONFIGURE, + sizeof(*msg), sizeof(*resp), &t); + if (ret) + return ret; + + msg =3D t->tx.buf; + /* Note that BOTH DE and GROUPS have a first ID field.. */ + msg->id =3D cpu_to_le32(grp->id); + /* Default to disable mode for one DE */ + msg->flags =3D DE_DISABLE_ONE; + msg->flags |=3D 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 |=3D *tstamp ? DE_ENABLE_WTH_TSTAMP : + DE_ENABLE_NO_TSTAMP; + } else { + msg->flags |=3D *tstamp_enabled_state ? + DE_ENABLE_WTH_TSTAMP : DE_ENABLE_NO_TSTAMP; + } + } + + resp =3D t->rx.buf; + ret =3D ph->xops->do_xfer(ph, t); + if (!ret) { + u32 id =3D le32_to_cpu(resp->shmti_id); + + /* Update DE SHMTI and offset, if applicable */ + if (enable && IS_SHMTI_ID_VALID(id)) { + if (id >=3D ti->num_shmti) { + ret =3D -EPROTO; + goto out; + } + + /* + * Update SHMTI/offset while skipping non-SHMTI-DEs like + * FCs and notif-only. + */ + if (!is_group) { + struct scmi_telemetry_de *de =3D priv; + struct telemetry_de *tde; + u32 offs; + + offs =3D le32_to_cpu(resp->tdcf_de_offset); + if (offs >=3D ti->shmti[id].len - de->data_sz) { + ret =3D -EPROTO; + goto out; + } + + tde =3D to_tde(de); + tde->base =3D ti->shmti[id].base; + tde->offset =3D offs; + /* A handy reference to the Epilogue updated */ + tde->eplg =3D tde->base + ti->shmti[id].len - + TDCF_EPLG_SZ; + } else { + /* + * A full SHMTI scan is needed when enabling a + * group in order to retrieve offsets. + */ + scmi_telemetry_shmti_scan(ti, id, 0, true); + } + } + + /* Update cached state on success */ + if (enable) + *enabled_state =3D *enable; + if (tstamp) + *tstamp_enabled_state =3D *tstamp; + + if (is_group) + scmi_telemetry_group_state_update(ti, grp, enable, + tstamp); + } + +out: + ph->xops->xfer_put(ph, t); + + return ret; +} + +static int scmi_telemetry_state_get(const struct scmi_protocol_handle *ph, + u32 id, bool *enabled, bool *tstamp_enabled) +{ + struct telemetry_info *ti =3D ph->get_priv(ph); + struct scmi_telemetry_de *de; + + if (!enabled || !tstamp_enabled) + return -EINVAL; + + de =3D xa_load(&ti->xa_des, id); + if (!de) + return -ENODEV; + + *enabled =3D de->enabled; + *tstamp_enabled =3D de->tstamp_enabled; + + return 0; +} + +static int scmi_telemetry_state_set(const struct scmi_protocol_handle *ph, + bool is_group, u32 id, bool *enable, + bool *tstamp) +{ + void *obj; + bool *enabled_state, *tstamp_enabled_state; + struct telemetry_info *ti =3D ph->get_priv(ph); + + if (!is_group) { + struct scmi_telemetry_de *de; + + de =3D xa_load(&ti->xa_des, id); + if (!de) + return -ENODEV; + + enabled_state =3D &de->enabled; + tstamp_enabled_state =3D &de->tstamp_enabled; + obj =3D de; + } else { + struct scmi_telemetry_group *grp; + + if (id >=3D ti->info.num_groups) + return -EINVAL; + + grp =3D &ti->info.des_groups[id]; + + enabled_state =3D &grp->enabled; + tstamp_enabled_state =3D &grp->tstamp_enabled; + obj =3D 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 *p= h, + bool is_group) +{ + struct scmi_msg_telemetry_de_configure *msg; + struct scmi_xfer *t; + int ret; + + ret =3D ph->xops->xfer_get_init(ph, TELEMETRY_DE_CONFIGURE, + sizeof(*msg), 0, &t); + if (ret) + return ret; + + msg =3D t->tx.buf; + msg->flags =3D DE_DISABLE_ALL; + if (is_group) + msg->flags |=3D GROUP_SELECTOR; + ret =3D ph->xops->do_xfer(ph, t); + if (!ret) { + struct telemetry_info *ti =3D ph->get_priv(ph); + + for (int i =3D 0; i < ti->info.num_de; i++) + ti->info.des[i]->enabled =3D false; + + if (is_group) { + for (int i =3D 0; i < ti->info.num_groups; i++) + ti->info.des_groups[i].enabled =3D false; + } + } + + ph->xops->xfer_put(ph, t); + + return ret; +} + +static int +scmi_telemetry_collection_configure(const struct scmi_protocol_handle *ph, + unsigned int res_id, bool grp_ignore, + bool *enable, + unsigned int *update_interval_ms, + enum scmi_telemetry_collection *mode) +{ + struct scmi_telemetry_update_interval *intervals; + struct telemetry_info *ti =3D ph->get_priv(ph); + enum scmi_telemetry_collection *current_mode, next_mode; + struct scmi_msg_telemetry_config_set *msg; + struct scmi_xfer *t; + bool tlm_enable; + u32 interval; + int ret; + + if (mode && *mode =3D=3D SCMI_TLM_NOTIFICATION && + !ti->info.continuos_update_support) + return -EINVAL; + + if (res_id !=3D SCMI_TLM_GRP_INVALID && res_id >=3D ti->info.num_groups) + return -EINVAL; + + if (res_id =3D=3D SCMI_TLM_GRP_INVALID || grp_ignore) { + intervals =3D &ti->info.intervals; + current_mode =3D &ti->info.current_mode; + } else { + intervals =3D &ti->info.des_groups[res_id].intervals; + current_mode =3D &ti->info.des_groups[res_id].current_mode; + } + + if (!enable && !update_interval_ms && (!mode || *mode =3D=3D *current_mod= e)) + return 0; + + ret =3D ph->xops->xfer_get_init(ph, TELEMETRY_CONFIG_SET, + sizeof(*msg), 0, &t); + if (ret) + return ret; + + if (!update_interval_ms) { + interval =3D cpu_to_le32(intervals->active_update_interval); + } else { + //TODO Fix .. not only ms...review API + interval =3D FIELD_PREP(GENMASK(20, 5), + cpu_to_le32(*update_interval_ms)); + interval |=3D FIELD_PREP(GENMASK(4, 0), (-3 & 0x1f)); + } + + tlm_enable =3D enable ? *enable : ti->info.enabled; + next_mode =3D mode ? *mode : *current_mode; + + msg =3D t->tx.buf; + msg->grp_id =3D res_id; + msg->control =3D tlm_enable ? TELEMETRY_ENABLE : 0; + msg->control |=3D grp_ignore ? TELEMETRY_SELECTOR_ALL : + TELEMETRY_SELECTOR_GROUP; + msg->control |=3D TELEMETRY_MODE(next_mode); + msg->sampling_rate =3D interval; + ret =3D ph->xops->do_xfer(ph, t); + if (!ret) { + ti->info.enabled =3D tlm_enable; + *current_mode =3D next_mode; + ti->info.notif_enabled =3D *current_mode =3D=3D SCMI_TLM_NOTIFICATION; + if (update_interval_ms) + intervals->active_update_interval =3D interval; + } + + ph->xops->xfer_put(ph, t); + + return ret; +} + +static int scmi_telemetry_de_data_fc_read(struct telemetry_de *tde, + u64 *tstamp, u64 *val) +{ + struct fc_tsline __iomem *fc =3D tde->base + tde->offset; + + *val =3D LINE_DATA_GET(fc); + if (tstamp) { + if (tde->de.tstamp_support) + *tstamp =3D LINE_TSTAMP_GET(fc); + else + *tstamp =3D 0; + } + + return 0; +} + +/* + * TDCF and TS Line Management Notes + * --------------------------------- + * (from a chat with ATG) + * + * TCDF Payload Metadata notable bits: + * - Bit[2]: USE Tstamp + * - Bit[1]: Tstamp VALID + * + * CASE_1: + * ------- + * + A DE is enabled with timestamp disabled, so the TS_Line is NOT present + * -> BIT[2:1] =3D 00b + * + that DE is then 're-enabled' with TS_line: so it was ON, it remains ON + * but using DE_CONFIGURE I now enabled also TS, so the platform + * relocates it at the end of the SHMTI and return the new offset + * -> BIT[2:1]: 11b + * + the hole left from the relocated DE can be reused by the platform to + * fit another equally sized DE. (i.e. without shuffling around any + * other enabled DE, since that would cause a change of the known + * offset) + * + * CASE_2: + * ------- + * + A DE is enabled with timestamp enabled, so the TS_Line is there + * -> BIT[2:1]:11b + * + that DE has its timestamp disabled: again, you can do this without + * disabling it fully but just disabling the TS, so now that TS_line + * fields are still physiclly there but NOT valid + * -> BIT[2:1]=3D 10b + * + the hole from the timestamp remain there unused until + * - you enable again the TS so the hole is used again + * -> BIT[2:1]=3D11b + * OR + * - you disable fully the DE and then re-enable it with the TS + * -> potentially CASE_1 the DE is relocated on enable + */ +static int scmi_telemetry_de_tdcf_parse(struct telemetry_de *tde, + u64 *tstamp, u64 *val) +{ + struct tdcf __iomem *tdcf =3D tde->base; + u64 startm, endm; + int retries =3D 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 =3D TDCF_START_SEQ_GET(tdcf); + if (IS_BAD_START_SEQ(startm)) + continue; + + payld =3D tde->base + tde->offset; + if (le32_to_cpu(payld->id) !=3D tde->de.id || DATA_INVALID(payld)) + return -EINVAL; + + //TODO BLK_TS + if (tstamp && USE_LINE_TS(payld) && TS_VALID(payld)) + *tstamp =3D LINE_TSTAMP_GET(&payld->tsl); + + *val =3D LINE_DATA_GET(&payld->tsl); + + endm =3D TDCF_END_SEQ_GET(tde->eplg); + } while (startm !=3D endm && --retries); + + if (startm !=3D endm) + return -EPROTO; + + return 0; +} + +static int scmi_telemetry_de_lookup(struct telemetry_de *tde, + u64 *tstamp, u64 *val) +{ + if (!tde->de.fc_support) + return scmi_telemetry_de_tdcf_parse(tde, tstamp, val); + + return scmi_telemetry_de_data_fc_read(tde, tstamp, val); +} + +static int scmi_telemetry_de_collect(struct telemetry_info *ti, + struct scmi_telemetry_de *de, + u64 *tstamp, u64 *val) +{ + struct telemetry_de *tde =3D 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 =3D tde->last_val; + if (tstamp) + *tstamp =3D tde->last_ts; + return 0; + } + } + + return scmi_telemetry_de_lookup(tde, tstamp, val); +} + +static int scmi_telemetry_de_data_read(const struct scmi_protocol_handle *= ph, + struct scmi_telemetry_de_sample *sample) +{ + struct telemetry_info *ti =3D ph->get_priv(ph); + struct scmi_telemetry_de *de; + + if (!ti->info.enabled || !sample) + return -EINVAL; + + de =3D xa_load(&ti->xa_des, sample->id); + if (!de) + return -ENODEV; + + return scmi_telemetry_de_collect(ti, de, &sample->tstamp, &sample->val); +} + +static int +scmi_telemetry_samples_collect(struct telemetry_info *ti, int grp_id, + int *num_samples, + struct scmi_telemetry_de_sample *samples) +{ + int max_samples; + + max_samples =3D *num_samples; + *num_samples =3D 0; + + for (int i =3D 0; i < ti->info.num_de; i++) { + struct scmi_telemetry_de *de; + u64 val, tstamp; + int ret; + + de =3D ti->info.des[i]; + if (grp_id !=3D SCMI_TLM_GRP_INVALID && + (!de->grp || de->grp->id !=3D grp_id)) + continue; + + ret =3D scmi_telemetry_de_collect(ti, de, &tstamp, &val); + if (ret) + continue; + + if (*num_samples =3D=3D max_samples) + return -ENOSPC; + + samples[*num_samples].tstamp =3D tstamp; + samples[*num_samples].val =3D val; + samples[*num_samples].id =3D de->id; + + (*num_samples)++; + } + + return 0; +} + +static int scmi_telemetry_des_bulk_read(const struct scmi_protocol_handle = *ph, + int grp_id, int *num_samples, + struct scmi_telemetry_de_sample *samples) +{ + struct telemetry_info *ti =3D ph->get_priv(ph); + + if (!ti->info.enabled || !num_samples || !samples) + return -EINVAL; + + return scmi_telemetry_samples_collect(ti, grp_id, num_samples, samples); +} + +static void +scmi_telemetry_msg_payld_process(struct telemetry_info *ti, + unsigned int num_dwords, unsigned int *dwords, + ktime_t timestamp) +{ + u32 next =3D 0; + + while (next < num_dwords) { + struct payload *payld =3D (struct payload *)&dwords[next]; + struct scmi_telemetry_de *de; + struct telemetry_de *tde; + u32 de_id; + + next +=3D USE_LINE_TS(payld) ? + TS_LINE_DATA_PAYLD_WORDS : LINE_DATA_PAYLD_WORDS; + + if (DATA_INVALID(payld)) { + dev_err(ti->dev, "MSG - Received INVALID DATA line\n"); + continue; + } + + de_id =3D le32_to_cpu(payld->id); + de =3D xa_load(&ti->xa_des, de_id); + if (!de || !de->enabled) { + dev_err(ti->dev, + "MSG - Received INVALID DE - ID:%u enabled:%d\n", + de_id, de ? (de->enabled ? 'Y' : 'N') : 'X'); + continue; + } + + tde =3D to_tde(de); + guard(mutex)(&tde->mtx); + tde->cached =3D true; + tde->last_val =3D LINE_DATA_GET(&payld->tsl); + //TODO BLK_TS ? + if (USE_LINE_TS(payld) && TS_VALID(payld)) + tde->last_ts =3D LINE_TSTAMP_GET(&payld->tsl); + else + tde->last_ts =3D 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 =3D 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 =3D grp_id =3D=3D SCMI_TLM_GRP_INVALID ? true : false; + if (!grp_ignore && grp_id >=3D ti->info.num_groups) + return -EINVAL; + + ret =3D ph->xops->xfer_get_init(ph, TELEMETRY_CONFIG_SET, + sizeof(*msg), 0, &t); + if (ret) + return ret; + + msg =3D t->tx.buf; + msg->grp_id =3D grp_id; + msg->control =3D TELEMETRY_ENABLE; + msg->control |=3D grp_ignore ? TELEMETRY_SELECTOR_ALL : + TELEMETRY_SELECTOR_GROUP; + msg->control |=3D TELEMETRY_MODE_SINGLE; + msg->sampling_rate =3D 0; + + ret =3D ph->xops->do_xfer_with_response(ph, t); + if (!ret) { + struct scmi_msg_resp_telemetry_reading_complete *r =3D t->rx.buf; + + /* Update cached DEs values from payload */ + if (r->num_dwords) + scmi_telemetry_msg_payld_process(ti, r->num_dwords, + r->dwords, 0); + ret =3D scmi_telemetry_samples_collect(ti, grp_id, num_samples, + samples); + } + + ph->xops->xfer_put(ph, t); + + return ret; +} + +static int scmi_telemetry_config_get(const struct scmi_protocol_handle *ph, + bool *enabled, int *mode, + u32 *update_interval) +{ + ///TODO + return 0; +} + +static int scmi_telemetry_reset(const struct scmi_protocol_handle *ph) +{ + int ret; + struct scmi_xfer *t; + + ret =3D ph->xops->xfer_get_init(ph, TELEMETRY_RESET, sizeof(u32), 0, &t); + if (ret) + return ret; + + put_unaligned_le32(0, t->tx.buf); + ret =3D ph->xops->do_xfer(ph, t); + if (!ret) { + struct telemetry_info *ti =3D ph->get_priv(ph); + + //XXX Better would be to read back from platform + // CONFIG_GET + DE_ENABLED_LIST + ti->info.enabled =3D false; + ti->info.notif_enabled =3D false; + ti->info.current_mode =3D SCMI_TLM_ONDEMAND; + ti->info.intervals.active_update_interval =3D 0; + + for (int i =3D 0; i < ti->info.num_de; i++) { + ti->info.des[i]->enabled =3D false; + ti->info.des[i]->tstamp_enabled =3D false; + } + + for (int i =3D 0; i < ti->info.num_groups; i++) { + ti->info.des_groups[i].enabled =3D false; + ti->info.des_groups[i].tstamp_enabled =3D false; + ti->info.des_groups[i].current_mode =3D SCMI_TLM_ONDEMAND; + ti->info.des_groups[i].intervals.active_update_interval =3D 0; + } + } + ph->xops->xfer_put(ph, t); + + return ret; +} + +static const struct scmi_telemetry_proto_ops tlm_proto_ops =3D { + .info_get =3D scmi_telemetry_info_get, + .state_get =3D scmi_telemetry_state_get, + .state_set =3D scmi_telemetry_state_set, + .all_disable =3D scmi_telemetry_all_disable, + .collection_configure =3D scmi_telemetry_collection_configure, + .de_data_read =3D scmi_telemetry_de_data_read, + .des_bulk_read =3D scmi_telemetry_des_bulk_read, + .des_sample_get =3D scmi_telemetry_des_sample_get, + .config_get =3D scmi_telemetry_config_get, + .reset =3D 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 =3D 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 =3D payld; + struct scmi_telemetry_update_report *r =3D report; + + /* At least sized as an empty notification */ + if (payld_sz < sizeof(*p)) + return NULL; + + r->timestamp =3D timestamp; + r->agent_id =3D le32_to_cpu(p->agent_id); + r->status =3D le32_to_cpu(p->status); + r->num_dwords =3D 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 =3D 0; + + return r; +} + +static const struct scmi_event tlm_events[] =3D { + { + .id =3D SCMI_EVENT_TELEMETRY_UPDATE, + .max_payld_sz =3D 0, + .max_report_sz =3D 0, + }, +}; + +static const struct scmi_event_ops tlm_event_ops =3D { + .is_notify_supported =3D scmi_telemetry_notify_supported, + .set_notify_enabled =3D scmi_telemetry_set_notify_enabled, + .fill_custom_report =3D scmi_telemetry_fill_custom_report, +}; + +static const struct scmi_protocol_events tlm_protocol_events =3D { + .queue_sz =3D SCMI_PROTO_QUEUE_SZ, + .ops =3D &tlm_event_ops, + .evts =3D tlm_events, + .num_events =3D ARRAY_SIZE(tlm_events), + .num_sources =3D 1, +}; + +static void scmi_telemetry_scan_update(struct telemetry_info *ti, u64 ts) +{ + /* Scan all SHMTIs ... */ + for (int id =3D 0; id < ti->num_shmti; id++) + scmi_telemetry_shmti_scan(ti, id, ts, false); + + /* ... then scan all FCs ... XXX Use a list */ + for (int i =3D 0; i < ti->info.num_de; i++) { + struct scmi_telemetry_de *de; + struct telemetry_de *tde; + u64 val, tstamp; + int ret; + + de =3D ti->info.des[i]; + if (!de->enabled) + continue; + + tde =3D to_tde(de); + if (!tde->de.fc_support) + continue; + + //TODO Repoet errors + ret =3D scmi_telemetry_de_data_fc_read(tde, &tstamp, &val); + if (ret) + return; + + guard(mutex)(&tde->mtx); + tde->last_val =3D val; + if (de->tstamp_enabled) + tde->last_ts =3D tstamp; + else + tde->last_ts =3D 0; + } +} + +static int scmi_telemetry_notifier(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct scmi_telemetry_update_report *er =3D data; + struct telemetry_info *ti =3D telemetry_nb_to_info(nb); + + if (er->status) { + dev_err(ti->dev, "Bad Telemetry update notification - ret: %dn", + er->status); + return NOTIFY_DONE; + } + + /* Lookup the embedded DEs in the notification payload ... */ + if (er->num_dwords) + scmi_telemetry_msg_payld_process(ti, er->num_dwords, + er->dwords, er->timestamp); + + /* ...scan the SHMTI/FCs for any other DE updates. */ + if (ti->streaming_mode) + scmi_telemetry_scan_update(ti, er->timestamp); + + return NOTIFY_OK; +} + +static int scmi_telemetry_protocol_init(const struct scmi_protocol_handle = *ph) +{ + struct telemetry_info *ti; + u32 version; + int ret; + + ret =3D ph->xops->version_get(ph, &version); + if (ret) + return ret; + + dev_dbg(ph->dev, "Telemetry Version %d.%d\n", + PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version)); + + ti =3D devm_kzalloc(ph->dev, sizeof(*ti), GFP_KERNEL); + if (!ti) + return -ENOMEM; + + ti->dev =3D ph->dev; + + ret =3D scmi_telemetry_protocol_attributes_get(ph, ti); + if (ret) + return ret; + + ret =3D scmi_telemetry_enumerate_de(ph, ti); + if (ret) + return ret; + + ret =3D scmi_telemetry_enumerate_update_intervals(ph, ti); + if (ret) + return ret; + + ret =3D scmi_telemetry_enumerate_shmti(ph, ti); + if (ret) + return ret; + + ti->info.version =3D version; + + ret =3D 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 =3D &scmi_telemetry_notifier; + ret =3D 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 =3D { + .id =3D SCMI_PROTOCOL_TELEMETRY, + .owner =3D THIS_MODULE, + .instance_init =3D &scmi_telemetry_protocol_init, + .ops =3D &tlm_proto_ops, + .events =3D &tlm_protocol_events, + .supported_version =3D SCMI_PROTOCOL_SUPPORTED_VERSION, +}; + +DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(telemetry, scmi_telemetry) diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h index 6f8d36e1f8fc..f02f8f353d30 100644 --- a/include/linux/scmi_protocol.h +++ b/include/linux/scmi_protocol.h @@ -2,7 +2,7 @@ /* * SCMI Message Protocol driver header * - * Copyright (C) 2018-2021 ARM Ltd. + * Copyright (C) 2018-2025 ARM Ltd. */ =20 #ifndef _LINUX_SCMI_PROTOCOL_H @@ -820,6 +820,192 @@ struct scmi_pinctrl_proto_ops { int (*pin_free)(const struct scmi_protocol_handle *ph, u32 pin); }; =20 +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 =3D 0xef, + SCMI_TLM_DE_TYPE_OEM_START =3D 0xf0, + SCMI_TLM_DE_TYPE_OEM_END =3D 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 =3D 0x1d, + SCMI_TLM_COMPO_TYPE_RESERVED_END =3D 0xdf, + SCMI_TLM_COMPO_TYPE_OEM_START =3D 0xe0, + SCMI_TLM_COMPO_TYPE_OEM_END =3D 0xff, +}; + +struct scmi_telemetry_update_interval { + bool discrete; +#define SCMI_TLM_UPDATE_INTVL_SEGMENT_LOW 0 +#define SCMI_TLM_UPDATE_INTVL_SEGMENT_HIGH 1 +#define SCMI_TLM_UPDATE_INTVL_SEGMENT_STEP 2 + int num; + unsigned int *update_intervals; + unsigned int active_update_interval; +#define SCMI_GET_UPDATE_INTERVAL_SECS(x) \ + (le32_get_bits((x), GENMASK(20, 5))) +#define SCM_IGET_UPDATE_INTERVAL_EXP(x) \ + ({ \ + int __signed_exp =3D FIELD_GET(GENMASK(4, 0), (x)); \ + \ + if (__signed_exp & BIT(4)) \ + __signed_exp |=3D GENMASK(31, 5); \ + __signed_exp; \ + }) +}; + +enum scmi_telemetry_collection { + SCMI_TLM_ONDEMAND, + SCMI_TLM_NOTIFICATION, + SCMI_TLM_SINGLE_READ, +}; + +struct scmi_telemetry_group { +#define SCMI_TLM_GRP_INVALID 0xFFFFFFFF + int id; + bool enabled; + bool tstamp_enabled; + unsigned int num_de; + unsigned int *des; + size_t des_str_sz; + char *des_str; + struct scmi_telemetry_update_interval intervals; + enum scmi_telemetry_collection current_mode; +}; + +//XXX Check what to hide +struct scmi_telemetry_de { + int id; + unsigned int data_sz; + enum scmi_telemetry_de_type type; + int unit; + int unit_exp; + int tstamp_exp; + int instance_id; + int compo_instance_id; + enum scmi_telemetry_compo_type compo_type; + bool persistent; + bool tstamp_support; + bool fc_support; + bool name_support; + char *name; + struct scmi_telemetry_group *grp; + bool enabled; + bool tstamp_enabled; +}; + +//XXX Check what to hide +struct scmi_telemetry_info { + unsigned int version; +#define SCMI_TLM_MAX_DWORD 4 + unsigned int de_impl_version[SCMI_TLM_MAX_DWORD]; + bool single_read_support; + bool continuos_update_support; + bool per_group_config_support; + bool reset_support; + bool fc_support; + int num_de; + struct scmi_telemetry_de **des; + struct scmi_telemetry_update_interval intervals; + int num_groups; + struct scmi_telemetry_group *des_groups; + bool enabled; + bool notif_enabled; + enum scmi_telemetry_collection current_mode; +}; + +struct scmi_telemetry_de_sample { + u32 id; + u64 tstamp; + u64 val; +}; + +/** + * struct scmi_telemetry_proto_ops - represents the various operations pro= vided + * by SCMI Telemetry Protocol + * + * @info_get: get the general Telemetry information. + * @state_get: retrieve the specific DE or GROUP state. + * @state_set: enable/disable the specific DE or GROUP with or without tim= estamps. + * @all_disable: disable ALL DEs or GROUPs. + * @collection_configure: choose a sampling rate and enable SHMTI/FC sampl= ing + * 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 times= tamp: + * 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 ju= st + * the ones belonging to a specific group when provided. + * This causes an immediate update platform-side of all the + * enabled DEs. + * @config_get: retrieve current telemetry configuration. + * @reset: reset configuration and telemetry data. + */ +struct scmi_telemetry_proto_ops { + const struct scmi_telemetry_info __must_check *(*info_get) + (const struct scmi_protocol_handle *ph); + int (*state_get)(const struct scmi_protocol_handle *ph, + u32 id, bool *enabled, bool *tstamp_enabled); + int (*state_set)(const struct scmi_protocol_handle *ph, + bool is_group, u32 id, bool *enable, bool *tstamp); + int (*all_disable)(const struct scmi_protocol_handle *ph, bool group); + int (*collection_configure)(const struct scmi_protocol_handle *ph, + unsigned int res_id, bool grp_ignore, + bool *enable, + unsigned int *update_interval_ms, + enum scmi_telemetry_collection *mode); + int (*de_data_read)(const struct scmi_protocol_handle *ph, + struct scmi_telemetry_de_sample *sample); + int __must_check (*des_bulk_read)(const struct scmi_protocol_handle *ph, + int grp_id, int *num_samples, + struct scmi_telemetry_de_sample *samples); + int __must_check (*des_sample_get)(const struct scmi_protocol_handle *ph, + int grp_id, int *num_samples, + struct scmi_telemetry_de_sample *samples); + int (*config_get)(const struct scmi_protocol_handle *ph, bool *enabled, + int *mode, u32 *update_interval); + int (*reset)(const struct scmi_protocol_handle *ph); +}; + /** * struct scmi_notify_ops - represents notifications' operations provided= by * SCMI core @@ -926,6 +1112,7 @@ enum scmi_std_protocol { SCMI_PROTOCOL_VOLTAGE =3D 0x17, SCMI_PROTOCOL_POWERCAP =3D 0x18, SCMI_PROTOCOL_PINCTRL =3D 0x19, + SCMI_PROTOCOL_TELEMETRY =3D 0x1b, SCMI_PROTOCOL_LAST =3D 0xff, }; =20 @@ -1027,6 +1214,7 @@ enum scmi_notification_events { SCMI_EVENT_SYSTEM_POWER_STATE_NOTIFIER =3D 0x0, SCMI_EVENT_POWERCAP_CAP_CHANGED =3D 0x0, SCMI_EVENT_POWERCAP_MEASUREMENTS_CHANGED =3D 0x1, + SCMI_EVENT_TELEMETRY_UPDATE =3D 0x0, }; =20 struct scmi_power_state_changed_report { @@ -1114,4 +1302,12 @@ struct scmi_powercap_meas_changed_report { unsigned int domain_id; unsigned int power; }; + +struct scmi_telemetry_update_report { + ktime_t timestamp; + unsigned int agent_id; + int status; + unsigned int num_dwords; + unsigned int dwords[]; +}; #endif /* _LINUX_SCMI_PROTOCOL_H */ --=20 2.47.0