From nobody Mon Feb 9 01:43:04 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 4A8C9396B7B; Wed, 14 Jan 2026 11:47:31 +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=1768391253; cv=none; b=JVaVX4vigzY6l4KvPtv9wr2YByF893W9/4ptVEXR4kePaQqnBBlpuKgklSwnf9k9ggqkJEcsNdwjRZKFtz/teyHc66QDKAeJ6YYVe0D5H++qC2e11rPzdjsAUzs+/1rIhGdpA44ZIfAW9QWvVyKsxSN8jy8SHcid9pXkO31Q3mY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768391253; c=relaxed/simple; bh=/76nAyObaQ5FpN+crLyZke6FddrHAlBATqCNYda0n8k=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=jj8KQtIWpINa7UX8tsQPXVqGObBZjnVhAZgvja7i5idUsiZR0Rq/GCzbGYqOmgXRuIiAeS2Nm4a/i/trCfGBtHeJNY10MpRhQSWEy+daxHbgOya2zrmIDBWy81WqBpZC8V+85BrOEEvJHXGfcZ2YwFEvqKq41J1fJY8tFsUtAHI= 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 222DE497; Wed, 14 Jan 2026 03:47:18 -0800 (PST) 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 56B503F59E; Wed, 14 Jan 2026 03:47:21 -0800 (PST) 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 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, dan.carpenter@linaro.org, d-gole@ti.com, jonathan.cameron@huawei.com, elif.topuz@arm.com, lukasz.luba@arm.com, philip.radford@arm.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH v2 01/17] firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value Date: Wed, 14 Jan 2026 11:46:05 +0000 Message-ID: <20260114114638.2290765-2-cristian.marussi@arm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260114114638.2290765-1-cristian.marussi@arm.com> References: <20260114114638.2290765-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 a common definition of SCMI_MAX_PROTOCOLS and use it all over the SCMI stack. Signed-off-by: Cristian Marussi --- drivers/firmware/arm_scmi/notify.c | 4 +--- include/linux/scmi_protocol.h | 3 +++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/firmware/arm_scmi/notify.c b/drivers/firmware/arm_scmi= /notify.c index dee9f238f6fd..78e9e27dc9ec 100644 --- a/drivers/firmware/arm_scmi/notify.c +++ b/drivers/firmware/arm_scmi/notify.c @@ -94,8 +94,6 @@ #include "common.h" #include "notify.h" =20 -#define SCMI_MAX_PROTO 256 - #define PROTO_ID_MASK GENMASK(31, 24) #define EVT_ID_MASK GENMASK(23, 16) #define SRC_ID_MASK GENMASK(15, 0) @@ -1673,7 +1671,7 @@ int scmi_notification_init(struct scmi_handle *handle) ni->gid =3D gid; ni->handle =3D handle; =20 - ni->registered_protocols =3D devm_kcalloc(handle->dev, SCMI_MAX_PROTO, + ni->registered_protocols =3D devm_kcalloc(handle->dev, SCMI_MAX_PROTOCOLS, sizeof(char *), GFP_KERNEL); if (!ni->registered_protocols) goto err; diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h index aafaac1496b0..c6efe4f371ac 100644 --- a/include/linux/scmi_protocol.h +++ b/include/linux/scmi_protocol.h @@ -926,8 +926,11 @@ enum scmi_std_protocol { SCMI_PROTOCOL_VOLTAGE =3D 0x17, SCMI_PROTOCOL_POWERCAP =3D 0x18, SCMI_PROTOCOL_PINCTRL =3D 0x19, + SCMI_PROTOCOL_LAST =3D 0x7f, }; =20 +#define SCMI_MAX_PROTOCOLS 256 + enum scmi_system_events { SCMI_SYSTEM_SHUTDOWN, SCMI_SYSTEM_COLDRESET, --=20 2.52.0 From nobody Mon Feb 9 01:43:04 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 3D16E396B63; Wed, 14 Jan 2026 11:47:29 +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=1768391251; cv=none; b=FDOlGnbTzSKYAkNtgyq2RyA0W5Ruh/Rb0pGG7yYJ0PcR5OdTM6KCL6B/j0dHbk8CEbF49MNqwsLknawhpiivGQGGGN/MbLr8Oex/f1nBZdgLIpgb/FEWpbTAkfNKjM5FKuqUfRuiS3Jb2yEokXr6ZXSHD9W5gClnOUEmuRuH2Ak= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768391251; c=relaxed/simple; bh=uV9vtkOJEybjNf/MnA9A4HVD1UKZz+XltZIp8k2sA3Y=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=rrfFzMHmeJcIgvbGsbYT8JGsASHG5tUu2OH0RkbCpXuB9GOBrQ5j1XQVC5dOHf19TSiS4gqEi4KTboxKNBB5Nxs+Gexk8PLKfuugExesHwKrrrVYA975oo8qpKNYvxxBON7+ixVXaLmWgceuzTGexmvNJ7C4LI3Xwybsbb3eGZw= 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 001D11424; Wed, 14 Jan 2026 03:47:22 -0800 (PST) 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 340A13F59E; Wed, 14 Jan 2026 03:47:25 -0800 (PST) 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 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, dan.carpenter@linaro.org, d-gole@ti.com, jonathan.cameron@huawei.com, elif.topuz@arm.com, lukasz.luba@arm.com, philip.radford@arm.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH v2 02/17] firmware: arm_scmi: Reduce the scope of protocols mutex Date: Wed, 14 Jan 2026 11:46:06 +0000 Message-ID: <20260114114638.2290765-3-cristian.marussi@arm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260114114638.2290765-1-cristian.marussi@arm.com> References: <20260114114638.2290765-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" Currently the mutex dedicated to the protection of the list of registered protocols is held during all the protocol initialization phase. Such a wide locking region is not needed and causes problem when trying to initialize notifications from within a protocol initialization routine. Reduce the scope of the protocol mutex. Signed-off-by: Cristian Marussi Reviewed-by: Jonathan Cameron --- v1-->v2 - Fixed improper mixed usage of cleanup and goto constructs --- drivers/firmware/arm_scmi/driver.c | 50 ++++++++++++++---------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi= /driver.c index 7e5429eff35d..b198c58da1dd 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -17,6 +17,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt =20 #include +#include #include #include #include @@ -2123,7 +2124,6 @@ static int scmi_protocol_version_negotiate(struct scm= i_protocol_handle *ph) * all resources management is handled via a dedicated per-protocol devres * group. * - * Context: Assumes to be called with @protocols_mtx already acquired. * Return: A reference to a freshly allocated and initialized protocol ins= tance * or ERR_PTR on failure. On failure the @proto reference is at first * put using @scmi_protocol_put() before releasing all the devres group. @@ -2162,8 +2162,10 @@ scmi_alloc_init_protocol_instance(struct scmi_info *= info, if (ret) goto clean; =20 - ret =3D idr_alloc(&info->protocols, pi, proto->id, proto->id + 1, - GFP_KERNEL); + /* Finally register the initialized protocol */ + mutex_lock(&info->protocols_mtx); + ret =3D idr_alloc(&info->protocols, pi, proto->id, proto->id + 1, GFP_KER= NEL); + mutex_unlock(&info->protocols_mtx); if (ret !=3D proto->id) goto clean; =20 @@ -2226,27 +2228,25 @@ scmi_alloc_init_protocol_instance(struct scmi_info = *info, static struct scmi_protocol_instance * __must_check scmi_get_protocol_instance(const struct scmi_handle *handle, u8 protocol_i= d) { - struct scmi_protocol_instance *pi; struct scmi_info *info =3D handle_to_scmi_info(handle); + const struct scmi_protocol *proto; =20 - mutex_lock(&info->protocols_mtx); - pi =3D idr_find(&info->protocols, protocol_id); - - if (pi) { - refcount_inc(&pi->users); - } else { - const struct scmi_protocol *proto; + scoped_guard(mutex, &info->protocols_mtx) { + struct scmi_protocol_instance *pi; =20 - /* Fails if protocol not registered on bus */ - proto =3D scmi_protocol_get(protocol_id, &info->version); - if (proto) - pi =3D scmi_alloc_init_protocol_instance(info, proto); - else - pi =3D ERR_PTR(-EPROBE_DEFER); + pi =3D idr_find(&info->protocols, protocol_id); + if (pi) { + refcount_inc(&pi->users); + return pi; + } } - mutex_unlock(&info->protocols_mtx); =20 - return pi; + /* Fails if protocol not registered on bus */ + proto =3D scmi_protocol_get(protocol_id, &info->version); + if (!proto) + return ERR_PTR(-EPROBE_DEFER); + + return scmi_alloc_init_protocol_instance(info, proto); } =20 /** @@ -2277,10 +2277,11 @@ void scmi_protocol_release(const struct scmi_handle= *handle, u8 protocol_id) struct scmi_info *info =3D handle_to_scmi_info(handle); struct scmi_protocol_instance *pi; =20 - mutex_lock(&info->protocols_mtx); - pi =3D idr_find(&info->protocols, protocol_id); - if (WARN_ON(!pi)) - goto out; + scoped_guard(mutex, &info->protocols_mtx) { + pi =3D idr_find(&info->protocols, protocol_id); + if (WARN_ON(!pi)) + return; + } =20 if (refcount_dec_and_test(&pi->users)) { void *gid =3D pi->gid; @@ -2299,9 +2300,6 @@ void scmi_protocol_release(const struct scmi_handle *= handle, u8 protocol_id) dev_dbg(handle->dev, "De-Initialized protocol: 0x%X\n", protocol_id); } - -out: - mutex_unlock(&info->protocols_mtx); } =20 void scmi_setup_protocol_implemented(const struct scmi_protocol_handle *ph, --=20 2.52.0 From nobody Mon Feb 9 01:43:04 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 144C0396D0B; Wed, 14 Jan 2026 11:47:32 +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=1768391254; cv=none; b=W3bv9fkHqQHNZrSsdisVSDz0KjT4C1o6zEg1oiX8bZDpt5TWcGB4PZkP9Zh09of2jMXYGE0js5sAsSTwBLw9bamRoQScSCW5OZJ41+ydmhKTz1/IRnmNlTF/yEir/Mrql0eEIg6f9/Yh/DviSPMwPL8++1b3XG7SfMZrE74WOhU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768391254; c=relaxed/simple; bh=S9I3UhAClWcqsGV6B1ZKFE6EF344tHX0qpPTnzW7EQM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Kc2L6324FPE940TTXemMzg0HGTwykgyGjAOjCZXa75DqY5dWITTMuEVpvkgWjGn9Y59Kr1Y+yJubbVggTDoyS8rdS4j/PHJhuwDFfBoaK6m4G8NvcW+2F+bpyk3tqylC0YCjAqQ3W3ftaldMo49W2etGWtPKsHDn1mKA3sPkAHs= 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 D22D8339; Wed, 14 Jan 2026 03:47:25 -0800 (PST) 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 134D23F59E; Wed, 14 Jan 2026 03:47:28 -0800 (PST) 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 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, dan.carpenter@linaro.org, d-gole@ti.com, jonathan.cameron@huawei.com, elif.topuz@arm.com, lukasz.luba@arm.com, philip.radford@arm.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH v2 03/17] firmware: arm_scmi: Allow protocols to register for notifications Date: Wed, 14 Jan 2026 11:46:07 +0000 Message-ID: <20260114114638.2290765-4-cristian.marussi@arm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260114114638.2290765-1-cristian.marussi@arm.com> References: <20260114114638.2290765-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" Allow protocols themselves to register for their own notifications and providing their own notifier callbacks. While at that, allow for a protocol to register events with compilation-time unknown report/event sizes: such events will use the maximum transport size. Signed-off-by: Cristian Marussi --- v1-->v2 - Fixed multiline comment format --- drivers/firmware/arm_scmi/common.h | 4 ++++ drivers/firmware/arm_scmi/driver.c | 12 ++++++++++++ drivers/firmware/arm_scmi/notify.c | 28 ++++++++++++++++++++------- drivers/firmware/arm_scmi/notify.h | 8 ++++++-- drivers/firmware/arm_scmi/protocols.h | 6 ++++++ 5 files changed, 49 insertions(+), 9 deletions(-) diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi= /common.h index 3b24831094b6..9f9a5a4bcf35 100644 --- a/drivers/firmware/arm_scmi/common.h +++ b/drivers/firmware/arm_scmi/common.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -527,5 +528,8 @@ static struct platform_driver __drv =3D { \ void scmi_notification_instance_data_set(const struct scmi_handle *handle, void *priv); void *scmi_notification_instance_data_get(const struct scmi_handle *handle= ); +int scmi_notifier_register(const struct scmi_handle *handle, u8 proto_id, + u8 evt_id, const u32 *src_id, + struct notifier_block *nb); int scmi_inflight_count(const struct scmi_handle *handle); #endif /* _SCMI_COMMON_H */ diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi= /driver.c index b198c58da1dd..1085c70ca457 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -1657,6 +1657,17 @@ static void *scmi_get_protocol_priv(const struct scm= i_protocol_handle *ph) return pi->priv; } =20 +static int +scmi_register_instance_notifier(const struct scmi_protocol_handle *ph, + u8 evt_id, const u32 *src_id, + struct notifier_block *nb) +{ + const struct scmi_protocol_instance *pi =3D ph_to_pi(ph); + + return scmi_notifier_register(pi->handle, pi->proto->id, + evt_id, src_id, nb); +} + static const struct scmi_xfer_ops xfer_ops =3D { .version_get =3D version_get, .xfer_get_init =3D xfer_get_init, @@ -2156,6 +2167,7 @@ scmi_alloc_init_protocol_instance(struct scmi_info *i= nfo, pi->ph.hops =3D &helpers_ops; pi->ph.set_priv =3D scmi_set_protocol_priv; pi->ph.get_priv =3D scmi_get_protocol_priv; + pi->ph.notifier_register =3D scmi_register_instance_notifier; refcount_set(&pi->users, 1); /* proto->init is assured NON NULL by scmi_protocol_register */ ret =3D pi->proto->instance_init(&pi->ph); diff --git a/drivers/firmware/arm_scmi/notify.c b/drivers/firmware/arm_scmi= /notify.c index 78e9e27dc9ec..e84b4dbefe82 100644 --- a/drivers/firmware/arm_scmi/notify.c +++ b/drivers/firmware/arm_scmi/notify.c @@ -593,7 +593,13 @@ int scmi_notify(const struct scmi_handle *handle, u8 p= roto_id, u8 evt_id, if (!r_evt) return -EINVAL; =20 - if (len > r_evt->evt->max_payld_sz) { + /* + * Events with a zero max_payld_sz are sized to be of the maximum + * size allowed by the transport: no need to be size-checked here + * since the transport layer would have already dropped such + * over-sized messages. + */ + if (r_evt->evt->max_payld_sz && len > r_evt->evt->max_payld_sz) { dev_err(handle->dev, "discard badly sized message\n"); return -EINVAL; } @@ -752,7 +758,7 @@ int scmi_register_protocol_events(const struct scmi_han= dle *handle, u8 proto_id, const struct scmi_protocol_handle *ph, const struct scmi_protocol_events *ee) { - int i; + int i, max_msg_sz; unsigned int num_sources; size_t payld_sz =3D 0; struct scmi_registered_events_desc *pd; @@ -767,6 +773,8 @@ int scmi_register_protocol_events(const struct scmi_han= dle *handle, u8 proto_id, if (!ni) return -ENOMEM; =20 + max_msg_sz =3D ph->hops->get_max_msg_size(ph); + /* num_sources cannot be <=3D 0 */ if (ee->num_sources) { num_sources =3D ee->num_sources; @@ -779,8 +787,13 @@ int scmi_register_protocol_events(const struct scmi_ha= ndle *handle, u8 proto_id, } =20 evt =3D ee->evts; - for (i =3D 0; i < ee->num_events; i++) + for (i =3D 0; i < ee->num_events; i++) { + if (evt[i].max_payld_sz =3D=3D 0) { + payld_sz =3D max_msg_sz; + break; + } payld_sz =3D max_t(size_t, payld_sz, evt[i].max_payld_sz); + } payld_sz +=3D sizeof(struct scmi_event_header); =20 pd =3D scmi_allocate_registered_events_desc(ni, proto_id, ee->queue_sz, @@ -809,7 +822,8 @@ int scmi_register_protocol_events(const struct scmi_han= dle *handle, u8 proto_id, mutex_init(&r_evt->sources_mtx); =20 r_evt->report =3D devm_kzalloc(ni->handle->dev, - evt->max_report_sz, GFP_KERNEL); + evt->max_report_sz ?: max_msg_sz, + GFP_KERNEL); if (!r_evt->report) return -ENOMEM; =20 @@ -1373,9 +1387,9 @@ static int scmi_event_handler_enable_events(struct sc= mi_event_handler *hndl) * * Return: 0 on Success */ -static int scmi_notifier_register(const struct scmi_handle *handle, - u8 proto_id, u8 evt_id, const u32 *src_id, - struct notifier_block *nb) +int scmi_notifier_register(const struct scmi_handle *handle, + u8 proto_id, u8 evt_id, const u32 *src_id, + struct notifier_block *nb) { int ret =3D 0; u32 evt_key; diff --git a/drivers/firmware/arm_scmi/notify.h b/drivers/firmware/arm_scmi= /notify.h index 76758a736cf4..ecfa4b746487 100644 --- a/drivers/firmware/arm_scmi/notify.h +++ b/drivers/firmware/arm_scmi/notify.h @@ -18,8 +18,12 @@ /** * struct scmi_event - Describes an event to be supported * @id: Event ID - * @max_payld_sz: Max possible size for the payload of a notification mess= age - * @max_report_sz: Max possible size for the report of a notification mess= age + * @max_payld_sz: Max possible size for the payload of a notification mess= age. + * Set to zero to use the maximum payload size allowed by the + * transport. + * @max_report_sz: Max possible size for the report of a notification mess= age. + * Set to zero to use the maximum payload size allowed by the + * transport. * * Each SCMI protocol, during its initialization phase, can describe the e= vents * it wishes to support in a few struct scmi_event and pass them to the co= re diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_s= cmi/protocols.h index d62c4469d1fd..afca1336267b 100644 --- a/drivers/firmware/arm_scmi/protocols.h +++ b/drivers/firmware/arm_scmi/protocols.h @@ -163,6 +163,9 @@ struct scmi_proto_helpers_ops; * can be used by the protocol implementation to generate SCMI messages. * @set_priv: A method to set protocol private data for this instance. * @get_priv: A method to get protocol private data previously set. + * @notifier_register: A method to register interest for notifications from + * within a protocol implementation unit: notifiers can + * be registered only for the same protocol. * * This structure represents a protocol initialized against specific SCMI * instance and it will be used as follows: @@ -182,6 +185,9 @@ struct scmi_protocol_handle { int (*set_priv)(const struct scmi_protocol_handle *ph, void *priv, u32 version); void *(*get_priv)(const struct scmi_protocol_handle *ph); + int (*notifier_register)(const struct scmi_protocol_handle *ph, + u8 evt_id, const u32 *src_id, + struct notifier_block *nb); }; =20 /** --=20 2.52.0 From nobody Mon Feb 9 01:43:04 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id D8E6E395263; Wed, 14 Jan 2026 11:47:36 +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=1768391258; cv=none; b=kiezfaHEd57Tjpg1BivHS5f2aNyrPrc/knt1KYz+Du/8LMHIsLl4frkK+nl5ErMz+ZnpTudfrsRajxSPSHDP2/ca9bLxP1kIhS2YSOT/jaj7GvYfHMp92nxakN5nmKjpCfKd3ZsyjaUMxntZ7DS2J+WRqL/AqRcLatyHXrXOZwo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768391258; c=relaxed/simple; bh=dUcNvpWFfrmBTMkcoYTsFq6XSacuMpXHDicNGslXPwI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=enmeIV7a5EWdOa0Y6E4ObCo68Ygl6T3+201Q5nx1XFbRy63Yyd2QOGVymAymKxpd15rx2eauLP0lVRnzJehYaQDtZajNOgaBSBOmnMq4ESqqkMPQcAoNKXnVDYuG64L/klzpkmwxa7xbqfJd3NXkrZN063VyfxQY96dBvrh4i5c= 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 A2C79497; Wed, 14 Jan 2026 03:47:29 -0800 (PST) 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 E6C8D3F59E; Wed, 14 Jan 2026 03:47:32 -0800 (PST) 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 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, dan.carpenter@linaro.org, d-gole@ti.com, jonathan.cameron@huawei.com, elif.topuz@arm.com, lukasz.luba@arm.com, philip.radford@arm.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH v2 04/17] uapi: Add ARM SCMI definitions Date: Wed, 14 Jan 2026 11:46:08 +0000 Message-ID: <20260114114638.2290765-5-cristian.marussi@arm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260114114638.2290765-1-cristian.marussi@arm.com> References: <20260114114638.2290765-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 a number of structures and ioctls definitions used by the ARM SCMI Telemetry protocol. Signed-off-by: Cristian Marussi --- v1 --> v2 - Added proper __counted_by marks - Fixed a few dox comments - Renamed reserved[] fields to pad[] --- MAINTAINERS | 1 + include/uapi/linux/scmi.h | 287 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 288 insertions(+) create mode 100644 include/uapi/linux/scmi.h diff --git a/MAINTAINERS b/MAINTAINERS index 12f49de7fe03..1c0ccaddc3f3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25392,6 +25392,7 @@ F: drivers/regulator/scmi-regulator.c F: drivers/reset/reset-scmi.c F: include/linux/sc[mp]i_protocol.h F: include/trace/events/scmi.h +F: include/uapi/linux/scmi.h F: include/uapi/linux/virtio_scmi.h =20 SYSTEM CONTROL MANAGEMENT INTERFACE (SCMI) i.MX Extension Message Protocol= drivers diff --git a/include/uapi/linux/scmi.h b/include/uapi/linux/scmi.h new file mode 100644 index 000000000000..e4e9939a1bf8 --- /dev/null +++ b/include/uapi/linux/scmi.h @@ -0,0 +1,287 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Copyright (C) 2026 ARM Ltd. + */ +#ifndef _UAPI_LINUX_SCMI_H +#define _UAPI_LINUX_SCMI_H + +/* + * Userspace interface SCMI Telemetry + */ + +#include +#include + +#define SCMI_TLM_DE_IMPL_MAX_DWORDS 4 + +#define SCMI_TLM_GRP_INVALID 0xFFFFFFFF + +/** + * scmi_tlm_base_info - Basic info about an instance + * + * @version: SCMI Telemetry protocol version + * @de_impl_version: SCMI Telemetry DE implementation revision + * @num_de: Number of defined DEs + * @num_groups Number of defined DEs groups + * @num_intervals: Number of update intervals available (instance-level) + * @flags: Instance specific feature-support bitmap + * + * Used by: + * RO - SCMI_TLM_GET_INFO + * + * Supported by: + * control/ + */ +struct scmi_tlm_base_info { + __u32 version; + __u32 de_impl_version[SCMI_TLM_DE_IMPL_MAX_DWORDS]; + __u32 num_des; + __u32 num_groups; + __u32 num_intervals; + __u32 flags; +#define SCMI_TLM_CAN_RESET (1 << 0) +}; + +/** + * scmi_tlm_config - Whole instance or group configuration + * + * @enable: Enable/Disable Telemetry for the whole instance or the group. + * @t_enable: Enable/Disable timestamping for all the DEs belonging to a g= roup. + * @pad: Padding fields to enforce alignment. + * @current_update_interval: Get/Set currently active update interval for = the + * whole instance or a group. + * + * Used by: + * RO - SCMI_TLM_GET_CFG + * WO - SCMI_TLM_SET_CFG + * + * Supported by: + * control/ + * groups//control + */ +struct scmi_tlm_config { + __u8 enable; + __u8 t_enable; + __u8 pad[2]; + __u32 current_update_interval; +}; + +/** + * scmi_tlm_intervals - Update intervals descriptor + * + * @discrete: Flag to indicate the nature of the intervals described in + * @update_intervals. + * When 'false' @update_intervals is a triplet: min/max/step + * @pad: Padding fields to enforce alignment. + * @num: Number of entries of @update_intervals + * @update_intervals: A variably-sized array containing the update interva= ls + * + * Used by: + * RW - SCMI_TLM_GET_INTRVS + * + * Supported by: + * control/ + * groups//control + */ +struct scmi_tlm_intervals { + __u8 discrete; + __u8 pad[3]; + __u32 num; +#define SCMI_TLM_UPDATE_INTVL_SEGMENT_LOW 0 +#define SCMI_TLM_UPDATE_INTVL_SEGMENT_HIGH 1 +#define SCMI_TLM_UPDATE_INTVL_SEGMENT_STEP 2 + __u32 update_intervals[] __counted_by(num); +}; + +/** + * scmi_tlm_de_config - DE configuration + * + * @id: Identifier of the DE to act upon (ignored by SCMI_TLM_SET_ALL_CFG) + * @enable: A boolean to enable/disable the DE + * @t_enable: A boolean to enable/disable the timestamp for this DE + * (if supported) + * + * Used by: + * RW - SCMI_TLM_GET_DE_CFG + * RW - SCMI_TLM_SET_DE_CFG + * WO - SCMI_TLM_SET_ALL_CFG + * + * Supported by: + * control/ + */ +struct scmi_tlm_de_config { + __u32 id; + __u32 enable; + __u32 t_enable; +}; + +/** + * scmi_tlm_de_info - DE Descriptor + * + * @id: DE identifier + * @grp_id: Identifier of the group which this DE belongs to; reported as + * SCMI_TLM_GRP_INVALID when not part of any group + * @data_sz: DE data size in bytes + * @type: DE type + * @unit: DE unit of measurements + * @unit_exp: Power-of-10 multiplier for DE unit + * @tstamp_exp: Power-of-10 multiplier for DE timestamp (if supported) + * @instance_id: DE instance ID + * @compo_instance_id: DE component instance ID + * @compo_type: Type of component which is associated to this DE + * @persistent: Data value for this DE survives reboot (non-cold ones) + * @name: Optional name of this DE + * + * Used to get the full description of a DE: it reflects DE Descriptors + * definitions in 3.12.4.6. + * + * Used by: + * RW - SCMI_TLM_GET_DE_INFO + * + * Supported by: + * control/ + */ +struct scmi_tlm_de_info { + __u32 id; + __u32 grp_id; + __u32 data_sz; + __u32 type; + __u32 unit; + __s32 unit_exp; + __s32 tstamp_exp; + __u32 instance_id; + __u32 compo_instance_id; + __u32 compo_type; + __u32 persistent; + __u8 name[16]; +}; + +/** + * scmi_tlm_des_list - List of all defined DEs + * + * @num_des: Number of entries in @des + * @des: An array containing descriptors for all defined DEs + * + * Used by: + * RW - SCMI_TLM_GET_DE_LIST + * + * Supported by: + * control/ + */ +struct scmi_tlm_des_list { + __u32 num_des; + struct scmi_tlm_de_info des[] __counted_by(num_des); +}; + +/** + * scmi_tlm_de_sample - A DE reading + * + * @id: DE identifier + * @tstamp: DE reading timestamp (equal 0 is NOT supported) + * @val: Reading of the DE data value + * + * Used by: + * RW - SCMI_TLM_GET_DE_VALUE + * + * Supported by: + * control/ + */ +struct scmi_tlm_de_sample { + __u32 id; + __u64 tstamp; + __u64 val; +}; + +/** + * scmi_tlm_data_read - Bulk read of multiple DEs + * + * @num_samples: Number of entries returned in @samples + * @samples: An array of samples containing an entry for each DE that was + * enabled when the single sample read request was issued. + * + * Used by: + * RW - SCMI_TLM_SINGLE_SAMPLE + * RW - SCMI_TLM_BULK_READ + * + * Supported by: + * control/ + * groups//control + */ +struct scmi_tlm_data_read { + __u32 num_samples; + struct scmi_tlm_de_sample samples[] __counted_by(num_samples); +}; + +/** + * scmi_tlm_grp_info - DE-group descriptor + * + * @id: Group ID number + * @num_des: Number of DEs part of this group + * @num_intervals: Number of update intervals supported. Zero if group doe= s not + * support per-group update interval configuration. + * + * Used by: + * RO - SCMI_TLM_GET_GRP_INFO + * + * Supported by: + * groups/control/ + */ +struct scmi_tlm_grp_info { + __u32 id; + __u32 num_des; + __u32 num_intervals; +}; + +/** + * scmi_tlm_grps_list - DE-groups List + * + * @num_grps: Number of entries returned in @grps + * @grps: An array containing descriptors for all defined DE Groups + * + * Used by: + * RW - SCMI_TLM_GET_GRP_LIST + * + * Supported by: + * control/ + */ +struct scmi_tlm_grps_list { + __u32 num_grps; + struct scmi_tlm_grp_info grps[] __counted_by(num_grps); +}; + +/** + * scmi_tlm_grp_desc - Group descriptor + * + * @num_des: Number of DEs part of this group + * @composing_des: An array containing the DE IDs that belongs to this gro= up. + * + * Used by: + * RW - SCMI_TLM_GET_GRP_DESC + * + * Supported by: + * groups/control/ + */ +struct scmi_tlm_grp_desc { + __u32 num_des; + __u32 composing_des[] __counted_by(num_des); +}; + +#define SCMI 0xF1 + +#define SCMI_TLM_GET_INFO _IOR(SCMI, 0x00, struct scmi_tlm_base_info) +#define SCMI_TLM_GET_CFG _IOR(SCMI, 0x01, struct scmi_tlm_config) +#define SCMI_TLM_SET_CFG _IOW(SCMI, 0x02, struct scmi_tlm_config) +#define SCMI_TLM_GET_INTRVS _IOWR(SCMI, 0x03, struct scmi_tlm_intervals) +#define SCMI_TLM_GET_DE_CFG _IOWR(SCMI, 0x04, struct scmi_tlm_de_config) +#define SCMI_TLM_SET_DE_CFG _IOWR(SCMI, 0x05, struct scmi_tlm_de_config) +#define SCMI_TLM_GET_DE_INFO _IOWR(SCMI, 0x06, struct scmi_tlm_de_info) +#define SCMI_TLM_GET_DE_LIST _IOWR(SCMI, 0x07, struct scmi_tlm_des_list) +#define SCMI_TLM_GET_DE_VALUE _IOWR(SCMI, 0x08, struct scmi_tlm_de_sample) +#define SCMI_TLM_SET_ALL_CFG _IOW(SCMI, 0x09, struct scmi_tlm_de_config) +#define SCMI_TLM_GET_GRP_LIST _IOWR(SCMI, 0x0A, struct scmi_tlm_grps_list) +#define SCMI_TLM_GET_GRP_INFO _IOR(SCMI, 0x0B, struct scmi_tlm_grp_info) +#define SCMI_TLM_GET_GRP_DESC _IOWR(SCMI, 0x0C, struct scmi_tlm_grp_desc) +#define SCMI_TLM_SINGLE_SAMPLE _IOWR(SCMI, 0x0D, struct scmi_tlm_data_read) +#define SCMI_TLM_BULK_READ _IOWR(SCMI, 0x0E, struct scmi_tlm_data_read) + +#endif /* _UAPI_LINUX_SCMI_H */ --=20 2.52.0 From nobody Mon Feb 9 01:43:04 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id C1BC93876B7; Wed, 14 Jan 2026 11:47:41 +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=1768391268; cv=none; b=pldncQGp5BuRAigKz/hhyICEqsX1dzx2jqyTYMnomQ58iEa/Qnm3A7A/qzU7+TXcg8bx/D1kVqF020H2GqOVSgSZQAh1ccAi4HDIW6jdBcNuX41BrybTjWVsgGHXzpXfhDWaE8CvDwkVKNj48EYH2VUqc2ea6m9E5tkWUOrzsKY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768391268; c=relaxed/simple; bh=z6lugL48h9klC5qDMLyJhuLjI93In0+MG8MzYleEdeo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=P8JjPm9DRRsDsBpwFVQZ5a2rS9o+lO1JdyuOwrz/42KWgHj4Hf6GDZf2Du8zL/TLAhGPN6Gi6PKFsp7CXW9E0v876vkKG/9Dm60KDw9QdUOXjC98tbDxmh7dZBv2z3WpU0NoVVx7hAIYjt2wxHSwa8EwaWBv3pSByEoj2jc7YR4= 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 D9640339; Wed, 14 Jan 2026 03:47:33 -0800 (PST) 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 BDC103F59E; Wed, 14 Jan 2026 03:47:36 -0800 (PST) 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 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, dan.carpenter@linaro.org, d-gole@ti.com, jonathan.cameron@huawei.com, elif.topuz@arm.com, lukasz.luba@arm.com, philip.radford@arm.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH v2 05/17] firmware: arm_scmi: Add Telemetry protocol support Date: Wed, 14 Jan 2026 11:46:09 +0000 Message-ID: <20260114114638.2290765-6-cristian.marussi@arm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260114114638.2290765-1-cristian.marussi@arm.com> References: <20260114114638.2290765-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 Telemetry protocol including SHMTI, FastChannels, Notifications and Single Sample Reads collection methods. Signed-off-by: Cristian Marussi --- v1 --> v2 - Add proper ioread accessors for TDCF areas - Rework resource allocation logic and lifecycle - Introduce new resources accessors and res_get() operation to implement lazy enumeration - Support boot-on telemetry: + Add DE_ENABLED_LIST cmd support + Add CONFIG_GET cmd support + Add TDCF_SCAN for best effort enumeration + Harden driver against out-of-spec FW with out of spec cmds - Use FCs list - Rework de_info_lookup to a moer general de_lookup - Fixed reset to use CONFIG_GET and DE_ENABLED_LIST to gather initial state - Using sign_extend32 helper - Added counted_by marker where appropriate --- drivers/firmware/arm_scmi/Makefile | 2 +- drivers/firmware/arm_scmi/driver.c | 2 + drivers/firmware/arm_scmi/protocols.h | 1 + drivers/firmware/arm_scmi/telemetry.c | 2671 +++++++++++++++++++++++++ include/linux/scmi_protocol.h | 188 +- 5 files changed, 2862 insertions(+), 2 deletions(-) create mode 100644 drivers/firmware/arm_scmi/telemetry.c diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi= /Makefile index 780cd62b2f78..fe55b7aa0707 100644 --- a/drivers/firmware/arm_scmi/Makefile +++ b/drivers/firmware/arm_scmi/Makefile @@ -8,7 +8,7 @@ scmi-driver-$(CONFIG_ARM_SCMI_RAW_MODE_SUPPORT) +=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 1085c70ca457..dd5da75a19b0 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -3450,6 +3450,7 @@ static int __init scmi_driver_init(void) scmi_system_register(); scmi_powercap_register(); scmi_pinctrl_register(); + scmi_telemetry_register(); =20 return platform_driver_register(&scmi_driver); } @@ -3468,6 +3469,7 @@ static void __exit scmi_driver_exit(void) scmi_system_unregister(); scmi_powercap_unregister(); scmi_pinctrl_unregister(); + scmi_telemetry_unregister(); =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 afca1336267b..766b68a3084e 100644 --- a/drivers/firmware/arm_scmi/protocols.h +++ b/drivers/firmware/arm_scmi/protocols.h @@ -385,5 +385,6 @@ DECLARE_SCMI_REGISTER_UNREGISTER(sensors); DECLARE_SCMI_REGISTER_UNREGISTER(voltage); DECLARE_SCMI_REGISTER_UNREGISTER(system); DECLARE_SCMI_REGISTER_UNREGISTER(powercap); +DECLARE_SCMI_REGISTER_UNREGISTER(telemetry); =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..16bcdcdc1dc3 --- /dev/null +++ b/drivers/firmware/arm_scmi/telemetry.c @@ -0,0 +1,2671 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Management Interface (SCMI) Telemetry Protocol + * + * Copyright (C) 2026 ARM Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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, + 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)) +}; + +struct scmi_telemetry_update_notify_payld { + __le32 agent_id; + __le32 status; + __le32 num_dwords; + __le32 array[] __counted_by(num_dwords); +}; + +struct scmi_shmti_desc { + __le32 id; + __le32 addr_low; + __le32 addr_high; + __le32 length; +}; + +struct scmi_msg_resp_telemetry_shmti_list { + __le32 num_shmti; + struct scmi_shmti_desc desc[] __counted_by(num_shmti); +}; + +struct de_desc_fc { + __le32 addr_low; + __le32 addr_high; + __le32 size; +}; + +struct scmi_de_desc { + __le32 id; + __le32 grp_id; + __le32 data_sz; + __le32 attr_1; +#define IS_NAME_SUPPORTED(d) ((d)->attr_1 & BIT(31)) +#define IS_FC_SUPPORTED(d) ((d)->attr_1 & BIT(30)) +#define GET_DE_TYPE(d) (le32_get_bits((d)->attr_1, GENMASK(29, 22))) +#define IS_PERSISTENT(d) ((d)->attr_1 & BIT(21)) +#define GET_DE_UNIT_EXP(d) \ + ({ \ + __u32 __signed_exp =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 GET_DE_TSTAMP_EXP(d) \ + ({ \ + __u32 __signed_exp =3D \ + FIELD_GET(GENMASK(4, 1), (d)->attr_1); \ + \ + sign_extend32(__signed_exp, 3); \ + }) +#define IS_TSTAMP_SUPPORTED(d) ((d)->attr_1 & BIT(0)) + __le32 attr_2; +#define GET_DE_INSTA_ID(d) (le32_get_bits((d)->attr_2, GENMASK(31, 24))) +#define GET_COMPO_INSTA_ID(d) (le32_get_bits((d)->attr_2, GENMASK(23, 8))) +#define GET_COMPO_TYPE(d) (le32_get_bits((d)->attr_2, GENMASK(7, 0))) + __le32 reserved; +}; + +struct scmi_msg_resp_telemetry_de_description { + __le32 num_desc; + struct scmi_de_desc desc[] __counted_by(num_desc); +}; + +struct scmi_msg_telemetry_update_intervals { + __le32 index; + __le32 group_identifier; +#define ALL_DES_NO_GROUP 0x0 +#define SPECIFIC_GROUP_DES 0x1 +#define ALL_DES_ANY_GROUP 0x2 + __le32 flags; +}; + +struct scmi_msg_resp_telemetry_update_intervals { + __le32 flags; +#define INTERVALS_DISCRETE(x) (!((x) & BIT(12))) + __le32 intervals[]; +}; + +struct scmi_msg_telemetry_de_enabled_list { + __le32 index; + __le32 flags; +}; + +struct scmi_enabled_de_desc { + __le32 id; + __le32 mode; +}; + +struct scmi_msg_resp_telemetry_de_enabled_list { + __le32 flags; + struct scmi_enabled_de_desc entry[]; +}; + +struct scmi_msg_telemetry_de_configure { + __le32 id; + __le32 flags; +#define DE_ENABLE_NO_TSTAMP BIT(0) +#define DE_ENABLE_WTH_TSTAMP BIT(1) +#define DE_DISABLE_ALL BIT(2) +#define GROUP_SELECTOR BIT(3) +#define EVENT_DE 0 +#define EVENT_GROUP 1 +#define DE_DISABLE_ONE 0x0 +}; + +struct scmi_msg_resp_telemetry_de_configure { + __le32 shmti_id; +#define IS_SHMTI_ID_VALID(x) ((x) !=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_SET(x) (FIELD_PREP(GENMASK(4, 1), (x))) +#define TLM_ONDEMAND (0) +#define TLM_NOTIFS (1) +#define TLM_SINGLE (2) +#define TELEMETRY_MODE_ONDEMAND TELEMETRY_MODE_SET(TLM_ONDEMAND) +#define TELEMETRY_MODE_NOTIFS TELEMETRY_MODE_SET(TLM_NOTIFS) +#define TELEMETRY_MODE_SINGLE TELEMETRY_MODE_SET(TLM_SINGLE) + +#define TLM_ORPHANS (0) +#define TLM_GROUP (1) +#define TLM_ALL (2) +#define TELEMETRY_SET_SELECTOR(x) (FIELD_PREP(GENMASK(8, 5), (x))) +#define TELEMETRY_SET_SELECTOR_ORPHANS TELEMETRY_SET_SELECTOR(TLM_ORPHANS) +#define TELEMETRY_SET_SELECTOR_GROUP TELEMETRY_SET_SELECTOR(TLM_GROUP) +#define TELEMETRY_SET_SELECTOR_ALL TELEMETRY_SET_SELECTOR(TLM_ALL) + __le32 sampling_rate; +}; + +struct scmi_msg_resp_telemetry_reading_complete { + __le32 num_dwords; + __le32 dwords[] __counted_by(num_dwords); +}; + +struct scmi_msg_telemetry_config_get { + __le32 grp_id; + __le32 flags; +#define TELEMETRY_GET_SELECTOR(x) (FIELD_PREP(GENMASK(3, 0), (x))) +#define TELEMETRY_GET_SELECTOR_ORPHANS TELEMETRY_GET_SELECTOR(TLM_ORPHANS) +#define TELEMETRY_GET_SELECTOR_GROUP TELEMETRY_GET_SELECTOR(TLM_GROUP) +#define TELEMETRY_GET_SELECTOR_ALL TELEMETRY_GET_SELECTOR(TLM_ALL) +}; + +struct scmi_msg_resp_telemetry_config_get { + __le32 control; +#define TELEMETRY_MODE_GET (FIELD_GET(GENMASK(4, 1))) + __le32 sampling_rate; +}; + +/* TDCF */ + +#define _I(__a) (ioread32((void __iomem *)(__a))) + +#define TO_CPU_64(h, l) ((((u64)(h)) << 32) | (l)) + +enum scan_mode { + SCAN_LOOKUP, + SCAN_UPDATE, + SCAN_DISCOVERY +}; + +struct fc_line { + u32 data_low; + u32 data_high; +}; + +struct fc_tsline { + u32 data_low; + u32 data_high; + u32 ts_low; + u32 ts_high; +}; + +struct line { + u32 data_low; + u32 data_high; +}; + +struct blk_tsline { + u32 ts_low; + u32 ts_high; +}; + +struct tsline { + u32 data_low; + u32 data_high; + u32 ts_low; + u32 ts_high; +}; + +#define LINE_DATA_GET(f) \ +({ \ + typeof(f) _f =3D (f); \ + \ + (TO_CPU_64(_I(&_f->data_high), _I(&_f->data_low))); \ +}) + +#define LINE_TSTAMP_GET(f) \ +({ \ + typeof(f) _f =3D (f); \ + \ + (TO_CPU_64(_I(&_f->ts_high), _I(&_f->ts_low))); \ +}) + +#define BLK_TSTAMP_GET(f) LINE_TSTAMP_GET(f) + +struct payload { + u32 meta; +#define IS_BLK_TS(x) (_I(&((x)->meta)) & BIT(4)) +#define USE_BLK_TS(x) (_I(&((x)->meta)) & BIT(3)) +#define USE_LINE_TS(x) (_I(&((x)->meta)) & BIT(2)) +#define TS_VALID(x) (_I(&((x)->meta)) & BIT(1)) +#define DATA_INVALID(x) (_I(&((x)->meta)) & BIT(0)) + u32 id; + union { + struct line l; + struct tsline tsl; + struct blk_tsline blk_tsl; + }; +}; + +#define PAYLD_ID(x) (_I(&(((struct payload *)(x))->id))) + +#define LINE_DATA_PAYLD_WORDS \ + ((sizeof(u32) + sizeof(u32) + sizeof(struct line)) / sizeof(u32)) +#define TS_LINE_DATA_PAYLD_WORDS \ + ((sizeof(u32) + sizeof(u32) + sizeof(struct tsline)) / sizeof(u32)) + +#define QWORDS_LINE_DATA_PAYLD (LINE_DATA_PAYLD_WORDS / 2) +#define QWORDS_TS_LINE_DATA_PAYLD (TS_LINE_DATA_PAYLD_WORDS / 2) + +struct prlg { + u32 seq_low; + u32 seq_high; + u32 num_qwords; + u32 _meta_header_high; +}; + +struct eplg { + u32 seq_low; + u32 seq_high; +}; + +#define TDCF_EPLG_SZ (sizeof(struct eplg)) + +struct tdcf { + struct prlg prlg; + unsigned char payld[]; +}; + +#define QWORDS(_t) (_I(&(_t)->prlg.num_qwords)) + +#define SHMTI_MIN_SIZE (sizeof(struct tdcf) + TDCF_EPLG_SZ) + +#define TDCF_BAD_END_SEQ GENMASK_U64(63, 0) +#define TDCF_START_SEQ_GET(x) \ + ({ \ + u64 _val; \ + struct prlg *_p =3D &((x)->prlg); \ + \ + _val =3D TO_CPU_64(_I(&_p->seq_high), _I(&_p->seq_low)); \ + (_val); \ + }) + +#define IS_BAD_START_SEQ(s) ((s) & 0x1) + +#define TDCF_END_SEQ_GET(e) \ + ({ \ + u64 _val; \ + struct eplg *_e =3D (e); \ + \ + _val =3D TO_CPU_64(_I(&_e->seq_high), _I(&_e->seq_low)); \ + (_val); \ + }) + +struct telemetry_shmti { + int id; + void __iomem *base; + u32 len; + u64 last_magic; +}; + +#define SHMTI_EPLG(s) \ + ({ \ + struct telemetry_shmti *_s =3D (s); \ + void *_eplg; \ + \ + _eplg =3D _s->base + _s->len - TDCF_EPLG_SZ; \ + (_eplg); \ + }) + +struct telemetry_block_ts { + refcount_t users; + /* Protect block_ts accesses */ + struct mutex mtx; + u64 last_ts; + u64 last_magic; + struct payload __iomem *payld; + struct xarray *xa_bts; +}; + +struct telemetry_de { + bool enumerated; + bool cached; + void __iomem *base; + void __iomem *eplg; + u32 offset; + /* NOTE THAT DE data_sz is registered in scmi_telemetry_de */ + u32 fc_size; + /* Protect last_val/ts/magic accesses */ + struct mutex mtx; + u64 last_val; + u64 last_ts; + u64 last_magic; + struct list_head item; + struct telemetry_block_ts *bts; + struct scmi_telemetry_de de; +}; + +#define to_tde(d) container_of(d, struct telemetry_de, de) + +#define DE_ENABLED_WITH_TSTAMP 2 + +struct telemetry_info { + bool streaming_mode; + int num_shmti; + const struct scmi_protocol_handle *ph; + struct telemetry_shmti *shmti; + struct telemetry_de *tdes; + struct scmi_telemetry_group *grps; + struct xarray xa_des; + struct xarray xa_bts; + /* Mutex to protect access to @free_des */ + struct mutex free_mtx; + struct list_head free_des; + struct list_head fcs_des; + struct scmi_telemetry_info info; + struct notifier_block telemetry_nb; + atomic_t rinfo_initializing; + struct completion rinfo_initdone; + struct scmi_telemetry_res_info __private *rinfo; + struct scmi_telemetry_res_info *(*res_get)(struct telemetry_info *ti); +}; + +#define telemetry_nb_to_info(x) \ + container_of(x, struct telemetry_info, telemetry_nb) + +static struct scmi_telemetry_res_info * +__scmi_telemetry_resources_get(struct telemetry_info *ti); + +static int scmi_telemetry_shmti_scan(struct telemetry_info *ti, + unsigned int shmti_id, u64 ts, + enum scan_mode mode); + +static struct telemetry_de * +scmi_telemetry_free_tde_get(struct telemetry_info *ti) +{ + struct telemetry_de *tde; + + guard(mutex)(&ti->free_mtx); + + tde =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) +{ + int ret; + + if (ti->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 */ + ti->rinfo->des[ti->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)); + } + + 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 scmi_telemetry_de_descriptor_parse(struct telemetry_info *ti, + struct telemetry_de *tde, + void **next) +{ + struct scmi_telemetry_res_info *rinfo =3D 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->tstamp_exp =3D GET_DE_TSTAMP_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->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); + /* Update DE_DESCRIPTOR size for the next iteration */ + *next +=3D sizeof(*desc); + 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_config_lookup(struct telemetry_info *ti, + unsigned int grp_id, bool *enabled, + unsigned int *active_update_interval) +{ + const struct scmi_protocol_handle *ph =3D ti->ph; + struct scmi_msg_telemetry_config_get *msg; + struct scmi_msg_resp_telemetry_config_get *resp; + struct scmi_xfer *t; + int ret; + + ret =3D ph->xops->xfer_get_init(ph, TELEMETRY_CONFIG_GET, + sizeof(*msg), sizeof(*resp), &t); + if (ret) + return ret; + + msg =3D t->tx.buf; + msg->grp_id =3D grp_id; + msg->flags =3D grp_id =3D=3D SCMI_TLM_GRP_INVALID ? + TELEMETRY_GET_SELECTOR_ORPHANS : TELEMETRY_GET_SELECTOR_GROUP; + + resp =3D t->rx.buf; + ret =3D ph->xops->do_xfer(ph, t); + if (!ret) { + *enabled =3D resp->control & TELEMETRY_ENABLE; + *active_update_interval =3D + SCMI_TLM_GET_UPDATE_INTERVAL(resp->sampling_rate); + } + + ph->xops->xfer_put(ph, t); + + return 0; +} + +static int scmi_telemetry_group_config_lookup(struct telemetry_info *ti, + struct scmi_telemetry_group *grp) +{ + return scmi_telemetry_config_lookup(ti, grp->info->id, &grp->enabled, + &grp->active_update_interval); +} + +static void iter_enabled_list_prepare_message(void *message, + unsigned int desc_index, + const void *priv) +{ + struct scmi_msg_telemetry_de_enabled_list *msg =3D message; + + msg->index =3D cpu_to_le32(desc_index); + msg->flags =3D 0; +} + +static int iter_enabled_list_update_state(struct scmi_iterator_state *st, + const void *response, void *priv) +{ + const struct scmi_msg_resp_telemetry_de_enabled_list *r =3D response; + + st->num_returned =3D le32_get_bits(r->flags, GENMASK(15, 0)); + st->num_remaining =3D le32_get_bits(r->flags, GENMASK(31, 16)); + + /* + * total enabled is not declared previously anywhere so we + * assume it's returned+remaining on first call. + */ + if (!st->max_resources) + st->max_resources =3D st->num_returned + st->num_remaining; + + return 0; +} + +static int +iter_enabled_list_process_response(const struct scmi_protocol_handle *ph, + const void *response, + struct scmi_iterator_state *st, void *priv) +{ + const struct scmi_msg_resp_telemetry_de_enabled_list *r =3D response; + const struct scmi_enabled_de_desc *desc; + struct telemetry_info *ti =3D priv; + struct telemetry_de *tde; + u32 de_id; + int ret; + + desc =3D &r->entry[st->loop_idx]; + de_id =3D le32_to_cpu(desc->id); + if (scmi_telemetry_tde_lookup(ti, de_id)) { + dev_err(ph->dev, + "Found INVALID DE with DUPLICATED ID:0x%08X\n", de_id); + return -EINVAL; + } + + tde =3D scmi_telemetry_tde_get(ti, de_id); + if (IS_ERR(tde)) + return PTR_ERR(tde); + + tde->de.info->id =3D de_id; + tde->de.enabled =3D true; + tde->de.tstamp_enabled =3D desc->mode =3D=3D DE_ENABLED_WITH_TSTAMP; + + ret =3D scmi_telemetry_tde_register(ti, tde); + if (ret) { + scmi_telemetry_free_tde_put(ti, tde); + return ret; + } + + dev_dbg(ph->dev, "Registered new ENABLED DE with ID:0x%08X\n", + tde->de.info->id); + + return 0; +} + +static int scmi_telemetry_enumerate_des_enabled_list(struct telemetry_info= *ti) +{ + const struct scmi_protocol_handle *ph =3D ti->ph; + struct scmi_iterator_ops ops =3D { + .prepare_message =3D iter_enabled_list_prepare_message, + .update_state =3D iter_enabled_list_update_state, + .process_response =3D iter_enabled_list_process_response, + }; + void *iter; + int ret; + + iter =3D ph->hops->iter_response_init(ph, &ops, 0, + TELEMETRY_DE_ENABLED_LIST, + sizeof(u32) * 2, ti); + if (IS_ERR(iter)) + return PTR_ERR(iter); + + ret =3D ph->hops->iter_response_run(iter); + if (ret) + return ret; + + dev_info(ti->ph->dev, "Found %u enabled DEs.\n", ti->rinfo->num_des); + + return 0; +} + +static int scmi_telemetry_initial_state_lookup(struct telemetry_info *ti) +{ + struct device *dev =3D ti->ph->dev; + int ret; + + ret =3D scmi_telemetry_config_lookup(ti, SCMI_TLM_GRP_INVALID, + &ti->info.enabled, + &ti->info.active_update_interval); + if (ret) + return ret; + + if (ti->info.enabled) { + /* + * When Telemetry is found already enabled on the platform, + * proceed with passive discovery using DE_ENABLED_LIST and + * TCDF scanning: note that this CAN only discover DEs exposed + * via SHMTIs. + * FastChannel DEs need a proper DE_DESCRIPTION enumeration, + * while, even though incoming Notifications could be used for + * passive discovery too, it would carry a considerable risk + * of assimilating trash as DEs. + */ + dev_info(dev, + "Telemetry found enabled with update interval %ux10^%d\n", + SCMI_TLM_GET_UPDATE_INTERVAL_SECS(ti->info.active_update_interval), + SCMI_TLM_GET_UPDATE_INTERVAL_EXP(ti->info.active_update_interval)); + /* + * Query enabled DEs list: collect states. + * It will include DEs from any interface. + * Enabled groups still NOT enumerated. + */ + ret =3D scmi_telemetry_enumerate_des_enabled_list(ti); + if (ret) + dev_warn(dev, FW_BUG "Cannot query enabled DE list. Carry-on.\n"); + + /* Discover DEs on SHMTis: collect states/offsets/values */ + for (int id =3D 0; id < ti->num_shmti; id++) { + ret =3D scmi_telemetry_shmti_scan(ti, id, 0, SCAN_DISCOVERY); + if (ret) + dev_warn(dev, "Failed discovery-scan of SHMTI ID:%d\n", id); + } + } + + return 0; +} + +static int +scmi_telemetry_de_groups_init(struct device *dev, struct telemetry_info *t= i) +{ + struct scmi_telemetry_res_info *rinfo =3D 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%04X%c", + rinfo->des[grp->des[j]]->info->id, term); + + buf +=3D len; + bufsize -=3D len; + } + } + + for (int i =3D 0; i < ti->info.base.num_groups; i++) + scmi_telemetry_group_config_lookup(ti, &rinfo->grps[i]); + + 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)); + + /* + * 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; + 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; + struct scmi_tlm_intervals *intrvs __free(kfree) =3D + kzalloc(sizeof(*intrvs) + inum * sizeof(__u32), GFP_KERNEL); + if (!intrvs) + return -ENOMEM; + + intrvs->num =3D inum; + intrvs->discrete =3D discrete; + st->max_resources =3D intrvs->num; + + *p->intrvs =3D no_free_ptr(intrvs); + } + + return 0; +} + +static int +iter_intervals_process_response(const struct scmi_protocol_handle *ph, + const void *response, + struct scmi_iterator_state *st, void *priv) +{ + const struct scmi_msg_resp_telemetry_update_intervals *r =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 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; + } + + 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; + + /* 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)); + + 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); + if (len < SHMTI_MIN_SIZE) + return -EINVAL; + + 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.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); + struct scmi_telemetry_de *de; + + ti->res_get(ti); + de =3D xa_load(&ti->xa_des, id); + if (!de) + return NULL; + + return de; +} + +static const struct scmi_telemetry_res_info * +scmi_telemetry_resources_get(const struct scmi_protocol_handle *ph) +{ + struct telemetry_info *ti =3D ph->get_priv(ph); + + return ti->res_get(ti); +} + +static u64 +scmi_telemetry_blkts_read(u64 magic, struct telemetry_block_ts *bts) +{ + if (WARN_ON(!bts || !refcount_read(&bts->users))) + return 0; + + guard(mutex)(&bts->mtx); + + if (bts->last_magic =3D=3D magic) + return bts->last_ts; + + bts->last_ts =3D BLK_TSTAMP_GET(&bts->payld->blk_tsl); + bts->last_magic =3D magic; + + return bts->last_ts; +} + +static void scmi_telemetry_blkts_update(u64 magic, + struct telemetry_block_ts *bts) +{ + guard(mutex)(&bts->mtx); + + if (bts->last_magic !=3D magic) { + bts->last_ts =3D BLK_TSTAMP_GET(&bts->payld->blk_tsl); + bts->last_magic =3D magic; + } +} + +static void scmi_telemetry_blkts_put(struct device *dev, + struct telemetry_block_ts *bts) +{ + if (refcount_dec_and_test(&bts->users)) { + scoped_guard(mutex, &bts->mtx) + xa_erase(bts->xa_bts, (unsigned long)bts->payld); + devm_kfree(dev, bts); + } +} + +static struct telemetry_block_ts * +scmi_telemetry_blkts_get(struct xarray *xa_bts, struct payload *payld) +{ + struct telemetry_block_ts *bts; + + bts =3D xa_load(xa_bts, (unsigned long)payld); + if (!bts) + return NULL; + + refcount_inc(&bts->users); + + return bts; +} + +static struct payload * +scmi_telemetry_nearest_blk_ts(struct telemetry_shmti *shmti, + struct payload *last_payld) +{ + struct payload *payld, *bts_payld =3D NULL; + struct tdcf __iomem *tdcf =3D shmti->base; + u32 *next; + + /* Scan from start of TDCF payloads up to last_payld */ + payld =3D (struct payload *)tdcf->payld; + next =3D (u32 *)payld; + while (payld < last_payld) { + if (IS_BLK_TS(payld)) + bts_payld =3D payld; + + next +=3D USE_LINE_TS(payld) ? + TS_LINE_DATA_PAYLD_WORDS : LINE_DATA_PAYLD_WORDS; + payld =3D (struct payload *)next; + } + + return bts_payld; +} + +static struct telemetry_block_ts * +scmi_telemetry_blkts_lookup(struct device *dev, struct xarray *xa_bts, + struct payload *payld) +{ + struct telemetry_block_ts *bts; + + bts =3D xa_load(xa_bts, (unsigned long)payld); + if (!bts) { + int ret; + + bts =3D devm_kzalloc(dev, sizeof(*bts), GFP_KERNEL); + if (!bts) + return NULL; + + refcount_set(&bts->users, 1); + bts->payld =3D payld; + bts->xa_bts =3D xa_bts; + mutex_init(&bts->mtx); + ret =3D xa_insert(xa_bts, (unsigned long)payld, bts, GFP_KERNEL); + if (ret) { + devm_kfree(dev, bts); + return NULL; + } + } + + return bts; +} + +static struct telemetry_block_ts * +scmi_telemetry_blkts_bind(struct device *dev, struct telemetry_shmti *shmt= i, + struct payload *payld, struct xarray *xa_bts) +{ + struct telemetry_block_ts *bts; + struct payload *bts_payld; + + /* Find the BLK_TS immediately preceding this DE payld */ + bts_payld =3D scmi_telemetry_nearest_blk_ts(shmti, payld); + if (!bts_payld) + return NULL; + + bts =3D scmi_telemetry_blkts_get(xa_bts, bts_payld); + if (bts) + return bts; + + return scmi_telemetry_blkts_lookup(dev, xa_bts, payld); +} + +static void scmi_telemetry_tdcf_blkts_parse(struct telemetry_info *ti, + struct payload __iomem *payld, + struct telemetry_shmti *shmti) +{ + struct telemetry_block_ts *bts; + + /* Check for spec compliance */ + if (USE_LINE_TS(payld) || USE_BLK_TS(payld) || + DATA_INVALID(payld) || (PAYLD_ID(payld) !=3D 0)) + return; + + /* A BLK_TS descriptor MUST be returned: it is found or it is crated */ + bts =3D scmi_telemetry_blkts_lookup(ti->ph->dev, &ti->xa_bts, payld); + if (WARN_ON(!bts)) + return; + + /* Update the descriptor with the lastest TS*/ + scmi_telemetry_blkts_update(shmti->last_magic, bts); +} + +static void scmi_telemetry_tdcf_data_parse(struct telemetry_info *ti, + struct payload __iomem *payld, + struct telemetry_shmti *shmti, + enum scan_mode mode) +{ + bool ts_valid =3D TS_VALID(payld); + struct telemetry_de *tde; + bool discovered =3D false; + u64 val, tstamp =3D 0; + u32 de_id; + + de_id =3D PAYLD_ID(payld); + /* Is thi DE ID know ? */ + tde =3D scmi_telemetry_tde_lookup(ti, de_id); + if (!tde) { + if (mode !=3D SCAN_DISCOVERY) + return; + + /* In SCAN_DISCOVERY mode we allocate new DEs for unknown IDs */ + tde =3D scmi_telemetry_tde_get(ti, de_id); + if (IS_ERR(tde)) + return; + + tde->de.info->id =3D de_id; + tde->de.enabled =3D true; + tde->de.tstamp_enabled =3D ts_valid; + discovered =3D true; + } + + /* Update DE location refs if requested: normally done only on enable */ + if (mode >=3D SCAN_UPDATE) { + tde->base =3D shmti->base; + tde->eplg =3D SHMTI_EPLG(shmti); + tde->offset =3D (void *)payld - (void *)shmti->base; + + dev_dbg(ti->ph->dev, + "TDCF-updated DE_ID:0x%08X - shmti:%pX offset:%u\n", + tde->de.info->id, tde->base, tde->offset); + } + + if (discovered) { + if (scmi_telemetry_tde_register(ti, tde)) { + scmi_telemetry_free_tde_put(ti, tde); + return; + } + } + + scoped_guard(mutex, &tde->mtx) { + if (tde->last_magic =3D=3D shmti->last_magic) + return; + } + + /* Data is always valid since we are NOT handling BLK TS lines here */ + val =3D LINE_DATA_GET(&payld->l); + /* Collect the right TS */ + if (ts_valid) { + if (USE_LINE_TS(payld)) { + tstamp =3D LINE_TSTAMP_GET(&payld->tsl); + } else if (USE_BLK_TS(payld)) { + if (!tde->bts) { + /* + * Scanning a TDCF looking for the nearest + * previous valid BLK_TS, after having found a + * USE_BLK_TS() payload, MUST succeed. + */ + tde->bts =3D scmi_telemetry_blkts_bind(ti->ph->dev, + shmti, payld, + &ti->xa_bts); + if (WARN_ON(!tde->bts)) + return; + } + + tstamp =3D scmi_telemetry_blkts_read(tde->last_magic, + tde->bts); + } + } + + guard(mutex)(&tde->mtx); + tde->last_magic =3D shmti->last_magic; + tde->last_val =3D val; + if (tde->de.tstamp_enabled) + tde->last_ts =3D tstamp; + else + tde->last_ts =3D 0; +} + +static int scmi_telemetry_tdcf_line_parse(struct telemetry_info *ti, + struct payload __iomem *payld, + struct telemetry_shmti *shmti, + enum scan_mode mode) +{ + int used_qwords; + + used_qwords =3D (USE_LINE_TS(payld) && TS_VALID(payld)) ? + QWORDS_TS_LINE_DATA_PAYLD : QWORDS_LINE_DATA_PAYLD; + + /* Invalid lines are not an error, could simply be disabled DEs */ + if (DATA_INVALID(payld)) + return used_qwords; + + if (!IS_BLK_TS(payld)) + scmi_telemetry_tdcf_data_parse(ti, payld, shmti, mode); + else + scmi_telemetry_tdcf_blkts_parse(ti, payld, shmti); + + return used_qwords; +} + +static int scmi_telemetry_shmti_scan(struct telemetry_info *ti, + unsigned int shmti_id, u64 ts, + enum scan_mode mode) +{ + 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 TDCF_BAD_END_SEQ; + 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; + + /* On a BAD_SEQ this will be updated on the next attempt */ + shmti->last_magic =3D startm; + + qwords =3D QWORDS(tdcf); + next =3D tdcf->payld; + while (qwords) { + int used_qwords; + + used_qwords =3D scmi_telemetry_tdcf_line_parse(ti, next, + shmti, mode); + if (qwords < used_qwords) + return -EINVAL; + + next +=3D used_qwords * 8; + 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_res_info *rinfo; + + rinfo =3D ti->res_get(ti); + for (int i =3D 0; i < grp->info->num_des; i++) { + struct scmi_telemetry_de *de =3D rinfo->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_resp_process(struct telemetry_info *ti, + struct scmi_telemetry_de *de, + void *r, bool is_group) +{ + struct scmi_msg_resp_telemetry_de_configure *resp =3D r; + u32 sid =3D le32_to_cpu(resp->shmti_id); + + /* Update DE SHMTI and offset, if applicable */ + if (IS_SHMTI_ID_VALID(sid)) { + if (sid >=3D ti->num_shmti) + return -EPROTO; + + /* + * Update SHMTI/offset while skipping non-SHMTI-DEs like + * FCs and notif-only. + */ + if (!is_group) { + struct telemetry_de *tde; + struct payload *payld; + u32 offs; + + offs =3D le32_to_cpu(resp->tdcf_de_offset); + if (offs >=3D ti->shmti[sid].len - de->info->data_sz) + return -EPROTO; + + tde =3D to_tde(de); + tde->base =3D ti->shmti[sid].base; + tde->offset =3D offs; + /* A handy reference to the Epilogue updated */ + tde->eplg =3D SHMTI_EPLG(&ti->shmti[sid]); + + payld =3D tde->base + tde->offset; + if (USE_BLK_TS(payld) && !tde->bts) { + tde->bts =3D scmi_telemetry_blkts_bind(ti->ph->dev, + &ti->shmti[sid], + payld, + &ti->xa_bts); + if (WARN_ON(!tde->bts)) + return -EPROTO; + } + } else { + int ret; + + /* + * A full SHMTI scan is needed when enabling a + * group or its timestamps in order to retrieve + * offsets: node that when group-timestamp is + * enabled for composing DEs a re-scan is needed + * since some DEs could have been relocated due + * to lack of space in the TDCF. + */ + ret =3D scmi_telemetry_shmti_scan(ti, sid, 0, SCAN_UPDATE); + if (ret) + dev_warn(ti->ph->dev, + "Failed group-scan of SHMTI ID:%d\n", sid); + } + } else if (!is_group) { + struct telemetry_de *tde; + + tde =3D to_tde(de); + if (tde->bts) { + /* Unlink the related BLK_TS on disable */ + scmi_telemetry_blkts_put(ti->ph->dev, tde->bts); + tde->bts =3D NULL; + } + } + + 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 *obj) +{ + 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; + struct scmi_telemetry_de *de; + unsigned int obj_id; + struct scmi_xfer *t; + int ret; + + if (!enabled_state || !tstamp_enabled_state) + return -EINVAL; + + /* Is anything to do at all on this DE ? */ + if (!is_group && (!enable || *enable =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; + } + + if (!is_group) { + de =3D obj; + obj_id =3D de->info->id; + } else { + grp =3D obj; + obj_id =3D grp->info->id; + } + + 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(obj_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) { + ret =3D scmi_telemetry_state_set_resp_process(ti, de, resp, is_group); + if (!ret) { + /* 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); + } + } + + 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) +{ + struct telemetry_info *ti =3D ph->get_priv(ph); + bool *enabled_state, *tstamp_enabled_state; + struct scmi_telemetry_res_info *rinfo; + void *obj; + + rinfo =3D ti->res_get(ti); + 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.base.num_groups) + return -EINVAL; + + grp =3D &rinfo->grps[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 telemetry_info *ti =3D ph->get_priv(ph); + struct scmi_msg_telemetry_de_configure *msg; + struct scmi_telemetry_res_info *rinfo; + struct scmi_xfer *t; + int ret; + + rinfo =3D ti->res_get(ti); + 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) { + for (int i =3D 0; i < ti->info.base.num_des; i++) + rinfo->des[i]->enabled =3D false; + + if (is_group) { + for (int i =3D 0; i < ti->info.base.num_groups; i++) + rinfo->grps[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) +{ + enum scmi_telemetry_collection *current_mode, next_mode; + struct telemetry_info *ti =3D ph->get_priv(ph); + struct scmi_msg_telemetry_config_set *msg; + unsigned int *active_update_interval; + struct scmi_xfer *t; + bool tlm_enable; + u32 interval; + int ret; + + if (mode && *mode =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.base.num_gro= ups) + return -EINVAL; + + if (res_id =3D=3D SCMI_TLM_GRP_INVALID || grp_ignore) { + active_update_interval =3D &ti->info.active_update_interval; + current_mode =3D &ti->info.current_mode; + } else { + struct scmi_telemetry_res_info *rinfo; + + rinfo =3D ti->res_get(ti); + active_update_interval =3D + &rinfo->grps[res_id].active_update_interval; + current_mode =3D &rinfo->grps[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(*active_update_interval); + else + interval =3D *update_interval_ms; + + 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_SET_SELECTOR_ALL : + TELEMETRY_SET_SELECTOR_GROUP; + msg->control |=3D TELEMETRY_MODE_SET(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) + *active_update_interval =3D interval; + } + + ph->xops->xfer_put(ph, t); + + return ret; +} + +static inline void scmi_telemetry_de_data_fc_read(struct telemetry_de *tde, + u64 *tstamp, u64 *val) +{ + struct fc_tsline __iomem *fc =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; + } +} + +static void scmi_telemetry_scan_update(struct telemetry_info *ti, u64 ts) +{ + struct telemetry_de *tde; + + /* Scan all SHMTIs ... */ + for (int id =3D 0; id < ti->num_shmti; id++) { + int ret; + + ret =3D scmi_telemetry_shmti_scan(ti, id, ts, SCAN_LOOKUP); + if (ret) + dev_warn(ti->ph->dev, + "Failed update-scan of SHMTI ID:%d\n", id); + } + + if (!ti->info.fc_support) + return; + + /* Need to enumerate resources to access fastchannels */ + ti->res_get(ti); + list_for_each_entry(tde, &ti->fcs_des, item) { + u64 val, tstamp; + + if (!tde->de.enabled) + continue; + + scmi_telemetry_de_data_fc_read(tde, &tstamp, &val); + + guard(mutex)(&tde->mtx); + tde->last_val =3D val; + if (tde->de.tstamp_enabled) + tde->last_ts =3D tstamp; + else + tde->last_ts =3D 0; + } +} + +/* + * TDCF and TS Line Management Notes + * --------------------------------- + * (from a chat with ATG) + * + * TCDF Payload Metadata notable bits: + * - Bit[3]: USE BLK Tstamp + * - Bit[2]: USE LINE Tstamp + * - Bit[1]: Tstamp VALID + * + * CASE_1: + * ------- + * + A DE is enabled with timestamp disabled, so the TS fields are + * NOT present + * -> BIT[3:1] =3D 000b + * + * - 1/A LINE_TSTAMP + * ------------------ + * + that DE is then 're-enabled' with TS: so it was ON, it remains + * ON but using DE_CONFIGURE I now enabled also TS, so the + * platform relocates it at the end of the SHMTI and return the + * new offset + * -> BIT[3:1] =3D 011b + * + * - 1/B BLK_TSTAMP + * ------------------ + * + that DE is then 're-enabled' with BLK TS: so it was ON, it + * remains ON but using DE_CONFIGURE, we now also enabled the TS, + * so the platform will: + * - IF a preceding BLK_TS line exist (with same clock freq) + * it relocates the DE at the end of the SHMTI and return the + * new offset (if there is enough room, if not in another SHMTI) + * - IF a preceding BLK_TS line DOES NOT exist (same clock freq) + * it creates a new BLK_TS line at the end of the SHMTI and then + * relocates the DE after the new BLK_TS and return the + * new offset (if there is enough room, if not in another SHMTI) + * -> BIT[3:1] =3D 101b + * + * + the hole left from the relocated DE can be reused by the platform + * to fit another equally sized DE. (i.e. without shuffling around any + * other enabled DE, since that would cause a change of the known offset) + * + * CASE_2: + * ------- + * + A DE is enabled with LINE timestamp enabled, so the TS_Line is there + * -> BIT[3:1] =3D 011b + * + that DE has its timestamp disabled: again, you can do this without + * disabling it fully but just disabling the TS, so now that TS_line + * fields are still physiclly there but NOT valid + * -> BIT[3:1] =3D 010b + * + the hole from the timestamp remain there unused until + * - you enable again the TS so the hole is used again + * -> BIT[3:1] =3D 011b + * OR + * - you disable fully the DE and then re-enable it with the TS + * -> potentially CASE_1 the DE is relocated on enable + * + same kind of dynamic applies if the DE had a BLK_TS line + */ +static int scmi_telemetry_tdcf_de_parse(struct telemetry_de *tde, + u64 *tstamp, u64 *val) +{ + struct tdcf __iomem *tdcf =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; + + /* Has anything changed at all at the SHMTI level ? */ + scoped_guard(mutex, &tde->mtx) { + if (tde->last_magic =3D=3D startm) { + *val =3D tde->last_val; + if (tstamp) + *tstamp =3D tde->last_ts; + return 0; + } + } + + payld =3D tde->base + tde->offset; + if (DATA_INVALID(payld)) + return -EINVAL; + + if (IS_BLK_TS(payld)) + return -EINVAL; + + if (PAYLD_ID(payld) !=3D tde->de.info->id) + return -EINVAL; + + /* Data is always valid since NOT handling BLK TS lines here */ + *val =3D LINE_DATA_GET(&payld->l); + /* Collect the right TS */ + if (tstamp) { + if (!TS_VALID(payld)) { + *tstamp =3D 0; + } else if (USE_LINE_TS(payld)) { + *tstamp =3D LINE_TSTAMP_GET(&payld->tsl); + } else if (USE_BLK_TS(payld)) { + /* + * A valid line using BLK_TS should have been + * initialized with the related BLK_TS when + * enabled. + */ + if (WARN_ON(!tde->bts)) + return -EPROTO; + *tstamp =3D scmi_telemetry_blkts_read(startm, + tde->bts); + } + } + + endm =3D TDCF_END_SEQ_GET(tde->eplg); + } while (startm !=3D endm && --retries); + + if (startm !=3D endm) + return -EPROTO; + + guard(mutex)(&tde->mtx); + tde->last_magic =3D startm; + tde->last_val =3D *val; + if (tstamp) + tde->last_ts =3D *tstamp; + + return 0; +} + +static int scmi_telemetry_de_read(struct telemetry_de *tde, u64 *tstamp, u= 64 *val) +{ + if (!tde->de.fc_support) + return scmi_telemetry_tdcf_de_parse(tde, tstamp, val); + + scmi_telemetry_de_data_fc_read(tde, tstamp, val); + + return 0; +} + +static int scmi_telemetry_de_collect(struct telemetry_info *ti, + struct scmi_telemetry_de *de, + u64 *tstamp, u64 *val) +{ + struct telemetry_de *tde =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_read(tde, tstamp, val); +} + +static int scmi_telemetry_de_cached_read(struct telemetry_info *ti, + struct scmi_telemetry_de *de, + u64 *tstamp, u64 *val) +{ + struct telemetry_de *tde =3D to_tde(de); + + if (!de->enabled) + return -EINVAL; + + guard(mutex)(&tde->mtx); + *val =3D tde->last_val; + if (tstamp) + *tstamp =3D tde->last_ts; + + return 0; +} + +static int scmi_telemetry_de_data_read(const struct scmi_protocol_handle *= ph, + struct scmi_telemetry_de_sample *sample) +{ + struct telemetry_info *ti =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) +{ + struct scmi_telemetry_res_info *rinfo; + int max_samples; + + max_samples =3D *num_samples; + *num_samples =3D 0; + + rinfo =3D ti->res_get(ti); + for (int i =3D 0; i < rinfo->num_des; i++) { + struct scmi_telemetry_de *de; + u64 val, tstamp; + int ret; + + de =3D rinfo->des[i]; + if (grp_id !=3D SCMI_TLM_GRP_INVALID && + (!de->grp || de->grp->info->id !=3D grp_id)) + continue; + + ret =3D scmi_telemetry_de_cached_read(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->info->id; + + (*num_samples)++; + } + + return 0; +} + +static int scmi_telemetry_des_bulk_read(const struct scmi_protocol_handle = *ph, + int grp_id, int *num_samples, + struct scmi_telemetry_de_sample *samples) +{ + struct telemetry_info *ti =3D ph->get_priv(ph); + + if (!ti->info.enabled || !num_samples || !samples) + return -EINVAL; + + /* Trigger a full SHMTIs & FCs scan */ + scmi_telemetry_scan_update(ti, 0); + + /* Collect all last cached values */ + return scmi_telemetry_samples_collect(ti, grp_id, num_samples, samples); +} + +static void +scmi_telemetry_msg_payld_process(struct telemetry_info *ti, + unsigned int num_dwords, u32 *dwords, + ktime_t timestamp) +{ + struct scmi_telemetry_res_info *rinfo; + u32 next =3D 0; + + rinfo =3D ti->res_get(ti); + if (!rinfo->fully_enumerated) { + dev_warn_once(ti->ph->dev, + "Cannot process DEs in message payload. Drop.\n"); + return; + } + + while (next < num_dwords) { + struct payload *payld =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->ph->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->ph->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 in notification payloads */ + 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.base.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_SET_SELECTOR_ALL : + TELEMETRY_SET_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); + /* Scan and update SMHTIs and FCs */ + scmi_telemetry_scan_update(ti, 0); + + /* Collect all last cached values */ + 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) +{ + return -EOPNOTSUPP; +} + +static void scmi_telemetry_local_resources_reset(struct telemetry_info *ti) +{ + struct scmi_telemetry_res_info *rinfo; + + /* Get rinfo as it is...without triggering an enumeration */ + rinfo =3D __scmi_telemetry_resources_get(ti); + /* Clear all local state...*/ + for (int i =3D 0; i < rinfo->num_des; i++) { + rinfo->des[i]->enabled =3D false; + rinfo->des[i]->tstamp_enabled =3D false; + } + for (int i =3D 0; i < rinfo->num_groups; i++) { + rinfo->grps[i].enabled =3D false; + rinfo->grps[i].tstamp_enabled =3D false; + rinfo->grps[i].current_mode =3D SCMI_TLM_ONDEMAND; + rinfo->grps[i].active_update_interval =3D 0; + } +} + +static int scmi_telemetry_reset(const struct scmi_protocol_handle *ph) +{ + struct scmi_xfer *t; + int ret; + + 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); + + scmi_telemetry_local_resources_reset(ti); + /* Fetch agaon states state from platform.*/ + ret =3D scmi_telemetry_initial_state_lookup(ti); + if (ret) + dev_warn(ph->dev, + FW_BUG "Cannot retrieve initial state after reset.\n"); + } + + ph->xops->xfer_put(ph, t); + + return ret; +} + +static const struct scmi_telemetry_proto_ops tlm_proto_ops =3D { + .info_get =3D scmi_telemetry_info_get, + .de_lookup =3D scmi_telemetry_de_lookup, + .res_get =3D scmi_telemetry_resources_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 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->ph->dev, "Bad Telemetry update notification - ret: %dn", + er->status); + return NOTIFY_DONE; + } + + /* Lookup the embedded DEs in the notification payload ... */ + if (er->num_dwords) + scmi_telemetry_msg_payld_process(ti, er->num_dwords, + er->dwords, er->timestamp); + + /* ...scan the SHMTI/FCs for any other DE updates. */ + if (ti->streaming_mode) + scmi_telemetry_scan_update(ti, er->timestamp); + + return NOTIFY_OK; +} + +static int scmi_telemetry_resources_alloc(struct telemetry_info *ti) +{ + /* Array to hold pointers to discovered DEs */ + struct scmi_telemetry_de **des __free(kfree) =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); + + ti->rinfo =3D no_free_ptr(rinfo); + + return 0; +} + +static void scmi_telemetry_resources_free(void *arg) +{ + struct telemetry_info *ti =3D arg; + + kfree(ti->tdes); + kfree(ti->rinfo->des); + kfree(ti->rinfo->dei_store); + kfree(ti->rinfo->grps); + kfree(ti->rinfo->grps_store); + + kfree(ti->rinfo); +} + +static struct scmi_telemetry_res_info * +__scmi_telemetry_resources_get(struct telemetry_info *ti) +{ + return ACCESS_PRIVATE(ti, rinfo); +} + +static struct scmi_telemetry_res_info * +scmi_telemetry_resources_enumerate(struct telemetry_info *ti) +{ + struct device *dev =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; + } + + ti->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 ti->rinfo; +} + +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; + u32 version; + int ret; + + ret =3D ph->xops->version_get(ph, &version); + if (ret) + return ret; + + dev_dbg(dev, "Telemetry Version %d.%d\n", + PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version)); + + ti =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_warn(dev, FW_BUG "Cannot enumerate SHMTIs. Carry-on.\n"); + + ret =3D scmi_telemetry_initial_state_lookup(ti); + if (ret) + dev_warn(dev, FW_BUG "Cannot retrieve initial state. Carry-on.\n"); + + ti->info.base.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 c6efe4f371ac..d58b81ffd81e 100644 --- a/include/linux/scmi_protocol.h +++ b/include/linux/scmi_protocol.h @@ -2,17 +2,21 @@ /* * SCMI Message Protocol driver header * - * Copyright (C) 2018-2021 ARM Ltd. + * Copyright (C) 2018-2026 ARM Ltd. */ =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,178 @@ 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_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. + * @de_lookup: get a specific DE descriptor from the DE id. + * @res_get: get a reference to the Telemetry resources descriptor. + * @state_get: retrieve the specific DE or GROUP state. + * @state_set: enable/disable the specific DE or GROUP with or without 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); + const struct scmi_telemetry_de __must_check *(*de_lookup) + (const struct scmi_protocol_handle *ph, u32 id); + const struct scmi_telemetry_res_info __must_check *(*res_get) + (const struct scmi_protocol_handle *ph); + int (*state_get)(const struct scmi_protocol_handle *ph, + u32 id, bool *enabled, bool *tstamp_enabled); + int (*state_set)(const struct scmi_protocol_handle *ph, + bool is_group, u32 id, bool *enable, bool *tstamp); + int (*all_disable)(const struct scmi_protocol_handle *ph, bool group); + int (*collection_configure)(const struct scmi_protocol_handle *ph, + unsigned int res_id, bool grp_ignore, + bool *enable, + unsigned int *update_interval_ms, + enum scmi_telemetry_collection *mode); + int (*de_data_read)(const struct scmi_protocol_handle *ph, + struct scmi_telemetry_de_sample *sample); + int __must_check (*des_bulk_read)(const struct scmi_protocol_handle *ph, + int grp_id, int *num_samples, + struct scmi_telemetry_de_sample *samples); + int __must_check (*des_sample_get)(const struct scmi_protocol_handle *ph, + int grp_id, int *num_samples, + struct scmi_telemetry_de_sample *samples); + int (*config_get)(const struct scmi_protocol_handle *ph, bool *enabled, + int *mode, u32 *update_interval); + int (*reset)(const struct scmi_protocol_handle *ph); +}; + /** * struct scmi_notify_ops - represents notifications' operations provided= by * SCMI core @@ -926,6 +1102,7 @@ enum scmi_std_protocol { SCMI_PROTOCOL_VOLTAGE =3D 0x17, SCMI_PROTOCOL_POWERCAP =3D 0x18, SCMI_PROTOCOL_PINCTRL =3D 0x19, + SCMI_PROTOCOL_TELEMETRY =3D 0x1b, SCMI_PROTOCOL_LAST =3D 0x7f, }; =20 @@ -1027,6 +1204,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 +1292,12 @@ struct scmi_powercap_meas_changed_report { unsigned int domain_id; unsigned int power; }; + +struct scmi_telemetry_update_report { + ktime_t timestamp; + unsigned int agent_id; + int status; + unsigned int num_dwords; + unsigned int dwords[]; +}; #endif /* _LINUX_SCMI_PROTOCOL_H */ --=20 2.52.0 From nobody Mon Feb 9 01:43:04 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 19694396D0E; Wed, 14 Jan 2026 11:47:44 +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=1768391270; cv=none; b=h1ztZN2kRD5fIsdSQNPpsmvw9OAi+U3H+irmN690ArWBate6su547v18iZ84r/oYstxIQU46DB+0f6ptiE2gN+8gidigIkox/3AlVOtMUVmNFosUJRyHO44tB+HaCqt3z7DoAHRK3XY0gZpQlOxXWdm1lCcbSn7kG0lkaP+7mZA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768391270; c=relaxed/simple; bh=D0g2vcVrBfhGyNmNbU3fjKfhqXSK4OK8D4ZY2a9U8hw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=HF1CXBk+Lxrc349jxpC3ZBcx61K8dk6Xno/Cr/GNCJSb+shVyPM4YAd/B5BumspYkY9rq7nAyEQeWj7/Kdf3kMxPdWBKhsz12agS/N371IvOX1gTUgj6piEj1hL3yyvsTa+M9gJu39xHqWLGdIZKu0ncNrES+ltWru76zc2ziwQ= 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 B384D497; Wed, 14 Jan 2026 03:47:37 -0800 (PST) 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 E6C573F59E; Wed, 14 Jan 2026 03:47:40 -0800 (PST) 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 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, dan.carpenter@linaro.org, d-gole@ti.com, jonathan.cameron@huawei.com, elif.topuz@arm.com, lukasz.luba@arm.com, philip.radford@arm.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH v2 06/17] include: trace: Add Telemetry trace events Date: Wed, 14 Jan 2026 11:46:10 +0000 Message-ID: <20260114114638.2290765-7-cristian.marussi@arm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260114114638.2290765-1-cristian.marussi@arm.com> References: <20260114114638.2290765-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 custom traces to report Telemetry failed accesses and to report when DE values are updated internally after a notification is processed. Signed-off-by: Cristian Marussi --- include/trace/events/scmi.h | 48 ++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/include/trace/events/scmi.h b/include/trace/events/scmi.h index 703b7bb68e44..b70b26e467b8 100644 --- a/include/trace/events/scmi.h +++ b/include/trace/events/scmi.h @@ -7,7 +7,8 @@ =20 #include =20 -#define TRACE_SCMI_MAX_TAG_LEN 6 +#define TRACE_SCMI_MAX_TAG_LEN 6 +#define TRACE_SCMI_TLM_MAX_TAG_LEN 16 =20 TRACE_EVENT(scmi_fc_call, TP_PROTO(u8 protocol_id, u8 msg_id, u32 res_id, u32 val1, u32 val2), @@ -180,6 +181,51 @@ TRACE_EVENT(scmi_msg_dump, __entry->tag, __entry->msg_id, __entry->seq, __entry->status, __print_hex_str(__get_dynamic_array(cmd), __entry->len)) ); + +TRACE_EVENT(scmi_tlm_access, + TP_PROTO(u64 de_id, unsigned char *tag, u64 startm, u64 endm), + TP_ARGS(de_id, tag, startm, endm), + + TP_STRUCT__entry( + __field(u64, de_id) + __array(char, tag, TRACE_SCMI_TLM_MAX_TAG_LEN) + __field(u64, startm) + __field(u64, endm) + ), + + TP_fast_assign( + __entry->de_id =3D de_id; + strscpy(__entry->tag, tag, TRACE_SCMI_TLM_MAX_TAG_LEN); + __entry->startm =3D startm; + __entry->endm =3D endm; + ), + + TP_printk("de_id=3D0x%llX [%s] - startm=3D%016llX endm=3D%016llX", + __entry->de_id, __entry->tag, __entry->startm, __entry->endm) +); + +TRACE_EVENT(scmi_tlm_collect, + TP_PROTO(u64 ts, u64 de_id, u64 value, unsigned char *tag), + TP_ARGS(ts, de_id, value, tag), + + TP_STRUCT__entry( + __field(u64, ts) + __field(u64, de_id) + __field(u64, value) + __array(char, tag, TRACE_SCMI_TLM_MAX_TAG_LEN) + ), + + TP_fast_assign( + __entry->ts =3D ts; + __entry->de_id =3D de_id; + __entry->value =3D value; + strscpy(__entry->tag, tag, TRACE_SCMI_TLM_MAX_TAG_LEN); + ), + + TP_printk("ts=3D%llu de_id=3D0x%04llX value=3D%016llu [%s]", + __entry->ts, __entry->de_id, __entry->value, __entry->tag) +); + #endif /* _TRACE_SCMI_H */ =20 /* This part must be outside protection */ --=20 2.52.0 From nobody Mon Feb 9 01:43:04 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id B2AB7396B63; Wed, 14 Jan 2026 11:47:48 +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=1768391270; cv=none; b=s2crQgWNmUAvsc51O/VSbNa8E7MhDN0a6JMhaIuO6tmGjjkEr0GchyHTmFEG0n3J7OA1PNpxCa/z0xpJz/Pxxm8eQusjonBF+OuFkdbmvHJ06S4mrT7g+dAmyCbuPo6IuRwXluRm+pspa0vORxBf5v85zduI0W0K/VpyVH7SOJU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768391270; c=relaxed/simple; bh=vUACGgHmNQmYY9fzmjU05P8Eou5F6psC41/zyArJWmo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=bxWJWrIPhP60EQdJ/ESIZ550L43InDC7d7RXXNwkNk25fxf8p24w8L9tJlq6haU1FXF5+wRzK0avLRzquKQA2M9O9ydVBzXwR/sTnTcoXW6lNUl0ndk2ZyROKBtXu5Qfvb7bdC+5iEoYwMwmNPhCGKCUZB52YrvMNVTxLtWNtPE= 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 7CF09339; Wed, 14 Jan 2026 03:47:41 -0800 (PST) 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 CA3DD3F59E; Wed, 14 Jan 2026 03:47:44 -0800 (PST) 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 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, dan.carpenter@linaro.org, d-gole@ti.com, jonathan.cameron@huawei.com, elif.topuz@arm.com, lukasz.luba@arm.com, philip.radford@arm.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH v2 07/17] firmware: arm_scmi: Use new Telemetry traces Date: Wed, 14 Jan 2026 11:46:11 +0000 Message-ID: <20260114114638.2290765-8-cristian.marussi@arm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260114114638.2290765-1-cristian.marussi@arm.com> References: <20260114114638.2290765-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" Track failed SHMTI accesses and received notifications. Signed-off-by: Cristian Marussi --- drivers/firmware/arm_scmi/telemetry.c | 57 ++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_s= cmi/telemetry.c index 16bcdcdc1dc3..443e032a3553 100644 --- a/drivers/firmware/arm_scmi/telemetry.c +++ b/drivers/firmware/arm_scmi/telemetry.c @@ -25,6 +25,8 @@ #include "protocols.h" #include "notify.h" =20 +#include + /* Updated only after ALL the mandatory features for that version are merg= ed */ #define SCMI_PROTOCOL_SUPPORTED_VERSION 0x10000 =20 @@ -1366,8 +1368,10 @@ static void scmi_telemetry_tdcf_blkts_parse(struct t= elemetry_info *ti, =20 /* Check for spec compliance */ if (USE_LINE_TS(payld) || USE_BLK_TS(payld) || - DATA_INVALID(payld) || (PAYLD_ID(payld) !=3D 0)) + DATA_INVALID(payld) || (PAYLD_ID(payld) !=3D 0)) { + trace_scmi_tlm_access(0, "BLK_TS_INVALID", 0, 0); return; + } =20 /* A BLK_TS descriptor MUST be returned: it is found or it is crated */ bts =3D scmi_telemetry_blkts_lookup(ti->ph->dev, &ti->xa_bts, payld); @@ -1376,6 +1380,9 @@ static void scmi_telemetry_tdcf_blkts_parse(struct te= lemetry_info *ti, =20 /* Update the descriptor with the lastest TS*/ scmi_telemetry_blkts_update(shmti->last_magic, bts); + + trace_scmi_tlm_collect(bts->last_ts, (u64)payld, + bts->last_magic, "SHMTI_BLK_TS"); } =20 static void scmi_telemetry_tdcf_data_parse(struct telemetry_info *ti, @@ -1393,8 +1400,10 @@ static void scmi_telemetry_tdcf_data_parse(struct te= lemetry_info *ti, /* Is thi DE ID know ? */ tde =3D scmi_telemetry_tde_lookup(ti, de_id); if (!tde) { - if (mode !=3D SCAN_DISCOVERY) + if (mode !=3D SCAN_DISCOVERY) { + trace_scmi_tlm_access(de_id, "DE_INVALID", 0, 0); return; + } =20 /* In SCAN_DISCOVERY mode we allocate new DEs for unknown IDs */ tde =3D scmi_telemetry_tde_get(ti, de_id); @@ -1462,6 +1471,8 @@ static void scmi_telemetry_tdcf_data_parse(struct tel= emetry_info *ti, tde->last_ts =3D tstamp; else tde->last_ts =3D 0; + + trace_scmi_tlm_collect(0, tde->de.info->id, tde->last_val, "SHMTI_DE_UPDT= "); } =20 static int scmi_telemetry_tdcf_line_parse(struct telemetry_info *ti, @@ -1507,8 +1518,10 @@ static int scmi_telemetry_shmti_scan(struct telemetr= y_info *ti, fsleep((SCMI_TLM_TDCF_MAX_RETRIES - retries) * 1000); =20 startm =3D TDCF_START_SEQ_GET(tdcf); - if (IS_BAD_START_SEQ(startm)) + if (IS_BAD_START_SEQ(startm)) { + trace_scmi_tlm_access(0, "MSEQ_BADSTART", startm, 0); continue; + } =20 /* On a BAD_SEQ this will be updated on the next attempt */ shmti->last_magic =3D startm; @@ -1520,18 +1533,25 @@ static int scmi_telemetry_shmti_scan(struct telemet= ry_info *ti, =20 used_qwords =3D scmi_telemetry_tdcf_line_parse(ti, next, shmti, mode); - if (qwords < used_qwords) + if (qwords < used_qwords) { + trace_scmi_tlm_access(PAYLD_ID(next), + "BAD_QWORDS", startm, 0); return -EINVAL; + } =20 next +=3D used_qwords * 8; qwords -=3D used_qwords; } =20 endm =3D TDCF_END_SEQ_GET(eplg); + if (startm !=3D endm) + trace_scmi_tlm_access(0, "MSEQ_MISMATCH", startm, endm); } while (startm !=3D endm && --retries); =20 - if (startm !=3D endm) + if (startm !=3D endm) { + trace_scmi_tlm_access(0, "TDCF_SCAN_FAIL", startm, endm); return -EPROTO; + } =20 return 0; } @@ -1923,6 +1943,8 @@ static void scmi_telemetry_scan_update(struct telemet= ry_info *ti, u64 ts) tde->last_ts =3D tstamp; else tde->last_ts =3D 0; + + trace_scmi_tlm_collect(ts, tde->de.info->id, tde->last_val, "FC_UPDATE"); } } =20 @@ -2001,8 +2023,11 @@ static int scmi_telemetry_tdcf_de_parse(struct telem= etry_de *tde, fsleep((SCMI_TLM_TDCF_MAX_RETRIES - retries) * 1000); =20 startm =3D TDCF_START_SEQ_GET(tdcf); - if (IS_BAD_START_SEQ(startm)) + if (IS_BAD_START_SEQ(startm)) { + trace_scmi_tlm_access(tde->de.info->id, "MSEQ_BADSTART", + startm, 0); continue; + } =20 /* Has anything changed at all at the SHMTI level ? */ scoped_guard(mutex, &tde->mtx) { @@ -2018,11 +2043,16 @@ static int scmi_telemetry_tdcf_de_parse(struct tele= metry_de *tde, if (DATA_INVALID(payld)) return -EINVAL; =20 - if (IS_BLK_TS(payld)) + if (IS_BLK_TS(payld)) { + trace_scmi_tlm_access(tde->de.info->id, + "BAD_DE_META", 0, 0); return -EINVAL; + } =20 - if (PAYLD_ID(payld) !=3D tde->de.info->id) + if (PAYLD_ID(payld) !=3D tde->de.info->id) { + trace_scmi_tlm_access(tde->de.info->id, "DE_INVALID", 0, 0); return -EINVAL; + } =20 /* Data is always valid since NOT handling BLK TS lines here */ *val =3D LINE_DATA_GET(&payld->l); @@ -2046,10 +2076,16 @@ static int scmi_telemetry_tdcf_de_parse(struct tele= metry_de *tde, } =20 endm =3D TDCF_END_SEQ_GET(tde->eplg); + if (startm !=3D endm) + trace_scmi_tlm_access(tde->de.info->id, "MSEQ_MISMATCH", + startm, endm); } while (startm !=3D endm && --retries); =20 - if (startm !=3D endm) + if (startm !=3D endm) { + trace_scmi_tlm_access(tde->de.info->id, "TDCF_DE_FAIL", + startm, endm); return -EPROTO; + } =20 guard(mutex)(&tde->mtx); tde->last_magic =3D startm; @@ -2230,6 +2266,9 @@ scmi_telemetry_msg_payld_process(struct telemetry_inf= o *ti, tde->last_ts =3D LINE_TSTAMP_GET(&payld->tsl); else tde->last_ts =3D 0; + + trace_scmi_tlm_collect(timestamp, tde->de.info->id, tde->last_val, + "MESSAGE"); } } =20 --=20 2.52.0 From nobody Mon Feb 9 01:43:04 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id B58B1396D3B; Wed, 14 Jan 2026 11:47:52 +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=1768391284; cv=none; b=kRlfpqu2TKQlXZeiMTWy7jKgRvuG1RiQXbLTQTlTJfwOxS3J3YatfiSS/FuXzPOMdQ1lkd4BSGSdbV6wO6qB2W4Kcs9/IaKdegV7TnGhVCvtTjlT1+Kv0UECe0+L79D6f5pz0dk3sS+vsslfPP44aqKC2BY2+FZ5eYCc7D9c4Hk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768391284; c=relaxed/simple; bh=nFMLkI+0iEJ6nEDbeT6P9ptIaNNpYN9xERbZGiTubRI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=SQ76d2HxlHaEeg5Ylm31t27B9CB4GOWr+vLYPDPrWZY23sBvuldj+kPNG9VCMnTZp2ehQI3flUpmqZ5WgChLZKJgTQQmtOn0otAd3mGgrEEBzzvCdoGHR6iuHG8CnF6owdtJOsIYBnHP6lRDheLrNLK8h34B7YFq0abtG4rsnYM= 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 562BF339; Wed, 14 Jan 2026 03:47:45 -0800 (PST) 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 95BB33F59E; Wed, 14 Jan 2026 03:47:48 -0800 (PST) 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 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, dan.carpenter@linaro.org, d-gole@ti.com, jonathan.cameron@huawei.com, elif.topuz@arm.com, lukasz.luba@arm.com, philip.radford@arm.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH v2 08/17] firmware: arm_scmi: Add System Telemetry driver Date: Wed, 14 Jan 2026 11:46:12 +0000 Message-ID: <20260114114638.2290765-9-cristian.marussi@arm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260114114638.2290765-1-cristian.marussi@arm.com> References: <20260114114638.2290765-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 a new SCMI System Telemetry driver which gathers platform Telemetry data through the new the SCMI Telemetry protocol and expose all of the discovered Telemetry data events on a dedicated pseudo-filesystem that can be used to interactively configure SCMI Telemetry and access its provided data. Signed-off-by: Cristian Marussi --- v1 --> v2 - Harden System Telemetry writes, DO report errors - New 'secs[, ]' for current_interval_update_ms - Use new mount_api based on fs_context - Use new res_get() operation to make use of new accessors - Move des/groups enumeration to mount time - Support partial out-of-spec FW lacking some cmds (best effort) - Reworked init/exit sequence - Using dev_err_probe - Reworked probing races handling - Avoid disabling telemetry on module removal and drop remove() code --- drivers/firmware/arm_scmi/Kconfig | 10 + drivers/firmware/arm_scmi/Makefile | 1 + .../firmware/arm_scmi/scmi_system_telemetry.c | 1436 +++++++++++++++++ 3 files changed, 1447 insertions(+) create mode 100644 drivers/firmware/arm_scmi/scmi_system_telemetry.c diff --git a/drivers/firmware/arm_scmi/Kconfig b/drivers/firmware/arm_scmi/= Kconfig index e3fb36825978..9e51b3cd0c93 100644 --- a/drivers/firmware/arm_scmi/Kconfig +++ b/drivers/firmware/arm_scmi/Kconfig @@ -99,4 +99,14 @@ config ARM_SCMI_POWER_CONTROL called scmi_power_control. Note this may needed early in boot to catch early shutdown/reboot SCMI requests. =20 +config ARM_SCMI_SYSTEM_TELEMETRY + tristate "SCMI System Telemetry driver" + depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF) + help + This enables SCMI Systemn Telemetry support that allows userspace to + retrieve ARM Telemetry data made available via SCMI. + + This driver can also be built as a module. If so, the module will be + called scmi_system_telemetry. + endmenu diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi= /Makefile index fe55b7aa0707..20f8d55840a5 100644 --- a/drivers/firmware/arm_scmi/Makefile +++ b/drivers/firmware/arm_scmi/Makefile @@ -18,3 +18,4 @@ obj-$(CONFIG_ARM_SCMI_PROTOCOL) +=3D scmi-core.o obj-$(CONFIG_ARM_SCMI_PROTOCOL) +=3D scmi-module.o =20 obj-$(CONFIG_ARM_SCMI_POWER_CONTROL) +=3D scmi_power_control.o +obj-$(CONFIG_ARM_SCMI_SYSTEM_TELEMETRY) +=3D scmi_system_telemetry.o diff --git a/drivers/firmware/arm_scmi/scmi_system_telemetry.c b/drivers/fi= rmware/arm_scmi/scmi_system_telemetry.c new file mode 100644 index 000000000000..b48f2d4eecae --- /dev/null +++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c @@ -0,0 +1,1436 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SCMI - System Telemetry Driver + * + * Copyright (C) 2026 ARM Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TLM_FS_MAGIC 0x75C01C80 +#define TLM_FS_NAME "stlmfs" +#define TLM_FS_MNT "arm_telemetry" + +#define MAX_AVAILABLE_INTERV_CHAR_LENGTH 25 +#define MAX_BULK_LINE_CHAR_LENGTH 64 + +static struct kmem_cache *stlmfs_inode_cachep; + +static DEFINE_MUTEX(stlmfs_mtx); +static struct super_block *stlmfs_sb; + +static atomic_t scmi_tlm_instance_count =3D ATOMIC_INIT(0); + +struct scmi_tlm_setup; + +struct scmi_tlm_priv { + char *buf; + size_t buf_sz; + int buf_len; + int (*bulk_retrieve)(struct scmi_tlm_setup *tsp, + int res_id, int *num_samples, + struct scmi_telemetry_de_sample *samples); +}; + +/** + * struct scmi_tlm_buffer - Output Telemetry buffer descriptor + * @used: Current number of used bytes in @buf + * @buf: Actual buffer for output data + * + * This describes an output buffer which will be made available to each r/w + * entry file_operations. + */ +struct scmi_tlm_buffer { + size_t used; +#define SCMI_TLM_MAX_BUF_SZ 128 + unsigned char buf[SCMI_TLM_MAX_BUF_SZ]; +}; + +/** + * struct scmi_tlm_setup - Telemetry setup descriptor + * @dev: A reference to the related device + * @ph: A reference to the protocol handle to be used with the ops + * @rinfo: A reference to the resource info descriptor + * @ops: A reference to the protocol ops + */ +struct scmi_tlm_setup { + struct device *dev; + struct scmi_protocol_handle *ph; + const struct scmi_telemetry_res_info __private *rinfo; + const struct scmi_telemetry_proto_ops *ops; +}; + +/** + * struct scmi_tlm_class - Telemetry class descriptor + * @name: A string to be used for filesystem dentry name. + * @mode: Filesystem mode mask. + * @flags: Optional misc flags that can slighly modify provided @f_op beha= viour; + * this way the same @scmi_tlm_class can be used to describe multiple + * entries in the filesystem whose @f_op behaviour is very similar. + * @f_op: Optional file ops attached to this object. Used to initialized i= nodes. + * @i_op: Optional inode ops attached to this object. Used to initialize i= nodes. + * + * This structure describes a class of telemetry entities that will be + * associated with filesystem inodes having the same behaviour, i.e. the s= ame + * @f_op and @i_op: this way it will be possible to statically define a se= t of + * common descriptors to describe all the possible behaviours and then lin= k it + * to the effective inodes that will be created to support the set of DEs + * effectively discovered at run-time via SCMI. + */ +struct scmi_tlm_class { + const char *name; + umode_t mode; + int flags; +#define TLM_IS_STATE BIT(0) +#define TLM_IS_GROUP BIT(1) +#define TLM_IS_DYNAMIC BIT(2) +#define IS_STATE(_f) ((_f) & TLM_IS_STATE) +#define IS_GROUP(_f) ((_f) & TLM_IS_GROUP) +#define IS_DYNAMIC(_f) ((_f) & TLM_IS_DYNAMIC) + const struct file_operations *f_op; + const struct inode_operations *i_op; +}; + +#define TLM_ANON_CLASS(_n, _f, _m, _fo, _io) \ + { \ + .name =3D _n, \ + .flags =3D _f, \ + .f_op =3D _fo, \ + .i_op =3D _io, \ + .mode =3D _m, \ + } + +#define DEFINE_TLM_CLASS(_tag, _ns, _fl, _mo, _fop, _iop) \ + static const struct scmi_tlm_class _tag =3D \ + TLM_ANON_CLASS(_ns, _fl, _mo, _fop, _iop) + +/** + * struct scmi_tlm_inode - Telemetry node descriptor + * @tsp: A reference to a structure holding data needed to interact with + * the SCMI instance associated to this inode. + * @cls: A reference to the @scmi_tlm_class describing the behaviour of th= is + * inode. + * @priv: Generic private data reference. + * @de: SCMI DE data reference. + * @grp: SCMI Group data reference. + * @info: SCMI instance information data reference. + * @vfs_inode: The embedded VFS inode that will be initialized and plugged + * into the live filesystem at mount time. + * + * This structure is used to describe each SCMI Telemetry entity discovered + * at probe time, store its related SCMI data, and link to the proper + * telemetry class @scmi_tlm_class. + */ +struct scmi_tlm_inode { + struct scmi_tlm_setup *tsp; + const struct scmi_tlm_class *cls; + union { + const void *priv; + const struct scmi_telemetry_de *de; + const struct scmi_telemetry_group *grp; + const struct scmi_telemetry_info *info; + }; + struct inode vfs_inode; +}; + +#define to_tlm_inode(t) container_of(t, struct scmi_tlm_inode, vfs_inode) + +#define MAX_INST_NAME 32 + +#define TOP_NODES_NUM 32 +#define NODES_PER_DE_NUM 12 +#define NODES_PER_GRP_NUM 9 + +/** + * struct scmi_tlm_instance - Telemetry instance descriptor + * @id: Progressive number identifying this probed instance; it will be us= ed + * to name the top node at the root of this instance. + * @res_enumerated: A flag to indicate if full resources enumeration has b= een + * successfully performed. + * @name: Name to be used for the top root node of the instance. (tlm_) + * @node: A node to link this in the list of all instances. + * @sb: A reference to the current super_block. + * @tsp: A reference to the SCMI instance data. + * @top_cls: A class to represent the top node behaviour. + * @top_dentry: A reference to the top dentry for this instance. + * @des_dentry: A reference to the DES dentry for this instance. + * @grps_dentry: A reference to the groups dentry for this instance. + * @info: A handy reference to this instance SCMI Telemetry info data. + * + */ +struct scmi_tlm_instance { + int id; + bool res_enumerated; + char name[MAX_INST_NAME]; + struct list_head node; + struct super_block *sb; + struct scmi_tlm_setup *tsp; + struct scmi_tlm_class top_cls; + struct dentry *top_dentry; + struct dentry *des_dentry; + struct dentry *grps_dentry; + const struct scmi_telemetry_info *info; +}; + +static int scmi_telemetry_instance_register(struct super_block *sb, + struct scmi_tlm_instance *ti); + +static LIST_HEAD(scmi_telemetry_instances); + +static struct inode *stlmfs_get_inode(struct super_block *sb) +{ + struct inode *inode =3D new_inode(sb); + + if (inode) { + inode->i_ino =3D get_next_ino(); + simple_inode_init_ts(inode); + } + + return inode; +} + +static int stlmfs_failed_creating(struct dentry *dentry) +{ + simple_done_creating(dentry); + + return -ENOMEM; +} + +static struct dentry * +stlmfs_create_dentry(struct super_block *sb, struct scmi_tlm_setup *tsp, + struct dentry *parent, const struct scmi_tlm_class *cls, + const void *priv) +{ + struct scmi_tlm_inode *tlmi; + struct dentry *dentry; + struct inode *inode; + + if (!parent) + parent =3D sb->s_root; + + if (IS_ERR(parent)) + return parent; + + dentry =3D simple_start_creating(parent, cls->name); + if (IS_ERR(dentry)) + return dentry; + + inode =3D stlmfs_get_inode(sb); + if (unlikely(!inode)) { + dev_err(tsp->dev, + "out of free dentries, cannot create '%s'", + cls->name); + return ERR_PTR(stlmfs_failed_creating(dentry)); + } + + if (S_ISDIR(cls->mode)) { + inode->i_op =3D cls->i_op ?: &simple_dir_inode_operations; + inode->i_fop =3D cls->f_op ?: &simple_dir_operations; + } else { + inode->i_op =3D cls->i_op ?: &simple_dir_inode_operations; + inode->i_fop =3D cls->f_op; + } + + inode->i_mode =3D cls->mode; + inode_init_owner(&nop_mnt_idmap, inode, NULL, inode->i_mode); + inode->i_private =3D (void *)priv; + + tlmi =3D to_tlm_inode(inode); + + tlmi->cls =3D cls; + tlmi->tsp =3D tsp; + tlmi->priv =3D priv; + + d_make_persistent(dentry, inode); + + simple_done_creating(dentry); + + return dentry; +} + +static inline int +__scmi_tlm_generic_open(struct inode *ino, struct file *filp, + int (*bulk_op)(struct scmi_tlm_setup *tsp, + int res_id, int *num_samples, + struct scmi_telemetry_de_sample *samples)) +{ + struct scmi_tlm_priv *tp; + + tp =3D kzalloc(sizeof(*tp), GFP_KERNEL); + if (!tp) + return -ENOMEM; + + tp->bulk_retrieve =3D bulk_op; + + filp->private_data =3D tp; + + return nonseekable_open(ino, filp); +} + +static int scmi_tlm_priv_release(struct inode *ino, struct file *filp) +{ + struct scmi_tlm_priv *tp =3D filp->private_data; + + kfree(tp->buf); + kfree(tp); + + return 0; +} + +static inline const struct scmi_telemetry_res_info * +scmi_telemetry_res_info_get(struct scmi_tlm_setup *tsp) +{ + const struct scmi_telemetry_res_info *rinfo; + + if (tsp->rinfo) + return ACCESS_PRIVATE(tsp, rinfo); + + rinfo =3D tsp->ops->res_get(tsp->ph); + /* Cache the retrieved resource info value */ + smp_store_mb(tsp->rinfo, rinfo); + + return rinfo; +} + +static ssize_t scmi_tlm_all_des_write(struct file *filp, + const char __user *buf, + size_t count, loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + struct scmi_tlm_setup *tsp =3D tlmi->tsp; + const struct scmi_tlm_class *cls =3D tlmi->cls; + bool enable; + int ret; + + ret =3D kstrtobool_from_user(buf, count, &enable); + if (ret) + return ret; + + /* When !IS_STATE imply that is a tstamp_enable operation */ + if (IS_STATE(cls->flags) && !enable) { + ret =3D tsp->ops->all_disable(tsp->ph, false); + if (ret) + return ret; + } else { + const struct scmi_telemetry_res_info *rinfo; + + rinfo =3D scmi_telemetry_res_info_get(tsp); + if (!rinfo) + return -ENODEV; + + for (int i =3D 0; i < rinfo->num_des; i++) { + ret =3D tsp->ops->state_set(tsp->ph, false, + rinfo->des[i]->info->id, + IS_STATE(cls->flags) ? &enable : NULL, + !IS_STATE(cls->flags) ? &enable : NULL); + if (ret) + return ret; + } + } + + return count; +} + +static const struct file_operations all_des_fops =3D { + .open =3D nonseekable_open, + .write =3D scmi_tlm_all_des_write, +}; + +static ssize_t scmi_tlm_obj_enable_write(struct file *filp, + const char __user *buf, + size_t count, loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + struct scmi_tlm_setup *tsp =3D tlmi->tsp; + const struct scmi_tlm_class *cls =3D tlmi->cls; + bool enabled, is_group =3D IS_GROUP(cls->flags); + int ret, res_id; + + ret =3D kstrtobool_from_user(buf, count, &enabled); + if (ret) + return ret; + + res_id =3D !is_group ? tlmi->de->info->id : tlmi->grp->info->id; + ret =3D tsp->ops->state_set(tsp->ph, is_group, res_id, + IS_STATE(cls->flags) ? &enabled : NULL, + !IS_STATE(cls->flags) ? &enabled : NULL); + if (ret) + return ret; + + return count; +} + +static ssize_t scmi_tlm_obj_enable_read(struct file *filp, char __user *bu= f, + size_t count, loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + const bool *enabled_state, *tstamp_enabled_state; + char o_buf[2]; + bool enabled; + + if (!IS_GROUP(tlmi->cls->flags)) { + enabled_state =3D &tlmi->de->enabled; + tstamp_enabled_state =3D &tlmi->de->tstamp_enabled; + } else { + enabled_state =3D &tlmi->grp->enabled; + tstamp_enabled_state =3D &tlmi->grp->tstamp_enabled; + } + + enabled =3D IS_STATE(tlmi->cls->flags) ? *enabled_state : *tstamp_enabled= _state; + o_buf[0] =3D enabled ? 'Y' : 'N'; + o_buf[1] =3D '\n'; + + return simple_read_from_buffer(buf, count, ppos, o_buf, 2); +} + +static const struct file_operations obj_enable_fops =3D { + .open =3D nonseekable_open, + .write =3D scmi_tlm_obj_enable_write, + .read =3D scmi_tlm_obj_enable_read, +}; + +static int scmi_tlm_open(struct inode *ino, struct file *filp) +{ + struct scmi_tlm_buffer *data; + + /* Allocate some per-open buffer */ + data =3D kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + filp->private_data =3D data; + + return nonseekable_open(ino, filp); +} + +static int scmi_tlm_release(struct inode *ino, struct file *filp) +{ + kfree(filp->private_data); + + return 0; +} + +static ssize_t +scmi_tlm_update_interval_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + struct scmi_tlm_buffer *data =3D filp->private_data; + unsigned int active_update_interval; + + if (!data) + return 0; + + if (!IS_GROUP(tlmi->cls->flags)) + active_update_interval =3D tlmi->info->active_update_interval; + else + active_update_interval =3D tlmi->grp->active_update_interval; + + if (!data->used) + data->used =3D + scnprintf(data->buf, SCMI_TLM_MAX_BUF_SZ, "%u, %d\n", + SCMI_TLM_GET_UPDATE_INTERVAL_SECS(active_update_interval), + SCMI_TLM_GET_UPDATE_INTERVAL_EXP(active_update_interval)); + + return simple_read_from_buffer(buf, count, ppos, data->buf, data->used); +} + +static ssize_t +scmi_tlm_update_interval_write(struct file *filp, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + struct scmi_tlm_setup *tsp =3D tlmi->tsp; + struct scmi_tlm_buffer *data =3D filp->private_data; + bool is_group =3D IS_GROUP(tlmi->cls->flags); + unsigned int update_interval_ms =3D 0, secs =3D 0; + int ret, grp_id, exp =3D -3; + char *p, *token; + + if (count >=3D SCMI_TLM_MAX_BUF_SZ) + return -ENOSPC; + + if (copy_from_user(data->buf, buf, count)) + return -EFAULT; + + p =3D data->buf; + token =3D strsep(&p, ","); + if (!token || iscntrl(token[0])) + return -EINVAL; + + ret =3D kstrtouint(strim(token), 0, &secs); + if (ret) + return ret; + + if (p) { + token =3D p; + if (!token || iscntrl(token[0])) + return -EINVAL; + + ret =3D kstrtoint(strim(token), 0, &exp); + if (ret) + return ret; + } + + update_interval_ms =3D SCMI_TLM_BUILD_UPDATE_INTERVAL(secs, exp); + + grp_id =3D !is_group ? SCMI_TLM_GRP_INVALID : tlmi->grp->info->id; + ret =3D tsp->ops->collection_configure(tsp->ph, grp_id, !is_group, NULL, + &update_interval_ms, NULL); + if (ret) + return ret; + + return count; +} + +static const struct file_operations current_interval_fops =3D { + .open =3D scmi_tlm_open, + .read =3D scmi_tlm_update_interval_read, + .write =3D scmi_tlm_update_interval_write, + .release =3D scmi_tlm_release, +}; + +static ssize_t scmi_tlm_de_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + struct scmi_tlm_setup *tsp =3D tlmi->tsp; + struct scmi_tlm_buffer *data =3D filp->private_data; + int ret; + + if (!data) + return 0; + + if (!data->used) { + struct scmi_telemetry_de_sample sample; + + sample.id =3D tlmi->de->info->id; + ret =3D tsp->ops->de_data_read(tsp->ph, &sample); + if (ret) + return ret; + + data->used =3D scnprintf(data->buf, SCMI_TLM_MAX_BUF_SZ, + "%llu: %016llX\n", sample.tstamp, + sample.val); + } + + return simple_read_from_buffer(buf, count, ppos, data->buf, data->used); +} + +static const struct file_operations de_read_fops =3D { + .open =3D scmi_tlm_open, + .read =3D scmi_tlm_de_read, + .release =3D scmi_tlm_release, +}; + +static ssize_t +scmi_tlm_enable_read(struct file *filp, char __user *buf, size_t count, + loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + char o_buf[2]; + + o_buf[0] =3D tlmi->info->enabled ? 'Y' : 'N'; + o_buf[1] =3D '\n'; + + return simple_read_from_buffer(buf, count, ppos, o_buf, 2); +} + +static ssize_t +scmi_tlm_enable_write(struct file *filp, const char __user *buf, size_t co= unt, + loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + enum scmi_telemetry_collection mode =3D SCMI_TLM_ONDEMAND; + struct scmi_tlm_setup *tsp =3D tlmi->tsp; + bool enabled; + int ret; + + ret =3D kstrtobool_from_user(buf, count, &enabled); + if (ret) + return ret; + + ret =3D tsp->ops->collection_configure(tsp->ph, SCMI_TLM_GRP_INVALID, tru= e, + &enabled, NULL, &mode); + if (ret) + return ret; + + return count; +} + +static const struct file_operations tlm_enable_fops =3D { + .open =3D nonseekable_open, + .read =3D scmi_tlm_enable_read, + .write =3D scmi_tlm_enable_write, +}; + +static ssize_t +scmi_tlm_intrv_discrete_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + bool discrete; + char o_buf[2]; + + discrete =3D !IS_GROUP(tlmi->cls->flags) ? + tlmi->info->intervals->discrete : tlmi->grp->intervals->discrete; + + o_buf[0] =3D discrete ? 'Y' : 'N'; + o_buf[1] =3D '\n'; + + return simple_read_from_buffer(buf, count, ppos, o_buf, 2); +} + +static const struct file_operations intrv_discrete_fops =3D { + .open =3D nonseekable_open, + .read =3D scmi_tlm_intrv_discrete_read, +}; + +static ssize_t +scmi_tlm_reset_write(struct file *filp, const char __user *buf, size_t cou= nt, + loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + int ret; + + ret =3D tlmi->tsp->ops->reset(tlmi->tsp->ph); + if (ret) + return ret; + + return count; +} + +static const struct file_operations reset_fops =3D { + .open =3D nonseekable_open, + .write =3D scmi_tlm_reset_write, +}; + +static int sa_u32_get(void *data, u64 *val) +{ + *val =3D *(u32 *)data; + return 0; +} + +static int sa_u32_set(void *data, u64 val) +{ + *(u32 *)data =3D val; + return 0; +} + +static int sa_u32_open(struct inode *ino, struct file *filp) +{ + return simple_attr_open(ino, filp, sa_u32_get, sa_u32_set, "%u\n"); +} + +static int sa_s32_open(struct inode *ino, struct file *filp) +{ + return simple_attr_open(ino, filp, sa_u32_get, sa_u32_set, "%d\n"); +} + +static int sa_x32_open(struct inode *ino, struct file *filp) +{ + return simple_attr_open(ino, filp, sa_u32_get, sa_u32_set, "0x%X\n"); +} + +static const struct file_operations sa_x32_ro_fops =3D { + .open =3D sa_x32_open, + .read =3D simple_attr_read, + .release =3D simple_attr_release, +}; + +static const struct file_operations sa_u32_ro_fops =3D { + .open =3D sa_u32_open, + .read =3D simple_attr_read, + .release =3D simple_attr_release, +}; + +static const struct file_operations sa_s32_ro_fops =3D { + .open =3D sa_s32_open, + .read =3D simple_attr_read, + .release =3D simple_attr_release, +}; + +static ssize_t +scmi_de_impl_version_read(struct file *filp, char __user *buf, size_t coun= t, + loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + struct scmi_tlm_buffer *data =3D filp->private_data; + + if (!data) + return 0; + + if (!data->used) + data->used =3D scnprintf(data->buf, SCMI_TLM_MAX_BUF_SZ, + "%pUL\n", tlmi->info->base.de_impl_version); + + return simple_read_from_buffer(buf, count, ppos, data->buf, data->used); +} + +static const struct file_operations de_impl_vers_fops =3D { + .open =3D scmi_tlm_open, + .read =3D scmi_de_impl_version_read, + .release =3D scmi_tlm_release, +}; + +static ssize_t scmi_string_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct scmi_tlm_priv *tp =3D filp->private_data; + + /* + * Note that tp->buf is a scratch buffer, filled once, used to support + * multiple chunked read and freed in scmi_tlm_priv_release. + */ + if (!tp->buf) { + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + const char *str =3D tlmi->priv; + + tp->buf =3D kasprintf(GFP_KERNEL, "%s\n", str); + if (!tp->buf) + return -ENOMEM; + + tp->buf_len =3D strlen(tp->buf) + 1; + } + + return simple_read_from_buffer(buf, count, ppos, tp->buf, tp->buf_len); +} + +static int scmi_tlm_priv_open(struct inode *ino, struct file *filp) +{ + return __scmi_tlm_generic_open(ino, filp, NULL); +} + +static const struct file_operations string_ro_fops =3D { + .open =3D scmi_tlm_priv_open, + .read =3D scmi_string_read, + .release =3D scmi_tlm_priv_release, +}; + +static ssize_t scmi_available_interv_read(struct file *filp, char __user *= buf, + size_t count, loff_t *ppos) +{ + struct scmi_tlm_priv *tp =3D filp->private_data; + + /* + * Note that tp->buf is a scratch buffer, filled once, used to support + * multiple chunked read and freed in scmi_tlm_priv_release. + */ + if (!tp->buf) { + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + struct scmi_tlm_intervals *intervals; + int len =3D 0; + + intervals =3D !IS_GROUP(tlmi->cls->flags) ? + tlmi->info->intervals : tlmi->grp->intervals; + tp->buf_len =3D intervals->num * MAX_AVAILABLE_INTERV_CHAR_LENGTH; + tp->buf =3D kzalloc(tp->buf_len, GFP_KERNEL); + if (!tp->buf) + return -ENOMEM; + + for (int i =3D 0; i < intervals->num; i++) { + u32 ivl; + + ivl =3D intervals->update_intervals[i]; + len +=3D scnprintf(tp->buf + len, tp->buf_len - len, + "%u,%d ", + SCMI_TLM_GET_UPDATE_INTERVAL_SECS(ivl), + SCMI_TLM_GET_UPDATE_INTERVAL_EXP(ivl)); + } + tp->buf[len - 1] =3D '\n'; + } + + return simple_read_from_buffer(buf, count, ppos, tp->buf, tp->buf_len); +} + +static const struct file_operations available_interv_fops =3D { + .open =3D scmi_tlm_priv_open, + .read =3D scmi_available_interv_read, + .release =3D scmi_tlm_priv_release, +}; + +static const struct scmi_tlm_class tlm_tops[] =3D { + TLM_ANON_CLASS("all_des_enable", TLM_IS_STATE, + S_IFREG | S_IWUSR, &all_des_fops, NULL), + TLM_ANON_CLASS("all_des_tstamp_enable", 0, + S_IFREG | S_IWUSR, &all_des_fops, NULL), + TLM_ANON_CLASS("current_update_interval_ms", 0, + S_IFREG | S_IRUSR | S_IWUSR, ¤t_interval_fops, NULL), + TLM_ANON_CLASS("intervals_discrete", 0, + S_IFREG | S_IRUSR, &intrv_discrete_fops, NULL), + TLM_ANON_CLASS("available_update_intervals_ms", 0, + S_IFREG | S_IRUSR, &available_interv_fops, NULL), + TLM_ANON_CLASS("de_implementation_version", 0, + S_IFREG | S_IRUSR, &de_impl_vers_fops, NULL), + TLM_ANON_CLASS("tlm_enable", 0, + S_IFREG | S_IRUSR | S_IWUSR, &tlm_enable_fops, NULL), + TLM_ANON_CLASS(NULL, 0, 0, NULL, NULL), +}; + +DEFINE_TLM_CLASS(reset_tlmo, "reset", 0, S_IFREG | S_IWUSR, &reset_fops, N= ULL); + +DEFINE_TLM_CLASS(des_dir_cls, "des", 0, + S_IFDIR | S_IRWXU, NULL, NULL); +DEFINE_TLM_CLASS(name_tlmo, "name", 0, + S_IFREG | S_IRUSR, &string_ro_fops, NULL); +DEFINE_TLM_CLASS(ena_tlmo, "enable", TLM_IS_STATE, + S_IFREG | S_IRUSR | S_IWUSR, &obj_enable_fops, NULL); +DEFINE_TLM_CLASS(tstamp_ena_tlmo, "tstamp_enable", 0, + S_IFREG | S_IRUSR | S_IWUSR, &obj_enable_fops, NULL); +DEFINE_TLM_CLASS(type_tlmo, "type", 0, + S_IFREG | S_IRUSR, &sa_u32_ro_fops, NULL); +DEFINE_TLM_CLASS(unit_tlmo, "unit", 0, + S_IFREG | S_IRUSR, &sa_u32_ro_fops, NULL); +DEFINE_TLM_CLASS(unit_exp_tlmo, "unit_exp", 0, + S_IFREG | S_IRUSR, &sa_s32_ro_fops, NULL); +DEFINE_TLM_CLASS(instance_id_tlmo, "instance_id", 0, + S_IFREG | S_IRUSR, &sa_u32_ro_fops, NULL); +DEFINE_TLM_CLASS(compo_type_tlmo, "compo_type", 0, + S_IFREG | S_IRUSR, &sa_u32_ro_fops, NULL); +DEFINE_TLM_CLASS(compo_inst_id_tlmo, "compo_instance_id", 0, + S_IFREG | S_IRUSR, &sa_u32_ro_fops, NULL); +DEFINE_TLM_CLASS(tstamp_exp_tlmo, "tstamp_exp", 0, + S_IFREG | S_IRUSR, &sa_s32_ro_fops, NULL); +DEFINE_TLM_CLASS(persistent_tlmo, "persistent", 0, + S_IFREG | S_IRUSR, &sa_u32_ro_fops, NULL); +DEFINE_TLM_CLASS(value_tlmo, "value", 0, + S_IFREG | S_IRUSR, &de_read_fops, NULL); + +static int scmi_telemetry_de_populate(struct super_block *sb, + struct scmi_tlm_setup *tsp, + struct dentry *parent, + const struct scmi_telemetry_de *de, + bool fully_enumerated) +{ + struct scmi_tlm_de_info *dei =3D de->info; + + stlmfs_create_dentry(sb, tsp, parent, &ena_tlmo, de); + stlmfs_create_dentry(sb, tsp, parent, &value_tlmo, de); + if (!fully_enumerated) + return 0; + + if (de->name_support) + stlmfs_create_dentry(sb, tsp, parent, &name_tlmo, dei->name); + + if (de->tstamp_support) { + stlmfs_create_dentry(sb, tsp, parent, &tstamp_ena_tlmo, de); + stlmfs_create_dentry(sb, tsp, parent, &tstamp_exp_tlmo, + &dei->tstamp_exp); + } + + stlmfs_create_dentry(sb, tsp, parent, &type_tlmo, &dei->type); + stlmfs_create_dentry(sb, tsp, parent, &unit_tlmo, &dei->unit); + stlmfs_create_dentry(sb, tsp, parent, &unit_exp_tlmo, &dei->unit_exp); + stlmfs_create_dentry(sb, tsp, parent, &instance_id_tlmo, &dei->instance_i= d); + stlmfs_create_dentry(sb, tsp, parent, &compo_type_tlmo, &dei->compo_type); + stlmfs_create_dentry(sb, tsp, parent, &compo_inst_id_tlmo, + &dei->compo_instance_id); + stlmfs_create_dentry(sb, tsp, parent, &persistent_tlmo, &dei->persistent); + + return 0; +} + +static int +scmi_telemetry_des_lazy_enumerate(struct scmi_tlm_instance *ti, + const struct scmi_telemetry_res_info *rinfo) +{ + struct scmi_tlm_setup *tsp =3D ti->tsp; + struct super_block *sb =3D ti->sb; + + for (int i =3D 0; i < rinfo->num_des; i++) { + const struct scmi_telemetry_de *de =3D rinfo->des[i]; + struct dentry *de_dir_dentry; + int ret; + + struct scmi_tlm_class *de_tlm_cls __free(kfree) =3D + kzalloc(sizeof(*de_tlm_cls), GFP_KERNEL); + if (!de_tlm_cls) + return -ENOMEM; + + de_tlm_cls->name =3D kasprintf(GFP_KERNEL, "0x%08X", de->info->id); + if (!de_tlm_cls->name) + return -ENOMEM; + + de_tlm_cls->mode =3D S_IFDIR | S_IRWXU; + de_tlm_cls->flags =3D TLM_IS_DYNAMIC; + de_dir_dentry =3D stlmfs_create_dentry(sb, tsp, ti->des_dentry, + de_tlm_cls, de); + + ret =3D scmi_telemetry_de_populate(sb, tsp, de_dir_dentry, de, + rinfo->fully_enumerated); + if (ret) + return ret; + + retain_and_null_ptr(de_tlm_cls); + } + + ti->res_enumerated =3D true; + + dev_info(tsp->dev, "Found %d Telemetry DE resources.\n", rinfo->num_des); + + return 0; +} + +static int scmi_telemetry_des_initialize(struct scmi_tlm_instance *ti) +{ + const struct scmi_telemetry_res_info *rinfo; + + rinfo =3D scmi_telemetry_res_info_get(ti->tsp); + if (!rinfo) + return -ENODEV; + + return scmi_telemetry_des_lazy_enumerate(ti, rinfo); +} + +DEFINE_TLM_CLASS(version_tlmo, "version", 0, + S_IFREG | S_IRUSR, &sa_x32_ro_fops, NULL); + +static int scmi_tlm_bulk_on_demand(struct scmi_tlm_setup *tsp, + int res_id, int *num_samples, + struct scmi_telemetry_de_sample *samples) +{ + return tsp->ops->des_bulk_read(tsp->ph, res_id, num_samples, samples); +} + +static int scmi_tlm_data_open(struct inode *ino, struct file *filp) +{ + return __scmi_tlm_generic_open(ino, filp, scmi_tlm_bulk_on_demand); +} + +static int scmi_tlm_buffer_fill(struct device *dev, char *buf, size_t size, + int *len, int num, + struct scmi_telemetry_de_sample *samples) +{ + int idx, bytes =3D 0; + + /* Loop till there space for the next line */ + for (idx =3D 0; idx < num && size - bytes >=3D MAX_BULK_LINE_CHAR_LENGTH;= idx++) { + bytes +=3D scnprintf(buf + bytes, size - bytes, + "0x%08X %llu %016llX\n", samples[idx].id, + samples[idx].tstamp, samples[idx].val); + } + + if (idx < num) { + dev_err(dev, "Bulk buffer truncated !\n"); + return -ENOSPC; + } + + if (len) + *len =3D bytes; + + return 0; +} + +static int scmi_tlm_bulk_buffer_allocate_and_fill(struct scmi_tlm_inode *t= lmi, + struct scmi_tlm_priv *tp) +{ + struct scmi_tlm_setup *tsp =3D tlmi->tsp; + const struct scmi_tlm_class *cls =3D tlmi->cls; + struct scmi_telemetry_de_sample *samples; + bool is_group =3D IS_GROUP(cls->flags); + int ret, num_samples, res_id; + + num_samples =3D !is_group ? tlmi->info->base.num_des : + tlmi->grp->info->num_des; + tp->buf_sz =3D num_samples * MAX_BULK_LINE_CHAR_LENGTH; + tp->buf =3D kzalloc(tp->buf_sz, GFP_KERNEL); + if (!tp->buf) + return -ENOMEM; + + res_id =3D is_group ? tlmi->grp->info->id : SCMI_TLM_GRP_INVALID; + samples =3D kcalloc(num_samples, sizeof(*samples), GFP_KERNEL); + if (!samples) { + kfree(tp->buf); + return -ENOMEM; + } + + ret =3D tp->bulk_retrieve(tsp, res_id, &num_samples, samples); + if (ret) { + kfree(tp->buf); + kfree(samples); + return ret; + } + + /* + * Note that tp->buf is a scratch buffer, filled once, used to support + * multiple chunked read and freed in scmi_tlm_priv_release. + */ + ret =3D scmi_tlm_buffer_fill(tsp->dev, tp->buf, tp->buf_sz, &tp->buf_len, + num_samples, samples); + kfree(samples); + + return ret; +} + +static ssize_t scmi_tlm_generic_data_read(struct file *filp, char __user *= buf, + size_t count, loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + struct scmi_tlm_priv *tp =3D filp->private_data; + int ret; + + if (!tp->buf) { + ret =3D scmi_tlm_bulk_buffer_allocate_and_fill(tlmi, tp); + if (ret) + return ret; + } + + return simple_read_from_buffer(buf, count, ppos, tp->buf, tp->buf_len); +} + +static const struct file_operations scmi_tlm_data_fops =3D { + .owner =3D THIS_MODULE, + .open =3D scmi_tlm_data_open, + .read =3D scmi_tlm_generic_data_read, + .release =3D scmi_tlm_priv_release, +}; + +DEFINE_TLM_CLASS(data_tlmo, "des_bulk_read", 0, + S_IFREG | S_IRUSR, &scmi_tlm_data_fops, NULL); + +static int scmi_tlm_bulk_single_read(struct scmi_tlm_setup *tsp, + int res_id, int *num_samples, + struct scmi_telemetry_de_sample *samples) +{ + return tsp->ops->des_sample_get(tsp->ph, res_id, num_samples, samples); +} + +static int scmi_tlm_single_read_open(struct inode *ino, struct file *filp) +{ + return __scmi_tlm_generic_open(ino, filp, scmi_tlm_bulk_single_read); +} + +static const struct file_operations scmi_tlm_single_sample_fops =3D { + .owner =3D THIS_MODULE, + .open =3D scmi_tlm_single_read_open, + .read =3D scmi_tlm_generic_data_read, + .release =3D scmi_tlm_priv_release, +}; + +DEFINE_TLM_CLASS(single_sample_tlmo, "des_single_sample_read", 0, + S_IFREG | S_IRUSR, &scmi_tlm_single_sample_fops, NULL); + +static const struct scmi_tlm_class tlm_grps[] =3D { + TLM_ANON_CLASS("enable", TLM_IS_STATE | TLM_IS_GROUP, + S_IFREG | S_IRUSR | S_IWUSR, &obj_enable_fops, NULL), + TLM_ANON_CLASS("tstamp_enable", TLM_IS_GROUP, + S_IFREG | S_IRUSR | S_IWUSR, &obj_enable_fops, NULL), + TLM_ANON_CLASS(NULL, 0, 0, NULL, NULL), +}; + +DEFINE_TLM_CLASS(grp_data_tlmo, "des_bulk_read", TLM_IS_GROUP, + S_IFREG | S_IRUSR, &scmi_tlm_data_fops, NULL); + +DEFINE_TLM_CLASS(groups_dir_cls, "groups", 0, S_IFDIR | S_IRWXU, NULL, NUL= L); + +DEFINE_TLM_CLASS(grp_single_sample_tlmo, "des_single_sample_read", TLM_IS_= GROUP, + S_IFREG | S_IRUSR, &scmi_tlm_single_sample_fops, NULL); + +DEFINE_TLM_CLASS(grp_composing_des_tlmo, "composing_des", TLM_IS_GROUP, + S_IFREG | S_IRUSR, &string_ro_fops, NULL); + +DEFINE_TLM_CLASS(grp_current_interval_tlmo, "current_update_interval_ms", + TLM_IS_GROUP, S_IFREG | S_IRUSR | S_IWUSR, + ¤t_interval_fops, NULL); + +DEFINE_TLM_CLASS(grp_available_interval_tlmo, "available_update_intervals_= ms", + TLM_IS_GROUP, S_IFREG | S_IRUSR, &available_interv_fops, NULL); + +DEFINE_TLM_CLASS(grp_intervals_discrete_tlmo, "intervals_discrete", + TLM_IS_GROUP, S_IFREG | S_IRUSR, &intrv_discrete_fops, NULL); + +static int scmi_telemetry_groups_initialize(struct scmi_tlm_instance *ti) +{ + const struct scmi_telemetry_res_info *rinfo; + struct scmi_tlm_setup *tsp =3D ti->tsp; + struct super_block *sb =3D ti->sb; + struct device *dev =3D tsp->dev; + struct dentry *grp_dir_dentry; + + if (ti->info->base.num_groups =3D=3D 0) + return 0; + + rinfo =3D scmi_telemetry_res_info_get(tsp); + if (!rinfo) + return -ENODEV; + + for (int i =3D 0; i < rinfo->num_groups; i++) { + const struct scmi_telemetry_group *grp =3D &rinfo->grps[i]; + + struct scmi_tlm_class *grp_tlm_cls __free(kfree) =3D + kzalloc(sizeof(*grp_tlm_cls), GFP_KERNEL); + if (!grp_tlm_cls) + return -ENOMEM; + + grp_tlm_cls->name =3D kasprintf(GFP_KERNEL, "%u", grp->info->id); + if (!grp_tlm_cls->name) + return -ENOMEM; + + grp_tlm_cls->mode =3D S_IFDIR | S_IRWXU; + grp_tlm_cls->flags =3D TLM_IS_DYNAMIC; + + grp_dir_dentry =3D stlmfs_create_dentry(sb, tsp, ti->grps_dentry, + grp_tlm_cls, grp); + + for (const struct scmi_tlm_class *gto =3D tlm_grps; gto->name; gto++) + stlmfs_create_dentry(sb, tsp, grp_dir_dentry, gto, grp); + + stlmfs_create_dentry(sb, tsp, grp_dir_dentry, + &grp_composing_des_tlmo, grp->des_str); + + stlmfs_create_dentry(sb, tsp, grp_dir_dentry, &grp_data_tlmo, grp); + stlmfs_create_dentry(sb, tsp, grp_dir_dentry, + &grp_single_sample_tlmo, grp); + + if (ti->info->per_group_config_support) { + stlmfs_create_dentry(sb, tsp, grp_dir_dentry, + &grp_current_interval_tlmo, grp); + stlmfs_create_dentry(sb, tsp, grp_dir_dentry, + &grp_available_interval_tlmo, grp); + stlmfs_create_dentry(sb, tsp, grp_dir_dentry, + &grp_intervals_discrete_tlmo, grp); + } + + retain_and_null_ptr(grp_tlm_cls); + } + + dev_info(dev, "Found %d Telemetry GROUPS resources.\n", + rinfo->num_groups); + + return 0; +} + +static struct scmi_tlm_instance *scmi_tlm_init(struct scmi_tlm_setup *tsp, + int instance_id) +{ + struct device *dev =3D tsp->dev; + struct scmi_tlm_instance *ti; + + ti =3D devm_kzalloc(dev, sizeof(*ti), GFP_KERNEL); + if (!ti) + return ERR_PTR(-ENOMEM); + + ti->info =3D tsp->ops->info_get(tsp->ph); + if (!ti->info) + return dev_err_ptr_probe(dev, + -EINVAL, "invalid Telemetry info !\n"); + + ti->id =3D instance_id; + ti->tsp =3D tsp; + + return ti; +} + +static int scmi_telemetry_probe(struct scmi_device *sdev) +{ + const struct scmi_handle *handle =3D sdev->handle; + struct scmi_protocol_handle *ph; + struct device *dev =3D &sdev->dev; + struct scmi_tlm_instance *ti; + struct scmi_tlm_setup *tsp; + struct super_block *sb; + const void *ops; + + if (!handle) + return -ENODEV; + + ops =3D handle->devm_protocol_get(sdev, sdev->protocol_id, &ph); + if (IS_ERR(ops)) + return dev_err_probe(dev, PTR_ERR(ops), + "Cannot access protocol:0x%X\n", + sdev->protocol_id); + + tsp =3D devm_kzalloc(dev, sizeof(*tsp), GFP_KERNEL); + if (!tsp) + return -ENOMEM; + + tsp->dev =3D dev; + tsp->ops =3D ops; + tsp->ph =3D ph; + + ti =3D scmi_tlm_init(tsp, atomic_fetch_inc(&scmi_tlm_instance_count)); + if (IS_ERR(ti)) + return PTR_ERR(ti); + + mutex_lock(&stlmfs_mtx); + list_add(&ti->node, &scmi_telemetry_instances); + sb =3D stlmfs_sb; + mutex_unlock(&stlmfs_mtx); + + /* + * If the file system was already mounted by the time this + * instance was probed, register explicitly, since the list + * has been scanned already. + */ + if (sb) { + int ret; + + ret =3D scmi_telemetry_instance_register(sb, ti); + if (ret) { + dev_err(dev, "Failed to register instance %u at probe.\n", + ti->id); + return ret; + } + } + + return 0; +} + +static const struct scmi_device_id scmi_id_table[] =3D { + { SCMI_PROTOCOL_TELEMETRY, "telemetry" }, + { } +}; +MODULE_DEVICE_TABLE(scmi, scmi_id_table); + +static struct scmi_driver scmi_telemetry_driver =3D { + .name =3D "scmi-telemetry-driver", + .probe =3D scmi_telemetry_probe, + .id_table =3D scmi_id_table, +}; + +static struct inode *stlmfs_alloc_inode(struct super_block *sb) +{ + struct scmi_tlm_inode *tlmi; + + tlmi =3D alloc_inode_sb(sb, stlmfs_inode_cachep, GFP_KERNEL); + if (!tlmi) + return NULL; + + tlmi->cls =3D NULL; + + return &tlmi->vfs_inode; +} + +static void stlmfs_free_inode(struct inode *inode) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(inode); + + if (tlmi->cls && IS_DYNAMIC(tlmi->cls->flags)) { + kfree(tlmi->cls->name); + kfree(tlmi->cls); + } + + kmem_cache_free(stlmfs_inode_cachep, tlmi); +} + +static const struct super_operations tlm_sops =3D { + .statfs =3D simple_statfs, + .alloc_inode =3D stlmfs_alloc_inode, + .free_inode =3D stlmfs_free_inode, +}; + +static struct dentry *stlmfs_create_root_dentry(struct super_block *sb) +{ + struct dentry *dentry; + struct inode *inode; + + inode =3D stlmfs_get_inode(sb); + if (!inode) + return ERR_PTR(-ENOMEM); + + inode->i_op =3D &simple_dir_inode_operations; + inode->i_fop =3D &simple_dir_operations; + inode_init_owner(&nop_mnt_idmap, inode, NULL, S_IFDIR | S_IRWXU); + + dentry =3D d_make_root(inode); + if (!dentry) + return ERR_PTR(-ENOMEM); + + return dentry; +} + +static int scmi_tlm_root_dentries_initialize(struct scmi_tlm_instance *ti) +{ + struct scmi_tlm_setup *tsp =3D ti->tsp; + struct super_block *sb =3D ti->sb; + + scnprintf(ti->name, MAX_INST_NAME, "tlm_%d", ti->id); + + /* Allocate top instance node */ + ti->top_cls.name =3D ti->name; + ti->top_cls.mode =3D S_IFDIR | S_IRWXU; + + /* Create the root of this instance */ + ti->top_dentry =3D stlmfs_create_dentry(sb, tsp, sb->s_root, &ti->top_cls= , NULL); + for (const struct scmi_tlm_class *tlmo =3D tlm_tops; tlmo->name; tlmo++) + stlmfs_create_dentry(sb, tsp, ti->top_dentry, tlmo, ti->info); + + if (ti->info->reset_support) + stlmfs_create_dentry(sb, tsp, ti->top_dentry, &reset_tlmo, NULL); + + stlmfs_create_dentry(sb, tsp, ti->top_dentry, &version_tlmo, + &ti->info->base.version); + stlmfs_create_dentry(sb, tsp, ti->top_dentry, &data_tlmo, ti->info); + stlmfs_create_dentry(sb, tsp, ti->top_dentry, &single_sample_tlmo, ti->in= fo); + ti->des_dentry =3D + stlmfs_create_dentry(sb, tsp, ti->top_dentry, &des_dir_cls, NULL); + ti->grps_dentry =3D + stlmfs_create_dentry(sb, tsp, ti->top_dentry, &groups_dir_cls, NULL); + + return 0; +} + +static int scmi_telemetry_instance_register(struct super_block *sb, + struct scmi_tlm_instance *ti) +{ + int ret; + + ti->sb =3D sb; + ret =3D scmi_tlm_root_dentries_initialize(ti); + if (ret) + return ret; + + ret =3D scmi_telemetry_des_initialize(ti); + if (ret) + return ret; + + ret =3D scmi_telemetry_groups_initialize(ti); + if (ret) { + dev_warn(ti->tsp->dev, + "Failed to initialize groups for instance %s.\n", + ti->top_cls.name); + } + + return 0; +} + +static int stlmfs_fill_super(struct super_block *sb, struct fs_context *fc) +{ + struct scmi_tlm_instance *ti; + struct dentry *root_dentry; + int ret; + + sb->s_magic =3D TLM_FS_MAGIC; + sb->s_blocksize =3D PAGE_SIZE; + sb->s_blocksize_bits =3D PAGE_SHIFT; + sb->s_op =3D &tlm_sops; + + root_dentry =3D stlmfs_create_root_dentry(sb); + if (IS_ERR(root_dentry)) + return PTR_ERR(root_dentry); + + sb->s_root =3D root_dentry; + + mutex_lock(&stlmfs_mtx); + list_for_each_entry(ti, &scmi_telemetry_instances, node) { + mutex_unlock(&stlmfs_mtx); + ret =3D scmi_telemetry_instance_register(sb, ti); + if (ret) + dev_err(ti->tsp->dev, + "Failed to register instance %u.\n", ti->id); + mutex_lock(&stlmfs_mtx); + } + stlmfs_sb =3D sb; + mutex_unlock(&stlmfs_mtx); + + return 0; +} + +static int stlmfs_get_tree(struct fs_context *fc) +{ + return get_tree_single(fc, stlmfs_fill_super); +} + +static const struct fs_context_operations stlmfs_fc_ops =3D { + .get_tree =3D stlmfs_get_tree, +}; + +static int stlmfs_init_fs_context(struct fs_context *fc) +{ + fc->ops =3D &stlmfs_fc_ops; + + return 0; +} + +static void stlmfs_kill_sb(struct super_block *sb) +{ + kill_anon_super(sb); +} + +static struct file_system_type scmi_telemetry_fs =3D { + .owner =3D THIS_MODULE, + .name =3D TLM_FS_NAME, + .kill_sb =3D stlmfs_kill_sb, + .init_fs_context =3D stlmfs_init_fs_context, + .fs_flags =3D 0, +}; + +static void stlmfs_init_once(void *arg) +{ + struct scmi_tlm_inode *tlmi =3D arg; + + inode_init_once(&tlmi->vfs_inode); +} + +static int __init scmi_telemetry_init(void) +{ + int ret; + + ret =3D sysfs_create_mount_point(fs_kobj, TLM_FS_MNT); + if (ret && ret !=3D -EEXIST) + return ret; + + stlmfs_inode_cachep =3D kmem_cache_create("stlmfs_inode_cache", + sizeof(struct scmi_tlm_inode), 0, + SLAB_RECLAIM_ACCOUNT | SLAB_ACCOUNT, + stlmfs_init_once); + if (!stlmfs_inode_cachep) { + ret =3D -ENOMEM; + goto out_mnt; + } + + ret =3D register_filesystem(&scmi_telemetry_fs); + if (ret) + goto out_kmem; + + ret =3D scmi_register(&scmi_telemetry_driver); + if (ret) + goto out_reg; + + return 0; + +out_reg: + unregister_filesystem(&scmi_telemetry_fs); +out_kmem: + kmem_cache_destroy(stlmfs_inode_cachep); +out_mnt: + sysfs_remove_mount_point(fs_kobj, TLM_FS_MNT); + + return ret; +} +module_init(scmi_telemetry_init); + +static void __exit scmi_telemetry_exit(void) +{ + int ret; + + scmi_unregister(&scmi_telemetry_driver); + ret =3D unregister_filesystem(&scmi_telemetry_fs); + if (ret) + pr_err("Failed to unregister %s\n", TLM_FS_NAME); + + sysfs_remove_mount_point(fs_kobj, TLM_FS_MNT); + kmem_cache_destroy(stlmfs_inode_cachep); +} +module_exit(scmi_telemetry_exit); + +MODULE_AUTHOR("Cristian Marussi "); +MODULE_DESCRIPTION("ARM SCMI Telemetry Driver"); +MODULE_LICENSE("GPL"); --=20 2.52.0 From nobody Mon Feb 9 01:43:04 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 72B7E396B7A; Wed, 14 Jan 2026 11:47:56 +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=1768391282; cv=none; b=E6+N1/t98M8uStD178Jh1V1zatkly6Y6psgmFGicKnLpfrvZ1R1ji9WILF4+phUEOdfG4Zh/8EQS2qj80L2BU7PLOBj/JR2H6MhU/9tBTlyQ/0i/AGhsLJkIoFhvPWY2GeQnXQL4DrU8Nfc16uUH4NhJyAiwYh0cEnexbhzwJsM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768391282; c=relaxed/simple; bh=MTufYMwq/JeNdgK8txWNeCfSGGYSMI7enP91UUkaen4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=kGk/gzrd6FrT8XXWuNHrm1cafEr5NTCx6+ggbY2UEckwz+Yo2jOvfymrPJRdHhkbypiRe0GA42fmcEcY2+G1aDoAsnE6DH92wwaCjdr9D74oPYLRueXQRzDQBfd/3kiTgdop8RoJznsYJ6I2zCEYw1vGuGufSUlETgEJzzW45CE= 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 0D657497; Wed, 14 Jan 2026 03:47:49 -0800 (PST) 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 646DC3F59E; Wed, 14 Jan 2026 03:47:52 -0800 (PST) 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 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, dan.carpenter@linaro.org, d-gole@ti.com, jonathan.cameron@huawei.com, elif.topuz@arm.com, lukasz.luba@arm.com, philip.radford@arm.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH v2 09/17] fs/stlmfs: Document ARM SCMI Telemetry filesystem Date: Wed, 14 Jan 2026 11:46:13 +0000 Message-ID: <20260114114638.2290765-10-cristian.marussi@arm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260114114638.2290765-1-cristian.marussi@arm.com> References: <20260114114638.2290765-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" Introduce initial ARM SCMI Telemetry filesystem documentation. Signed-off-by: Cristian Marussi --- Documentation/filesystems/stlmfs.rst | 198 +++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 Documentation/filesystems/stlmfs.rst diff --git a/Documentation/filesystems/stlmfs.rst b/Documentation/filesyste= ms/stlmfs.rst new file mode 100644 index 000000000000..7ea8878098f7 --- /dev/null +++ b/Documentation/filesystems/stlmfs.rst @@ -0,0 +1,198 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +STLMFS - Arm SCMI Telemetry Pseudo Filesystem +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +.. contents:: + +Overview +=3D=3D=3D=3D=3D=3D=3D=3D + +ARM SCMI is a System and Configuration Management protocol, based on a +client-server model, that defines a number of messages that allows a +client/agent like Linux to discover, configure and make use of services +provided by the server/platform firmware. + +SCMI v4.0 introduced support for System Telemetry, through which an agent +can dynamically enumerate configure and collect Telemetry Data Events (DE) +exposed by the platform. + +This filesystem, in turn, exposes to userspace the set of discovered DEs +allowing for their configuration and retrieval. + +Rationale +=3D=3D=3D=3D=3D=3D=3D=3D=3D + +**Why not using SysFS/KernFS or DebugFS ?** + +The provided userspace interface aims to satisfy 2 main concurrent +requirements: + + - expose an FS-based human-readable interface that can be used to + discover, configure and access Telemetry data directly from the + shell + + - allow also alternative machine-friendly, more-performant, binary + interfaces that can be used by custom tools without the overhead of + multiple accesses through the VFS layers and the hassle of navigating + vast filesystem tree structures + +All of the above is meant to be available on production systems, not +simply as a tool for development or testing, so no debugFS option here. + +An initial design based on SysFS and chardev/ioctl based interfaces was +dropped in favour of this full-fledged filesystem implementation since: + +- SysFS is a standard way to expose device related properties using a few + common helpers built on kernfs; this means, though, that unfortunately in + our scenario we would have to generate a dummy simple device for each + discovered DE. + This by itself seems an abuse of the SysFS framework, but even ignoring + this, the sheer number of potentially discoverable DEs (in the order of + tens of thousands easily) would have led to the creation of a sensibly + vast number of dummy DE devices. + +- SysFS usage itself has its well-known constraints and best practices, + like the one-file/one-value rule, that hardly cope with SCMI Telemetry + needs. + +- The need to implement more complex file operations (ioctls/mmap) in + order to support the alternative binary interfaces does not fit with + SysFS/kernFS facilities. + +- Given the nature of the Telemetry protocol, the hybrid approach with + chardev/ioctl was itself problematic: on one side being upper-limited + in the number of chardev potentially created by the minor numbers + availability, on the other side the hassle of having to maintain a + completely different interface on the side of a FS based one. + +Design +=3D=3D=3D=3D=3D=3D + +STLMFS is a pseudo filesystem used to expose ARM SCMI Telemetry data +discovered dynamically at run-time via SCMI. + +Inodes are all dynamically created at mount-time from a dedicated +kmem_cache based on the gathered available SCMI Telemetry information. + +Since inodes represent the discovered Telemetry entities, which in turn are +statically defined at the platform level and immutable throughout the same +session (boot), allocated inodes are freed only at unmount-time and the +user is not allowed to delete or create any kind of file within the STLMFS +filesystem after mount has completed. + +A single instance of STLMFS is created at the filesystem level, using +get_single_tree(), given that the same SCMI backend entities will be +involved no matter how many times you mount it. + +Mountpoints +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +A pre-defined mountpoint is available at:: + + /sys/fs/arm_telemetry/ + +Usage +=3D=3D=3D=3D=3D + +.. Note:: + See Documentation/ABI/testing/stlmfs for a detailed description of + this ABI. + +The filesystem can be typically mounted with:: + + mount -t stlmfs none /sys/fs/arm_telemetry + +It will proceed to create a top subdirectory for each of the discovered +SCMI Telemetry instances named as 'tlm_' under which it will create +the following directory structure:: + + /sys/fs/arm_telemetry/tlm_0/ + |-- all_des_enable + |-- all_des_tstamp_enable + |-- available_update_intervals_ms + |-- current_update_interval_ms + |-- de_implementation_version + |-- des/ + | |-- ... + | |-- ... + | `-- ... + |-- des_bulk_read + |-- des_single_sample_read + |-- groups + | |-- ... + | |-- ... + | `-- ... + |-- intervals_discrete + |-- reset + |-- tlm_enable + `-- version + +Each subdirectory is defined as follows. + +des/ +---- +A subtree containing in turn one subdirectory for each discovered DE and +named by Data Event ID in hexadecimal form as in:: + + |-- des + | |-- 0x00000000 + | |-- 0x00000016 + | |-- 0x00001010 + | |-- 0x0000A000 + | |-- 0x0000A001 + | |-- 0x0000A002 + | |-- 0x0000A005 + | |-- .......... + | |-- .......... + | |-- 0x0000A007 + | |-- 0x0000A008 + | |-- 0x0000A00A + | |-- 0x0000A00B + | |-- 0x0000A00C + | `-- 0x0000A010 + +where each dedicated DE subdirectory in turn will contain files used to +describe some DE characteristics, configure it, or read its current data +value as in:: + + tlm_0/des/0xA001/ + |-- compo_instance_id + |-- compo_type + |-- enable + |-- instance_id + |-- name + |-- persistent + |-- tstamp_enable + |-- tstamp_exp + |-- type + |-- unit + |-- unit_exp + `-- value + +groups/ +------- + +An optional subtree containing in turn one subdirectory for each discovered +Group and named by Group ID as in:: + + |-- groups + | |-- 0 + | |-- .. + | `-- 1 + +where each dedicated GROUP subdirectory in turn will contain files used to +describe some Group characteristics, configure it, or read its current data +values, as in:: + + scmi_tlm_0/groups/0/ + |-- available_update_intervals_ms + |-- composing_des + |-- current_update_interval_ms + |-- des_bulk_read + |-- des_single_sample_read + |-- enable + |-- intervals_discrete + `-- tstamp_enable + --=20 2.52.0 From nobody Mon Feb 9 01:43:04 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 2D4B2397AA1; Wed, 14 Jan 2026 11:47:59 +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=1768391284; cv=none; b=gczZgdwPscvFBMNRiIsJXGY2t2IY/qXdcgZe0hmS4ImTaNvttXif9RyY21ZJTZoV8znC15qOZ4jV4Esi7rCIbnG5XsloCjBhd9CH5ArNfdxwZx5O7eKlWQEOP83S73KpStuiQtBPL2ntuQp6Cy9VQW+Soul7cmEdC1G9J6M//uI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768391284; c=relaxed/simple; bh=sb8GSx2KVdCaDVSv8YzQ0hF6IsADgcr7Wff0YgVsrgM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=bYUNckcG1aZast4ELpuAC4njuzJVijc7SkpfFJ9x+GfZqTMU606xRzV7QRjrKHpimdIEQayB1i0YwiY5btOb+lK1jHHjwEVczmANKGWmuWuWJg6eJA1nyNUR5GZAzTCOJ+PI6S67vl/8lSvGKG8NslVfigYoqISOXXhiYZjiJyc= 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 DEAB11424; Wed, 14 Jan 2026 03:47:52 -0800 (PST) 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 45AC63F59E; Wed, 14 Jan 2026 03:47:56 -0800 (PST) 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 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, dan.carpenter@linaro.org, d-gole@ti.com, jonathan.cameron@huawei.com, elif.topuz@arm.com, lukasz.luba@arm.com, philip.radford@arm.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH v2 10/17] firmware: arm_scmi: Add System Telemetry ioctls support Date: Wed, 14 Jan 2026 11:46:14 +0000 Message-ID: <20260114114638.2290765-11-cristian.marussi@arm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260114114638.2290765-1-cristian.marussi@arm.com> References: <20260114114638.2290765-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" Extend the filesystem based interface with special 'control' files that can be used to configure and retrieve SCMI Telemetry data in binary form using the ioctls-based ABI described in uapi/linux/scmi.h. This alternative ABI is meant to provide a more performant access to SCMI Telemetry configuration and data events, without the hassle of navigating the human readable VFS based intwerface. Signed-off-by: Cristian Marussi --- v1 --> v2 - Use new res_get() operation which use new resource accessors - Use new de_lookup() tlm_ops - Using cleanup.h --- .../firmware/arm_scmi/scmi_system_telemetry.c | 404 ++++++++++++++++++ 1 file changed, 404 insertions(+) diff --git a/drivers/firmware/arm_scmi/scmi_system_telemetry.c b/drivers/fi= rmware/arm_scmi/scmi_system_telemetry.c index b48f2d4eecae..721de615bec3 100644 --- a/drivers/firmware/arm_scmi/scmi_system_telemetry.c +++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c @@ -24,6 +24,8 @@ #include #include =20 +#include + #define TLM_FS_MAGIC 0x75C01C80 #define TLM_FS_NAME "stlmfs" #define TLM_FS_MNT "arm_telemetry" @@ -1056,6 +1058,406 @@ DEFINE_TLM_CLASS(grp_available_interval_tlmo, "avai= lable_update_intervals_ms", DEFINE_TLM_CLASS(grp_intervals_discrete_tlmo, "intervals_discrete", TLM_IS_GROUP, S_IFREG | S_IRUSR, &intrv_discrete_fops, NULL); =20 +static long +scmi_tlm_info_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned long a= rg) +{ + const struct scmi_telemetry_info *info =3D tlmi->priv; + void * __user uptr =3D (void * __user)arg; + + if (copy_to_user(uptr, &info->base, sizeof(info->base))) + return -EFAULT; + + return 0; +} + +static long +scmi_tlm_intervals_get_ioctl(const struct scmi_tlm_inode *tlmi, + unsigned long arg, bool is_group) +{ + struct scmi_tlm_intervals ivs, *tlm_ivs; + void * __user uptr =3D (void * __user)arg; + + if (copy_from_user(&ivs, uptr, sizeof(ivs))) + return -EFAULT; + + if (!is_group) { + const struct scmi_telemetry_info *info =3D tlmi->priv; + + tlm_ivs =3D info->intervals; + } else { + const struct scmi_telemetry_group *grp =3D tlmi->priv; + + tlm_ivs =3D grp->intervals; + } + + if (ivs.num !=3D tlm_ivs->num) + return -EINVAL; + + if (copy_to_user(uptr, tlm_ivs, + sizeof(*tlm_ivs) + sizeof(u32) * ivs.num)) + return -EFAULT; + + return 0; +} + +static long +scmi_tlm_de_config_set_ioctl(const struct scmi_tlm_inode *tlmi, + unsigned long arg, bool all) +{ + const struct scmi_telemetry_res_info *rinfo; + void * __user uptr =3D (void * __user)arg; + struct scmi_tlm_setup *tsp =3D tlmi->tsp; + struct scmi_tlm_de_config tcfg =3D {}; + int ret; + + if (copy_from_user(&tcfg, uptr, sizeof(tcfg))) + return -EFAULT; + + if (!all) + return tsp->ops->state_set(tsp->ph, false, tcfg.id, + (bool *)&tcfg.enable, + (bool *)&tcfg.t_enable); + + rinfo =3D scmi_telemetry_res_info_get(tsp); + for (int i =3D 0; i < rinfo->num_des; i++) { + ret =3D tsp->ops->state_set(tsp->ph, false, + rinfo->des[i]->info->id, + (bool *)&tcfg.enable, + (bool *)&tcfg.t_enable); + if (ret) + return ret; + } + + return 0; +} + +static long +scmi_tlm_de_config_get_ioctl(const struct scmi_tlm_inode *tlmi, + unsigned long arg) +{ + struct scmi_tlm_setup *tsp =3D tlmi->tsp; + void * __user uptr =3D (void * __user)arg; + struct scmi_tlm_de_config tcfg =3D {}; + int ret; + + if (copy_from_user(&tcfg, uptr, sizeof(tcfg))) + return -EFAULT; + + ret =3D tsp->ops->state_get(tsp->ph, tcfg.id, + (bool *)&tcfg.enable, (bool *)&tcfg.t_enable); + if (ret) + return ret; + + if (copy_to_user(uptr, &tcfg, sizeof(tcfg))) + return -EFAULT; + + return 0; +} + +static long +scmi_tlm_config_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned long= arg, + bool is_group) +{ + void * __user uptr =3D (void * __user)arg; + struct scmi_tlm_config cfg; + + if (!is_group) { + const struct scmi_telemetry_info *info =3D tlmi->priv; + + cfg.enable =3D !!info->enabled; + cfg.current_update_interval =3D info->active_update_interval; + } else { + const struct scmi_telemetry_group *grp =3D tlmi->priv; + + cfg.enable =3D !!grp->enabled; + cfg.t_enable =3D !!grp->tstamp_enabled; + cfg.current_update_interval =3D grp->active_update_interval; + } + + if (copy_to_user(uptr, &cfg, sizeof(cfg))) + return -EFAULT; + + return 0; +} + +static long +scmi_tlm_config_set_ioctl(const struct scmi_tlm_inode *tlmi, unsigned long= arg, + bool is_group) +{ + struct scmi_tlm_setup *tsp =3D tlmi->tsp; + void * __user uptr =3D (void * __user)arg; + struct scmi_tlm_config cfg =3D {}; + bool grp_ignore; + int res_id; + + if (copy_from_user(&cfg, uptr, sizeof(cfg))) + return -EFAULT; + + if (!is_group) { + res_id =3D SCMI_TLM_GRP_INVALID; + grp_ignore =3D true; + } else { + const struct scmi_telemetry_group *grp =3D tlmi->priv; + int ret; + + res_id =3D grp->info->id; + grp_ignore =3D false; + ret =3D tsp->ops->state_set(tsp->ph, true, res_id, + (bool *)&cfg.enable, + (bool *)&cfg.t_enable); + if (ret) + return ret; + } + + return tsp->ops->collection_configure(tsp->ph, res_id, grp_ignore, + (bool *)&cfg.enable, + &cfg.current_update_interval, + NULL); +} + +static long +scmi_tlm_de_info_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned lon= g arg) +{ + struct scmi_tlm_setup *tsp =3D tlmi->tsp; + void * __user uptr =3D (void * __user)arg; + const struct scmi_telemetry_de *de; + struct scmi_tlm_de_info dei; + + if (copy_from_user(&dei, uptr, sizeof(dei))) + return -EFAULT; + + de =3D tsp->ops->de_lookup(tsp->ph, dei.id); + if (!de) + return -EINVAL; + + if (copy_to_user(uptr, de->info, sizeof(*de->info))) + return -EFAULT; + + return 0; +} + +static long +scmi_tlm_des_list_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned lo= ng arg) +{ + struct scmi_tlm_setup *tsp =3D tlmi->tsp; + const struct scmi_telemetry_res_info *rinfo; + void * __user uptr =3D (void * __user)arg; + struct scmi_tlm_des_list dsl; + + rinfo =3D scmi_telemetry_res_info_get(tsp); + if (copy_from_user(&dsl, uptr, sizeof(dsl))) + return -EFAULT; + + if (dsl.num_des < rinfo->num_des) + return -EINVAL; + + if (copy_to_user(uptr, &rinfo->num_des, sizeof(rinfo->num_des))) + return -EFAULT; + + if (copy_to_user(uptr + sizeof(rinfo->num_des), rinfo->dei_store, + rinfo->num_des * sizeof(*rinfo->dei_store))) + return -EFAULT; + + return 0; +} + +static long +scmi_tlm_de_value_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned lo= ng arg) +{ + struct scmi_tlm_setup *tsp =3D tlmi->tsp; + void * __user uptr =3D (void * __user)arg; + struct scmi_tlm_de_sample sample; + int ret; + + if (copy_from_user(&sample, uptr, sizeof(sample))) + return -EFAULT; + + ret =3D tsp->ops->de_data_read(tsp->ph, + (struct scmi_telemetry_de_sample *)&sample); + if (ret) + return ret; + + if (copy_to_user(uptr, &sample, sizeof(sample))) + return -EFAULT; + + return 0; +} + +static long +scmi_tlm_grp_info_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned lo= ng arg) +{ + const struct scmi_telemetry_group *grp =3D tlmi->priv; + void * __user uptr =3D (void * __user)arg; + + if (copy_to_user(uptr, grp->info, sizeof(*grp->info))) + return -EFAULT; + + return 0; +} + +static long +scmi_tlm_grp_desc_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned lo= ng arg) +{ + const struct scmi_telemetry_group *grp =3D tlmi->priv; + unsigned int num_des =3D grp->info->num_des; + void * __user uptr =3D (void * __user)arg; + struct scmi_tlm_grp_desc grp_desc; + + if (copy_from_user(&grp_desc, uptr, sizeof(grp_desc))) + return -EFAULT; + + if (grp_desc.num_des < num_des) + return -EINVAL; + + if (copy_to_user(uptr, &num_des, sizeof(num_des))) + return -EFAULT; + + if (copy_to_user(uptr + sizeof(num_des), grp->des, + sizeof(*grp->des) * num_des)) + return -EFAULT; + + return 0; +} + +static long +scmi_tlm_grps_list_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned l= ong arg) +{ + struct scmi_tlm_setup *tsp =3D tlmi->tsp; + const struct scmi_telemetry_res_info *rinfo; + void * __user uptr =3D (void * __user)arg; + struct scmi_tlm_grps_list gsl; + + if (copy_from_user(&gsl, uptr, sizeof(gsl))) + return -EFAULT; + + rinfo =3D scmi_telemetry_res_info_get(tsp); + if (gsl.num_grps < rinfo->num_groups) + return -EINVAL; + + if (copy_to_user(uptr, &rinfo->num_groups, sizeof(rinfo->num_groups))) + return -EFAULT; + + if (copy_to_user(uptr + sizeof(rinfo->num_groups), rinfo->grps_store, + rinfo->num_groups * sizeof(*rinfo->grps_store))) + return -EFAULT; + + return 0; +} + +static long scmi_tlm_des_read_ioctl(const struct scmi_tlm_inode *tlmi, + unsigned long arg, bool single, + bool is_group) +{ + struct scmi_tlm_setup *tsp =3D tlmi->tsp; + void * __user uptr =3D (void * __user)arg; + struct scmi_tlm_data_read bulk; + int ret, grp_id =3D SCMI_TLM_GRP_INVALID; + + if (copy_from_user(&bulk, uptr, sizeof(bulk))) + return -EFAULT; + + struct scmi_tlm_data_read *bulk_ptr __free(kfree) =3D + kzalloc(struct_size(bulk_ptr, samples, bulk.num_samples), + GFP_KERNEL); + if (!bulk_ptr) + return -ENOMEM; + + if (is_group) { + const struct scmi_telemetry_group *grp =3D tlmi->priv; + + grp_id =3D grp->info->id; + } + + bulk_ptr->num_samples =3D bulk.num_samples; + if (!single) + ret =3D tsp->ops->des_bulk_read(tsp->ph, grp_id, + &bulk_ptr->num_samples, + (struct scmi_telemetry_de_sample *)bulk_ptr->samples); + else + ret =3D tsp->ops->des_sample_get(tsp->ph, grp_id, + &bulk_ptr->num_samples, + (struct scmi_telemetry_de_sample *)bulk_ptr->samples); + if (ret) + return ret; + + if (copy_to_user(uptr, bulk_ptr, sizeof(*bulk_ptr) + + bulk_ptr->num_samples * sizeof(bulk_ptr->samples[0]))) + return -EFAULT; + + return 0; +} + +static long scmi_tlm_unlocked_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + bool is_group =3D IS_GROUP(tlmi->cls->flags); + + switch (cmd) { + case SCMI_TLM_GET_INFO: + if (is_group) + return -EOPNOTSUPP; + return scmi_tlm_info_get_ioctl(tlmi, arg); + case SCMI_TLM_GET_CFG: + return scmi_tlm_config_get_ioctl(tlmi, arg, is_group); + case SCMI_TLM_SET_CFG: + return scmi_tlm_config_set_ioctl(tlmi, arg, is_group); + case SCMI_TLM_GET_INTRVS: + return scmi_tlm_intervals_get_ioctl(tlmi, arg, is_group); + case SCMI_TLM_GET_DE_CFG: + if (is_group) + return -EOPNOTSUPP; + return scmi_tlm_de_config_get_ioctl(tlmi, arg); + case SCMI_TLM_SET_DE_CFG: + if (is_group) + return -EOPNOTSUPP; + return scmi_tlm_de_config_set_ioctl(tlmi, arg, false); + case SCMI_TLM_GET_DE_INFO: + if (is_group) + return -EOPNOTSUPP; + return scmi_tlm_de_info_get_ioctl(tlmi, arg); + case SCMI_TLM_GET_DE_LIST: + if (is_group) + return -EOPNOTSUPP; + return scmi_tlm_des_list_get_ioctl(tlmi, arg); + case SCMI_TLM_GET_DE_VALUE: + if (is_group) + return -EOPNOTSUPP; + return scmi_tlm_de_value_get_ioctl(tlmi, arg); + case SCMI_TLM_SET_ALL_CFG: + return scmi_tlm_de_config_set_ioctl(tlmi, arg, true); + case SCMI_TLM_GET_GRP_LIST: + if (is_group) + return -EOPNOTSUPP; + return scmi_tlm_grps_list_get_ioctl(tlmi, arg); + case SCMI_TLM_GET_GRP_INFO: + if (!is_group) + return -EOPNOTSUPP; + return scmi_tlm_grp_info_get_ioctl(tlmi, arg); + case SCMI_TLM_GET_GRP_DESC: + if (!is_group) + return -EOPNOTSUPP; + return scmi_tlm_grp_desc_get_ioctl(tlmi, arg); + case SCMI_TLM_SINGLE_SAMPLE: + return scmi_tlm_des_read_ioctl(tlmi, arg, true, is_group); + case SCMI_TLM_BULK_READ: + return scmi_tlm_des_read_ioctl(tlmi, arg, false, is_group); + default: + return -ENOTTY; + } +} + +static const struct file_operations scmi_tlm_ctrl_fops =3D { + .owner =3D THIS_MODULE, + .open =3D nonseekable_open, + .unlocked_ioctl =3D scmi_tlm_unlocked_ioctl, +}; + +DEFINE_TLM_CLASS(ctrl_tlmo, "control", 0, + S_IFREG | S_IRUSR | S_IWUSR, &scmi_tlm_ctrl_fops, NULL); +DEFINE_TLM_CLASS(grp_ctrl_tlmo, "control", TLM_IS_GROUP, + S_IFREG | S_IRUSR | S_IWUSR, &scmi_tlm_ctrl_fops, NULL); + static int scmi_telemetry_groups_initialize(struct scmi_tlm_instance *ti) { const struct scmi_telemetry_res_info *rinfo; @@ -1095,6 +1497,7 @@ static int scmi_telemetry_groups_initialize(struct sc= mi_tlm_instance *ti) stlmfs_create_dentry(sb, tsp, grp_dir_dentry, &grp_composing_des_tlmo, grp->des_str); =20 + stlmfs_create_dentry(sb, tsp, grp_dir_dentry, &grp_ctrl_tlmo, grp); stlmfs_create_dentry(sb, tsp, grp_dir_dentry, &grp_data_tlmo, grp); stlmfs_create_dentry(sb, tsp, grp_dir_dentry, &grp_single_sample_tlmo, grp); @@ -1279,6 +1682,7 @@ static int scmi_tlm_root_dentries_initialize(struct s= cmi_tlm_instance *ti) &ti->info->base.version); stlmfs_create_dentry(sb, tsp, ti->top_dentry, &data_tlmo, ti->info); stlmfs_create_dentry(sb, tsp, ti->top_dentry, &single_sample_tlmo, ti->in= fo); + stlmfs_create_dentry(sb, tsp, ti->top_dentry, &ctrl_tlmo, ti->info); ti->des_dentry =3D stlmfs_create_dentry(sb, tsp, ti->top_dentry, &des_dir_cls, NULL); ti->grps_dentry =3D --=20 2.52.0 From nobody Mon Feb 9 01:43:04 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 5AD83396B7F; Wed, 14 Jan 2026 11:48:03 +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=1768391291; cv=none; b=Vku9kUAJX03L/FIxIdFaLwTkUJR0/gVpvkKVgSuGckHnzykvonr+7i9n/Qj/BUEfN0M/Ft0pX1TttsmuH7lvfA4HIPgSaliiUK2QMWcdvivFCdWkp1NKyu+AD4j5pU8IiHd1N27efE1jKr3Dn6czNTgrxSww5ovTLl3rDJ2iibM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768391291; c=relaxed/simple; bh=11wXPYjHj3h165OF2QwSpTZnR8wyVKglIC99G0i0DeA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=kEGZLZOeQbK7rMiZmKb6KkM3tOZTNaJW9XoCjVNrNwZ4BgzNiHgzd6BgiO5qIyW4JBsc8PHtOYOI2Xe73eRsqQgfNLHoQ15vPBwp9rJgufsyixVas09E0LglE42QmBpq/9NAw4Usn36iA9FncrS+ZiOzT9Zb3JEEthFVbBd7B+Q= 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 BA52D1515; Wed, 14 Jan 2026 03:47:56 -0800 (PST) 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 25C843F59E; Wed, 14 Jan 2026 03:47:59 -0800 (PST) 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 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, dan.carpenter@linaro.org, d-gole@ti.com, jonathan.cameron@huawei.com, elif.topuz@arm.com, lukasz.luba@arm.com, philip.radford@arm.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH v2 11/17] fs/stlmfs: Document alternative ioctl based binary interface Date: Wed, 14 Jan 2026 11:46:15 +0000 Message-ID: <20260114114638.2290765-12-cristian.marussi@arm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260114114638.2290765-1-cristian.marussi@arm.com> References: <20260114114638.2290765-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" Document the additionally provided special files and their usage in the context of the alternative binary ioctl-based interface. Signed-off-by: Cristian Marussi --- Documentation/filesystems/stlmfs.rst | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Documentation/filesystems/stlmfs.rst b/Documentation/filesyste= ms/stlmfs.rst index 7ea8878098f7..5c23f7e5f12c 100644 --- a/Documentation/filesystems/stlmfs.rst +++ b/Documentation/filesystems/stlmfs.rst @@ -112,6 +112,7 @@ the following directory structure:: |-- all_des_enable |-- all_des_tstamp_enable |-- available_update_intervals_ms + |-- control |-- current_update_interval_ms |-- de_implementation_version |-- des/ @@ -129,6 +130,10 @@ the following directory structure:: |-- tlm_enable `-- version =20 +.. Note:: + The control/ special file can be used to use the alternative + binary interface described in include/uapi/linux/scmi.h + Each subdirectory is defined as follows. =20 des/ @@ -189,6 +194,7 @@ values, as in:: scmi_tlm_0/groups/0/ |-- available_update_intervals_ms |-- composing_des + |-- control |-- current_update_interval_ms |-- des_bulk_read |-- des_single_sample_read @@ -196,3 +202,21 @@ values, as in:: |-- intervals_discrete `-- tstamp_enable =20 +Alternative Binary Interfaces - Special files +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Special files are populated across the filesystem so as to implement the s= upport +of more performant alternative binary interfaces that can be used instead = of the +main human readable ABI. + +IOCTLs Interface +---------------- + +The ioctl-based interface is detailed in:: + + include/uapi/linux/smci.h + +The filesystem provides special files named *control/* to be used with the +ioctl interface mentioned above: note that the behaviour of some of the io= ctls +is dependent on which *control/* file is used to invoke them (as detailed = in the +UAPI header above). --=20 2.52.0 From nobody Mon Feb 9 01:43:04 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 37044399003; Wed, 14 Jan 2026 11:48:07 +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=1768391290; cv=none; b=kGHDMkylG2TJNkyBGLC6iw51cCpps7FNkrhC3Kw4SKce6WQQDH3uCfA41UIgfT64CXWDIANDnHv/zzNc7SzfrXgs5uZrtb4Ve+29t5s89Kx5MfSsp0TwM/pRpck1AsRT3Ilk5xOKH+0RUi0GZ4n6IQY9nDqeR7ZXWu/3F9VMUv8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768391290; c=relaxed/simple; bh=OZgZ5YG15OG22JYQY6PWXweLR+WYGD8unQGsfd3zcWE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=KZ2yiyLbNX0WE3TcdESkt0BwhrMLXaNjZSQeOL9Xt8Q72m1iGI4ApQTkz+7Sd4CyQiN8a5+nsxjyO7bCTKYh6Dgn0vTPBSMZUJc1LkLpmdQzS6ufNIfIYduMoUgM9MbucH5Nqq5dDIERi+7aSg36tHp2XJ7+8cSx+JYjno59hEw= 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 98873339; Wed, 14 Jan 2026 03:48:00 -0800 (PST) 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 01C663F59E; Wed, 14 Jan 2026 03:48:03 -0800 (PST) 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 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, dan.carpenter@linaro.org, d-gole@ti.com, jonathan.cameron@huawei.com, elif.topuz@arm.com, lukasz.luba@arm.com, philip.radford@arm.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH v2 12/17] firmware: arm_scmi: Add Telemetry components view Date: Wed, 14 Jan 2026 11:46:16 +0000 Message-ID: <20260114114638.2290765-13-cristian.marussi@arm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260114114638.2290765-1-cristian.marussi@arm.com> References: <20260114114638.2290765-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 an alternative filesystem view for the discovered Data Events, where the tree of DEs is laid out following the discovered topological order instead of the existing flat layout. Signed-off-by: Cristian Marussi --- v1 --> v2 - Use new FS API - Introduce new stlmfs_lookup_by_name helper --- .../firmware/arm_scmi/scmi_system_telemetry.c | 684 ++++++++++++++++++ 1 file changed, 684 insertions(+) diff --git a/drivers/firmware/arm_scmi/scmi_system_telemetry.c b/drivers/fi= rmware/arm_scmi/scmi_system_telemetry.c index 721de615bec3..1221520356fd 100644 --- a/drivers/firmware/arm_scmi/scmi_system_telemetry.c +++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c @@ -174,6 +174,7 @@ struct scmi_tlm_inode { * @top_dentry: A reference to the top dentry for this instance. * @des_dentry: A reference to the DES dentry for this instance. * @grps_dentry: A reference to the groups dentry for this instance. + * @compo_dentry: A reference to the components dentry for this instance. * @info: A handy reference to this instance SCMI Telemetry info data. * */ @@ -188,6 +189,7 @@ struct scmi_tlm_instance { struct dentry *top_dentry; struct dentry *des_dentry; struct dentry *grps_dentry; + struct dentry *compo_dentry; const struct scmi_telemetry_info *info; }; =20 @@ -196,6 +198,526 @@ static int scmi_telemetry_instance_register(struct su= per_block *sb, =20 static LIST_HEAD(scmi_telemetry_instances); =20 +#define TYPES_ARRAY_SZ 256 + +static const char *compo_types[TYPES_ARRAY_SZ] =3D { + "unspec", + "cpu", + "cluster", + "gpu", + "npu", + "interconnnect", + "mem_cntrl", + "l1_cache", + "l2_cache", + "l3_cache", + "ll_cache", + "sys_cache", + "disp_cntrl", + "ipu", + "chiplet", + "package", + "soc", + "system", + "smcu", + "accel", + "battery", + "charger", + "pmic", + "board", + "memory", + "periph", + "periph_subc", + "lid", + "display", + "res_29", + "res_30", + "res_31", + "res_32", + "res_33", + "res_34", + "res_35", + "res_36", + "res_37", + "res_38", + "res_39", + "res_40", + "res_41", + "res_42", + "res_43", + "res_44", + "res_45", + "res_46", + "res_47", + "res_48", + "res_49", + "res_50", + "res_51", + "res_52", + "res_53", + "res_54", + "res_55", + "res_56", + "res_57", + "res_58", + "res_59", + "res_60", + "res_61", + "res_62", + "res_63", + "res_64", + "res_65", + "res_66", + "res_67", + "res_68", + "res_69", + "res_70", + "res_71", + "res_72", + "res_73", + "res_74", + "res_75", + "res_76", + "res_77", + "res_78", + "res_79", + "res_80", + "res_81", + "res_82", + "res_83", + "res_84", + "res_85", + "res_86", + "res_87", + "res_88", + "res_89", + "res_90", + "res_91", + "res_92", + "res_93", + "res_94", + "res_95", + "res_96", + "res_97", + "res_98", + "res_99", + "res_100", + "res_101", + "res_102", + "res_103", + "res_104", + "res_105", + "res_106", + "res_107", + "res_108", + "res_109", + "res_110", + "res_111", + "res_112", + "res_113", + "res_114", + "res_115", + "res_116", + "res_117", + "res_118", + "res_119", + "res_120", + "res_121", + "res_122", + "res_123", + "res_124", + "res_125", + "res_126", + "res_127", + "res_128", + "res_129", + "res_130", + "res_131", + "res_132", + "res_133", + "res_134", + "res_135", + "res_136", + "res_137", + "res_138", + "res_139", + "res_140", + "res_141", + "res_142", + "res_143", + "res_144", + "res_145", + "res_146", + "res_147", + "res_148", + "res_149", + "res_150", + "res_151", + "res_152", + "res_153", + "res_154", + "res_155", + "res_156", + "res_157", + "res_158", + "res_159", + "res_160", + "res_161", + "res_162", + "res_163", + "res_164", + "res_165", + "res_166", + "res_167", + "res_168", + "res_169", + "res_170", + "res_171", + "res_172", + "res_173", + "res_174", + "res_175", + "res_176", + "res_177", + "res_178", + "res_179", + "res_180", + "res_181", + "res_182", + "res_183", + "res_184", + "res_185", + "res_186", + "res_187", + "res_188", + "res_189", + "res_190", + "res_191", + "res_192", + "res_193", + "res_194", + "res_195", + "res_196", + "res_197", + "res_198", + "res_199", + "res_200", + "res_201", + "res_202", + "res_203", + "res_204", + "res_205", + "res_206", + "res_207", + "res_208", + "res_209", + "res_210", + "res_211", + "res_212", + "res_213", + "res_214", + "res_215", + "res_216", + "res_217", + "res_218", + "res_219", + "res_220", + "res_221", + "res_222", + "res_223", + "oem_224", + "oem_225", + "oem_226", + "oem_227", + "oem_228", + "oem_229", + "oem_230", + "oem_231", + "oem_232", + "oem_233", + "oem_234", + "oem_235", + "oem_236", + "oem_237", + "oem_238", + "oem_239", + "oem_240", + "oem_241", + "oem_242", + "oem_243", + "oem_244", + "oem_245", + "oem_246", + "oem_247", + "oem_248", + "oem_249", + "oem_250", + "oem_251", + "oem_252", + "oem_253", + "oem_254", + "oem_255", +}; + +static const char *unit_types[TYPES_ARRAY_SZ] =3D { + "none", + "unspec", + "celsius", + "fahrenheit", + "kelvin", + "volts", + "amps", + "watts", + "joules", + "coulombs", + "va", + "nits", + "lumens", + "lux", + "candelas", + "kpa", + "psi", + "newtons", + "cfm", + "rpm", + "hertz", + "seconds", + "minutes", + "hours", + "days", + "weeks", + "mils", + "inches", + "feet", + "cubic_inches", + "cubic_feet", + "meters", + "cubic_centimeters", + "cubic_meters", + "liters", + "fluid_ounces", + "radians", + "steradians", + "revolutions", + "cycles", + "gravities", + "ounces", + "pounds", + "foot_pounds", + "ounce_inches", + "gauss", + "gilberts", + "henries", + "farads", + "ohms", + "siemens", + "moles", + "becquerels", + "ppm", + "decibels", + "dba", + "dbc", + "grays", + "sieverts", + "color_temp_kelvin", + "bits", + "bytes", + "words", + "dwords", + "qwords", + "percentage", + "pascals", + "counts", + "grams", + "newton_meters", + "hits", + "misses", + "retries", + "overruns", + "underruns", + "collisions", + "packets", + "messages", + "chars", + "errors", + "corrected_err", + "uncorrectable_err", + "square_mils", + "square_inches", + "square_feet", + "square_centimeters", + "square_meters", + "radians_per_secs", + "beats_per_minute", + "meters_per_secs_squared", + "meters_per_secs", + "cubic_meter_per_secs", + "millimeters_mercury", + "radians_per_secs_squared", + "state", + "bps", + "res_96", + "res_97", + "res_98", + "res_99", + "res_100", + "res_101", + "res_102", + "res_103", + "res_104", + "res_105", + "res_106", + "res_107", + "res_108", + "res_109", + "res_110", + "res_111", + "res_112", + "res_113", + "res_114", + "res_115", + "res_116", + "res_117", + "res_118", + "res_119", + "res_120", + "res_121", + "res_122", + "res_123", + "res_124", + "res_125", + "res_126", + "res_127", + "res_128", + "res_129", + "res_130", + "res_131", + "res_132", + "res_133", + "res_134", + "res_135", + "res_136", + "res_137", + "res_138", + "res_139", + "res_140", + "res_141", + "res_142", + "res_143", + "res_144", + "res_145", + "res_146", + "res_147", + "res_148", + "res_149", + "res_150", + "res_151", + "res_152", + "res_153", + "res_154", + "res_155", + "res_156", + "res_157", + "res_158", + "res_159", + "res_160", + "res_161", + "res_162", + "res_163", + "res_164", + "res_165", + "res_166", + "res_167", + "res_168", + "res_169", + "res_170", + "res_171", + "res_172", + "res_173", + "res_174", + "res_175", + "res_176", + "res_177", + "res_178", + "res_179", + "res_180", + "res_181", + "res_182", + "res_183", + "res_184", + "res_185", + "res_186", + "res_187", + "res_188", + "res_189", + "res_190", + "res_191", + "res_192", + "res_193", + "res_194", + "res_195", + "res_196", + "res_197", + "res_198", + "res_199", + "res_200", + "res_201", + "res_202", + "res_203", + "res_204", + "res_205", + "res_206", + "res_207", + "res_208", + "res_209", + "res_210", + "res_211", + "res_212", + "res_213", + "res_214", + "res_215", + "res_216", + "res_217", + "res_218", + "res_219", + "res_220", + "res_221", + "res_222", + "res_223", + "res_224", + "res_225", + "res_226", + "res_227", + "res_228", + "res_229", + "res_230", + "res_231", + "res_232", + "res_233", + "res_234", + "res_235", + "res_236", + "res_237", + "res_238", + "res_239", + "res_240", + "res_241", + "res_242", + "res_243", + "res_244", + "res_245", + "res_246", + "res_247", + "res_248", + "res_249", + "res_250", + "res_251", + "res_252", + "res_253", + "res_254", + "oem_unit", +}; + static struct inode *stlmfs_get_inode(struct super_block *sb) { struct inode *inode =3D new_inode(sb); @@ -815,6 +1337,18 @@ DEFINE_TLM_CLASS(persistent_tlmo, "persistent", 0, DEFINE_TLM_CLASS(value_tlmo, "value", 0, S_IFREG | S_IRUSR, &de_read_fops, NULL); =20 +static inline struct dentry * +stlmfs_lookup_by_name(struct dentry *parent, const char *dname) +{ + struct qstr qstr; + + qstr.name =3D dname; + qstr.len =3D strlen(dname); + qstr.hash =3D full_name_hash(parent, qstr.name, qstr.len); + + return d_lookup(parent, &qstr); +} + static int scmi_telemetry_de_populate(struct super_block *sb, struct scmi_tlm_setup *tsp, struct dentry *parent, @@ -1659,6 +2193,150 @@ static struct dentry *stlmfs_create_root_dentry(str= uct super_block *sb) return dentry; } =20 +static int scmi_telemetry_de_subdir_symlink(struct super_block *sb, + struct scmi_tlm_setup *tsp, + const struct scmi_telemetry_de *de, + struct dentry *parent) +{ + struct dentry *dentry; + struct inode *inode; + int ret; + + if (IS_ERR(parent)) + return 0; + + char *name __free(kfree) =3D kasprintf(GFP_KERNEL, "0x%08X", de->info->id= ); + if (!name) + return -ENOMEM; + + char *link __free(kfree) =3D + kasprintf(GFP_KERNEL, "../../../../../des/0x%08X", de->info->id); + if (!link) + return -ENOMEM; + + dentry =3D simple_start_creating(parent, name); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + + inode =3D stlmfs_get_inode(sb); + if (unlikely(!inode)) { + dev_err(tsp->dev, + "out of free dentries, cannot create '%s'", name); + return stlmfs_failed_creating(dentry); + } + + inode->i_mode =3D S_IFLNK | 0777; + inode->i_op =3D &simple_symlink_inode_operations; + inode_init_owner(&nop_mnt_idmap, inode, NULL, inode->i_mode); + inode->i_link =3D no_free_ptr(link); + + //d_add(dentry, inode); + d_make_persistent(dentry, inode); + + simple_done_creating(dentry); + + return ret; +} + +static struct dentry * +scmi_telemetry_topology_path_get(struct super_block *sb, + struct scmi_tlm_setup *tsp, + struct dentry *parent, const char *dname) +{ + struct dentry *dentry; + + dentry =3D stlmfs_lookup_by_name(parent, dname); + if (!dentry) { + struct scmi_tlm_class *dir_tlm_cls __free(kfree) =3D + kzalloc(sizeof(*dir_tlm_cls), GFP_KERNEL); + if (!dir_tlm_cls) + return NULL; + + dir_tlm_cls->name =3D kasprintf(GFP_KERNEL, "%s", dname); + if (!dir_tlm_cls->name) + return NULL; + + dir_tlm_cls->mode =3D S_IFDIR | S_IRWXU; + dir_tlm_cls->flags =3D TLM_IS_DYNAMIC; + + dentry =3D stlmfs_create_dentry(sb, tsp, parent, + dir_tlm_cls, NULL); + if (!IS_ERR(dentry)) + retain_and_null_ptr(dir_tlm_cls); + } + + return dentry; +} + +static int scmi_telemetry_topology_add_node(struct super_block *sb, + struct scmi_tlm_instance *ti, + const struct scmi_telemetry_de *de) +{ + struct dentry *ctype, *cinst, *cunit, *dinst; + struct scmi_tlm_de_info *dei =3D de->info; + char inst_str[32]; + int ret; + + /* by_compo_type// */ + ctype =3D scmi_telemetry_topology_path_get(sb, ti->tsp, ti->compo_dentry, + compo_types[dei->compo_type]); + if (!ctype) + return -ENOMEM; + + /* by_compo_type/// */ + snprintf(inst_str, 32, "%u", dei->compo_instance_id); + cinst =3D scmi_telemetry_topology_path_get(sb, ti->tsp, ctype, inst_str); + dput(ctype); + if (!cinst) + return -ENOMEM; + + /* by_compo_type//// */ + cunit =3D scmi_telemetry_topology_path_get(sb, ti->tsp, cinst, + unit_types[dei->unit]); + dput(cinst); + if (!cunit) + return -ENOMEM; + + /* by_compo_type//// */ + snprintf(inst_str, 32, "%u", dei->instance_id); + dinst =3D scmi_telemetry_topology_path_get(sb, ti->tsp, cunit, inst_str); + dput(cunit); + if (!dinst) + return -ENOMEM; + + ret =3D scmi_telemetry_de_subdir_symlink(sb, ti->tsp, de, dinst); + dput(dinst); + + return ret; +} + +DEFINE_TLM_CLASS(compo_dir_cls, "components", 0, S_IFDIR | S_IRWXU, NULL, = NULL); + +static int scmi_telemetry_topology_view_add(struct scmi_tlm_instance *ti) +{ + const struct scmi_telemetry_res_info *rinfo; + struct scmi_tlm_setup *tsp =3D ti->tsp; + struct device *dev =3D tsp->dev; + + rinfo =3D scmi_telemetry_res_info_get(tsp); + if (!rinfo || !rinfo->fully_enumerated) + return -ENODEV; + + ti->compo_dentry =3D + stlmfs_create_dentry(ti->sb, tsp, ti->top_dentry, &compo_dir_cls, NULL); + + for (int i =3D 0; i < rinfo->num_des; i++) { + int ret; + + ret =3D scmi_telemetry_topology_add_node(ti->sb, ti, rinfo->des[i]); + if (ret) + dev_err(dev, "Fail to add node %s to topology. Skip.\n", + rinfo->des[i]->info->name); + } + + return 0; +} + static int scmi_tlm_root_dentries_initialize(struct scmi_tlm_instance *ti) { struct scmi_tlm_setup *tsp =3D ti->tsp; @@ -1712,6 +2390,12 @@ static int scmi_telemetry_instance_register(struct s= uper_block *sb, ti->top_cls.name); } =20 + ret =3D scmi_telemetry_topology_view_add(ti); + if (ret) + dev_warn(ti->tsp->dev, + "Failed to create topology view for instance %s.\n", + ti->top_cls.name); + return 0; } =20 --=20 2.52.0 From nobody Mon Feb 9 01:43:04 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 6B104399A47; Wed, 14 Jan 2026 11:48:11 +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=1768391293; cv=none; b=ZZRFcAeBkLbCT5oOSpsaEMGZFTneiBkpM2T/NVkk7ARuEa4psi6yEuwUFnSfruzW5dZGTkvf0Zt3Y73Er+GzuO6I4MdWQuP+k8lifoA9KhNfDkZoeks6PnfaARr4Yc6UfY1dAse3b1OG+01DAd1UK+4kpLCnQN3Jp/S4TKVEpFQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768391293; c=relaxed/simple; bh=roNAJnkVEbqMeo26MUqjkgkOgd6VucBPugu8dfXkMJc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=NrDWOM1p0XsRFLe/0xDXHVeRevZmfMRTLVNOtCvyZY5gYwWcCL2tWB0rl7ljgY7NTUske+4D+CCPIREtglmg+4wfiQyn0WY2Ne9pLY3hp01+dMwS1kjjavxFBfVQnYlxXVDPuhc1V7Q8Mj6CwJEc7IQE1oJ4ZjiK5BOX5MUbZAg= 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 6F9A7497; Wed, 14 Jan 2026 03:48:04 -0800 (PST) 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 A69873F59E; Wed, 14 Jan 2026 03:48:07 -0800 (PST) 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 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, dan.carpenter@linaro.org, d-gole@ti.com, jonathan.cameron@huawei.com, elif.topuz@arm.com, lukasz.luba@arm.com, philip.radford@arm.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH v2 13/17] fs/stlmfs: Document alternative topological view Date: Wed, 14 Jan 2026 11:46:17 +0000 Message-ID: <20260114114638.2290765-14-cristian.marussi@arm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260114114638.2290765-1-cristian.marussi@arm.com> References: <20260114114638.2290765-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-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable The human readable interface presents an alternative view based on the discovered topological relations between the DEs. Signed-off-by: Cristian Marussi --- Documentation/filesystems/stlmfs.rst | 72 ++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/Documentation/filesystems/stlmfs.rst b/Documentation/filesyste= ms/stlmfs.rst index 5c23f7e5f12c..cc9585f77ba5 100644 --- a/Documentation/filesystems/stlmfs.rst +++ b/Documentation/filesystems/stlmfs.rst @@ -112,6 +112,7 @@ the following directory structure:: |-- all_des_enable |-- all_des_tstamp_enable |-- available_update_intervals_ms + |-- components/ |-- control |-- current_update_interval_ms |-- de_implementation_version @@ -202,6 +203,77 @@ values, as in:: |-- intervals_discrete `-- tstamp_enable =20 +components/ +----------- + +An alternative topological view of the des/ directory based on the topology +relationship information described in des/ :: + + components/ + =E2=94=9C=E2=94=80=E2=94=80 cpu + =E2=94=82=C2=A0=C2=A0 =E2=94=9C=E2=94=80=E2=94=80 0 + =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2=94=9C=E2=94=80=E2=94=80 c= elsius + =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2=94= =94=E2=94=80=E2=94=80 0 + =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2= =94=94=E2=94=80=E2=94=80 0x00000001 -> ../../../../../des/0x00000001 + =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 c= ycles + =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2=94=9C=E2=94=80=E2=94= =80 0 + =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2= =94=94=E2=94=80=E2=94=80 0x00001010 -> ../../../../../des/0x00001010 + =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94= =80 1 + =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2= =94=80 0x00002020 -> ../../../../../des/0x00002020 + =E2=94=82=C2=A0=C2=A0 =E2=94=9C=E2=94=80=E2=94=80 1 + =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 c= elsius + =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94= =80 0 + =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2= =94=80 0x00000002 -> ../../../../../des/0x00000002 + =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 2 + =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 celsius + =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 0 + =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 0x00000003 = -> ../../../../../des/0x00000003 + =E2=94=9C=E2=94=80=E2=94=80 interconnnect + =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 0 + =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 hertz + =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 0 + =E2=94=82=C2=A0=C2=A0 =E2=94=9C=E2=94=80=E2=94=80 0x0000A008 = -> ../../../../../des/0x0000A008 + =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 0x0000A00B = -> ../../../../../des/0x0000A00B + =E2=94=9C=E2=94=80=E2=94=80 mem_cntrl + =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 0 + =E2=94=82=C2=A0=C2=A0 =E2=94=9C=E2=94=80=E2=94=80 bps + =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94= =80 0 + =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2= =94=80 0x0000A00A -> ../../../../../des/0x0000A00A + =E2=94=82=C2=A0=C2=A0 =E2=94=9C=E2=94=80=E2=94=80 celsius + =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94= =80 0 + =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2= =94=80 0x0000A007 -> ../../../../../des/0x0000A007 + =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 joules + =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 0 + =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 0x0000A002 = -> ../../../../../des/0x0000A002 + =E2=94=9C=E2=94=80=E2=94=80 periph + =E2=94=82=C2=A0=C2=A0 =E2=94=9C=E2=94=80=E2=94=80 0 + =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 m= essages + =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94= =80 0 + =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2= =94=80 0x00000016 -> ../../../../../des/0x00000016 + =E2=94=82=C2=A0=C2=A0 =E2=94=9C=E2=94=80=E2=94=80 1 + =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 m= essages + =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94= =80 0 + =E2=94=82=C2=A0=C2=A0 =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2= =94=80 0x00000017 -> ../../../../../des/0x00000017 + =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 2 + =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 messages + =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 0 + =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 0x00000018 = -> ../../../../../des/0x00000018 + =E2=94=94=E2=94=80=E2=94=80 unspec + =E2=94=94=E2=94=80=E2=94=80 0 + =E2=94=9C=E2=94=80=E2=94=80 celsius + =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 0 + =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 0x0000A005 -> ../..= /../../../des/0x0000A005 + =E2=94=9C=E2=94=80=E2=94=80 counts + =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 0 + =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 0x0000A00C -> ../..= /../../../des/0x0000A00C + =E2=94=9C=E2=94=80=E2=94=80 joules + =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 0 + =E2=94=82=C2=A0=C2=A0 =E2=94=9C=E2=94=80=E2=94=80 0x0000A000 -> ../..= /../../../des/0x0000A000 + =E2=94=82=C2=A0=C2=A0 =E2=94=94=E2=94=80=E2=94=80 0x0000A001 -> ../..= /../../../des/0x0000A001 + =E2=94=94=E2=94=80=E2=94=80 state + =E2=94=94=E2=94=80=E2=94=80 0 + =E2=94=94=E2=94=80=E2=94=80 0x0000A010 -> ../../../../../des/0x0000A010 + Alternative Binary Interfaces - Special files =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =20 --=20 2.52.0 From nobody Mon Feb 9 01:43:04 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 9734239A7F3; Wed, 14 Jan 2026 11:48:15 +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=1768391297; cv=none; b=dSY88yxZFlpPPVUpNZlqqQY6RPFJ23qkl9nhrTJbkQKAl87ZvZStgyuBbwU+61N1aqwtTKDwKNJ4QNRkPBPcXp+UtvoaVll+8NRtHkelLxzSINhifjZf159Z9Z2InrSEcwcrSsalWnXTcaRNsb2Azh+l+rO48P/+Yw6i5smxim8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768391297; c=relaxed/simple; bh=x4SrwuVDC56sNg8/TeRG66soJYVXmHU/A7KUL6AIB2s=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=qWsrDmZeQE1MDBEfYOsrJGgTIfBm3MLmVNp8SLflWdfX+OJZ2m5VuRxBEcfUd85DJa1mzWzZ7Tpcy0jPrFnj4eARqlT0kvSAjoyE2buHJM9lcgHhRpmTjp6zSpQmbu+LKekUwhkPrjT5wR8dmhtOH0F0psYFWzligqdAOmJj+4c= 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 4D6E11424; Wed, 14 Jan 2026 03:48:08 -0800 (PST) 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 81F773F59E; Wed, 14 Jan 2026 03:48:11 -0800 (PST) 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 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, dan.carpenter@linaro.org, d-gole@ti.com, jonathan.cameron@huawei.com, elif.topuz@arm.com, lukasz.luba@arm.com, philip.radford@arm.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH v2 14/17] [RFC] docs: stlmfs: Document ARM SCMI Telemetry FS ABI Date: Wed, 14 Jan 2026 11:46:18 +0000 Message-ID: <20260114114638.2290765-15-cristian.marussi@arm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260114114638.2290765-1-cristian.marussi@arm.com> References: <20260114114638.2290765-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 full ABI dcoumentation for stlmfs under testing/ Signed-off-by: Cristian Marussi --- RFC since the documentation is still NOT complete and unsure if place this into stable/ or testing/ --- Documentation/ABI/testing/stlmfs | 153 +++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 Documentation/ABI/testing/stlmfs diff --git a/Documentation/ABI/testing/stlmfs b/Documentation/ABI/testing/s= tlmfs new file mode 100644 index 000000000000..efa001a7d82f --- /dev/null +++ b/Documentation/ABI/testing/stlmfs @@ -0,0 +1,153 @@ +What: /sys/fs/arm_telemetry/tlm_/all_des_enable +Date: January 2026 +KernelVersion: 7.0 +Contact: cristian.marussi@arm.com +Description: A boolean WO entry to enable all the discovered Data Events f= or + SCMI instance . +Users: Any userspace telemetry tool + +What: /sys/fs/arm_telemetry/tlm_/all_tstamp_des_enable +Date: January 2026 +KernelVersion: 7.0 +Contact: cristian.marussi@arm.com +Description: A boolean WO entry to enable timestamps for all the discovered + Data Events for SCMI instance . (when available) +Users: Any userspace telemetry tool + +What: /sys/fs/arm_telemetry/tlm_/available_update_intervals_ms +Date: January 2026 +KernelVersion: 7.0 +Contact: cristian.marussi@arm.com +Description: A RO entry that returns a space separated list of tuples of + values, separated by a coma, each one representing a + configurable update interval for SCMI instance . + Each tuple describes a possible update interval using the + format , where the final represented interval is + calculated as: * 10 ^ + An example of list of tuples that can be read from this entry: + 3,0 4,-1 75,-2 300,-3 1,1 5,3 222,-7 +Users: Any userspace telemetry tool + +What: /sys/fs/arm_telemetry/tlm_/current_update_intervals_ms +Date: January 2026 +KernelVersion: 7.0 +Contact: cristian.marussi@arm.com +Description: An RW entry that can be used to get or set the platform update + interval for SCMI instance . + On read the returned tuple represents the current update + interval using the format , where the final + represented interval is calculated as: * 10 ^ + On write the accepted format is the same as on read , + but, optionally, the second element of the tuple can be omitted + and in that case the assumed value for the exponent will default + to -3 signifying milliseconds. +Users: Any userspace telemetry tool + +What: /sys/fs/arm_telemetry/tlm_/control +Date: January 2026 +KernelVersion: 7.0 +Contact: cristian.marussi@arm.com +Description: An RW entry that can be used to discover, configure and retri= eve + Telemetry data using the alternative binary interface based on + ioctls which is documented in include/uapi/linux/scmi.h +Users: Any userspace telemetry tool + +What: /sys/fs/arm_telemetry/tlm_/de_implementation_version +Date: January 2026 +KernelVersion: 7.0 +Contact: cristian.marussi@arm.com +Description: A RO entry that returns a string representing the 128bit UUID + that uniquely identifies the set of SCMI Telemetry Data Events + and their semantic for SCMI instance . + This is compliant with the DE_IMPLEMENTATION_REVISION described + in SCMI v4.0 Telemetry 3.12.4.3. +Users: Any userspace telemetry tool + +What: /sys/fs/arm_telemetry/tlm_/des_bulk_read +Date: January 2026 +KernelVersion: 7.0 +Contact: cristian.marussi@arm.com +Description: A RO entry that returns a multi-line string containing all the + the DEs enabled for SCMI instance , one-per-line, formmatted + as: . + These DEs readings represent the last value updated by the + platform following the configured update interval: on the + backend they may have been collected in a number of different + ways: on-demand SHMTI lookup, notifications, fastchannels. +Users: Any userspace telemetry tool + +What: /sys/fs/arm_telemetry/tlm_/des_single_sample_read +Date: January 2026 +KernelVersion: 7.0 +Contact: cristian.marussi@arm.com +Description: A RO entry that returns a multi-line string containing all the + the DEs enabled for SCMI instance , one-per-line, formmatted + as: . + These DEs readings are generated by triggering an explicit and + immediate platform update using single sample asynchronous + collect methods. +Users: Any userspace telemetry tool + +What: /sys/fs/arm_telemetry/tlm_/intervals_discrete +Date: January 2026 +KernelVersion: 7.0 +Contact: cristian.marussi@arm.com +Description: A boolean RO entry to specify if the intervals reported for + SCMI instance in available_update_intervals_ms are a list of + discrete intervals or a triplet of values representing + . +Users: Any userspace telemetry tool + +What: /sys/fs/arm_telemetry/tlm_/reset +Date: January 2026 +KernelVersion: 7.0 +Contact: cristian.marussi@arm.com +Description: A boolean WO entry that can be used the full reset of the SCMI + Telemetry subsystem, both of the configurations and of the + collected data, as specified in SCMI v4.0 3.12.4.12 +Users: Any userspace telemetry tool + +What: /sys/fs/arm_telemetry/tlm_/tlm_enable +Date: January 2026 +KernelVersion: 7.0 +Contact: cristian.marussi@arm.com +Description: A boolean RW entry that can be used to get or set the general + enable status of the Telemetry subsystem. Temporarily disabling + Telemetry as a whole does NOT reset the current configuration, + it only stops all the DEs updates platform side. +Users: Any userspace telemetry tool + +What: /sys/fs/arm_telemetry/tlm_/version +Date: January 2026 +KernelVersion: 7.0 +Contact: cristian.marussi@arm.com +Description: A RO entry used to report the SCMI Telemetry protocol version + used in this implementation. +Users: Any userspace telemetry tool + +What: /sys/fs/arm_telemetry/tlm_/des/0x/value +Date: January 2026 +KernelVersion: 7.0 +Contact: cristian.marussi@arm.com +Description: A RO entry used to read the last value reported for Data Event + with id 0x for SCMI instance . +Users: Any userspace telemetry tool + +What: /sys/fs/arm_telemetry/tlm_/des/0x/enable +Date: January 2026 +KernelVersion: 7.0 +Contact: cristian.marussi@arm.com +Description: A RW boolean entry used to enable or disable Data Event + with id 0x for SCMI instance . +Users: Any userspace telemetry tool + +What: /sys/fs/arm_telemetry/tlm_/des/0x/tstamp_enable +Date: January 2026 +KernelVersion: 7.0 +Contact: cristian.marussi@arm.com +Description: A RW boolean entry used to enable or disable timestamping for + Data Event with id 0x for SCMI instance . +Users: Any userspace telemetry tool + + +... To BE CONTINUED ... --=20 2.52.0 From nobody Mon Feb 9 01:43:04 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 7CD6E399A4F; Wed, 14 Jan 2026 11:48:19 +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=1768391301; cv=none; b=ItW9Nfz0EhM4IfsVKFsAo0dM/ibmz4ikr5UabzaWy7j4WAuczSqoaPS9QAJff99qTleei/peFRHGv4AYo/mF7oMvfQwcLcWlNLsaNfasYDaoECVhIATjMMQEQ9SSROAU4ivtH4/yGtxPuFG32dtrrEdNhAM5NfAH0YuYFliy7gU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768391301; c=relaxed/simple; bh=+Dw4ZKEWRdwriEoXdvt82FAboGO8HQU3eefbIVG8Smo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=JkW7nbGwqnONFa4pspcUyYk63Xrw/j1l13Gj/xgzLRKoUflsm4WmdjM7W68CM+cHKTY7Z1evcEOgo7nlsSsg9mwO+SVdzy3q4oS0MmXfI6zrtobnbMJTRyoazA0aj/lz1B2wbHN7KDWI2DG3V62xImgknvCBFuD/Zgv0zRmaQ7k= 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 31C15339; Wed, 14 Jan 2026 03:48:12 -0800 (PST) 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 64F4F3F59E; Wed, 14 Jan 2026 03:48:15 -0800 (PST) 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 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, dan.carpenter@linaro.org, d-gole@ti.com, jonathan.cameron@huawei.com, elif.topuz@arm.com, lukasz.luba@arm.com, philip.radford@arm.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH v2 15/17] [RFC] firmware: arm_scmi: Add lazy population support to Telemetry FS Date: Wed, 14 Jan 2026 11:46:19 +0000 Message-ID: <20260114114638.2290765-16-cristian.marussi@arm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260114114638.2290765-1-cristian.marussi@arm.com> References: <20260114114638.2290765-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 a filesystem mount option to the SCMI Telemetry filesystem so as to delay resources enumeration and related fs subtrees population to the last possible moment when the related fs paths are accessed. Only basic global fs entries are populated at mount time when the lazy mount option is used. Signed-off-by: Cristian Marussi --- Posted as an RFC since not sure if the lazy population methods used in this patch are acceptable from the FS standpoint. --- .../firmware/arm_scmi/scmi_system_telemetry.c | 581 +++++++++++++++--- 1 file changed, 492 insertions(+), 89 deletions(-) diff --git a/drivers/firmware/arm_scmi/scmi_system_telemetry.c b/drivers/fi= rmware/arm_scmi/scmi_system_telemetry.c index 1221520356fd..543977b4b7a5 100644 --- a/drivers/firmware/arm_scmi/scmi_system_telemetry.c +++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c @@ -33,10 +33,36 @@ #define MAX_AVAILABLE_INTERV_CHAR_LENGTH 25 #define MAX_BULK_LINE_CHAR_LENGTH 64 =20 +enum { + Opt_lazy, +}; + +static const struct fs_parameter_spec stlmfs_param_spec[] =3D { + fsparam_flag("lazy", Opt_lazy), + {} +}; + +struct stlmfs_fs_context { + bool lazy; +}; + +struct stlmfs_lazy_tracker { + bool des; + bool grps; + bool topo; +}; + +struct stlmfs_sb_info { + bool lazy; + unsigned int num_inst; + struct stlmfs_lazy_tracker populated[] __counted_by(num_inst); +}; + static struct kmem_cache *stlmfs_inode_cachep; =20 static DEFINE_MUTEX(stlmfs_mtx); static struct super_block *stlmfs_sb; +static unsigned int stlmfs_instances; =20 static atomic_t scmi_tlm_instance_count =3D ATOMIC_INIT(0); =20 @@ -103,9 +129,11 @@ struct scmi_tlm_class { #define TLM_IS_STATE BIT(0) #define TLM_IS_GROUP BIT(1) #define TLM_IS_DYNAMIC BIT(2) +#define TLM_IS_LAZY BIT(3) #define IS_STATE(_f) ((_f) & TLM_IS_STATE) #define IS_GROUP(_f) ((_f) & TLM_IS_GROUP) #define IS_DYNAMIC(_f) ((_f) & TLM_IS_DYNAMIC) +#define IS_LAZY(_f) ((_f) & TLM_IS_LAZY) const struct file_operations *f_op; const struct inode_operations *i_op; }; @@ -135,6 +163,10 @@ struct scmi_tlm_class { * @info: SCMI instance information data reference. * @vfs_inode: The embedded VFS inode that will be initialized and plugged * into the live filesystem at mount time. + * @node: List item field. + * @children: A list containing all the children of this node. + * @num_children: Number of items stored in the @children list. + * @mtx: A mutex to protect the @children list. * * This structure is used to describe each SCMI Telemetry entity discovered * at probe time, store its related SCMI data, and link to the proper @@ -150,6 +182,11 @@ struct scmi_tlm_inode { const struct scmi_telemetry_info *info; }; struct inode vfs_inode; + struct list_head node; + struct list_head children; + unsigned int num_children; + /* Mutext to protect @children list */ + struct mutex mtx; }; =20 #define to_tlm_inode(t) container_of(t, struct scmi_tlm_inode, vfs_inode) @@ -164,8 +201,6 @@ struct scmi_tlm_inode { * struct scmi_tlm_instance - Telemetry instance descriptor * @id: Progressive number identifying this probed instance; it will be us= ed * to name the top node at the root of this instance. - * @res_enumerated: A flag to indicate if full resources enumeration has b= een - * successfully performed. * @name: Name to be used for the top root node of the instance. (tlm_) * @node: A node to link this in the list of all instances. * @sb: A reference to the current super_block. @@ -180,7 +215,6 @@ struct scmi_tlm_inode { */ struct scmi_tlm_instance { int id; - bool res_enumerated; char name[MAX_INST_NAME]; struct list_head node; struct super_block *sb; @@ -193,6 +227,8 @@ struct scmi_tlm_instance { const struct scmi_telemetry_info *info; }; =20 +static int scmi_telemetry_groups_initialize(const struct scmi_tlm_instance= *ti); +static int scmi_telemetry_topology_view_initialize(const struct scmi_tlm_i= nstance *ti); static int scmi_telemetry_instance_register(struct super_block *sb, struct scmi_tlm_instance *ti); =20 @@ -742,17 +778,23 @@ stlmfs_create_dentry(struct super_block *sb, struct s= cmi_tlm_setup *tsp, struct dentry *parent, const struct scmi_tlm_class *cls, const void *priv) { - struct scmi_tlm_inode *tlmi; + struct scmi_tlm_inode *tlmi, *tlmi_parent; + struct stlmfs_sb_info *sbi =3D sb->s_fs_info; struct dentry *dentry; - struct inode *inode; - - if (!parent) - parent =3D sb->s_root; + struct inode *inode, *i_parent; =20 if (IS_ERR(parent)) return parent; =20 - dentry =3D simple_start_creating(parent, cls->name); + i_parent =3D d_inode(parent); + if (!i_parent) + return ERR_PTR(-ENOENT); + + if (!sbi->lazy) + dentry =3D simple_start_creating(parent, cls->name); + else + dentry =3D d_alloc_name(parent, cls->name); + if (IS_ERR(dentry)) return dentry; =20 @@ -777,14 +819,24 @@ stlmfs_create_dentry(struct super_block *sb, struct s= cmi_tlm_setup *tsp, inode->i_private =3D (void *)priv; =20 tlmi =3D to_tlm_inode(inode); - tlmi->cls =3D cls; tlmi->tsp =3D tsp; tlmi->priv =3D priv; =20 + tlmi_parent =3D to_tlm_inode(i_parent); + if (sbi->lazy && tlmi_parent->cls && IS_LAZY(tlmi_parent->cls->flags)) { + scoped_guard(mutex, &tlmi_parent->mtx) { + list_add(&tlmi->node, &tlmi_parent->children); + tlmi_parent->num_children++; + } + } + d_make_persistent(dentry, inode); =20 - simple_done_creating(dentry); + if (!sbi->lazy) + simple_done_creating(dentry); + else + dput(dentry); =20 return dentry; } @@ -1310,8 +1362,6 @@ static const struct scmi_tlm_class tlm_tops[] =3D { =20 DEFINE_TLM_CLASS(reset_tlmo, "reset", 0, S_IFREG | S_IWUSR, &reset_fops, N= ULL); =20 -DEFINE_TLM_CLASS(des_dir_cls, "des", 0, - S_IFDIR | S_IRWXU, NULL, NULL); DEFINE_TLM_CLASS(name_tlmo, "name", 0, S_IFREG | S_IRUSR, &string_ro_fops, NULL); DEFINE_TLM_CLASS(ena_tlmo, "enable", TLM_IS_STATE, @@ -1383,48 +1433,72 @@ static int scmi_telemetry_de_populate(struct super_= block *sb, return 0; } =20 +static struct dentry * +scmi_telemetry_subdir_create(struct super_block *sb, struct scmi_tlm_setup= *tsp, + const char *dname, struct dentry *parent, + const void *priv) +{ + struct stlmfs_sb_info *sbi =3D sb->s_fs_info; + struct dentry *dentry; + + struct scmi_tlm_class *tlm_cls __free(kfree) =3D + kzalloc(sizeof(*tlm_cls), GFP_KERNEL); + if (!tlm_cls) + return ERR_PTR(-ENOMEM); + + tlm_cls->name =3D dname; + tlm_cls->mode =3D S_IFDIR | S_IRWXU; + tlm_cls->flags =3D TLM_IS_DYNAMIC; + if (sbi->lazy) + tlm_cls->flags |=3D TLM_IS_LAZY; + dentry =3D stlmfs_create_dentry(sb, tsp, parent, tlm_cls, priv); + if (IS_ERR(dentry)) + return dentry; + + retain_and_null_ptr(tlm_cls); + + return dentry; +} + static int -scmi_telemetry_des_lazy_enumerate(struct scmi_tlm_instance *ti, - const struct scmi_telemetry_res_info *rinfo) +scmi_telemetry_des_enumerate(const struct scmi_tlm_instance *ti, + const struct scmi_telemetry_res_info *rinfo) { struct scmi_tlm_setup *tsp =3D ti->tsp; struct super_block *sb =3D ti->sb; + struct stlmfs_sb_info *sbi =3D sb->s_fs_info; =20 for (int i =3D 0; i < rinfo->num_des; i++) { const struct scmi_telemetry_de *de =3D rinfo->des[i]; struct dentry *de_dir_dentry; int ret; =20 - struct scmi_tlm_class *de_tlm_cls __free(kfree) =3D - kzalloc(sizeof(*de_tlm_cls), GFP_KERNEL); - if (!de_tlm_cls) + const char *dname __free(kfree) =3D + kasprintf(GFP_KERNEL, "0x%08X", de->info->id); + if (!dname) return -ENOMEM; =20 - de_tlm_cls->name =3D kasprintf(GFP_KERNEL, "0x%08X", de->info->id); - if (!de_tlm_cls->name) - return -ENOMEM; - - de_tlm_cls->mode =3D S_IFDIR | S_IRWXU; - de_tlm_cls->flags =3D TLM_IS_DYNAMIC; - de_dir_dentry =3D stlmfs_create_dentry(sb, tsp, ti->des_dentry, - de_tlm_cls, de); + de_dir_dentry =3D scmi_telemetry_subdir_create(sb, tsp, dname, + ti->des_dentry, de); + if (IS_ERR(de_dir_dentry)) + return PTR_ERR(de_dir_dentry); =20 ret =3D scmi_telemetry_de_populate(sb, tsp, de_dir_dentry, de, rinfo->fully_enumerated); if (ret) return ret; =20 - retain_and_null_ptr(de_tlm_cls); + retain_and_null_ptr(dname); } =20 - ti->res_enumerated =3D true; + sbi->populated[ti->id].des =3D true; =20 dev_info(tsp->dev, "Found %d Telemetry DE resources.\n", rinfo->num_des); =20 return 0; } =20 -static int scmi_telemetry_des_initialize(struct scmi_tlm_instance *ti) +static int scmi_telemetry_des_initialize(const struct scmi_tlm_instance *t= i) { const struct scmi_telemetry_res_info *rinfo; =20 @@ -1432,9 +1506,196 @@ static int scmi_telemetry_des_initialize(struct scm= i_tlm_instance *ti) if (!rinfo) return -ENODEV; =20 - return scmi_telemetry_des_lazy_enumerate(ti, rinfo); + return scmi_telemetry_des_enumerate(ti, rinfo); +} + +static inline struct dentry * +scmi_telemetry_dentry_lookup(struct inode *dir, struct dentry *dentry, + unsigned int flags) +{ + struct dentry *d, *dentry_dir; + + const char *dname __free(kfree) =3D + kmemdup_nul(dentry->d_name.name, dentry->d_name.len, GFP_KERNEL); + if (!dname) + return ERR_PTR(-ENOMEM); + + dentry_dir =3D d_find_alias(dir); + if (!dentry_dir) + return simple_lookup(dir, dentry, flags); + + d =3D stlmfs_lookup_by_name(dentry_dir, dname); + dput(dentry_dir); + + return d; +} + +static struct dentry * +stlmfs_lazy_des_lookup(struct inode *dir, struct dentry *dentry, + unsigned int flags) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(dir); + struct scmi_tlm_instance *ti =3D (struct scmi_tlm_instance *)tlmi->priv; + struct super_block *sb =3D ti->sb; + struct stlmfs_sb_info *sbi =3D sb->s_fs_info; + int ret; + + if (sbi->populated[ti->id].des) + return simple_lookup(dir, dentry, flags); + + ret =3D scmi_telemetry_des_initialize(ti); + if (ret) + return ERR_PTR(ret); + + return scmi_telemetry_dentry_lookup(dir, dentry, flags); +} + +static const struct inode_operations lazy_des_dir_iops =3D { + .lookup =3D stlmfs_lazy_des_lookup, +}; + +static struct dentry * +stlmfs_lazy_grps_lookup(struct inode *dir, struct dentry *dentry, + unsigned int flags) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(dir); + struct scmi_tlm_instance *ti =3D (struct scmi_tlm_instance *)tlmi->priv; + struct super_block *sb =3D ti->sb; + struct stlmfs_sb_info *sbi =3D sb->s_fs_info; + int ret; + + if (sbi->populated[ti->id].grps) + return simple_lookup(dir, dentry, flags); + + ret =3D scmi_telemetry_groups_initialize(ti); + if (ret) + return ERR_PTR(ret); + + return scmi_telemetry_dentry_lookup(dir, dentry, flags); +} + +static const struct inode_operations lazy_grps_dir_iops =3D { + .lookup =3D stlmfs_lazy_grps_lookup, +}; + +static struct dentry * +stlmfs_lazy_compo_lookup(struct inode *dir, struct dentry *dentry, + unsigned int flags) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(dir); + struct scmi_tlm_instance *ti =3D (struct scmi_tlm_instance *)tlmi->priv; + struct super_block *sb =3D ti->sb; + struct stlmfs_sb_info *sbi =3D sb->s_fs_info; + int ret; + + if (sbi->populated[ti->id].topo) + return simple_lookup(dir, dentry, flags); + + ret =3D scmi_telemetry_topology_view_initialize(ti); + if (ret) + return ERR_PTR(ret); + + return scmi_telemetry_dentry_lookup(dir, dentry, flags); +} + +static const struct inode_operations lazy_compo_dir_iops =3D { + .lookup =3D stlmfs_lazy_compo_lookup, +}; + +static inline void +scmi_telemetry_children_dir_emit(struct dir_context *ctx, + struct scmi_tlm_inode *tlmi_parent) +{ + struct scmi_tlm_inode *tlmi; + + if (ctx->pos >=3D tlmi_parent->num_children) + return; + + guard(mutex)(&tlmi_parent->mtx); + list_for_each_entry(tlmi, &tlmi_parent->children, node) { + if (!dir_emit(ctx, tlmi->cls->name, strlen(tlmi->cls->name), + tlmi->vfs_inode.i_ino, + S_ISDIR(tlmi->cls->mode) ? DT_DIR : DT_REG)) + break; + ctx->pos++; + } +} + +static int +stlmfs_lazy_des_iterate_shared(struct file *filp, struct dir_context *ctx) +{ + struct scmi_tlm_inode *tlmi_des =3D to_tlm_inode(file_inode(filp)); + const struct scmi_tlm_instance *ti =3D tlmi_des->priv; + struct super_block *sb =3D ti->sb; + struct stlmfs_sb_info *sbi =3D sb->s_fs_info; + + if (!sbi->populated[ti->id].des) { + int ret; + + ret =3D scmi_telemetry_des_initialize(ti); + if (ret) + return ret; + } + + scmi_telemetry_children_dir_emit(ctx, tlmi_des); + + return 0; +} + +static const struct file_operations lazy_des_fops =3D { + .iterate_shared =3D stlmfs_lazy_des_iterate_shared, +}; + +static int +stlmfs_lazy_grps_iterate_shared(struct file *filp, struct dir_context *ctx) +{ + struct scmi_tlm_inode *tlmi_des =3D to_tlm_inode(file_inode(filp)); + const struct scmi_tlm_instance *ti =3D tlmi_des->priv; + struct super_block *sb =3D ti->sb; + struct stlmfs_sb_info *sbi =3D sb->s_fs_info; + + if (!sbi->populated[ti->id].grps) { + int ret; + + ret =3D scmi_telemetry_groups_initialize(ti); + if (ret) + return ret; + } + + scmi_telemetry_children_dir_emit(ctx, tlmi_des); + + return 0; } =20 +static const struct file_operations lazy_grps_fops =3D { + .iterate_shared =3D stlmfs_lazy_grps_iterate_shared, +}; + +static int +stlmfs_lazy_compo_iterate_shared(struct file *filp, struct dir_context *ct= x) +{ + struct scmi_tlm_inode *tlmi_des =3D to_tlm_inode(file_inode(filp)); + const struct scmi_tlm_instance *ti =3D tlmi_des->priv; + struct super_block *sb =3D ti->sb; + struct stlmfs_sb_info *sbi =3D sb->s_fs_info; + + if (!sbi->populated[ti->id].topo) { + int ret; + + ret =3D scmi_telemetry_topology_view_initialize(ti); + if (ret) + return ret; + } + + scmi_telemetry_children_dir_emit(ctx, tlmi_des); + + return 0; +} + +static const struct file_operations lazy_compo_fops =3D { + .iterate_shared =3D stlmfs_lazy_compo_iterate_shared, +}; + DEFINE_TLM_CLASS(version_tlmo, "version", 0, S_IFREG | S_IRUSR, &sa_x32_ro_fops, NULL); =20 @@ -1574,8 +1835,6 @@ static const struct scmi_tlm_class tlm_grps[] =3D { DEFINE_TLM_CLASS(grp_data_tlmo, "des_bulk_read", TLM_IS_GROUP, S_IFREG | S_IRUSR, &scmi_tlm_data_fops, NULL); =20 -DEFINE_TLM_CLASS(groups_dir_cls, "groups", 0, S_IFDIR | S_IRWXU, NULL, NUL= L); - DEFINE_TLM_CLASS(grp_single_sample_tlmo, "des_single_sample_read", TLM_IS_= GROUP, S_IFREG | S_IRUSR, &scmi_tlm_single_sample_fops, NULL); =20 @@ -1992,66 +2251,82 @@ DEFINE_TLM_CLASS(ctrl_tlmo, "control", 0, DEFINE_TLM_CLASS(grp_ctrl_tlmo, "control", TLM_IS_GROUP, S_IFREG | S_IRUSR | S_IWUSR, &scmi_tlm_ctrl_fops, NULL); =20 -static int scmi_telemetry_groups_initialize(struct scmi_tlm_instance *ti) +static int +scmi_telemetry_grp_populate(struct super_block *sb, struct scmi_tlm_setup = *tsp, + struct dentry *parent, + const struct scmi_telemetry_group *grp, + bool per_group_config_support) +{ + for (const struct scmi_tlm_class *gto =3D tlm_grps; gto->name; gto++) + stlmfs_create_dentry(sb, tsp, parent, gto, grp); + + stlmfs_create_dentry(sb, tsp, parent, &grp_composing_des_tlmo, + grp->des_str); + + stlmfs_create_dentry(sb, tsp, parent, &grp_ctrl_tlmo, grp); + stlmfs_create_dentry(sb, tsp, parent, &grp_data_tlmo, grp); + stlmfs_create_dentry(sb, tsp, parent, &grp_single_sample_tlmo, grp); + + if (per_group_config_support) { + stlmfs_create_dentry(sb, tsp, parent, + &grp_current_interval_tlmo, grp); + stlmfs_create_dentry(sb, tsp, parent, + &grp_available_interval_tlmo, grp); + stlmfs_create_dentry(sb, tsp, parent, + &grp_intervals_discrete_tlmo, grp); + } + + return 0; +} + +static int +scmi_telemetry_groups_enumerate(const struct scmi_tlm_instance *ti, + const struct scmi_telemetry_res_info *rinfo) { - const struct scmi_telemetry_res_info *rinfo; struct scmi_tlm_setup *tsp =3D ti->tsp; struct super_block *sb =3D ti->sb; - struct device *dev =3D tsp->dev; - struct dentry *grp_dir_dentry; - - if (ti->info->base.num_groups =3D=3D 0) - return 0; - - rinfo =3D scmi_telemetry_res_info_get(tsp); - if (!rinfo) - return -ENODEV; + struct stlmfs_sb_info *sbi =3D sb->s_fs_info; =20 for (int i =3D 0; i < rinfo->num_groups; i++) { - const struct scmi_telemetry_group *grp =3D &rinfo->grps[i]; - - struct scmi_tlm_class *grp_tlm_cls __free(kfree) =3D - kzalloc(sizeof(*grp_tlm_cls), GFP_KERNEL); - if (!grp_tlm_cls) - return -ENOMEM; + struct dentry *grp_dentry; + int ret; =20 - grp_tlm_cls->name =3D kasprintf(GFP_KERNEL, "%u", grp->info->id); - if (!grp_tlm_cls->name) + const char *dname __free(kfree) =3D + kasprintf(GFP_KERNEL, "%u", rinfo->grps[i].info->id); + if (!dname) return -ENOMEM; =20 - grp_tlm_cls->mode =3D S_IFDIR | S_IRWXU; - grp_tlm_cls->flags =3D TLM_IS_DYNAMIC; + grp_dentry =3D scmi_telemetry_subdir_create(sb, tsp, dname, + ti->grps_dentry, + &rinfo->grps[i]); + if (IS_ERR(grp_dentry)) + return PTR_ERR(grp_dentry); =20 - grp_dir_dentry =3D stlmfs_create_dentry(sb, tsp, ti->grps_dentry, - grp_tlm_cls, grp); + ret =3D scmi_telemetry_grp_populate(sb, tsp, grp_dentry, + &rinfo->grps[i], + ti->info->per_group_config_support); + if (ret) + return ret; =20 - for (const struct scmi_tlm_class *gto =3D tlm_grps; gto->name; gto++) - stlmfs_create_dentry(sb, tsp, grp_dir_dentry, gto, grp); + retain_and_null_ptr(dname); + } =20 - stlmfs_create_dentry(sb, tsp, grp_dir_dentry, - &grp_composing_des_tlmo, grp->des_str); + sbi->populated[ti->id].grps =3D true; =20 - stlmfs_create_dentry(sb, tsp, grp_dir_dentry, &grp_ctrl_tlmo, grp); - stlmfs_create_dentry(sb, tsp, grp_dir_dentry, &grp_data_tlmo, grp); - stlmfs_create_dentry(sb, tsp, grp_dir_dentry, - &grp_single_sample_tlmo, grp); + dev_info(tsp->dev, "Found %d Telemetry GROUPS resources.\n", rinfo->num_g= roups); =20 - if (ti->info->per_group_config_support) { - stlmfs_create_dentry(sb, tsp, grp_dir_dentry, - &grp_current_interval_tlmo, grp); - stlmfs_create_dentry(sb, tsp, grp_dir_dentry, - &grp_available_interval_tlmo, grp); - stlmfs_create_dentry(sb, tsp, grp_dir_dentry, - &grp_intervals_discrete_tlmo, grp); - } + return 0; +} =20 - retain_and_null_ptr(grp_tlm_cls); - } +static int scmi_telemetry_groups_initialize(const struct scmi_tlm_instance= *ti) +{ + const struct scmi_telemetry_res_info *rinfo; =20 - dev_info(dev, "Found %d Telemetry GROUPS resources.\n", - rinfo->num_groups); + rinfo =3D scmi_telemetry_res_info_get(ti->tsp); + if (!rinfo || !rinfo->fully_enumerated) + return -ENODEV; =20 - return 0; + return scmi_telemetry_groups_enumerate(ti, rinfo); } =20 static struct scmi_tlm_instance *scmi_tlm_init(struct scmi_tlm_setup *tsp, @@ -2108,6 +2383,7 @@ static int scmi_telemetry_probe(struct scmi_device *s= dev) =20 mutex_lock(&stlmfs_mtx); list_add(&ti->node, &scmi_telemetry_instances); + stlmfs_instances++; sb =3D stlmfs_sb; mutex_unlock(&stlmfs_mtx); =20 @@ -2151,6 +2427,9 @@ static struct inode *stlmfs_alloc_inode(struct super_= block *sb) return NULL; =20 tlmi->cls =3D NULL; + mutex_init(&tlmi->mtx); + INIT_LIST_HEAD(&tlmi->children); + tlmi->num_children =3D 0; =20 return &tlmi->vfs_inode; } @@ -2243,6 +2522,7 @@ scmi_telemetry_topology_path_get(struct super_block *= sb, struct scmi_tlm_setup *tsp, struct dentry *parent, const char *dname) { + struct stlmfs_sb_info *sbi =3D sb->s_fs_info; struct dentry *dentry; =20 dentry =3D stlmfs_lookup_by_name(parent, dname); @@ -2258,6 +2538,8 @@ scmi_telemetry_topology_path_get(struct super_block *= sb, =20 dir_tlm_cls->mode =3D S_IFDIR | S_IRWXU; dir_tlm_cls->flags =3D TLM_IS_DYNAMIC; + if (sbi->lazy) + dir_tlm_cls->flags |=3D TLM_IS_LAZY; =20 dentry =3D stlmfs_create_dentry(sb, tsp, parent, dir_tlm_cls, NULL); @@ -2269,7 +2551,7 @@ scmi_telemetry_topology_path_get(struct super_block *= sb, } =20 static int scmi_telemetry_topology_add_node(struct super_block *sb, - struct scmi_tlm_instance *ti, + const struct scmi_tlm_instance *ti, const struct scmi_telemetry_de *de) { struct dentry *ctype, *cinst, *cunit, *dinst; @@ -2310,21 +2592,19 @@ static int scmi_telemetry_topology_add_node(struct = super_block *sb, return ret; } =20 -DEFINE_TLM_CLASS(compo_dir_cls, "components", 0, S_IFDIR | S_IRWXU, NULL, = NULL); - -static int scmi_telemetry_topology_view_add(struct scmi_tlm_instance *ti) +static int +scmi_telemetry_topology_view_initialize(const struct scmi_tlm_instance *ti) { const struct scmi_telemetry_res_info *rinfo; struct scmi_tlm_setup *tsp =3D ti->tsp; + struct super_block *sb =3D ti->sb; + struct stlmfs_sb_info *sbi =3D sb->s_fs_info; struct device *dev =3D tsp->dev; =20 rinfo =3D scmi_telemetry_res_info_get(tsp); if (!rinfo || !rinfo->fully_enumerated) return -ENODEV; =20 - ti->compo_dentry =3D - stlmfs_create_dentry(ti->sb, tsp, ti->top_dentry, &compo_dir_cls, NULL); - for (int i =3D 0; i < rinfo->num_des; i++) { int ret; =20 @@ -2334,13 +2614,51 @@ static int scmi_telemetry_topology_view_add(struct = scmi_tlm_instance *ti) rinfo->des[i]->info->name); } =20 + sbi->populated[ti->id].topo =3D true; + + if (sbi->lazy && !sbi->populated[ti->id].des) { + int ret; + + ret =3D scmi_telemetry_des_initialize(ti); + if (ret) + return ret; + } + return 0; } =20 +static struct dentry * +scmi_telemetry_top_dentry_create(struct scmi_tlm_instance *ti, bool lazy, + const char *dname, struct dentry *parent, + const struct file_operations *lazy_fops, + const struct inode_operations *lazy_dir_iops, + void *priv) +{ + struct scmi_tlm_setup *tsp =3D ti->tsp; + struct super_block *sb =3D ti->sb; + + struct scmi_tlm_class *tlm_cls __free(kfree) =3D + kzalloc(sizeof(*tlm_cls), GFP_KERNEL); + if (!tlm_cls) + return ERR_PTR(-ENOMEM); + + tlm_cls->name =3D kasprintf(GFP_KERNEL, "%s", dname); + tlm_cls->mode =3D S_IFDIR | S_IRWXU; + tlm_cls->flags =3D TLM_IS_DYNAMIC; + if (lazy) { + tlm_cls->flags |=3D TLM_IS_LAZY; + tlm_cls->f_op =3D lazy_fops; + tlm_cls->i_op =3D lazy_dir_iops; + } + + return stlmfs_create_dentry(sb, tsp, parent, no_free_ptr(tlm_cls), priv); +} + static int scmi_tlm_root_dentries_initialize(struct scmi_tlm_instance *ti) { struct scmi_tlm_setup *tsp =3D ti->tsp; struct super_block *sb =3D ti->sb; + struct stlmfs_sb_info *sbi =3D sb->s_fs_info; =20 scnprintf(ti->name, MAX_INST_NAME, "tlm_%d", ti->id); =20 @@ -2361,10 +2679,25 @@ static int scmi_tlm_root_dentries_initialize(struct= scmi_tlm_instance *ti) stlmfs_create_dentry(sb, tsp, ti->top_dentry, &data_tlmo, ti->info); stlmfs_create_dentry(sb, tsp, ti->top_dentry, &single_sample_tlmo, ti->in= fo); stlmfs_create_dentry(sb, tsp, ti->top_dentry, &ctrl_tlmo, ti->info); - ti->des_dentry =3D - stlmfs_create_dentry(sb, tsp, ti->top_dentry, &des_dir_cls, NULL); - ti->grps_dentry =3D - stlmfs_create_dentry(sb, tsp, ti->top_dentry, &groups_dir_cls, NULL); + + ti->des_dentry =3D scmi_telemetry_top_dentry_create(ti, sbi->lazy, "des", + ti->top_dentry, + &lazy_des_fops, + &lazy_des_dir_iops, + ti); + + ti->grps_dentry =3D scmi_telemetry_top_dentry_create(ti, sbi->lazy, "grou= ps", + ti->top_dentry, + &lazy_grps_fops, + &lazy_grps_dir_iops, + ti); + + ti->compo_dentry =3D scmi_telemetry_top_dentry_create(ti, sbi->lazy, + "components", + ti->top_dentry, + &lazy_compo_fops, + &lazy_compo_dir_iops, + ti); =20 return 0; } @@ -2372,6 +2705,7 @@ static int scmi_tlm_root_dentries_initialize(struct s= cmi_tlm_instance *ti) static int scmi_telemetry_instance_register(struct super_block *sb, struct scmi_tlm_instance *ti) { + struct stlmfs_sb_info *sbi =3D sb->s_fs_info; int ret; =20 ti->sb =3D sb; @@ -2379,6 +2713,9 @@ static int scmi_telemetry_instance_register(struct su= per_block *sb, if (ret) return ret; =20 + if (sbi->lazy) + return 0; + ret =3D scmi_telemetry_des_initialize(ti); if (ret) return ret; @@ -2390,21 +2727,37 @@ static int scmi_telemetry_instance_register(struct = super_block *sb, ti->top_cls.name); } =20 - ret =3D scmi_telemetry_topology_view_add(ti); - if (ret) + ret =3D scmi_telemetry_topology_view_initialize(ti); + if (ret) { dev_warn(ti->tsp->dev, "Failed to create topology view for instance %s.\n", ti->top_cls.name); + } =20 return 0; } =20 static int stlmfs_fill_super(struct super_block *sb, struct fs_context *fc) { + struct stlmfs_fs_context *ctx; struct scmi_tlm_instance *ti; struct dentry *root_dentry; int ret; =20 + /* Bail out if already initialized */ + if (sb->s_fs_info) + return 0; + + struct stlmfs_sb_info *sbi __free(kfree) =3D + kzalloc(struct_size(sbi, populated, stlmfs_instances), GFP_KERNEL); + if (!sbi) + return -ENOMEM; + + ctx =3D fc->fs_private; + sbi->num_inst =3D stlmfs_instances; + sbi->lazy =3D ctx->lazy; + + sb->s_fs_info =3D sbi; sb->s_magic =3D TLM_FS_MAGIC; sb->s_blocksize =3D PAGE_SIZE; sb->s_blocksize_bits =3D PAGE_SHIFT; @@ -2414,6 +2767,7 @@ static int stlmfs_fill_super(struct super_block *sb, = struct fs_context *fc) if (IS_ERR(root_dentry)) return PTR_ERR(root_dentry); =20 + retain_and_null_ptr(sbi); sb->s_root =3D root_dentry; =20 mutex_lock(&stlmfs_mtx); @@ -2431,17 +2785,61 @@ static int stlmfs_fill_super(struct super_block *sb= , struct fs_context *fc) return 0; } =20 +static void stlmfs_free(struct fs_context *fc) +{ + struct stlmfs_fs_context *ctx; + + ctx =3D fc->fs_private; + + kfree(ctx); +} + static int stlmfs_get_tree(struct fs_context *fc) { return get_tree_single(fc, stlmfs_fill_super); } =20 +static int stlmfs_parse_param(struct fs_context *fc, struct fs_parameter *= param) +{ + struct stlmfs_fs_context *ctx; + struct fs_parse_result result; + int opt; + + opt =3D fs_parse(fc, stlmfs_param_spec, param, &result); + if (opt < 0) + return opt; + + ctx =3D fc->fs_private; + + switch (opt) { + case Opt_lazy: + ctx->lazy =3D true; + break; + default: + return -ENOPARAM; + } + + return 0; +} + static const struct fs_context_operations stlmfs_fc_ops =3D { .get_tree =3D stlmfs_get_tree, + .parse_param =3D stlmfs_parse_param, + .free =3D stlmfs_free, }; =20 static int stlmfs_init_fs_context(struct fs_context *fc) { + struct stlmfs_fs_context *ctx; + + ctx =3D kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + /* defaults */ + ctx->lazy =3D false; + + fc->fs_private =3D ctx; fc->ops =3D &stlmfs_fc_ops; =20 return 0; @@ -2449,7 +2847,11 @@ static int stlmfs_init_fs_context(struct fs_context = *fc) =20 static void stlmfs_kill_sb(struct super_block *sb) { + struct stlmfs_sb_info *sbi =3D sb->s_fs_info; + kill_anon_super(sb); + + kfree(sbi); } =20 static struct file_system_type scmi_telemetry_fs =3D { @@ -2457,6 +2859,7 @@ static struct file_system_type scmi_telemetry_fs =3D { .name =3D TLM_FS_NAME, .kill_sb =3D stlmfs_kill_sb, .init_fs_context =3D stlmfs_init_fs_context, + .parameters =3D stlmfs_param_spec, .fs_flags =3D 0, }; =20 --=20 2.52.0 From nobody Mon Feb 9 01:43:04 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 33E1439B4BF; Wed, 14 Jan 2026 11:48:23 +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=1768391305; cv=none; b=GTQgdbbrB9V/q9HrDayYtHoxgczm06/5lOstcIkVxmI4A3lLaNzd32MJzkuO0CmE1SyIMa0uvdp8/tYGhtiUbjB5bS1EXGz8fjXs+6Jxjx7qcgoAmcFE5rOcSav1FGAnJRyyNcJ67Q7yTznrjkZ0E/fa9DmJwRBcx/3zGeggieI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768391305; c=relaxed/simple; bh=v0gSagoTXdNujHLEdHah6G5k++0JX3XNCNkmRI+Jlxo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=UfDsw6bw6/2sqnx9y97LzfiI4ZXs4jLFkhj5N7BBE6p3FqOQOG7dae547cjEv1L4uheUZRK/p2i3JbQoQtyv4pSA2pfjsjCkQKmrkru13EQflBQ9gPAOTIMN1cOMoxpGexpq7g2m0Lpjb25GMAY6GnKdasLEoqPamF4k0FzmMUc= 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 0E603497; Wed, 14 Jan 2026 03:48:16 -0800 (PST) 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 440073F59E; Wed, 14 Jan 2026 03:48:19 -0800 (PST) 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 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, dan.carpenter@linaro.org, d-gole@ti.com, jonathan.cameron@huawei.com, elif.topuz@arm.com, lukasz.luba@arm.com, philip.radford@arm.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH v2 16/17] fs/stlmfs: Document lazy mode and related mount option Date: Wed, 14 Jan 2026 11:46:20 +0000 Message-ID: <20260114114638.2290765-17-cristian.marussi@arm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260114114638.2290765-1-cristian.marussi@arm.com> References: <20260114114638.2290765-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" Document optional lazy enumeration behaviour and related mount option. Signed-off-by: Cristian Marussi --- Documentation/filesystems/stlmfs.rst | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Documentation/filesystems/stlmfs.rst b/Documentation/filesyste= ms/stlmfs.rst index cc9585f77ba5..79633cdc71b8 100644 --- a/Documentation/filesystems/stlmfs.rst +++ b/Documentation/filesystems/stlmfs.rst @@ -73,8 +73,12 @@ Design STLMFS is a pseudo filesystem used to expose ARM SCMI Telemetry data discovered dynamically at run-time via SCMI. =20 -Inodes are all dynamically created at mount-time from a dedicated -kmem_cache based on the gathered available SCMI Telemetry information. +Normally all of the top level file/inodes are dynamically created at +mount-time from a dedicated kmem_cache based on the gathered available +SCMI Telemetry information, but it is possible to enable a lazy enumeration +and FS population mode that delays SCMI Telemetry resources enumerations +and related FS population till the moment a user steps into the related FS +subdirectories: *des/* *groups/* and *components/*. =20 Since inodes represent the discovered Telemetry entities, which in turn are statically defined at the platform level and immutable throughout the same @@ -104,6 +108,19 @@ The filesystem can be typically mounted with:: =20 mount -t stlmfs none /sys/fs/arm_telemetry =20 +It is possible to mount it in lazy-mode by using the *lazy* mount option:: + + mount -t stlmfs -o lazy none /sys/fs/arm_telemetry + +In this latter case, the des/ groups/ and components/ directory will be +created empty at mount-time and only filled later when 'walked in'. + +This allows a user to benefit from a lazy enumeration scheme of the SCMI +Telemetry resources by delaying such, usually expensive, message exchanges +to the last possible moment: ideally, even never, if using some of the +other alternative binary interfaces that does not need any resource +enumeration at all. + It will proceed to create a top subdirectory for each of the discovered SCMI Telemetry instances named as 'tlm_' under which it will create the following directory structure:: --=20 2.52.0 From nobody Mon Feb 9 01:43:04 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 4D53839C649; Wed, 14 Jan 2026 11:48:27 +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=1768391314; cv=none; b=MW7aNBDnYYw3MDZGJy1f+CX8h/j9HcQCJZppsuPyz8DO+Dzp0duogAqJ5krw5UawbXhJO2hqeXreFRDZNPk5D497nQLD9yCYEFoACAzyxdCFhuyV1XKEFQgNEe2dIGP3NSisymzrf+QTn6iO/cXwYH4ZsmZO3jSH/XiElHFszoo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768391314; c=relaxed/simple; bh=wAAYAlIO0UmFZi/Es8txwA5DsgG9TK3GwB1vMaYxHnw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=umz0APclOPZp0luJmLbXw/Z6zEH4tCLO+JPackF+N9PLwwKoSI+42sEdVuZOlTL/D/1UZQifavQ2hj8E3qz0yIGI2iBxNvaCSS8eSxJF08+Rw4MO9ISPezHFKvELMxxX0hLi/GnGNa9LVukZnoZemCaAdFclkmhyA2+KDe2Jepw= 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 D927E1424; Wed, 14 Jan 2026 03:48:19 -0800 (PST) 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 2306F3F59E; Wed, 14 Jan 2026 03:48:22 -0800 (PST) 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 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, dan.carpenter@linaro.org, d-gole@ti.com, jonathan.cameron@huawei.com, elif.topuz@arm.com, lukasz.luba@arm.com, philip.radford@arm.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH v2 17/17] [RFC] tools/scmi: Add SCMI Telemetry testing tool Date: Wed, 14 Jan 2026 11:46:21 +0000 Message-ID: <20260114114638.2290765-18-cristian.marussi@arm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260114114638.2290765-1-cristian.marussi@arm.com> References: <20260114114638.2290765-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 a testing tool that exercises the SCMI ioctls UAPI interface: as of now the tool simply queries the initial state of the SCMI Telemetry subsystem, tries to enable all the existent Data Events and dumps all the Telemetry data. Signed-off-by: Cristian Marussi --- Basic implementation just to exercise a few IOCTls: to be refined and extended to support a more interactive usage. --- tools/testing/scmi/Makefile | 25 +++ tools/testing/scmi/stlm.c | 385 ++++++++++++++++++++++++++++++++++++ 2 files changed, 410 insertions(+) create mode 100644 tools/testing/scmi/Makefile create mode 100644 tools/testing/scmi/stlm.c diff --git a/tools/testing/scmi/Makefile b/tools/testing/scmi/Makefile new file mode 100644 index 000000000000..a6a101f8398b --- /dev/null +++ b/tools/testing/scmi/Makefile @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +CC?=3D$(CROSS_COMPILE)gcc +OBJS =3D stlm.o + +CFLAGS=3D-Wall -static -std=3Dgnu11 -I ../../../include/uapi/ +ifneq ($(DEBUG), ) + CFLAGS+=3D-O0 -g -ggdb +else + CFLAGS+=3D-static +endif + +all: stlm + +stlm: $(OBJS) + $(CC) $(CFLAGS) $^ -o $@ + +%.o: %.c + $(CC) $(CFLAGS) -c $< + +clean: + rm -f *.o + rm -f stlm + +.PHONY: clean diff --git a/tools/testing/scmi/stlm.c b/tools/testing/scmi/stlm.c new file mode 100644 index 000000000000..137ef278d1dd --- /dev/null +++ b/tools/testing/scmi/stlm.c @@ -0,0 +1,385 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#define SLEEP_MS 3000 +#define DEF_TLM_ROOT "/sys/fs/arm_telemetry/" + +#define IOCTL_ERR_STR(_ioctl) "IOCTL:" #_ioctl + +struct tlm_de { + struct scmi_tlm_de_info *info; + struct scmi_tlm_de_config cfg; + struct scmi_tlm_de_sample sample; +}; + +struct tlm_group { + int fd; + struct scmi_tlm_grp_info *info; + struct scmi_tlm_grp_desc *desc; + struct scmi_tlm_intervals *ivs; +}; + +struct tlm_state { + int dfd; + int fd; + int g_dfd; + const char *path; + struct scmi_tlm_base_info info; + struct scmi_tlm_config cfg; + struct scmi_tlm_intervals *ivs; + unsigned int num_des; + struct tlm_de *des; + unsigned int num_groups; + struct tlm_group *grps; +}; + +static inline void dump_state(struct tlm_state *st) +{ + uint32_t *uuid32 =3D st->info.de_impl_version; + uint16_t *uuid16 =3D (uint16_t *)&st->info.de_impl_version[1]; + + fprintf(stdout, "- SYSTEM TELEMETRY @instance: %s\n\n", st->path); + fprintf(stdout, "+ Version: 0x%08X\n", st->info.version); + fprintf(stdout, "+ DEs#: %d\n", st->info.num_des); + fprintf(stdout, "+ GRPS#: %d\n", st->info.num_groups); + fprintf(stdout, "+ INTRV#: %d\n", st->info.num_intervals); + + fprintf(stdout, "+ UUID: "); + fprintf(stdout, "%X-", uuid32[0]); + fprintf(stdout, "%X-", uuid16[0]); + fprintf(stdout, "%X-", uuid16[1]); + fprintf(stdout, "%X", uuid16[2]); + fprintf(stdout, "%X\n", uuid32[3]); + + fprintf(stdout, "\n+ TLM_ENABLED: %d\n", st->cfg.enable); + fprintf(stdout, "+ CURRENT_UPDATE_INTERVAL: %d\n", + st->cfg.current_update_interval); + + fprintf(stdout, "+ Found #%u Global Update Intervals\n", + st->info.num_intervals); + for (int i =3D 0; i < st->ivs->num; i++) + fprintf(stdout, "\t[%d]::%u\n", i, st->ivs->update_intervals[i]); + + if (st->info.num_des !=3D st->num_des) { + fprintf(stdout, "\n++++++ DES NOT FULLY_ENUMERATED ++++++\n"); + fprintf(stdout, "+++ DECLARED:%u ENUMERATED:%u +++\n", + st->info.num_des, st->num_des); + } + + fprintf(stdout, "\n+ Found #%d DEs:\n", st->num_des); + for (int i =3D 0; i < st->num_des; i++) + fprintf(stdout, "\t0x%08X %s %s -- TS:%16llu %016llX\n", + st->des[i].info->id, + st->des[i].cfg.enable ? "ON" : "--", + st->des[i].cfg.t_enable ? "TS_ON" : "-----", + st->des[i].sample.tstamp, st->des[i].sample.val); + fprintf(stdout, "\n"); + + fprintf(stdout, "+ Found %d GRPs: ", st->num_groups); + for (int i =3D 0; i < st->num_groups; i++) { + fprintf(stdout, "\n\tGRP_ID:%d DES#:%d INTRVS#:%d\n", + st->grps[i].info->id, st->grps[i].info->num_des, + st->grps[i].info->num_intervals); + + fprintf(stdout, "\tCOMPOSING_DES:"); + for (int j =3D 0; j < st->grps[i].desc->num_des; j++) + fprintf(stdout, "0x%08X ", + st->grps[i].desc->composing_des[j]); + fprintf(stdout, "\n"); + } +} + +static int discover_base_info(int fd, struct scmi_tlm_base_info *info) +{ + int ret; + + ret =3D ioctl(fd, SCMI_TLM_GET_INFO, info); + if (ret) { + perror(IOCTL_ERR_STR(SCMI_TLM_GET_INFO)); + return ret; + } + + return ret; +} + +static struct scmi_tlm_des_list *scmi_get_des_list(int fd, int num_des) +{ + struct scmi_tlm_des_list *dsl; + size_t size =3D sizeof(*dsl) + num_des * sizeof(dsl->des[0]); + int ret; + + dsl =3D malloc(size); + if (!dsl) + return NULL; + + bzero(dsl, size); + dsl->num_des =3D num_des; + ret =3D ioctl(fd, SCMI_TLM_GET_DE_LIST, dsl); + if (ret) { + perror(IOCTL_ERR_STR(SCMI_TLM_GET_DE_LIST)); + return NULL; + } + + return dsl; +} + +static struct tlm_de *enumerate_des(struct tlm_state *st) +{ + struct scmi_tlm_des_list *dsl; + struct tlm_de *des; + + dsl =3D scmi_get_des_list(st->fd, st->info.num_des); + if (!dsl) + return NULL; + + st->num_des =3D dsl->num_des; + des =3D malloc(sizeof(*des) * st->num_des); + if (!des) + return NULL; + + bzero(des, sizeof(*des) * st->num_des); + for (int i =3D 0; i < st->num_des; i++) { + struct tlm_de *de =3D &des[i]; + int ret; + + de->info =3D &dsl->des[i]; + de->cfg.id =3D de->info->id; + ret =3D ioctl(st->fd, SCMI_TLM_GET_DE_CFG, &de->cfg); + if (ret) { + perror(IOCTL_ERR_STR(SCMI_TLM_GET_DE_CFG)); + continue; + } + + if (!de->cfg.enable) + continue; + + /* Collect initial sample */ + de->sample.id =3D de->info->id; + ret =3D ioctl(st->fd, SCMI_TLM_GET_DE_VALUE, &de->sample); + if (ret) { + perror(IOCTL_ERR_STR(SCMI_TLM_GET_DE_VALUE)); + continue; + } + } + + return des; +} + +static int get_current_config(int fd, struct scmi_tlm_config *cfg) +{ + int ret; + + ret =3D ioctl(fd, SCMI_TLM_GET_CFG, cfg); + if (ret) { + perror(IOCTL_ERR_STR(SCMI_TLM_GET_CFG)); + return ret; + } + + return ret; +} + +static struct scmi_tlm_grps_list *scmi_get_grps_list(int fd, int num_group= s) +{ + struct scmi_tlm_grps_list *gsl; + size_t size =3D sizeof(*gsl) + num_groups * sizeof(gsl->grps[0]); + int ret; + + gsl =3D malloc(size); + if (!gsl) + return NULL; + + bzero(gsl, size); + gsl->num_grps =3D num_groups; + ret =3D ioctl(fd, SCMI_TLM_GET_GRP_LIST, gsl); + if (ret) { + perror(IOCTL_ERR_STR(SCMI_TLM_GET_GRP_LIST)); + return NULL; + } + + return gsl; +} + +static struct scmi_tlm_intervals *enumerate_intervals(int fd, int num_inte= rvals) +{ + struct scmi_tlm_intervals *ivs; + size_t sz; + int ret; + + sz =3D sizeof(*ivs) + sizeof(*ivs->update_intervals) * num_intervals; + ivs =3D malloc(sz); + if (!ivs) + return NULL; + + memset(ivs, 0, sz); + + ivs->num =3D num_intervals; + ret =3D ioctl(fd, SCMI_TLM_GET_INTRVS, ivs); + if (ret) { + perror(IOCTL_ERR_STR(SCMI_TLM_GET_INTRVS)); + free(ivs); + return NULL; + } + + return ivs; +} + +static struct tlm_group *enumerate_groups(struct tlm_state *st) +{ + struct scmi_tlm_grps_list *gsl; + struct tlm_group *grps; + + gsl =3D scmi_get_grps_list(st->fd, st->info.num_groups); + if (!gsl) + return NULL; + + st->g_dfd =3D openat(st->dfd, "groups", O_RDONLY); + if (st->g_dfd < 0) + return NULL; + + st->num_groups =3D gsl->num_grps; + grps =3D malloc(sizeof(*grps) * st->num_groups); + if (!grps) + return NULL; + + bzero(grps, sizeof(*grps) * st->num_groups); + for (int i =3D 0; i < st->num_groups; i++) { + struct tlm_group *grp =3D &grps[i]; + char gctrl[32]; + size_t size; + int ret; + + snprintf(gctrl, 32, "%d/control", i); + grp->fd =3D openat(st->g_dfd, gctrl, O_RDWR); + if (grp->fd < 0) + return NULL; + + grp->info =3D &gsl->grps[i]; + size =3D sizeof(*grp->desc) + sizeof(uint32_t) * grp->info->num_des; + grp->desc =3D malloc(size); + if (!grp->desc) + return NULL; + + bzero(grp->desc, size); + grp->desc->num_des =3D grp->info->num_des; + ret =3D ioctl(grp->fd, SCMI_TLM_GET_GRP_DESC, grp->desc); + if (ret) { + perror(IOCTL_ERR_STR(SCMI_TLM_GET_GRP_DESC)); + continue; + } + + grp->ivs =3D enumerate_intervals(grp->fd, grp->info->num_intervals); + } + + return grps; +} + +static int get_tlm_state(const char *path, struct tlm_state *st) +{ + int ret; + + st->dfd =3D open(path, O_RDONLY); + if (st->dfd < 0) { + perror("open"); + return st->dfd; + } + + st->fd =3D openat(st->dfd, "control", O_RDWR); + if (st->fd < 0) { + perror("openat"); + return st->fd; + } + + ret =3D discover_base_info(st->fd, &st->info); + if (ret) + return ret; + + st->ivs =3D enumerate_intervals(st->fd, st->info.num_intervals); + if (!st->ivs) + return -1; + + ret =3D get_current_config(st->fd, &st->cfg); + if (ret) + return ret; + + if (st->info.num_des) + st->des =3D enumerate_des(st); + + if (st->info.num_groups) + st->grps =3D enumerate_groups(st); + + st->path =3D path; + + return 0; +} + +int main(int argc, char **argv) +{ + const char *tlm_root_instance =3D DEF_TLM_ROOT "tlm_0/"; + struct scmi_tlm_data_read *bulk; + struct scmi_tlm_de_config de_cfg =3D {}; + struct tlm_state st =3D {}; + size_t bulk_sz; + int ret; + + ret =3D get_tlm_state(tlm_root_instance, &st); + if (ret) + return ret; + + dump_state(&st); + + bulk_sz =3D sizeof(*bulk) + sizeof(bulk->samples[0]) * st.info.num_des; + bulk =3D malloc(bulk_sz); + if (!bulk) + return -1; + + bzero(bulk, bulk_sz); + bulk->num_samples =3D st.info.num_des; + ret =3D ioctl(st.fd, SCMI_TLM_SINGLE_SAMPLE, bulk); + if (ret) { + perror(IOCTL_ERR_STR(SCMI_TLM_SINGLE_SAMPLE)); + return -1; + } + + fprintf(stdout, "\n--- Enabling ALL DEs with timestamp...\n"); + de_cfg.enable =3D 1; + de_cfg.t_enable =3D 1; + ret =3D ioctl(st.fd, SCMI_TLM_SET_ALL_CFG, &de_cfg); + if (ret) { + perror(IOCTL_ERR_STR(SCMI_TLM_SET_ALL_CFG)); + return ret; + } + + fprintf(stdout, "\n- Single ASYNC read -\n-------------------\n"); + for (int i =3D 0; i < bulk->num_samples; i++) + fprintf(stdout, "0x%08X %016llu %016llX\n", + bulk->samples[i].id, bulk->samples[i].tstamp, + bulk->samples[i].val); + + bzero(bulk, bulk_sz); + bulk->num_samples =3D st.info.num_des; + ret =3D ioctl(st.fd, SCMI_TLM_BULK_READ, bulk); + if (ret) { + perror(IOCTL_ERR_STR(SCMI_TLM_BULK_READ)); + return -1; + } + + fprintf(stdout, "\n- BULK read -\n-------------------\n"); + for (int i =3D 0; i < bulk->num_samples; i++) + fprintf(stdout, "0x%08X %016llu %016llX\n", + bulk->samples[i].id, bulk->samples[i].tstamp, + bulk->samples[i].val); + + return 0; +} --=20 2.52.0