[PATCH v5 20/23] firmware: arm_scmi: Add System Telemetry driver
Cristian Marussi
cristian.marussi at arm.com
Fri Jul 3 05:35:58 PDT 2026
Add an SCMI driver to allow enumeration, configuration and data collection
of SCMI Telemetry data.
The driver exposes one character device for each found SCMI instance and a
set of related IOCTLs can be used to enumerate, configure and collect
Telemetry resources.
Signed-off-by: Cristian Marussi <cristian.marussi at arm.com>
---
drivers/firmware/arm_scmi/Kconfig | 10 +
drivers/firmware/arm_scmi/Makefile | 1 +
.../firmware/arm_scmi/scmi_system_telemetry.c | 1233 +++++++++++++++++
3 files changed, 1244 insertions(+)
create mode 100644 drivers/firmware/arm_scmi/scmi_system_telemetry.c
diff --git a/drivers/firmware/arm_scmi/Kconfig b/drivers/firmware/arm_scmi/Kconfig
index 06d2319420a0..1fc9182224cc 100644
--- a/drivers/firmware/arm_scmi/Kconfig
+++ b/drivers/firmware/arm_scmi/Kconfig
@@ -113,4 +113,14 @@ config ARM_SCMI_POWER_CONTROL
called scmi_power_control. Note this may needed early in boot to catch
early shutdown/reboot SCMI requests.
+config ARM_SCMI_SYSTEM_TELEMETRY
+ tristate "SCMI System Telemetry driver"
+ depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF)
+ help
+ This enables SCMI Systemn Telemetry support that allows userspace to
+ retrieve ARM Telemetry data made available via SCMI.
+
+ This driver can also be built as a module. If so, the module will be
+ called scmi_system_telemetry.
+
endmenu
diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
index fe55b7aa0707..20f8d55840a5 100644
--- a/drivers/firmware/arm_scmi/Makefile
+++ b/drivers/firmware/arm_scmi/Makefile
@@ -18,3 +18,4 @@ obj-$(CONFIG_ARM_SCMI_PROTOCOL) += scmi-core.o
obj-$(CONFIG_ARM_SCMI_PROTOCOL) += scmi-module.o
obj-$(CONFIG_ARM_SCMI_POWER_CONTROL) += scmi_power_control.o
+obj-$(CONFIG_ARM_SCMI_SYSTEM_TELEMETRY) += scmi_system_telemetry.o
diff --git a/drivers/firmware/arm_scmi/scmi_system_telemetry.c b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
new file mode 100644
index 000000000000..e72ac363cf10
--- /dev/null
+++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
@@ -0,0 +1,1233 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SCMI - System Telemetry Driver
+ *
+ * Copyright (C) 2026 ARM Ltd.
+ */
+
+#include <linux/anon_inodes.h>
+#include <linux/atomic.h>
+#include <linux/ctype.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/list.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/overflow.h>
+#include <linux/scmi_protocol.h>
+#include <linux/slab.h>
+#include <linux/sprintf.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include <linux/wait.h>
+
+#include <uapi/linux/scmi.h>
+
+#define SCMI_TLM_DRIVER "scmi-telemetry-driver"
+#define SCMI_TLM_NUM_DEVS 256
+
+#define MAX_BULK_LINE_CHAR_LENGTH 64
+
+/*
+ * Break if inadvertently struct scmi_telemetry_de_sample deviates from the
+ * UAPI counterpart struct scmi_tlm_de_sample: this allows more performant
+ * single DE reads.
+ */
+#define SCMI_DE_SAMPLE_SANITY_CHECK \
+({ \
+ BUILD_BUG_ON_MSG(sizeof(struct scmi_telemetry_de_sample) != \
+ sizeof(struct scmi_tlm_de_sample), \
+ "UAPI DE sample mismatch !"); \
+ ASSERT_STRUCT_OFFSET(struct scmi_telemetry_de_sample, id, \
+ offsetof(struct scmi_tlm_de_sample, id)); \
+ ASSERT_STRUCT_OFFSET(struct scmi_telemetry_de_sample, pad, \
+ offsetof(struct scmi_tlm_de_sample, pad)); \
+ ASSERT_STRUCT_OFFSET(struct scmi_telemetry_de_sample, tstamp, \
+ offsetof(struct scmi_tlm_de_sample, tstamp));\
+ ASSERT_STRUCT_OFFSET(struct scmi_telemetry_de_sample, val, \
+ offsetof(struct scmi_tlm_de_sample, val)); \
+})
+
+static dev_t stlm_devt;
+
+struct scmi_tlm_setup;
+
+struct scmi_tlm_priv {
+ char *buf;
+ size_t buf_sz;
+ int buf_len;
+ unsigned int num_samples;
+ struct scmi_tlm_setup *tsp;
+};
+
+/**
+ * struct scmi_tlm_setup - Telemetry setup descriptor
+ * @dev: A reference to the related device
+ * @ph: A reference to the protocol handle to be used with the ops
+ * @rinfo: A reference to the resource info descriptor
+ * @ops: A reference to the protocol ops
+ */
+struct scmi_tlm_setup {
+ struct device *dev;
+ struct scmi_protocol_handle *ph;
+ const struct scmi_telemetry_res_info *rinfo;
+ const struct scmi_telemetry_proto_ops *ops;
+};
+
+/**
+ * struct scmi_tlm_instance - Telemetry instance descriptor
+ * @id: Progressive number identifying this probed instance.
+ * @devt: The dev_t descriptor attached to this instance
+ * @cdev:
+ * @node: A node to link this in the list of all instances.
+ * @tsp: A reference to the SCMI instance data.
+ * @info: A reference to this instance SCMI Telemetry info data.
+ */
+struct scmi_tlm_instance {
+ unsigned int id;
+ dev_t devt;
+ struct cdev cdev;
+ struct list_head node;
+ struct scmi_tlm_setup *tsp;
+ const struct scmi_telemetry_info *info;
+};
+
+#define to_instance(c) container_of(c, struct scmi_tlm_instance, cdev)
+
+struct scmi_tlm_shmti_ctx {
+ struct scmi_telemetry_shmti_info *shmti;
+ struct kref kref;
+};
+
+#define to_shmti_ctx(c) container_of(c, struct scmi_tlm_shmti_ctx, kref)
+
+static int scmi_tlm_buffer_fill(struct device *dev, char *buf, size_t size,
+ unsigned int *len, unsigned int num,
+ struct scmi_telemetry_de_sample *samples)
+{
+ unsigned int idx, bytes = 0;
+
+ /* Loop till there space for the next line */
+ for (idx = 0; idx < num && size - bytes >= MAX_BULK_LINE_CHAR_LENGTH; idx++) {
+ bytes += scnprintf(buf + bytes, size - bytes,
+ "0x%08X %llu %016llX\n", samples[idx].id,
+ samples[idx].tstamp, samples[idx].val);
+ }
+
+ if (idx < num) {
+ dev_err(dev, "Bulk buffer truncated !\n");
+ return -ENOSPC;
+ }
+
+ if (len)
+ *len = bytes;
+
+ return 0;
+}
+
+static int scmi_tlm_bulk_buffer_fill(struct scmi_tlm_priv *tp)
+{
+ struct scmi_tlm_setup *tsp = tp->tsp;
+ int ret;
+
+ struct scmi_telemetry_de_sample *samples __free(kfree) =
+ kcalloc(tp->num_samples, sizeof(*samples), GFP_KERNEL);
+ if (!samples)
+ return -ENOMEM;
+
+ ret = tsp->ops->des_bulk_read(tsp->ph, SCMI_TLM_GRP_INVALID,
+ &tp->num_samples, samples);
+ if (ret)
+ return ret;
+
+ return scmi_tlm_buffer_fill(tsp->dev, tp->buf, tp->buf_sz, &tp->buf_len,
+ tp->num_samples, samples);
+}
+
+static int scmi_tlm_priv_data_open(struct inode *ino, struct file *filp)
+{
+ struct scmi_tlm_instance *ti = to_instance(ino->i_cdev);
+
+ if (!ti)
+ return -ENODEV;
+
+ struct scmi_tlm_priv *tp __free(kfree) = kzalloc_obj(*tp);
+ if (!tp)
+ return -ENOMEM;
+
+ /* Allocate and empty buffer bug enough to contain all possible DEs */
+ tp->num_samples = ti->info->base.num_des;
+ tp->buf_sz = tp->num_samples * MAX_BULK_LINE_CHAR_LENGTH;
+
+ /*
+ * Note that tp->buf is a scratch buffer, filled once, used to
+ * support multiple chunked read and freed in
+ * scmi_tlm_priv_release.
+ */
+ tp->buf = kzalloc(tp->buf_sz, GFP_KERNEL);
+ if (!tp->buf)
+ return -ENOMEM;
+
+ tp->tsp = ti->tsp;
+ filp->private_data = no_free_ptr(tp);
+
+ return 0;
+}
+
+static ssize_t scmi_tlm_priv_read(struct file *filp, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct scmi_tlm_priv *tp = filp->private_data;
+
+ /*
+ * Retrieve fresh data snapshot on the first read and on each
+ * subsequent seek to zero
+ */
+ if (!tp->buf_len || *ppos == 0) {
+ int ret;
+
+ ret = scmi_tlm_bulk_buffer_fill(tp);
+ if (ret)
+ return ret;
+
+ /* update inode timestamps */
+ simple_inode_init_ts(file_inode(filp));
+ }
+
+ return simple_read_from_buffer(buf, count, ppos, tp->buf, tp->buf_len);
+}
+
+static int scmi_tlm_priv_release(struct inode *ino, struct file *filp)
+{
+ struct scmi_tlm_priv *tp = filp->private_data;
+
+ kfree(tp->buf);
+ kfree(tp);
+
+ return 0;
+}
+
+static inline void scmi_tlm_to_uapi_base_info(struct scmi_tlm_base_info *out,
+ const struct scmi_telemetry_info *in)
+{
+ /* This is a trivial copy */
+ out->version = in->base.version;
+ memcpy(out->de_impl_version, in->base.de_impl_version,
+ sizeof(out->de_impl_version));
+ out->num_des = in->base.num_des;
+ out->num_groups = in->base.num_groups;
+ out->num_intervals = in->base.num_intervals;
+ out->num_shmtis = in->base.num_shmtis;
+ out->flags |= in->reset_support ? SCMI_TLM_BASE_CAN_RESET : 0;
+}
+
+static inline int
+scmi_tlm_to_uapi_intervals(struct scmi_tlm_intervals *out,
+ struct scmi_telemetry_intervals *in,
+ struct scmi_tlm_update_interval *out_interv)
+{
+ unsigned int *in_interv = in->update_intervals;
+
+ /* Does it fit provided param ? */
+ if (out->num_intervals < in->num_intervals)
+ return -EINVAL;
+
+ for (int i = 0; i < in->num_intervals; i++) {
+ out_interv[i].secs =
+ SCMI_TLM_GET_UPDATE_INTERVAL_SECS(in_interv[i]);
+ out_interv[i].exp =
+ SCMI_TLM_GET_UPDATE_INTERVAL_EXP(in_interv[i]);
+ }
+
+ /* Just in case less elements where provided */
+ out->num_intervals = in->num_intervals;
+
+ return 0;
+}
+
+static inline void scmi_tlm_to_uapi_de_info(struct scmi_tlm_de_info *out,
+ struct scmi_telemetry_de_info *in)
+{
+ out->id = in->id;
+ out->grp_id = in->grp_id;
+ out->data_sz = in->data_sz;
+ out->type = in->type;
+ out->unit = in->unit;
+ out->unit_exp = in->unit_exp;
+ out->ts_rate = in->ts_rate;
+ out->instance_id = in->instance_id;
+ out->compo_instance_id = in->compo_instance_id;
+ out->compo_type = in->compo_type;
+ out->persistent = !!in->persistent;
+ out->flags = in->grp_id != SCMI_TLM_GRP_INVALID ?
+ SCMI_TLM_DEINFO_GROUP : 0;
+ out->pad[0] = 0;
+ out->pad[1] = 0;
+ out->pad2 = 0;
+ memcpy(out->name, in->name, 16);
+}
+
+static inline int
+scmi_tlm_to_uapi_des_list(struct scmi_tlm_des_list *out,
+ const struct scmi_telemetry_res_info *in,
+ struct scmi_tlm_de_info *des)
+{
+ struct scmi_tlm_de_info *out_des = des;
+
+ /* Does it fit provided param ? */
+ if (out->num_des < in->num_des)
+ return -EINVAL;
+
+ for (int i = 0; i < in->num_des; i++)
+ scmi_tlm_to_uapi_de_info(&out_des[i], &in->dei_store[i]);
+
+ /* Just in case less elements where provided */
+ out->num_des = in->num_des;
+
+ return 0;
+}
+
+static inline void scmi_tlm_to_uapi_grp_info(struct scmi_tlm_grp_info *out,
+ struct scmi_telemetry_grp_info *in)
+{
+ out->grp_id = in->grp_id;
+ out->num_des = in->num_des;
+ out->num_intervals = in->num_intervals;
+ out->pad = 0;
+}
+
+static inline int
+scmi_tlm_to_uapi_grps_list(struct scmi_tlm_grps_list *out,
+ const struct scmi_telemetry_res_info *in,
+ struct scmi_tlm_grp_info *ginfo)
+{
+ struct scmi_tlm_grp_info *out_ginfo = ginfo;
+
+ /* Does it fit provided param ? */
+ if (out->num_grps < in->num_groups)
+ return -EINVAL;
+
+ for (int i = 0; i < in->num_groups; i++)
+ scmi_tlm_to_uapi_grp_info(&out_ginfo[i], &in->grps_store[i]);
+
+ /* Just in case less elements where provided */
+ out->num_grps = in->num_groups;
+
+ return 0;
+}
+
+static inline int
+scmi_tlm_to_uapi_composing_des(struct scmi_tlm_grp_desc *out,
+ const struct scmi_telemetry_res_info *in,
+ u32 *composing_des)
+{
+ struct scmi_telemetry_grp_info *ginfo = &in->grps_store[out->grp_id];
+ struct scmi_telemetry_group *grp = &in->grps[out->grp_id];
+
+ /* Does it fit provided param ? */
+ if (out->num_des < ginfo->num_des)
+ return -EINVAL;
+
+ for (int i = 0; i < ginfo->num_des; i++)
+ composing_des[i] = in->des[grp->des[i]]->info->id;
+
+ /* Just in case less elements where provided */
+ out->num_des = ginfo->num_des;
+
+ return 0;
+}
+
+/**
+ * scmi_telemetry_res_info_get - Resources info getter
+ * @tsp: A reference to the telemetry instance setup
+ *
+ * On first call this helper takes care to retrieve and cache all the resources
+ * descriptor from the platform, then, on the following invocations it will
+ * always return the cached value.
+ */
+static inline const struct scmi_telemetry_res_info *
+scmi_telemetry_res_info_get(struct scmi_tlm_setup *tsp)
+{
+ const struct scmi_telemetry_res_info *rinfo;
+
+ if (tsp->rinfo)
+ return tsp->rinfo;
+
+ rinfo = tsp->ops->res_get(tsp->ph);
+ /* Cache the retrieved resource info value */
+ smp_store_mb(tsp->rinfo, rinfo);
+
+ return rinfo;
+}
+
+static long
+scmi_tlm_info_get_ioctl(const struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_base_info base;
+
+ scmi_tlm_to_uapi_base_info(&base, ti->info);
+ if (copy_to_user(uptr, &base, sizeof(base)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long
+scmi_tlm_config_get_ioctl(const struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_config cfg;
+
+ if (copy_from_user(&cfg, uptr, sizeof(cfg)))
+ return -EFAULT;
+
+ if (cfg.pad || cfg.flags & ~SCMI_TLM_CONFIG_FLAGS)
+ return -EINVAL;
+
+ if (!(SCMI_TLM_CONFIG_IS_GROUP(cfg.flags))) {
+ unsigned int active = ti->info->active_update_interval;
+
+ cfg.enable = !!ti->info->enabled;
+ cfg.active.secs = SCMI_TLM_GET_UPDATE_INTERVAL_SECS(active);
+ cfg.active.exp = SCMI_TLM_GET_UPDATE_INTERVAL_EXP(active);
+ } else {
+ const struct scmi_telemetry_res_info *rinfo;
+ struct scmi_telemetry_group *grp;
+ unsigned int active;
+
+ rinfo = scmi_telemetry_res_info_get(ti->tsp);
+ if (cfg.grp_id >= rinfo->num_groups)
+ return -EINVAL;
+
+ grp = &rinfo->grps[cfg.grp_id];
+ active = grp->active_update_interval;
+
+ cfg.enable = grp->enabled;
+ cfg.t_enable = grp->tstamp_enabled;
+ cfg.active.secs = SCMI_TLM_GET_UPDATE_INTERVAL_SECS(active);
+ cfg.active.exp = SCMI_TLM_GET_UPDATE_INTERVAL_EXP(active);
+ }
+
+ if (copy_to_user(uptr, &cfg, sizeof(cfg)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long
+scmi_tlm_config_set_ioctl(const struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ struct scmi_tlm_config cfg;
+ unsigned int active;
+
+ if (copy_from_user(&cfg, uptr, sizeof(cfg)))
+ return -EFAULT;
+
+ if (cfg.pad || cfg.flags & ~SCMI_TLM_CONFIG_FLAGS)
+ return -EINVAL;
+
+ if (!SCMI_TLM_CONFIG_IS_GROUP(cfg.flags)) {
+ cfg.grp_id = SCMI_TLM_GRP_INVALID;
+ } else {
+ int ret;
+
+ ret = tsp->ops->state_set(tsp->ph, true, cfg.grp_id,
+ (bool *)&cfg.enable,
+ (bool *)&cfg.t_enable);
+ if (ret)
+ return ret;
+ }
+
+ active = SCMI_TLM_BUILD_UPDATE_INTERVAL(cfg.active.secs, cfg.active.exp);
+
+ return tsp->ops->collection_configure(tsp->ph, cfg.grp_id,
+ (bool *)&cfg.enable, &active, NULL);
+}
+
+static long
+scmi_tlm_intervals_get_ioctl(const struct scmi_tlm_instance *ti,
+ unsigned long arg)
+{
+ struct scmi_telemetry_intervals *tlm_ivs;
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_intervals ivs;
+ size_t ivs_intrv_sz;
+ int ret;
+
+ if (copy_from_user(&ivs, uptr, sizeof(ivs)))
+ return -EFAULT;
+
+ if (ivs.pad[0] || ivs.pad[1] || ivs.pad[2] || ivs.pad2 ||
+ ivs.flags & ~SCMI_TLM_INTERV_FLAGS)
+ return -EINVAL;
+
+ if (!SCMI_TLM_INTERV_IS_GROUP(ivs.flags)) {
+ tlm_ivs = ti->info->intervals;
+ } else {
+ const struct scmi_telemetry_res_info *rinfo;
+
+ rinfo = scmi_telemetry_res_info_get(ti->tsp);
+ if (ivs.grp_id >= rinfo->num_groups)
+ return -EINVAL;
+
+ tlm_ivs = rinfo->grps[ivs.grp_id].intervals;
+ }
+
+ /* A local scratch buffer... */
+ ivs_intrv_sz = ivs.num_intervals * sizeof(struct scmi_tlm_update_interval);
+ struct scmi_tlm_update_interval *ivs_intrv __free(kfree) =
+ kzalloc(ivs_intrv_sz, GFP_KERNEL);
+ if (!ivs_intrv)
+ return -ENOMEM;
+
+ if (copy_from_user(ivs_intrv, u64_to_user_ptr(ivs.intervals),
+ ivs_intrv_sz))
+ return -EFAULT;
+
+ /* Passing the array as a param avoids dancing with uptr->intervals */
+ ret = scmi_tlm_to_uapi_intervals(&ivs, tlm_ivs, ivs_intrv);
+ if (ret)
+ return ret;
+
+ if (copy_to_user(u64_to_user_ptr(ivs.intervals),
+ (void *)ivs_intrv, ivs_intrv_sz))
+ return -EFAULT;
+
+ if (copy_to_user(uptr, &ivs, sizeof(ivs)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long
+scmi_tlm_de_config_set_ioctl(const struct scmi_tlm_instance *ti,
+ unsigned long arg, bool all)
+{
+ const struct scmi_telemetry_res_info *rinfo;
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ struct scmi_tlm_de_config tcfg = {};
+ int ret;
+
+ if (copy_from_user(&tcfg, uptr, sizeof(tcfg)))
+ return -EFAULT;
+
+ if (tcfg.pad)
+ return -EINVAL;
+
+ if (!all)
+ return tsp->ops->state_set(tsp->ph, false, tcfg.id,
+ (bool *)&tcfg.enable,
+ (bool *)&tcfg.t_enable);
+
+ rinfo = scmi_telemetry_res_info_get(tsp);
+ for (int i = 0; i < rinfo->num_des; i++) {
+ ret = tsp->ops->state_set(tsp->ph, false,
+ rinfo->des[i]->info->id,
+ (bool *)&tcfg.enable,
+ (bool *)&tcfg.t_enable);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static long
+scmi_tlm_de_config_get_ioctl(const struct scmi_tlm_instance *ti,
+ unsigned long arg, bool all)
+{
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ struct scmi_tlm_de_config tcfg;
+ int ret;
+
+ if (copy_from_user(&tcfg, uptr, sizeof(tcfg)))
+ return -EFAULT;
+
+ if (tcfg.pad)
+ return -EINVAL;
+
+ ret = tsp->ops->state_get(tsp->ph, all ? NULL : &tcfg.id,
+ (bool *)&tcfg.enable, (bool *)&tcfg.t_enable);
+ if (ret)
+ return ret;
+
+ if (copy_to_user(uptr, &tcfg, sizeof(tcfg)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long
+scmi_tlm_de_info_get_ioctl(const struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ const struct scmi_telemetry_de *de;
+ struct scmi_tlm_de_info dei;
+
+ if (copy_from_user(&dei, uptr, sizeof(dei)))
+ return -EFAULT;
+
+ /* Flags does NOT make sense in the input param */
+ if (dei.pad[0] || dei.pad[1] || dei.pad[2] || dei.flags)
+ return -EINVAL;
+
+ de = tsp->ops->de_lookup(tsp->ph, dei.id);
+ if (!de)
+ return -EINVAL;
+
+ scmi_tlm_to_uapi_de_info(&dei, de->info);
+ if (copy_to_user(uptr, &dei, sizeof(dei)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long
+scmi_tlm_des_list_get_ioctl(const struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ const struct scmi_telemetry_res_info *rinfo;
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_des_list dsl;
+ size_t dsl_des_sz;
+ int ret;
+
+ rinfo = scmi_telemetry_res_info_get(ti->tsp);
+ if (copy_from_user(&dsl, uptr, sizeof(dsl)))
+ return -EFAULT;
+
+ if (dsl.pad || dsl.num_des < rinfo->num_des)
+ return -EINVAL;
+
+ /* A local scratch buffer... */
+ dsl_des_sz = dsl.num_des * sizeof(struct scmi_tlm_de_info);
+ struct scmi_tlm_de_info *dsl_des __free(kfree) =
+ kzalloc(dsl_des_sz, GFP_KERNEL);
+ if (!dsl_des)
+ return -ENOMEM;
+
+ if (copy_from_user(dsl_des, u64_to_user_ptr(dsl.des), dsl_des_sz))
+ return -EFAULT;
+
+ /* Passing the array as a param avoids dancing with uptr->intervals */
+ ret = scmi_tlm_to_uapi_des_list(&dsl, rinfo, dsl_des);
+ if (ret)
+ return ret;
+
+ if (copy_to_user(u64_to_user_ptr(dsl.des), (void *)dsl_des, dsl_des_sz))
+ return -EFAULT;
+
+ if (copy_to_user(uptr, &dsl, sizeof(dsl)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long
+scmi_tlm_de_value_get_ioctl(const struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ struct scmi_tlm_de_sample sample;
+ int ret;
+
+ SCMI_DE_SAMPLE_SANITY_CHECK;
+
+ if (copy_from_user(&sample, uptr, sizeof(sample)))
+ return -EFAULT;
+
+ if (sample.pad)
+ return -EINVAL;
+
+ ret = tsp->ops->de_data_read(tsp->ph,
+ (struct scmi_telemetry_de_sample *)&sample);
+ if (ret)
+ return ret;
+
+ if (copy_to_user(uptr, &sample, sizeof(sample)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long
+scmi_tlm_grp_info_get_ioctl(const struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ const struct scmi_telemetry_res_info *rinfo;
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_grp_info ginfo;
+
+ if (copy_from_user(&ginfo, uptr, sizeof(ginfo)))
+ return -EFAULT;
+
+ if (ginfo.pad)
+ return -EINVAL;
+
+ rinfo = scmi_telemetry_res_info_get(ti->tsp);
+ if (ginfo.grp_id > rinfo->num_groups)
+ return -EINVAL;
+
+ scmi_tlm_to_uapi_grp_info(&ginfo, &rinfo->grps_store[ginfo.grp_id]);
+
+ if (copy_to_user(uptr, &ginfo, sizeof(ginfo)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long
+scmi_tlm_grp_desc_get_ioctl(const struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ const struct scmi_telemetry_res_info *rinfo;
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_grp_desc gdesc;
+ size_t composing_sz;
+ int ret;
+
+ if (copy_from_user(&gdesc, uptr, sizeof(gdesc)))
+ return -EFAULT;
+
+ rinfo = scmi_telemetry_res_info_get(ti->tsp);
+ if (gdesc.grp_id > rinfo->num_groups)
+ return -EINVAL;
+
+ /* A local scratch buffer... */
+ composing_sz = gdesc.num_des * sizeof(u32);
+ u32 *composing_des __free(kfree) = kzalloc(composing_sz, GFP_KERNEL);
+ if (!composing_des)
+ return -ENOMEM;
+
+ if (copy_from_user(composing_des, u64_to_user_ptr(gdesc.composing_des),
+ composing_sz))
+ return -EFAULT;
+
+ /* Passing the array as a param avoids dancing with uptr->intervals */
+ ret = scmi_tlm_to_uapi_composing_des(&gdesc, rinfo, composing_des);
+ if (ret)
+ return ret;
+
+ if (copy_to_user(u64_to_user_ptr(gdesc.composing_des), composing_des,
+ composing_sz))
+ return -EFAULT;
+
+ if (copy_to_user(uptr, &gdesc, sizeof(gdesc)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long
+scmi_tlm_grps_list_get_ioctl(const struct scmi_tlm_instance *ti, unsigned long arg)
+{
+ const struct scmi_telemetry_res_info *rinfo;
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_grps_list gsl;
+ size_t ginfo_sz;
+ int ret;
+
+ if (copy_from_user(&gsl, uptr, sizeof(gsl)))
+ return -EFAULT;
+
+ if (gsl.pad)
+ return -EINVAL;
+
+ rinfo = scmi_telemetry_res_info_get(ti->tsp);
+ if (gsl.num_grps < rinfo->num_groups)
+ return -EINVAL;
+
+ /* A local scratch buffer... */
+ ginfo_sz = gsl.num_grps * sizeof(struct scmi_tlm_grp_info);
+ struct scmi_tlm_grp_info *ginfo __free(kfree) =
+ kzalloc(ginfo_sz, GFP_KERNEL);
+ if (!ginfo)
+ return -ENOMEM;
+
+ if (copy_from_user(ginfo, u64_to_user_ptr(gsl.grps), ginfo_sz))
+ return -EFAULT;
+
+ ret = scmi_tlm_to_uapi_grps_list(&gsl, rinfo, ginfo);
+ if (ret)
+ return ret;
+
+ if (copy_to_user(u64_to_user_ptr(gsl.grps), ginfo, ginfo_sz))
+ return -EFAULT;
+
+ if (copy_to_user(uptr, &gsl, sizeof(gsl)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long scmi_tlm_des_read_ioctl(const struct scmi_tlm_instance *ti,
+ unsigned long arg, bool single)
+{
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ struct scmi_tlm_data_read bulk;
+ unsigned int grp_id;
+ int ret;
+
+ if (copy_from_user(&bulk, uptr, sizeof(bulk)))
+ return -EFAULT;
+
+ if (bulk.pad[0] || bulk.pad[1] || bulk.pad[2] || bulk.pad2 ||
+ bulk.flags & ~SCMI_TLM_READ_FLAGS)
+ return -EINVAL;
+
+ if (!SCMI_TLM_READ_IS_GROUP(bulk.flags)) {
+ grp_id = SCMI_TLM_GRP_INVALID;
+ } else {
+ const struct scmi_telemetry_res_info *rinfo;
+
+ rinfo = scmi_telemetry_res_info_get(tsp);
+ if (bulk.grp_id >= rinfo->num_groups)
+ return -EINVAL;
+
+ grp_id = bulk.grp_id;
+ }
+
+ struct scmi_telemetry_de_sample *samples __free(kfree) =
+ kcalloc(bulk.num_samples, sizeof(*samples), GFP_KERNEL);
+ if (!samples)
+ return -ENOMEM;
+
+ if (!single)
+ ret = tsp->ops->des_bulk_read(tsp->ph, grp_id,
+ &bulk.num_samples, samples);
+ else
+ ret = tsp->ops->des_sample_get(tsp->ph, grp_id,
+ &bulk.num_samples, samples);
+ if (ret)
+ return ret;
+
+ if (copy_to_user(u64_to_user_ptr(bulk.samples), samples,
+ bulk.num_samples * sizeof(*samples)))
+ return -EFAULT;
+
+ if (copy_to_user(uptr, &bulk, sizeof(bulk)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long scmi_tlm_des_batch_read_ioctl(const struct scmi_tlm_instance *ti,
+ unsigned long arg)
+{
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_setup *tsp = ti->tsp;
+ struct scmi_tlm_data_read batch;
+ int ret;
+
+ if (copy_from_user(&batch, uptr, sizeof(batch)))
+ return -EFAULT;
+
+ if (batch.pad[0] || batch.pad[1] || batch.pad[2] || batch.pad2 ||
+ batch.flags & ~SCMI_TLM_READ_FLAGS)
+ return -EINVAL;
+
+ struct scmi_telemetry_de_sample *samples __free(kfree) =
+ kcalloc(batch.num_samples, sizeof(*samples), GFP_KERNEL);
+ if (!samples)
+ return -ENOMEM;
+
+ /* Read all the requested samples */
+ if (copy_from_user(samples, u64_to_user_ptr(batch.samples),
+ batch.num_samples * sizeof(*samples)))
+ return -EFAULT;
+
+ for (int i = 0; i < batch.num_samples; i++) {
+ ret = tsp->ops->de_data_read(tsp->ph, &samples[i]);
+ if (ret)
+ return -EINVAL;
+ }
+
+ if (copy_to_user(u64_to_user_ptr(batch.samples), samples,
+ batch.num_samples * sizeof(*samples)))
+ return -EFAULT;
+
+ if (copy_to_user(uptr, &batch, sizeof(batch)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static struct scmi_tlm_shmti_ctx *
+scmi_tlm_shmti_ctx_alloc(struct scmi_telemetry_shmti_info *shmti)
+{
+ struct scmi_tlm_shmti_ctx *ctx;
+
+ ctx = kzalloc_obj(*ctx);
+ if (!ctx)
+ return ERR_PTR(-ENOMEM);
+
+ ctx->shmti = shmti;
+ kref_init(&ctx->kref);
+
+ return ctx;
+}
+
+static void scmi_tlm_shmti_ctx_release(struct kref *kref)
+{
+ struct scmi_tlm_shmti_ctx *ctx = to_shmti_ctx(kref);
+
+ kfree(ctx);
+}
+
+static struct scmi_tlm_shmti_ctx *
+scmi_tlm_shmti_ctx_get(struct scmi_tlm_shmti_ctx *ctx)
+{
+ kref_get(&ctx->kref);
+
+ return ctx;
+}
+
+static void scmi_tlm_shmti_ctx_put(struct scmi_tlm_shmti_ctx *ctx)
+{
+ kref_put(&ctx->kref, scmi_tlm_shmti_ctx_release);
+}
+
+static void scmi_tlm_shmti_vma_open(struct vm_area_struct *vma)
+{
+ scmi_tlm_shmti_ctx_get(vma->vm_private_data);
+}
+
+static void scmi_tlm_shmti_vma_close(struct vm_area_struct *vma)
+{
+ scmi_tlm_shmti_ctx_put(vma->vm_private_data);
+}
+
+static const struct vm_operations_struct scmi_tlm_shmti_vm_ops = {
+ .open = scmi_tlm_shmti_vma_open,
+ .close = scmi_tlm_shmti_vma_close,
+};
+
+static int scmi_tlm_shmti_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ struct scmi_tlm_shmti_ctx *ctx = filp->private_data;
+ unsigned long req = vma->vm_end - vma->vm_start;
+ phys_addr_t base;
+ size_t map_sz;
+ int ret;
+
+ if (vma->vm_flags & VM_WRITE)
+ return -EPERM;
+
+ if (req < ctx->shmti->len)
+ return -EINVAL;
+
+ /* Align */
+ base = ctx->shmti->phys & PAGE_MASK;
+ map_sz = PAGE_ALIGN(ctx->shmti->offset + req);
+
+ vma->vm_private_data = scmi_tlm_shmti_ctx_get(ctx);
+ vma->vm_ops = &scmi_tlm_shmti_vm_ops;
+ /* Set appropriate caching attributes */
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+
+ /* Map physical address range into user space */
+ ret = vm_iomap_memory(vma, base, map_sz);
+ if (ret) {
+ scmi_tlm_shmti_ctx_put(ctx);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int scmi_tlm_shmti_release(struct inode *ino, struct file *filp)
+{
+ scmi_tlm_shmti_ctx_put(filp->private_data);
+
+ return 0;
+}
+
+static const struct file_operations scmi_tlm_shmti_fops = {
+ .owner = THIS_MODULE,
+ .mmap = scmi_tlm_shmti_mmap,
+ .release = scmi_tlm_shmti_release,
+};
+
+static int
+scmi_tlm_allocate_anon_fds(const struct scmi_telemetry_info *in,
+ struct scmi_tlm_shmtis_list *out,
+ struct scmi_tlm_shmti_info *shinfo)
+{
+ unsigned int num_shmtis;
+
+ if (out->num_shmtis < in->base.num_shmtis)
+ return -EINVAL;
+
+ /* Count effectively returned ... */
+ num_shmtis = out->num_shmtis;
+ out->num_shmtis = 0;
+
+ for (int i = 0; i < num_shmtis; i++) {
+ struct scmi_telemetry_shmti_info *shmti = in->shmtis[i];
+ struct scmi_tlm_shmti_ctx *ctx;
+ int fd;
+
+ ctx = scmi_tlm_shmti_ctx_alloc(shmti);
+ if (IS_ERR(ctx)) {
+ pr_err("ERR CTX_ALLOC on SID: %u\n", shmti->sid);
+ continue;
+ }
+
+ fd = anon_inode_getfd(SCMI_TLM_DRIVER, &scmi_tlm_shmti_fops, ctx,
+ O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ scmi_tlm_shmti_ctx_put(ctx);
+ pr_err("ERR ANON INODE on SID: %u\n", shmti->sid);
+ continue;
+ }
+
+ shinfo[i].sid = shmti->sid;
+ shinfo[i].fd = fd;
+ shinfo[i].len = shmti->len;
+ shinfo[i].offset = shmti->offset;
+
+ out->num_shmtis++;
+ }
+
+ return 0;
+}
+
+static long
+scmi_tlm_shmtis_list_get_ioctl(const struct scmi_tlm_instance *ti,
+ unsigned long arg)
+{
+ void __user *uptr = (void __user *)arg;
+ struct scmi_tlm_shmtis_list ssl;
+ size_t shinfo_sz;
+ int ret;
+
+ if (copy_from_user(&ssl, uptr, sizeof(ssl)))
+ return -EFAULT;
+
+ if (ssl.pad)
+ return -EINVAL;
+
+ /* A local scratch buffer... */
+ shinfo_sz = ssl.num_shmtis * sizeof(struct scmi_tlm_shmti_info);
+ struct scmi_tlm_shmti_info *shinfo __free(kfree) =
+ kzalloc(shinfo_sz, GFP_KERNEL);
+ if (!shinfo)
+ return -ENOMEM;
+
+ if (copy_from_user(shinfo, u64_to_user_ptr(ssl.shmtis), shinfo_sz))
+ return -EFAULT;
+
+ ret = scmi_tlm_allocate_anon_fds(ti->info, &ssl, shinfo);
+ if (ret)
+ return ret;
+
+ if (copy_to_user(u64_to_user_ptr(ssl.shmtis), shinfo, shinfo_sz))
+ return -EFAULT;
+
+ if (copy_to_user(uptr, &ssl, sizeof(ssl)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long scmi_tlm_unlocked_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct inode *ino = file_inode(filp);
+ struct scmi_tlm_instance *ti = to_instance(ino->i_cdev);
+
+ switch (cmd) {
+ case SCMI_TLM_GET_INFO:
+ return scmi_tlm_info_get_ioctl(ti, arg);
+ case SCMI_TLM_GET_CFG:
+ return scmi_tlm_config_get_ioctl(ti, arg);
+ case SCMI_TLM_SET_CFG:
+ return scmi_tlm_config_set_ioctl(ti, arg);
+ case SCMI_TLM_GET_INTRVS:
+ return scmi_tlm_intervals_get_ioctl(ti, arg);
+ case SCMI_TLM_GET_DE_CFG:
+ return scmi_tlm_de_config_get_ioctl(ti, arg, false);
+ case SCMI_TLM_SET_DE_CFG:
+ return scmi_tlm_de_config_set_ioctl(ti, arg, false);
+ case SCMI_TLM_GET_DE_INFO:
+ return scmi_tlm_de_info_get_ioctl(ti, arg);
+ case SCMI_TLM_GET_DE_LIST:
+ return scmi_tlm_des_list_get_ioctl(ti, arg);
+ case SCMI_TLM_GET_DE_VALUE:
+ return scmi_tlm_de_value_get_ioctl(ti, arg);
+ case SCMI_TLM_GET_ALL_CFG:
+ return scmi_tlm_de_config_get_ioctl(ti, arg, true);
+ case SCMI_TLM_SET_ALL_CFG:
+ return scmi_tlm_de_config_set_ioctl(ti, arg, true);
+ case SCMI_TLM_GET_GRP_LIST:
+ return scmi_tlm_grps_list_get_ioctl(ti, arg);
+ case SCMI_TLM_GET_GRP_INFO:
+ return scmi_tlm_grp_info_get_ioctl(ti, arg);
+ case SCMI_TLM_GET_GRP_DESC:
+ return scmi_tlm_grp_desc_get_ioctl(ti, arg);
+ case SCMI_TLM_SINGLE_SAMPLE:
+ return scmi_tlm_des_read_ioctl(ti, arg, true);
+ case SCMI_TLM_BULK_READ:
+ return scmi_tlm_des_read_ioctl(ti, arg, false);
+ case SCMI_TLM_BATCH_READ:
+ return scmi_tlm_des_batch_read_ioctl(ti, arg);
+ case SCMI_TLM_GET_SHMTI_LIST:
+ return scmi_tlm_shmtis_list_get_ioctl(ti, arg);
+ default:
+ return -ENOTTY;
+ }
+}
+
+static const struct file_operations stlm_fops = {
+ .owner = THIS_MODULE,
+ .open = scmi_tlm_priv_data_open,
+ .read = scmi_tlm_priv_read,
+ .llseek = generic_file_llseek,
+ .unlocked_ioctl = scmi_tlm_unlocked_ioctl,
+ .release = scmi_tlm_priv_release,
+};
+
+static struct scmi_tlm_instance *scmi_tlm_init(struct scmi_tlm_setup *tsp,
+ unsigned int instance_id)
+{
+ struct device *dev = tsp->dev;
+ struct scmi_tlm_instance *ti;
+
+ ti = devm_kzalloc(dev, sizeof(*ti), GFP_KERNEL);
+ if (!ti)
+ return ERR_PTR(-ENOMEM);
+
+ ti->info = tsp->ops->info_get(tsp->ph);
+ if (!ti->info)
+ return dev_err_ptr_probe(dev,
+ -EINVAL, "invalid Telemetry info !\n");
+
+ ti->id = instance_id;
+ ti->tsp = tsp;
+
+ return ti;
+}
+
+static char *scmi_tlm_devnode(const struct device *dev, umode_t *mode)
+{
+ return kasprintf(GFP_KERNEL, "scmi/%s", dev_name(dev));
+}
+
+static struct class stlm_class = {
+ .name = "stlm",
+ .devnode = scmi_tlm_devnode,
+};
+
+static int scmi_telemetry_probe(struct scmi_device *sdev)
+{
+ const struct scmi_handle *handle = sdev->handle;
+ struct device *tdev, *dev = &sdev->dev;
+ struct scmi_protocol_handle *ph;
+ struct scmi_tlm_instance *ti;
+ struct scmi_tlm_setup *tsp;
+ unsigned int instance;
+ const void *ops;
+ int ret;
+
+ if (!handle)
+ return -ENODEV;
+
+ ops = handle->devm_protocol_get(sdev, sdev->protocol_id, &ph);
+ if (IS_ERR(ops))
+ return dev_err_probe(dev, PTR_ERR(ops),
+ "Cannot access protocol:0x%X\n",
+ sdev->protocol_id);
+
+ tsp = devm_kzalloc(dev, sizeof(*tsp), GFP_KERNEL);
+ if (!tsp)
+ return -ENOMEM;
+
+ tsp->dev = dev;
+ tsp->ops = ops;
+ tsp->ph = ph;
+
+ instance = handle->id;
+ if (instance >= SCMI_TLM_NUM_DEVS)
+ return -ENOSPC;
+
+ ti = scmi_tlm_init(tsp, instance);
+ if (IS_ERR(ti))
+ return PTR_ERR(ti);
+
+ cdev_init(&ti->cdev, &stlm_fops);
+ ti->cdev.owner = THIS_MODULE;
+ ti->devt = MKDEV(MAJOR(stlm_devt), MINOR(stlm_devt) + instance);
+ ret = cdev_add(&ti->cdev, ti->devt, 1);
+ if (ret)
+ return ret;
+
+ tdev = device_create(&stlm_class, NULL, ti->devt, ti, "tlm_%d", ti->id);
+ if (IS_ERR(tdev)) {
+ cdev_del(&ti->cdev);
+ return PTR_ERR(tdev);
+ }
+
+ dev_set_drvdata(&sdev->dev, ti);
+
+ return 0;
+}
+
+static void scmi_telemetry_remove(struct scmi_device *sdev)
+{
+ struct scmi_tlm_instance *ti;
+
+ ti = dev_get_drvdata(&sdev->dev);
+ if (!ti)
+ return;
+
+ device_destroy(&stlm_class, ti->devt);
+}
+
+static const struct scmi_device_id scmi_id_table[] = {
+ { SCMI_PROTOCOL_TELEMETRY, "telemetry" },
+ { }
+};
+MODULE_DEVICE_TABLE(scmi, scmi_id_table);
+
+static struct scmi_driver scmi_telemetry_driver = {
+ .name = SCMI_TLM_DRIVER,
+ .probe = scmi_telemetry_probe,
+ .remove = scmi_telemetry_remove,
+ .id_table = scmi_id_table,
+};
+
+static int __init scmi_telemetry_init(void)
+{
+ int ret;
+
+ ret = alloc_chrdev_region(&stlm_devt, 0, SCMI_TLM_NUM_DEVS,
+ SCMI_TLM_DRIVER);
+ if (ret)
+ return ret;
+
+ ret = class_register(&stlm_class);
+ if (ret)
+ return ret;
+
+ return scmi_register(&scmi_telemetry_driver);
+}
+module_init(scmi_telemetry_init);
+
+static void __exit scmi_telemetry_exit(void)
+{
+ scmi_unregister(&scmi_telemetry_driver);
+ unregister_chrdev_region(stlm_devt, SCMI_TLM_NUM_DEVS);
+ class_unregister(&stlm_class);
+}
+module_exit(scmi_telemetry_exit);
+
+MODULE_AUTHOR("Cristian Marussi <cristian.marussi at arm.com>");
+MODULE_DESCRIPTION("ARM SCMI Telemetry Driver");
+MODULE_LICENSE("GPL");
--
2.54.0
More information about the linux-arm-kernel
mailing list