* [PATCH v3 11/24] firmware: arm_scmi: Add Telemetry DataEvent read capabilities
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
brauner, souvik.chakravarty, Cristian Marussi
In-Reply-To: <20260329163337.637393-1-cristian.marussi@arm.com>
Add support for Telemetry operations needed to read DataEvent values and
timestamps as single entities or all together in a single bulk buffer.
The returned values are effectiely retrieved from the platform only
when strictly needed, i.e. when no fresh recent cached value was already
available.
The DataEvent values are fetched transparently from the platform origins
using the proper synchronization and consistency primitives, directly from
the SHMTIs areas or the FastChannels memory depending on the configuration.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2 --> v3
- split from monolithic Telemetry patch
- simplity using a few assignement using ternary ops
- remove useless ts param from scanning function
- use a compound literal to simplify samples init
- add a missing __must_check on telemetry_ops
- changed errno on DE read troubles:
- ENODEV/ENOENT: DE is UNKNOWN
- EINVAL: DE is marked as DATA_INVALID
- ENODATA: TLM susbsystem or the specific DE is OFF
---
drivers/firmware/arm_scmi/telemetry.c | 428 ++++++++++++++++++++++++++
include/linux/scmi_protocol.h | 23 ++
2 files changed, 451 insertions(+)
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
index 9536262bbdf7..61eaad817db4 100644
--- a/drivers/firmware/arm_scmi/telemetry.c
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -2031,6 +2031,431 @@ scmi_telemetry_collection_configure(const struct scmi_protocol_handle *ph,
return ret;
}
+static inline void scmi_telemetry_de_data_fc_read(struct telemetry_de *tde,
+ u64 *tstamp, u64 *val)
+{
+ struct fc_tsline __iomem *fc = tde->base + tde->offset;
+
+ *val = LINE_DATA_GET(fc);
+ if (tstamp)
+ *tstamp = tde->de.tstamp_support ? LINE_TSTAMP_GET(fc) : 0;
+
+ trace_scmi_tlm_collect(tstamp ? *tstamp : 0,
+ tde->de.info->id, *val, "FC_READ");
+}
+
+static void scmi_telemetry_scan_update(struct telemetry_info *ti)
+{
+ struct telemetry_de *tde;
+
+ /* Scan all SHMTIs ... */
+ for (int id = 0; id < ti->num_shmti; id++) {
+ int ret;
+
+ ret = scmi_telemetry_shmti_scan(ti, id, SCAN_LOOKUP);
+ if (ret)
+ dev_warn(ti->ph->dev,
+ "Failed update-scan of SHMTI ID:%d - ret:%d\n",
+ id, ret);
+ }
+
+ if (!ti->info.fc_support)
+ return;
+
+ /* Need to enumerate resources to access fastchannels */
+ ti->res_get(ti);
+ list_for_each_entry(tde, &ti->fcs_des, item) {
+ u64 val, tstamp;
+
+ if (!tde->de.enabled)
+ continue;
+
+ scmi_telemetry_de_data_fc_read(tde, &tstamp, &val);
+
+ guard(mutex)(&tde->mtx);
+ tde->last_val = val;
+ tde->last_ts = tde->de.tstamp_enabled ? tstamp : 0;
+ }
+}
+
+/*
+ * TDCF and TS Line Management Notes
+ * ---------------------------------
+ *
+ * TCDF Payload Metadata notable bits:
+ * - Bit[3]: USE BLK Tstamp
+ * - Bit[2]: Line-Extension Field present (LineTstamp)
+ * - Bit[1]: Tstamp VALID
+ * - Bit[0]: Data INVALID
+ *
+ * CASE_1:
+ * -------
+ * + A DE is enabled with timestamp disabled, so the TS fields are
+ * NOT present
+ * -> BIT[3:0] = 0000b
+ *
+ * - 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:0] = 0110b
+ *
+ * - 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 clk rate)
+ * 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 (with same clk rate)
+ * 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] = 1010b
+ *
+ * + 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)
+ * anyway it will be marked as:
+ * -> BIT[3:0] = 0101b iff it was a LINE_TSTAMP
+ * -> BIT[3:0] = 0001b iff it was a BLK_TSTAMP
+ *
+ * CASE_2:
+ * -------
+ * + A DE is enabled with LINE timestamp enabled, so the TS_Line is there
+ * -> BIT[3:0] = 0110b
+ * + 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 physically there but NOT valid
+ * -> BIT[3:0] = 0100b
+ * + the hole from the timestamp remain there unused until
+ * - you enable again the TS so the hole is used again
+ * -> BIT[3:0] = 0110b
+ * 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 struct payload __iomem *
+scmi_telemetry_tdcf_de_payld_get(struct telemetry_de *tde)
+{
+ struct payload __iomem *payld;
+
+ payld = tde->base + tde->offset;
+ if (DATA_INVALID(payld)) {
+ trace_scmi_tlm_access(PAYLD_ID(payld), "DE_DATA_INVALID", 0, 0);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (IS_BLK_TS_LINE(payld)) {
+ trace_scmi_tlm_access(tde->de.info->id, "BAD_DE_META", 0, 0);
+ return ERR_PTR(-EPROTO);
+ }
+
+ if (PAYLD_ID(payld) != tde->de.info->id) {
+ trace_scmi_tlm_access(tde->de.info->id, "DE_ID_MISMATCH", 0, 0);
+ return ERR_PTR(-ENODEV);
+ }
+
+ /*
+ * A valid line using BLK_TS should have been initialized with the
+ * related BLK_TS when enabled.
+ */
+ if (WARN_ON((USE_BLK_TS(payld) && !tde->bts))) {
+ trace_scmi_tlm_access(tde->de.info->id, "BAD_USE_BLK_TS", 0, 0);
+ return ERR_PTR(-EPROTO);
+ }
+
+ return payld;
+}
+
+static int
+scmi_telemetry_tdcf_de_parse(struct telemetry_de *tde, u64 *tstamp, u64 *val)
+{
+ int retries = SCMI_TLM_TDCF_MAX_RETRIES;
+ struct tdcf __iomem *tdcf = tde->base;
+ u32 startm, endm;
+
+ if (!tdcf)
+ return -ENODEV;
+
+ do {
+ struct payload __iomem *payld;
+
+ /* A bit of exponential backoff between retries */
+ fsleep((SCMI_TLM_TDCF_MAX_RETRIES - retries) * 1000);
+
+ startm = TDCF_START_SEQ_GET(tdcf);
+ if (IS_BAD_START_SEQ(startm)) {
+ trace_scmi_tlm_access(tde->de.info->id, "MSEQ_BADSTART",
+ startm, 0);
+ continue;
+ }
+
+ /* Has anything changed at all at the SHMTI level ? */
+ scoped_guard(mutex, &tde->mtx) {
+ if (tde->last_magic == startm) {
+ *val = tde->last_val;
+ if (tstamp)
+ *tstamp = tde->last_ts;
+ return 0;
+ }
+ }
+
+ payld = scmi_telemetry_tdcf_de_payld_get(tde);
+ if (IS_ERR(payld))
+ return PTR_ERR(payld);
+
+ /* Parse data words */
+ scmi_telemetry_line_data_parse(tde, val, tstamp, payld, startm);
+
+ endm = TDCF_END_SEQ_GET(tde->eplg);
+ if (startm != endm)
+ trace_scmi_tlm_access(tde->de.info->id, "MSEQ_MISMATCH",
+ startm, endm);
+ } while (startm != endm && --retries);
+
+ if (startm != endm) {
+ trace_scmi_tlm_access(tde->de.info->id, "TDCF_DE_FAIL",
+ startm, endm);
+ return -EPROTO;
+ }
+
+ guard(mutex)(&tde->mtx);
+ tde->last_magic = startm;
+ tde->last_val = *val;
+ if (tstamp)
+ tde->last_ts = *tstamp;
+
+ return 0;
+}
+
+static int scmi_telemetry_de_access(struct telemetry_de *tde, u64 *tstamp, u64 *val)
+{
+ if (!tde->de.fc_support)
+ return scmi_telemetry_tdcf_de_parse(tde, tstamp, val);
+
+ scmi_telemetry_de_data_fc_read(tde, tstamp, val);
+
+ return 0;
+}
+
+static int scmi_telemetry_de_collect(struct telemetry_info *ti,
+ struct scmi_telemetry_de *de,
+ u64 *tstamp, u64 *val)
+{
+ struct telemetry_de *tde = to_tde(de);
+
+ if (!de->enabled)
+ return -ENODATA;
+
+ /*
+ * DE readings returns cached values when:
+ * - DE data value was retrieved via notification
+ */
+ scoped_guard(mutex, &tde->mtx) {
+ if (tde->cached) {
+ *val = tde->last_val;
+ if (tstamp)
+ *tstamp = tde->last_ts;
+ return 0;
+ }
+ }
+
+ return scmi_telemetry_de_access(tde, tstamp, val);
+}
+
+static int scmi_telemetry_de_cached_read(struct telemetry_info *ti,
+ struct scmi_telemetry_de *de,
+ u64 *tstamp, u64 *val)
+{
+ struct telemetry_de *tde = to_tde(de);
+
+ if (!de->enabled)
+ return -ENODATA;
+
+ guard(mutex)(&tde->mtx);
+ *val = tde->last_val;
+ if (tstamp)
+ *tstamp = tde->last_ts;
+
+ return 0;
+}
+
+static int scmi_telemetry_de_data_read(const struct scmi_protocol_handle *ph,
+ struct scmi_telemetry_de_sample *sample)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_telemetry_de *de;
+
+ if (!ti->info.enabled || !sample)
+ return -ENODATA;
+
+ de = xa_load(&ti->xa_des, sample->id);
+ if (!de)
+ return -ENOENT;
+
+ return scmi_telemetry_de_collect(ti, de, &sample->tstamp, &sample->val);
+}
+
+static int
+scmi_telemetry_samples_collect(struct telemetry_info *ti, int grp_id,
+ int *num_samples,
+ struct scmi_telemetry_de_sample *samples)
+{
+ struct scmi_telemetry_res_info *rinfo;
+ int max_samples;
+
+ max_samples = *num_samples;
+ *num_samples = 0;
+
+ rinfo = ti->res_get(ti);
+ for (int i = 0; i < rinfo->num_des; i++) {
+ struct scmi_telemetry_de *de;
+ u64 val, tstamp;
+ int ret;
+
+ de = rinfo->des[i];
+ if (grp_id != SCMI_TLM_GRP_INVALID &&
+ (!de->grp || de->grp->info->id != grp_id))
+ continue;
+
+ ret = scmi_telemetry_de_cached_read(ti, de, &tstamp, &val);
+ if (ret)
+ continue;
+
+ if (*num_samples == max_samples)
+ return -ENOSPC;
+
+ samples[(*num_samples)++] = (struct scmi_telemetry_de_sample) {
+ .tstamp = tstamp,
+ .val = val,
+ .id = de->info->id,
+ };
+ }
+
+ return 0;
+}
+
+static int scmi_telemetry_des_bulk_read(const struct scmi_protocol_handle *ph,
+ int grp_id, int *num_samples,
+ struct scmi_telemetry_de_sample *samples)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ if (!ti->info.enabled || !num_samples || !samples)
+ return -EINVAL;
+
+ /* Trigger a full SHMTIs & FCs scan */
+ scmi_telemetry_scan_update(ti);
+
+ /* Collect all last cached values */
+ return scmi_telemetry_samples_collect(ti, grp_id, num_samples, samples);
+}
+
+static void
+scmi_telemetry_msg_payld_process(struct telemetry_info *ti,
+ unsigned int num_dwords, u32 *dwords)
+{
+ struct scmi_telemetry_res_info *rinfo;
+ u32 next = 0;
+
+ rinfo = ti->res_get(ti);
+ if (!rinfo->fully_enumerated) {
+ dev_warn_once(ti->ph->dev,
+ "Cannot process DEs in message payload. Drop.\n");
+ return;
+ }
+
+ while (next < num_dwords) {
+ struct payload *payld = (struct payload *)&dwords[next];
+ struct scmi_telemetry_de *de;
+ struct telemetry_de *tde;
+ u32 de_id;
+
+ next += LINE_LENGTH_WORDS(payld);
+
+ if (DATA_INVALID(payld)) {
+ dev_err(ti->ph->dev, "MSG - Received INVALID DATA line\n");
+ continue;
+ }
+
+ de_id = le32_to_cpu(payld->id);
+ de = xa_load(&ti->xa_des, de_id);
+ if (!de || !de->enabled) {
+ dev_err(ti->ph->dev,
+ "MSG - Received INVALID DE - ID:%u enabled:%c\n",
+ de_id, de ? (de->enabled ? 'Y' : 'N') : 'X');
+ continue;
+ }
+
+ tde = to_tde(de);
+ guard(mutex)(&tde->mtx);
+ tde->cached = true;
+ tde->last_val = LINE_DATA_GET(&payld->tsl);
+ /* TODO BLK_TS in notification payloads */
+ tde->last_ts = HAS_LINE_EXT(payld) && LINE_TS_VALID(payld) ?
+ LINE_TSTAMP_GET(&payld->tsl) : 0;
+
+ trace_scmi_tlm_collect(tde->last_ts, tde->de.info->id,
+ tde->last_val, "MESSAGE");
+ }
+}
+
+static int scmi_telemetry_des_sample_get(const struct scmi_protocol_handle *ph,
+ int grp_id, int *num_samples,
+ struct scmi_telemetry_de_sample *samples)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_msg_telemetry_config_set *msg;
+ struct scmi_xfer *t;
+ bool grp_ignore;
+ int ret;
+
+ if (!ti->info.enabled || !num_samples || !samples)
+ return -EINVAL;
+
+ grp_ignore = grp_id == SCMI_TLM_GRP_INVALID ? true : false;
+ if (!grp_ignore && grp_id >= ti->info.base.num_groups)
+ return -EINVAL;
+
+ ret = ph->xops->xfer_get_init(ph, TELEMETRY_CONFIG_SET,
+ sizeof(*msg), 0, &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ msg->grp_id = grp_id;
+ msg->control = TELEMETRY_ENABLE;
+ msg->control |= grp_ignore ? TELEMETRY_SET_SELECTOR_ALL :
+ TELEMETRY_SET_SELECTOR_GROUP;
+ msg->control |= TELEMETRY_MODE_SINGLE;
+ msg->sampling_rate = 0;
+
+ ret = ph->xops->do_xfer_with_response(ph, t);
+ if (!ret) {
+ struct scmi_msg_resp_telemetry_reading_complete *r = t->rx.buf;
+
+ /* Update cached DEs values from payload */
+ if (r->num_dwords)
+ scmi_telemetry_msg_payld_process(ti, r->num_dwords,
+ r->dwords);
+ /* Scan and update SMHTIs and FCs */
+ scmi_telemetry_scan_update(ti);
+
+ /* Collect all last cached values */
+ ret = scmi_telemetry_samples_collect(ti, grp_id, num_samples,
+ samples);
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.info_get = scmi_telemetry_info_get,
.de_lookup = scmi_telemetry_de_lookup,
@@ -2039,6 +2464,9 @@ static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.state_set = scmi_telemetry_state_set,
.all_disable = scmi_telemetry_all_disable,
.collection_configure = scmi_telemetry_collection_configure,
+ .de_data_read = scmi_telemetry_de_data_read,
+ .des_bulk_read = scmi_telemetry_des_bulk_read,
+ .des_sample_get = scmi_telemetry_des_sample_get,
};
/**
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index 03aa6b3dbd6b..5f05df297064 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -935,6 +935,12 @@ struct scmi_telemetry_info {
enum scmi_telemetry_collection current_mode;
};
+struct scmi_telemetry_de_sample {
+ u32 id;
+ u64 tstamp;
+ u64 val;
+};
+
/**
* struct scmi_telemetry_proto_ops - represents the various operations provided
* by SCMI Telemetry Protocol
@@ -948,6 +954,15 @@ struct scmi_telemetry_info {
* @collection_configure: choose a sampling rate and enable SHMTI/FC sampling
* for on demand collection via @de_data_read or async
* notificatioins for all the enabled DEs.
+ * @de_data_read: on-demand read of a single DE and related optional timestamp:
+ * the value will be retrieved at the proper SHMTI offset OR
+ * from the dedicated FC area (if supported by that DE).
+ * @des_bulk_read: on-demand read of all the currently enabled DEs, or just
+ * the ones belonging to a specific group when provided.
+ * @des_sample_get: on-demand read of all the currently enabled DEs, or just
+ * the ones belonging to a specific group when provided.
+ * This causes an immediate update platform-side of all the
+ * enabled DEs.
*/
struct scmi_telemetry_proto_ops {
const struct scmi_telemetry_info __must_check *(*info_get)
@@ -966,6 +981,14 @@ struct scmi_telemetry_proto_ops {
bool *enable,
unsigned int *update_interval_ms,
enum scmi_telemetry_collection *mode);
+ int __must_check (*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);
};
/**
--
2.53.0
^ permalink raw reply related
* [PATCH v3 13/24] firmware: arm_scmi: Add Telemetry notification support
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
brauner, souvik.chakravarty, Cristian Marussi
In-Reply-To: <20260329163337.637393-1-cristian.marussi@arm.com>
Add support for notifications to Telemetry protocol and register an
internal notifier during protocol initialization: any DE value received
inside a notification payload will be cached for future user consumption.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2 --> v3
- changed a few dev_err into traces
- split from monolithic telemetry protocol patch
- use memcpy_from_le32
---
drivers/firmware/arm_scmi/telemetry.c | 124 ++++++++++++++++++++++++--
include/linux/scmi_protocol.h | 9 ++
2 files changed, 128 insertions(+), 5 deletions(-)
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
index ff0a5a8f6f57..c793ac616a2a 100644
--- a/drivers/firmware/arm_scmi/telemetry.c
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -448,12 +448,16 @@ struct telemetry_info {
struct list_head free_des;
struct list_head fcs_des;
struct scmi_telemetry_info info;
+ struct notifier_block telemetry_nb;
atomic_t rinfo_initializing;
struct completion rinfo_initdone;
struct scmi_telemetry_res_info __private *rinfo;
struct scmi_telemetry_res_info *(*res_get)(struct telemetry_info *ti);
};
+#define telemetry_nb_to_info(x) \
+ container_of(x, struct telemetry_info, telemetry_nb)
+
static struct scmi_telemetry_res_info *
__scmi_telemetry_resources_get(struct telemetry_info *ti);
@@ -2379,16 +2383,15 @@ scmi_telemetry_msg_payld_process(struct telemetry_info *ti,
next += LINE_LENGTH_WORDS(payld);
if (DATA_INVALID(payld)) {
- dev_err(ti->ph->dev, "MSG - Received INVALID DATA line\n");
+ trace_scmi_tlm_access(PAYLD_ID(payld), "MSG_INVALID", 0, 0);
continue;
}
de_id = le32_to_cpu(payld->id);
de = xa_load(&ti->xa_des, de_id);
if (!de || !de->enabled) {
- dev_err(ti->ph->dev,
- "MSG - Received INVALID DE - ID:%u enabled:%c\n",
- de_id, de ? (de->enabled ? 'Y' : 'N') : 'X');
+ trace_scmi_tlm_access(de_id, de ? "MSG_DE_DISABLED" :
+ "MSG_DE_UNKNOWN", 0, 0);
continue;
}
@@ -2513,6 +2516,98 @@ static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.reset = scmi_telemetry_reset,
};
+static bool
+scmi_telemetry_notify_supported(const struct scmi_protocol_handle *ph,
+ u8 evt_id, u32 src_id)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ return ti->info.continuos_update_support;
+}
+
+static int
+scmi_telemetry_set_notify_enabled(const struct scmi_protocol_handle *ph,
+ u8 evt_id, u32 src_id, bool enable)
+{
+ return 0;
+}
+
+static void *
+scmi_telemetry_fill_custom_report(const struct scmi_protocol_handle *ph,
+ u8 evt_id, ktime_t timestamp,
+ const void *payld, size_t payld_sz,
+ void *report, u32 *src_id)
+{
+ const struct scmi_telemetry_update_notify_payld *p = payld;
+ struct scmi_telemetry_update_report *r = report;
+
+ /* At least sized as an empty notification */
+ if (payld_sz < sizeof(*p))
+ return NULL;
+
+ r->timestamp = timestamp;
+ r->agent_id = le32_to_cpu(p->agent_id);
+ r->status = le32_to_cpu(p->status);
+ r->num_dwords = le32_to_cpu(p->num_dwords);
+ /*
+ * Allocated dwords and report are sized as max_msg_size, so as
+ * to allow for the maximum payload permitted by the configured
+ * transport. Overflow is not possible since out-of-size messages
+ * are dropped at the transport layer.
+ */
+ if (r->num_dwords)
+ memcpy_from_le32(r->dwords, p->array, r->num_dwords);
+
+ *src_id = 0;
+
+ return r;
+}
+
+static const struct scmi_event tlm_events[] = {
+ {
+ .id = SCMI_EVENT_TELEMETRY_UPDATE,
+ .max_payld_sz = 0,
+ .max_report_sz = 0,
+ },
+};
+
+static const struct scmi_event_ops tlm_event_ops = {
+ .is_notify_supported = scmi_telemetry_notify_supported,
+ .set_notify_enabled = scmi_telemetry_set_notify_enabled,
+ .fill_custom_report = scmi_telemetry_fill_custom_report,
+};
+
+static const struct scmi_protocol_events tlm_protocol_events = {
+ .queue_sz = SCMI_PROTO_QUEUE_SZ,
+ .ops = &tlm_event_ops,
+ .evts = tlm_events,
+ .num_events = ARRAY_SIZE(tlm_events),
+ .num_sources = 1,
+};
+
+static int scmi_telemetry_notifier(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct scmi_telemetry_update_report *er = data;
+ struct telemetry_info *ti = telemetry_nb_to_info(nb);
+
+ if (er->status) {
+ trace_scmi_tlm_access(0, "BAD_NOTIF_MSG", 0, 0);
+ return NOTIFY_DONE;
+ }
+
+ trace_scmi_tlm_access(0, "TLM_UPDATE_MSG", 0, 0);
+ /* Lookup the embedded DEs in the notification payload ... */
+ if (er->num_dwords)
+ scmi_telemetry_msg_payld_process(ti, er->num_dwords, er->dwords);
+
+ /* ...scan the SHMTI/FCs for any other DE updates. */
+ if (ti->streaming_mode)
+ scmi_telemetry_scan_update(ti);
+
+ return NOTIFY_OK;
+}
+
/**
* scmi_telemetry_resources_alloc - Resources allocation
* @ti: A reference to the telemetry info descriptor for this instance
@@ -2757,7 +2852,25 @@ static int scmi_telemetry_protocol_init(const struct scmi_protocol_handle *ph)
ti->info.base.version = ph->version;
- return ph->set_priv(ph, ti);
+ ret = ph->set_priv(ph, ti);
+ if (ret)
+ return ret;
+
+ /*
+ * Register a notifier anyway straight upon protocol initialization
+ * since there could be some DEs that are ONLY reported by notifications
+ * even though the chosen collection method was SHMTI/FCs.
+ */
+ if (ti->info.continuos_update_support) {
+ ti->telemetry_nb.notifier_call = &scmi_telemetry_notifier;
+ ret = ph->notifier_register(ph, SCMI_EVENT_TELEMETRY_UPDATE,
+ NULL, &ti->telemetry_nb);
+ if (ret)
+ dev_warn(ph->dev,
+ "Could NOT register Telemetry notifications\n");
+ }
+
+ return ret;
}
static const struct scmi_protocol scmi_telemetry = {
@@ -2765,6 +2878,7 @@ static const struct scmi_protocol scmi_telemetry = {
.owner = THIS_MODULE,
.instance_init = &scmi_telemetry_protocol_init,
.ops = &tlm_proto_ops,
+ .events = &tlm_protocol_events,
.supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
};
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index fc3b5493dc1a..9d8b12b2160e 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -1198,6 +1198,7 @@ enum scmi_notification_events {
SCMI_EVENT_SYSTEM_POWER_STATE_NOTIFIER = 0x0,
SCMI_EVENT_POWERCAP_CAP_CHANGED = 0x0,
SCMI_EVENT_POWERCAP_MEASUREMENTS_CHANGED = 0x1,
+ SCMI_EVENT_TELEMETRY_UPDATE = 0x0,
};
struct scmi_power_state_changed_report {
@@ -1285,4 +1286,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 */
--
2.53.0
^ permalink raw reply related
* [PATCH v3 12/24] firmware: arm_scmi: Add support for Telemetry reset
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
brauner, souvik.chakravarty, Cristian Marussi
In-Reply-To: <20260329163337.637393-1-cristian.marussi@arm.com>
Add support for Telemetry operations needed to request platform to
reset telemetry current configuration and data events values.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2 --> v3
- split from monolithic Telemetry patch
- use scmi_telemetry_de_unlink
---
drivers/firmware/arm_scmi/telemetry.c | 44 +++++++++++++++++++++++++++
include/linux/scmi_protocol.h | 2 ++
2 files changed, 46 insertions(+)
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
index 61eaad817db4..ff0a5a8f6f57 100644
--- a/drivers/firmware/arm_scmi/telemetry.c
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -2456,6 +2456,49 @@ static int scmi_telemetry_des_sample_get(const struct scmi_protocol_handle *ph,
return ret;
}
+static void scmi_telemetry_local_resources_reset(struct telemetry_info *ti)
+{
+ struct scmi_telemetry_res_info *rinfo;
+
+ /* Get rinfo as it is...without triggering an enumeration */
+ rinfo = __scmi_telemetry_resources_get(ti);
+ /* Clear all local state...*/
+ for (int i = 0; i < rinfo->num_des; i++) {
+ rinfo->des[i]->enabled = false;
+ rinfo->des[i]->tstamp_enabled = false;
+
+ scmi_telemetry_de_unlink(rinfo->des[i]);
+ }
+ for (int i = 0; i < rinfo->num_groups; i++) {
+ rinfo->grps[i].enabled = false;
+ rinfo->grps[i].tstamp_enabled = false;
+ rinfo->grps[i].current_mode = SCMI_TLM_ONDEMAND;
+ rinfo->grps[i].active_update_interval = 0;
+ }
+}
+
+static int scmi_telemetry_reset(const struct scmi_protocol_handle *ph)
+{
+ struct scmi_xfer *t;
+ int ret;
+
+ ret = ph->xops->xfer_get_init(ph, TELEMETRY_RESET, sizeof(u32), 0, &t);
+ if (ret)
+ return ret;
+
+ put_unaligned_le32(0, t->tx.buf);
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret) {
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ scmi_telemetry_local_resources_reset(ti);
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.info_get = scmi_telemetry_info_get,
.de_lookup = scmi_telemetry_de_lookup,
@@ -2467,6 +2510,7 @@ static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.de_data_read = scmi_telemetry_de_data_read,
.des_bulk_read = scmi_telemetry_des_bulk_read,
.des_sample_get = scmi_telemetry_des_sample_get,
+ .reset = scmi_telemetry_reset,
};
/**
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index 5f05df297064..fc3b5493dc1a 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -963,6 +963,7 @@ struct scmi_telemetry_de_sample {
* the ones belonging to a specific group when provided.
* This causes an immediate update platform-side of all the
* enabled DEs.
+ * @reset: reset configuration and telemetry data.
*/
struct scmi_telemetry_proto_ops {
const struct scmi_telemetry_info __must_check *(*info_get)
@@ -989,6 +990,7 @@ struct scmi_telemetry_proto_ops {
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 (*reset)(const struct scmi_protocol_handle *ph);
};
/**
--
2.53.0
^ permalink raw reply related
* [PATCH v3 09/24] firmware: arm_scmi: Add support to parse SHMTIs areas
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
brauner, souvik.chakravarty, Cristian Marussi
In-Reply-To: <20260329163337.637393-1-cristian.marussi@arm.com>
Add logic to scan the SHMTI areas, parsing the TDCF descriptors while
collecting DataEvent, BlockTimestamp and UUID lines.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2 --> v3
- split from monolithic Telemetry patch
- avoid devres allocation for resources that are added to the xa_lines XArray
- simplify prototype of line parsing helpers to drop unneeded dev
- flip tstmap logic in scmi_telemetry_line_data_parse() to properly emit
a TLM ftrace event
- use ternary ops to simplify quite a few expressions
---
drivers/firmware/arm_scmi/telemetry.c | 576 ++++++++++++++++++++++++++
1 file changed, 576 insertions(+)
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
index 7e5af7bd9fdc..6c5a988b2aac 100644
--- a/drivers/firmware/arm_scmi/telemetry.c
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -254,6 +254,23 @@ struct uuid_line {
u32 dwords[SCMI_TLM_DE_IMPL_MAX_DWORDS];
};
+#define LINE_DATA_GET(f) \
+({ \
+ typeof(f) _f = (f); \
+ \
+ (TO_CPU_64(_I(&_f->data_high), _I(&_f->data_low))); \
+})
+
+#define LINE_TSTAMP_GET(f) \
+({ \
+ typeof(f) _f = (f); \
+ \
+ (TO_CPU_64(_I(&_f->ts_high), _I(&_f->ts_low))); \
+})
+
+#define BLK_TS_STAMP(f) LINE_TSTAMP_GET(f)
+#define BLK_TS_RATE(p) PAYLD_ID(p)
+
enum tdcf_line_types {
TDCF_DATA_LINE,
TDCF_BLK_TS_LINE,
@@ -365,6 +382,7 @@ struct telemetry_line {
refcount_t users;
u32 last_magic;
struct payload __iomem *payld;
+ struct xarray *xa_lines;
/* Protect line accesses */
struct mutex mtx;
};
@@ -424,6 +442,7 @@ struct telemetry_info {
struct telemetry_de *tdes;
struct scmi_telemetry_group *grps;
struct xarray xa_des;
+ struct xarray xa_lines;
/* Mutex to protect access to @free_des */
struct mutex free_mtx;
struct list_head free_des;
@@ -1119,6 +1138,555 @@ scmi_telemetry_resources_get(const struct scmi_protocol_handle *ph)
return ti->res_get(ti);
}
+static u64
+scmi_telemetry_blkts_read(u32 magic, struct telemetry_block_ts *bts)
+{
+ if (WARN_ON(!bts || !refcount_read(&bts->line.users)))
+ return 0;
+
+ guard(mutex)(&bts->line.mtx);
+
+ if (bts->line.last_magic == magic)
+ return bts->last_ts;
+
+ /* Note that the bts->last_rate can change ONLY on creation */
+ bts->last_ts = BLK_TS_STAMP(&bts->line.payld->blk_tsl);
+ bts->line.last_magic = magic;
+
+ return bts->last_ts;
+}
+
+static void scmi_telemetry_blkts_update(struct telemetry_info *ti, u32 magic,
+ struct telemetry_block_ts *bts)
+{
+ guard(mutex)(&bts->line.mtx);
+
+ if (bts->line.last_magic != magic) {
+ bts->last_ts = BLK_TS_STAMP(&bts->line.payld->blk_tsl);
+ bts->last_rate = BLK_TS_RATE(bts->line.payld);
+ /* BLK_TS clock rate value can change ONLY here on creation */
+ if (!bts->last_rate)
+ bts->last_rate = ti->default_blk_ts_rate;
+ bts->line.last_magic = magic;
+ }
+}
+
+static void scmi_telemetry_line_put(struct telemetry_line *line, void *blob)
+{
+ if (refcount_dec_and_test(&line->users)) {
+ scoped_guard(mutex, &line->mtx)
+ xa_erase(line->xa_lines, (unsigned long)line->payld);
+ kfree(blob);
+ }
+}
+
+static void scmi_telemetry_blkts_unlink(struct telemetry_de *tde)
+{
+ scmi_telemetry_line_put(&tde->bts->line, tde->bts);
+ tde->bts = NULL;
+}
+
+static void scmi_telemetry_uuid_unlink(struct telemetry_de *tde)
+{
+ scmi_telemetry_line_put(&tde->uuid->line, tde->uuid);
+ tde->uuid = NULL;
+}
+
+static void scmi_telemetry_de_unlink(struct scmi_telemetry_de *de)
+{
+ struct telemetry_de *tde = to_tde(de);
+
+ /* Unlink all related lines triggering their deallocation */
+ if (tde->bts)
+ scmi_telemetry_blkts_unlink(tde);
+ if (tde->uuid)
+ scmi_telemetry_uuid_unlink(tde);
+}
+
+static struct telemetry_line *
+scmi_telemetry_line_get(struct xarray *xa_lines, struct payload *payld)
+{
+ struct telemetry_line *line;
+
+ line = xa_load(xa_lines, (unsigned long)payld);
+ if (!line)
+ return NULL;
+
+ refcount_inc(&line->users);
+
+ return line;
+}
+
+static int
+scmi_telemetry_line_init(struct telemetry_line *line, struct xarray *xa_lines,
+ struct payload __iomem *payld)
+{
+ refcount_set(&line->users, 1);
+ line->payld = payld;
+ line->xa_lines = xa_lines;
+ mutex_init(&line->mtx);
+
+ return xa_insert(xa_lines, (unsigned long)payld, line, GFP_KERNEL);
+}
+
+static struct telemetry_block_ts *
+scmi_telemetry_blkts_create(struct device *dev, struct xarray *xa_lines,
+ struct payload *payld)
+{
+ struct telemetry_block_ts *bts;
+ int ret;
+
+ bts = kzalloc(sizeof(*bts), GFP_KERNEL);
+ if (!bts)
+ return NULL;
+
+ ret = scmi_telemetry_line_init(&bts->line, xa_lines, payld);
+ if (ret) {
+ kfree(bts);
+ return NULL;
+ }
+
+ trace_scmi_tlm_collect(0, (u64)payld, 0, "SHMTI_NEW_BLKTS");
+
+ return bts;
+}
+
+static struct telemetry_block_ts *
+scmi_telemetry_blkts_get_or_create(struct device *dev, struct xarray *xa_lines,
+ struct payload *payld)
+{
+ struct telemetry_line *line;
+
+ line = scmi_telemetry_line_get(xa_lines, payld);
+ if (line)
+ return to_blkts(line);
+
+ return scmi_telemetry_blkts_create(dev, xa_lines, payld);
+}
+
+static struct telemetry_uuid *
+scmi_telemetry_uuid_create(struct device *dev, struct xarray *xa_lines,
+ struct payload *payld)
+{
+ struct telemetry_uuid *uuid;
+ int ret;
+
+ uuid = kzalloc(sizeof(*uuid), GFP_KERNEL);
+ if (!uuid)
+ return NULL;
+
+ for (int i = 0; i < SCMI_TLM_DE_IMPL_MAX_DWORDS; i++)
+ uuid->de_impl_version[i] = le32_to_cpu(payld->uuid_l.dwords[i]);
+
+ ret = scmi_telemetry_line_init(&uuid->line, xa_lines, payld);
+ if (ret) {
+ kfree(uuid);
+ return NULL;
+ }
+
+ trace_scmi_tlm_collect(0, (u64)payld, 0, "SHMTI_NEW_UUID");
+
+ return uuid;
+}
+
+static struct telemetry_uuid *
+scmi_telemetry_uuid_get_or_create(struct device *dev, struct xarray *xa_lines,
+ struct payload *payld)
+{
+ struct telemetry_line *line;
+
+ line = scmi_telemetry_line_get(xa_lines, payld);
+ if (line)
+ return to_uuid(line);
+
+ return scmi_telemetry_uuid_create(dev, xa_lines, payld);
+}
+
+static void scmi_telemetry_tdcf_uuid_parse(struct telemetry_info *ti,
+ struct payload __iomem *payld,
+ struct telemetry_shmti *shmti,
+ void **active_uuid)
+{
+ struct telemetry_uuid *uuid;
+
+ if (UUID_INVALID(payld)) {
+ trace_scmi_tlm_access(0, "UUID_INVALID", 0, 0);
+ return;
+ }
+
+ /* A UUID descriptor MUST be returned: it is found or it is created */
+ uuid = scmi_telemetry_uuid_get_or_create(ti->ph->dev, &ti->xa_lines,
+ payld);
+ if (WARN_ON(!uuid))
+ return;
+
+ *active_uuid = uuid;
+}
+
+static struct payload *
+scmi_telemetry_nearest_line_by_type(struct telemetry_shmti *shmti,
+ void *last, enum tdcf_line_types ltype)
+{
+ struct tdcf __iomem *tdcf = shmti->base;
+ void *next, *found = NULL;
+
+ /* Scan from start of TDCF payloads up to last_payld */
+ next = tdcf->payld;
+ while (next < last) {
+ if (LINE_TYPE((struct payload *)next) == ltype)
+ found = next;
+
+ next += LINE_LENGTH_WORDS((struct payload *)next);
+ }
+
+ return found;
+}
+
+static struct telemetry_block_ts *
+scmi_telemetry_blkts_bind(struct device *dev, struct telemetry_shmti *shmti,
+ struct payload *payld, struct xarray *xa_lines,
+ struct payload *bts_payld)
+{
+ /* Trigger a manual search when no BLK_TS payload offset was provided */
+ if (!bts_payld) {
+ /* Find the BLK_TS immediately preceding this DE payld */
+ bts_payld = scmi_telemetry_nearest_line_by_type(shmti, payld,
+ TDCF_BLK_TS_LINE);
+ if (!bts_payld)
+ return NULL;
+ }
+
+ return scmi_telemetry_blkts_get_or_create(dev, xa_lines, bts_payld);
+}
+
+/**
+ * scmi_telemetry_tdcf_blkts_parse - A BLK_TS line parser
+ *
+ * @ti: A reference to the telemetry_info descriptor
+ * @payld: TDCF payld line to process
+ * @shmti: SHMTI descriptor inside which the scan is happening
+ * @active_bts: Input/output reference to keep track of the last blk_ts found
+ *
+ * Process a valid TDCF BLK_TS line and, after having looked up or created a
+ * blk_ts descriptor, update the related data and return it as the currently
+ * active blk_ts, given that it is effectively the last found during this
+ * scan.
+ */
+static void scmi_telemetry_tdcf_blkts_parse(struct telemetry_info *ti,
+ struct payload __iomem *payld,
+ struct telemetry_shmti *shmti,
+ void **active_bts)
+{
+ struct telemetry_block_ts *bts;
+
+ /* Check for spec compliance */
+ if (BLK_TS_INVALID(payld)) {
+ trace_scmi_tlm_access(0, "BLK_TS_INVALID", 0, 0);
+ return;
+ }
+
+ /* A BLK_TS descriptor MUST be returned: it is found or it is created */
+ bts = scmi_telemetry_blkts_get_or_create(ti->ph->dev,
+ &ti->xa_lines, payld);
+ if (WARN_ON(!bts))
+ return;
+
+ /* Update the descriptor with the lastest TS */
+ scmi_telemetry_blkts_update(ti, shmti->last_magic, bts);
+ *active_bts = bts;
+}
+
+static inline struct telemetry_de *
+scmi_telemetry_tde_allocate(struct telemetry_info *ti, u32 de_id,
+ struct payload __iomem *payld)
+{
+ struct telemetry_de *tde;
+
+ tde = scmi_telemetry_tde_get(ti, de_id);
+ if (IS_ERR(tde))
+ return NULL;
+
+ tde->de.info->id = de_id;
+ tde->de.enabled = true;
+ tde->de.tstamp_enabled = LINE_TS_VALID(payld) || USE_BLK_TS(payld);
+
+ if (scmi_telemetry_tde_register(ti, tde)) {
+ scmi_telemetry_free_tde_put(ti, tde);
+ return NULL;
+ }
+
+ return tde;
+}
+
+static inline void
+scmi_telemetry_line_data_parse(struct telemetry_de *tde, u64 *val, u64 *tstamp,
+ struct payload __iomem *payld, u32 magic)
+{
+ /* Data is always valid since we are NOT handling BLK TS lines here */
+ *val = LINE_DATA_GET(&payld->l);
+ if (tstamp) {
+ if (USE_BLK_TS(payld)) {
+ /* Read out the actual BLK_TS */
+ *tstamp = scmi_telemetry_blkts_read(magic, tde->bts);
+ } else if (LINE_TS_VALID(payld)) {
+ /*
+ * Note that LINE_TS_VALID implies HAS_LINE_EXT and that
+ * the per DE line_ts_rate is advertised in the DE
+ * descriptor.
+ */
+ *tstamp = LINE_TSTAMP_GET(&payld->tsl);
+ } else {
+ *tstamp = 0;
+ }
+ }
+
+ trace_scmi_tlm_collect(tstamp ? *tstamp : 0, tde->de.info->id,
+ *val, "SHMTI_DE_READ");
+}
+
+static inline void scmi_telemetry_bts_link(struct telemetry_de *tde,
+ struct telemetry_block_ts *bts)
+{
+ refcount_inc(&bts->line.users);
+ tde->bts = bts;
+ /* Update TS clock rate if provided by the BLK_TS */
+ if (tde->bts->last_rate)
+ tde->de.info->ts_rate = tde->bts->last_rate;
+}
+
+static inline void scmi_telemetry_uuid_link(struct telemetry_de *tde,
+ struct telemetry_uuid *uuid)
+{
+ refcount_inc(&uuid->line.users);
+ tde->uuid = uuid;
+}
+
+/**
+ * scmi_telemetry_tdcf_data_parse - TDCF DataLine parsing
+ * @ti: A reference to the telemetry info descriptor
+ * @payld: Line payload to parse
+ * @shmti: A reference to the containing SHMTI area
+ * @mode: A flag to determine the behaviour of the scan
+ * @active_bts: A pointer to keep track and report any found BLK timestamp line
+ * @active_uuid: A pointer to keep track and report any found UUID line
+ *
+ * This routine takes care to:
+ * - verify line consistency in relation to the used flags and the current
+ * context: e.g. is there an active preceding BLK_TS line if the DataLine
+ * sports a USE_BLKTS flag ?
+ * - verify the related Data Event ID exists OR create a brand new DE
+ * (depending on the @mode of operation)
+ * - links any active BLK_TS or UUID line to the current DE
+ * - read and save value/tstamp for the DE ONLY if anything has changed (by
+ * tracking the last TDCF magic) and update related magic: this allows to
+ * minimize future needs of single-DE reads
+ *
+ * Modes of operation.
+ *
+ * The scan behaviour depends on the chosen @mode:
+ * - SCAN_LOOKUP: the basic scan which aims to update value associated to
+ * existing DEs. Any discovered DataLine that could NOT be
+ * matched to an existing, previously discovered, DE is
+ * discarded. This is the normal scan behaviour.
+ * - SCAN_UPDATE: a more advanced scan which provides all the SCAN_LOOKUP
+ * features plus takes care to update the DEs location
+ * coordinates inside the SHMTI: note that the related DEs are
+ * still supposed to have been previously discovered when
+ * this scan runs. This is used to update location
+ * coordinates for DEs contained in a Group when such group
+ * is enabled.
+ * - SCAN_DISCOVERY: the most advanced scan available which provides all
+ * the SCAN_LOOKUP features plus discovery capabilities:
+ * any DataLine referring to a previously unknown DE leads
+ * to the allocation of a new DE descriptor.
+ * This mode is used on the first scan at init time, ONLY
+ * if Telemetry was found to be already enabled at boot on
+ * the platform side: this helps to maximize gathered
+ * information when dealing with out of spec firmwares.
+ * Any usage of this discovery mode other than in a boot-on
+ * enabled scenario is discouraged since it can easily
+ * lead to spurious DE discoveries.
+ */
+static void scmi_telemetry_tdcf_data_parse(struct telemetry_info *ti,
+ struct payload __iomem *payld,
+ struct telemetry_shmti *shmti,
+ enum scan_mode mode,
+ void *active_bts, void *active_uuid)
+{
+ bool use_blk_ts = USE_BLK_TS(payld);
+ struct telemetry_de *tde;
+ u64 val, tstamp = 0;
+ u32 de_id;
+
+ de_id = PAYLD_ID(payld);
+ /* Discard malformed lines...a preceding BLK_TS must exist */
+ if (use_blk_ts && !active_bts) {
+ trace_scmi_tlm_access(de_id, "BAD_USE_BLK_TS", 0, 0);
+ return;
+ }
+
+ /* Is this DE ID known ? */
+ tde = scmi_telemetry_tde_lookup(ti, de_id);
+ if (!tde) {
+ if (mode != SCAN_DISCOVERY) {
+ trace_scmi_tlm_access(de_id, "DE_INVALID", 0, 0);
+ return;
+ }
+
+ /* In SCAN_DISCOVERY mode we allocate new DEs for unknown IDs */
+ tde = scmi_telemetry_tde_allocate(ti, de_id, payld);
+ if (!tde)
+ return;
+ }
+
+ /* Update DE location refs if requested: normally done only on enable */
+ if (mode >= SCAN_UPDATE) {
+ tde->base = shmti->base;
+ tde->eplg = SHMTI_EPLG(shmti);
+ tde->offset = (void *)payld - (void *)shmti->base;
+
+ dev_dbg(ti->ph->dev,
+ "TDCF-updated DE_ID:0x%08X - shmti:%pK offset:%u\n",
+ tde->de.info->id, tde->base, tde->offset);
+ }
+
+ /* Has any value/tstamp really changed ?*/
+ scoped_guard(mutex, &tde->mtx) {
+ if (tde->last_magic == shmti->last_magic)
+ return;
+ }
+
+ /* Link the related BTS when needed, it's unlinked on disable */
+ if (use_blk_ts && !tde->bts)
+ scmi_telemetry_bts_link(tde, active_bts);
+
+ /* Link the active UUID when existent, it's unlinked on disable */
+ if (active_uuid)
+ scmi_telemetry_uuid_link(tde, active_uuid);
+
+ /* Parse data words */
+ scmi_telemetry_line_data_parse(tde, &val, &tstamp, payld,
+ shmti->last_magic);
+
+ guard(mutex)(&tde->mtx);
+ tde->last_magic = shmti->last_magic;
+ tde->last_val = val;
+ tde->last_ts = tde->de.tstamp_enabled ? tstamp : 0;
+}
+
+static int scmi_telemetry_tdcf_line_parse(struct telemetry_info *ti,
+ struct payload __iomem *payld,
+ struct telemetry_shmti *shmti,
+ enum scan_mode mode,
+ void **active_bts, void **active_uuid)
+{
+ int used_qwords;
+
+ used_qwords = LINE_LENGTH_QWORDS(payld);
+ /* Invalid lines are not an error, could simply be disabled DEs */
+ if (DATA_INVALID(payld)) {
+ trace_scmi_tlm_access(PAYLD_ID(payld), "TDCF_INVALID", 0, 0);
+ return used_qwords;
+ }
+
+ switch (LINE_TYPE(payld)) {
+ case TDCF_DATA_LINE:
+ scmi_telemetry_tdcf_data_parse(ti, payld, shmti, mode,
+ *active_bts, *active_uuid);
+ break;
+ case TDCF_BLK_TS_LINE:
+ scmi_telemetry_tdcf_blkts_parse(ti, payld, shmti, active_bts);
+ break;
+ case TDCF_UUID_LINE:
+ scmi_telemetry_tdcf_uuid_parse(ti, payld, shmti, active_uuid);
+ break;
+ default:
+ trace_scmi_tlm_access(PAYLD_ID(payld), "TDCF_UNKNOWN", 0, 0);
+ break;
+ }
+
+ return used_qwords;
+}
+
+/**
+ * scmi_telemetry_shmti_scan - Full SHMTI scan
+ * @ti: A reference to the telemetry info descriptor
+ * @shmti_id: ID of the SHMTI area that has to be scanned
+ * @mode: A flag to determine the behaviour of the scan
+ *
+ * Return: 0 on Success
+ */
+static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
+ unsigned int shmti_id, enum scan_mode mode)
+{
+ struct telemetry_shmti *shmti = &ti->shmti[shmti_id];
+ struct tdcf __iomem *tdcf = shmti->base;
+ int retries = SCMI_TLM_TDCF_MAX_RETRIES;
+ u32 startm = 0, endm = TDCF_BAD_END_SEQ;
+
+ if (!tdcf)
+ return -ENODEV;
+
+ do {
+ void *active_bts = NULL, *active_uuid = NULL;
+ unsigned int qwords;
+ void __iomem *next;
+
+ /* A bit of exponential backoff between retries */
+ fsleep((SCMI_TLM_TDCF_MAX_RETRIES - retries) * 1000);
+
+ /*
+ * Note that during a full SHMTI scan the magic seq numbers are
+ * checked only at the start and at the end of the scan, NOT
+ * between each parsed line and this has these consequences:
+ * - TDCF magic numbers accesses are reduced to 2 reads
+ * - the set of values obtained from a full scan belong all
+ * to the same platform update (same magic number)
+ * - a SHMTI full scan is an all or nothing operation: when
+ * a potentially corrupted read is detected along the way
+ * (MSEQ_MISMATCH) another full scan is triggered.
+ */
+ startm = TDCF_START_SEQ_GET(tdcf);
+ if (IS_BAD_START_SEQ(startm)) {
+ trace_scmi_tlm_access(0, "MSEQ_BADSTART", startm, 0);
+ continue;
+ }
+
+ /* On a BAD_SEQ this will be updated on the next attempt */
+ shmti->last_magic = startm;
+
+ qwords = QWORDS(tdcf);
+ next = tdcf->payld;
+ while (qwords) {
+ int used_qwords;
+
+ used_qwords = scmi_telemetry_tdcf_line_parse(ti, next,
+ shmti, mode,
+ &active_bts,
+ &active_uuid);
+ if (qwords < used_qwords) {
+ trace_scmi_tlm_access(PAYLD_ID(next),
+ "BAD_QWORDS", startm, 0);
+ return -EINVAL;
+ }
+
+ next += used_qwords * 8;
+ qwords -= used_qwords;
+ }
+
+ endm = TDCF_END_SEQ_GET(SHMTI_EPLG(shmti));
+ if (startm != endm)
+ trace_scmi_tlm_access(0, "MSEQ_MISMATCH", startm, endm);
+ } while (startm != endm && --retries);
+
+ if (startm != endm) {
+ trace_scmi_tlm_access(0, "TDCF_SCAN_FAIL", startm, endm);
+ return -EPROTO;
+ }
+
+ return 0;
+}
+
static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.info_get = scmi_telemetry_info_get,
.de_lookup = scmi_telemetry_de_lookup,
@@ -1208,6 +1776,13 @@ static void scmi_telemetry_resources_free(void *arg)
struct telemetry_info *ti = arg;
struct scmi_telemetry_res_info *rinfo = ACCESS_PRIVATE(ti, rinfo);
+ /*
+ * Unlinking all the BLK_TS/UUID lines related to a DE triggers also
+ * the deallocation of such lines when the embedded refcount hits zero.
+ */
+ for (int i = 0; i < rinfo->num_des; i++)
+ scmi_telemetry_de_unlink(rinfo->des[i]);
+
kfree(ti->tdes);
kfree(rinfo->des);
kfree(rinfo->dei_store);
@@ -1313,6 +1888,7 @@ static int scmi_telemetry_instance_init(struct telemetry_info *ti)
return ret;
xa_init(&ti->xa_des);
+ xa_init(&ti->xa_lines);
/* Setup resources lazy initialization */
atomic_set(&ti->rinfo_initializing, 0);
init_completion(&ti->rinfo_initdone);
--
2.53.0
^ permalink raw reply related
* [PATCH v3 10/24] firmware: arm_scmi: Add Telemetry configuration operations
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
brauner, souvik.chakravarty, Cristian Marussi
In-Reply-To: <20260329163337.637393-1-cristian.marussi@arm.com>
Add support for basic Telemetry configuration operations to selectively
enable or disable DataEvents monitoring.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2 --> v3
- split from monolithic Telemetry patch
- simplify clenaup with scmi_telemetry_de_unlink
---
drivers/firmware/arm_scmi/telemetry.c | 348 ++++++++++++++++++++++++++
include/linux/scmi_protocol.h | 16 ++
2 files changed, 364 insertions(+)
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
index 6c5a988b2aac..9536262bbdf7 100644
--- a/drivers/firmware/arm_scmi/telemetry.c
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -457,6 +457,9 @@ struct telemetry_info {
static struct scmi_telemetry_res_info *
__scmi_telemetry_resources_get(struct telemetry_info *ti);
+static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
+ unsigned int shmti_id, enum scan_mode mode);
+
static struct telemetry_de *
scmi_telemetry_free_tde_get(struct telemetry_info *ti)
{
@@ -1687,10 +1690,355 @@ static int scmi_telemetry_shmti_scan(struct telemetry_info *ti,
return 0;
}
+static int scmi_telemetry_group_state_update(struct telemetry_info *ti,
+ struct scmi_telemetry_group *grp,
+ bool *enable, bool *tstamp)
+{
+ struct scmi_telemetry_res_info *rinfo;
+
+ rinfo = ti->res_get(ti);
+ for (int i = 0; i < grp->info->num_des; i++) {
+ struct scmi_telemetry_de *de = rinfo->des[grp->des[i]];
+
+ if (enable)
+ de->enabled = *enable;
+ if (tstamp)
+ de->tstamp_enabled = *tstamp;
+ }
+
+ return 0;
+}
+
+static int
+scmi_telemetry_state_set_resp_process(struct telemetry_info *ti,
+ struct scmi_telemetry_de *de,
+ void *r, bool is_group)
+{
+ struct scmi_msg_resp_telemetry_de_configure *resp = r;
+ u32 sid = le32_to_cpu(resp->shmti_id);
+
+ /* Update DE SHMTI and offset, if applicable */
+ if (IS_SHMTI_ID_VALID(sid)) {
+ if (sid >= ti->num_shmti)
+ return -EPROTO;
+
+ /*
+ * Update SHMTI/offset while skipping non-SHMTI-DEs like
+ * FCs and notif-only.
+ */
+ if (!is_group) {
+ struct telemetry_de *tde;
+ struct payload *payld;
+ u32 de_offs;
+
+ de_offs = le32_to_cpu(resp->shmti_de_offset);
+ if (de_offs >= ti->shmti[sid].len - de->info->data_sz)
+ return -EPROTO;
+
+ tde = to_tde(de);
+ tde->base = ti->shmti[sid].base;
+ tde->offset = de_offs;
+ /* A handy reference to the Epilogue updated */
+ tde->eplg = SHMTI_EPLG(&ti->shmti[sid]);
+
+ payld = tde->base + tde->offset;
+ if (USE_BLK_TS(payld) && !tde->bts) {
+ struct payload *bts_payld;
+ u32 bts_offs;
+
+ bts_offs = le32_to_cpu(resp->blk_ts_offset);
+ bts_payld = (bts_offs) ? tde->base + bts_offs : NULL;
+ tde->bts = scmi_telemetry_blkts_bind(ti->ph->dev,
+ &ti->shmti[sid],
+ payld,
+ &ti->xa_lines,
+ bts_payld);
+ if (WARN_ON(!tde->bts))
+ return -EPROTO;
+ }
+ } else {
+ int ret;
+
+ /*
+ * A full SHMTI scan is needed when enabling a
+ * group or its timestamps in order to retrieve
+ * offsets: note that when group-timestamp is
+ * enabled for composing DEs a re-scan is needed
+ * since some DEs could have been relocated due
+ * to lack of space in the TDCF.
+ */
+ ret = scmi_telemetry_shmti_scan(ti, sid, SCAN_UPDATE);
+ if (ret)
+ dev_warn(ti->ph->dev,
+ "Failed group-scan of SHMTI ID:%d - ret:%d\n",
+ sid, ret);
+ }
+ } else if (!is_group) {
+ /* Unlink the related BLK_TS/UUID lines on disable */
+ scmi_telemetry_de_unlink(de);
+ }
+
+ return 0;
+}
+
+static int __scmi_telemetry_state_set(const struct scmi_protocol_handle *ph,
+ bool is_group, bool *enable,
+ bool *enabled_state, bool *tstamp,
+ bool *tstamp_enabled_state, void *obj)
+{
+ struct scmi_msg_resp_telemetry_de_configure *resp;
+ struct scmi_msg_telemetry_de_configure *msg;
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_telemetry_group *grp;
+ struct scmi_telemetry_de *de;
+ unsigned int obj_id;
+ struct scmi_xfer *t;
+ int ret;
+
+ if (!enabled_state || !tstamp_enabled_state)
+ return -EINVAL;
+
+ /* Is anything to do at all on this DE ? */
+ if (!is_group && (!enable || *enable == *enabled_state) &&
+ (!tstamp || *tstamp == *tstamp_enabled_state))
+ return 0;
+
+ /*
+ * DE is currently disabled AND no enable state change was requested,
+ * while timestamp is being changed: update only local state...no need
+ * to send a message.
+ */
+ if (!is_group && !enable && !*enabled_state) {
+ *tstamp_enabled_state = *tstamp;
+ return 0;
+ }
+
+ if (!is_group) {
+ de = obj;
+ obj_id = de->info->id;
+ } else {
+ grp = obj;
+ obj_id = grp->info->id;
+ }
+
+ ret = ph->xops->xfer_get_init(ph, TELEMETRY_DE_CONFIGURE,
+ sizeof(*msg), sizeof(*resp), &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ /* Note that BOTH DE and GROUPS have a first ID field.. */
+ msg->id = cpu_to_le32(obj_id);
+ /* Default to disable mode for one DE */
+ msg->flags = DE_DISABLE_ONE;
+ msg->flags |= FIELD_PREP(GENMASK(3, 3),
+ is_group ? EVENT_GROUP : EVENT_DE);
+
+ if ((!enable && *enabled_state) || (enable && *enable)) {
+ /* Already enabled but tstamp_enabled state changed */
+ if (tstamp) {
+ /* Here, tstamp cannot be NULL too */
+ msg->flags |= *tstamp ? DE_ENABLE_WTH_TSTAMP :
+ DE_ENABLE_NO_TSTAMP;
+ } else {
+ msg->flags |= *tstamp_enabled_state ?
+ DE_ENABLE_WTH_TSTAMP : DE_ENABLE_NO_TSTAMP;
+ }
+ }
+
+ resp = t->rx.buf;
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret) {
+ ret = scmi_telemetry_state_set_resp_process(ti, de, resp, is_group);
+ if (!ret) {
+ /* Update cached state on success */
+ if (enable)
+ *enabled_state = *enable;
+ if (tstamp)
+ *tstamp_enabled_state = *tstamp;
+
+ if (is_group)
+ scmi_telemetry_group_state_update(ti, grp, enable,
+ tstamp);
+ }
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static int scmi_telemetry_state_get(const struct scmi_protocol_handle *ph,
+ u32 id, bool *enabled, bool *tstamp_enabled)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_telemetry_de *de;
+
+ if (!enabled || !tstamp_enabled)
+ return -EINVAL;
+
+ de = xa_load(&ti->xa_des, id);
+ if (!de)
+ return -ENODEV;
+
+ *enabled = de->enabled;
+ *tstamp_enabled = de->tstamp_enabled;
+
+ return 0;
+}
+
+static int scmi_telemetry_state_set(const struct scmi_protocol_handle *ph,
+ bool is_group, u32 id, bool *enable,
+ bool *tstamp)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+ bool *enabled_state, *tstamp_enabled_state;
+ struct scmi_telemetry_res_info *rinfo;
+ void *obj;
+
+ rinfo = ti->res_get(ti);
+ if (!is_group) {
+ struct scmi_telemetry_de *de;
+
+ de = xa_load(&ti->xa_des, id);
+ if (!de)
+ return -ENODEV;
+
+ enabled_state = &de->enabled;
+ tstamp_enabled_state = &de->tstamp_enabled;
+ obj = de;
+ } else {
+ struct scmi_telemetry_group *grp;
+
+ if (id >= ti->info.base.num_groups)
+ return -EINVAL;
+
+ grp = &rinfo->grps[id];
+
+ enabled_state = &grp->enabled;
+ tstamp_enabled_state = &grp->tstamp_enabled;
+ obj = grp;
+ }
+
+ return __scmi_telemetry_state_set(ph, is_group, enable, enabled_state,
+ tstamp, tstamp_enabled_state, obj);
+}
+
+static int scmi_telemetry_all_disable(const struct scmi_protocol_handle *ph,
+ bool is_group)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_msg_telemetry_de_configure *msg;
+ struct scmi_telemetry_res_info *rinfo;
+ struct scmi_xfer *t;
+ int ret;
+
+ rinfo = ti->res_get(ti);
+ ret = ph->xops->xfer_get_init(ph, TELEMETRY_DE_CONFIGURE,
+ sizeof(*msg), 0, &t);
+ if (ret)
+ return ret;
+
+ msg = t->tx.buf;
+ msg->flags = DE_DISABLE_ALL;
+ if (is_group)
+ msg->flags |= GROUP_SELECTOR;
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret) {
+ for (int i = 0; i < ti->info.base.num_des; i++)
+ rinfo->des[i]->enabled = false;
+
+ if (is_group) {
+ for (int i = 0; i < ti->info.base.num_groups; i++)
+ rinfo->grps[i].enabled = false;
+ }
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static int
+scmi_telemetry_collection_configure(const struct scmi_protocol_handle *ph,
+ unsigned int res_id, bool grp_ignore,
+ bool *enable,
+ unsigned int *update_interval_ms,
+ enum scmi_telemetry_collection *mode)
+{
+ enum scmi_telemetry_collection *current_mode, next_mode;
+ struct telemetry_info *ti = ph->get_priv(ph);
+ struct scmi_msg_telemetry_config_set *msg;
+ unsigned int *active_update_interval;
+ struct scmi_xfer *t;
+ bool tlm_enable;
+ u32 interval;
+ int ret;
+
+ if (mode && *mode == SCMI_TLM_NOTIFICATION &&
+ !ti->info.continuos_update_support)
+ return -EINVAL;
+
+ if (res_id != SCMI_TLM_GRP_INVALID && res_id >= ti->info.base.num_groups)
+ return -EINVAL;
+
+ if (res_id == SCMI_TLM_GRP_INVALID || grp_ignore) {
+ active_update_interval = &ti->info.active_update_interval;
+ current_mode = &ti->info.current_mode;
+ } else {
+ struct scmi_telemetry_res_info *rinfo;
+
+ rinfo = ti->res_get(ti);
+ active_update_interval =
+ &rinfo->grps[res_id].active_update_interval;
+ current_mode = &rinfo->grps[res_id].current_mode;
+ }
+
+ if (!enable && !update_interval_ms && (!mode || *mode == *current_mode))
+ return 0;
+
+ ret = ph->xops->xfer_get_init(ph, TELEMETRY_CONFIG_SET,
+ sizeof(*msg), 0, &t);
+ if (ret)
+ return ret;
+
+ if (!update_interval_ms)
+ interval = cpu_to_le32(*active_update_interval);
+ else
+ interval = *update_interval_ms;
+
+ tlm_enable = enable ? *enable : ti->info.enabled;
+ next_mode = mode ? *mode : *current_mode;
+
+ msg = t->tx.buf;
+ msg->grp_id = res_id;
+ msg->control = tlm_enable ? TELEMETRY_ENABLE : 0;
+ msg->control |= grp_ignore ? TELEMETRY_SET_SELECTOR_ALL :
+ TELEMETRY_SET_SELECTOR_GROUP;
+ msg->control |= TELEMETRY_MODE_SET(next_mode);
+ msg->sampling_rate = interval;
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret) {
+ ti->info.enabled = tlm_enable;
+ *current_mode = next_mode;
+ ti->info.notif_enabled = *current_mode == SCMI_TLM_NOTIFICATION;
+ if (update_interval_ms)
+ *active_update_interval = interval;
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
.info_get = scmi_telemetry_info_get,
.de_lookup = scmi_telemetry_de_lookup,
.res_get = scmi_telemetry_resources_get,
+ .state_get = scmi_telemetry_state_get,
+ .state_set = scmi_telemetry_state_set,
+ .all_disable = scmi_telemetry_all_disable,
+ .collection_configure = scmi_telemetry_collection_configure,
};
/**
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index fcb45bd4b44c..03aa6b3dbd6b 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -942,6 +942,12 @@ struct scmi_telemetry_info {
* @info_get: get the general Telemetry information.
* @de_lookup: get a specific DE descriptor from the DE id.
* @res_get: get a reference to the Telemetry resources descriptor.
+ * @state_get: retrieve the specific DE or GROUP state.
+ * @state_set: enable/disable the specific DE or GROUP with or without timestamps.
+ * @all_disable: disable ALL DEs or GROUPs.
+ * @collection_configure: choose a sampling rate and enable SHMTI/FC sampling
+ * for on demand collection via @de_data_read or async
+ * notificatioins for all the enabled DEs.
*/
struct scmi_telemetry_proto_ops {
const struct scmi_telemetry_info __must_check *(*info_get)
@@ -950,6 +956,16 @@ struct scmi_telemetry_proto_ops {
(const struct scmi_protocol_handle *ph, u32 id);
const struct scmi_telemetry_res_info __must_check *(*res_get)
(const struct scmi_protocol_handle *ph);
+ int (*state_get)(const struct scmi_protocol_handle *ph,
+ u32 id, bool *enabled, bool *tstamp_enabled);
+ int (*state_set)(const struct scmi_protocol_handle *ph,
+ bool is_group, u32 id, bool *enable, bool *tstamp);
+ int (*all_disable)(const struct scmi_protocol_handle *ph, bool group);
+ int (*collection_configure)(const struct scmi_protocol_handle *ph,
+ unsigned int res_id, bool grp_ignore,
+ bool *enable,
+ unsigned int *update_interval_ms,
+ enum scmi_telemetry_collection *mode);
};
/**
--
2.53.0
^ permalink raw reply related
* [PATCH v3 08/24] firmware: arm_scmi: Add basic Telemetry support
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
brauner, souvik.chakravarty, Cristian Marussi
In-Reply-To: <20260329163337.637393-1-cristian.marussi@arm.com>
Add SCMIv4.0 Telemetry basic support to enable initialization and resources
enumeration: add all the telemetry messages definitions and parsing logic
but only a few simple state gathering protocol operations.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2 --> v3
- split from monolithic Telemetry patch
- fix checkpatch macros complaints
- fix ACCESS_PRIVATE usage
- add a few comments on allocation/enumeration lifetime
- use interval.num_intervals
- removed needless cleanup handler usage
- simply return from scmi_telemetry_de_lookup()
- fixed composing_des name length to 08X
---
drivers/firmware/arm_scmi/Makefile | 2 +-
drivers/firmware/arm_scmi/driver.c | 2 +
drivers/firmware/arm_scmi/protocols.h | 1 +
drivers/firmware/arm_scmi/telemetry.c | 1375 +++++++++++++++++++++++++
include/linux/scmi_protocol.h | 135 ++-
5 files changed, 1513 insertions(+), 2 deletions(-)
create mode 100644 drivers/firmware/arm_scmi/telemetry.c
diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
index 780cd62b2f78..fe55b7aa0707 100644
--- a/drivers/firmware/arm_scmi/Makefile
+++ b/drivers/firmware/arm_scmi/Makefile
@@ -8,7 +8,7 @@ scmi-driver-$(CONFIG_ARM_SCMI_RAW_MODE_SUPPORT) += raw_mode.o
scmi-transport-$(CONFIG_ARM_SCMI_HAVE_SHMEM) = shmem.o
scmi-transport-$(CONFIG_ARM_SCMI_HAVE_MSG) += msg.o
scmi-protocols-y := base.o clock.o perf.o power.o reset.o sensors.o system.o voltage.o powercap.o
-scmi-protocols-y += pinctrl.o
+scmi-protocols-y += pinctrl.o telemetry.o
scmi-module-objs := $(scmi-driver-y) $(scmi-protocols-y) $(scmi-transport-y)
obj-$(CONFIG_ARM_SCMI_PROTOCOL) += transports/
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index c4aefbeead62..a4a2e52e1f3d 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -3508,6 +3508,7 @@ static int __init scmi_driver_init(void)
scmi_system_register();
scmi_powercap_register();
scmi_pinctrl_register();
+ scmi_telemetry_register();
return platform_driver_register(&scmi_driver);
}
@@ -3526,6 +3527,7 @@ static void __exit scmi_driver_exit(void)
scmi_system_unregister();
scmi_powercap_unregister();
scmi_pinctrl_unregister();
+ scmi_telemetry_unregister();
platform_driver_unregister(&scmi_driver);
diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h
index 3e7b6f8aa72c..3250d981664b 100644
--- a/drivers/firmware/arm_scmi/protocols.h
+++ b/drivers/firmware/arm_scmi/protocols.h
@@ -386,5 +386,6 @@ DECLARE_SCMI_REGISTER_UNREGISTER(sensors);
DECLARE_SCMI_REGISTER_UNREGISTER(voltage);
DECLARE_SCMI_REGISTER_UNREGISTER(system);
DECLARE_SCMI_REGISTER_UNREGISTER(powercap);
+DECLARE_SCMI_REGISTER_UNREGISTER(telemetry);
#endif /* _SCMI_PROTOCOLS_H */
diff --git a/drivers/firmware/arm_scmi/telemetry.c b/drivers/firmware/arm_scmi/telemetry.c
new file mode 100644
index 000000000000..7e5af7bd9fdc
--- /dev/null
+++ b/drivers/firmware/arm_scmi/telemetry.c
@@ -0,0 +1,1375 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * System Control and Management Interface (SCMI) Telemetry Protocol
+ *
+ * Copyright (C) 2026 ARM Ltd.
+ */
+
+#include <linux/atomic.h>
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/compiler_types.h>
+#include <linux/completion.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/refcount.h>
+#include <linux/slab.h>
+#include <linux/sprintf.h>
+#include <linux/string.h>
+#include <linux/xarray.h>
+
+#include "protocols.h"
+#include "notify.h"
+
+#include <trace/events/scmi.h>
+
+/* Updated only after ALL the mandatory features for that version are merged */
+#define SCMI_PROTOCOL_SUPPORTED_VERSION 0x10000
+
+#define SCMI_TLM_TDCF_MAX_RETRIES 5
+
+enum scmi_telemetry_protocol_cmd {
+ TELEMETRY_LIST_SHMTI = 0x3,
+ TELEMETRY_DE_DESCRIPTION = 0x4,
+ TELEMETRY_LIST_UPDATE_INTERVALS = 0x5,
+ TELEMETRY_DE_CONFIGURE = 0x6,
+ TELEMETRY_DE_ENABLED_LIST = 0x7,
+ TELEMETRY_CONFIG_SET = 0x8,
+ TELEMETRY_READING_COMPLETE = TELEMETRY_CONFIG_SET,
+ TELEMETRY_CONFIG_GET = 0x9,
+ TELEMETRY_RESET = 0xA,
+};
+
+struct scmi_msg_resp_telemetry_protocol_attributes {
+ __le32 de_num;
+ __le32 groups_num;
+ __le32 de_implementation_rev_dword[SCMI_TLM_DE_IMPL_MAX_DWORDS];
+ __le32 attributes;
+#define SUPPORTS_SINGLE_READ(x) ((x) & BIT(31))
+#define SUPPORTS_CONTINUOS_UPDATE(x) ((x) & BIT(30))
+#define SUPPORTS_PER_GROUP_CONFIG(x) ((x) & BIT(18))
+#define SUPPORTS_RESET(x) ((x) & BIT(17))
+#define SUPPORTS_FC(x) ((x) & BIT(16))
+ __le32 default_blk_ts_rate;
+};
+
+struct scmi_telemetry_update_notify_payld {
+ __le32 agent_id;
+ __le32 status;
+ __le32 num_dwords;
+ __le32 array[] __counted_by(num_dwords);
+};
+
+struct scmi_shmti_desc {
+ __le32 id;
+ __le32 addr_low;
+ __le32 addr_high;
+ __le32 length;
+ __le32 flags;
+};
+
+struct scmi_msg_resp_telemetry_shmti_list {
+ __le32 num_shmti;
+ struct scmi_shmti_desc desc[] __counted_by(num_shmti);
+};
+
+struct de_desc_fc {
+ __le32 addr_low;
+ __le32 addr_high;
+ __le32 size;
+};
+
+struct scmi_de_desc {
+ __le32 id;
+ __le32 grp_id;
+ __le32 data_sz;
+ __le32 attr_1;
+#define IS_NAME_SUPPORTED(d) ((d)->attr_1 & BIT(31))
+#define IS_FC_SUPPORTED(d) ((d)->attr_1 & BIT(30))
+#define GET_DE_TYPE(d) (le32_get_bits((d)->attr_1, GENMASK(29, 22)))
+#define IS_PERSISTENT(d) ((d)->attr_1 & BIT(21))
+#define GET_DE_UNIT_EXP(d) \
+ ({ \
+ __u32 __signed_exp = \
+ le32_get_bits((d)->attr_1, GENMASK(20, 13)); \
+ \
+ sign_extend32(__signed_exp, 7); \
+ })
+#define GET_DE_UNIT(d) (le32_get_bits((d)->attr_1, GENMASK(12, 5)))
+#define TSTAMP_SUPPORT(d) (le32_get_bits((d)->attr_1, GENMASK(1, 0)))
+ __le32 attr_2;
+#define GET_DE_INSTA_ID(d) (le32_get_bits((d)->attr_2, GENMASK(31, 24)))
+#define GET_COMPO_INSTA_ID(d) (le32_get_bits((d)->attr_2, GENMASK(23, 8)))
+#define GET_COMPO_TYPE(d) (le32_get_bits((d)->attr_2, GENMASK(7, 0)))
+ __le32 reserved;
+};
+
+struct scmi_msg_resp_telemetry_de_description {
+ __le32 num_desc;
+ struct scmi_de_desc desc[] __counted_by(num_desc);
+};
+
+struct scmi_msg_telemetry_update_intervals {
+ __le32 index;
+ __le32 group_identifier;
+#define ALL_DES_NO_GROUP 0x0
+#define SPECIFIC_GROUP_DES 0x1
+#define ALL_DES_ANY_GROUP 0x2
+ __le32 flags;
+};
+
+struct scmi_msg_resp_telemetry_update_intervals {
+ __le32 flags;
+#define INTERVALS_DISCRETE(x) (!((x) & BIT(12)))
+ __le32 intervals[];
+};
+
+struct scmi_msg_telemetry_de_enabled_list {
+ __le32 index;
+ __le32 flags;
+};
+
+struct scmi_enabled_de_desc {
+ __le32 id;
+ __le32 mode;
+};
+
+struct scmi_msg_resp_telemetry_de_enabled_list {
+ __le32 flags;
+ struct scmi_enabled_de_desc entry[];
+};
+
+struct scmi_msg_telemetry_de_configure {
+ __le32 id;
+ __le32 flags;
+#define DE_ENABLE_NO_TSTAMP BIT(0)
+#define DE_ENABLE_WTH_TSTAMP BIT(1)
+#define DE_DISABLE_ALL BIT(2)
+#define GROUP_SELECTOR BIT(3)
+#define EVENT_DE 0
+#define EVENT_GROUP 1
+#define DE_DISABLE_ONE 0x0
+};
+
+struct scmi_msg_resp_telemetry_de_configure {
+ __le32 shmti_id;
+#define IS_SHMTI_ID_VALID(x) ((x) != 0xFFFFFFFF)
+ __le32 shmti_de_offset;
+ __le32 blk_ts_offset;
+};
+
+struct scmi_msg_telemetry_config_set {
+ __le32 grp_id;
+ __le32 control;
+#define TELEMETRY_ENABLE (BIT(0))
+
+#define TELEMETRY_MODE_SET(x) (FIELD_PREP(GENMASK(4, 1), (x)))
+#define TLM_ONDEMAND (0)
+#define TLM_NOTIFS (1)
+#define TLM_SINGLE (2)
+#define TELEMETRY_MODE_ONDEMAND TELEMETRY_MODE_SET(TLM_ONDEMAND)
+#define TELEMETRY_MODE_NOTIFS TELEMETRY_MODE_SET(TLM_NOTIFS)
+#define TELEMETRY_MODE_SINGLE TELEMETRY_MODE_SET(TLM_SINGLE)
+
+#define TLM_ORPHANS (0)
+#define TLM_GROUP (1)
+#define TLM_ALL (2)
+#define TELEMETRY_SET_SELECTOR(x) (FIELD_PREP(GENMASK(8, 5), (x)))
+#define TELEMETRY_SET_SELECTOR_ORPHANS TELEMETRY_SET_SELECTOR(TLM_ORPHANS)
+#define TELEMETRY_SET_SELECTOR_GROUP TELEMETRY_SET_SELECTOR(TLM_GROUP)
+#define TELEMETRY_SET_SELECTOR_ALL TELEMETRY_SET_SELECTOR(TLM_ALL)
+ __le32 sampling_rate;
+};
+
+struct scmi_msg_resp_telemetry_reading_complete {
+ __le32 num_dwords;
+ __le32 dwords[] __counted_by(num_dwords);
+};
+
+struct scmi_msg_telemetry_config_get {
+ __le32 grp_id;
+ __le32 flags;
+#define TELEMETRY_GET_SELECTOR(x) (FIELD_PREP(GENMASK(3, 0), (x)))
+#define TELEMETRY_GET_SELECTOR_ORPHANS TELEMETRY_GET_SELECTOR(TLM_ORPHANS)
+#define TELEMETRY_GET_SELECTOR_GROUP TELEMETRY_GET_SELECTOR(TLM_GROUP)
+#define TELEMETRY_GET_SELECTOR_ALL TELEMETRY_GET_SELECTOR(TLM_ALL)
+};
+
+struct scmi_msg_resp_telemetry_config_get {
+ __le32 control;
+#define TELEMETRY_MODE_GET (FIELD_GET(GENMASK(4, 1)))
+ __le32 sampling_rate;
+};
+
+/* TDCF */
+
+#define _I(__a) (ioread32((void __iomem *)(__a)))
+
+#define TO_CPU_64(h, l) ((((u64)(h)) << 32) | (l))
+
+/*
+ * Define the behaviour of a SHMTI scan defining what information will
+ * be gathered and which Telemetry items can be updated.
+ */
+enum scan_mode {
+ SCAN_LOOKUP, /* Update only value/tstamp */
+ SCAN_UPDATE, /* Update also location offset */
+ SCAN_DISCOVERY /* Update xa_des: allows for new DEs to be discovered */
+};
+
+struct fc_line {
+ u32 data_low;
+ u32 data_high;
+};
+
+struct fc_tsline {
+ u32 data_low;
+ u32 data_high;
+ u32 ts_low;
+ u32 ts_high;
+};
+
+struct line {
+ u32 data_low;
+ u32 data_high;
+};
+
+struct blk_tsline {
+ u32 ts_low;
+ u32 ts_high;
+};
+
+struct tsline {
+ u32 data_low;
+ u32 data_high;
+ u32 ts_low;
+ u32 ts_high;
+};
+
+struct uuid_line {
+ u32 dwords[SCMI_TLM_DE_IMPL_MAX_DWORDS];
+};
+
+enum tdcf_line_types {
+ TDCF_DATA_LINE,
+ TDCF_BLK_TS_LINE,
+ TDCF_UUID_LINE,
+};
+
+struct payload {
+ u32 meta;
+#define LINE_TYPE(x) (le32_get_bits(_I(&((x)->meta)), GENMASK(7, 4)))
+#define IS_DATA_LINE(x) (LINE_TYPE(x) == TDCF_DATA_LINE)
+#define IS_BLK_TS_LINE(x) (LINE_TYPE(x) == TDCF_BLK_TS_LINE)
+#define IS_UUID_LINE(x) (LINE_TYPE(x) == TDCF_UUID_LINE)
+#define USE_BLK_TS(x) (_I(&((x)->meta)) & BIT(3))
+#define HAS_LINE_EXT(x) (_I(&((x)->meta)) & BIT(2))
+#define LINE_TS_VALID(x) (_I(&((x)->meta)) & BIT(1))
+#define DATA_INVALID(x) (_I(&((x)->meta)) & BIT(0))
+#define BLK_TS_INVALID(p) \
+({ \
+ typeof(p) _p = (p); \
+ bool invalid; \
+ \
+ invalid = LINE_TS_VALID(_p) || HAS_LINE_EXT(_p) || \
+ USE_BLK_TS(_p) || DATA_INVALID(_p); \
+ invalid; \
+})
+
+#define UUID_INVALID(p) \
+({ \
+ typeof(p) _p = (p); \
+ bool invalid; \
+ \
+ invalid = LINE_TS_VALID(_p) || USE_BLK_TS(_p) || \
+ DATA_INVALID(_p) || !HAS_LINE_EXT(_p); \
+ invalid; \
+})
+ u32 id;
+ union {
+ struct line l;
+ struct tsline tsl;
+ struct blk_tsline blk_tsl;
+ struct uuid_line uuid_l;
+ };
+};
+
+#define PAYLD_ID(x) (_I(&(((struct payload *)(x))->id)))
+
+#define LINE_DATA_PAYLD_WORDS \
+ ((sizeof(u32) + sizeof(u32) + sizeof(struct line)) / sizeof(u32))
+#define EXT_LINE_DATA_PAYLD_WORDS \
+ ((sizeof(u32) + sizeof(u32) + sizeof(struct tsline)) / sizeof(u32))
+
+#define LINE_LENGTH_WORDS(x) \
+ (HAS_LINE_EXT((x)) ? EXT_LINE_DATA_PAYLD_WORDS : LINE_DATA_PAYLD_WORDS)
+
+#define LINE_LENGTH_QWORDS(x) ((LINE_LENGTH_WORDS(x)) / 2)
+
+struct prlg {
+ u32 sign_start;
+#define SIGNATURE_START 0x5442474E /* TBGN */
+ u32 match_start;
+ u32 num_qwords;
+ u32 hdr_meta_1;
+#define TDCF_REVISION_GET(x) (le32_get_bits((x)->hdr_meta_1, GENMASK(7, 0)))
+};
+
+struct eplg {
+ u32 match_end;
+ u32 sign_end;
+#define SIGNATURE_END 0x54454E44 /* TEND */
+};
+
+#define TDCF_EPLG_SZ (sizeof(struct eplg))
+
+struct tdcf {
+ struct prlg prlg;
+ unsigned char payld[];
+};
+
+#define QWORDS(_t) (_I(&(_t)->prlg.num_qwords))
+
+#define SHMTI_MIN_SIZE (sizeof(struct tdcf) + TDCF_EPLG_SZ)
+
+#define TDCF_START_SIGNATURE(x) (_I(&((x)->prlg.sign_start)))
+#define TDCF_START_SEQ_GET(x) (_I(&((x)->prlg.match_start)))
+#define IS_BAD_START_SEQ(s) ((s) & 0x1)
+
+#define TDCF_END_SEQ_GET(e) (_I(&((e)->match_end)))
+#define TDCF_END_SIGNATURE(e) (_I(&((e)->sign_end)))
+#define TDCF_BAD_END_SEQ GENMASK(31, 0)
+
+struct telemetry_shmti {
+ int id;
+ u32 flags;
+ void __iomem *base;
+ u32 len;
+ u32 last_magic;
+};
+
+#define SHMTI_EPLG(s) \
+ ({ \
+ struct telemetry_shmti *_s = (s); \
+ struct eplg *_eplg; \
+ \
+ _eplg = _s->base + _s->len - TDCF_EPLG_SZ; \
+ (_eplg); \
+ })
+
+struct telemetry_line {
+ refcount_t users;
+ u32 last_magic;
+ struct payload __iomem *payld;
+ /* Protect line accesses */
+ struct mutex mtx;
+};
+
+struct telemetry_block_ts {
+ u64 last_ts;
+ u32 last_rate;
+ struct telemetry_line line;
+};
+
+#define to_blkts(l) container_of(l, struct telemetry_block_ts, line)
+
+struct telemetry_uuid {
+ u32 de_impl_version[SCMI_TLM_DE_IMPL_MAX_DWORDS];
+ struct telemetry_line line;
+};
+
+#define to_uuid(l) container_of(l, struct telemetry_uuid, line)
+
+enum timestamps {
+ TSTAMP_NONE,
+ TSTAMP_LINE,
+ TSTAMP_BLK
+};
+
+struct telemetry_de {
+ enum timestamps ts_type;
+ u32 ts_rate;
+ bool enumerated;
+ bool cached;
+ void __iomem *base;
+ struct eplg __iomem *eplg;
+ u32 offset;
+ /* NOTE THAT DE data_sz is registered in scmi_telemetry_de */
+ u32 fc_size;
+ /* Protect last_val/ts/magic accesses */
+ struct mutex mtx;
+ u64 last_val;
+ u64 last_ts;
+ u32 last_magic;
+ struct list_head item;
+ struct telemetry_block_ts *bts;
+ struct telemetry_uuid *uuid;
+ struct scmi_telemetry_de de;
+};
+
+#define to_tde(d) container_of(d, struct telemetry_de, de)
+
+#define DE_ENABLED_WITH_TSTAMP 2
+
+struct telemetry_info {
+ bool streaming_mode;
+ unsigned int num_shmti;
+ unsigned int default_blk_ts_rate;
+ const struct scmi_protocol_handle *ph;
+ struct telemetry_shmti *shmti;
+ struct telemetry_de *tdes;
+ struct scmi_telemetry_group *grps;
+ struct xarray xa_des;
+ /* Mutex to protect access to @free_des */
+ struct mutex free_mtx;
+ struct list_head free_des;
+ struct list_head fcs_des;
+ struct scmi_telemetry_info info;
+ atomic_t rinfo_initializing;
+ struct completion rinfo_initdone;
+ struct scmi_telemetry_res_info __private *rinfo;
+ struct scmi_telemetry_res_info *(*res_get)(struct telemetry_info *ti);
+};
+
+static struct scmi_telemetry_res_info *
+__scmi_telemetry_resources_get(struct telemetry_info *ti);
+
+static struct telemetry_de *
+scmi_telemetry_free_tde_get(struct telemetry_info *ti)
+{
+ struct telemetry_de *tde;
+
+ guard(mutex)(&ti->free_mtx);
+
+ tde = list_first_entry_or_null(&ti->free_des, struct telemetry_de, item);
+ if (!tde)
+ return tde;
+
+ list_del(&tde->item);
+
+ return tde;
+}
+
+static void scmi_telemetry_free_tde_put(struct telemetry_info *ti,
+ struct telemetry_de *tde)
+{
+ guard(mutex)(&ti->free_mtx);
+
+ list_add_tail(&tde->item, &ti->free_des);
+}
+
+static struct telemetry_de *scmi_telemetry_tde_lookup(struct telemetry_info *ti,
+ unsigned int de_id)
+{
+ struct scmi_telemetry_de *de;
+
+ de = xa_load(&ti->xa_des, de_id);
+ if (!de)
+ return NULL;
+
+ return to_tde(de);
+}
+
+static struct telemetry_de *scmi_telemetry_tde_get(struct telemetry_info *ti,
+ unsigned int de_id)
+{
+ static struct telemetry_de *tde;
+
+ /* Pick a new tde */
+ tde = scmi_telemetry_free_tde_get(ti);
+ if (!tde) {
+ dev_err(ti->ph->dev, "Cannot allocate DE for ID:0x%08X\n", de_id);
+ return ERR_PTR(-ENOSPC);
+ }
+
+ return tde;
+}
+
+static int scmi_telemetry_tde_register(struct telemetry_info *ti,
+ struct telemetry_de *tde)
+{
+ struct scmi_telemetry_res_info *rinfo = ACCESS_PRIVATE(ti, rinfo);
+ int ret;
+
+ if (rinfo->num_des >= ti->info.base.num_des) {
+ ret = -ENOSPC;
+ goto err;
+ }
+
+ /* Store DE pointer by de_id ... */
+ ret = xa_insert(&ti->xa_des, tde->de.info->id, &tde->de, GFP_KERNEL);
+ if (ret)
+ goto err;
+
+ /* ... and in the general array */
+ rinfo->des[rinfo->num_des++] = &tde->de;
+
+ return 0;
+
+err:
+ dev_err(ti->ph->dev, "Cannot register DE for ID:0x%08X\n",
+ tde->de.info->id);
+
+ return ret;
+}
+
+struct scmi_tlm_de_priv {
+ struct telemetry_info *ti;
+ void *next;
+};
+
+static int
+scmi_telemetry_protocol_attributes_get(struct telemetry_info *ti)
+{
+ struct scmi_msg_resp_telemetry_protocol_attributes *resp;
+ const struct scmi_protocol_handle *ph = ti->ph;
+ struct scmi_xfer *t;
+ int ret;
+
+ ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0,
+ sizeof(*resp), &t);
+ if (ret)
+ return ret;
+
+ resp = t->rx.buf;
+ ret = ph->xops->do_xfer(ph, t);
+ if (!ret) {
+ __le32 attr = resp->attributes;
+
+ ti->info.base.num_des = le32_to_cpu(resp->de_num);
+ ti->info.base.num_groups = le32_to_cpu(resp->groups_num);
+ for (int i = 0; i < SCMI_TLM_DE_IMPL_MAX_DWORDS; i++)
+ ti->info.base.de_impl_version[i] =
+ le32_to_cpu(resp->de_implementation_rev_dword[i]);
+ ti->info.single_read_support = SUPPORTS_SINGLE_READ(attr);
+ ti->info.continuos_update_support = SUPPORTS_CONTINUOS_UPDATE(attr);
+ ti->info.per_group_config_support = SUPPORTS_PER_GROUP_CONFIG(attr);
+ ti->info.reset_support = SUPPORTS_RESET(attr);
+ ti->info.fc_support = SUPPORTS_FC(attr);
+ ti->num_shmti = le32_get_bits(attr, GENMASK(15, 0));
+ ti->default_blk_ts_rate = le32_to_cpu(resp->default_blk_ts_rate);
+ }
+
+ ph->xops->xfer_put(ph, t);
+
+ return ret;
+}
+
+static void iter_tlm_prepare_message(void *message,
+ unsigned int desc_index, const void *priv)
+{
+ put_unaligned_le32(desc_index, message);
+}
+
+static int iter_de_descr_update_state(struct scmi_iterator_state *st,
+ const void *response, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_de_description *r = response;
+ struct scmi_tlm_de_priv *p = priv;
+
+ st->num_returned = le32_get_bits(r->num_desc, GENMASK(15, 0));
+ st->num_remaining = le32_get_bits(r->num_desc, GENMASK(31, 16));
+
+ if (st->rx_len < (sizeof(*r) + sizeof(r->desc[0]) * st->num_returned))
+ return -EINVAL;
+
+ /* Initialized to first descriptor */
+ p->next = (void *)r->desc;
+
+ return 0;
+}
+
+static int scmi_telemetry_de_descriptor_parse(struct telemetry_info *ti,
+ struct telemetry_de *tde,
+ void **next)
+{
+ struct scmi_telemetry_res_info *rinfo = ACCESS_PRIVATE(ti, rinfo);
+ const struct scmi_de_desc *desc = *next;
+ unsigned int grp_id;
+
+ tde->de.info->id = le32_to_cpu(desc->id);
+ grp_id = le32_to_cpu(desc->grp_id);
+ if (grp_id != SCMI_TLM_GRP_INVALID) {
+ /* Group descriptors are empty but allocated at this point */
+ if (grp_id >= ti->info.base.num_groups)
+ return -EINVAL;
+
+ /* Link to parent group */
+ tde->de.info->grp_id = grp_id;
+ tde->de.grp = &rinfo->grps[grp_id];
+ }
+
+ tde->de.info->data_sz = le32_to_cpu(desc->data_sz);
+ tde->de.info->type = GET_DE_TYPE(desc);
+ tde->de.info->unit = GET_DE_UNIT(desc);
+ tde->de.info->unit_exp = GET_DE_UNIT_EXP(desc);
+ tde->de.info->instance_id = GET_DE_INSTA_ID(desc);
+ tde->de.info->compo_instance_id = GET_COMPO_INSTA_ID(desc);
+ tde->de.info->compo_type = GET_COMPO_TYPE(desc);
+ tde->de.info->persistent = IS_PERSISTENT(desc);
+ tde->ts_type = TSTAMP_SUPPORT(desc);
+ tde->de.tstamp_support = !!tde->ts_type;
+ tde->de.fc_support = IS_FC_SUPPORTED(desc);
+ tde->de.name_support = IS_NAME_SUPPORTED(desc);
+ /* Update DE_DESCRIPTOR size for the next iteration */
+ *next += sizeof(*desc);
+
+ if (tde->ts_type == TSTAMP_LINE) {
+ u32 *line_ts_rate = *next;
+
+ tde->de.info->ts_rate = *line_ts_rate;
+
+ /* Variably sized depending on TS support */
+ *next += sizeof(*line_ts_rate);
+ } else if (tde->ts_type == TSTAMP_BLK) {
+ /* Setup default BLK TS value at first */
+ tde->de.info->ts_rate = ti->default_blk_ts_rate;
+ }
+
+ if (tde->de.fc_support) {
+ u32 size;
+ u64 phys_addr;
+ void __iomem *addr;
+ struct de_desc_fc *dfc;
+
+ dfc = *next;
+ phys_addr = le32_to_cpu(dfc->addr_low);
+ phys_addr |= (u64)le32_to_cpu(dfc->addr_high) << 32;
+
+ size = le32_to_cpu(dfc->size);
+ addr = devm_ioremap(ti->ph->dev, phys_addr, size);
+ if (!addr)
+ return -EADDRNOTAVAIL;
+
+ tde->base = addr;
+ tde->offset = 0;
+ tde->fc_size = size;
+
+ /* Add to FastChannels list */
+ list_add(&tde->item, &ti->fcs_des);
+
+ /* Variably sized depending on FC support */
+ *next += sizeof(*dfc);
+ }
+
+ if (tde->de.name_support) {
+ const char *de_name = *next;
+
+ strscpy(tde->de.info->name, de_name, SCMI_SHORT_NAME_MAX_SIZE);
+ /* Variably sized depending on name support */
+ *next += SCMI_SHORT_NAME_MAX_SIZE;
+ }
+
+ return 0;
+}
+
+static int iter_de_descr_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st,
+ void *priv)
+{
+ struct scmi_tlm_de_priv *p = priv;
+ struct telemetry_info *ti = p->ti;
+ const struct scmi_de_desc *desc = p->next;
+ struct telemetry_de *tde;
+ bool discovered = false;
+ unsigned int de_id;
+ int ret;
+
+ de_id = le32_to_cpu(desc->id);
+ /* Check if this DE has already been discovered by other means... */
+ tde = scmi_telemetry_tde_lookup(ti, de_id);
+ if (!tde) {
+ /* Create a new one */
+ tde = scmi_telemetry_tde_get(ti, de_id);
+ if (IS_ERR(tde))
+ return PTR_ERR(tde);
+
+ discovered = true;
+ } else if (tde->enumerated) {
+ /* Cannot be a duplicate of a DE already created by enumeration */
+ dev_err(ph->dev,
+ "Discovered INVALID DE with DUPLICATED ID:0x%08X\n",
+ de_id);
+ return -EINVAL;
+ }
+
+ ret = scmi_telemetry_de_descriptor_parse(ti, tde, &p->next);
+ if (ret)
+ goto err;
+
+ if (discovered) {
+ /* Register if it was not already ... */
+ ret = scmi_telemetry_tde_register(ti, tde);
+ if (ret)
+ goto err;
+
+ tde->enumerated = true;
+ }
+
+ /* Account for this DE in group num_de counter */
+ if (tde->de.grp)
+ tde->de.grp->info->num_des++;
+
+ return 0;
+
+err:
+ /* DE not enumerated at this point were created in this call */
+ if (!tde->enumerated)
+ scmi_telemetry_free_tde_put(ti, tde);
+
+ return ret;
+}
+
+static int
+scmi_telemetry_de_groups_init(struct device *dev, struct telemetry_info *ti)
+{
+ struct scmi_telemetry_res_info *rinfo = ACCESS_PRIVATE(ti, rinfo);
+
+ /* Allocate all groups DEs IDs arrays at first ... */
+ for (int i = 0; i < ti->info.base.num_groups; i++) {
+ struct scmi_telemetry_group *grp = &rinfo->grps[i];
+ size_t des_str_sz;
+
+ unsigned int *des __free(kfree) = kcalloc(grp->info->num_des,
+ sizeof(unsigned int),
+ GFP_KERNEL);
+ if (!des)
+ return -ENOMEM;
+
+ /*
+ * Max size 32bit ID string in Hex: 0xCAFECAFE
+ * - 10 digits + ' '/'\n' = 11 bytes per number
+ * - terminating NUL character
+ */
+ des_str_sz = grp->info->num_des * 11 + 1;
+ char *des_str __free(kfree) = kzalloc(des_str_sz, GFP_KERNEL);
+ if (!des_str)
+ return -ENOMEM;
+
+ grp->des = no_free_ptr(des);
+ grp->des_str = no_free_ptr(des_str);
+ /* Reset group DE counter */
+ grp->info->num_des = 0;
+ }
+
+ /* Scan DEs and populate DE IDs arrays for all groups */
+ for (int i = 0; i < rinfo->num_des; i++) {
+ struct scmi_telemetry_group *grp = rinfo->des[i]->grp;
+
+ if (!grp)
+ continue;
+
+ /*
+ * Note that, at this point, num_des is guaranteed to be
+ * sane (in-bounds) by construction.
+ */
+ grp->des[grp->info->num_des++] = i;
+ }
+
+ /* Build composing DES string */
+ for (int i = 0; i < ti->info.base.num_groups; i++) {
+ struct scmi_telemetry_group *grp = &rinfo->grps[i];
+ size_t bufsize = grp->info->num_des * 11 + 1;
+ char *buf = grp->des_str;
+
+ for (int j = 0; j < grp->info->num_des; j++) {
+ char term = j != (grp->info->num_des - 1) ? ' ' : '\0';
+ int len;
+
+ len = scnprintf(buf, bufsize, "0x%08X%c",
+ rinfo->des[grp->des[j]]->info->id, term);
+
+ buf += len;
+ bufsize -= len;
+ }
+ }
+
+ rinfo->num_groups = ti->info.base.num_groups;
+
+ return 0;
+}
+
+static int scmi_telemetry_de_descriptors_get(struct telemetry_info *ti)
+{
+ const struct scmi_protocol_handle *ph = ti->ph;
+
+ struct scmi_iterator_ops ops = {
+ .prepare_message = iter_tlm_prepare_message,
+ .update_state = iter_de_descr_update_state,
+ .process_response = iter_de_descr_process_response,
+ };
+ struct scmi_tlm_de_priv tpriv = {
+ .ti = ti,
+ .next = NULL,
+ };
+ void *iter;
+ int ret;
+
+ if (!ti->info.base.num_des)
+ return 0;
+
+ iter = ph->hops->iter_response_init(ph, &ops, ti->info.base.num_des,
+ TELEMETRY_DE_DESCRIPTION,
+ sizeof(u32), &tpriv);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+
+ ret = ph->hops->iter_response_run(iter);
+ if (ret)
+ return ret;
+
+ return scmi_telemetry_de_groups_init(ph->dev, ti);
+}
+
+struct scmi_tlm_ivl_priv {
+ struct device *dev;
+ struct scmi_tlm_intervals **intrvs;
+ unsigned int grp_id;
+ unsigned int flags;
+};
+
+static void iter_intervals_prepare_message(void *message,
+ unsigned int desc_index,
+ const void *priv)
+{
+ struct scmi_msg_telemetry_update_intervals *msg = message;
+ const struct scmi_tlm_ivl_priv *p = priv;
+
+ msg->index = cpu_to_le32(desc_index);
+ msg->group_identifier = cpu_to_le32(p->grp_id);
+ msg->flags = FIELD_PREP(GENMASK(3, 0), p->flags);
+}
+
+static int iter_intervals_update_state(struct scmi_iterator_state *st,
+ const void *response, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_update_intervals *r = response;
+
+ st->num_returned = le32_get_bits(r->flags, GENMASK(11, 0));
+ st->num_remaining = le32_get_bits(r->flags, GENMASK(31, 16));
+
+ if (st->rx_len < (sizeof(*r) + sizeof(r->intervals[0]) * st->num_returned))
+ return -EINVAL;
+
+ /*
+ * total intervals is not declared previously anywhere so we
+ * assume it's returned+remaining on first call.
+ */
+ if (!st->max_resources) {
+ struct scmi_tlm_ivl_priv *p = priv;
+ struct scmi_tlm_intervals *intrvs;
+ bool discrete;
+ int inum;
+
+ discrete = INTERVALS_DISCRETE(r->flags);
+ /* Check consistency on first call */
+ if (!discrete && (st->num_returned != 3 || st->num_remaining != 0))
+ return -EINVAL;
+
+ inum = st->num_returned + st->num_remaining;
+ intrvs = kzalloc(sizeof(*intrvs) + inum * sizeof(__u32), GFP_KERNEL);
+ if (!intrvs)
+ return -ENOMEM;
+
+ intrvs->num_intervals = inum;
+ intrvs->discrete = discrete;
+ st->max_resources = intrvs->num_intervals;
+
+ *p->intrvs = intrvs;
+ }
+
+ return 0;
+}
+
+static int
+iter_intervals_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_update_intervals *r = response;
+ struct scmi_tlm_ivl_priv *p = priv;
+ struct scmi_tlm_intervals *intrvs = *p->intrvs;
+ unsigned int idx = st->loop_idx;
+
+ intrvs->update_intervals[st->desc_index + idx] = r->intervals[idx];
+
+ return 0;
+}
+
+static int
+scmi_tlm_enumerate_update_intervals(struct telemetry_info *ti,
+ struct scmi_tlm_intervals **intervals,
+ int grp_id, unsigned int flags)
+{
+ struct scmi_iterator_ops ops = {
+ .prepare_message = iter_intervals_prepare_message,
+ .update_state = iter_intervals_update_state,
+ .process_response = iter_intervals_process_response,
+ };
+ const struct scmi_protocol_handle *ph = ti->ph;
+ struct scmi_tlm_ivl_priv ipriv = {
+ .dev = ph->dev,
+ .grp_id = grp_id,
+ .intrvs = intervals,
+ .flags = flags,
+ };
+ void *iter;
+
+ iter = ph->hops->iter_response_init(ph, &ops, 0,
+ TELEMETRY_LIST_UPDATE_INTERVALS,
+ sizeof(struct scmi_msg_telemetry_update_intervals),
+ &ipriv);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+
+ return ph->hops->iter_response_run(iter);
+}
+
+static int
+scmi_telemetry_enumerate_groups_intervals(struct telemetry_info *ti)
+{
+ struct scmi_telemetry_res_info *rinfo = ACCESS_PRIVATE(ti, rinfo);
+
+ if (!ti->info.per_group_config_support)
+ return 0;
+
+ for (int id = 0; id < rinfo->num_groups; id++) {
+ int ret;
+
+ ret = scmi_tlm_enumerate_update_intervals(ti,
+ &rinfo->grps[id].intervals,
+ id, SPECIFIC_GROUP_DES);
+ if (ret)
+ return ret;
+
+ rinfo->grps_store[id].num_intervals =
+ rinfo->grps[id].intervals->num_intervals;
+ }
+
+ return 0;
+}
+
+static void scmi_telemetry_intervals_free(void *interval)
+{
+ kfree(interval);
+}
+
+static int
+scmi_telemetry_enumerate_common_intervals(struct telemetry_info *ti)
+{
+ unsigned int flags;
+ int ret;
+
+ flags = !ti->info.per_group_config_support ?
+ ALL_DES_ANY_GROUP : ALL_DES_NO_GROUP;
+
+ ret = scmi_tlm_enumerate_update_intervals(ti, &ti->info.intervals,
+ SCMI_TLM_GRP_INVALID, flags);
+ if (ret)
+ return ret;
+
+ /* A copy for UAPI access... */
+ ti->info.base.num_intervals = ti->info.intervals->num_intervals;
+
+ /* Delegate freeing of allocated intervals to unbind time */
+ return devm_add_action_or_reset(ti->ph->dev,
+ scmi_telemetry_intervals_free,
+ ti->info.intervals);
+}
+
+static int iter_shmti_update_state(struct scmi_iterator_state *st,
+ const void *response, void *priv)
+{
+ const struct scmi_msg_resp_telemetry_shmti_list *r = response;
+
+ st->num_returned = le32_get_bits(r->num_shmti, GENMASK(15, 0));
+ st->num_remaining = le32_get_bits(r->num_shmti, GENMASK(31, 16));
+
+ if (st->rx_len < (sizeof(*r) + sizeof(r->desc[0]) * st->num_returned))
+ return -EINVAL;
+
+ return 0;
+}
+
+static inline int
+scmi_telemetry_shmti_validate(struct device *dev, struct telemetry_shmti *shmti)
+{
+ struct tdcf __iomem *tdcf = shmti->base;
+ u32 sign_start, sign_end;
+
+ sign_start = TDCF_START_SIGNATURE(tdcf);
+ sign_end = TDCF_END_SIGNATURE(SHMTI_EPLG(shmti));
+
+ if (sign_start != SIGNATURE_START || sign_end != SIGNATURE_END) {
+ dev_err(dev,
+ "BAD signature for SHMTI ID:%u @phys:%pK - START:0x%04X END:0x%04X\n",
+ shmti->id, shmti->base, sign_start, sign_end);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int iter_shmti_process_response(const struct scmi_protocol_handle *ph,
+ const void *response,
+ struct scmi_iterator_state *st,
+ void *priv)
+{
+ const struct scmi_msg_resp_telemetry_shmti_list *r = response;
+ struct telemetry_info *ti = priv;
+ struct telemetry_shmti *shmti;
+ const struct scmi_shmti_desc *desc;
+ void __iomem *addr;
+ u64 phys_addr;
+ u32 len;
+
+ desc = &r->desc[st->loop_idx];
+ shmti = &ti->shmti[st->desc_index + st->loop_idx];
+
+ shmti->id = le32_to_cpu(desc->id);
+ shmti->flags = le32_to_cpu(desc->flags);
+ phys_addr = le32_to_cpu(desc->addr_low);
+ phys_addr |= (u64)le32_to_cpu(desc->addr_high) << 32;
+
+ len = le32_to_cpu(desc->length);
+ if (len < SHMTI_MIN_SIZE) {
+ dev_err(ph->dev, "Invalid length for SHMTI ID:%u len:%u\n",
+ shmti->id, len);
+ return -EINVAL;
+ }
+
+ addr = devm_ioremap(ph->dev, phys_addr, len);
+ if (!addr)
+ return -EADDRNOTAVAIL;
+
+ shmti->base = addr;
+ shmti->len = len;
+
+ return scmi_telemetry_shmti_validate(ph->dev, shmti);
+}
+
+static int scmi_telemetry_shmti_list(const struct scmi_protocol_handle *ph,
+ struct telemetry_info *ti)
+{
+ struct scmi_iterator_ops ops = {
+ .prepare_message = iter_tlm_prepare_message,
+ .update_state = iter_shmti_update_state,
+ .process_response = iter_shmti_process_response,
+ };
+ void *iter;
+
+ iter = ph->hops->iter_response_init(ph, &ops, ti->info.base.num_des,
+ TELEMETRY_LIST_SHMTI,
+ sizeof(u32), ti);
+ if (IS_ERR(iter))
+ return PTR_ERR(iter);
+
+ return ph->hops->iter_response_run(iter);
+}
+
+static int scmi_telemetry_enumerate_shmti(struct telemetry_info *ti)
+{
+ const struct scmi_protocol_handle *ph = ti->ph;
+ int ret;
+
+ if (!ti->num_shmti)
+ return 0;
+
+ ti->shmti = devm_kcalloc(ph->dev, ti->num_shmti, sizeof(*ti->shmti),
+ GFP_KERNEL);
+ if (!ti->shmti)
+ return -ENOMEM;
+
+ ret = scmi_telemetry_shmti_list(ph, ti);
+ if (ret) {
+ dev_err(ph->dev, "Cannot get SHMTI list descriptors");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct scmi_telemetry_info *
+scmi_telemetry_info_get(const struct scmi_protocol_handle *ph)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ return &ti->info;
+}
+
+static const struct scmi_telemetry_de *
+scmi_telemetry_de_lookup(const struct scmi_protocol_handle *ph, u32 id)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ ti->res_get(ti);
+ return xa_load(&ti->xa_des, id);
+}
+
+static const struct scmi_telemetry_res_info *
+scmi_telemetry_resources_get(const struct scmi_protocol_handle *ph)
+{
+ struct telemetry_info *ti = ph->get_priv(ph);
+
+ return ti->res_get(ti);
+}
+
+static const struct scmi_telemetry_proto_ops tlm_proto_ops = {
+ .info_get = scmi_telemetry_info_get,
+ .de_lookup = scmi_telemetry_de_lookup,
+ .res_get = scmi_telemetry_resources_get,
+};
+
+/**
+ * scmi_telemetry_resources_alloc - Resources allocation
+ * @ti: A reference to the telemetry info descriptor for this instance
+ *
+ * This allocates and initializes dedicated resources for the maximum possible
+ * number of needed telemetry resources, based on information gathered from
+ * the initial enumeration: these allocations represent an upper bound on
+ * the number of discoverable telemetry resources and they will be later
+ * populated during late deferred further discovery phases.
+ *
+ * Return: 0 on Success, errno otherwise
+ */
+static int scmi_telemetry_resources_alloc(struct telemetry_info *ti)
+{
+ /* Array to hold pointers to discovered DEs */
+ struct scmi_telemetry_de **des __free(kfree) =
+ kcalloc(ti->info.base.num_des, sizeof(*des), GFP_KERNEL);
+ if (!des)
+ return -ENOMEM;
+
+ /* The allocated DE descriptors */
+ struct telemetry_de *tdes __free(kfree) =
+ kcalloc(ti->info.base.num_des, sizeof(*tdes), GFP_KERNEL);
+ if (!tdes)
+ return -ENOMEM;
+
+ /* Allocate a set of contiguous DE info descriptors. */
+ struct scmi_tlm_de_info *dei_store __free(kfree) =
+ kcalloc(ti->info.base.num_des, sizeof(*dei_store), GFP_KERNEL);
+ if (!dei_store)
+ return -ENOMEM;
+
+ /* Array to hold descriptors of discovered GROUPs */
+ struct scmi_telemetry_group *grps __free(kfree) =
+ kcalloc(ti->info.base.num_groups, sizeof(*grps), GFP_KERNEL);
+ if (!grps)
+ return -ENOMEM;
+
+ /* Allocate a set of contiguous Group info descriptors. */
+ struct scmi_tlm_grp_info *grps_store __free(kfree) =
+ kcalloc(ti->info.base.num_groups, sizeof(*grps_store), GFP_KERNEL);
+ if (!grps_store)
+ return -ENOMEM;
+
+ struct scmi_telemetry_res_info *rinfo __free(kfree) =
+ kzalloc(sizeof(*rinfo), GFP_KERNEL);
+ if (!rinfo)
+ return -ENOMEM;
+
+ mutex_init(&ti->free_mtx);
+ INIT_LIST_HEAD(&ti->free_des);
+ for (int i = 0; i < ti->info.base.num_des; i++) {
+ mutex_init(&tdes[i].mtx);
+ /* Bind contiguous DE info structures */
+ tdes[i].de.info = &dei_store[i];
+ list_add_tail(&tdes[i].item, &ti->free_des);
+ }
+
+ for (int i = 0; i < ti->info.base.num_groups; i++) {
+ grps_store[i].id = i;
+ /* Bind contiguous Group info struct */
+ grps[i].info = &grps_store[i];
+ }
+
+ INIT_LIST_HEAD(&ti->fcs_des);
+
+ ti->tdes = no_free_ptr(tdes);
+
+ rinfo->des = no_free_ptr(des);
+ rinfo->dei_store = no_free_ptr(dei_store);
+ rinfo->grps = no_free_ptr(grps);
+ rinfo->grps_store = no_free_ptr(grps_store);
+
+ ACCESS_PRIVATE(ti, rinfo) = no_free_ptr(rinfo);
+
+ return 0;
+}
+
+static void scmi_telemetry_resources_free(void *arg)
+{
+ struct telemetry_info *ti = arg;
+ struct scmi_telemetry_res_info *rinfo = ACCESS_PRIVATE(ti, rinfo);
+
+ kfree(ti->tdes);
+ kfree(rinfo->des);
+ kfree(rinfo->dei_store);
+ kfree(rinfo->grps);
+ kfree(rinfo->grps_store);
+
+ kfree(rinfo);
+
+ ACCESS_PRIVATE(ti, rinfo) = NULL;
+}
+
+static struct scmi_telemetry_res_info *
+__scmi_telemetry_resources_get(struct telemetry_info *ti)
+{
+ return ACCESS_PRIVATE(ti, rinfo);
+}
+
+/**
+ * scmi_telemetry_resources_enumerate - Enumeration helper
+ * @ti: A reference to the telemetry info descriptor for this instance
+ *
+ * This helper is configured to be called once on the first enumeration
+ * attempt, when triggered by invoking ti->res_get() from somewhere else.
+ * Once run it substitues itself in ti->res_get() with the simple accessor
+ * __scmi_telemetry_resources_get, which returns a descriptor to the resources
+ * that were possibly discovered.
+ *
+ * Note that, while it attempts to fully enumerate Data Events and Groups, it
+ * does NOT fail when such enumerations fail, instead it simply gives up with
+ * the end result that only a partially populated, but consistent, resources
+ * descriptor will be returned; in such a case the incomplete descriptor will
+ * be marked as NOT fully_enumerated: this design enables the kernel to deal
+ * with badly implemented out-of-spec firmware support while keep on providing
+ * a minimal sane, albeit possibly incomplete, set of telemetry respources.
+ *
+ * Return: A reference to a fully or partially populated resources descriptor
+ */
+static struct scmi_telemetry_res_info *
+scmi_telemetry_resources_enumerate(struct telemetry_info *ti)
+{
+ struct scmi_telemetry_res_info *rinfo = ACCESS_PRIVATE(ti, rinfo);
+ struct device *dev = ti->ph->dev;
+ int ret;
+
+ /*
+ * Ensure this init function can be called only once and
+ * handles properly concurrent calls.
+ */
+ if (atomic_cmpxchg(&ti->rinfo_initializing, 0, 1)) {
+ if (!completion_done(&ti->rinfo_initdone))
+ wait_for_completion(&ti->rinfo_initdone);
+ goto out;
+ }
+
+ ret = scmi_telemetry_de_descriptors_get(ti);
+ if (ret) {
+ dev_err(dev, FW_BUG "Cannot enumerate DEs resources. Carry-on.\n");
+ goto done;
+ }
+
+ ret = scmi_telemetry_enumerate_groups_intervals(ti);
+ if (ret) {
+ dev_err(dev, FW_BUG "Cannot enumerate group intervals. Carry-on.\n");
+ goto done;
+ }
+
+ /* If we got here, the enumeration was fully successful */
+ rinfo->fully_enumerated = true;
+done:
+ /* Disable initialization permanently */
+ smp_store_mb(ti->res_get, __scmi_telemetry_resources_get);
+ complete_all(&ti->rinfo_initdone);
+
+out:
+ return rinfo;
+}
+
+/**
+ * scmi_telemetry_instance_init - Instance initializer
+ * @ti: A reference to the telemetry info descriptor for this instance
+ *
+ * Note that this allocates and initialize all the resources possibly needed
+ * and then setups the @scmi_telemetry_resources_enumerate helper as the
+ * default method for the first call to ti->res_get(): this mechanism enables
+ * the possibility of optionally implementing deferred enumeration policies
+ * which optionally delay the discovery phase and related SCMI message exchanges
+ * to a later point in time.
+ *
+ * Return: 0 on Success, errno otherwise
+ */
+static int scmi_telemetry_instance_init(struct telemetry_info *ti)
+{
+ int ret;
+
+ /* Allocate and Initialize on first call... */
+ ret = scmi_telemetry_resources_alloc(ti);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(ti->ph->dev,
+ scmi_telemetry_resources_free, ti);
+ if (ret)
+ return ret;
+
+ xa_init(&ti->xa_des);
+ /* Setup resources lazy initialization */
+ atomic_set(&ti->rinfo_initializing, 0);
+ init_completion(&ti->rinfo_initdone);
+ /* Ensure the new res_get() operation is visible after this point */
+ smp_store_mb(ti->res_get, scmi_telemetry_resources_enumerate);
+
+ return 0;
+}
+
+static int scmi_telemetry_protocol_init(const struct scmi_protocol_handle *ph)
+{
+ struct device *dev = ph->dev;
+ struct telemetry_info *ti;
+ int ret;
+
+ dev_dbg(dev, "Telemetry Version %d.%d\n",
+ PROTOCOL_REV_MAJOR(ph->version), PROTOCOL_REV_MINOR(ph->version));
+
+ ti = devm_kzalloc(dev, sizeof(*ti), GFP_KERNEL);
+ if (!ti)
+ return -ENOMEM;
+
+ ti->ph = ph;
+
+ ret = scmi_telemetry_protocol_attributes_get(ti);
+ if (ret) {
+ dev_err(dev, FW_BUG "Cannot retrieve protocol attributes. Abort.\n");
+ return ret;
+ }
+
+ ret = scmi_telemetry_instance_init(ti);
+ if (ret) {
+ dev_err(dev, "Cannot initialize instance. Abort.\n");
+ return ret;
+ }
+
+ ret = scmi_telemetry_enumerate_common_intervals(ti);
+ if (ret)
+ dev_warn(dev, FW_BUG "Cannot enumerate update intervals. Carry-on.\n");
+
+ ret = scmi_telemetry_enumerate_shmti(ti);
+ if (ret) {
+ dev_err(dev, FW_BUG "Cannot enumerate SHMTIs. Abort.\n");
+ return ret;
+ }
+
+ ti->info.base.version = ph->version;
+
+ return ph->set_priv(ph, ti);
+}
+
+static const struct scmi_protocol scmi_telemetry = {
+ .id = SCMI_PROTOCOL_TELEMETRY,
+ .owner = THIS_MODULE,
+ .instance_init = &scmi_telemetry_protocol_init,
+ .ops = &tlm_proto_ops,
+ .supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION,
+};
+
+DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(telemetry, scmi_telemetry)
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index aafaac1496b0..fcb45bd4b44c 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -2,17 +2,21 @@
/*
* SCMI Message Protocol driver header
*
- * Copyright (C) 2018-2021 ARM Ltd.
+ * Copyright (C) 2018-2026 ARM Ltd.
*/
#ifndef _LINUX_SCMI_PROTOCOL_H
#define _LINUX_SCMI_PROTOCOL_H
#include <linux/bitfield.h>
+#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/notifier.h>
#include <linux/types.h>
+#include <uapi/linux/limits.h>
+#include <uapi/linux/scmi.h>
+
#define SCMI_MAX_STR_SIZE 64
#define SCMI_SHORT_NAME_MAX_SIZE 16
#define SCMI_MAX_NUM_RATES 16
@@ -820,6 +824,134 @@ struct scmi_pinctrl_proto_ops {
int (*pin_free)(const struct scmi_protocol_handle *ph, u32 pin);
};
+enum scmi_telemetry_de_type {
+ SCMI_TLM_DE_TYPE_USPECIFIED,
+ SCMI_TLM_DE_TYPE_ACCUMUL_IDLE_RESIDENCY,
+ SCMI_TLM_DE_TYPE_ACCUMUL_IDLE_COUNTS,
+ SCMI_TLM_DE_TYPE_ACCUMUL_OTHERS,
+ SCMI_TLM_DE_TYPE_INSTA_IDLE_STATE,
+ SCMI_TLM_DE_TYPE_INSTA_OTHERS,
+ SCMI_TLM_DE_TYPE_AVERAGE,
+ SCMI_TLM_DE_TYPE_STATUS,
+ SCMI_TLM_DE_TYPE_RESERVED_START,
+ SCMI_TLM_DE_TYPE_RESERVED_END = 0xef,
+ SCMI_TLM_DE_TYPE_OEM_START = 0xf0,
+ SCMI_TLM_DE_TYPE_OEM_END = 0xff,
+};
+
+enum scmi_telemetry_compo_type {
+ SCMI_TLM_COMPO_TYPE_USPECIFIED,
+ SCMI_TLM_COMPO_TYPE_CPU,
+ SCMI_TLM_COMPO_TYPE_CLUSTER,
+ SCMI_TLM_COMPO_TYPE_GPU,
+ SCMI_TLM_COMPO_TYPE_NPU,
+ SCMI_TLM_COMPO_TYPE_INTERCONNECT,
+ SCMI_TLM_COMPO_TYPE_MEM_CNTRL,
+ SCMI_TLM_COMPO_TYPE_L1_CACHE,
+ SCMI_TLM_COMPO_TYPE_L2_CACHE,
+ SCMI_TLM_COMPO_TYPE_L3_CACHE,
+ SCMI_TLM_COMPO_TYPE_LL_CACHE,
+ SCMI_TLM_COMPO_TYPE_SYS_CACHE,
+ SCMI_TLM_COMPO_TYPE_DISP_CNTRL,
+ SCMI_TLM_COMPO_TYPE_IPU,
+ SCMI_TLM_COMPO_TYPE_CHIPLET,
+ SCMI_TLM_COMPO_TYPE_PACKAGE,
+ SCMI_TLM_COMPO_TYPE_SOC,
+ SCMI_TLM_COMPO_TYPE_SYSTEM,
+ SCMI_TLM_COMPO_TYPE_SMCU,
+ SCMI_TLM_COMPO_TYPE_ACCEL,
+ SCMI_TLM_COMPO_TYPE_BATTERY,
+ SCMI_TLM_COMPO_TYPE_CHARGER,
+ SCMI_TLM_COMPO_TYPE_PMIC,
+ SCMI_TLM_COMPO_TYPE_BOARD,
+ SCMI_TLM_COMPO_TYPE_MEMORY,
+ SCMI_TLM_COMPO_TYPE_PERIPH,
+ SCMI_TLM_COMPO_TYPE_PERIPH_SUBC,
+ SCMI_TLM_COMPO_TYPE_LID,
+ SCMI_TLM_COMPO_TYPE_DISPLAY,
+ SCMI_TLM_COMPO_TYPE_RESERVED_START = 0x1d,
+ SCMI_TLM_COMPO_TYPE_RESERVED_END = 0xdf,
+ SCMI_TLM_COMPO_TYPE_OEM_START = 0xe0,
+ SCMI_TLM_COMPO_TYPE_OEM_END = 0xff,
+};
+
+#define SCMI_TLM_GET_UPDATE_INTERVAL_SECS(x) \
+ (le32_get_bits((x), GENMASK(20, 5)))
+#define SCMI_TLM_GET_UPDATE_INTERVAL_EXP(x) (sign_extend32((x), 4))
+
+#define SCMI_TLM_GET_UPDATE_INTERVAL(x) (FIELD_GET(GENMASK(20, 0), (x)))
+#define SCMI_TLM_BUILD_UPDATE_INTERVAL(s, e) \
+ (FIELD_PREP(GENMASK(20, 5), (s)) | FIELD_PREP(GENMASK(4, 0), (e)))
+
+enum scmi_telemetry_collection {
+ SCMI_TLM_ONDEMAND,
+ SCMI_TLM_NOTIFICATION,
+ SCMI_TLM_SINGLE_READ,
+};
+
+#define SCMI_TLM_GRP_INVALID 0xFFFFFFFF
+struct scmi_telemetry_group {
+ bool enabled;
+ bool tstamp_enabled;
+ unsigned int *des;
+ char *des_str;
+ struct scmi_tlm_grp_info *info;
+ unsigned int active_update_interval;
+ struct scmi_tlm_intervals *intervals;
+ enum scmi_telemetry_collection current_mode;
+};
+
+struct scmi_telemetry_de {
+ bool tstamp_support;
+ bool fc_support;
+ bool name_support;
+ struct scmi_tlm_de_info *info;
+ struct scmi_telemetry_group *grp;
+ bool enabled;
+ bool tstamp_enabled;
+};
+
+struct scmi_telemetry_res_info {
+ bool fully_enumerated;
+ unsigned int num_des;
+ struct scmi_telemetry_de **des;
+ struct scmi_tlm_de_info *dei_store;
+ unsigned int num_groups;
+ struct scmi_telemetry_group *grps;
+ struct scmi_tlm_grp_info *grps_store;
+};
+
+struct scmi_telemetry_info {
+ bool single_read_support;
+ bool continuos_update_support;
+ bool per_group_config_support;
+ bool reset_support;
+ bool fc_support;
+ struct scmi_tlm_base_info base;
+ unsigned int active_update_interval;
+ struct scmi_tlm_intervals *intervals;
+ bool enabled;
+ bool notif_enabled;
+ enum scmi_telemetry_collection current_mode;
+};
+
+/**
+ * struct scmi_telemetry_proto_ops - represents the various operations provided
+ * by SCMI Telemetry Protocol
+ *
+ * @info_get: get the general Telemetry information.
+ * @de_lookup: get a specific DE descriptor from the DE id.
+ * @res_get: get a reference to the Telemetry resources descriptor.
+ */
+struct scmi_telemetry_proto_ops {
+ const struct scmi_telemetry_info __must_check *(*info_get)
+ (const struct scmi_protocol_handle *ph);
+ const struct scmi_telemetry_de __must_check *(*de_lookup)
+ (const struct scmi_protocol_handle *ph, u32 id);
+ const struct scmi_telemetry_res_info __must_check *(*res_get)
+ (const struct scmi_protocol_handle *ph);
+};
+
/**
* struct scmi_notify_ops - represents notifications' operations provided by
* SCMI core
@@ -926,6 +1058,7 @@ enum scmi_std_protocol {
SCMI_PROTOCOL_VOLTAGE = 0x17,
SCMI_PROTOCOL_POWERCAP = 0x18,
SCMI_PROTOCOL_PINCTRL = 0x19,
+ SCMI_PROTOCOL_TELEMETRY = 0x1b,
};
enum scmi_system_events {
--
2.53.0
^ permalink raw reply related
* [PATCH v3 07/24] include: trace: Add Telemetry trace events
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
brauner, souvik.chakravarty, Cristian Marussi
In-Reply-To: <20260329163337.637393-1-cristian.marussi@arm.com>
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 <cristian.marussi@arm.com>
---
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 @@
#include <linux/tracepoint.h>
-#define TRACE_SCMI_MAX_TAG_LEN 6
+#define TRACE_SCMI_MAX_TAG_LEN 6
+#define TRACE_SCMI_TLM_MAX_TAG_LEN 16
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 = de_id;
+ strscpy(__entry->tag, tag, TRACE_SCMI_TLM_MAX_TAG_LEN);
+ __entry->startm = startm;
+ __entry->endm = endm;
+ ),
+
+ TP_printk("de_id=0x%llX [%s] - startm=%016llX endm=%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 = ts;
+ __entry->de_id = de_id;
+ __entry->value = value;
+ strscpy(__entry->tag, tag, TRACE_SCMI_TLM_MAX_TAG_LEN);
+ ),
+
+ TP_printk("ts=%llu de_id=0x%04llX value=%016llu [%s]",
+ __entry->ts, __entry->de_id, __entry->value, __entry->tag)
+);
+
#endif /* _TRACE_SCMI_H */
/* This part must be outside protection */
--
2.53.0
^ permalink raw reply related
* [PATCH v3 05/24] uapi: Add ARM SCMI definitions
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
brauner, souvik.chakravarty, Cristian Marussi
In-Reply-To: <20260329163337.637393-1-cristian.marussi@arm.com>
Add a number of structures and ioctls definitions used by the ARM
SCMI Telemetry protocol.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2 --> v3
- Change tstamp_exp tp ts_rate
- renamed num -> num_intervals in scmi_tlm_interval
- added padding in scmi_tlm_de_sample to avoid packing issues on 32bit
v1 --> v2
- Added proper __counted_by marks
- Fixed a few dox comments
- Renamed reserved[] fields to pad[]
---
MAINTAINERS | 1 +
include/uapi/linux/scmi.h | 289 ++++++++++++++++++++++++++++++++++++++
2 files changed, 290 insertions(+)
create mode 100644 include/uapi/linux/scmi.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 7d10988cbc62..dcf2c97e4e5c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -25708,6 +25708,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
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..abf68bb99960
--- /dev/null
+++ b/include/uapi/linux/scmi.h
@@ -0,0 +1,289 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Copyright (C) 2026 ARM Ltd.
+ */
+#ifndef _UAPI_LINUX_SCMI_H
+#define _UAPI_LINUX_SCMI_H
+
+/*
+ * Userspace interface SCMI Telemetry
+ */
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+#define SCMI_TLM_DE_IMPL_MAX_DWORDS 4
+
+#define SCMI_TLM_GRP_INVALID 0xFFFFFFFF
+
+/**
+ * scmi_tlm_base_info - Basic info about an instance
+ *
+ * @version: SCMI Telemetry protocol version
+ * @de_impl_version: SCMI Telemetry DE implementation revision
+ * @num_de: Number of defined DEs
+ * @num_groups Number of defined DEs groups
+ * @num_intervals: Number of update intervals available (instance-level)
+ * @flags: Instance specific feature-support bitmap
+ *
+ * Used by:
+ * RO - SCMI_TLM_GET_INFO
+ *
+ * Supported by:
+ * control/
+ */
+struct scmi_tlm_base_info {
+ __u32 version;
+ __u32 de_impl_version[SCMI_TLM_DE_IMPL_MAX_DWORDS];
+ __u32 num_des;
+ __u32 num_groups;
+ __u32 num_intervals;
+ __u32 flags;
+#define SCMI_TLM_CAN_RESET (1 << 0)
+};
+
+/**
+ * scmi_tlm_config - Whole instance or group configuration
+ *
+ * @enable: Enable/Disable Telemetry for the whole instance or the group.
+ * @t_enable: Enable/Disable timestamping for all the DEs belonging to a group.
+ * @pad: Padding fields to enforce alignment.
+ * @current_update_interval: Get/Set currently active update interval for the
+ * whole instance or a group.
+ *
+ * Used by:
+ * RO - SCMI_TLM_GET_CFG
+ * WO - SCMI_TLM_SET_CFG
+ *
+ * Supported by:
+ * control/
+ * groups/<N>/control
+ */
+struct scmi_tlm_config {
+ __u8 enable;
+ __u8 t_enable;
+ __u8 pad[2];
+ __u32 current_update_interval;
+};
+
+/**
+ * scmi_tlm_intervals - Update intervals descriptor
+ *
+ * @discrete: Flag to indicate the nature of the intervals described in
+ * @update_intervals.
+ * When 'false' @update_intervals is a triplet: min/max/step
+ * @pad: Padding fields to enforce alignment.
+ * @num_intervals: Number of entries of @update_intervals
+ * @update_intervals: A variably-sized array containing the update intervals
+ *
+ * Used by:
+ * RW - SCMI_TLM_GET_INTRVS
+ *
+ * Supported by:
+ * control/
+ * groups/<N>/control
+ */
+struct scmi_tlm_intervals {
+ __u8 discrete;
+ __u8 pad[3];
+ __u32 num_intervals;
+#define SCMI_TLM_UPDATE_INTVL_SEGMENT_LOW 0
+#define SCMI_TLM_UPDATE_INTVL_SEGMENT_HIGH 1
+#define SCMI_TLM_UPDATE_INTVL_SEGMENT_STEP 2
+ __u32 update_intervals[] __counted_by(num_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
+ * @ts_rate: Clock rate in kHz used to generate the DE timestamp
+ * @instance_id: DE instance ID
+ * @compo_instance_id: DE component instance ID
+ * @compo_type: Type of component which is associated to this DE
+ * @persistent: Data value for this DE survives reboot (non-cold ones)
+ * @name: Optional name of this DE
+ *
+ * Used to get the full description of a DE: it reflects DE Descriptors
+ * definitions in 3.12.4.6.
+ *
+ * Used by:
+ * RW - SCMI_TLM_GET_DE_INFO
+ *
+ * Supported by:
+ * control/
+ */
+struct scmi_tlm_de_info {
+ __u32 id;
+ __u32 grp_id;
+ __u32 data_sz;
+ __u32 type;
+ __u32 unit;
+ __s32 unit_exp;
+ __s32 ts_rate;
+ __u32 instance_id;
+ __u32 compo_instance_id;
+ __u32 compo_type;
+ __u32 persistent;
+ __u8 name[16];
+};
+
+/**
+ * scmi_tlm_des_list - List of all defined DEs
+ *
+ * @num_des: Number of entries in @des
+ * @des: An array containing descriptors for all defined DEs
+ *
+ * Used by:
+ * RW - SCMI_TLM_GET_DE_LIST
+ *
+ * Supported by:
+ * control/
+ */
+struct scmi_tlm_des_list {
+ __u32 num_des;
+ struct scmi_tlm_de_info des[] __counted_by(num_des);
+};
+
+/**
+ * scmi_tlm_de_sample - A DE reading
+ *
+ * @id: DE identifier
+ * @pad: Padding fields to enforce alignment.
+ * @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;
+ __u32 pad;
+ __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/<N>/control
+ */
+struct scmi_tlm_data_read {
+ __u32 num_samples;
+ struct scmi_tlm_de_sample samples[] __counted_by(num_samples);
+};
+
+/**
+ * scmi_tlm_grp_info - DE-group descriptor
+ *
+ * @id: Group ID number
+ * @num_des: Number of DEs part of this group
+ * @num_intervals: Number of update intervals supported. Zero if group does not
+ * support per-group update interval configuration.
+ *
+ * Used by:
+ * RO - SCMI_TLM_GET_GRP_INFO
+ *
+ * Supported by:
+ * groups/<N>control/
+ */
+struct scmi_tlm_grp_info {
+ __u32 id;
+ __u32 num_des;
+ __u32 num_intervals;
+};
+
+/**
+ * scmi_tlm_grps_list - DE-groups List
+ *
+ * @num_grps: Number of entries returned in @grps
+ * @grps: An array containing descriptors for all defined DE Groups
+ *
+ * Used by:
+ * RW - SCMI_TLM_GET_GRP_LIST
+ *
+ * Supported by:
+ * control/
+ */
+struct scmi_tlm_grps_list {
+ __u32 num_grps;
+ struct scmi_tlm_grp_info grps[] __counted_by(num_grps);
+};
+
+/**
+ * scmi_tlm_grp_desc - Group descriptor
+ *
+ * @num_des: Number of DEs part of this group
+ * @composing_des: An array containing the DE IDs that belongs to this group.
+ *
+ * Used by:
+ * RW - SCMI_TLM_GET_GRP_DESC
+ *
+ * Supported by:
+ * groups/<N>control/
+ */
+struct scmi_tlm_grp_desc {
+ __u32 num_des;
+ __u32 composing_des[] __counted_by(num_des);
+};
+
+#define SCMI 0xF1
+
+#define SCMI_TLM_GET_INFO _IOR(SCMI, 0x00, struct scmi_tlm_base_info)
+#define SCMI_TLM_GET_CFG _IOR(SCMI, 0x01, struct scmi_tlm_config)
+#define SCMI_TLM_SET_CFG _IOW(SCMI, 0x02, struct scmi_tlm_config)
+#define SCMI_TLM_GET_INTRVS _IOWR(SCMI, 0x03, struct scmi_tlm_intervals)
+#define SCMI_TLM_GET_DE_CFG _IOWR(SCMI, 0x04, struct scmi_tlm_de_config)
+#define SCMI_TLM_SET_DE_CFG _IOWR(SCMI, 0x05, struct scmi_tlm_de_config)
+#define SCMI_TLM_GET_DE_INFO _IOWR(SCMI, 0x06, struct scmi_tlm_de_info)
+#define SCMI_TLM_GET_DE_LIST _IOWR(SCMI, 0x07, struct scmi_tlm_des_list)
+#define SCMI_TLM_GET_DE_VALUE _IOWR(SCMI, 0x08, struct scmi_tlm_de_sample)
+#define SCMI_TLM_SET_ALL_CFG _IOW(SCMI, 0x09, struct scmi_tlm_de_config)
+#define SCMI_TLM_GET_GRP_LIST _IOWR(SCMI, 0x0A, struct scmi_tlm_grps_list)
+#define SCMI_TLM_GET_GRP_INFO _IOR(SCMI, 0x0B, struct scmi_tlm_grp_info)
+#define SCMI_TLM_GET_GRP_DESC _IOWR(SCMI, 0x0C, struct scmi_tlm_grp_desc)
+#define SCMI_TLM_SINGLE_SAMPLE _IOWR(SCMI, 0x0D, struct scmi_tlm_data_read)
+#define SCMI_TLM_BULK_READ _IOWR(SCMI, 0x0E, struct scmi_tlm_data_read)
+
+#endif /* _UAPI_LINUX_SCMI_H */
--
2.53.0
^ permalink raw reply related
* [PATCH v3 06/24] dt-bindings: firmware: arm,scmi: Add support for telemetry protocol
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
brauner, souvik.chakravarty, Cristian Marussi, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, devicetree
In-Reply-To: <20260329163337.637393-1-cristian.marussi@arm.com>
Add new SCMI v4.0 Telemetry protocol bindings definitions.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
Cc: Rob Herring <robh@kernel.org>
Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
Cc: Conor Dooley <conor+dt@kernel.org>
Cc: devicetree@vger.kernel.org
---
Documentation/devicetree/bindings/firmware/arm,scmi.yaml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/Documentation/devicetree/bindings/firmware/arm,scmi.yaml b/Documentation/devicetree/bindings/firmware/arm,scmi.yaml
index be817fd9cc34..e936ae7c0fb9 100644
--- a/Documentation/devicetree/bindings/firmware/arm,scmi.yaml
+++ b/Documentation/devicetree/bindings/firmware/arm,scmi.yaml
@@ -298,6 +298,14 @@ properties:
reg:
const: 0x19
+ protocol@1B:
+ $ref: '#/$defs/protocol-node'
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ const: 0x1B
+
patternProperties:
'-pins$':
type: object
--
2.53.0
^ permalink raw reply related
* [PATCH v3 04/24] firmware: arm_scmi: Allow protocols to register for notifications
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
brauner, souvik.chakravarty, Cristian Marussi
In-Reply-To: <20260329163337.637393-1-cristian.marussi@arm.com>
Allow protocols themselves to register for their own notifications and
provide their own notifier callbacks.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2-->v3
- split out unrelated changes on event sizing
v1-->v2
- Fixed multiline comment format
---
drivers/firmware/arm_scmi/common.h | 6 ++++++
drivers/firmware/arm_scmi/driver.c | 12 ++++++++++++
drivers/firmware/arm_scmi/notify.c | 6 +++---
drivers/firmware/arm_scmi/protocols.h | 6 ++++++
4 files changed, 27 insertions(+), 3 deletions(-)
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index 44af2018e21d..7989c79e9bd9 100644
--- a/drivers/firmware/arm_scmi/common.h
+++ b/drivers/firmware/arm_scmi/common.h
@@ -17,6 +17,9 @@
#include <linux/hashtable.h>
#include <linux/list.h>
#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/property.h>
#include <linux/refcount.h>
#include <linux/scmi_protocol.h>
#include <linux/spinlock.h>
@@ -529,5 +532,8 @@ static struct platform_driver __drv = { \
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 26f192b8d7a9..c4aefbeead62 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -1655,6 +1655,17 @@ static void *scmi_get_protocol_priv(const struct scmi_protocol_handle *ph)
return pi->priv;
}
+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 = 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 = {
.xfer_get_init = xfer_get_init,
.reset_rx_to_maxsz = reset_rx_to_maxsz,
@@ -2223,6 +2234,7 @@ scmi_alloc_init_protocol_instance(struct scmi_info *info,
pi->ph.hops = &helpers_ops;
pi->ph.set_priv = scmi_set_protocol_priv;
pi->ph.get_priv = scmi_get_protocol_priv;
+ pi->ph.notifier_register = scmi_register_instance_notifier;
refcount_set(&pi->users, 1);
/*
diff --git a/drivers/firmware/arm_scmi/notify.c b/drivers/firmware/arm_scmi/notify.c
index 3e4c97ab7b61..2a8efdf0bab8 100644
--- a/drivers/firmware/arm_scmi/notify.c
+++ b/drivers/firmware/arm_scmi/notify.c
@@ -1389,9 +1389,9 @@ static int scmi_event_handler_enable_events(struct scmi_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 = 0;
u32 evt_key;
diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h
index f51245aca259..3e7b6f8aa72c 100644
--- a/drivers/firmware/arm_scmi/protocols.h
+++ b/drivers/firmware/arm_scmi/protocols.h
@@ -166,6 +166,9 @@ struct scmi_proto_helpers_ops;
* can be used by the protocol implementation to generate SCMI messages.
* @set_priv: A method to set protocol private data for this instance.
* @get_priv: A method to get protocol private data previously set.
+ * @notifier_register: A method to register interest for notifications from
+ * within a protocol implementation unit: notifiers can
+ * be registered only for the same protocol.
*
* This structure represents a protocol initialized against specific SCMI
* instance and it will be used as follows:
@@ -185,6 +188,9 @@ struct scmi_protocol_handle {
const struct scmi_proto_helpers_ops *hops;
int (*set_priv)(const struct scmi_protocol_handle *ph, void *priv);
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);
};
/**
--
2.53.0
^ permalink raw reply related
* [PATCH v3 03/24] firmware: arm_scmi: Allow registration of unknown-size events/reports
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
brauner, souvik.chakravarty, Cristian Marussi
In-Reply-To: <20260329163337.637393-1-cristian.marussi@arm.com>
Allow protocols to register events with build-time unknown sizes: such
events can be declared zero-sized and let the core SCMI stack perform the
needed safe-net boundary checks based on the configured transport size.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
v2 --> v3
- split out of previous patch on protocol notifier
- use max() instead of max_t()
---
drivers/firmware/arm_scmi/notify.c | 24 +++++++++++++++++++-----
drivers/firmware/arm_scmi/notify.h | 8 ++++++--
2 files changed, 25 insertions(+), 7 deletions(-)
diff --git a/drivers/firmware/arm_scmi/notify.c b/drivers/firmware/arm_scmi/notify.c
index 40ec184eedae..3e4c97ab7b61 100644
--- a/drivers/firmware/arm_scmi/notify.c
+++ b/drivers/firmware/arm_scmi/notify.c
@@ -595,7 +595,13 @@ int scmi_notify(const struct scmi_handle *handle, u8 proto_id, u8 evt_id,
if (!r_evt)
return -EINVAL;
- 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;
}
@@ -754,7 +760,7 @@ int scmi_register_protocol_events(const struct scmi_handle *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 = 0;
struct scmi_registered_events_desc *pd;
@@ -769,6 +775,8 @@ int scmi_register_protocol_events(const struct scmi_handle *handle, u8 proto_id,
if (!ni)
return -ENOMEM;
+ max_msg_sz = ph->hops->get_max_msg_size(ph);
+
/* num_sources cannot be <= 0 */
if (ee->num_sources) {
num_sources = ee->num_sources;
@@ -781,8 +789,13 @@ int scmi_register_protocol_events(const struct scmi_handle *handle, u8 proto_id,
}
evt = ee->evts;
- for (i = 0; i < ee->num_events; i++)
- payld_sz = max_t(size_t, payld_sz, evt[i].max_payld_sz);
+ for (i = 0; i < ee->num_events; i++) {
+ if (evt[i].max_payld_sz == 0) {
+ payld_sz = max_msg_sz;
+ break;
+ }
+ payld_sz = max(payld_sz, evt[i].max_payld_sz);
+ }
payld_sz += sizeof(struct scmi_event_header);
pd = scmi_allocate_registered_events_desc(ni, proto_id, ee->queue_sz,
@@ -811,7 +824,8 @@ int scmi_register_protocol_events(const struct scmi_handle *handle, u8 proto_id,
mutex_init(&r_evt->sources_mtx);
r_evt->report = 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;
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 message
- * @max_report_sz: Max possible size for the report of a notification message
+ * @max_payld_sz: Max possible size for the payload of a notification message.
+ * 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 message.
+ * Set to zero to use the maximum payload size allowed by the
+ * transport.
*
* Each SCMI protocol, during its initialization phase, can describe the events
* it wishes to support in a few struct scmi_event and pass them to the core
--
2.53.0
^ permalink raw reply related
* [PATCH v3 02/24] firmware: arm_scmi: Reduce the scope of protocols mutex
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
brauner, souvik.chakravarty, Cristian Marussi
In-Reply-To: <20260329163337.637393-1-cristian.marussi@arm.com>
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 <cristian.marussi@arm.com>
---
v1-->v2
- Fixed improper mixed usage of cleanup and goto constructs
---
drivers/firmware/arm_scmi/driver.c | 50 ++++++++++++++----------------
1 file changed, 24 insertions(+), 26 deletions(-)
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index 3e76a3204ba4..26f192b8d7a9 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
#include <linux/bitmap.h>
+#include <linux/cleanup.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/export.h>
@@ -2190,7 +2191,6 @@ static void scmi_protocol_version_initialize(struct device *dev,
* all resources management is handled via a dedicated per-protocol devres
* group.
*
- * Context: Assumes to be called with @protocols_mtx already acquired.
* Return: A reference to a freshly allocated and initialized protocol instance
* or ERR_PTR on failure. On failure the @proto reference is at first
* put using @scmi_protocol_put() before releasing all the devres group.
@@ -2236,8 +2236,10 @@ scmi_alloc_init_protocol_instance(struct scmi_info *info,
if (ret)
goto clean;
- ret = idr_alloc(&info->protocols, pi, proto->id, proto->id + 1,
- GFP_KERNEL);
+ /* Finally register the initialized protocol */
+ mutex_lock(&info->protocols_mtx);
+ ret = idr_alloc(&info->protocols, pi, proto->id, proto->id + 1, GFP_KERNEL);
+ mutex_unlock(&info->protocols_mtx);
if (ret != proto->id)
goto clean;
@@ -2284,27 +2286,25 @@ scmi_alloc_init_protocol_instance(struct scmi_info *info,
static struct scmi_protocol_instance * __must_check
scmi_get_protocol_instance(const struct scmi_handle *handle, u8 protocol_id)
{
- struct scmi_protocol_instance *pi;
struct scmi_info *info = handle_to_scmi_info(handle);
+ const struct scmi_protocol *proto;
- mutex_lock(&info->protocols_mtx);
- pi = idr_find(&info->protocols, protocol_id);
-
- if (pi) {
- refcount_inc(&pi->users);
- } else {
- const struct scmi_protocol *proto;
+ scoped_guard(mutex, &info->protocols_mtx) {
+ struct scmi_protocol_instance *pi;
- /* Fails if protocol not registered on bus */
- proto = scmi_protocol_get(protocol_id, &info->version);
- if (proto)
- pi = scmi_alloc_init_protocol_instance(info, proto);
- else
- pi = ERR_PTR(-EPROBE_DEFER);
+ pi = idr_find(&info->protocols, protocol_id);
+ if (pi) {
+ refcount_inc(&pi->users);
+ return pi;
+ }
}
- mutex_unlock(&info->protocols_mtx);
- return pi;
+ /* Fails if protocol not registered on bus */
+ proto = scmi_protocol_get(protocol_id, &info->version);
+ if (!proto)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ return scmi_alloc_init_protocol_instance(info, proto);
}
/**
@@ -2335,10 +2335,11 @@ void scmi_protocol_release(const struct scmi_handle *handle, u8 protocol_id)
struct scmi_info *info = handle_to_scmi_info(handle);
struct scmi_protocol_instance *pi;
- mutex_lock(&info->protocols_mtx);
- pi = idr_find(&info->protocols, protocol_id);
- if (WARN_ON(!pi))
- goto out;
+ scoped_guard(mutex, &info->protocols_mtx) {
+ pi = idr_find(&info->protocols, protocol_id);
+ if (WARN_ON(!pi))
+ return;
+ }
if (refcount_dec_and_test(&pi->users)) {
void *gid = pi->gid;
@@ -2357,9 +2358,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);
}
void scmi_setup_protocol_implemented(const struct scmi_protocol_handle *ph,
--
2.53.0
^ permalink raw reply related
* [PATCH v3 01/24] firmware: arm_scmi: Add new SCMIv4.0 error codes definitions
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
brauner, souvik.chakravarty, Cristian Marussi
In-Reply-To: <20260329163337.637393-1-cristian.marussi@arm.com>
SCMIv4.0 introduces a couple of new possible protocol error codes: add
the needed definitions and mappings to Linux error values.
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
---
drivers/firmware/arm_scmi/common.h | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index 7c35c95fddba..44af2018e21d 100644
--- a/drivers/firmware/arm_scmi/common.h
+++ b/drivers/firmware/arm_scmi/common.h
@@ -45,6 +45,8 @@ enum scmi_error_codes {
SCMI_ERR_GENERIC = -8, /* Generic Error */
SCMI_ERR_HARDWARE = -9, /* Hardware Error */
SCMI_ERR_PROTOCOL = -10,/* Protocol Error */
+ SCMI_ERR_IN_USE = -11, /* In Use Error */
+ SCMI_ERR_PARTIAL = -12, /* Partial Error */
};
static const int scmi_linux_errmap[] = {
@@ -60,6 +62,8 @@ static const int scmi_linux_errmap[] = {
-EIO, /* SCMI_ERR_GENERIC */
-EREMOTEIO, /* SCMI_ERR_HARDWARE */
-EPROTO, /* SCMI_ERR_PROTOCOL */
+ -EPERM, /* SCMI_ERR_IN_USE */
+ -EINVAL, /* SCMI_ERR_PARTIAL */
};
static inline int scmi_to_linux_errno(int errno)
--
2.53.0
^ permalink raw reply related
* [PATCH v3 00/24] Introduce SCMI Telemetry FS support
From: Cristian Marussi @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel, arm-scmi, linux-fsdevel,
linux-doc
Cc: sudeep.holla, james.quinlan, f.fainelli, vincent.guittot,
etienne.carriere, peng.fan, michal.simek, dan.carpenter, d-gole,
jonathan.cameron, elif.topuz, lukasz.luba, philip.radford,
brauner, souvik.chakravarty, Cristian Marussi
Hi all,
the upcoming SCMI v4.0 specification [0] introduces a new SCMI protocol
dedicated to System Telemetry.
In a nutshell, the SCMI Telemetry protocol allows an agent to discover at
runtime the set of Telemetry Data Events (DEs) available on a specific
platform and provides the means to configure the set of DEs that a user is
interested into, while reading them back using the collection method that
is deeemed more suitable for the usecase at hand. (...amongst the various
possible collection methods allowed by SCMI specification)
Without delving into the gory details of the whole SCMI Telemetry protocol
let's just say that the SCMI platform/server firmware advertises a number
of Telemetry Data Events, each one identified by a 32bit unique ID, and an
SCMI agent/client, like Linux, can discover them and read back at will the
associated data value in a number of ways.
Data collection is mainly intended to happen on demand via shared memory
areas exposed by the platform firmware, discovered dynamically via SCMI
Telemetry and accessed by Linux on-demand, but some DE can also be reported
via SCMI Notifications asynchronous messages or via direct dedicated
FastChannels (another kind of SCMI memory based access): all of this
underlying mechanism is anyway hidden to the user since it is mediated by
the kernel driver which will return the proper data value when queried.
Anyway, the set of well-known architected DE IDs defined by the spec is
limited to a dozen IDs, which means that the vast majority of DE IDs are
customizable per-platform: as a consequence, though, the same ID, say
'0x1234', could represent completely different things on different systems.
Precise definitions and semantic of such custom Data Event IDs are out of
the scope of the SCMI Telemetry specification and of this implementation:
they are supposed to be provided using some kind of JSON-like description
file that will have to be consumed by a userspace tool which would be
finally in charge of making sense of the set of available DEs.
IOW, in turn, this means that even though the DEs enumerated via SCMI come
with some sort of topological and qualitative description provided by the
protocol (like unit of measurements, name, topology info etc), kernel-wise
we CANNOT be completely sure of "what is what" without being fed-back some
sort of information about the DEs by the afore mentioned userspace tool.
For these reasons, currently this series does NOT attempt to register any
of these DEs with any of the usual in-kernel subsystems (like HWMON, IIO,
PERF etc), simply because we cannot be sure which DE is suitable, or even
desirable, for a given subsystem. This also means there are NO in-kernel
users of these Telemetry data events as of now.
So, while we do not exclude, for the future, to feed/register some of the
discovered DEs to/with some of the above mentioned Kernel subsystems, as
of now we have ONLY modeled a custom userspace API to make SCMI Telemetry
available to userspace tools.
In deciding which kind of interface to expose SCMI Telemetry data to a
user, this new SCMI Telemetry driver aims at satisfying 2 main reqs:
- exposing an FS-based human-readable interface that can be used to
discover, configure and access our Telemetry data directly also from
the shell without special tools
- exposing alternative machine-friendly, more-performant, binary
interfaces that can be used to avoid the overhead of multiple accesses
to the VFS and that can be more suitable to access with custom tools
In the initial RFC posted a few months ago [1], the above was achieved
with a combination of a SysFS interface, for the human-readable side of
the story, and a classic chardev/ioctl for the plain binary access.
Since V1, instead, we moved away from this combined approach, especially
away from SysFS, for the following reason:
1. "Abusing SysFS": SysFS is a handy way to expose device related
properties in a common way, using a few common helpers built on
kernfs; this means, though, that unfortunately in our scenario I had
to generate a dummy simple device for EACH SCMI Telemetry DataEvent
that I got to discover at runtime and attach to them, all of the
properties I need.
This by itself seemed to me abusing the SysFS framework, but, even
ignoring this, the impact on the system when we have to deal with
hundreds or tens of thousands of DEs is sensible.
In some test scenario I ended with 50k DE devices and half-a-millon
related property files ... O_o
2. "SysFS constraints": SysFS usage itself has its well-known constraints
and best practices, like the one-file/one-value rule, and due to the
fact that any virtual file with a complex structure or handling logic
is frowned upon, you can forget about IOCTLs and mmap'ing to provide
a more performant interface within SysFs, which is the reason why,
in the previous RFC, there was an additional alternative chardev
interface.
These latter limitations around the implementation of files with a
more complex semantic (i.e. with a broader set of file_operations)
derive from the underlying KernFS support, so KernFS is equally not
suitable as a building block for our implementation.
2. "Chardev limitations": Given the nature of the protocol, the hybrid
approach employing character devices was itself problematic: first
of all because there is an upper limit on the number of chardev we
can create, dictated by the range of available minor numbers, and
then because the fact itself to have to maintain 2 completely
different interfaces (FS + chardev) is painful.
As a final remark, please NOTE THAT all of this is supposed to be available
in production systems across a number of heterogeneous platforms: for these
reasons the easy choice, debugFS, is NOT an option here.
Due to the above reasoning, since V1 we opted for a new approach with the
proposed interfaces now based on a full fledged, unified, virtual pseudo
filesystem implemented from scratch, so that we can:
- expose all the DEs property we like as before with SysFS, but without
any of the constraint imposed by the usage of SysFs or kernfs.
- easily expose additional alternative views of the same set of DEs
using symlinking capabilities (e.g. alternative topological view)
- additionally expose a few alternative and more performant interfaces
by embedding in that same FS, a few special virtual files:
+ 'control': to issue IOCTLs for quicker discovery and on-demand access
to data
+ 'pipe' [TBD]: to provide a stream of events using a virtual
infinite-style file
+ 'raw_<N>' [TBD]: to provide direct memory mapped access to the raw
SCMI Telemetry data from userspace
- use a mount option to enable a lazy enumeration operation mode to delay
SCMI related background discovery activities to the effective point in
time when the user needs it (if ever) so as to mitigate the effect at
boot-time of the initial SCMI full discovery process
INTERFACES
===========
We propose a couple of interfaces, both rooted in the same unified
SCMI Telemetry Filesystem STLMFS, which can be mounted with:
mount -t stlmfs none /sys/fs/arm_telemetry/
The new pseudo FS rationale, design and related ABI interface is documented
in detail at:
- Documentation/filesystems/stlmfs.rst
- Documentation/ABI/testing/stlmfs
...anyway, roughly, STLMFS exposes the following interfaces, rooted at
different points in the FS:
1. a FS based human-readable API tree
This API present the discovered DEs and DEs-groups rooted under a
structrure like this:
/sys/fs/arm_telemetry/tlm_0/
|-- all_des_enable
|-- all_des_tstamp_enable
|-- available_update_intervals_ms
|-- current_update_interval_ms
|-- de_implementation_version
|-- des
| |-- 0x00000000/
| |-- 0x00000016/
| |-- 0x00001010/
| |-- 0x0000A000/
| |-- 0x0000A001/
| |-- 0x0000A002/
| |-- 0x0000A005/
| |-- 0x0000A007/
| |-- 0x0000A008/
| |-- 0x0000A00A/
| |-- 0x0000A00B/
| |-- 0x0000A00C/
| `-- 0x0000A010/
|-- des_bulk_read
|-- des_single_sample_read
|-- groups
| |-- 0/
| `-- 1/
|-- intervals_discrete
|-- reset
|-- tlm_enable
`-- version
At the top level we have general configuration knobs to:
- enable/disable all DEs with or without tstamp
- configure the update interval that the platform will use
- enable Telemetry as a whole
- read all the enabled DEs in a buffer one-per-line
<DE_ID> <TIMESTAMP> <DATA_VALUE>
- des_single_sample_read to request an immediate updated read of
all the enabled DEs in a single buffer one-per-line:
<DE_ID> <TIMESTAMP> <DATA_VALUE>
where each DE in turn is represented by a flat subtree like:
tlm_0/des/0x0000A001/
|-- compo_instance_id
|-- compo_type
|-- enable
|-- instance_id
|-- name
|-- persistent
|-- tstamp_enable
|-- tstamp_exp
|-- type
|-- unit
|-- unit_exp
`-- value
where, beside a bunch of description items, you can:
- enable/disable a single DE
- read back its tstamp and data from 'value' as in:
<TIMESTAMP>: <DATA_VALUE>
then for each (optionally) discovered group of DEs:
scmi_tlm_0/groups/0/
|-- available_update_intervals_ms
|-- composing_des
|-- current_update_interval_ms
|-- des_bulk_read
|-- des_single_sample_read
|-- enable
|-- intervals_discrete
`-- tstamp_enable
you can find the knobs to:
- enable/disable the group as a whole
- lookup group composition
- set a per-group update interval (if supported)
- des_bulk_read to read all the enabled DEs for this group in a
single buffer one-per-line:
<DE_ID> <TIMESTAMP> <DATA_VALUE>
- des_single_sample_read to request an immediate updated read of
all the enabled DEs for this group in a single buffer
one-per-line:
<DE_ID> <TIMESTAMP> <DATA_VALUE>
2. Leveraging the capabilities offered by the full-fledged filesystem
implementation and the topological information provided by SCMI
Telemetry we expose also and alternative view of the above tree, by
symlinking a few of the same entries above under another, topologically
sorted, subtree:
by_components/
├── cpu
│ ├── 0
│ │ ├── celsius
│ │ │ └── 0
│ │ │ └── 0x00000001[pe_0] -> ../../../../../des/0x00000001
│ │ └── cycles
│ │ ├── 0
│ │ │ └── 0x00001010[] -> ../../../../../des/0x00001010
│ │ └── 1
│ │ └── 0x00002020[] -> ../../../../../des/0x00002020
│ ├── 1
│ │ └── celsius
│ │ └── 0
│ │ └── 0x00000002[pe_1] -> ../../../../../des/0x00000002
│ └── 2
│ └── celsius
│ └── 0
│ └── 0x00000003[pe_2] -> ../../../../../des/0x00000003
├── interconnnect
│ └── 0
│ └── hertz
│ └── 0
│ ├── 0x0000A008[A008_de] -> ../../../../../des/0x0000A008
│ └── 0x0000A00B[] -> ../../../../../des/0x0000A00B
├── mem_cntrl
│ └── 0
│ ├── bps
│ │ └── 0
│ │ └── 0x0000A00A[] -> ../../../../../des/0x0000A00A
│ ├── celsius
│ │ └── 0
│ │ └── 0x0000A007[DRAM_temp] -> ../../../../../des/0x0000A007
│ └── joules
│ └── 0
│ └── 0x0000A002[DRAM_energy] -> ../../../../../des/0x0000A002
├── periph
│ ├── 0
│ │ └── messages
│ │ └── 0
│ │ └── 0x00000016[device_16] -> ../../../../../des/0x00000016
│ ├── 1
│ │ └── messages
│ │ └── 0
│ │ └── 0x00000017[device_17] -> ../../../../../des/0x00000017
│ └── 2
│ └── messages
│ └── 0
│ └── 0x00000018[device_18] -> ../../../../../des/0x00000018
└── unspec
└── 0
├── celsius
│ └── 0
│ └── 0x0000A005[] -> ../../../../../des/0x0000A005
├── counts
│ └── 0
│ └── 0x0000A00C[] -> ../../../../../des/0x0000A00C
├── joules
│ └── 0
│ ├── 0x0000A000[SOC_Energy] -> ../../../../../des/0x0000A000
│ └── 0x0000A001[] -> ../../../../../des/0x0000A001
└── state
└── 0
└── 0x0000A010[] -> ../../../../../des/0x0000A010
...so as to provide the human user with a more understandable topological
layout of the madness...
All of this is nice and fancy human-readable, easily scriptable, but
certainly not the fastest possible to access especially on huge trees...
... so for the afore-mentioned reasons we alternatively expose
3. a more performant API based on IOCTLs as described fully in:
include/uapi/linux/scmi.h
As described succinctly in the above UAPI header too, this API is meant
to be called on a few special files named 'control' that are populated
into the tree:
.
|-- all_des_enable
.....
|-- components
| |-- cpu
| |-- interconnnect
| |-- mem_cntrl
| |-- periph
| `-- unspec
|-- control
.....................
|-- groups
| |-- 0
| | |-- available_update_intervals_ms
| | |-- composing_des
| | |-- control
.....................
| |-- 1
| | |-- available_update_intervals_ms
| | |-- composing_des
| | |-- control
.....................
| `-- 2
| |-- available_update_intervals_ms
| |-- composing_des
| |-- control
.....................
This allows a tool to:
- use some IOCTLs to configure a set of properties equivalent to the
ones above in FS
- use some other IOCTLs for direct access to data in binary format
for a single DEs or all of them
4. [FUTURE/NOT IN THIS V3]
Add another alternative and completely binary direct raw access
interface via a new set of memory mappable special files so as to allow
userspace tools to access SCMI Telemetry data directly in binary form
without any kernel mediation.
NOTE THAT this series, at the firmware interface level NOW supports ONLY
the latest SCMI v4.0 BETA specification [0].
Missing feats & next steps
--------------------------
- add direct access interface via mmap-able 'raw' files
- add streaming mode interface via 'pipe' file (tentative)
- evolve/enhance app in tools/testing/scmi/stlm to be interactive
KNOWN ISSUES
------------
- STLMFS code layout and location...nothing lives in fs/ and no distinct
FS Kconfig...but the SCMI Telemetry driver itself has no point in existing
without the FS that exposes...so should I split the pure FS part into fs/
anyway or not ?
- residual sparse/smatch static analyzers errors
- stlm tool utility is minimal for testing or development
Based on V7.0-rc5, tested on an emulated setup.
This series is available also at [2].
If you still reading...any feedback welcome :P
Thanks,
Cristian
----
V2 --> V3
- rebased on v7.0-rc5
- ported the firmware interface to SCMI v4.0 BETA
- split the SCMI protocol layer in a lot of small patches
- completd filesystem and ABI documentation
- renamed components subtree to by_components
- fixed uninitialized var in scmi_telemetry_de_subdir_symlink
- renamd tstamp_exp to tstamp_rate
- swap logic in scmi_telemetry_initial_state_lookup
- use memcpy_from_le32 where required
- changed a dfew dev_err into Telemetry traces
- define and use new helper scmi_telemetry_de_unlink
- simplify a few assignments with ternary ops
- added a missing __mmust_check on the internal SCMI API
- reworked and clarified de_data_read returned errno:
ENODATA vs EINVAL vs ENODEV/ENOENT
- removed some risky/unneeded devres allocations
- various checkpatch fixes
- reworked and clarified usage of traces in Telemetry
- added the missing DT binding for protocol 0x1B
- split out unrelated change around notification from patch
adding support for protocol internal notifier
- more comments
V1 --> V2
- rebased on v6.19-rc3
- harden TDCF shared memory areas accesses by using proper accessors
- reworked protocol resources lifecycle to allow lazy enumeration
- using NEW FS mount API
- reworked FS inode allocation to use a std kmem_cache
- fixed a few IOCTLs support routine to support lazy enumeration
- added (RFC) a new FS lazy mount option to support lazily population of
some subtrees of the FS (des/ groups/ components/)
- reworked implementation of components/ alternative FS view to use
symlinks instead of hardlinks
- added a basic simple (RFC) testing tool to exercise UAPI ioctls interface
- hardened Telmetry protocol and driver to support partial out-of-spec FW
lacking some cmds (best effort)
- reworked probing races handling
- reviewed behaviour on unmount/unload
- added support for Boot_ON Telemetry by supporting SCMI Telemetry cmds:
+ DE_ENABLED_LIST
+ CONFIG_GET
- added FS and ABI docs
RFC --> V1
---
- moved from SysFS/chardev to a full fledged FS
- added support for SCMI Telemetry BLK timestamps
Thanks,
Cristian
[0]: https://developer.arm.com/documentation/den0056/fb/?lang=en
[1]: https://lore.kernel.org/arm-scmi/20250620192813.2463367-1-cristian.marussi@arm.com/
[2]: https://git.kernel.org/pub/scm/linux/kernel/git/cris/linux.git/log/?h=scmi/scmi_telemetry_unified_fs_V3
Cristian Marussi (24):
firmware: arm_scmi: Add new SCMIv4.0 error codes definitions
firmware: arm_scmi: Reduce the scope of protocols mutex
firmware: arm_scmi: Allow registration of unknown-size events/reports
firmware: arm_scmi: Allow protocols to register for notifications
uapi: Add ARM SCMI definitions
dt-bindings: firmware: arm,scmi: Add support for telemetry protocol
include: trace: Add Telemetry trace events
firmware: arm_scmi: Add basic Telemetry support
firmware: arm_scmi: Add support to parse SHMTIs areas
firmware: arm_scmi: Add Telemetry configuration operations
firmware: arm_scmi: Add Telemetry DataEvent read capabilities
firmware: arm_scmi: Add support for Telemetry reset
firmware: arm_scmi: Add Telemetry notification support
firmware: arm_scmi: Add support for boot-on Telemetry
firmware: arm_scmi: Add System Telemetry filesystem driver
fs/stlmfs: Document ARM SCMI Telemetry filesystem
firmware: arm_scmi: Add System Telemetry ioctls support
fs/stlmfs: Document alternative ioctl based binary interface
firmware: arm_scmi: Add Telemetry components view
fs/stlmfs: Document alternative topological view
[RFC] docs: stlmfs: Document ARM SCMI Telemetry FS ABI
firmware: arm_scmi: Add lazy population support to Telemetry FS
fs/stlmfs: Document lazy mode and related mount option
[RFC] tools/scmi: Add SCMI Telemetry testing tool
Documentation/ABI/testing/stlmfs | 297 ++
.../bindings/firmware/arm,scmi.yaml | 8 +
Documentation/filesystems/stlmfs.rst | 312 ++
MAINTAINERS | 1 +
drivers/firmware/arm_scmi/Kconfig | 10 +
drivers/firmware/arm_scmi/Makefile | 3 +-
drivers/firmware/arm_scmi/common.h | 10 +
drivers/firmware/arm_scmi/driver.c | 64 +-
drivers/firmware/arm_scmi/notify.c | 30 +-
drivers/firmware/arm_scmi/notify.h | 8 +-
drivers/firmware/arm_scmi/protocols.h | 7 +
.../firmware/arm_scmi/scmi_system_telemetry.c | 2946 ++++++++++++++++
drivers/firmware/arm_scmi/telemetry.c | 3081 +++++++++++++++++
include/linux/scmi_protocol.h | 185 +-
include/trace/events/scmi.h | 48 +-
include/uapi/linux/scmi.h | 289 ++
tools/testing/scmi/Makefile | 25 +
tools/testing/scmi/stlm.c | 385 ++
18 files changed, 7670 insertions(+), 39 deletions(-)
create mode 100644 Documentation/ABI/testing/stlmfs
create mode 100644 Documentation/filesystems/stlmfs.rst
create mode 100644 drivers/firmware/arm_scmi/scmi_system_telemetry.c
create mode 100644 drivers/firmware/arm_scmi/telemetry.c
create mode 100644 include/uapi/linux/scmi.h
create mode 100644 tools/testing/scmi/Makefile
create mode 100644 tools/testing/scmi/stlm.c
--
2.53.0
^ permalink raw reply
* Re: [PATCH] mailbox: Fix NULL message support in mbox_send_message()
From: Jassi Brar @ 2026-03-29 16:33 UTC (permalink / raw)
To: linux-kernel, linux-arm-kernel
Cc: dianders, shawn.guo, maz, andersson, tglx, joonwonkang
In-Reply-To: <20260327220040.53326-1-jassisinghbrar@gmail.com>
On Fri, Mar 27, 2026 at 5:00 PM <jassisinghbrar@gmail.com> wrote:
>
> From: Jassi Brar <jassisinghbrar@gmail.com>
>
> The active_req field serves double duty as both the "is a TX in
> flight" flag (NULL means idle) and the storage for the in-flight
> message pointer. When a client sends NULL via mbox_send_message(),
> active_req is set to NULL, which the framework misinterprets as
> "no active request". This breaks the TX state machine by:
>
> - tx_tick() short-circuits on (!mssg), skipping the tx_done
> callback and the tx_complete completion
> - txdone_hrtimer() skips the channel entirely since active_req
> is NULL, so poll-based TX-done detection never fires.
>
> Fix this by introducing a MBOX_NO_MSG sentinel value that means
> "no active request," freeing NULL to be valid message data. The
> sentinel is defined in the subsystem-internal mailbox.h so that
> controller drivers within drivers/mailbox/ can reference it, but
> it is not exposed to clients outside the subsystem.
>
> Fifteen in-tree callers send NULL (doorbell-style IPCs on Qualcomm,
> Tegra, TI, Xilinx, i.MX, SCMI, and PCC platforms). All were
> audited for regression:
>
> - Most already work around the bug via knows_txdone=true with a
> manual mbox_client_txdone() call, making the framework's
> tracking irrelevant. These are unaffected.
>
> - Poll-based callers (Xilinx zynqmp/r5) are strictly better off:
> the poll timer now correctly detects NULL-active channels
> instead of silently skipping them.
>
> - irq-qcom-mpm.c was a pre-existing bug -- the only Qualcomm
> caller that omitted the knows_txdone + mbox_client_txdone()
> pattern. Fixed in a companion commit ("irqchip/qcom-mpm: Fix
> missing mailbox TX done acknowledgment").
>
> - No caller sets both a tx_done callback and sends NULL, nor
> combines tx_block=true with NULL sends, so the newly reachable
> callback/completion paths are never exercised.
>
> Also update tegra-hsp's flush callback, which directly inspects
> active_req to wait for the channel to drain: the old "!= NULL"
> check becomes "!= MBOX_NO_MSG", otherwise flush spins until
> timeout since the sentinel is non-NULL.
>
> The only tradeoff is that 'MBOX_NO_MSG' can not be used as a message
> by clients.
>
> Reported-by: Joonwon Kang <joonwonkang@google.com>
> Reviewed-by: Douglas Anderson <dianders@chromium.org>
> Signed-off-by: Jassi Brar <jassisinghbrar@gmail.com>
> ---
> drivers/mailbox/mailbox.c | 15 ++++++++-------
> drivers/mailbox/mailbox.h | 3 +++
> drivers/mailbox/tegra-hsp.c | 2 +-
> 3 files changed, 12 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/mailbox/mailbox.c b/drivers/mailbox/mailbox.c
> index 617ba505691d..9622369cab66 100644
> --- a/drivers/mailbox/mailbox.c
> +++ b/drivers/mailbox/mailbox.c
> @@ -52,7 +52,7 @@ static void msg_submit(struct mbox_chan *chan)
> int err = -EBUSY;
>
> scoped_guard(spinlock_irqsave, &chan->lock) {
> - if (!chan->msg_count || chan->active_req)
> + if (!chan->msg_count || chan->active_req != MBOX_NO_MSG)
> break;
>
> count = chan->msg_count;
> @@ -87,13 +87,13 @@ static void tx_tick(struct mbox_chan *chan, int r)
>
> scoped_guard(spinlock_irqsave, &chan->lock) {
> mssg = chan->active_req;
> - chan->active_req = NULL;
> + chan->active_req = MBOX_NO_MSG;
> }
>
> /* Submit next message */
> msg_submit(chan);
>
> - if (!mssg)
> + if (mssg == MBOX_NO_MSG)
> return;
>
> /* Notify the client */
> @@ -114,7 +114,7 @@ static enum hrtimer_restart txdone_hrtimer(struct hrtimer *hrtimer)
> for (i = 0; i < mbox->num_chans; i++) {
> struct mbox_chan *chan = &mbox->chans[i];
>
> - if (chan->active_req && chan->cl) {
> + if (chan->active_req != MBOX_NO_MSG && chan->cl) {
> txdone = chan->mbox->ops->last_tx_done(chan);
> if (txdone)
> tx_tick(chan, 0);
> @@ -246,7 +246,7 @@ int mbox_send_message(struct mbox_chan *chan, void *mssg)
> {
> int t;
>
> - if (!chan || !chan->cl)
> + if (!chan || !chan->cl || mssg == MBOX_NO_MSG)
> return -EINVAL;
>
> t = add_to_rbuf(chan, mssg);
> @@ -319,7 +319,7 @@ static int __mbox_bind_client(struct mbox_chan *chan, struct mbox_client *cl)
> scoped_guard(spinlock_irqsave, &chan->lock) {
> chan->msg_free = 0;
> chan->msg_count = 0;
> - chan->active_req = NULL;
> + chan->active_req = MBOX_NO_MSG;
> chan->cl = cl;
> init_completion(&chan->tx_complete);
>
> @@ -477,7 +477,7 @@ void mbox_free_channel(struct mbox_chan *chan)
> /* The queued TX requests are simply aborted, no callbacks are made */
> scoped_guard(spinlock_irqsave, &chan->lock) {
> chan->cl = NULL;
> - chan->active_req = NULL;
> + chan->active_req = MBOX_NO_MSG;
> if (chan->txdone_method == TXDONE_BY_ACK)
> chan->txdone_method = TXDONE_BY_POLL;
> }
> @@ -532,6 +532,7 @@ int mbox_controller_register(struct mbox_controller *mbox)
>
> chan->cl = NULL;
> chan->mbox = mbox;
> + chan->active_req = MBOX_NO_MSG;
> chan->txdone_method = txdone;
> spin_lock_init(&chan->lock);
> }
> diff --git a/drivers/mailbox/mailbox.h b/drivers/mailbox/mailbox.h
> index e1ec4efab693..c77dd6fc5b8a 100644
> --- a/drivers/mailbox/mailbox.h
> +++ b/drivers/mailbox/mailbox.h
> @@ -5,6 +5,9 @@
>
> #include <linux/bits.h>
>
> +/* Sentinel value distinguishing "no active request" from "NULL message data" */
> +#define MBOX_NO_MSG ((void *)-1)
> +
> #define TXDONE_BY_IRQ BIT(0) /* controller has remote RTR irq */
> #define TXDONE_BY_POLL BIT(1) /* controller can read status of last TX */
> #define TXDONE_BY_ACK BIT(2) /* S/W ACK received by Client ticks the TX */
> diff --git a/drivers/mailbox/tegra-hsp.c b/drivers/mailbox/tegra-hsp.c
> index ed9a0bb2bcd8..7991e8dba579 100644
> --- a/drivers/mailbox/tegra-hsp.c
> +++ b/drivers/mailbox/tegra-hsp.c
> @@ -497,7 +497,7 @@ static int tegra_hsp_mailbox_flush(struct mbox_chan *chan,
> mbox_chan_txdone(chan, 0);
>
> /* Wait until channel is empty */
> - if (chan->active_req != NULL)
> + if (chan->active_req != MBOX_NO_MSG)
> continue;
>
> return 0;
> --
> 2.52.0
>
Applied to mailbox/for-next
^ permalink raw reply
* Re: [PATCH] mailbox: cix: Add IRQF_NO_SUSPEND to mailbox interrupt
From: Jassi Brar @ 2026-03-29 16:32 UTC (permalink / raw)
To: Dylan Wu
Cc: Peter Chen, Fugang Duan, cix-kernel-upstream, linux-arm-kernel,
linux-kernel
In-Reply-To: <20260209083452.154983-1-fredwudi0305@gmail.com>
On Mon, Feb 9, 2026 at 2:34 AM Dylan Wu <fredwudi0305@gmail.com> wrote:
>
> During the system suspend process, device interrupts are masked in the
> noirq phase. However, SCMI often needs to exchange final messages with the
> firmware to complete the power-down transition. Without the IRQF_NO_SUSPEND
> flag, the mailbox ISR cannot run during this late stage, leading to SCMI
> communication timeouts and error messages like "SCMI protocol wait for
> resp timeout" during suspend.
>
> Add the IRQF_NO_SUSPEND flag to the interrupt request to ensure the mailbox
> can continue to handle responses during the noirq stages of suspend and
> resume, thereby ensuring a reliable power state transition.
>
> Signed-off-by: Dylan Wu <fredwudi0305@gmail.com>
> ---
> drivers/mailbox/cix-mailbox.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/mailbox/cix-mailbox.c b/drivers/mailbox/cix-mailbox.c
> index 5bb1416c26a5..5e27c2bf3492 100644
> --- a/drivers/mailbox/cix-mailbox.c
> +++ b/drivers/mailbox/cix-mailbox.c
> @@ -405,7 +405,7 @@ static int cix_mbox_startup(struct mbox_chan *chan)
> int index = cp->index, ret;
> u32 val;
>
> - ret = request_irq(priv->irq, cix_mbox_isr, 0,
> + ret = request_irq(priv->irq, cix_mbox_isr, IRQF_NO_SUSPEND,
> dev_name(priv->dev), chan);
> if (ret) {
> dev_err(priv->dev, "Unable to acquire IRQ %d\n", priv->irq);
> --
> 2.52.0
>
Applied to mailbox/for-next
Thanks
Jassi
^ permalink raw reply
* Re: [PATCH v2 1/2] dt-bindings: perf: marvell: Document CN20K DDR PMU
From: Rob Herring (Arm) @ 2026-03-29 16:31 UTC (permalink / raw)
To: Geetha sowjanya
Cc: krzk+dt, mark.rutland, linux-kernel, will, linux-perf-users,
linux-arm-kernel, devicetree
In-Reply-To: <20260329152439.10573-2-gakula@marvell.com>
On Sun, 29 Mar 2026 20:54:38 +0530, Geetha sowjanya wrote:
> Add a devicetree binding for the Marvell CN20K DDR performance
> monitor block, including the marvell,cn20k-ddr-pmu compatible
> string and the required MMIO reg region.
>
> Signed-off-by: Geetha sowjanya <gakula@marvell.com>
> ---
>
> Changes in v1:
> - Added a description field to the binding.
> - Simplified the compatible property using 'const' instead of 'items/enum'.
> - Updated the example node name to include a unit-address matching the reg base.
>
> .../bindings/perf/marvell-cn20k-ddr.yaml | 39 +++++++++++++++++++
> 1 file changed, 39 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/perf/marvell-cn20k-ddr.yaml
>
My bot found errors running 'make dt_binding_check' on your patch:
yamllint warnings/errors:
./Documentation/devicetree/bindings/perf/marvell-cn20k-ddr.yaml:35:1: [error] syntax error: found character '\t' that cannot start any token (syntax)
dtschema/dtc warnings/errors:
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/perf/marvell-cn20k-ddr.yaml: ignoring, error parsing file
./Documentation/devicetree/bindings/perf/marvell-cn20k-ddr.yaml:35:1: found character '\t' that cannot start any token
make[2]: *** Deleting file 'Documentation/devicetree/bindings/perf/marvell-cn20k-ddr.example.dts'
Documentation/devicetree/bindings/perf/marvell-cn20k-ddr.yaml:35:1: found character '\t' that cannot start any token
make[2]: *** [Documentation/devicetree/bindings/Makefile:26: Documentation/devicetree/bindings/perf/marvell-cn20k-ddr.example.dts] Error 1
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [/builds/robherring/dt-review-ci/linux/Makefile:1614: dt_binding_check] Error 2
make: *** [Makefile:248: __sub-make] Error 2
doc reference errors (make refcheckdocs):
See https://patchwork.kernel.org/project/devicetree/patch/20260329152439.10573-2-gakula@marvell.com
The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.
If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:
pip3 install dtschema --upgrade
Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.
^ permalink raw reply
* Re: [PATCH] mailbox: mtk-vcp-mailbox: Fix the return value in mtk_vcp_mbox_xlate()
From: Jassi Brar @ 2026-03-29 16:29 UTC (permalink / raw)
To: Felix Gu
Cc: Matthias Brugger, AngeloGioacchino Del Regno, Jjian Zhou,
Chen-Yu Tsai, linux-kernel, linux-arm-kernel, linux-mediatek
In-Reply-To: <20260226-vcp-v1-1-766c5877017d@gmail.com>
On Wed, Feb 25, 2026 at 10:33 AM Felix Gu <ustc.gu@gmail.com> wrote:
>
> The return value of mtk_vcp_mbox_xlate() is checked by IS_ERR(), so
> return NULL is incorrect and could lead to a NULL pointer dereference.
>
> Fixes: b562abd95672 ("mailbox: mediatek: Add mtk-vcp-mailbox driver")
> Signed-off-by: Felix Gu <ustc.gu@gmail.com>
> ---
> drivers/mailbox/mtk-vcp-mailbox.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/mailbox/mtk-vcp-mailbox.c b/drivers/mailbox/mtk-vcp-mailbox.c
> index cedad575528f..1b291b8ea15a 100644
> --- a/drivers/mailbox/mtk-vcp-mailbox.c
> +++ b/drivers/mailbox/mtk-vcp-mailbox.c
> @@ -50,7 +50,7 @@ static struct mbox_chan *mtk_vcp_mbox_xlate(struct mbox_controller *mbox,
> const struct of_phandle_args *sp)
> {
> if (sp->args_count)
> - return NULL;
> + return ERR_PTR(-EINVAL);
>
> return &mbox->chans[0];
> }
>
Applied to mailbox/for-next
Thanks
Jassi
^ permalink raw reply
* Re: [PATCH] mailbox: rockchip: kzalloc + kcalloc to kzalloc
From: Jassi Brar @ 2026-03-29 16:28 UTC (permalink / raw)
To: Rosen Penev
Cc: linux-kernel, Heiko Stuebner,
moderated list:ARM/Rockchip SoC support,
open list:ARM/Rockchip SoC support
In-Reply-To: <20260311003744.32099-1-rosenp@gmail.com>
On Tue, Mar 10, 2026 at 7:38 PM Rosen Penev <rosenp@gmail.com> wrote:
>
> Use a flexible array member to reduce allocations.
>
> Signed-off-by: Rosen Penev <rosenp@gmail.com>
> ---
> drivers/mailbox/rockchip-mailbox.c | 9 ++-------
> 1 file changed, 2 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/mailbox/rockchip-mailbox.c b/drivers/mailbox/rockchip-mailbox.c
> index 4d966cb2ed03..a1a7dee64356 100644
> --- a/drivers/mailbox/rockchip-mailbox.c
> +++ b/drivers/mailbox/rockchip-mailbox.c
> @@ -46,7 +46,7 @@ struct rockchip_mbox {
> /* The maximum size of buf for each channel */
> u32 buf_size;
>
> - struct rockchip_mbox_chan *chans;
> + struct rockchip_mbox_chan chans[];
> };
>
> static int rockchip_mbox_send_data(struct mbox_chan *chan, void *data)
> @@ -173,15 +173,10 @@ static int rockchip_mbox_probe(struct platform_device *pdev)
>
> drv_data = (const struct rockchip_mbox_data *) device_get_match_data(&pdev->dev);
>
> - mb = devm_kzalloc(&pdev->dev, sizeof(*mb), GFP_KERNEL);
> + mb = devm_kzalloc(&pdev->dev, struct_size(mb, chans, drv_data->num_chans), GFP_KERNEL);
> if (!mb)
> return -ENOMEM;
>
> - mb->chans = devm_kcalloc(&pdev->dev, drv_data->num_chans,
> - sizeof(*mb->chans), GFP_KERNEL);
> - if (!mb->chans)
> - return -ENOMEM;
> -
> mb->mbox.chans = devm_kcalloc(&pdev->dev, drv_data->num_chans,
> sizeof(*mb->mbox.chans), GFP_KERNEL);
> if (!mb->mbox.chans)
> --
> 2.53.0
>
Applied to mailbox/for-next
Thanks
Jassi
^ permalink raw reply
* Re: [PATCH] mailbox: mtk-cmdq: Fix CURR and END addr for task insert case
From: Jassi Brar @ 2026-03-29 16:18 UTC (permalink / raw)
To: Jason-JH Lin
Cc: Chun-Kuang Hu, AngeloGioacchino Del Regno, Matthias Brugger,
Nancy Lin, Singo Chang, Paul-PL Chen, Xiandong Wang, Sirius Wang,
Fei Shao, Chen-yu Tsai, Project_Global_Chrome_Upstream_Group,
linux-kernel, linux-mediatek, linux-arm-kernel
In-Reply-To: <20260323090742.1522607-1-jason-jh.lin@mediatek.com>
On Mon, Mar 23, 2026 at 4:07 AM Jason-JH Lin <jason-jh.lin@mediatek.com> wrote:
>
> Fix CURR and END address calculation for inserting a cmdq task into the
> task list by using cmdq_reg_shift_addr() for proper address converting.
> This ensures both CURR and END addresses are set correctly when
> enabling the thread.
>
> Fixes: a195c7ccfb7a ("mailbox: mtk-cmdq: Refine DMA address handling for the command buffer")
> Signed-off-by: Jason-JH Lin <jason-jh.lin@mediatek.com>
> ---
> drivers/mailbox/mtk-cmdq-mailbox.c | 8 ++++----
> 1 file changed, 4 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/mailbox/mtk-cmdq-mailbox.c b/drivers/mailbox/mtk-cmdq-mailbox.c
> index d7c6b38888a3..547a10a8fad3 100644
> --- a/drivers/mailbox/mtk-cmdq-mailbox.c
> +++ b/drivers/mailbox/mtk-cmdq-mailbox.c
> @@ -493,14 +493,14 @@ static int cmdq_mbox_send_data(struct mbox_chan *chan, void *data)
> if (curr_pa == end_pa - CMDQ_INST_SIZE ||
> curr_pa == end_pa) {
> /* set to this task directly */
> - writel(task->pa_base >> cmdq->pdata->shift,
> - thread->base + CMDQ_THR_CURR_ADDR);
> + gce_addr = cmdq_convert_gce_addr(task->pa_base, cmdq->pdata);
> + writel(gce_addr, thread->base + CMDQ_THR_CURR_ADDR);
> } else {
> cmdq_task_insert_into_thread(task);
> smp_mb(); /* modify jump before enable thread */
> }
> - writel((task->pa_base + pkt->cmd_buf_size) >> cmdq->pdata->shift,
> - thread->base + CMDQ_THR_END_ADDR);
> + gce_addr = cmdq_convert_gce_addr(task->pa_base + pkt->cmd_buf_size, cmdq->pdata);
> + writel(gce_addr, thread->base + CMDQ_THR_END_ADDR);
> cmdq_thread_resume(thread);
> }
> list_move_tail(&task->list_entry, &thread->task_busy_list);
> --
> 2.43.0
>
Applied to mailbox/for-next
Thanks
Jassi
^ permalink raw reply
* Re: [PATCH v3] mailbox: remove superfluous internal header
From: Jassi Brar @ 2026-03-29 16:18 UTC (permalink / raw)
To: Wolfram Sang
Cc: linux-renesas-soc, Sudeep Holla, Daniel Baluta, Peter Chen,
Fugang Duan, CIX Linux Kernel Upstream Group, Frank Li,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
Thierry Reding, Jonathan Hunter, linux-kernel, linux-arm-kernel,
imx, linux-acpi, linux-tegra
In-Reply-To: <20260327151112.5202-2-wsa+renesas@sang-engineering.com>
On Fri, Mar 27, 2026 at 10:11 AM Wolfram Sang
<wsa+renesas@sang-engineering.com> wrote:
>
> Quite some controller drivers use the defines from the internal header
> already. This prevents controller drivers outside the mailbox directory.
> Move the defines to the public controller header to allow this again as
> the defines are not strictly internal anyhow.
>
> Signed-off-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
> Reviewed-by: Sudeep Holla <sudeep.holla@kernel.org>
> Reviewed-by: Daniel Baluta <daniel.baluta@nxp.com>
> ---
>
> Changes since v2:
> * rebased to 7.0-rc5
> * add tag (Thanks, Daniel!)
>
> drivers/mailbox/cix-mailbox.c | 2 --
> drivers/mailbox/hi3660-mailbox.c | 2 --
> drivers/mailbox/imx-mailbox.c | 2 --
> drivers/mailbox/mailbox-sti.c | 2 --
> drivers/mailbox/mailbox.c | 2 --
> drivers/mailbox/mailbox.h | 12 ------------
> drivers/mailbox/omap-mailbox.c | 2 --
> drivers/mailbox/pcc.c | 2 --
> drivers/mailbox/tegra-hsp.c | 2 --
> include/linux/mailbox_controller.h | 5 +++++
> 10 files changed, 5 insertions(+), 28 deletions(-)
> delete mode 100644 drivers/mailbox/mailbox.h
>
> diff --git a/drivers/mailbox/cix-mailbox.c b/drivers/mailbox/cix-mailbox.c
> index 443620e8ae37..864f98f21fc3 100644
> --- a/drivers/mailbox/cix-mailbox.c
> +++ b/drivers/mailbox/cix-mailbox.c
> @@ -12,8 +12,6 @@
> #include <linux/module.h>
> #include <linux/platform_device.h>
>
> -#include "mailbox.h"
> -
> /*
> * The maximum transmission size is 32 words or 128 bytes.
> */
> diff --git a/drivers/mailbox/hi3660-mailbox.c b/drivers/mailbox/hi3660-mailbox.c
> index 17c29e960fbf..9b727a2b54a5 100644
> --- a/drivers/mailbox/hi3660-mailbox.c
> +++ b/drivers/mailbox/hi3660-mailbox.c
> @@ -15,8 +15,6 @@
> #include <linux/platform_device.h>
> #include <linux/slab.h>
>
> -#include "mailbox.h"
> -
> #define MBOX_CHAN_MAX 32
>
> #define MBOX_RX 0x0
> diff --git a/drivers/mailbox/imx-mailbox.c b/drivers/mailbox/imx-mailbox.c
> index 003f9236c35e..22331b579489 100644
> --- a/drivers/mailbox/imx-mailbox.c
> +++ b/drivers/mailbox/imx-mailbox.c
> @@ -23,8 +23,6 @@
> #include <linux/slab.h>
> #include <linux/workqueue.h>
>
> -#include "mailbox.h"
> -
> #define IMX_MU_CHANS 24
> /* TX0/RX0/RXDB[0-3] */
> #define IMX_MU_SCU_CHANS 6
> diff --git a/drivers/mailbox/mailbox-sti.c b/drivers/mailbox/mailbox-sti.c
> index b4b5bdd503cf..b6c9ecbbc8ec 100644
> --- a/drivers/mailbox/mailbox-sti.c
> +++ b/drivers/mailbox/mailbox-sti.c
> @@ -21,8 +21,6 @@
> #include <linux/property.h>
> #include <linux/slab.h>
>
> -#include "mailbox.h"
> -
> #define STI_MBOX_INST_MAX 4 /* RAM saving: Max supported instances */
> #define STI_MBOX_CHAN_MAX 20 /* RAM saving: Max supported channels */
>
> diff --git a/drivers/mailbox/mailbox.c b/drivers/mailbox/mailbox.c
> index e63b2292ee7a..9d41a1ab9018 100644
> --- a/drivers/mailbox/mailbox.c
> +++ b/drivers/mailbox/mailbox.c
> @@ -18,8 +18,6 @@
> #include <linux/property.h>
> #include <linux/spinlock.h>
>
> -#include "mailbox.h"
> -
> static LIST_HEAD(mbox_cons);
> static DEFINE_MUTEX(con_mutex);
>
> diff --git a/drivers/mailbox/mailbox.h b/drivers/mailbox/mailbox.h
> deleted file mode 100644
> index e1ec4efab693..000000000000
> --- a/drivers/mailbox/mailbox.h
> +++ /dev/null
> @@ -1,12 +0,0 @@
> -/* SPDX-License-Identifier: GPL-2.0-only */
> -
> -#ifndef __MAILBOX_H
> -#define __MAILBOX_H
> -
> -#include <linux/bits.h>
> -
> -#define TXDONE_BY_IRQ BIT(0) /* controller has remote RTR irq */
> -#define TXDONE_BY_POLL BIT(1) /* controller can read status of last TX */
> -#define TXDONE_BY_ACK BIT(2) /* S/W ACK received by Client ticks the TX */
> -
> -#endif /* __MAILBOX_H */
> diff --git a/drivers/mailbox/omap-mailbox.c b/drivers/mailbox/omap-mailbox.c
> index d9f100c18895..5772c6b9886a 100644
> --- a/drivers/mailbox/omap-mailbox.c
> +++ b/drivers/mailbox/omap-mailbox.c
> @@ -22,8 +22,6 @@
> #include <linux/pm_runtime.h>
> #include <linux/mailbox_controller.h>
>
> -#include "mailbox.h"
> -
> #define MAILBOX_REVISION 0x000
> #define MAILBOX_MESSAGE(m) (0x040 + 4 * (m))
> #define MAILBOX_FIFOSTATUS(m) (0x080 + 4 * (m))
> diff --git a/drivers/mailbox/pcc.c b/drivers/mailbox/pcc.c
> index 22e70af1ae5d..636879ae1db7 100644
> --- a/drivers/mailbox/pcc.c
> +++ b/drivers/mailbox/pcc.c
> @@ -59,8 +59,6 @@
> #include <linux/io-64-nonatomic-lo-hi.h>
> #include <acpi/pcc.h>
>
> -#include "mailbox.h"
> -
> #define MBOX_IRQ_NAME "pcc-mbox"
>
> /**
> diff --git a/drivers/mailbox/tegra-hsp.c b/drivers/mailbox/tegra-hsp.c
> index ed9a0bb2bcd8..2231050bb5a9 100644
> --- a/drivers/mailbox/tegra-hsp.c
> +++ b/drivers/mailbox/tegra-hsp.c
> @@ -16,8 +16,6 @@
>
> #include <dt-bindings/mailbox/tegra186-hsp.h>
>
> -#include "mailbox.h"
> -
> #define HSP_INT_IE(x) (0x100 + ((x) * 4))
> #define HSP_INT_IV 0x300
> #define HSP_INT_IR 0x304
> diff --git a/include/linux/mailbox_controller.h b/include/linux/mailbox_controller.h
> index 80a427c7ca29..16fef421c30c 100644
> --- a/include/linux/mailbox_controller.h
> +++ b/include/linux/mailbox_controller.h
> @@ -3,6 +3,7 @@
> #ifndef __MAILBOX_CONTROLLER_H
> #define __MAILBOX_CONTROLLER_H
>
> +#include <linux/bits.h>
> #include <linux/completion.h>
> #include <linux/device.h>
> #include <linux/hrtimer.h>
> @@ -11,6 +12,10 @@
>
> struct mbox_chan;
>
> +#define TXDONE_BY_IRQ BIT(0) /* controller has remote RTR irq */
> +#define TXDONE_BY_POLL BIT(1) /* controller can read status of last TX */
> +#define TXDONE_BY_ACK BIT(2) /* S/W ACK received by Client ticks the TX */
> +
> /**
> * struct mbox_chan_ops - methods to control mailbox channels
> * @send_data: The API asks the MBOX controller driver, in atomic
> --
> 2.51.0
>
Applied to mailbox/for-next
Thanks
Jassi
^ permalink raw reply
* Re: [PATCH v2] mailbox: exynos: drop superfluous mbox setting per channel
From: Jassi Brar @ 2026-03-29 16:18 UTC (permalink / raw)
To: Wolfram Sang
Cc: linux-renesas-soc, Tudor Ambarus, Krzysztof Kozlowski,
Alim Akhtar, linux-kernel, linux-samsung-soc, linux-arm-kernel
In-Reply-To: <20260327151332.5425-2-wsa+renesas@sang-engineering.com>
On Fri, Mar 27, 2026 at 10:13 AM Wolfram Sang
<wsa+renesas@sang-engineering.com> wrote:
>
> The core initializes the 'mbox' field exactly like this, so don't
> duplicate it in the driver.
>
> Signed-off-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
> Reviewed-by: Tudor Ambarus <tudor.ambarus@linaro.org>
> Tested-by: Tudor Ambarus <tudor.ambarus@linaro.org>
> ---
> Changes since v1:
> * rebased to 7.0-rc5
> * add tags (Thanks, Tudor!) and dropped RFT
>
> drivers/mailbox/exynos-mailbox.c | 4 ----
> 1 file changed, 4 deletions(-)
>
> diff --git a/drivers/mailbox/exynos-mailbox.c b/drivers/mailbox/exynos-mailbox.c
> index 5f2d3b81c1db..d2355b128ba4 100644
> --- a/drivers/mailbox/exynos-mailbox.c
> +++ b/drivers/mailbox/exynos-mailbox.c
> @@ -99,7 +99,6 @@ static int exynos_mbox_probe(struct platform_device *pdev)
> struct mbox_controller *mbox;
> struct mbox_chan *chans;
> struct clk *pclk;
> - int i;
>
> exynos_mbox = devm_kzalloc(dev, sizeof(*exynos_mbox), GFP_KERNEL);
> if (!exynos_mbox)
> @@ -129,9 +128,6 @@ static int exynos_mbox_probe(struct platform_device *pdev)
> mbox->ops = &exynos_mbox_chan_ops;
> mbox->of_xlate = exynos_mbox_of_xlate;
>
> - for (i = 0; i < EXYNOS_MBOX_CHAN_COUNT; i++)
> - chans[i].mbox = mbox;
> -
> exynos_mbox->mbox = mbox;
>
> platform_set_drvdata(pdev, exynos_mbox);
> --
> 2.51.0
>
Applied to mailbox/for-next
Thanks
Jassi
^ permalink raw reply
* Re: [PATCH v2 3/3] mailbox: mtk-cmdq: Remove unsued cmdq_get_shift_pa()
From: Jassi Brar @ 2026-03-29 16:03 UTC (permalink / raw)
To: Jason-JH Lin
Cc: Chun-Kuang Hu, AngeloGioacchino Del Regno, Nicolas Dufresne,
Mauro Carvalho Chehab, Matthias Brugger, Nancy Lin, Singo Chang,
Paul-PL Chen, Moudy Ho, Xiandong Wang, Sirius Wang, Fei Shao,
Chen-yu Tsai, Project_Global_Chrome_Upstream_Group, linux-kernel,
dri-devel, linux-mediatek, linux-arm-kernel, linux-media
In-Reply-To: <20260325040457.2113120-4-jason-jh.lin@mediatek.com>
On Tue, Mar 24, 2026 at 11:05 PM Jason-JH Lin <jason-jh.lin@mediatek.com> wrote:
>
> Since the mailbox driver data can be obtained using cmdq_get_mbox_priv()
> and all CMDQ users have transitioned to cmdq_get_mbox_priv(),
> cmdq_get_shift_pa() can be removed.
>
> Signed-off-by: Jason-JH Lin <jason-jh.lin@mediatek.com>
> Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
> ---
> drivers/mailbox/mtk-cmdq-mailbox.c | 8 --------
> include/linux/mailbox/mtk-cmdq-mailbox.h | 12 ------------
> 2 files changed, 20 deletions(-)
>
> diff --git a/drivers/mailbox/mtk-cmdq-mailbox.c b/drivers/mailbox/mtk-cmdq-mailbox.c
> index d7c6b38888a3..f463f443e834 100644
> --- a/drivers/mailbox/mtk-cmdq-mailbox.c
> +++ b/drivers/mailbox/mtk-cmdq-mailbox.c
> @@ -123,14 +123,6 @@ void cmdq_get_mbox_priv(struct mbox_chan *chan, struct cmdq_mbox_priv *priv)
> }
> EXPORT_SYMBOL(cmdq_get_mbox_priv);
>
> -u8 cmdq_get_shift_pa(struct mbox_chan *chan)
> -{
> - struct cmdq *cmdq = container_of(chan->mbox, struct cmdq, mbox);
> -
> - return cmdq->pdata->shift;
> -}
> -EXPORT_SYMBOL(cmdq_get_shift_pa);
> -
> static void cmdq_vm_init(struct cmdq *cmdq)
> {
> int i;
> diff --git a/include/linux/mailbox/mtk-cmdq-mailbox.h b/include/linux/mailbox/mtk-cmdq-mailbox.h
> index 07c1bfbdb8c4..a42b44d5fd49 100644
> --- a/include/linux/mailbox/mtk-cmdq-mailbox.h
> +++ b/include/linux/mailbox/mtk-cmdq-mailbox.h
> @@ -96,16 +96,4 @@ struct cmdq_pkt {
> */
> void cmdq_get_mbox_priv(struct mbox_chan *chan, struct cmdq_mbox_priv *priv);
>
> -/**
> - * cmdq_get_shift_pa() - get the shift bits of physical address
> - * @chan: mailbox channel
> - *
> - * GCE can only fetch the command buffer address from a 32-bit register.
> - * Some SOCs support more than 32-bit command buffer address for GCE, which
> - * requires some shift bits to make the address fit into the 32-bit register.
> - *
> - * Return: the shift bits of physical address
> - */
> -u8 cmdq_get_shift_pa(struct mbox_chan *chan);
> -
> #endif /* __MTK_CMDQ_MAILBOX_H__ */
I think the simplest would be to take this with the other two
predecessor patches.
Acked-by: Jassi Brar <jassisinghbrar@gmail.com>
^ permalink raw reply
* [PATCH v2 2/2] perf: marvell: Add CN20K DDR PMU support
From: Geetha sowjanya @ 2026-03-29 15:24 UTC (permalink / raw)
To: linux-perf-users, linux-kernel, linux-arm-kernel, devicetree
Cc: mark.rutland, will, krzk+dt
In-Reply-To: <20260329152439.10573-1-gakula@marvell.com>
The CN20K DRAM Subsystem exposes eight programmable
performance counters and two fixed counters for DDR
read and write traffic. Software selects events for
the programmable counters from traffic at the DDR PHY
interface, the CHI interconnect, or inside the DDR controller.
Add CN20K register offsets, event maps, and sysfs attributes;
match the device via OF (marvell,cn20k-ddr-pmu) and ACPI (MRVL000B).
Represent the SoC variant in platform data with bit flags so
CN20K can reuse the Odyssey PMU code path where appropriate.
Signed-off-by: Geetha sowjanya <gakula@marvell.com>
---
drivers/perf/marvell_cn10k_ddr_pmu.c | 187 ++++++++++++++++++++++++---
1 file changed, 171 insertions(+), 16 deletions(-)
diff --git a/drivers/perf/marvell_cn10k_ddr_pmu.c b/drivers/perf/marvell_cn10k_ddr_pmu.c
index 72ac17efd846..7e2e1823b009 100644
--- a/drivers/perf/marvell_cn10k_ddr_pmu.c
+++ b/drivers/perf/marvell_cn10k_ddr_pmu.c
@@ -13,31 +13,43 @@
#include <linux/hrtimer.h>
#include <linux/acpi.h>
#include <linux/platform_device.h>
+#include <linux/bits.h>
+
+/* SoC variant flags for struct ddr_pmu_platform_data (mutually exclusive in pdata) */
+#define IS_CN10K BIT(0)
+#define IS_ODY BIT(1)
+#define IS_CN20K BIT(2)
/* Performance Counters Operating Mode Control Registers */
#define CN10K_DDRC_PERF_CNT_OP_MODE_CTRL 0x8020
#define ODY_DDRC_PERF_CNT_OP_MODE_CTRL 0x20020
+#define CN20K_DDRC_PERF_CNT_OP_MODE_CTRL 0x20000
#define OP_MODE_CTRL_VAL_MANUAL 0x1
/* Performance Counters Start Operation Control Registers */
#define CN10K_DDRC_PERF_CNT_START_OP_CTRL 0x8028
#define ODY_DDRC_PERF_CNT_START_OP_CTRL 0x200A0
+#define CN20K_DDRC_PERF_CNT_START_OP_CTRL 0x20080
#define START_OP_CTRL_VAL_START 0x1ULL
#define START_OP_CTRL_VAL_ACTIVE 0x2
/* Performance Counters End Operation Control Registers */
#define CN10K_DDRC_PERF_CNT_END_OP_CTRL 0x8030
#define ODY_DDRC_PERF_CNT_END_OP_CTRL 0x200E0
+#define CN20K_DDRC_PERF_CNT_END_OP_CTRL 0x200C0
#define END_OP_CTRL_VAL_END 0x1ULL
/* Performance Counters End Status Registers */
#define CN10K_DDRC_PERF_CNT_END_STATUS 0x8038
#define ODY_DDRC_PERF_CNT_END_STATUS 0x20120
+#define CN20K_DDRC_PERF_CNT_END_STATUS 0x20100
#define END_STATUS_VAL_END_TIMER_MODE_END 0x1
/* Performance Counters Configuration Registers */
#define CN10K_DDRC_PERF_CFG_BASE 0x8040
#define ODY_DDRC_PERF_CFG_BASE 0x20160
+#define CN20K_DDRC_PERF_CFG_BASE 0x20140
+#define CN20K_DDRC_PERF_CFG1_BASE 0x20180
/* 8 Generic event counter + 2 fixed event counters */
#define DDRC_PERF_NUM_GEN_COUNTERS 8
@@ -61,6 +73,23 @@
* DO NOT change these event-id numbers, they are used to
* program event bitmap in h/w.
*/
+
+/* CN20K specific events */
+#define EVENT_PERF_OP_IS_RD16 61
+#define EVENT_PERF_OP_IS_RD32 60
+#define EVENT_PERF_OP_IS_WR16 59
+#define EVENT_PERF_OP_IS_WR32 58
+#define EVENT_OP_IS_ENTER_DSM 44
+#define EVENT_OP_IS_RFM 43
+
+#define EVENT_CN20K_OP_IS_TCR_MRR 50
+#define EVENT_CN20K_OP_IS_DQSOSC_MRR 49
+#define EVENT_CN20K_OP_IS_DQSOSC_MPC 48
+#define EVENT_CN20K_VISIBLE_WIN_LIMIT_REACHED_WR 47
+#define EVENT_CN20K_VISIBLE_WIN_LIMIT_REACHED_RD 46
+#define EVENT_CN20K_OP_IS_ZQLATCH 21
+#define EVENT_CN20K_OP_IS_ZQSTART 22
+
#define EVENT_DFI_CMD_IS_RETRY 61
#define EVENT_RD_UC_ECC_ERROR 60
#define EVENT_RD_CRC_ERROR 59
@@ -87,6 +116,9 @@
#define EVENT_OP_IS_SPEC_REF 41
#define EVENT_OP_IS_CRIT_REF 40
#define EVENT_OP_IS_REFRESH 39
+#define EVENT_OP_IS_CAS_WCK_SUS 38
+#define EVENT_OP_IS_CAS_WS_OFF 37
+#define EVENT_OP_IS_CAS_WS 36
#define EVENT_OP_IS_ENTER_MPSM 35
#define EVENT_OP_IS_ENTER_POWERDOWN 31
#define EVENT_OP_IS_ENTER_SELFREF 27
@@ -183,8 +215,8 @@ struct ddr_pmu_platform_data {
u64 cnt_freerun_clr;
u64 cnt_value_wr_op;
u64 cnt_value_rd_op;
- bool is_cn10k;
- bool is_ody;
+ u64 cfg1_base;
+ unsigned int silicon_flags; /* IS_CN10K, IS_ODY, or IS_CN20K */
};
static ssize_t cn10k_ddr_pmu_event_show(struct device *dev,
@@ -336,6 +368,80 @@ static struct attribute *odyssey_ddr_perf_events_attrs[] = {
NULL
};
+static struct attribute *cn20k_ddr_perf_events_attrs[] = {
+ /* Programmable */
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_hif_rd_or_wr_access, EVENT_HIF_RD_OR_WR),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_hif_wr_access, EVENT_HIF_WR),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_hif_rd_access, EVENT_HIF_RD),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_hif_rmw_access, EVENT_HIF_RMW),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_hif_pri_rdaccess, EVENT_HIF_HI_PRI_RD),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_rd_bypass_access, EVENT_READ_BYPASS),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_act_bypass_access, EVENT_ACT_BYPASS),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_dfi_wr_data_access,
+ EVENT_DFI_WR_DATA_CYCLES),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_dfi_rd_data_access,
+ EVENT_DFI_RD_DATA_CYCLES),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_hpri_sched_rd_crit_access,
+ EVENT_HPR_XACT_WHEN_CRITICAL),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_lpri_sched_rd_crit_access,
+ EVENT_LPR_XACT_WHEN_CRITICAL),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_wr_trxn_crit_access,
+ EVENT_WR_XACT_WHEN_CRITICAL),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_cam_active_access, EVENT_OP_IS_ACTIVATE),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_cam_rd_or_wr_access,
+ EVENT_OP_IS_RD_OR_WR),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_cam_rd_active_access,
+ EVENT_OP_IS_RD_ACTIVATE),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_cam_read, EVENT_OP_IS_RD),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_cam_write, EVENT_OP_IS_WR),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_cam_mwr, EVENT_OP_IS_MWR),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_precharge, EVENT_OP_IS_PRECHARGE),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_precharge_for_rdwr,
+ EVENT_PRECHARGE_FOR_RDWR),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_precharge_for_other,
+ EVENT_PRECHARGE_FOR_OTHER),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_rdwr_transitions, EVENT_RDWR_TRANSITIONS),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_write_combine, EVENT_WRITE_COMBINE),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_war_hazard, EVENT_WAR_HAZARD),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_raw_hazard, EVENT_RAW_HAZARD),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_waw_hazard, EVENT_WAW_HAZARD),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_enter_selfref, EVENT_OP_IS_ENTER_SELFREF),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_enter_powerdown,
+ EVENT_OP_IS_ENTER_POWERDOWN),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_cas_ws, EVENT_OP_IS_CAS_WS),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_cas_ws_off, EVENT_OP_IS_CAS_WS_OFF),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_cas_wck_sus, EVENT_OP_IS_CAS_WCK_SUS),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_refresh, EVENT_OP_IS_REFRESH),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_crit_ref, EVENT_OP_IS_CRIT_REF),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_spec_ref, EVENT_OP_IS_SPEC_REF),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_load_mode, EVENT_OP_IS_LOAD_MODE),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_rfm, EVENT_OP_IS_RFM),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_enter_dsm, EVENT_OP_IS_ENTER_DSM),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_dfi_cycles, EVENT_DFI_CYCLES),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_win_limit_reached_rd,
+ EVENT_CN20K_VISIBLE_WIN_LIMIT_REACHED_RD),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_win_limit_reached_wr,
+ EVENT_CN20K_VISIBLE_WIN_LIMIT_REACHED_WR),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_dqsosc_mpc, EVENT_CN20K_OP_IS_DQSOSC_MPC),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_dqsosc_mrr, EVENT_CN20K_OP_IS_DQSOSC_MRR),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_tcr_mrr, EVENT_CN20K_OP_IS_TCR_MRR),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_zqstart, EVENT_CN20K_OP_IS_ZQSTART),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_zqlatch, EVENT_CN20K_OP_IS_ZQLATCH),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_read16, EVENT_PERF_OP_IS_RD16),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_read32, EVENT_PERF_OP_IS_RD32),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_write16, EVENT_PERF_OP_IS_WR16),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_write32, EVENT_PERF_OP_IS_WR32),
+ /* Free run event counters */
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_ddr_reads, EVENT_DDR_READS),
+ CN10K_DDR_PMU_EVENT_ATTR(ddr_ddr_writes, EVENT_DDR_WRITES),
+ NULL
+};
+
+static struct attribute_group cn20k_ddr_perf_events_attr_group = {
+ .name = "events",
+ .attrs = cn20k_ddr_perf_events_attrs,
+};
+
static struct attribute_group odyssey_ddr_perf_events_attr_group = {
.name = "events",
.attrs = odyssey_ddr_perf_events_attrs,
@@ -393,6 +499,13 @@ static const struct attribute_group *odyssey_attr_groups[] = {
NULL
};
+static const struct attribute_group *cn20k_attr_groups[] = {
+ &cn20k_ddr_perf_events_attr_group,
+ &cn10k_ddr_perf_format_attr_group,
+ &cn10k_ddr_perf_cpumask_attr_group,
+ NULL
+};
+
/* Default poll timeout is 100 sec, which is very sufficient for
* 48 bit counter incremented max at 5.6 GT/s, which may take many
* hours to overflow.
@@ -412,7 +525,7 @@ static int ddr_perf_get_event_bitmap(int eventid, u64 *event_bitmap,
switch (eventid) {
case EVENT_DFI_PARITY_POISON ...EVENT_DFI_CMD_IS_RETRY:
- if (!ddr_pmu->p_data->is_ody) {
+ if (!(ddr_pmu->p_data->silicon_flags & IS_ODY)) {
err = -EINVAL;
break;
}
@@ -524,9 +637,9 @@ static void cn10k_ddr_perf_counter_enable(struct cn10k_ddr_pmu *pmu,
int counter, bool enable)
{
const struct ddr_pmu_platform_data *p_data = pmu->p_data;
+ unsigned int silicon_flags = pmu->p_data->silicon_flags;
u64 ctrl_reg = pmu->p_data->cnt_op_mode_ctrl;
const struct ddr_pmu_ops *ops = pmu->ops;
- bool is_ody = pmu->p_data->is_ody;
u32 reg;
u64 val;
@@ -546,7 +659,7 @@ static void cn10k_ddr_perf_counter_enable(struct cn10k_ddr_pmu *pmu,
writeq_relaxed(val, pmu->base + reg);
- if (is_ody) {
+ if (silicon_flags & IS_ODY) {
if (enable) {
/*
* Setup the PMU counter to work in
@@ -621,6 +734,7 @@ static int cn10k_ddr_perf_event_add(struct perf_event *event, int flags)
{
struct cn10k_ddr_pmu *pmu = to_cn10k_ddr_pmu(event->pmu);
const struct ddr_pmu_platform_data *p_data = pmu->p_data;
+ unsigned int silicon_flags = pmu->p_data->silicon_flags;
const struct ddr_pmu_ops *ops = pmu->ops;
struct hw_perf_event *hwc = &event->hw;
u8 config = event->attr.config;
@@ -642,10 +756,17 @@ static int cn10k_ddr_perf_event_add(struct perf_event *event, int flags)
if (counter < DDRC_PERF_NUM_GEN_COUNTERS) {
/* Generic counters, configure event id */
reg_offset = DDRC_PERF_CFG(p_data->cfg_base, counter);
- ret = ddr_perf_get_event_bitmap(config, &val, pmu);
- if (ret)
- return ret;
+ if (silicon_flags & IS_CN20K) {
+ val = (1ULL << (config - 1));
+ if (config == EVENT_CN20K_OP_IS_ZQSTART ||
+ config == EVENT_CN20K_OP_IS_ZQLATCH)
+ reg_offset = DDRC_PERF_CFG(p_data->cfg1_base, counter);
+ } else {
+ ret = ddr_perf_get_event_bitmap(config, &val, pmu);
+ if (ret)
+ return ret;
+ }
writeq_relaxed(val, pmu->base + reg_offset);
} else {
/* fixed event counter, clear counter value */
@@ -952,7 +1073,25 @@ static const struct ddr_pmu_platform_data cn10k_ddr_pmu_pdata = {
.cnt_freerun_clr = 0,
.cnt_value_wr_op = CN10K_DDRC_PERF_CNT_VALUE_WR_OP,
.cnt_value_rd_op = CN10K_DDRC_PERF_CNT_VALUE_RD_OP,
- .is_cn10k = TRUE,
+ .silicon_flags = IS_CN10K,
+};
+
+static const struct ddr_pmu_platform_data cn20k_ddr_pmu_pdata = {
+ .counter_overflow_val = 0,
+ .counter_max_val = GENMASK_ULL(63, 0),
+ .cnt_base = ODY_DDRC_PERF_CNT_VALUE_BASE,
+ .cfg_base = CN20K_DDRC_PERF_CFG_BASE,
+ .cfg1_base = CN20K_DDRC_PERF_CFG1_BASE,
+ .cnt_op_mode_ctrl = CN20K_DDRC_PERF_CNT_OP_MODE_CTRL,
+ .cnt_start_op_ctrl = CN20K_DDRC_PERF_CNT_START_OP_CTRL,
+ .cnt_end_op_ctrl = CN20K_DDRC_PERF_CNT_END_OP_CTRL,
+ .cnt_end_status = CN20K_DDRC_PERF_CNT_END_STATUS,
+ .cnt_freerun_en = 0,
+ .cnt_freerun_ctrl = ODY_DDRC_PERF_CNT_FREERUN_CTRL,
+ .cnt_freerun_clr = ODY_DDRC_PERF_CNT_FREERUN_CLR,
+ .cnt_value_wr_op = ODY_DDRC_PERF_CNT_VALUE_WR_OP,
+ .cnt_value_rd_op = ODY_DDRC_PERF_CNT_VALUE_RD_OP,
+ .silicon_flags = IS_CN20K,
};
#endif
@@ -979,7 +1118,7 @@ static const struct ddr_pmu_platform_data odyssey_ddr_pmu_pdata = {
.cnt_freerun_clr = ODY_DDRC_PERF_CNT_FREERUN_CLR,
.cnt_value_wr_op = ODY_DDRC_PERF_CNT_VALUE_WR_OP,
.cnt_value_rd_op = ODY_DDRC_PERF_CNT_VALUE_RD_OP,
- .is_ody = TRUE,
+ .silicon_flags = IS_ODY,
};
#endif
@@ -989,8 +1128,7 @@ static int cn10k_ddr_perf_probe(struct platform_device *pdev)
struct cn10k_ddr_pmu *ddr_pmu;
struct resource *res;
void __iomem *base;
- bool is_cn10k;
- bool is_ody;
+ unsigned int silicon_flags;
char *name;
int ret;
@@ -1014,10 +1152,9 @@ static int cn10k_ddr_perf_probe(struct platform_device *pdev)
ddr_pmu->base = base;
ddr_pmu->p_data = dev_data;
- is_cn10k = ddr_pmu->p_data->is_cn10k;
- is_ody = ddr_pmu->p_data->is_ody;
+ silicon_flags = ddr_pmu->p_data->silicon_flags;
- if (is_cn10k) {
+ if (silicon_flags & IS_CN10K) {
ddr_pmu->ops = &ddr_pmu_ops;
/* Setup the PMU counter to work in manual mode */
writeq_relaxed(OP_MODE_CTRL_VAL_MANUAL, ddr_pmu->base +
@@ -1039,7 +1176,7 @@ static int cn10k_ddr_perf_probe(struct platform_device *pdev)
};
}
- if (is_ody) {
+ if (silicon_flags & IS_ODY) {
ddr_pmu->ops = &ddr_pmu_ody_ops;
ddr_pmu->pmu = (struct pmu) {
@@ -1056,6 +1193,22 @@ static int cn10k_ddr_perf_probe(struct platform_device *pdev)
};
}
+ if (silicon_flags & IS_CN20K) {
+ ddr_pmu->ops = &ddr_pmu_ody_ops;
+
+ ddr_pmu->pmu = (struct pmu) {
+ .module = THIS_MODULE,
+ .capabilities = PERF_PMU_CAP_NO_EXCLUDE,
+ .task_ctx_nr = perf_invalid_context,
+ .attr_groups = cn20k_attr_groups,
+ .event_init = cn10k_ddr_perf_event_init,
+ .add = cn10k_ddr_perf_event_add,
+ .del = cn10k_ddr_perf_event_del,
+ .start = cn10k_ddr_perf_event_start,
+ .stop = cn10k_ddr_perf_event_stop,
+ .read = cn10k_ddr_perf_event_update,
+ };
+ }
/* Choose this cpu to collect perf data */
ddr_pmu->cpu = raw_smp_processor_id();
@@ -1098,6 +1251,7 @@ static void cn10k_ddr_perf_remove(struct platform_device *pdev)
#ifdef CONFIG_OF
static const struct of_device_id cn10k_ddr_pmu_of_match[] = {
{ .compatible = "marvell,cn10k-ddr-pmu", .data = &cn10k_ddr_pmu_pdata },
+ { .compatible = "marvell,cn20k-ddr-pmu", .data = &cn20k_ddr_pmu_pdata },
{ },
};
MODULE_DEVICE_TABLE(of, cn10k_ddr_pmu_of_match);
@@ -1107,6 +1261,7 @@ MODULE_DEVICE_TABLE(of, cn10k_ddr_pmu_of_match);
static const struct acpi_device_id cn10k_ddr_pmu_acpi_match[] = {
{"MRVL000A", (kernel_ulong_t)&cn10k_ddr_pmu_pdata },
{"MRVL000C", (kernel_ulong_t)&odyssey_ddr_pmu_pdata},
+ {"MRVL000B", (kernel_ulong_t)&cn20k_ddr_pmu_pdata},
{},
};
MODULE_DEVICE_TABLE(acpi, cn10k_ddr_pmu_acpi_match);
--
2.25.1
^ permalink raw reply related
* [PATCH v2 1/2] dt-bindings: perf: marvell: Document CN20K DDR PMU
From: Geetha sowjanya @ 2026-03-29 15:24 UTC (permalink / raw)
To: linux-perf-users, linux-kernel, linux-arm-kernel, devicetree
Cc: mark.rutland, will, krzk+dt
In-Reply-To: <20260329152439.10573-1-gakula@marvell.com>
Add a devicetree binding for the Marvell CN20K DDR performance
monitor block, including the marvell,cn20k-ddr-pmu compatible
string and the required MMIO reg region.
Signed-off-by: Geetha sowjanya <gakula@marvell.com>
---
Changes in v1:
- Added a description field to the binding.
- Simplified the compatible property using 'const' instead of 'items/enum'.
- Updated the example node name to include a unit-address matching the reg base.
.../bindings/perf/marvell-cn20k-ddr.yaml | 39 +++++++++++++++++++
1 file changed, 39 insertions(+)
create mode 100644 Documentation/devicetree/bindings/perf/marvell-cn20k-ddr.yaml
diff --git a/Documentation/devicetree/bindings/perf/marvell-cn20k-ddr.yaml b/Documentation/devicetree/bindings/perf/marvell-cn20k-ddr.yaml
new file mode 100644
index 000000000000..470eac0a53c4
--- /dev/null
+++ b/Documentation/devicetree/bindings/perf/marvell-cn20k-ddr.yaml
@@ -0,0 +1,39 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/perf/marvell-cn20k-ddr.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Marvell CN20K DDR performance monitor
+
+description:
+ Performance Monitoring Unit (PMU) for the DDR controller
+ in Marvell CN20K SoCs.
+
+maintainers:
+ - Geetha sowjanya <gakula@marvell.com>
+
+properties:
+ compatible:
+ const: marvell,cn20k-ddr-pmu
+
+ reg:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ bus {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ ddr-pmu@c200000000 {
+ compatible = "marvell,cn20k-ddr-pmu";
+ reg = <0xc200 0x00000000 0x0 0x100000>;
+ };
+ };
--
2.25.1
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox