From nobody Thu Apr 2 12:10:08 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id ABF283815E6; Sun, 29 Mar 2026 16:34:40 +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=1774802083; cv=none; b=W/l99yeB0/IKekUVh5H8mT+VJrNmwitKK35Xb6haYJUNzxBrOhuxrYZrlSq8coTUBCRPxR4e90ixuCM6bxMeW4uf2CSqwbkHQrcPsb+mGEyTwU8zNtOw+SMxIzK2qKeOhPTviyBkrzkOEFcrP89rfKeUnPv0isTxIfixADMoq1c= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774802083; c=relaxed/simple; bh=pEDrVpTUwr2CuFEboSiYqoTtpczSJOosk06pbX21F3g=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=oTISBXLBWSZo6zRLWvAU4A/4tzP1liLkhWz5QBlZzdKRG/0ST93Fixm5euoobeCq03WICuOMB7s4ZkDZWD3EHnl9r0KkqLWj5+qPoVWOm3VN9+NSnSj3/YVJX3qP1IFjnnLBz1BoEMWyhtAt4DDy37TjYg7uwWB6aBLevEOYaTg= 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; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=UcgJAiKZ; 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 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="UcgJAiKZ" 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 261BB2A31; Sun, 29 Mar 2026 09:34:34 -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 DF3F83F915; Sun, 29 Mar 2026 09:34:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1774802080; bh=pEDrVpTUwr2CuFEboSiYqoTtpczSJOosk06pbX21F3g=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UcgJAiKZvAMAmNHnhSPguDuBNMZZxfuJRyEbr6REnZLZuTo79lUH1rCRXvEU9uSCr thIsYnTEbQ5rB8Hn6VvXBhHY7zH0/TQC0+Vgi9Sb8ioxcM5iKSAed+wAOCd2P4covg 494jmZBwc+MUR093OkuYIphfhTHngmOxqZUFbjsQ= From: Cristian Marussi To: linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, arm-scmi@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-doc@vger.kernel.org Cc: sudeep.holla@kernel.org, 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, dan.carpenter@linaro.org, d-gole@ti.com, jonathan.cameron@huawei.com, elif.topuz@arm.com, lukasz.luba@arm.com, philip.radford@arm.com, brauner@kernel.org, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH v3 08/24] firmware: arm_scmi: Add basic Telemetry support Date: Sun, 29 Mar 2026 17:33:19 +0100 Message-ID: <20260329163337.637393-9-cristian.marussi@arm.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260329163337.637393-1-cristian.marussi@arm.com> References: <20260329163337.637393-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 SCMIv4.0 Telemetry basic support to enable initialization and resources enumeration: add all the telemetry messages definitions and parsing logic but only a few simple state gathering protocol operations. Signed-off-by: Cristian Marussi --- v2 --> v3 - split from monolithic Telemetry patch - fix checkpatch macros complaints - fix ACCESS_PRIVATE usage - add a few comments on allocation/enumeration lifetime - use interval.num_intervals - removed needless cleanup handler usage - simply return from scmi_telemetry_de_lookup() - fixed composing_des name length to 08X --- drivers/firmware/arm_scmi/Makefile | 2 +- drivers/firmware/arm_scmi/driver.c | 2 + drivers/firmware/arm_scmi/protocols.h | 1 + drivers/firmware/arm_scmi/telemetry.c | 1375 +++++++++++++++++++++++++ include/linux/scmi_protocol.h | 135 ++- 5 files changed, 1513 insertions(+), 2 deletions(-) create mode 100644 drivers/firmware/arm_scmi/telemetry.c diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi= /Makefile index 780cd62b2f78..fe55b7aa0707 100644 --- a/drivers/firmware/arm_scmi/Makefile +++ b/drivers/firmware/arm_scmi/Makefile @@ -8,7 +8,7 @@ scmi-driver-$(CONFIG_ARM_SCMI_RAW_MODE_SUPPORT) +=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 c4aefbeead62..a4a2e52e1f3d 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -3508,6 +3508,7 @@ static int __init scmi_driver_init(void) scmi_system_register(); scmi_powercap_register(); scmi_pinctrl_register(); + scmi_telemetry_register(); =20 return platform_driver_register(&scmi_driver); } @@ -3526,6 +3527,7 @@ static void __exit scmi_driver_exit(void) scmi_system_unregister(); scmi_powercap_unregister(); scmi_pinctrl_unregister(); + scmi_telemetry_unregister(); =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 3e7b6f8aa72c..3250d981664b 100644 --- a/drivers/firmware/arm_scmi/protocols.h +++ b/drivers/firmware/arm_scmi/protocols.h @@ -386,5 +386,6 @@ DECLARE_SCMI_REGISTER_UNREGISTER(sensors); DECLARE_SCMI_REGISTER_UNREGISTER(voltage); DECLARE_SCMI_REGISTER_UNREGISTER(system); DECLARE_SCMI_REGISTER_UNREGISTER(powercap); +DECLARE_SCMI_REGISTER_UNREGISTER(telemetry); =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..7e5af7bd9fdc --- /dev/null +++ b/drivers/firmware/arm_scmi/telemetry.c @@ -0,0 +1,1375 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Management Interface (SCMI) Telemetry Protocol + * + * Copyright (C) 2026 ARM Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "protocols.h" +#include "notify.h" + +#include + +/* 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, + TELEMETRY_CONFIG_SET =3D 0x8, + TELEMETRY_READING_COMPLETE =3D TELEMETRY_CONFIG_SET, + TELEMETRY_CONFIG_GET =3D 0x9, + TELEMETRY_RESET =3D 0xA, +}; + +struct scmi_msg_resp_telemetry_protocol_attributes { + __le32 de_num; + __le32 groups_num; + __le32 de_implementation_rev_dword[SCMI_TLM_DE_IMPL_MAX_DWORDS]; + __le32 attributes; +#define SUPPORTS_SINGLE_READ(x) ((x) & BIT(31)) +#define SUPPORTS_CONTINUOS_UPDATE(x) ((x) & BIT(30)) +#define SUPPORTS_PER_GROUP_CONFIG(x) ((x) & BIT(18)) +#define SUPPORTS_RESET(x) ((x) & BIT(17)) +#define SUPPORTS_FC(x) ((x) & BIT(16)) + __le32 default_blk_ts_rate; +}; + +struct scmi_telemetry_update_notify_payld { + __le32 agent_id; + __le32 status; + __le32 num_dwords; + __le32 array[] __counted_by(num_dwords); +}; + +struct scmi_shmti_desc { + __le32 id; + __le32 addr_low; + __le32 addr_high; + __le32 length; + __le32 flags; +}; + +struct scmi_msg_resp_telemetry_shmti_list { + __le32 num_shmti; + struct scmi_shmti_desc desc[] __counted_by(num_shmti); +}; + +struct de_desc_fc { + __le32 addr_low; + __le32 addr_high; + __le32 size; +}; + +struct scmi_de_desc { + __le32 id; + __le32 grp_id; + __le32 data_sz; + __le32 attr_1; +#define IS_NAME_SUPPORTED(d) ((d)->attr_1 & BIT(31)) +#define IS_FC_SUPPORTED(d) ((d)->attr_1 & BIT(30)) +#define GET_DE_TYPE(d) (le32_get_bits((d)->attr_1, GENMASK(29, 22))) +#define IS_PERSISTENT(d) ((d)->attr_1 & BIT(21)) +#define GET_DE_UNIT_EXP(d) \ + ({ \ + __u32 __signed_exp =3D \ + le32_get_bits((d)->attr_1, GENMASK(20, 13)); \ + \ + sign_extend32(__signed_exp, 7); \ + }) +#define GET_DE_UNIT(d) (le32_get_bits((d)->attr_1, GENMASK(12, 5))) +#define TSTAMP_SUPPORT(d) (le32_get_bits((d)->attr_1, GENMASK(1, 0))) + __le32 attr_2; +#define GET_DE_INSTA_ID(d) (le32_get_bits((d)->attr_2, GENMASK(31, 24))) +#define GET_COMPO_INSTA_ID(d) (le32_get_bits((d)->attr_2, GENMASK(23, 8))) +#define GET_COMPO_TYPE(d) (le32_get_bits((d)->attr_2, GENMASK(7, 0))) + __le32 reserved; +}; + +struct scmi_msg_resp_telemetry_de_description { + __le32 num_desc; + struct scmi_de_desc desc[] __counted_by(num_desc); +}; + +struct scmi_msg_telemetry_update_intervals { + __le32 index; + __le32 group_identifier; +#define ALL_DES_NO_GROUP 0x0 +#define SPECIFIC_GROUP_DES 0x1 +#define ALL_DES_ANY_GROUP 0x2 + __le32 flags; +}; + +struct scmi_msg_resp_telemetry_update_intervals { + __le32 flags; +#define INTERVALS_DISCRETE(x) (!((x) & BIT(12))) + __le32 intervals[]; +}; + +struct scmi_msg_telemetry_de_enabled_list { + __le32 index; + __le32 flags; +}; + +struct scmi_enabled_de_desc { + __le32 id; + __le32 mode; +}; + +struct scmi_msg_resp_telemetry_de_enabled_list { + __le32 flags; + struct scmi_enabled_de_desc entry[]; +}; + +struct scmi_msg_telemetry_de_configure { + __le32 id; + __le32 flags; +#define DE_ENABLE_NO_TSTAMP BIT(0) +#define DE_ENABLE_WTH_TSTAMP BIT(1) +#define DE_DISABLE_ALL BIT(2) +#define GROUP_SELECTOR BIT(3) +#define EVENT_DE 0 +#define EVENT_GROUP 1 +#define DE_DISABLE_ONE 0x0 +}; + +struct scmi_msg_resp_telemetry_de_configure { + __le32 shmti_id; +#define IS_SHMTI_ID_VALID(x) ((x) !=3D 0xFFFFFFFF) + __le32 shmti_de_offset; + __le32 blk_ts_offset; +}; + +struct scmi_msg_telemetry_config_set { + __le32 grp_id; + __le32 control; +#define TELEMETRY_ENABLE (BIT(0)) + +#define TELEMETRY_MODE_SET(x) (FIELD_PREP(GENMASK(4, 1), (x))) +#define TLM_ONDEMAND (0) +#define TLM_NOTIFS (1) +#define TLM_SINGLE (2) +#define TELEMETRY_MODE_ONDEMAND TELEMETRY_MODE_SET(TLM_ONDEMAND) +#define TELEMETRY_MODE_NOTIFS TELEMETRY_MODE_SET(TLM_NOTIFS) +#define TELEMETRY_MODE_SINGLE TELEMETRY_MODE_SET(TLM_SINGLE) + +#define TLM_ORPHANS (0) +#define TLM_GROUP (1) +#define TLM_ALL (2) +#define TELEMETRY_SET_SELECTOR(x) (FIELD_PREP(GENMASK(8, 5), (x))) +#define TELEMETRY_SET_SELECTOR_ORPHANS TELEMETRY_SET_SELECTOR(TLM_ORPHANS) +#define TELEMETRY_SET_SELECTOR_GROUP TELEMETRY_SET_SELECTOR(TLM_GROUP) +#define TELEMETRY_SET_SELECTOR_ALL TELEMETRY_SET_SELECTOR(TLM_ALL) + __le32 sampling_rate; +}; + +struct scmi_msg_resp_telemetry_reading_complete { + __le32 num_dwords; + __le32 dwords[] __counted_by(num_dwords); +}; + +struct scmi_msg_telemetry_config_get { + __le32 grp_id; + __le32 flags; +#define TELEMETRY_GET_SELECTOR(x) (FIELD_PREP(GENMASK(3, 0), (x))) +#define TELEMETRY_GET_SELECTOR_ORPHANS TELEMETRY_GET_SELECTOR(TLM_ORPHANS) +#define TELEMETRY_GET_SELECTOR_GROUP TELEMETRY_GET_SELECTOR(TLM_GROUP) +#define TELEMETRY_GET_SELECTOR_ALL TELEMETRY_GET_SELECTOR(TLM_ALL) +}; + +struct scmi_msg_resp_telemetry_config_get { + __le32 control; +#define TELEMETRY_MODE_GET (FIELD_GET(GENMASK(4, 1))) + __le32 sampling_rate; +}; + +/* TDCF */ + +#define _I(__a) (ioread32((void __iomem *)(__a))) + +#define TO_CPU_64(h, l) ((((u64)(h)) << 32) | (l)) + +/* + * Define the behaviour of a SHMTI scan defining what information will + * be gathered and which Telemetry items can be updated. + */ +enum scan_mode { + SCAN_LOOKUP, /* Update only value/tstamp */ + SCAN_UPDATE, /* Update also location offset */ + SCAN_DISCOVERY /* Update xa_des: allows for new DEs to be discovered */ +}; + +struct fc_line { + u32 data_low; + u32 data_high; +}; + +struct fc_tsline { + u32 data_low; + u32 data_high; + u32 ts_low; + u32 ts_high; +}; + +struct line { + u32 data_low; + u32 data_high; +}; + +struct blk_tsline { + u32 ts_low; + u32 ts_high; +}; + +struct tsline { + u32 data_low; + u32 data_high; + u32 ts_low; + u32 ts_high; +}; + +struct uuid_line { + u32 dwords[SCMI_TLM_DE_IMPL_MAX_DWORDS]; +}; + +enum tdcf_line_types { + TDCF_DATA_LINE, + TDCF_BLK_TS_LINE, + TDCF_UUID_LINE, +}; + +struct payload { + u32 meta; +#define LINE_TYPE(x) (le32_get_bits(_I(&((x)->meta)), GENMASK(7, 4))) +#define IS_DATA_LINE(x) (LINE_TYPE(x) =3D=3D TDCF_DATA_LINE) +#define IS_BLK_TS_LINE(x) (LINE_TYPE(x) =3D=3D TDCF_BLK_TS_LINE) +#define IS_UUID_LINE(x) (LINE_TYPE(x) =3D=3D TDCF_UUID_LINE) +#define USE_BLK_TS(x) (_I(&((x)->meta)) & BIT(3)) +#define HAS_LINE_EXT(x) (_I(&((x)->meta)) & BIT(2)) +#define LINE_TS_VALID(x) (_I(&((x)->meta)) & BIT(1)) +#define DATA_INVALID(x) (_I(&((x)->meta)) & BIT(0)) +#define BLK_TS_INVALID(p) \ +({ \ + typeof(p) _p =3D (p); \ + bool invalid; \ + \ + invalid =3D LINE_TS_VALID(_p) || HAS_LINE_EXT(_p) || \ + USE_BLK_TS(_p) || DATA_INVALID(_p); \ + invalid; \ +}) + +#define UUID_INVALID(p) \ +({ \ + typeof(p) _p =3D (p); \ + bool invalid; \ + \ + invalid =3D LINE_TS_VALID(_p) || USE_BLK_TS(_p) || \ + DATA_INVALID(_p) || !HAS_LINE_EXT(_p); \ + invalid; \ +}) + u32 id; + union { + struct line l; + struct tsline tsl; + struct blk_tsline blk_tsl; + struct uuid_line uuid_l; + }; +}; + +#define PAYLD_ID(x) (_I(&(((struct payload *)(x))->id))) + +#define LINE_DATA_PAYLD_WORDS \ + ((sizeof(u32) + sizeof(u32) + sizeof(struct line)) / sizeof(u32)) +#define EXT_LINE_DATA_PAYLD_WORDS \ + ((sizeof(u32) + sizeof(u32) + sizeof(struct tsline)) / sizeof(u32)) + +#define LINE_LENGTH_WORDS(x) \ + (HAS_LINE_EXT((x)) ? EXT_LINE_DATA_PAYLD_WORDS : LINE_DATA_PAYLD_WORDS) + +#define LINE_LENGTH_QWORDS(x) ((LINE_LENGTH_WORDS(x)) / 2) + +struct prlg { + u32 sign_start; +#define SIGNATURE_START 0x5442474E /* TBGN */ + u32 match_start; + u32 num_qwords; + u32 hdr_meta_1; +#define TDCF_REVISION_GET(x) (le32_get_bits((x)->hdr_meta_1, GENMASK(7, 0)= )) +}; + +struct eplg { + u32 match_end; + u32 sign_end; +#define SIGNATURE_END 0x54454E44 /* TEND */ +}; + +#define TDCF_EPLG_SZ (sizeof(struct eplg)) + +struct tdcf { + struct prlg prlg; + unsigned char payld[]; +}; + +#define QWORDS(_t) (_I(&(_t)->prlg.num_qwords)) + +#define SHMTI_MIN_SIZE (sizeof(struct tdcf) + TDCF_EPLG_SZ) + +#define TDCF_START_SIGNATURE(x) (_I(&((x)->prlg.sign_start))) +#define TDCF_START_SEQ_GET(x) (_I(&((x)->prlg.match_start))) +#define IS_BAD_START_SEQ(s) ((s) & 0x1) + +#define TDCF_END_SEQ_GET(e) (_I(&((e)->match_end))) +#define TDCF_END_SIGNATURE(e) (_I(&((e)->sign_end))) +#define TDCF_BAD_END_SEQ GENMASK(31, 0) + +struct telemetry_shmti { + int id; + u32 flags; + void __iomem *base; + u32 len; + u32 last_magic; +}; + +#define SHMTI_EPLG(s) \ + ({ \ + struct telemetry_shmti *_s =3D (s); \ + struct eplg *_eplg; \ + \ + _eplg =3D _s->base + _s->len - TDCF_EPLG_SZ; \ + (_eplg); \ + }) + +struct telemetry_line { + refcount_t users; + u32 last_magic; + struct payload __iomem *payld; + /* Protect line accesses */ + struct mutex mtx; +}; + +struct telemetry_block_ts { + u64 last_ts; + u32 last_rate; + struct telemetry_line line; +}; + +#define to_blkts(l) container_of(l, struct telemetry_block_ts, line) + +struct telemetry_uuid { + u32 de_impl_version[SCMI_TLM_DE_IMPL_MAX_DWORDS]; + struct telemetry_line line; +}; + +#define to_uuid(l) container_of(l, struct telemetry_uuid, line) + +enum timestamps { + TSTAMP_NONE, + TSTAMP_LINE, + TSTAMP_BLK +}; + +struct telemetry_de { + enum timestamps ts_type; + u32 ts_rate; + bool enumerated; + bool cached; + void __iomem *base; + struct eplg __iomem *eplg; + u32 offset; + /* NOTE THAT DE data_sz is registered in scmi_telemetry_de */ + u32 fc_size; + /* Protect last_val/ts/magic accesses */ + struct mutex mtx; + u64 last_val; + u64 last_ts; + u32 last_magic; + struct list_head item; + struct telemetry_block_ts *bts; + struct telemetry_uuid *uuid; + struct scmi_telemetry_de de; +}; + +#define to_tde(d) container_of(d, struct telemetry_de, de) + +#define DE_ENABLED_WITH_TSTAMP 2 + +struct telemetry_info { + bool streaming_mode; + unsigned int num_shmti; + unsigned int default_blk_ts_rate; + const struct scmi_protocol_handle *ph; + struct telemetry_shmti *shmti; + struct telemetry_de *tdes; + struct scmi_telemetry_group *grps; + struct xarray xa_des; + /* Mutex to protect access to @free_des */ + struct mutex free_mtx; + struct list_head free_des; + struct list_head fcs_des; + struct scmi_telemetry_info info; + atomic_t rinfo_initializing; + struct completion rinfo_initdone; + struct scmi_telemetry_res_info __private *rinfo; + struct scmi_telemetry_res_info *(*res_get)(struct telemetry_info *ti); +}; + +static struct scmi_telemetry_res_info * +__scmi_telemetry_resources_get(struct telemetry_info *ti); + +static struct telemetry_de * +scmi_telemetry_free_tde_get(struct telemetry_info *ti) +{ + struct telemetry_de *tde; + + guard(mutex)(&ti->free_mtx); + + tde =3D 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_inf= o *ti, + unsigned int de_id) +{ + struct scmi_telemetry_de *de; + + de =3D 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 =3D scmi_telemetry_free_tde_get(ti); + if (!tde) { + dev_err(ti->ph->dev, "Cannot allocate DE for ID:0x%08X\n", de_id); + return ERR_PTR(-ENOSPC); + } + + return tde; +} + +static int scmi_telemetry_tde_register(struct telemetry_info *ti, + struct telemetry_de *tde) +{ + struct scmi_telemetry_res_info *rinfo =3D ACCESS_PRIVATE(ti, rinfo); + int ret; + + if (rinfo->num_des >=3D ti->info.base.num_des) { + ret =3D -ENOSPC; + goto err; + } + + /* Store DE pointer by de_id ... */ + ret =3D xa_insert(&ti->xa_des, tde->de.info->id, &tde->de, GFP_KERNEL); + if (ret) + goto err; + + /* ... and in the general array */ + rinfo->des[rinfo->num_des++] =3D &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 =3D ti->ph; + struct scmi_xfer *t; + int ret; + + 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.base.num_des =3D le32_to_cpu(resp->de_num); + ti->info.base.num_groups =3D le32_to_cpu(resp->groups_num); + for (int i =3D 0; i < SCMI_TLM_DE_IMPL_MAX_DWORDS; i++) + ti->info.base.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)); + ti->default_blk_ts_rate =3D le32_to_cpu(resp->default_blk_ts_rate); + } + + ph->xops->xfer_put(ph, t); + + return ret; +} + +static void iter_tlm_prepare_message(void *message, + unsigned int desc_index, const void *priv) +{ + put_unaligned_le32(desc_index, message); +} + +static int iter_de_descr_update_state(struct scmi_iterator_state *st, + const void *response, void *priv) +{ + const struct scmi_msg_resp_telemetry_de_description *r =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)); + + if (st->rx_len < (sizeof(*r) + sizeof(r->desc[0]) * st->num_returned)) + return -EINVAL; + + /* Initialized to first descriptor */ + p->next =3D (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 =3D ACCESS_PRIVATE(ti, rinfo); + const struct scmi_de_desc *desc =3D *next; + unsigned int grp_id; + + tde->de.info->id =3D le32_to_cpu(desc->id); + grp_id =3D le32_to_cpu(desc->grp_id); + if (grp_id !=3D SCMI_TLM_GRP_INVALID) { + /* Group descriptors are empty but allocated at this point */ + if (grp_id >=3D ti->info.base.num_groups) + return -EINVAL; + + /* Link to parent group */ + tde->de.info->grp_id =3D grp_id; + tde->de.grp =3D &rinfo->grps[grp_id]; + } + + tde->de.info->data_sz =3D le32_to_cpu(desc->data_sz); + tde->de.info->type =3D GET_DE_TYPE(desc); + tde->de.info->unit =3D GET_DE_UNIT(desc); + tde->de.info->unit_exp =3D GET_DE_UNIT_EXP(desc); + tde->de.info->instance_id =3D GET_DE_INSTA_ID(desc); + tde->de.info->compo_instance_id =3D GET_COMPO_INSTA_ID(desc); + tde->de.info->compo_type =3D GET_COMPO_TYPE(desc); + tde->de.info->persistent =3D IS_PERSISTENT(desc); + tde->ts_type =3D TSTAMP_SUPPORT(desc); + tde->de.tstamp_support =3D !!tde->ts_type; + tde->de.fc_support =3D IS_FC_SUPPORTED(desc); + tde->de.name_support =3D IS_NAME_SUPPORTED(desc); + /* Update DE_DESCRIPTOR size for the next iteration */ + *next +=3D sizeof(*desc); + + if (tde->ts_type =3D=3D TSTAMP_LINE) { + u32 *line_ts_rate =3D *next; + + tde->de.info->ts_rate =3D *line_ts_rate; + + /* Variably sized depending on TS support */ + *next +=3D sizeof(*line_ts_rate); + } else if (tde->ts_type =3D=3D TSTAMP_BLK) { + /* Setup default BLK TS value at first */ + tde->de.info->ts_rate =3D ti->default_blk_ts_rate; + } + + if (tde->de.fc_support) { + u32 size; + u64 phys_addr; + void __iomem *addr; + struct de_desc_fc *dfc; + + dfc =3D *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(ti->ph->dev, phys_addr, size); + if (!addr) + return -EADDRNOTAVAIL; + + tde->base =3D addr; + tde->offset =3D 0; + tde->fc_size =3D size; + + /* Add to FastChannels list */ + list_add(&tde->item, &ti->fcs_des); + + /* Variably sized depending on FC support */ + *next +=3D sizeof(*dfc); + } + + if (tde->de.name_support) { + const char *de_name =3D *next; + + strscpy(tde->de.info->name, de_name, SCMI_SHORT_NAME_MAX_SIZE); + /* Variably sized depending on name support */ + *next +=3D SCMI_SHORT_NAME_MAX_SIZE; + } + + 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 scmi_tlm_de_priv *p =3D priv; + struct telemetry_info *ti =3D p->ti; + const struct scmi_de_desc *desc =3D p->next; + struct telemetry_de *tde; + bool discovered =3D false; + unsigned int de_id; + int ret; + + de_id =3D le32_to_cpu(desc->id); + /* Check if this DE has already been discovered by other means... */ + tde =3D scmi_telemetry_tde_lookup(ti, de_id); + if (!tde) { + /* Create a new one */ + tde =3D scmi_telemetry_tde_get(ti, de_id); + if (IS_ERR(tde)) + return PTR_ERR(tde); + + discovered =3D 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 =3D scmi_telemetry_de_descriptor_parse(ti, tde, &p->next); + if (ret) + goto err; + + if (discovered) { + /* Register if it was not already ... */ + ret =3D scmi_telemetry_tde_register(ti, tde); + if (ret) + goto err; + + tde->enumerated =3D true; + } + + /* Account for this DE in group num_de counter */ + if (tde->de.grp) + tde->de.grp->info->num_des++; + + return 0; + +err: + /* DE not enumerated at this point were created in this call */ + if (!tde->enumerated) + scmi_telemetry_free_tde_put(ti, tde); + + return ret; +} + +static int +scmi_telemetry_de_groups_init(struct device *dev, struct telemetry_info *t= i) +{ + struct scmi_telemetry_res_info *rinfo =3D ACCESS_PRIVATE(ti, rinfo); + + /* Allocate all groups DEs IDs arrays at first ... */ + for (int i =3D 0; i < ti->info.base.num_groups; i++) { + struct scmi_telemetry_group *grp =3D &rinfo->grps[i]; + size_t des_str_sz; + + unsigned int *des __free(kfree) =3D 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' =3D 11 bytes per number + * - terminating NUL character + */ + des_str_sz =3D grp->info->num_des * 11 + 1; + char *des_str __free(kfree) =3D kzalloc(des_str_sz, GFP_KERNEL); + if (!des_str) + return -ENOMEM; + + grp->des =3D no_free_ptr(des); + grp->des_str =3D no_free_ptr(des_str); + /* Reset group DE counter */ + grp->info->num_des =3D 0; + } + + /* Scan DEs and populate DE IDs arrays for all groups */ + for (int i =3D 0; i < rinfo->num_des; i++) { + struct scmi_telemetry_group *grp =3D 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++] =3D i; + } + + /* Build composing DES string */ + for (int i =3D 0; i < ti->info.base.num_groups; i++) { + struct scmi_telemetry_group *grp =3D &rinfo->grps[i]; + size_t bufsize =3D grp->info->num_des * 11 + 1; + char *buf =3D grp->des_str; + + for (int j =3D 0; j < grp->info->num_des; j++) { + char term =3D j !=3D (grp->info->num_des - 1) ? ' ' : '\0'; + int len; + + len =3D scnprintf(buf, bufsize, "0x%08X%c", + rinfo->des[grp->des[j]]->info->id, term); + + buf +=3D len; + bufsize -=3D len; + } + } + + rinfo->num_groups =3D ti->info.base.num_groups; + + return 0; +} + +static int scmi_telemetry_de_descriptors_get(struct telemetry_info *ti) +{ + const struct scmi_protocol_handle *ph =3D ti->ph; + + 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; + + if (!ti->info.base.num_des) + return 0; + + iter =3D 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 =3D 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 =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)); + + if (st->rx_len < (sizeof(*r) + sizeof(r->intervals[0]) * st->num_returned= )) + return -EINVAL; + + /* + * total intervals is not declared previously anywhere so we + * assume it's returned+remaining on first call. + */ + if (!st->max_resources) { + struct scmi_tlm_ivl_priv *p =3D priv; + struct scmi_tlm_intervals *intrvs; + bool discrete; + int inum; + + discrete =3D INTERVALS_DISCRETE(r->flags); + /* Check consistency on first call */ + if (!discrete && (st->num_returned !=3D 3 || st->num_remaining !=3D 0)) + return -EINVAL; + + inum =3D st->num_returned + st->num_remaining; + intrvs =3D kzalloc(sizeof(*intrvs) + inum * sizeof(__u32), GFP_KERNEL); + if (!intrvs) + return -ENOMEM; + + intrvs->num_intervals =3D inum; + intrvs->discrete =3D discrete; + st->max_resources =3D intrvs->num_intervals; + + *p->intrvs =3D 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 =3D response; + struct scmi_tlm_ivl_priv *p =3D priv; + struct scmi_tlm_intervals *intrvs =3D *p->intrvs; + unsigned int idx =3D st->loop_idx; + + intrvs->update_intervals[st->desc_index + idx] =3D 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 =3D { + .prepare_message =3D iter_intervals_prepare_message, + .update_state =3D iter_intervals_update_state, + .process_response =3D iter_intervals_process_response, + }; + const struct scmi_protocol_handle *ph =3D ti->ph; + struct scmi_tlm_ivl_priv ipriv =3D { + .dev =3D ph->dev, + .grp_id =3D grp_id, + .intrvs =3D 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_groups_intervals(struct telemetry_info *ti) +{ + struct scmi_telemetry_res_info *rinfo =3D ACCESS_PRIVATE(ti, rinfo); + + if (!ti->info.per_group_config_support) + return 0; + + for (int id =3D 0; id < rinfo->num_groups; id++) { + int ret; + + ret =3D scmi_tlm_enumerate_update_intervals(ti, + &rinfo->grps[id].intervals, + id, SPECIFIC_GROUP_DES); + if (ret) + return ret; + + rinfo->grps_store[id].num_intervals =3D + rinfo->grps[id].intervals->num_intervals; + } + + return 0; +} + +static void scmi_telemetry_intervals_free(void *interval) +{ + kfree(interval); +} + +static int +scmi_telemetry_enumerate_common_intervals(struct telemetry_info *ti) +{ + unsigned int flags; + int ret; + + flags =3D !ti->info.per_group_config_support ? + ALL_DES_ANY_GROUP : ALL_DES_NO_GROUP; + + ret =3D 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 =3D ti->info.intervals->num_intervals; + + /* Delegate freeing of allocated intervals to unbind time */ + return devm_add_action_or_reset(ti->ph->dev, + scmi_telemetry_intervals_free, + ti->info.intervals); +} + +static int iter_shmti_update_state(struct scmi_iterator_state *st, + const void *response, void *priv) +{ + const struct scmi_msg_resp_telemetry_shmti_list *r =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)); + + if (st->rx_len < (sizeof(*r) + sizeof(r->desc[0]) * st->num_returned)) + return -EINVAL; + + return 0; +} + +static inline int +scmi_telemetry_shmti_validate(struct device *dev, struct telemetry_shmti *= shmti) +{ + struct tdcf __iomem *tdcf =3D shmti->base; + u32 sign_start, sign_end; + + sign_start =3D TDCF_START_SIGNATURE(tdcf); + sign_end =3D TDCF_END_SIGNATURE(SHMTI_EPLG(shmti)); + + if (sign_start !=3D SIGNATURE_START || sign_end !=3D SIGNATURE_END) { + dev_err(dev, + "BAD signature for SHMTI ID:%u @phys:%pK - START:0x%04X END:0x%04X\n", + shmti->id, shmti->base, sign_start, sign_end); + return -EINVAL; + } + + return 0; +} + +static int iter_shmti_process_response(const struct scmi_protocol_handle *= ph, + const void *response, + struct scmi_iterator_state *st, + void *priv) +{ + const struct scmi_msg_resp_telemetry_shmti_list *r =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); + shmti->flags =3D le32_to_cpu(desc->flags); + 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); + if (len < SHMTI_MIN_SIZE) { + dev_err(ph->dev, "Invalid length for SHMTI ID:%u len:%u\n", + shmti->id, len); + return -EINVAL; + } + + addr =3D devm_ioremap(ph->dev, phys_addr, len); + if (!addr) + return -EADDRNOTAVAIL; + + shmti->base =3D addr; + shmti->len =3D len; + + return scmi_telemetry_shmti_validate(ph->dev, shmti); +} + +static int scmi_telemetry_shmti_list(const struct scmi_protocol_handle *ph, + struct telemetry_info *ti) +{ + struct scmi_iterator_ops ops =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.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 =3D ti->ph; + 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 const struct scmi_telemetry_de * +scmi_telemetry_de_lookup(const struct scmi_protocol_handle *ph, u32 id) +{ + struct telemetry_info *ti =3D ph->get_priv(ph); + + ti->res_get(ti); + return xa_load(&ti->xa_des, id); +} + +static const struct scmi_telemetry_res_info * +scmi_telemetry_resources_get(const struct scmi_protocol_handle *ph) +{ + struct telemetry_info *ti =3D ph->get_priv(ph); + + return ti->res_get(ti); +} + +static const struct scmi_telemetry_proto_ops tlm_proto_ops =3D { + .info_get =3D scmi_telemetry_info_get, + .de_lookup =3D scmi_telemetry_de_lookup, + .res_get =3D scmi_telemetry_resources_get, +}; + +/** + * scmi_telemetry_resources_alloc - Resources allocation + * @ti: A reference to the telemetry info descriptor for this instance + * + * This allocates and initializes dedicated resources for the maximum poss= ible + * number of needed telemetry resources, based on information gathered from + * the initial enumeration: these allocations represent an upper bound on + * the number of discoverable telemetry resources and they will be later + * populated during late deferred further discovery phases. + * + * Return: 0 on Success, errno otherwise + */ +static int scmi_telemetry_resources_alloc(struct telemetry_info *ti) +{ + /* Array to hold pointers to discovered DEs */ + struct scmi_telemetry_de **des __free(kfree) =3D + kcalloc(ti->info.base.num_des, sizeof(*des), GFP_KERNEL); + if (!des) + return -ENOMEM; + + /* The allocated DE descriptors */ + struct telemetry_de *tdes __free(kfree) =3D + 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) =3D + 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) =3D + 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) =3D + kcalloc(ti->info.base.num_groups, sizeof(*grps_store), GFP_KERNEL); + if (!grps_store) + return -ENOMEM; + + struct scmi_telemetry_res_info *rinfo __free(kfree) =3D + kzalloc(sizeof(*rinfo), GFP_KERNEL); + if (!rinfo) + return -ENOMEM; + + mutex_init(&ti->free_mtx); + INIT_LIST_HEAD(&ti->free_des); + for (int i =3D 0; i < ti->info.base.num_des; i++) { + mutex_init(&tdes[i].mtx); + /* Bind contiguous DE info structures */ + tdes[i].de.info =3D &dei_store[i]; + list_add_tail(&tdes[i].item, &ti->free_des); + } + + for (int i =3D 0; i < ti->info.base.num_groups; i++) { + grps_store[i].id =3D i; + /* Bind contiguous Group info struct */ + grps[i].info =3D &grps_store[i]; + } + + INIT_LIST_HEAD(&ti->fcs_des); + + ti->tdes =3D no_free_ptr(tdes); + + rinfo->des =3D no_free_ptr(des); + rinfo->dei_store =3D no_free_ptr(dei_store); + rinfo->grps =3D no_free_ptr(grps); + rinfo->grps_store =3D no_free_ptr(grps_store); + + ACCESS_PRIVATE(ti, rinfo) =3D no_free_ptr(rinfo); + + return 0; +} + +static void scmi_telemetry_resources_free(void *arg) +{ + struct telemetry_info *ti =3D arg; + struct scmi_telemetry_res_info *rinfo =3D ACCESS_PRIVATE(ti, rinfo); + + kfree(ti->tdes); + kfree(rinfo->des); + kfree(rinfo->dei_store); + kfree(rinfo->grps); + kfree(rinfo->grps_store); + + kfree(rinfo); + + ACCESS_PRIVATE(ti, rinfo) =3D NULL; +} + +static struct scmi_telemetry_res_info * +__scmi_telemetry_resources_get(struct telemetry_info *ti) +{ + return ACCESS_PRIVATE(ti, rinfo); +} + +/** + * scmi_telemetry_resources_enumerate - Enumeration helper + * @ti: A reference to the telemetry info descriptor for this instance + * + * This helper is configured to be called once on the first enumeration + * attempt, when triggered by invoking ti->res_get() from somewhere else. + * Once run it substitues itself in ti->res_get() with the simple accessor + * __scmi_telemetry_resources_get, which returns a descriptor to the resou= rces + * that were possibly discovered. + * + * Note that, while it attempts to fully enumerate Data Events and Groups,= it + * does NOT fail when such enumerations fail, instead it simply gives up w= ith + * the end result that only a partially populated, but consistent, resourc= es + * descriptor will be returned; in such a case the incomplete descriptor w= ill + * be marked as NOT fully_enumerated: this design enables the kernel to de= al + * with badly implemented out-of-spec firmware support while keep on provi= ding + * a minimal sane, albeit possibly incomplete, set of telemetry respources. + * + * Return: A reference to a fully or partially populated resources descrip= tor + */ +static struct scmi_telemetry_res_info * +scmi_telemetry_resources_enumerate(struct telemetry_info *ti) +{ + struct scmi_telemetry_res_info *rinfo =3D ACCESS_PRIVATE(ti, rinfo); + struct device *dev =3D 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 =3D scmi_telemetry_de_descriptors_get(ti); + if (ret) { + dev_err(dev, FW_BUG "Cannot enumerate DEs resources. Carry-on.\n"); + goto done; + } + + ret =3D scmi_telemetry_enumerate_groups_intervals(ti); + if (ret) { + dev_err(dev, FW_BUG "Cannot enumerate group intervals. Carry-on.\n"); + goto done; + } + + /* If we got here, the enumeration was fully successful */ + rinfo->fully_enumerated =3D true; +done: + /* Disable initialization permanently */ + smp_store_mb(ti->res_get, __scmi_telemetry_resources_get); + complete_all(&ti->rinfo_initdone); + +out: + return rinfo; +} + +/** + * scmi_telemetry_instance_init - Instance initializer + * @ti: A reference to the telemetry info descriptor for this instance + * + * Note that this allocates and initialize all the resources possibly need= ed + * and then setups the @scmi_telemetry_resources_enumerate helper as the + * default method for the first call to ti->res_get(): this mechanism enab= les + * the possibility of optionally implementing deferred enumeration policies + * which optionally delay the discovery phase and related SCMI message exc= hanges + * to a later point in time. + * + * Return: 0 on Success, errno otherwise + */ +static int scmi_telemetry_instance_init(struct telemetry_info *ti) +{ + int ret; + + /* Allocate and Initialize on first call... */ + ret =3D scmi_telemetry_resources_alloc(ti); + if (ret) + return ret; + + ret =3D 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 =3D ph->dev; + struct telemetry_info *ti; + int ret; + + dev_dbg(dev, "Telemetry Version %d.%d\n", + PROTOCOL_REV_MAJOR(ph->version), PROTOCOL_REV_MINOR(ph->version)); + + ti =3D devm_kzalloc(dev, sizeof(*ti), GFP_KERNEL); + if (!ti) + return -ENOMEM; + + ti->ph =3D ph; + + ret =3D scmi_telemetry_protocol_attributes_get(ti); + if (ret) { + dev_err(dev, FW_BUG "Cannot retrieve protocol attributes. Abort.\n"); + return ret; + } + + ret =3D scmi_telemetry_instance_init(ti); + if (ret) { + dev_err(dev, "Cannot initialize instance. Abort.\n"); + return ret; + } + + ret =3D scmi_telemetry_enumerate_common_intervals(ti); + if (ret) + dev_warn(dev, FW_BUG "Cannot enumerate update intervals. Carry-on.\n"); + + ret =3D scmi_telemetry_enumerate_shmti(ti); + if (ret) { + dev_err(dev, FW_BUG "Cannot enumerate SHMTIs. Abort.\n"); + return ret; + } + + ti->info.base.version =3D ph->version; + + return ph->set_priv(ph, ti); +} + +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, + .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 aafaac1496b0..fcb45bd4b44c 100644 --- a/include/linux/scmi_protocol.h +++ b/include/linux/scmi_protocol.h @@ -2,17 +2,21 @@ /* * SCMI Message Protocol driver header * - * Copyright (C) 2018-2021 ARM Ltd. + * Copyright (C) 2018-2026 ARM Ltd. */ =20 #ifndef _LINUX_SCMI_PROTOCOL_H #define _LINUX_SCMI_PROTOCOL_H =20 #include +#include #include #include #include =20 +#include +#include + #define SCMI_MAX_STR_SIZE 64 #define SCMI_SHORT_NAME_MAX_SIZE 16 #define SCMI_MAX_NUM_RATES 16 @@ -820,6 +824,134 @@ struct scmi_pinctrl_proto_ops { int (*pin_free)(const struct scmi_protocol_handle *ph, u32 pin); }; =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, +}; + +#define SCMI_TLM_GET_UPDATE_INTERVAL_SECS(x) \ + (le32_get_bits((x), GENMASK(20, 5))) +#define SCMI_TLM_GET_UPDATE_INTERVAL_EXP(x) (sign_extend32((x), 4)) + +#define SCMI_TLM_GET_UPDATE_INTERVAL(x) (FIELD_GET(GENMASK(20, 0), (x))) +#define SCMI_TLM_BUILD_UPDATE_INTERVAL(s, e) \ + (FIELD_PREP(GENMASK(20, 5), (s)) | FIELD_PREP(GENMASK(4, 0), (e))) + +enum scmi_telemetry_collection { + SCMI_TLM_ONDEMAND, + SCMI_TLM_NOTIFICATION, + SCMI_TLM_SINGLE_READ, +}; + +#define SCMI_TLM_GRP_INVALID 0xFFFFFFFF +struct scmi_telemetry_group { + bool enabled; + bool tstamp_enabled; + unsigned int *des; + char *des_str; + struct scmi_tlm_grp_info *info; + unsigned int active_update_interval; + struct scmi_tlm_intervals *intervals; + enum scmi_telemetry_collection current_mode; +}; + +struct scmi_telemetry_de { + bool tstamp_support; + bool fc_support; + bool name_support; + struct scmi_tlm_de_info *info; + struct scmi_telemetry_group *grp; + bool enabled; + bool tstamp_enabled; +}; + +struct scmi_telemetry_res_info { + bool fully_enumerated; + unsigned int num_des; + struct scmi_telemetry_de **des; + struct scmi_tlm_de_info *dei_store; + unsigned int num_groups; + struct scmi_telemetry_group *grps; + struct scmi_tlm_grp_info *grps_store; +}; + +struct scmi_telemetry_info { + bool single_read_support; + bool continuos_update_support; + bool per_group_config_support; + bool reset_support; + bool fc_support; + struct scmi_tlm_base_info base; + unsigned int active_update_interval; + struct scmi_tlm_intervals *intervals; + bool enabled; + bool notif_enabled; + enum scmi_telemetry_collection current_mode; +}; + +/** + * struct scmi_telemetry_proto_ops - represents the various operations pro= vided + * by SCMI Telemetry Protocol + * + * @info_get: get the general Telemetry information. + * @de_lookup: get a specific DE descriptor from the DE id. + * @res_get: get a reference to the Telemetry resources descriptor. + */ +struct scmi_telemetry_proto_ops { + const struct scmi_telemetry_info __must_check *(*info_get) + (const struct scmi_protocol_handle *ph); + const struct scmi_telemetry_de __must_check *(*de_lookup) + (const struct scmi_protocol_handle *ph, u32 id); + const struct scmi_telemetry_res_info __must_check *(*res_get) + (const struct scmi_protocol_handle *ph); +}; + /** * struct scmi_notify_ops - represents notifications' operations provided= by * SCMI core @@ -926,6 +1058,7 @@ enum scmi_std_protocol { SCMI_PROTOCOL_VOLTAGE =3D 0x17, SCMI_PROTOCOL_POWERCAP =3D 0x18, SCMI_PROTOCOL_PINCTRL =3D 0x19, + SCMI_PROTOCOL_TELEMETRY =3D 0x1b, }; =20 enum scmi_system_events { --=20 2.53.0