From nobody Wed Oct 1 23:35:40 2025 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 9BDD61F8AC5; Thu, 25 Sep 2025 20:36:24 +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=1758832587; cv=none; b=V84wr5cMKj5iDpk0Yzo7F6mDeRpaGJKlEKIXIm7Ih6KH8KgJd6YG1KTj9mTGypsrifT/hYdLsFrq0HdNUx7Z9b382kUreXUUtib0Zv86vK9tBiDZoZgVmKy/a43Z9+zIhCHPd9C0BTkm/gZgLivXViruyQeaSKcbX4CRy0ZkHA8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758832587; c=relaxed/simple; bh=CaAAdPzlWPvcy2qjq1UEGpKLI3+wDLrkFifkVdWo2e0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=aWIH38agPsWgvN22Ieo0sZT6Ux1znLG/TBXdjuNrGm7CykO0qBgXo0OZhAZkUGVzjixnf4ocXnKCmL+ttQeefGxNtca6Ifw9rNbBBk2g/Gw97Rzwc1t8mNRD9dEL+yHiCoByNjxyNsvQ1nyrcJJIABKUUh9/Q7YBo4JsSO4S5Gc= 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 9464E1C2B; Thu, 25 Sep 2025 13:36:15 -0700 (PDT) Received: from pluto.fritz.box (usa-sjc-mx-foss1.foss.arm.com [172.31.20.19]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id 042AA3F694; Thu, 25 Sep 2025 13:36:20 -0700 (PDT) From: Cristian Marussi To: linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, arm-scmi@vger.kernel.org Cc: sudeep.holla@arm.com, james.quinlan@broadcom.com, f.fainelli@gmail.com, vincent.guittot@linaro.org, etienne.carriere@st.com, peng.fan@oss.nxp.com, michal.simek@amd.com, quic_sibis@quicinc.com, dan.carpenter@linaro.org, d-gole@ti.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH 01/10] firmware: arm_scmi: Define a common SCMI_MAX_PROTOCOLS value Date: Thu, 25 Sep 2025 21:35:45 +0100 Message-ID: <20250925203554.482371-2-cristian.marussi@arm.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20250925203554.482371-1-cristian.marussi@arm.com> References: <20250925203554.482371-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 688466a0e816..59527193d6dd 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.51.0 From nobody Wed Oct 1 23:35:40 2025 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 3E59D303C87; Thu, 25 Sep 2025 20:36: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=1758832589; cv=none; b=Xiwvojz2NUUAd0bxS2l6K3J9mRtwQ0s8Cqe0D2HTdO2y0931VuDaBlW4jSLd0H4jkKIH+s6U1HJoVX3pkXpAZQJvUFznc0/6182ABdsSCodkYyjlVBl46IViQWaJ9HoBc1aWEjC2dvbLSDc8mNZMANXuLCymGn9NpwqCUuhTZ9M= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758832589; c=relaxed/simple; bh=bUuDv9Cj31qwosKQlIYpDeI5XqkB/KdNSJdev/qZzu8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=NVvslTmD07xFZC6pvk1R8OaJDVxRYFpuAyLoNSgMapeEXmUqd7wqEAAj9APOHxXUq/C3DOipreEAzuWQGKY3xptIxFtERiT3Agk53zvIA6f1OoNCvNZF8/L6Om30TA6tPG8kIjuhn//p3LT2Lepj0Q36QDShczGSCm5DwGntfvo= 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 A72041692; Thu, 25 Sep 2025 13:36:18 -0700 (PDT) Received: from pluto.fritz.box (usa-sjc-mx-foss1.foss.arm.com [172.31.20.19]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id 094F33F694; Thu, 25 Sep 2025 13:36:23 -0700 (PDT) From: Cristian Marussi To: linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, arm-scmi@vger.kernel.org Cc: sudeep.holla@arm.com, james.quinlan@broadcom.com, f.fainelli@gmail.com, vincent.guittot@linaro.org, etienne.carriere@st.com, peng.fan@oss.nxp.com, michal.simek@amd.com, quic_sibis@quicinc.com, dan.carpenter@linaro.org, d-gole@ti.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH 02/10] firmware: arm_scmi: Reduce the scope of protocols mutex Date: Thu, 25 Sep 2025 21:35:46 +0100 Message-ID: <20250925203554.482371-3-cristian.marussi@arm.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20250925203554.482371-1-cristian.marussi@arm.com> References: <20250925203554.482371-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 --- drivers/firmware/arm_scmi/driver.c | 53 +++++++++++++++--------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi= /driver.c index bd56a877fdfc..71ee25b78624 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 @@ -2179,10 +2180,13 @@ 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); - if (ret !=3D proto->id) - goto clean; + /* Finally register the initialized protocol */ + scoped_guard(mutex, &info->protocols_mtx) { + ret =3D idr_alloc(&info->protocols, pi, proto->id, proto->id + 1, + GFP_KERNEL); + if (ret !=3D proto->id) + goto clean; + } =20 /* * Warn but ignore events registration errors since we do not want @@ -2243,25 +2247,22 @@ 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_protocol_instance *pi =3D ERR_PTR(-EPROBE_DEFER); 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; - - /* 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); + scoped_guard(mutex, &info->protocols_mtx) { + pi =3D idr_find(&info->protocols, protocol_id); + if (pi) { + refcount_inc(&pi->users); + return pi; + } } - mutex_unlock(&info->protocols_mtx); + + /* 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); =20 return pi; } @@ -2294,10 +2295,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; @@ -2316,9 +2318,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.51.0 From nobody Wed Oct 1 23:35:40 2025 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 24BB331C580; Thu, 25 Sep 2025 20:36:30 +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=1758832592; cv=none; b=ZdCjHCINUG2AoqKp8ObL3eF1/RUsTE5dTpYPLc/36dVZ90leqzwFiEZxC84/YQV8bj+tC9KZais511VZWfVPtH6vsuEBGDmgTVDkdaLJtC18GFU/y/GgWnfyxtk0k5HnHSkAhqCkhoNIgF80rZvFvNnVqyoSyXt1STHDtJuGIw8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758832592; c=relaxed/simple; bh=OQzMdWF0UYZfLJJtt+kIRQouIWv1ByDrqDFdpmYd8xg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Gf57ky3MeDonL1kd6diXZkaZR6DZI5BencgMAuycI4KP+UTbEovoRdsvg8n7/3w6umYLFsc8xXcGltgwxS4xOs2HE4VEtandW9hI4S+DqcjHn4QMijDhwio7fBW4a+BtsIcKjMYictq0RfjoRnZGTKUJkoY1Y+1S9GSWdiyMGos= 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 978F81692; Thu, 25 Sep 2025 13:36:21 -0700 (PDT) Received: from pluto.fritz.box (usa-sjc-mx-foss1.foss.arm.com [172.31.20.19]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id 1C9C63F694; Thu, 25 Sep 2025 13:36:26 -0700 (PDT) From: Cristian Marussi To: linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, arm-scmi@vger.kernel.org Cc: sudeep.holla@arm.com, james.quinlan@broadcom.com, f.fainelli@gmail.com, vincent.guittot@linaro.org, etienne.carriere@st.com, peng.fan@oss.nxp.com, michal.simek@amd.com, quic_sibis@quicinc.com, dan.carpenter@linaro.org, d-gole@ti.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH 03/10] firmware: arm_scmi: Allow protocols to register for notifications Date: Thu, 25 Sep 2025 21:35:47 +0100 Message-ID: <20250925203554.482371-4-cristian.marussi@arm.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20250925203554.482371-1-cristian.marussi@arm.com> References: <20250925203554.482371-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 --- drivers/firmware/arm_scmi/common.h | 4 ++++ drivers/firmware/arm_scmi/driver.c | 12 ++++++++++++ drivers/firmware/arm_scmi/notify.c | 27 ++++++++++++++++++++------- drivers/firmware/arm_scmi/notify.h | 8 ++++++-- drivers/firmware/arm_scmi/protocols.h | 8 ++++++++ 5 files changed, 50 insertions(+), 9 deletions(-) diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi= /common.h index 07b9e629276d..2392ca98bc3e 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 @@ -505,5 +506,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 71ee25b78624..8f969d8b86a6 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -1674,6 +1674,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, @@ -2174,6 +2185,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..3e623c14745d 100644 --- a/drivers/firmware/arm_scmi/notify.c +++ b/drivers/firmware/arm_scmi/notify.c @@ -593,7 +593,12 @@ 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 +757,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 +772,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 +786,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 +821,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 +1386,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..2e40a7bb5b01 100644 --- a/drivers/firmware/arm_scmi/protocols.h +++ b/drivers/firmware/arm_scmi/protocols.h @@ -161,8 +161,13 @@ struct scmi_proto_helpers_ops; * @dev: A reference to the associated SCMI instance device (handle->dev). * @xops: A reference to a struct holding refs to the core xfer operations= that * can be used by the protocol implementation to generate SCMI messages. + * @hops: A reference to a struct holding refs to the common helper operat= ions + * that can be used by the protocol implementation. * @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 +187,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.51.0 From nobody Wed Oct 1 23:35:40 2025 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 9E92331FEDF; Thu, 25 Sep 2025 20:36:33 +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=1758832595; cv=none; b=ZbUhyn4kwn1U8SFN14p8rIp+6zbSZa+92XOcGoTgGRjcR64NK+Rl5bxSrzxZaJG3kw5o8G4cgm9Ba+Zp4h0NVEM8x9BcF3JDJe/wpoiCtiS0HOaGWb+TWsQ+/QN4RVGjOf0foFImOFAfs41zkESyM23VGlW3AUagdULWF1BXmsM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758832595; c=relaxed/simple; bh=lx3+DDXz17q9zchXS+pHqcpMn5hljDyFdA5F67r54e8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=KpVfotG0UojpmIlhAbTZdxRvPVcMO+hybEeAcdNqLpq91Np6GTd8dDH/bxcuWxrMz3MFcrWpxHSklPopzsLnCt4caXMtIvRJ0Y2ntowj3TctJAU3MdwdLttQ3b9KIMG3wHGbdumAQTsDdUYG62EN5bNkocqVe83Z2IPCN/YITU0= 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 C2BF91692; Thu, 25 Sep 2025 13:36:24 -0700 (PDT) Received: from pluto.fritz.box (usa-sjc-mx-foss1.foss.arm.com [172.31.20.19]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id 0EAA93F694; Thu, 25 Sep 2025 13:36:29 -0700 (PDT) From: Cristian Marussi To: linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, arm-scmi@vger.kernel.org Cc: sudeep.holla@arm.com, james.quinlan@broadcom.com, f.fainelli@gmail.com, vincent.guittot@linaro.org, etienne.carriere@st.com, peng.fan@oss.nxp.com, michal.simek@amd.com, quic_sibis@quicinc.com, dan.carpenter@linaro.org, d-gole@ti.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH 04/10] uapi: Add ARM SCMI definitions Date: Thu, 25 Sep 2025 21:35:48 +0100 Message-ID: <20250925203554.482371-5-cristian.marussi@arm.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20250925203554.482371-1-cristian.marussi@arm.com> References: <20250925203554.482371-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 --- MAINTAINERS | 1 + include/uapi/linux/scmi.h | 286 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 287 insertions(+) create mode 100644 include/uapi/linux/scmi.h diff --git a/MAINTAINERS b/MAINTAINERS index f6206963efbf..5c10e096e638 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24552,6 +24552,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..b1a6d34fee4a --- /dev/null +++ b/include/uapi/linux/scmi.h @@ -0,0 +1,286 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Copyright (C) 2025 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) +///TODO more flags +}; + +/** + * 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. + * @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 reserved[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 + * @num: Number of entries of @available + * @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 reserved[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[]; +}; + +/** + * 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 + * @peristent: 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[]; +}; + +/** + * 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[]; +}; + +/** + * 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[]; +}; + +/** + * 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[]; +}; + +#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.51.0 From nobody Wed Oct 1 23:35:40 2025 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 922862F617E; Thu, 25 Sep 2025 20:36: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=1758832600; cv=none; b=Ixu7o4yvs1IxHN3myUX0ihgEaDaNq4C8sjGpmwxGnfeaeQwjorkqKX7pwQhkL0JHe/dngHThQFdnGpH4q2xffkF7AWQCo8TDbOGyZgV/nB/OQZvg4jKEarlgSpyWHf9+4btRmIg7LDJet/Kcrxrg7JuV8HnL4XMavpLqorfGzsA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758832600; c=relaxed/simple; bh=jrQ9sWR+KZQ8VBfBQJTmNpOte+3egQ/rrrwlGG1JM2A=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=DYAjkWoNN4JsLEQXtw0wHz7gi2RP8082ZqkaHE40kubEQ7CtBYjk/pCwJkL9rEmJtWe+6BdhMay8G+S+Q2S2QMrL8HuVwHtZx4bmP5ofdSW3eWexec7KTw8OVHiZnQECHqRU/zWZQP9Iacnb1M/qa8dqdVyawyz8UzvVnOalfm0= 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 249081C2B; Thu, 25 Sep 2025 13:36:28 -0700 (PDT) Received: from pluto.fritz.box (usa-sjc-mx-foss1.foss.arm.com [172.31.20.19]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id 3BD4E3F694; Thu, 25 Sep 2025 13:36:33 -0700 (PDT) From: Cristian Marussi To: linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, arm-scmi@vger.kernel.org Cc: sudeep.holla@arm.com, james.quinlan@broadcom.com, f.fainelli@gmail.com, vincent.guittot@linaro.org, etienne.carriere@st.com, peng.fan@oss.nxp.com, michal.simek@amd.com, quic_sibis@quicinc.com, dan.carpenter@linaro.org, d-gole@ti.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH 05/10] firmware: arm_scmi: Add Telemetry protocol support Date: Thu, 25 Sep 2025 21:35:49 +0100 Message-ID: <20250925203554.482371-6-cristian.marussi@arm.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20250925203554.482371-1-cristian.marussi@arm.com> References: <20250925203554.482371-1-cristian.marussi@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add basic support for SCMI V4.0-alpha_0 Telemetry protocol including SHMTI, FastChannels, Notifications and Single Sample Reads collection methods. Signed-off-by: Cristian Marussi --- drivers/firmware/arm_scmi/Makefile | 2 +- drivers/firmware/arm_scmi/driver.c | 2 + drivers/firmware/arm_scmi/protocols.h | 1 + drivers/firmware/arm_scmi/telemetry.c | 2117 +++++++++++++++++++++++++ include/linux/scmi_protocol.h | 185 ++- 5 files changed, 2305 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 8f969d8b86a6..801d59e6b3bc 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -3468,6 +3468,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); } @@ -3486,6 +3487,7 @@ static void __exit scmi_driver_exit(void) scmi_system_unregister(); scmi_powercap_unregister(); scmi_pinctrl_unregister(); + scmi_telemetry_unregister(); =20 platform_driver_unregister(&scmi_driver); =20 diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_s= cmi/protocols.h index 2e40a7bb5b01..edd83a02e272 100644 --- a/drivers/firmware/arm_scmi/protocols.h +++ b/drivers/firmware/arm_scmi/protocols.h @@ -387,5 +387,6 @@ DECLARE_SCMI_REGISTER_UNREGISTER(sensors); DECLARE_SCMI_REGISTER_UNREGISTER(voltage); DECLARE_SCMI_REGISTER_UNREGISTER(system); DECLARE_SCMI_REGISTER_UNREGISTER(powercap); +DECLARE_SCMI_REGISTER_UNREGISTER(telemetry); =20 #endif /* _SCMI_PROTOCOLS_H */ diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_s= cmi/telemetry.c new file mode 100644 index 000000000000..f03000c173c2 --- /dev/null +++ b/drivers/firmware/arm_scmi/telemetry.c @@ -0,0 +1,2117 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Management Interface (SCMI) Telemetry Protocol + * + * Copyright (C) 2025 ARM Ltd. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "protocols.h" +#include "notify.h" + +/* Updated only after ALL the mandatory features for that version are merg= ed */ +#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x10000 + +#define SCMI_TLM_TDCF_MAX_RETRIES 5 + +enum scmi_telemetry_protocol_cmd { + TELEMETRY_LIST_SHMTI =3D 0x3, + TELEMETRY_DE_DESCRIPTION =3D 0x4, + TELEMETRY_LIST_UPDATE_INTERVALS =3D 0x5, + TELEMETRY_DE_CONFIGURE =3D 0x6, + TELEMETRY_DE_ENABLED_LIST =3D 0x7, //TODO IMPLEMENT + TELEMETRY_CONFIG_SET =3D 0x8, + TELEMETRY_READING_COMPLETE =3D TELEMETRY_CONFIG_SET, + TELEMETRY_CONFIG_GET =3D 0x9, //TODO IMPLEMENT ! + TELEMETRY_RESET =3D 0xA, +}; + +struct scmi_msg_resp_telemetry_protocol_attributes { + __le32 de_num; + __le32 groups_num; + __le32 de_implementation_rev_dword[SCMI_TLM_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[]; +}; + +struct scmi_shmti_desc { + __le32 id; + __le32 addr_low; + __le32 addr_high; + __le32 length; +}; + +struct scmi_msg_resp_telemetry_shmti_list { + __le32 num_shmti; + struct scmi_shmti_desc desc[]; +}; + +struct de_desc_fc { + __le32 addr_low; + __le32 addr_high; + __le32 size; +}; + +struct scmi_de_desc { + __le32 id; + __le32 grp_id; + __le32 data_sz; + __le32 attr_1; +#define IS_NAME_SUPPORTED(d) ((d)->attr_1 & BIT(31)) +#define IS_FC_SUPPORTED(d) ((d)->attr_1 & BIT(30)) +#define GET_DE_TYPE(d) (le32_get_bits((d)->attr_1, GENMASK(29, 22))) +#define IS_PERSISTENT(d) ((d)->attr_1 & BIT(21)) +#define GET_DE_UNIT_EXP(d) \ + ({ \ + int __signed_exp =3D \ + le32_get_bits((d)->attr_1, GENMASK(20, 13)); \ + \ + if (__signed_exp & BIT(7)) \ + __signed_exp |=3D GENMASK(31, 8); \ + __signed_exp; \ + }) +#define GET_DE_UNIT(d) (le32_get_bits((d)->attr_1, GENMASK(12, 5))) + +#define GET_DE_TSTAMP_EXP(d) \ + ({ \ + int __signed_exp =3D \ + FIELD_GET(GENMASK(4, 1), (d)->attr_1); \ + \ + if (__signed_exp & BIT(3)) \ + __signed_exp |=3D GENMASK(31, 4); \ + __signed_exp; \ + }) +#define IS_TSTAMP_SUPPORTED(d) ((d)->attr_1 & BIT(0)) + __le32 attr_2; +#define GET_DE_INSTA_ID(d) (le32_get_bits((d)->attr_2, GENMASK(31, 24))) +#define GET_COMPO_INSTA_ID(d) (le32_get_bits((d)->attr_2, GENMASK(23, 8))) +#define GET_COMPO_TYPE(d) (le32_get_bits((d)->attr_2, GENMASK(7, 0))) + __le32 reserved; +}; + +struct scmi_msg_resp_telemetry_de_description { + __le32 num_desc; + struct scmi_de_desc desc[]; +}; + +struct scmi_msg_telemetry_update_intervals { + __le32 index; + __le32 group_identifier; +#define ALL_DES_NO_GROUP 0x0 +#define SPECIFIC_GROUP_DES 0x1 +#define ALL_DES_ANY_GROUP 0x2 + __le32 flags; +}; + +struct scmi_msg_resp_telemetry_update_intervals { + __le32 flags; +#define INTERVALS_DISCRETE(x) (!((x) & BIT(12))) + __le32 intervals[]; +}; + +struct scmi_msg_telemetry_de_configure { + __le32 id; + __le32 flags; +#define DE_ENABLE_NO_TSTAMP BIT(0) +#define DE_ENABLE_WTH_TSTAMP BIT(1) +#define DE_DISABLE_ALL BIT(2) +#define GROUP_SELECTOR BIT(3) +#define EVENT_DE 0 +#define EVENT_GROUP 1 +#define DE_DISABLE_ONE 0x0 +}; + +struct scmi_msg_resp_telemetry_de_configure { + __le32 shmti_id; +#define IS_SHMTI_ID_VALID(x) ((x) !=3D 0xFFFFFFFF) + __le32 tdcf_de_offset; +}; + +struct scmi_msg_telemetry_config_set { + __le32 grp_id; + __le32 control; +#define TELEMETRY_ENABLE (BIT(0)) + +#define TELEMETRY_MODE(x) (FIELD_PREP(GENMASK(4, 1), (x))) +#define TELEMETRY_MODE_ONDEMAND TELEMETRY_MODE(0) +#define TELEMETRY_MODE_NOTIFS TELEMETRY_MODE(1) +#define TELEMETRY_MODE_SINGLE TELEMETRY_MODE(2) + +#define TELEMETRY_SELECTOR(x) (FIELD_PREP(GENMASK(8, 5), (x))) +#define TELEMETRY_SELECTOR_ORPHANS TELEMETRY_SELECTOR(0) +#define TELEMETRY_SELECTOR_GROUP TELEMETRY_SELECTOR(1) +#define TELEMETRY_SELECTOR_ALL TELEMETRY_SELECTOR(2) + __le32 sampling_rate; +}; + +struct scmi_msg_resp_telemetry_reading_complete { + __le32 num_dwords; + __le32 dwords[]; +}; + +/* TDCF */ + +#define TO_CPU_64(h, l) (((u64)le32_to_cpu((h)) << 32) | le32_to_cpu((l))) + +struct fc_line { + u32 data_low; + u32 data_high; +}; + +struct fc_tsline { + u32 data_low; + u32 data_high; + u32 ts_low; + u32 ts_high; +}; + +struct line { + u32 data_low; + u32 data_high; +}; + +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((_f)->data_high, (_f)->data_low)); \ +}) + +#define LINE_TSTAMP_GET(f) \ +({ \ + typeof(f) _f =3D (f); \ + \ + (TO_CPU_64((_f)->ts_high, (_f)->ts_low)); \ +}) + +#define BLK_TSTAMP_GET(f) LINE_TSTAMP_GET(f) + +struct payload { + u32 meta; +#define IS_BLK_TS(x) ((x)->meta & BIT(4)) +#define USE_BLK_TS(x) ((x)->meta & BIT(3)) +#define USE_LINE_TS(x) ((x)->meta & BIT(2)) +#define TS_VALID(x) ((x)->meta & BIT(1)) +#define DATA_INVALID(x) ((x)->meta & BIT(0)) + u32 id; + union { + struct line l; + struct tsline tsl; + struct blk_tsline blk_tsl; + }; +}; + +#define PAYLD_ID(x) (le32_to_cpu(((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 SHMTI_MIN_SIZE (sizeof(struct tdcf) + TDCF_EPLG_SZ) + +#define TDCF_START_SEQ_GET(x) \ + ({ \ + u64 _val; \ + struct prlg *_p =3D &((x)->prlg); \ + \ + _val =3D TO_CPU_64(_p->seq_high, _p->seq_low); \ + (_val); \ + }) + +#define IS_BAD_START_SEQ(s) ((s) & 0x1) + +#define TDCF_END_SEQ_GET(e) \ + ({ \ + u64 _val; \ + struct eplg *_e =3D (e); \ + \ + _val =3D TO_CPU_64(_e->seq_high, _e->seq_low); \ + (_val); \ + }) + +struct telemetry_shmti { + int id; + void __iomem *base; + u32 len; + 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_info { + bool streaming_mode; + int num_shmti; + struct device *dev; + struct telemetry_shmti *shmti; + struct xarray xa_des; + struct xarray xa_bts; + struct scmi_telemetry_info info; + struct notifier_block telemetry_nb; +}; + +#define telemetry_nb_to_info(x) \ + container_of(x, struct telemetry_info, telemetry_nb) + +struct telemetry_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 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 telemetry_block_ts *bts; + struct scmi_telemetry_de de; +}; + +#define to_tde(d) container_of(d, struct telemetry_de, de) + +struct scmi_tlm_de_priv { + struct telemetry_info *ti; + void *next; +}; + +static int +scmi_telemetry_protocol_attributes_get(const struct scmi_protocol_handle *= ph, + struct telemetry_info *ti) +{ + int ret; + struct scmi_xfer *t; + struct scmi_msg_resp_telemetry_protocol_attributes *resp; + + ret =3D ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, + 0, sizeof(*resp), &t); + if (ret) + return ret; + + resp =3D t->rx.buf; + ret =3D ph->xops->do_xfer(ph, t); + if (!ret) { + __le32 attr =3D resp->attributes; + + ti->info.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)); + /* Allocate DEs descriptors */ + ti->info.des =3D devm_kcalloc(ph->dev, ti->info.base.num_des, + sizeof(*ti->info.des), GFP_KERNEL); + if (!ti->info.des) { + ret =3D -ENOMEM; + goto out; + } + + /* Allocate a set of contiguous DE info descriptors. */ + ti->info.des_store =3D devm_kcalloc(ph->dev, ti->info.base.num_des, + sizeof(*ti->info.des_store), + GFP_KERNEL); + if (!ti->info.des_store) { + ret =3D -ENOMEM; + goto out; + } + + /* Allocate DE GROUPS descriptors */ + ti->info.groups =3D devm_kcalloc(ph->dev, ti->info.base.num_groups, + sizeof(*ti->info.groups), GFP_KERNEL); + if (!ti->info.groups) { + ret =3D -ENOMEM; + goto out; + } + + /* Allocate a set of contiguous Group info descriptors. */ + ti->info.grps_store =3D devm_kcalloc(ph->dev, ti->info.base.num_groups, + sizeof(*ti->info.grps_store), + GFP_KERNEL); + if (!ti->info.grps_store) { + ret =3D -ENOMEM; + goto out; + } + + for (int i =3D 0; i < ti->info.base.num_groups; i++) { + ti->info.grps_store[i].id =3D i; + /* Bind contiguous Group info struct */ + ti->info.groups[i].info =3D &ti->info.grps_store[i]; + } + } + +out: + ph->xops->xfer_put(ph, t); + + return ret; +} + +static void iter_tlm_prepare_message(void *message, + unsigned int desc_index, const void *priv) +{ + put_unaligned_le32(desc_index, message); +} + +static int iter_de_descr_update_state(struct scmi_iterator_state *st, + const void *response, void *priv) +{ + const struct scmi_msg_resp_telemetry_de_description *r =3D response; + struct scmi_tlm_de_priv *p =3D priv; + + st->num_returned =3D le32_get_bits(r->num_desc, GENMASK(15, 0)); + st->num_remaining =3D le32_get_bits(r->num_desc, GENMASK(31, 16)); + + /* Initialized to first descriptor */ + p->next =3D (void *)r->desc; + + return 0; +} + +static int iter_de_descr_process_response(const struct scmi_protocol_handl= e *ph, + const void *response, + struct scmi_iterator_state *st, + void *priv) +{ + struct telemetry_de *tde; + struct scmi_tlm_de_priv *p =3D priv; + const struct scmi_de_desc *desc =3D p->next; + unsigned int grp_id; + int ret; + + tde =3D to_tde(p->ti->info.des[st->desc_index + st->loop_idx]); + + tde->de.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 p->ti->info.base.num_groups) + return -EINVAL; + + /* Link to parent group */ + tde->de.info->grp_id =3D grp_id; + tde->de.grp =3D &p->ti->info.groups[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); + p->next +=3D sizeof(*desc); + if (tde->de.fc_support) { + u32 size; + u64 phys_addr; + void __iomem *addr; + struct de_desc_fc *dfc; + + dfc =3D p->next; + phys_addr =3D le32_to_cpu(dfc->addr_low); + phys_addr |=3D (u64)le32_to_cpu(dfc->addr_high) << 32; + + size =3D le32_to_cpu(dfc->size); + addr =3D devm_ioremap(ph->dev, phys_addr, size); + if (!addr) + return -EADDRNOTAVAIL; + + tde->base =3D addr; + tde->offset =3D 0; + tde->fc_size =3D size; + + /* Variably sized depending on FC support */ + p->next +=3D sizeof(*dfc); + } + + if (tde->de.name_support) { + const char *de_name =3D p->next; + + strscpy(tde->de.info->name, de_name, SCMI_SHORT_NAME_MAX_SIZE); + //tde->de.name =3D tde->name; + + /* Variably sized depending on name support */ + p->next +=3D SCMI_SHORT_NAME_MAX_SIZE; + } + + /* Store DE pointer by de_id */ + ret =3D xa_insert(&p->ti->xa_des, tde->de.info->id, &tde->de, GFP_KERNEL); + if (ret) + return ret; + + /* Account for this DE in group num_de counter */ + if (tde->de.grp) + tde->de.grp->info->num_des++; + + return 0; +} + +static int +scmi_telemetry_de_groups_init(struct device *dev, struct telemetry_info *t= i) +{ + /* Allocate all groups DEs IDs arrays at first ... */ + for (int i =3D 0; i < ti->info.base.num_groups; i++) { + struct scmi_telemetry_group *grp =3D &ti->info.groups[i]; + + grp->des =3D devm_kcalloc(dev, grp->info->num_des, + sizeof(unsigned int), GFP_KERNEL); + if (!grp->des) + return -ENOMEM; + + /* + * Max size 32bit ID string in Hex: 0xCAFECAFE + * - 10 digits + ' '/'\n' =3D 11 bytes per number + * - terminating NUL character + */ + grp->des_str_sz =3D grp->info->num_des * 11 + 1; + grp->des_str =3D devm_kzalloc(dev, grp->des_str_sz, GFP_KERNEL); + if (!grp->des_str) + return -ENOMEM; + + /* Reset group DE counter */ + grp->info->num_des =3D 0; + } + + /* Scan DEs and populate DE IDs arrays for all groups */ + for (int i =3D 0; i < ti->info.base.num_des; i++) { + struct scmi_telemetry_group *grp =3D ti->info.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 compsing DES string */ + for (int i =3D 0; i < ti->info.base.num_groups; i++) { + struct scmi_telemetry_group *grp =3D &ti->info.groups[i]; + char *buf =3D grp->des_str; + size_t bufsize =3D grp->des_str_sz; + + 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", + ti->info.des[grp->des[j]]->info->id, term); + + buf +=3D len; + bufsize -=3D len; + } + } + + return 0; +} + +static int +scmi_telemetry_de_descriptors_get(const struct scmi_protocol_handle *ph, + struct telemetry_info *ti) +{ + struct scmi_iterator_ops ops =3D { + .prepare_message =3D iter_tlm_prepare_message, + .update_state =3D iter_de_descr_update_state, + .process_response =3D iter_de_descr_process_response, + }; + struct scmi_tlm_de_priv tpriv =3D { + .ti =3D ti, + .next =3D NULL, + }; + void *iter; + int ret; + + xa_init(&ti->xa_des); + iter =3D ph->hops->iter_response_init(ph, &ops, ti->info.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); +} + +static int scmi_telemetry_enumerate_de(const struct scmi_protocol_handle *= ph, + struct telemetry_info *ti) +{ + int ret; + + if (!ti->info.base.num_des) + return 0; + + for (int i =3D 0; i < ti->info.base.num_des; i++) { + struct telemetry_de *tde; + + tde =3D devm_kzalloc(ph->dev, sizeof(*tde), GFP_KERNEL); + if (!tde) + return -ENOMEM; + + mutex_init(&tde->mtx); + + /* Bind contiguous DE info structures */ + tde->de.info =3D &ti->info.des_store[i]; + ti->info.des[i] =3D &tde->de; + } + + ret =3D scmi_telemetry_de_descriptors_get(ph, ti); + if (ret) { + dev_err(ph->dev, "Cannot get DE descriptors"); + return ret; + } + + return 0; +} + +struct scmi_tlm_ivl_priv { + struct device *dev; + struct scmi_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; + struct scmi_tlm_intervals *intrvs; + int inum; + + inum =3D st->num_returned + st->num_remaining; + intrvs =3D devm_kzalloc(p->dev, + sizeof(*intrvs) + inum * sizeof(__u32), + GFP_KERNEL); + if (!intrvs) + return -ENOMEM; + + intrvs->discrete =3D INTERVALS_DISCRETE(r->flags); + /* Check consistency on first call */ + if (!intrvs->discrete && + (st->num_returned !=3D 3 || st->num_remaining !=3D 0)) + return -EINVAL; + + intrvs->num =3D inum; + st->max_resources =3D intrvs->num; + + *p->intrvs =3D intrvs; + } + + return 0; +} + +static int +iter_intervals_process_response(const struct scmi_protocol_handle *ph, + const void *response, + struct scmi_iterator_state *st, void *priv) +{ + const struct scmi_msg_resp_telemetry_update_intervals *r =3D response; + struct scmi_tlm_ivl_priv *p =3D priv; + struct scmi_tlm_intervals *intrvs =3D *p->intrvs; + unsigned int idx =3D st->loop_idx; + + intrvs->update_intervals[st->desc_index + idx] =3D r->intervals[idx]; + + return 0; +} + +static int +scmi_tlm_enumerate_update_intervals(const struct scmi_protocol_handle *ph, + struct telemetry_info *ti, int grp_id, + unsigned int flags) +{ + struct scmi_iterator_ops ops =3D { + .prepare_message =3D iter_intervals_prepare_message, + .update_state =3D iter_intervals_update_state, + .process_response =3D iter_intervals_process_response, + }; + struct scmi_tlm_ivl_priv ipriv =3D { + .dev =3D ph->dev, + .grp_id =3D grp_id, + .intrvs =3D (grp_id =3D=3D SCMI_TLM_GRP_INVALID) ? + &ti->info.intervals : + &ti->info.groups[grp_id].intervals, + .flags =3D flags, + }; + void *iter; + + iter =3D ph->hops->iter_response_init(ph, &ops, 0, + TELEMETRY_LIST_UPDATE_INTERVALS, + sizeof(struct scmi_msg_telemetry_update_intervals), + &ipriv); + if (IS_ERR(iter)) + return PTR_ERR(iter); + + return ph->hops->iter_response_run(iter); +} + +static int +scmi_telemetry_enumerate_update_intervals(const struct scmi_protocol_handl= e *ph, + struct telemetry_info *ti) +{ + int ret; + unsigned int flags; + + flags =3D !ti->info.per_group_config_support ? + ALL_DES_ANY_GROUP : ALL_DES_NO_GROUP; + + ret =3D scmi_tlm_enumerate_update_intervals(ph, ti, SCMI_TLM_GRP_INVALID, + flags); + if (ret) + return ret; + + /* A copy for UAPI access... */ + ti->info.base.num_intervals =3D ti->info.intervals->num; + if (ti->info.base.num_groups && ti->info.per_group_config_support) { + flags =3D SPECIFIC_GROUP_DES; + for (int id =3D 0; id < ti->info.base.num_groups; id++) { + ret =3D scmi_tlm_enumerate_update_intervals(ph, ti, id, + flags); + if (ret) + break; + + ti->info.grps_store[id].num_intervals =3D + ti->info.groups[id].intervals->num; + } + } + + return ret; +} + +static int iter_shmti_update_state(struct scmi_iterator_state *st, + const void *response, void *priv) +{ + const struct scmi_msg_resp_telemetry_shmti_list *r =3D response; + + st->num_returned =3D le32_get_bits(r->num_shmti, GENMASK(15, 0)); + st->num_remaining =3D le32_get_bits(r->num_shmti, GENMASK(31, 16)); + + return 0; +} + +static int iter_shmti_process_response(const struct scmi_protocol_handle *= ph, + const void *response, + struct scmi_iterator_state *st, + void *priv) +{ + const struct scmi_msg_resp_telemetry_shmti_list *r =3D response; + struct telemetry_info *ti =3D priv; + struct telemetry_shmti *shmti; + const struct scmi_shmti_desc *desc; + void __iomem *addr; + u64 phys_addr; + u32 len; + + desc =3D &r->desc[st->loop_idx]; + shmti =3D &ti->shmti[st->desc_index + st->loop_idx]; + + shmti->id =3D le32_to_cpu(desc->id); + phys_addr =3D le32_to_cpu(desc->addr_low); + phys_addr |=3D (u64)le32_to_cpu(desc->addr_high) << 32; + + len =3D le32_to_cpu(desc->length); + 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(const struct scmi_protocol_handl= e *ph, + struct telemetry_info *ti) +{ + int ret; + + if (!ti->num_shmti) + return 0; + + ti->shmti =3D devm_kcalloc(ph->dev, ti->num_shmti, sizeof(*ti->shmti), + GFP_KERNEL); + if (!ti->shmti) + return -ENOMEM; + + ret =3D scmi_telemetry_shmti_list(ph, ti); + if (ret) { + dev_err(ph->dev, "Cannot get SHMTI list descriptors"); + return ret; + } + + return 0; +} + +static const struct scmi_telemetry_info * +scmi_telemetry_info_get(const struct scmi_protocol_handle *ph) +{ + struct telemetry_info *ti =3D ph->get_priv(ph); + + return &ti->info; +} + +static const struct scmi_tlm_de_info * +scmi_telemetry_de_info_get(const struct scmi_protocol_handle *ph, u32 id) +{ + struct telemetry_info *ti =3D ph->get_priv(ph); + + return xa_load(&ti->xa_des, id); +} + +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->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, + bool update) +{ + bool ts_valid =3D TS_VALID(payld); + struct scmi_telemetry_de *de; + struct telemetry_de *tde; + u64 val, tstamp =3D 0; + u32 id; + + id =3D PAYLD_ID(payld); + de =3D xa_load(&ti->xa_des, id); + if (!de) + return; + + tde =3D to_tde(de); + /* Update DE location refs if requested: normally done only on enable */ + if (update) { + tde->base =3D shmti->base; + tde->eplg =3D SHMTI_EPLG(shmti); + tde->offset =3D (void *)payld - (void *)shmti->base; + } + + 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->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 (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, + bool update) +{ + 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, update); + 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, + bool update) +{ + struct telemetry_shmti *shmti =3D &ti->shmti[shmti_id]; + struct tdcf __iomem *tdcf =3D shmti->base; + int retries =3D SCMI_TLM_TDCF_MAX_RETRIES; + u64 startm =3D 0, endm =3D 0xffffffffffffffff; + void *eplg =3D SHMTI_EPLG(shmti); + + if (!tdcf) + return -ENODEV; + + do { + unsigned int qwords; + void __iomem *next; + + /* A bit of exponential backoff between retries */ + fsleep((SCMI_TLM_TDCF_MAX_RETRIES - retries) * 1000); + + startm =3D TDCF_START_SEQ_GET(tdcf); + if (IS_BAD_START_SEQ(startm)) + continue; + + /* On a BAD_SEQ this will be updated on the next attempt */ + shmti->last_magic =3D startm; + + qwords =3D tdcf->prlg.num_qwords; + next =3D tdcf->payld; + while (qwords) { + int used_qwords; + + used_qwords =3D scmi_telemetry_tdcf_line_parse(ti, next, + shmti, update); + 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_de *de; + + for (int i =3D 0; i < grp->info->num_des; i++) { + de =3D ti->info.des[grp->des[i]]; + + if (enable) + de->enabled =3D *enable; + if (tstamp) + de->tstamp_enabled =3D *tstamp; + } + + return 0; +} + +static int +scmi_telemetry_state_set_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->dev, + &ti->shmti[sid], + payld, + &ti->xa_bts); + if (WARN_ON(!tde->bts)) + return -EPROTO; + } + } else { + /* + * 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. + */ + scmi_telemetry_shmti_scan(ti, sid, 0, true); + } + } 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->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) +{ + void *obj; + bool *enabled_state, *tstamp_enabled_state; + struct telemetry_info *ti =3D ph->get_priv(ph); + + if (!is_group) { + struct scmi_telemetry_de *de; + + de =3D xa_load(&ti->xa_des, id); + if (!de) + return -ENODEV; + + enabled_state =3D &de->enabled; + tstamp_enabled_state =3D &de->tstamp_enabled; + obj =3D de; + } else { + struct scmi_telemetry_group *grp; + + if (id >=3D ti->info.base.num_groups) + return -EINVAL; + + grp =3D &ti->info.groups[id]; + + enabled_state =3D &grp->enabled; + tstamp_enabled_state =3D &grp->tstamp_enabled; + obj =3D grp; + } + + return __scmi_telemetry_state_set(ph, is_group, enable, enabled_state, + tstamp, tstamp_enabled_state, obj); +} + +static int scmi_telemetry_all_disable(const struct scmi_protocol_handle *p= h, + bool is_group) +{ + struct scmi_msg_telemetry_de_configure *msg; + struct scmi_xfer *t; + int ret; + + ret =3D ph->xops->xfer_get_init(ph, TELEMETRY_DE_CONFIGURE, + sizeof(*msg), 0, &t); + if (ret) + return ret; + + msg =3D t->tx.buf; + msg->flags =3D DE_DISABLE_ALL; + if (is_group) + msg->flags |=3D GROUP_SELECTOR; + ret =3D ph->xops->do_xfer(ph, t); + if (!ret) { + struct telemetry_info *ti =3D ph->get_priv(ph); + + for (int i =3D 0; i < ti->info.base.num_des; i++) + ti->info.des[i]->enabled =3D false; + + if (is_group) { + for (int i =3D 0; i < ti->info.base.num_groups; i++) + ti->info.groups[i].enabled =3D false; + } + } + + ph->xops->xfer_put(ph, t); + + return ret; +} + +static int +scmi_telemetry_collection_configure(const struct scmi_protocol_handle *ph, + unsigned int res_id, bool grp_ignore, + bool *enable, + unsigned int *update_interval_ms, + enum scmi_telemetry_collection *mode) +{ + 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 { + active_update_interval =3D + &ti->info.groups[res_id].active_update_interval; + current_mode =3D &ti->info.groups[res_id].current_mode; + } + + if (!enable && !update_interval_ms && (!mode || *mode =3D=3D *current_mod= e)) + return 0; + + ret =3D ph->xops->xfer_get_init(ph, TELEMETRY_CONFIG_SET, + sizeof(*msg), 0, &t); + if (ret) + return ret; + + if (!update_interval_ms) + interval =3D cpu_to_le32(*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_SELECTOR_ALL : + TELEMETRY_SELECTOR_GROUP; + msg->control |=3D TELEMETRY_MODE(next_mode); + msg->sampling_rate =3D interval; + ret =3D ph->xops->do_xfer(ph, t); + if (!ret) { + ti->info.enabled =3D tlm_enable; + *current_mode =3D next_mode; + ti->info.notif_enabled =3D *current_mode =3D=3D SCMI_TLM_NOTIFICATION; + if (update_interval_ms) + *active_update_interval =3D interval; + } + + ph->xops->xfer_put(ph, t); + + return ret; +} + +static int scmi_telemetry_de_data_fc_read(struct telemetry_de *tde, + u64 *tstamp, u64 *val) +{ + struct fc_tsline __iomem *fc =3D tde->base + tde->offset; + + *val =3D LINE_DATA_GET(fc); + if (tstamp) { + if (tde->de.tstamp_support) + *tstamp =3D LINE_TSTAMP_GET(fc); + else + *tstamp =3D 0; + } + + return 0; +} + +static void scmi_telemetry_scan_update(struct telemetry_info *ti, u64 ts) +{ + /* Scan all SHMTIs ... */ + for (int id =3D 0; id < ti->num_shmti; id++) + scmi_telemetry_shmti_scan(ti, id, ts, false); + + /* ... then scan all FCs ... XXX Use a list */ + for (int i =3D 0; i < ti->info.base.num_des; i++) { + struct scmi_telemetry_de *de; + struct telemetry_de *tde; + u64 val, tstamp; + int ret; + + de =3D ti->info.des[i]; + if (!de->enabled) + continue; + + tde =3D to_tde(de); + if (!tde->de.fc_support) + continue; + + //TODO Report errors + ret =3D scmi_telemetry_de_data_fc_read(tde, &tstamp, &val); + if (ret) + return; + + guard(mutex)(&tde->mtx); + tde->last_val =3D val; + if (de->tstamp_enabled) + tde->last_ts =3D tstamp; + else + tde->last_ts =3D 0; + } +} + +/* + * 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 (le32_to_cpu(payld->id) !=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_lookup(struct telemetry_de *tde, + u64 *tstamp, u64 *val) +{ + if (!tde->de.fc_support) + return scmi_telemetry_tdcf_de_parse(tde, tstamp, val); + + return scmi_telemetry_de_data_fc_read(tde, tstamp, val); +} + +static int scmi_telemetry_de_collect(struct telemetry_info *ti, + struct scmi_telemetry_de *de, + u64 *tstamp, u64 *val) +{ + struct telemetry_de *tde =3D to_tde(de); + + if (!de->enabled) + return -EINVAL; + + /* + * DE readings returns cached values when: + * - DE data value was retrieved via notification + */ + scoped_guard(mutex, &tde->mtx) { + if (tde->cached) { + *val =3D tde->last_val; + if (tstamp) + *tstamp =3D tde->last_ts; + return 0; + } + } + + return scmi_telemetry_de_lookup(tde, tstamp, val); +} + +static int scmi_telemetry_de_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) +{ + int max_samples; + + max_samples =3D *num_samples; + *num_samples =3D 0; + + for (int i =3D 0; i < ti->info.base.num_des; i++) { + struct scmi_telemetry_de *de; + u64 val, tstamp; + int ret; + + de =3D ti->info.des[i]; + if (grp_id !=3D SCMI_TLM_GRP_INVALID && + (!de->grp || de->grp->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, unsigned int *dwords, + ktime_t timestamp) +{ + u32 next =3D 0; + + while (next < num_dwords) { + struct payload *payld =3D (struct payload *)&dwords[next]; + struct scmi_telemetry_de *de; + struct telemetry_de *tde; + u32 de_id; + + next +=3D USE_LINE_TS(payld) ? + TS_LINE_DATA_PAYLD_WORDS : LINE_DATA_PAYLD_WORDS; + + if (DATA_INVALID(payld)) { + dev_err(ti->dev, "MSG - Received INVALID DATA line\n"); + continue; + } + + de_id =3D le32_to_cpu(payld->id); + de =3D xa_load(&ti->xa_des, de_id); + if (!de || !de->enabled) { + dev_err(ti->dev, + "MSG - Received INVALID DE - ID:%u enabled:%d\n", + de_id, de ? (de->enabled ? 'Y' : 'N') : 'X'); + continue; + } + + tde =3D to_tde(de); + guard(mutex)(&tde->mtx); + tde->cached =3D true; + tde->last_val =3D LINE_DATA_GET(&payld->tsl); + //TODO BLK_TS 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_SELECTOR_ALL : + TELEMETRY_SELECTOR_GROUP; + msg->control |=3D TELEMETRY_MODE_SINGLE; + msg->sampling_rate =3D 0; + + ret =3D ph->xops->do_xfer_with_response(ph, t); + if (!ret) { + struct scmi_msg_resp_telemetry_reading_complete *r =3D t->rx.buf; + + /* Update cached DEs values from payload */ + if (r->num_dwords) + scmi_telemetry_msg_payld_process(ti, r->num_dwords, + r->dwords, 0); + /* 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 int scmi_telemetry_reset(const struct scmi_protocol_handle *ph) +{ + int ret; + struct scmi_xfer *t; + + ret =3D ph->xops->xfer_get_init(ph, TELEMETRY_RESET, sizeof(u32), 0, &t); + if (ret) + return ret; + + put_unaligned_le32(0, t->tx.buf); + ret =3D ph->xops->do_xfer(ph, t); + if (!ret) { + struct telemetry_info *ti =3D ph->get_priv(ph); + + //XXX Better would be to read back from platform + // CONFIG_GET + DE_ENABLED_LIST + ti->info.enabled =3D false; + ti->info.notif_enabled =3D false; + ti->info.current_mode =3D SCMI_TLM_ONDEMAND; + ti->info.active_update_interval =3D 0; + + for (int i =3D 0; i < ti->info.base.num_des; i++) { + ti->info.des[i]->enabled =3D false; + ti->info.des[i]->tstamp_enabled =3D false; + } + + for (int i =3D 0; i < ti->info.base.num_groups; i++) { + ti->info.groups[i].enabled =3D false; + ti->info.groups[i].tstamp_enabled =3D false; + ti->info.groups[i].current_mode =3D SCMI_TLM_ONDEMAND; + ti->info.groups[i].active_update_interval =3D 0; + } + } + ph->xops->xfer_put(ph, t); + + return ret; +} + +static const struct scmi_telemetry_proto_ops tlm_proto_ops =3D { + .info_get =3D scmi_telemetry_info_get, + .de_info_get =3D scmi_telemetry_de_info_get, + .state_get =3D scmi_telemetry_state_get, + .state_set =3D scmi_telemetry_state_set, + .all_disable =3D scmi_telemetry_all_disable, + .collection_configure =3D scmi_telemetry_collection_configure, + .de_data_read =3D scmi_telemetry_de_data_read, + .des_bulk_read =3D scmi_telemetry_des_bulk_read, + .des_sample_get =3D scmi_telemetry_des_sample_get, + .config_get =3D scmi_telemetry_config_get, + .reset =3D scmi_telemetry_reset, +}; + +static bool +scmi_telemetry_notify_supported(const struct scmi_protocol_handle *ph, + u8 evt_id, u32 src_id) +{ + struct telemetry_info *ti =3D ph->get_priv(ph); + + return ti->info.continuos_update_support; +} + +static int +scmi_telemetry_set_notify_enabled(const struct scmi_protocol_handle *ph, + u8 evt_id, u32 src_id, bool enable) +{ + return 0; +} + +static void * +scmi_telemetry_fill_custom_report(const struct scmi_protocol_handle *ph, + u8 evt_id, ktime_t timestamp, + const void *payld, size_t payld_sz, + void *report, u32 *src_id) +{ + const struct scmi_telemetry_update_notify_payld *p =3D payld; + struct scmi_telemetry_update_report *r =3D report; + + /* At least sized as an empty notification */ + if (payld_sz < sizeof(*p)) + return NULL; + + r->timestamp =3D timestamp; + r->agent_id =3D le32_to_cpu(p->agent_id); + r->status =3D le32_to_cpu(p->status); + r->num_dwords =3D le32_to_cpu(p->num_dwords); + /* + * Allocated dwords and report are sized as max_msg_size, so as + * to allow for the maximum payload permitted by the configured + * transport. Overflow is not possible since out-of-size messages + * are dropped at the transport layer. + */ + if (r->num_dwords) + memcpy(r->dwords, p->array, r->num_dwords * sizeof(u32)); + + *src_id =3D 0; + + return r; +} + +static const struct scmi_event tlm_events[] =3D { + { + .id =3D SCMI_EVENT_TELEMETRY_UPDATE, + .max_payld_sz =3D 0, + .max_report_sz =3D 0, + }, +}; + +static const struct scmi_event_ops tlm_event_ops =3D { + .is_notify_supported =3D scmi_telemetry_notify_supported, + .set_notify_enabled =3D scmi_telemetry_set_notify_enabled, + .fill_custom_report =3D scmi_telemetry_fill_custom_report, +}; + +static const struct scmi_protocol_events tlm_protocol_events =3D { + .queue_sz =3D SCMI_PROTO_QUEUE_SZ, + .ops =3D &tlm_event_ops, + .evts =3D tlm_events, + .num_events =3D ARRAY_SIZE(tlm_events), + .num_sources =3D 1, +}; + +static int scmi_telemetry_notifier(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct scmi_telemetry_update_report *er =3D data; + struct telemetry_info *ti =3D telemetry_nb_to_info(nb); + + if (er->status) { + dev_err(ti->dev, "Bad Telemetry update notification - ret: %dn", + er->status); + return NOTIFY_DONE; + } + + /* Lookup the embedded DEs in the notification payload ... */ + if (er->num_dwords) + scmi_telemetry_msg_payld_process(ti, er->num_dwords, + er->dwords, er->timestamp); + + /* ...scan the SHMTI/FCs for any other DE updates. */ + if (ti->streaming_mode) + scmi_telemetry_scan_update(ti, er->timestamp); + + return NOTIFY_OK; +} + +static int scmi_telemetry_protocol_init(const struct scmi_protocol_handle = *ph) +{ + struct telemetry_info *ti; + u32 version; + int ret; + + ret =3D ph->xops->version_get(ph, &version); + if (ret) + return ret; + + dev_dbg(ph->dev, "Telemetry Version %d.%d\n", + PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version)); + + ti =3D devm_kzalloc(ph->dev, sizeof(*ti), GFP_KERNEL); + if (!ti) + return -ENOMEM; + + ti->dev =3D ph->dev; + xa_init(&ti->xa_bts); + + ret =3D scmi_telemetry_protocol_attributes_get(ph, ti); + if (ret) + return ret; + + ret =3D scmi_telemetry_enumerate_de(ph, ti); + if (ret) + return ret; + + ret =3D scmi_telemetry_enumerate_update_intervals(ph, ti); + if (ret) + return ret; + + ret =3D scmi_telemetry_enumerate_shmti(ph, ti); + if (ret) + return ret; + + ti->info.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 59527193d6dd..6c6db95d0089 100644 --- a/include/linux/scmi_protocol.h +++ b/include/linux/scmi_protocol.h @@ -2,7 +2,7 @@ /* * SCMI Message Protocol driver header * - * Copyright (C) 2018-2021 ARM Ltd. + * Copyright (C) 2018-2025 ARM Ltd. */ =20 #ifndef _LINUX_SCMI_PROTOCOL_H @@ -13,6 +13,9 @@ #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 +823,176 @@ 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) \ + ({ \ + int __signed_exp =3D FIELD_GET(GENMASK(4, 0), (x)); \ + \ + if (__signed_exp & BIT(4)) \ + __signed_exp |=3D GENMASK(31, 5); \ + __signed_exp; \ + }) + +#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; + size_t des_str_sz; + 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_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; + struct scmi_tlm_de_info *des_store; + struct scmi_telemetry_de **des; + struct scmi_tlm_grp_info *grps_store; + struct scmi_telemetry_group *groups; + 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_info_get: get a specific DE information descriptor from the DE id. + * @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_tlm_de_info __must_check *(*de_info_get) + (const struct scmi_protocol_handle *ph, u32 id); + 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 +1099,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 +1201,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 +1289,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.51.0 From nobody Wed Oct 1 23:35:40 2025 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 22161244671; Thu, 25 Sep 2025 20:36:39 +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=1758832604; cv=none; b=nyCnDKCbRnlJieShJ3rFVHp4CHk+R+Ruf0flsBOCWvaLilVkNDse68Va7zNwAG98DW0xe8GUXGJbeMykAuepVd6J5iaaOeAr9Sv3PVrOyW+tS3Z4o0K+4ILXtKz2QeGLvURYVCYRdAK5argKnRoX0dY6JM+9F6Zch5Pn0xQIhiA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758832604; c=relaxed/simple; bh=w/5+NBFblRZAJMBL6+0s5P24iFBjWfwZcAMTUFnRiUE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=JPP+lPodIZ58GYPjzrXSBP2tVV1Uez0Pzg/ycwijfO2Ke3rj+mS4vU7LdE5PkdnvC2hSPHJDdrkR8aZBP4mgZQQYvDRld2GIY/ECtf97SDF8i9KXgnMyp2TjheH+7RWooEEYWX4bf/Ctmlt6tMrI7ML6vtdd/CqIwSu418s1Czw= 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 505781692; Thu, 25 Sep 2025 13:36:31 -0700 (PDT) Received: from pluto.fritz.box (usa-sjc-mx-foss1.foss.arm.com [172.31.20.19]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id B3F3C3F694; Thu, 25 Sep 2025 13:36:36 -0700 (PDT) From: Cristian Marussi To: linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, arm-scmi@vger.kernel.org Cc: sudeep.holla@arm.com, james.quinlan@broadcom.com, f.fainelli@gmail.com, vincent.guittot@linaro.org, etienne.carriere@st.com, peng.fan@oss.nxp.com, michal.simek@amd.com, quic_sibis@quicinc.com, dan.carpenter@linaro.org, d-gole@ti.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH 06/10] firmware: arm_scmi: Add System Telemetry driver Date: Thu, 25 Sep 2025 21:35:50 +0100 Message-ID: <20250925203554.482371-7-cristian.marussi@arm.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20250925203554.482371-1-cristian.marussi@arm.com> References: <20250925203554.482371-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 --- drivers/firmware/arm_scmi/Kconfig | 10 + drivers/firmware/arm_scmi/Makefile | 1 + .../firmware/arm_scmi/scmi_system_telemetry.c | 1364 +++++++++++++++++ 3 files changed, 1375 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..2fec465b0f33 --- /dev/null +++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c @@ -0,0 +1,1364 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SCMI - System Telemetry Driver + * + * Copyright (C) 2025 ARM Ltd. + */ + +#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 +#define MAX_PROP_PER_DE 12 + +static DEFINE_MUTEX(scmi_tlm_mtx); +static struct super_block *scmi_tlm_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)(const 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 + * @ops: A reference to the protocol ops + * @ph: A reference to the protocol handle to be used with the ops + */ +struct scmi_tlm_setup { + struct device *dev; + struct scmi_protocol_handle *ph; + 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; + const int flags; +#define TLM_IS_STATE BIT(0) +#define TLM_IS_GROUP BIT(1) +#define IS_STATE(_f) ((_f) & TLM_IS_STATE) +#define IS_GROUP(_f) ((_f) & TLM_IS_GROUP) + 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. + * @parent: A reference to the parent inode if any. + * @dentry: A reference to the dentry associated to this inode. + * @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 calss @scmi_tlm_class: all of these created descriptors are s= tored + * then in a root-to-leaves order at probe time, so that at mount time the= y can + * be used to build the needed filesystem entries in the proper order maki= ng use + * of the embeddded @vfs_inode. + */ +struct scmi_tlm_inode { + const 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 scmi_tlm_inode *parent; + struct dentry *dentry; + struct inode vfs_inode; +}; + +#define to_tlm_inode(t) container_of(t, struct scmi_tlm_inode, vfs_inode) + +#define TLM_INODE_SETUP(_ti, _tsp, _cls, _parent, _ptr) \ +({ \ + typeof(_ti) _t =3D _ti; \ + struct scmi_tlm_inode *_ino; \ + \ + if (_t->num_nodes >=3D _t->max_nodes) \ + return -ENOSPC; \ + \ + _ino =3D scmi_tlm_inode_create(_tsp, _cls, _parent, _ptr);\ + if (!_ino) \ + return -ENOMEM; \ + \ + _t->all_nodes[_t->num_nodes++] =3D _ino; \ + \ + _ino; \ +}) + +#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. + * @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. + * @tsp: A reference to the SCMI instance data. + * @top_cls: A class to represent the top node behaviour. + * @top_inode: A reference to the inode at the top of this instance tree. + * @max_nodes: Maximum number of entries that can be hold in @all_nodes. + * @num_nodes: Number of nodes effectively initialized in @all_nodes + * @all_nodes: An array to keep track of all the initialized TLM nodes that + * have been created as a result of the usual probe time SCMI + * enumeration process. + * @info: A handy reference to this instance SCMI Telemetry info data. + * + * The most notable field in this structure is the @all_nodes array, which + * keeps tracks of all of the nodes that has been initialized at probe tim= e, + * one for each SCMI Telemetry discovered entity disposed in a strict + * parent-child order: this way at mount time this array can be scanned in= its + * natural order and each contained inode is initialized and plugged into = the + * SCMI Telemetry filesystem tree. + */ +struct scmi_tlm_instance { + int id; + char name[MAX_INST_NAME]; + struct list_head node; + struct scmi_tlm_setup *tsp; + struct scmi_tlm_class top_cls; + struct scmi_tlm_inode *top_inode; + int max_nodes; + int num_nodes; + struct scmi_tlm_inode **all_nodes; + 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 inline int +__scmi_tlm_generic_open(struct inode *ino, struct file *filp, + int (*bulk_op)(const 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 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)); + const struct scmi_telemetry_info *info =3D tlmi->priv; + const 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 { + for (int i =3D 0; i < info->base.num_des; i++) { + const struct scmi_telemetry_de *de =3D info->des[i]; + + ret =3D tsp->ops->state_set(tsp->ph, false, de->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)); + const 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)); + const struct scmi_tlm_setup *tsp =3D tlmi->tsp; + 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 *kbuf, *p, *token; + + kbuf =3D memdup_user_nul(buf, count); + if (IS_ERR(kbuf)) + return PTR_ERR(kbuf); + + p =3D kbuf; + token =3D strsep(&p, " "); + if (!token) { + /* At least one token must exist to be a valid input */ + ret =3D -EINVAL; + goto err; + } + + ret =3D kstrtouint(token, 0, &secs); + if (ret) + goto err; + + token =3D strsep(&p, " "); + if (token) { + ret =3D kstrtoint(token, 0, &exp); + if (ret) + goto err; + } + + kfree(kbuf); + + 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; + +err: + kfree(kbuf); + return ret; +} + +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)); + const 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; + const 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; + + 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; + + 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, 0600, &all_des_fops, NULL), + TLM_ANON_CLASS("all_des_tstamp_enable", 0, 0600, &all_des_fops, NULL), + TLM_ANON_CLASS("current_update_interval_ms", 0, 0600, ¤t_interval_f= ops, NULL), + TLM_ANON_CLASS("intervals_discrete", 0, 0400, &intrv_discrete_fops, NULL), + TLM_ANON_CLASS("available_update_intervals_ms", 0, 0400, + &available_interv_fops, NULL), + TLM_ANON_CLASS("de_implementation_version", 0, 0400, &de_impl_vers_fops, = NULL), + TLM_ANON_CLASS("tlm_enable", 0, 0600, &tlm_enable_fops, NULL), + TLM_ANON_CLASS(NULL, 0, 0, NULL, NULL), +}; + +DEFINE_TLM_CLASS(reset_tlmo, "reset", 0, 0200, &reset_fops, NULL); + +static const struct inode_operations tlm_dir_inode_ops =3D { + .lookup =3D simple_lookup, +}; + +static const struct inode_operations tlm_file_inode_ops =3D { }; + +DEFINE_TLM_CLASS(des_dir_cls, "des", 0, 0700, NULL, &tlm_dir_inode_ops); +DEFINE_TLM_CLASS(name_tlmo, "name", 0, 0400, &string_ro_fops, NULL); +DEFINE_TLM_CLASS(ena_tlmo, "enable", TLM_IS_STATE, 0600, &obj_enable_fops,= NULL); +DEFINE_TLM_CLASS(tstamp_ena_tlmo, "tstamp_enable", 0, 0600, &obj_enable_fo= ps, NULL); +DEFINE_TLM_CLASS(type_tlmo, "type", 0, 0400, &sa_u32_ro_fops, NULL); +DEFINE_TLM_CLASS(unit_tlmo, "unit", 0, 0400, &sa_u32_ro_fops, NULL); +DEFINE_TLM_CLASS(unit_exp_tlmo, "unit_exp", 0, 0400, &sa_s32_ro_fops, NULL= ); +DEFINE_TLM_CLASS(instance_id_tlmo, "instance_id", 0, 0400, &sa_u32_ro_fops= , NULL); +DEFINE_TLM_CLASS(compo_type_tlmo, "compo_type", 0, 0400, &sa_u32_ro_fops, = NULL); +DEFINE_TLM_CLASS(compo_inst_id_tlmo, "compo_instance_id", 0, 0400, &sa_u32= _ro_fops, NULL); +DEFINE_TLM_CLASS(tstamp_exp_tlmo, "tstamp_exp", 0, 0400, &sa_s32_ro_fops, = NULL); +DEFINE_TLM_CLASS(persistent_tlmo, "persistent", 0, 0400, &sa_u32_ro_fops, = NULL); +DEFINE_TLM_CLASS(value_tlmo, "value", 0, 0400, &de_read_fops, NULL); + +static struct scmi_tlm_inode * +scmi_tlm_inode_create(const struct scmi_tlm_setup *tsp, + const struct scmi_tlm_class *cls, + struct scmi_tlm_inode *parent, const void *priv) +{ + struct scmi_tlm_inode *tlmi; + + tlmi =3D devm_kzalloc(tsp->dev, sizeof(*tlmi), GFP_KERNEL); + if (!tlmi) + return NULL; + + tlmi->cls =3D cls; + tlmi->parent =3D parent; + tlmi->tsp =3D tsp; + tlmi->priv =3D priv; + + return tlmi; +} + +static int scmi_telemetry_des_initialize(struct device *dev, + struct scmi_tlm_instance *ti) +{ + struct scmi_tlm_setup *tsp =3D ti->tsp; + struct scmi_tlm_inode *des_top_inode; + + des_top_inode =3D TLM_INODE_SETUP(ti, tsp, &des_dir_cls, NULL, NULL); + + for (int i =3D 0; i < ti->info->base.num_des; i++) { + const struct scmi_telemetry_de *de =3D ti->info->des[i]; + struct scmi_tlm_de_info *dei =3D de->info; + struct scmi_tlm_inode *de_dir_inode; + struct scmi_tlm_class *de_tlm_cls; + + de_tlm_cls =3D devm_kzalloc(tsp->dev, sizeof(*de_tlm_cls), GFP_KERNEL); + if (!de_tlm_cls) + return -ENOMEM; + + de_tlm_cls->name =3D devm_kasprintf(dev, GFP_KERNEL, "0x%04X", dei->id); + if (!de_tlm_cls->name) + return -ENOMEM; + + de_tlm_cls->mode =3D 0700; + de_tlm_cls->i_op =3D &tlm_dir_inode_ops; + de_dir_inode =3D TLM_INODE_SETUP(ti, tsp, de_tlm_cls, des_top_inode, de); + + if (de->name_support) + TLM_INODE_SETUP(ti, tsp, &name_tlmo, de_dir_inode, dei->name); + + TLM_INODE_SETUP(ti, tsp, &ena_tlmo, de_dir_inode, de); + if (de->tstamp_support) { + TLM_INODE_SETUP(ti, tsp, &tstamp_ena_tlmo, de_dir_inode, de); + TLM_INODE_SETUP(ti, tsp, &tstamp_exp_tlmo, de_dir_inode, + &dei->tstamp_exp); + } + + TLM_INODE_SETUP(ti, tsp, &type_tlmo, de_dir_inode, &dei->type); + TLM_INODE_SETUP(ti, tsp, &unit_tlmo, de_dir_inode, &dei->unit); + TLM_INODE_SETUP(ti, tsp, &unit_exp_tlmo, de_dir_inode, + &dei->unit_exp); + TLM_INODE_SETUP(ti, tsp, &instance_id_tlmo, de_dir_inode, + &dei->instance_id); + TLM_INODE_SETUP(ti, tsp, &compo_type_tlmo, de_dir_inode, + &dei->compo_type); + TLM_INODE_SETUP(ti, tsp, &compo_inst_id_tlmo, de_dir_inode, + &dei->compo_instance_id); + TLM_INODE_SETUP(ti, tsp, &persistent_tlmo, de_dir_inode, + &dei->persistent); + + TLM_INODE_SETUP(ti, tsp, &value_tlmo, de_dir_inode, de); + } + + dev_info(dev, "Found %d Telemetry DE resources.\n", ti->info->base.num_de= s); + + return 0; +} + +DEFINE_TLM_CLASS(version_tlmo, "version", 0, 0400, &sa_x32_ro_fops, NULL); + +static int scmi_tlm_bulk_on_demand(const 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%04X %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) +{ + const 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(samples); + kfree(tp->buf); + return ret; + } + + 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, 0400, &scmi_tlm_data_fops,= NULL); + +static int scmi_tlm_bulk_single_read(const 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); +} + +/* TODO + * Could be better with a .poll fops since single sample + * reads trigger an asynchronous request. + */ +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, 0400, + &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, 0600, &obj_enable_f= ops, NULL), + TLM_ANON_CLASS("tstamp_enable", TLM_IS_GROUP, 0600, &obj_enable_fops, NUL= L), + TLM_ANON_CLASS(NULL, 0, 0, NULL, NULL), +}; + +DEFINE_TLM_CLASS(grp_data_tlmo, "des_bulk_read", TLM_IS_GROUP, 0400, + &scmi_tlm_data_fops, NULL); + +DEFINE_TLM_CLASS(groups_dir_cls, "groups", 0, 0700, NULL, &tlm_dir_inode_o= ps); + +DEFINE_TLM_CLASS(grp_single_sample_tlmo, "des_single_sample_read", TLM_IS_= GROUP, + 0400, &scmi_tlm_single_sample_fops, NULL); + +DEFINE_TLM_CLASS(grp_composing_des_tlmo, "composing_des", TLM_IS_GROUP, 04= 00, + &string_ro_fops, NULL); + +DEFINE_TLM_CLASS(grp_current_interval_tlmo, "current_update_interval_ms", + TLM_IS_GROUP, 0600, ¤t_interval_fops, NULL); + +DEFINE_TLM_CLASS(grp_available_interval_tlmo, "available_update_intervals_= ms", + TLM_IS_GROUP, 0400, &available_interv_fops, NULL); + +DEFINE_TLM_CLASS(grp_intervals_discrete_tlmo, "intervals_discrete", + TLM_IS_GROUP, 0400, &intrv_discrete_fops, NULL); + +static int scmi_telemetry_groups_initialize(struct device *dev, + struct scmi_tlm_instance *ti) +{ + struct scmi_tlm_setup *tsp =3D ti->tsp; + struct scmi_tlm_inode *groups_top_inode; + + if (ti->info->base.num_groups =3D=3D 0) + return 0; + + groups_top_inode =3D TLM_INODE_SETUP(ti, tsp, &groups_dir_cls, NULL, NULL= ); + + for (int i =3D 0; i < ti->info->base.num_groups; i++) { + const struct scmi_telemetry_group *grp =3D &ti->info->groups[i]; + struct scmi_tlm_class *grp_tlm_cls; + struct scmi_tlm_inode *grp_dir_inode; + + grp_tlm_cls =3D devm_kzalloc(tsp->dev, sizeof(*grp_tlm_cls), GFP_KERNEL); + if (!grp_tlm_cls) + return -ENOMEM; + + grp_tlm_cls->name =3D devm_kasprintf(dev, GFP_KERNEL, "%u", grp->info->i= d); + if (!grp_tlm_cls->name) + return -ENOMEM; + + grp_tlm_cls->mode =3D 0700; + grp_tlm_cls->i_op =3D &tlm_dir_inode_ops; + + grp_dir_inode =3D TLM_INODE_SETUP(ti, tsp, grp_tlm_cls, + groups_top_inode, grp); + + for (const struct scmi_tlm_class *gto =3D tlm_grps; gto->name; gto++) + TLM_INODE_SETUP(ti, tsp, gto, grp_dir_inode, grp); + + TLM_INODE_SETUP(ti, tsp, &grp_composing_des_tlmo, grp_dir_inode, + grp->des_str); + + TLM_INODE_SETUP(ti, tsp, &grp_data_tlmo, grp_dir_inode, grp); + TLM_INODE_SETUP(ti, tsp, &grp_single_sample_tlmo, grp_dir_inode, grp); + + if (ti->info->per_group_config_support) { + TLM_INODE_SETUP(ti, tsp, &grp_current_interval_tlmo, + grp_dir_inode, grp); + TLM_INODE_SETUP(ti, tsp, &grp_available_interval_tlmo, + grp_dir_inode, grp); + TLM_INODE_SETUP(ti, tsp, &grp_intervals_discrete_tlmo, + grp_dir_inode, grp); + } + } + + dev_info(dev, "Found %d Telemetry GROUPS resources.\n", + ti->info->base.num_groups); + + return 0; +} + +static int scmi_tlm_root_instance_initialize(struct device *dev, + struct scmi_tlm_instance *ti) +{ + struct scmi_tlm_setup *tsp =3D ti->tsp; + + /* + * Allocate space for all possible nodes, i.e. in order: + * - top level nodes + * - all DE subdirs contained in des/ + * - all DE proeperties files inside each 0xNNNN/ DE subdir + * - all GRPS subdirs contained in groups/ + * - all GRPS proeperties files inside each / GRP subdir + */ + ti->max_nodes =3D TOP_NODES_NUM + ti->info->base.num_des + + NODES_PER_DE_NUM * ti->info->base.num_des + + ti->info->base.num_groups + + NODES_PER_GRP_NUM * ti->info->base.num_groups; + ti->all_nodes =3D devm_kcalloc(tsp->dev, ti->max_nodes, + sizeof(*ti->all_nodes), GFP_KERNEL); + if (!ti->all_nodes) + return -ENOMEM; + + 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 0755; + /* + * Do NOT register the top_node root in all_nodes[] since it is + * treated differently at mount time + */ + ti->top_inode =3D scmi_tlm_inode_create(tsp, &ti->top_cls, NULL, NULL); + + for (const struct scmi_tlm_class *tlmo =3D tlm_tops; tlmo->name; tlmo++) + TLM_INODE_SETUP(ti, tsp, tlmo, NULL, ti->info); + + if (ti->info->reset_support) + TLM_INODE_SETUP(ti, tsp, &reset_tlmo, NULL, NULL); + + TLM_INODE_SETUP(ti, tsp, &version_tlmo, NULL, &ti->info->base.version); + TLM_INODE_SETUP(ti, tsp, &data_tlmo, NULL, ti->info); + TLM_INODE_SETUP(ti, tsp, &single_sample_tlmo, NULL, ti->info); + + 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; + int ret; + + 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) { + dev_err(dev, "invalid Telemetry info !\n"); + return ERR_PTR(-EINVAL); + } + + ti->id =3D instance_id; + ti->tsp =3D tsp; + + ret =3D scmi_tlm_root_instance_initialize(dev, ti); + if (ret) + return ERR_PTR(ret); + + ret =3D scmi_telemetry_des_initialize(dev, ti); + if (ret) + return ERR_PTR(ret); + + ret =3D scmi_telemetry_groups_initialize(dev, ti); + if (ret) + return ERR_PTR(ret); + + 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; + 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(&scmi_tlm_mtx); + list_add(&ti->node, &scmi_telemetry_instances); + if (scmi_tlm_sb) { + int ret; + + /* + * If the file system was already mounted by the time this + * instance was probed, register explicitly, since the list + * has been scanned already. + */ + mutex_unlock(&scmi_tlm_mtx); + ret =3D scmi_telemetry_instance_register(scmi_tlm_sb, ti); + if (ret) + return ret; + mutex_lock(&scmi_tlm_mtx); + } + mutex_unlock(&scmi_tlm_mtx); + + dev_set_drvdata(&sdev->dev, ti); + + return 0; +} + +static void scmi_telemetry_remove(struct scmi_device *sdev) +{ + struct device *dev =3D &sdev->dev; + struct scmi_tlm_instance *ti; + bool enabled =3D false; + int ret; + + ti =3D dev_get_drvdata(&sdev->dev); + + /* Stop SCMI Telemetry collection */ + ret =3D ti->tsp->ops->collection_configure(ti->tsp->ph, + SCMI_TLM_GRP_INVALID, true, + &enabled, NULL, NULL); + if (ret) + dev_warn(dev, "Failed to stop Telemetry collection\n"); + + list_del(&ti->node); +} + +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, + .remove =3D scmi_telemetry_remove, + .id_table =3D scmi_id_table, +}; + +static const struct super_operations tlm_sops =3D { + .statfs =3D simple_statfs, + .drop_inode =3D generic_delete_inode, +}; + +static struct inode * +scmi_tlm_inode_initialize(struct super_block *sb, umode_t mode, + struct scmi_tlm_inode *tlmi) +{ + struct inode *inode; + + if (!tlmi) + return NULL; + + inode =3D &tlmi->vfs_inode; + if (unlikely(inode_init_always(sb, inode))) + return NULL; + + inode_sb_list_add(inode); + inode->i_ino =3D get_next_ino(); + inode_init_owner(&nop_mnt_idmap, inode, NULL, mode); + simple_inode_init_ts(inode); + + if (S_ISDIR(mode)) { + inode->i_op =3D &tlm_dir_inode_ops; + inode->i_fop =3D &simple_dir_operations; + } else if (S_ISREG(mode)) { + inode->i_op =3D &tlm_file_inode_ops; + inode->i_fop =3D tlmi ? tlmi->cls->f_op : NULL; + } + + inode->i_private =3D (void *)tlmi->priv; + + return inode; +} + +static struct dentry * +scmi_tlm_node_add(struct super_block *sb, struct dentry *parent, + const char *name, umode_t mode, struct scmi_tlm_inode *tlmi) +{ + struct inode *ino; + struct dentry *dentry; + + ino =3D scmi_tlm_inode_initialize(sb, mode, tlmi); + if (!ino) + return ERR_PTR(-ENOMEM); + + dentry =3D d_alloc_name(parent, name); + if (!dentry) + return ERR_PTR(-ENOMEM); + + tlmi->dentry =3D dentry; + d_add(dentry, ino); + + return dentry; +} + +static int scmi_telemetry_instance_register(struct super_block *sb, + struct scmi_tlm_instance *ti) +{ + struct dentry *top; + + /* AT first create instance top dir ... */ + top =3D scmi_tlm_node_add(sb, sb->s_root, ti->top_cls.name, + S_IFDIR | ti->top_cls.mode, ti->top_inode); + if (IS_ERR(top)) + return PTR_ERR(top); + + /* + * Scan the array of tlm_inode pre-initialized with SCMI Telemetry + * discovered entities and map it into the filesystem . + */ + for (int i =3D 0; i < ti->num_nodes; i++) { + struct scmi_tlm_inode *tlmi =3D ti->all_nodes[i]; + struct dentry *dentry, *parent; + umode_t mode; + + /* Check sanity of node tree */ + if (WARN_ON_ONCE(!tlmi)) + continue; + + parent =3D !tlmi->parent ? top : tlmi->parent->dentry; + mode =3D (tlmi->cls->f_op ? S_IFREG : S_IFDIR) | tlmi->cls->mode; + dentry =3D scmi_tlm_node_add(sb, parent, tlmi->cls->name, mode, tlmi); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + } + + return 0; +} + +static struct scmi_tlm_inode root_tlm; + +static int tlm_fill_super(struct super_block *sb, void *data, int silent) +{ + struct inode *root_inode; + struct dentry *root_dentry; + struct scmi_tlm_instance *ti; + + sb->s_magic =3D TLM_FS_MAGIC; + sb->s_op =3D &tlm_sops; + + /* create root inode (directory) */ + root_inode =3D scmi_tlm_inode_initialize(sb, S_IFDIR | 0755, &root_tlm); + if (!root_inode) + return -ENOMEM; + + root_dentry =3D d_make_root(root_inode); + if (!root_dentry) + return -ENOMEM; + + sb->s_root =3D root_dentry; + list_for_each_entry(ti, &scmi_telemetry_instances, node) { + int ret; + + ret =3D scmi_telemetry_instance_register(sb, ti); + if (ret) + return ret; + } + + guard(mutex)(&scmi_tlm_mtx); + if (!scmi_tlm_sb) + scmi_tlm_sb =3D sb; + + return 0; +} + +static struct dentry *tlm_mount(struct file_system_type *fs_type, int flag= s, + const char *dev_name, void *data) +{ + return mount_nodev(fs_type, flags, data, tlm_fill_super); +} + +static void tlm_kill_sb(struct super_block *sb) +{ + kill_litter_super(sb); +} + +//XXX Move to new fs_context-based MOUNT process !!! (see debugfs_parse_pa= ram) +static struct file_system_type scmi_telemetry_fs =3D { + .owner =3D THIS_MODULE, + .name =3D TLM_FS_NAME, + .mount =3D tlm_mount, + .kill_sb =3D tlm_kill_sb, + .fs_flags =3D 0, +}; + +static int __init scmi_telemetry_init(void) +{ + int ret; + + ret =3D scmi_register(&scmi_telemetry_driver); + if (ret) + return ret; + + ret =3D sysfs_create_mount_point(fs_kobj, TLM_FS_MNT); + if (ret && ret !=3D -EEXIST) { + scmi_unregister(&scmi_telemetry_driver); + return ret; + } + + ret =3D register_filesystem(&scmi_telemetry_fs); + if (ret) { + sysfs_remove_mount_point(fs_kobj, TLM_FS_MNT); + scmi_unregister(&scmi_telemetry_driver); + } + + return ret; +} +module_init(scmi_telemetry_init); + +static void __exit scmi_telemetry_exit(void) +{ + int ret; + + ret =3D unregister_filesystem(&scmi_telemetry_fs); + if (!ret) + sysfs_remove_mount_point(fs_kobj, TLM_FS_MNT); + else + pr_err("Failed to unregister %s\n", TLM_FS_NAME); + + scmi_unregister(&scmi_telemetry_driver); +} +module_exit(scmi_telemetry_exit); + +MODULE_AUTHOR("Cristian Marussi "); +MODULE_DESCRIPTION("ARM SCMI Telemetry Driver"); +MODULE_LICENSE("GPL"); --=20 2.51.0 From nobody Wed Oct 1 23:35:40 2025 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 325103164AA; Thu, 25 Sep 2025 20:36:42 +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=1758832605; cv=none; b=RSgY7MW7seFmDe+MCkXgPCMDpr91EkK454fkpbO5DNXxwLXJwgdzdLamuaouygy5AFizyF+Xyjd4wcITeLgw4tTPizOE8ToqdIV7db9AXqqyEewg9x9MjzpoY3fEfpHHqRAm27CxfkVCFjP+jdDdf6MGfUXFiB6pWD+I/s0BaIw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758832605; c=relaxed/simple; bh=MXI05lzB2MxvNIXy4Yb0bT1lPRjp0/t3s80/eNONFew=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=BchOPonA0KaPb+e6xIqIP02QoyCZiqHEYQcUzSZY6SX/RsPDJqmM5vr+SecSI2iQHbrlNkbdL/uuJO/dMJjY23N5R93hd4DFHG+h5atmNaZVY0Xn9utkv84a3BwSlCcrDuoVx7oNUbYwZYiyqTYOp3cE/b/X+rgnnsuFc7pxd7Q= 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 727F31C2B; Thu, 25 Sep 2025 13:36:34 -0700 (PDT) Received: from pluto.fritz.box (usa-sjc-mx-foss1.foss.arm.com [172.31.20.19]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id BC06B3F694; Thu, 25 Sep 2025 13:36:39 -0700 (PDT) From: Cristian Marussi To: linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, arm-scmi@vger.kernel.org Cc: sudeep.holla@arm.com, james.quinlan@broadcom.com, f.fainelli@gmail.com, vincent.guittot@linaro.org, etienne.carriere@st.com, peng.fan@oss.nxp.com, michal.simek@amd.com, quic_sibis@quicinc.com, dan.carpenter@linaro.org, d-gole@ti.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH 07/10] firmware: arm_scmi: Add System Telemetry ioctls support Date: Thu, 25 Sep 2025 21:35:51 +0100 Message-ID: <20250925203554.482371-8-cristian.marussi@arm.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20250925203554.482371-1-cristian.marussi@arm.com> References: <20250925203554.482371-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' file that can be used to configure and retrieve SCMI Telemetry data in binary form using the alternative ioctls-based ABI described in uapi/linux/scmi.h. Signed-off-by: Cristian Marussi --- .../firmware/arm_scmi/scmi_system_telemetry.c | 402 ++++++++++++++++++ 1 file changed, 402 insertions(+) diff --git a/drivers/firmware/arm_scmi/scmi_system_telemetry.c b/drivers/fi= rmware/arm_scmi/scmi_system_telemetry.c index 2fec465b0f33..f591aad10302 100644 --- a/drivers/firmware/arm_scmi/scmi_system_telemetry.c +++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c @@ -21,6 +21,8 @@ #include #include =20 +#include + #define TLM_FS_MAGIC 0x75C01C80 #define TLM_FS_NAME "stlmfs" #define TLM_FS_MNT "arm_telemetry" @@ -953,6 +955,404 @@ DEFINE_TLM_CLASS(grp_available_interval_tlmo, "availa= ble_update_intervals_ms", DEFINE_TLM_CLASS(grp_intervals_discrete_tlmo, "intervals_discrete", TLM_IS_GROUP, 0400, &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_info *info =3D tlmi->priv; + const 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; + + if (!all) + return tsp->ops->state_set(tsp->ph, false, tcfg.id, + (bool *)&tcfg.enable, + (bool *)&tcfg.t_enable); + + for (int i =3D 0; i < info->base.num_des; i++) { + const struct scmi_telemetry_de *de =3D info->des[i]; + + ret =3D tsp->ops->state_set(tsp->ph, false, de->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 l= ong arg) +{ + const 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) +{ + const 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) +{ + const struct scmi_tlm_setup *tsp =3D tlmi->tsp; + void * __user uptr =3D (void * __user)arg; + const struct scmi_tlm_de_info *de_info; + struct scmi_tlm_de_info dei; + + if (copy_from_user(&dei, uptr, sizeof(dei))) + return -EFAULT; + + de_info =3D tsp->ops->de_info_get(tsp->ph, dei.id); + if (!de_info) + 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) +{ + const struct scmi_telemetry_info *info =3D tlmi->priv; + void * __user uptr =3D (void * __user)arg; + int num_des =3D info->base.num_des; + struct scmi_tlm_des_list dsl; + + if (copy_from_user(&dsl, uptr, sizeof(dsl))) + return -EFAULT; + + if (dsl.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), info->des_store, + info->base.num_des * sizeof(*info->des_store))) + return -EFAULT; + + return 0; +} + +static long +scmi_tlm_de_value_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned lo= ng arg) +{ + const 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; + void * __user uptr =3D (void * __user)arg; + struct scmi_tlm_grp_desc grp_desc; + unsigned int num_des =3D grp->info->num_des; + + 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) +{ + const struct scmi_telemetry_info *info =3D tlmi->priv; + void * __user uptr =3D (void * __user)arg; + int num_grps =3D info->base.num_groups; + struct scmi_tlm_grps_list gsl; + + if (copy_from_user(&gsl, uptr, sizeof(gsl))) + return -EFAULT; + + if (gsl.num_grps < num_grps) + return -EINVAL; + + if (copy_to_user(uptr, &num_grps, sizeof(num_grps))) + return -EFAULT; + + if (copy_to_user(uptr + sizeof(num_grps), info->grps_store, + info->base.num_groups * sizeof(*info->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) +{ + const struct scmi_tlm_setup *tsp =3D tlmi->tsp; + void * __user uptr =3D (void * __user)arg; + struct scmi_tlm_data_read bulk, *bulk_ptr; + int ret, grp_id =3D SCMI_TLM_GRP_INVALID; + + if (copy_from_user(&bulk, uptr, sizeof(bulk))) + return -EFAULT; + + bulk_ptr =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) + goto out; + + if (copy_to_user(uptr, bulk_ptr, sizeof(*bulk_ptr) + + bulk_ptr->num_samples * sizeof(bulk_ptr->samples[0]))) + ret =3D -EFAULT; + +out: + kfree(bulk_ptr); + + return ret; +} + +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, 0600, &scmi_tlm_ctrl_fops, NULL); +DEFINE_TLM_CLASS(grp_ctrl_tlmo, "control", TLM_IS_GROUP, 0600, + &scmi_tlm_ctrl_fops, NULL); + static int scmi_telemetry_groups_initialize(struct device *dev, struct scmi_tlm_instance *ti) { @@ -989,6 +1389,7 @@ static int scmi_telemetry_groups_initialize(struct dev= ice *dev, TLM_INODE_SETUP(ti, tsp, &grp_composing_des_tlmo, grp_dir_inode, grp->des_str); =20 + TLM_INODE_SETUP(ti, tsp, &grp_ctrl_tlmo, grp_dir_inode, grp); TLM_INODE_SETUP(ti, tsp, &grp_data_tlmo, grp_dir_inode, grp); TLM_INODE_SETUP(ti, tsp, &grp_single_sample_tlmo, grp_dir_inode, grp); =20 @@ -1050,6 +1451,7 @@ static int scmi_tlm_root_instance_initialize(struct d= evice *dev, TLM_INODE_SETUP(ti, tsp, &version_tlmo, NULL, &ti->info->base.version); TLM_INODE_SETUP(ti, tsp, &data_tlmo, NULL, ti->info); TLM_INODE_SETUP(ti, tsp, &single_sample_tlmo, NULL, ti->info); + TLM_INODE_SETUP(ti, tsp, &ctrl_tlmo, NULL, ti->info); =20 return 0; } --=20 2.51.0 From nobody Wed Oct 1 23:35:40 2025 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 638533203A7; Thu, 25 Sep 2025 20:36:46 +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=1758832608; cv=none; b=TsiCT8B8OY4AEPP4WCnb8x6v3iqQUsiZ3HfX95y/Q7XWt+lXuvSgd7BL9Z9Zm+DzqRGq0Rtskwa98znmzpqZlDfwtbHrZtpzbT+dRPdPWJ985bhWLbZpYgXLkBo8XexBJPPBWpN/udm5pxkbnpvnxIB+EmtffKzcO3ZuvcaorWg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758832608; c=relaxed/simple; bh=eek8QLsSSuq2WvGDze5TSuJPWIpkvuCu3CnxAI2I+t8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=PknNNPE/Nd56nWMmcNKTdKmowDRhcCmc9mtTprNInJNmx6jCtjHWF1FGt1QFoBWlZyYXO0zGXAmwaj1gwyJRMCr7Lmdll0VHCRmw8gGvX6rbyLKzE54ubpchFudae4mFCohVsoMHjURnVWyFXQAPxQO9xRWTplDjdD6Q40m69lA= 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 71FD41692; Thu, 25 Sep 2025 13:36:37 -0700 (PDT) Received: from pluto.fritz.box (usa-sjc-mx-foss1.foss.arm.com [172.31.20.19]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id DC0083F694; Thu, 25 Sep 2025 13:36:42 -0700 (PDT) From: Cristian Marussi To: linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, arm-scmi@vger.kernel.org Cc: sudeep.holla@arm.com, james.quinlan@broadcom.com, f.fainelli@gmail.com, vincent.guittot@linaro.org, etienne.carriere@st.com, peng.fan@oss.nxp.com, michal.simek@amd.com, quic_sibis@quicinc.com, dan.carpenter@linaro.org, d-gole@ti.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH 08/10] firmware: arm_scmi: Add Telemetry components view Date: Thu, 25 Sep 2025 21:35:52 +0100 Message-ID: <20250925203554.482371-9-cristian.marussi@arm.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20250925203554.482371-1-cristian.marussi@arm.com> References: <20250925203554.482371-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 --- .../firmware/arm_scmi/scmi_system_telemetry.c | 730 ++++++++++++++++++ 1 file changed, 730 insertions(+) diff --git a/drivers/firmware/arm_scmi/scmi_system_telemetry.c b/drivers/fi= rmware/arm_scmi/scmi_system_telemetry.c index f591aad10302..a4b6d23b211e 100644 --- a/drivers/firmware/arm_scmi/scmi_system_telemetry.c +++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c @@ -189,6 +189,8 @@ struct scmi_tlm_inode { * @all_nodes: An array to keep track of all the initialized TLM nodes that * have been created as a result of the usual probe time SCMI * enumeration process. + * @des_nodes: An array to use as handy reference only to the set of nodes + * representing the DEs top directories. * @info: A handy reference to this instance SCMI Telemetry info data. * * The most notable field in this structure is the @all_nodes array, which @@ -208,6 +210,7 @@ struct scmi_tlm_instance { int max_nodes; int num_nodes; struct scmi_tlm_inode **all_nodes; + struct scmi_tlm_inode **des_nodes; const struct scmi_telemetry_info *info; }; =20 @@ -216,6 +219,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 inline int __scmi_tlm_generic_open(struct inode *ino, struct file *filp, int (*bulk_op)(const struct scmi_tlm_setup *tsp, @@ -753,6 +1276,10 @@ static int scmi_telemetry_des_initialize(struct devic= e *dev, struct scmi_tlm_inode *des_top_inode; =20 des_top_inode =3D TLM_INODE_SETUP(ti, tsp, &des_dir_cls, NULL, NULL); + ti->des_nodes =3D devm_kcalloc(dev, ti->info->base.num_des, + sizeof(*ti->des_nodes), GFP_KERNEL); + if (!ti->des_nodes) + return -ENOMEM; =20 for (int i =3D 0; i < ti->info->base.num_des; i++) { const struct scmi_telemetry_de *de =3D ti->info->des[i]; @@ -796,6 +1323,8 @@ static int scmi_telemetry_des_initialize(struct device= *dev, &dei->persistent); =20 TLM_INODE_SETUP(ti, tsp, &value_tlmo, de_dir_inode, de); + + ti->des_nodes[i] =3D de_dir_inode; } =20 dev_info(dev, "Found %d Telemetry DE resources.\n", ti->info->base.num_de= s); @@ -1633,10 +2162,204 @@ scmi_tlm_node_add(struct super_block *sb, struct d= entry *parent, return dentry; } =20 +static struct inode *scmi_telemetry_make_dir_inode(struct super_block *sb) +{ + struct inode *inode =3D new_inode(sb); + + if (!inode) + return NULL; + + inode->i_ino =3D get_next_ino(); + inode_init_owner(&nop_mnt_idmap, inode, NULL, S_IFDIR | 0700); + simple_inode_init_ts(inode); + + inode->i_op =3D &tlm_dir_inode_ops; + inode->i_fop =3D &simple_dir_operations; + + return inode; +} + +static struct dentry *scmi_telemetry_topology_dir_alloc(struct super_block= *sb, + struct dentry *parent, + const char *name) +{ + struct dentry *dentry; + struct inode *inode; + + inode =3D scmi_telemetry_make_dir_inode(sb); + if (!inode) + return ERR_PTR(-ENOMEM); + + dentry =3D d_alloc_name(parent, name); + if (!dentry) + return ERR_PTR(-ENOMEM); + + d_add(dentry, inode); + + dget(dentry); + + return dentry; +} + +static struct scmi_tlm_inode * +scmi_telemetry_de_subdir_clone(struct super_block *sb, + struct scmi_tlm_inode *tlmi, + struct dentry *parent) +{ + struct device *dev =3D tlmi->tsp->dev; + struct scmi_tlm_inode *dtlmi; + struct dentry *twin, *child; + struct qstr qstr; + + /* + * NOTE THAT I CANNOT HARD-LINK A DIRECTORY ... BREAKS VFS + * ...so we just duplicate the tlm_inode here + */ + dtlmi =3D devm_kmemdup(dev, tlmi, sizeof(*tlmi), GFP_KERNEL); + if (!dtlmi) + return ERR_PTR(-ENOMEM); + + qstr.name =3D dtlmi->cls->name; + qstr.len =3D strlen(dtlmi->cls->name); + qstr.hash =3D full_name_hash(parent, qstr.name, qstr.len); + + /* by_compo_type//// */ + twin =3D d_lookup(parent, &qstr); + if (twin) { + dev_err(dev, "Found duplicated entry '%s' in topology.\n", + dtlmi->cls->name); + return ERR_PTR(-EBUSY); + } + + twin =3D d_alloc_name(parent, dtlmi->cls->name); + if (!twin) + return ERR_PTR(-ENOMEM); + + dtlmi->dentry =3D twin; + dtlmi->parent =3D NULL; + if (!scmi_tlm_inode_initialize(sb, S_IFDIR | dtlmi->cls->mode, dtlmi)) + return ERR_PTR(-ENOMEM); + + /* Add another dentry to the duplicated inode under another parent */ + d_add(twin, &dtlmi->vfs_inode); + + /* Hard-link all child of tlmi to the duplicate */ + spin_lock(&tlmi->dentry->d_parent->d_lock); + hlist_for_each_entry(child, &tlmi->dentry->d_children, d_sib) { + struct dentry *hdl; + + if (!child->d_inode) + continue; + + hdl =3D d_alloc_name(twin, child->d_name.name); + if (!hdl) { + spin_unlock(&tlmi->dentry->d_parent->d_lock); + return ERR_PTR(-ENOMEM); + } + + inode_inc_link_count(child->d_inode); + d_add(hdl, child->d_inode); + } + spin_unlock(&tlmi->dentry->d_parent->d_lock); + + return 0; +} + +static struct dentry * +scmi_telemetry_topology_path_get(struct super_block *sb, struct dentry *pa= rent, + const char *dname) +{ + struct dentry *dentry; + struct qstr qstr; + + qstr.name =3D dname; + qstr.len =3D strlen(dname); + qstr.hash =3D full_name_hash(parent, qstr.name, qstr.len); + + dentry =3D d_lookup(parent, &qstr); + if (!dentry) { + dentry =3D scmi_telemetry_topology_dir_alloc(sb, parent, dname); + if (!dentry) + return ERR_PTR(-ENOMEM); + } + + return dentry; +} + +static int scmi_telemetry_topology_add_node(struct super_block *sb, + struct dentry *top_dentry, + struct scmi_tlm_inode *tlmi) +{ + struct dentry *ctype, *cinst, *cunit, *dinst; + const struct scmi_telemetry_de *de =3D tlmi->priv; + struct scmi_tlm_de_info *dei =3D de->info; + struct scmi_tlm_inode *dtlmi; + char inst_str[32]; + + /* by_compo_type// */ + ctype =3D scmi_telemetry_topology_path_get(sb, top_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, ctype, inst_str); + dput(ctype); + if (!cinst) + return -ENOMEM; + + /* by_compo_type//// */ + cunit =3D scmi_telemetry_topology_path_get(sb, 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, cunit, inst_str); + dput(cunit); + if (!dinst) + return -ENOMEM; + + dtlmi =3D scmi_telemetry_de_subdir_clone(sb, tlmi, dinst); + dput(dinst); + + return PTR_ERR(dtlmi); +} + +static int scmi_telemetry_topology_view_add(struct super_block *sb, + struct scmi_tlm_instance *ti) +{ + struct device *dev =3D ti->tsp->dev; + struct dentry *topo; + + topo =3D scmi_telemetry_topology_dir_alloc(sb, ti->top_inode->dentry, + "components"); + if (!topo) + return -ENOMEM; + + for (int i =3D 0; i < ti->info->base.num_des; i++) { + int ret; + + ret =3D scmi_telemetry_topology_add_node(sb, topo, + ti->des_nodes[i]); + if (ret) + dev_err(dev, "Fail to add node %s to topology. Skip.\n", + ti->des_nodes[i]->cls->name); + } + + dput(topo); + + return 0; +} + static int scmi_telemetry_instance_register(struct super_block *sb, struct scmi_tlm_instance *ti) { struct dentry *top; + int ret; =20 /* AT first create instance top dir ... */ top =3D scmi_tlm_node_add(sb, sb->s_root, ti->top_cls.name, @@ -1664,6 +2387,13 @@ static int scmi_telemetry_instance_register(struct s= uper_block *sb, return PTR_ERR(dentry); } =20 + /* Add an alternative topological view for the DES nodes */ + ret =3D scmi_telemetry_topology_view_add(sb, 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.51.0 From nobody Wed Oct 1 23:35:40 2025 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 12F6C320A05; Thu, 25 Sep 2025 20:36:49 +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=1758832610; cv=none; b=pZEcoRvampinb2kQ9AzKAyxHgXrdljnzprULGchHqafFNBa/LKHrw/3zpbbcCDirzyorIM6S9AJXZRUESdsOpCxuScmpevRdageCnWSKXMAJR0N0XiBsjx1jxa/ltC37a/gG/1eRwMcok/L6prifqRMzIU+OuMZTIccHr2gb6vk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758832610; c=relaxed/simple; bh=W0hDqv4nvaDNZusXF86LC3z7XJ1wjKcULVvLDVyaxLw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=rxGVdmLASnvwxgeDZLBsFd2LkrPgZKqFY2bDbt+BQsAu3NXKkVaVhES2QC/6fZTZAtMzGP1vumKvmGap+YqF5EFjwtqGxyuiEG9irZPU5+3FVcwa5MnJGlXRGLNvRfcRgEuVHjC988RwON+epgCoe81sJfzcPAD8dldTKM0ayPU= 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 9F9E71692; Thu, 25 Sep 2025 13:36:40 -0700 (PDT) Received: from pluto.fritz.box (usa-sjc-mx-foss1.foss.arm.com [172.31.20.19]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id DD7193F694; Thu, 25 Sep 2025 13:36:45 -0700 (PDT) From: Cristian Marussi To: linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, arm-scmi@vger.kernel.org Cc: sudeep.holla@arm.com, james.quinlan@broadcom.com, f.fainelli@gmail.com, vincent.guittot@linaro.org, etienne.carriere@st.com, peng.fan@oss.nxp.com, michal.simek@amd.com, quic_sibis@quicinc.com, dan.carpenter@linaro.org, d-gole@ti.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH 09/10] include: trace: Add Telemetry trace events Date: Thu, 25 Sep 2025 21:35:53 +0100 Message-ID: <20250925203554.482371-10-cristian.marussi@arm.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20250925203554.482371-1-cristian.marussi@arm.com> References: <20250925203554.482371-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.51.0 From nobody Wed Oct 1 23:35:40 2025 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 61B4431E0F0; Thu, 25 Sep 2025 20:36: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=1758832614; cv=none; b=H8x2fhkyCGXslPdNZqADFR2GOGnpcYhrxW1wbW78EYezqxklY7w5IRm2GBoaoaw5uVhVd9fzmtivE3nX8omt6q3VJjZ/YmiLxmUkwIm9/DdG6XH3yLcTENHN8b+BJ8zjfCvVgyv/o1GauM1kdzDBEgpsCuFg0HS3t5IvtLiZIdk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758832614; c=relaxed/simple; bh=Hm2Qd5r+67hzVASCM80xs+J6rdkp6prOWAVTiQnZvME=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=NKaxN2Zec5dQta5Qe4C3F1y75QWh4yCkMoIPeVqTyWwar7xR/4Q/3QSiyy3WM/yI0R4iKxq7jd1N1QuHr+jU3xuv2BUYDZMp7LCOz0qAWbn8RymMScTJBNJr0QpILjPaDmA8VHaeCK1D5ihFAE3GyplD0oaaUnDrOAEdO5efXD0= 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 A99861692; Thu, 25 Sep 2025 13:36:43 -0700 (PDT) Received: from pluto.fritz.box (usa-sjc-mx-foss1.foss.arm.com [172.31.20.19]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id 15BFA3F694; Thu, 25 Sep 2025 13:36:48 -0700 (PDT) From: Cristian Marussi To: linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, arm-scmi@vger.kernel.org Cc: sudeep.holla@arm.com, james.quinlan@broadcom.com, f.fainelli@gmail.com, vincent.guittot@linaro.org, etienne.carriere@st.com, peng.fan@oss.nxp.com, michal.simek@amd.com, quic_sibis@quicinc.com, dan.carpenter@linaro.org, d-gole@ti.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH 10/10] firmware: arm_scmi: Use new Telemetry traces Date: Thu, 25 Sep 2025 21:35:54 +0100 Message-ID: <20250925203554.482371-11-cristian.marussi@arm.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20250925203554.482371-1-cristian.marussi@arm.com> References: <20250925203554.482371-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 f03000c173c2..aa706518e5b6 100644 --- a/drivers/firmware/arm_scmi/telemetry.c +++ b/drivers/firmware/arm_scmi/telemetry.c @@ -23,6 +23,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 @@ -1021,8 +1023,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->dev, &ti->xa_bts, payld); @@ -1031,6 +1035,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, @@ -1046,8 +1053,10 @@ static void scmi_telemetry_tdcf_data_parse(struct te= lemetry_info *ti, =20 id =3D PAYLD_ID(payld); de =3D xa_load(&ti->xa_des, id); - if (!de) + if (!de) { + trace_scmi_tlm_access(id, "DE_INVALID", 0, 0); return; + } =20 tde =3D to_tde(de); /* Update DE location refs if requested: normally done only on enable */ @@ -1094,6 +1103,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, de->info->id, tde->last_val, "SHMTI_DE_UPDT"); } =20 static int scmi_telemetry_tdcf_line_parse(struct telemetry_info *ti, @@ -1139,8 +1150,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; @@ -1152,18 +1165,25 @@ static int scmi_telemetry_shmti_scan(struct telemet= ry_info *ti, =20 used_qwords =3D scmi_telemetry_tdcf_line_parse(ti, next, shmti, update); - 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; } @@ -1544,6 +1564,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, de->info->id, tde->last_val, "FC_UPDATE"); } } =20 @@ -1622,8 +1644,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) { @@ -1639,11 +1664,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 (le32_to_cpu(payld->id) !=3D tde->de.info->id) + if (le32_to_cpu(payld->id) !=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); @@ -1667,10 +1697,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; @@ -1840,6 +1876,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.51.0