[PATCH v9 21/24] firmware: arm_scmi: Add System Telemetry driver

Cristian Marussi <[email protected]>
Newsgroups org.kernel.vger.linux-doc,org.infradead.lists.linux-arm-kernel,org.kernel.vger.arm-scmi,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
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 <[email protected]>
---
v8 --> v9
 - Fix some Sashiko reports on v8
  - use kvmalloc/kvcalloc for all buffers with user/FW generated size
  - add a proper upper bounds checks on IOCTL requests
  - make SET_ALL_CFG carry on after a failure (reporting last error anyway)
  - properly initialize some possibly unused stack vars
  - refactor SHMTIs list processing and ANON fds allocation to avoid leaking
    fds by premature installing them into process.
  - completely rework cdev and scmi_tlm_instance interactions and lifecycle
    and drop all devres allocation: reject fops on cdev after driver unbind
    and delay data structures freeing till the last reference has been
    dropped.
  - reworked BATCH helpers to handle ZERO items requests (for IOCTL query)
  - reworked all the _LIST IOCTLs to use scmi_tlm_batch as a container
    structure instead of custom structs
  - add support for 0-items query pattern to all _LIST IOCTLs
v7 --> v8
 - Fixes for some Sashiko issues on v7
  - refactor batch handling into common batch init/fini routines
  - use array_size to check for overflows
  - explain cleanup logic of eventfd_ctx (xa_for_each)
  - add a mutex to protect events_xa
  - introduce per-IOCTL check on batch upper bounds
v6 --> v7
 - fixed module_init errors paths
 - introduced a check on FMODE to filter-out unprivleged writers
 - added  UUID list IOCTL
 - added UUID/SID/OFFS tp GET/SET config IOCTLs
 - added EVENT subscribe IOCTL
 - modified BATCH READ with per-DE status
 - added BATCHed mode for DE_CFG_SET/GET
 - fix setting properly discrete interval flag
 - new ABI feats flags\
 - new SCMI feats flags
 - new reserved checks
v5 --> v6
 - drop hunam readable .read capabilities on chardev
 - fill-up the new UAPI de_implementation version u8 array
 - added compat_ioctl support
 - fixed initialization of flags in scmi_tlm_to_uapi_base_info
 - fix grp_id bounds check in scmi_tlm_grp_info_get_ioctl
 - fix booleans
 - fix SHMTI get list to loop on input params
 - reworked bounds checks into:
   - scmi_tlm_intervals_get_ioctl
   - scmi_tlm_des_list_get_ioctl
   - scmi_tlm_grp_list_get_ioctl
   - scmi_tlm_grp_desc_get_ioctl
 - renamed some READ ioctl to be consistent
 - reject oversized mmap requests
 - support new GET_ABI_INFO IOCTL
 - support RESET IOCTL
 - add a missing READ_ONCE() to rinfo get
 - avoid gaps in the anon fds returned array needed by shmti mmap
 - adding missing cdev_del on remove
---
 drivers/firmware/arm_scmi/Kconfig             |   10 +
 drivers/firmware/arm_scmi/Makefile            |    1 +
 .../firmware/arm_scmi/scmi_system_telemetry.c | 1567 +++++++++++++++++
 3 files changed, 1578 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 f506c4d1d96a..e7d8dd9fa907 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 be 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..0409ac3f2bed
--- /dev/null
+++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c
@@ -0,0 +1,1567 @@
+// 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/file.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/uuid.h>
+#include <linux/wait.h>
+#include <linux/xarray.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_BUILD_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  - Telemetry setup descriptor
+ * @sdev: A reference to the related SCMI device
+ * @ph: A reference to the protocol handle to be used with the ops
+ * @ops: A reference to the protocol ops
+ * @rinfo: A reference to the resource info descriptor
+ */
+struct scmi_tlm_setup {
+	struct scmi_device *sdev;
+	struct scmi_protocol_handle *ph;
+	const struct scmi_telemetry_proto_ops *ops;
+	const struct scmi_telemetry_res_info *rinfo;
+};
+
+/**
+ * struct scmi_tlm_instance  - Telemetry instance descriptor
+ * @id: Progressive number identifying this probed instance.
+ * @devt: The dev_t descriptor attached to this instance.
+ * @cdev: An embedded CDEV structure.
+ * @node: A node to link this in the list of all instances.
+ * @tsp: A reference to the telemetry instance setup
+ * @info: A reference to this instance SCMI Telemetry info data.
+ * @events_xa: An XArray holding Telemetry events currently subscribed by this
+ *	       instance of the driver.
+ * @events_mtx: A mutex to protect events_xa
+ * @dead_mtx: A mutex to protect @dead
+ * @dead: A boolean to indicate when the device has been unboud
+ * @ref: A kref to track this structure lifecycle
+ */
+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 SCMI_TLM_EVT_XA_MIN	1
+#define scmi_evt_xa_limit	XA_LIMIT(SCMI_TLM_EVT_XA_MIN, UINT_MAX)
+	struct xarray events_xa;
+	/* Protect events_xa */
+	struct mutex events_mtx;
+	/* Protect dead */
+	struct mutex dead_mtx;
+	bool dead;
+	struct kref ref;
+};
+
+#define cdev_to_instance(c)	container_of(c, struct scmi_tlm_instance, cdev)
+#define kref_to_instance(r)	container_of(r, struct scmi_tlm_instance, ref)
+
+struct scmi_tlm_shmti_ctx {
+	struct scmi_tlm_instance *ti;
+	struct scmi_telemetry_shmti_info *shmti;
+	struct kref ref;
+};
+
+#define to_shmti_ctx(c)	container_of(c, struct scmi_tlm_shmti_ctx, ref)
+
+static void scmi_tlm_instance_release(struct kref *ref);
+
+static inline void scmi_tlm_to_uapi_abi_info(struct scmi_tlm_abi_info *out,
+					     const struct scmi_telemetry_info *in)
+{
+	out->size = sizeof(*out);
+	out->abi_version = SCMI_TLM_CURRENT_ABI_VERSION;
+	out->abi_features = SCMI_TLM_ABI_FEAT_RESET | SCMI_TLM_ABI_FEAT_EVENT |
+		SCMI_TLM_ABI_FEAT_UUID_LIST | SCMI_TLM_ABI_FEAT_BATCH_STATE |
+		SCMI_TLM_ABI_FEAT_BATCHED_CFG | SCMI_TLM_ABI_FEAT_DE_TRACKING;
+	export_uuid(out->primary_de_impl_version, &in->base.primary_revision);
+	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->features = in->reset_support ? SCMI_TLM_SCMI_SUPPORT_RESET : 0;
+	out->features |= in->single_read_support ?
+		SCMI_TLM_SCMI_SUPPORT_SINGLE_SAMPLE : 0;
+	out->features |= in->per_group_config_support ?
+		SCMI_TLM_SCMI_SUPPORT_GROUP_CONFIG : 0;
+	out->features |= in->continuos_update_support ?
+		SCMI_TLM_SCMI_SUPPORT_UPDATE_NOTIFICATION : 0;
+}
+
+static inline void
+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;
+
+	out->flags |= in->discrete ? SCMI_TLM_INTERV_DISCRETE : 0;
+	for (int i = 0; i < out->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]);
+	}
+}
+
+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 void
+scmi_tlm_to_uapi_des_items(unsigned int num_des,
+			   const struct scmi_telemetry_res_info *in,
+			   struct scmi_tlm_de_info *des)
+{
+	for (int i = 0; i < num_des; i++)
+		scmi_tlm_to_uapi_de_info(&des[i], &in->dei_store[i]);
+}
+
+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 void
+scmi_tlm_to_uapi_grps_list(unsigned int num_groups,
+			   const struct scmi_telemetry_res_info *in,
+			   struct scmi_tlm_grp_info *ginfo)
+{
+	struct scmi_tlm_grp_info *out_ginfo = ginfo;
+
+	for (int i = 0; i < num_groups; i++)
+		scmi_tlm_to_uapi_grp_info(&out_ginfo[i], &in->grps_store[i]);
+}
+
+static inline void
+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_group *grp = &in->grps[out->grp_id];
+
+	for (int i = 0; i < out->num_des; i++)
+		composing_des[i] = in->des[grp->des[i]]->info->id;
+}
+
+/**
+ * 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 (READ_ONCE(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_abi_info_get_ioctl(const struct scmi_tlm_instance *ti, unsigned long arg)
+{
+	struct scmi_tlm_abi_info base = {};
+	void __user *uptr = (void __user *)arg;
+	bool ignored_trailing;
+	u32 usize;
+	int err;
+
+	if (get_user(usize, (u32 __user *)arg))
+		return -EFAULT;
+
+	if (usize < offsetofend(struct scmi_tlm_abi_info, reserved))
+		return -EINVAL;
+
+	if (copy_struct_from_user(&base, sizeof(base), uptr, usize))
+		return -EFAULT;
+
+	if (base.reserved)
+		return -EINVAL;
+
+	scmi_tlm_to_uapi_abi_info(&base, ti->info);
+	err = copy_struct_to_user(uptr, usize, &base, sizeof(base),
+				  &ignored_trailing);
+	if (err)
+		return err;
+
+	if (ignored_trailing)
+		return -EMSGSIZE;
+
+	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.reserved || 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;
+	bool ena, t_ena;
+
+	if (copy_from_user(&cfg, uptr, sizeof(cfg)))
+		return -EFAULT;
+
+	if (cfg.pad || cfg.reserved || cfg.flags & ~SCMI_TLM_CONFIG_FLAGS)
+		return -EINVAL;
+
+	ena = !!cfg.enable;
+	t_ena = !!cfg.t_enable;
+	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, &ena, &t_ena,
+					  NULL, NULL, NULL);
+		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, &ena, &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;
+
+	if (copy_from_user(&ivs, uptr, sizeof(ivs)))
+		return -EFAULT;
+
+	if (ivs.pad[0] || ivs.pad[1] || ivs.pad[2] || ivs.pad2 ||
+	    ivs.reserved || 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;
+	}
+
+	/* Reject only oversized output buffers */
+	if (ivs.num_intervals > tlm_ivs->num_intervals)
+		return -ENOSPC;
+
+	ivs_intrv_sz = array_size(ivs.num_intervals,
+				  sizeof(struct scmi_tlm_update_interval));
+	if (ivs_intrv_sz == SIZE_MAX)
+		return -EOVERFLOW;
+
+	/* A local scratch buffer... */
+	struct scmi_tlm_update_interval *ivs_intrv __free(kvfree) =
+		kvzalloc(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 */
+	scmi_tlm_to_uapi_intervals(&ivs, tlm_ivs, ivs_intrv);
+	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 int
+scmi_tlm_batch_initialize(void __user *uptr, struct scmi_tlm_batch *batch,
+			  size_t item_sz, unsigned int max_items,
+			  void **out_items, size_t *out_batch_sz,
+			  int **out_states, size_t *out_states_sz)
+{
+	size_t batch_sz;
+
+	if (copy_from_user(batch, uptr, sizeof(*batch)))
+		return -EFAULT;
+
+	if (batch->reserved || batch->item_sz != item_sz ||
+	    batch->num_items > max_items)
+		return -EINVAL;
+
+	/* Sanity check on user invocation */
+	if (!out_items || !out_batch_sz)
+		return -EINVAL;
+
+	/* Was a size query ? */
+	if (batch->num_items == 0) {
+		*out_items = 0;
+		*out_batch_sz = 0;
+		return 0;
+	}
+
+	batch_sz = array_size(batch->num_items, batch->item_sz);
+	if (batch_sz == SIZE_MAX)
+		return -EOVERFLOW;
+
+	void *items __free(kvfree) = kvzalloc(batch_sz, GFP_KERNEL);
+	if (!items)
+		return -ENOMEM;
+
+	/* Read all the requested configs */
+	if (copy_from_user(items, u64_to_user_ptr(batch->items), batch_sz))
+		return -EFAULT;
+
+	/* Is per-read status required ? */
+	if (batch->states) {
+		size_t states_sz;
+
+		states_sz = array_size(batch->num_items, sizeof(int));
+		if (states_sz == SIZE_MAX)
+			return -EOVERFLOW;
+
+		int *states_arr __free(kvfree) = kvzalloc(states_sz, GFP_KERNEL);
+		if (!states_arr)
+			return -ENOMEM;
+
+		if (copy_from_user(states_arr, u64_to_user_ptr(batch->states),
+				   states_sz))
+			return -EFAULT;
+
+		if (out_states)
+			*out_states = no_free_ptr(states_arr);
+		if (out_states_sz)
+			*out_states_sz = states_sz;
+	} else {
+		if (out_states)
+			*out_states = NULL;
+		if (out_states_sz)
+			*out_states_sz = 0;
+	}
+
+	*out_items = no_free_ptr(items);
+	*out_batch_sz = batch_sz;
+
+	return 0;
+}
+
+static int
+scmi_tlm_batch_finalize(void __user *uptr, struct scmi_tlm_batch *batch,
+			void *items, int *states, size_t batch_sz,
+			size_t states_sz)
+{
+	if (batch_sz) {
+		if (copy_to_user(u64_to_user_ptr(batch->items), items, batch_sz))
+			return -EFAULT;
+
+		if (batch->states) {
+			if (copy_to_user(u64_to_user_ptr(batch->states),
+					 states, states_sz))
+				return -EFAULT;
+		}
+	}
+
+	if (copy_to_user(uptr, batch, sizeof(*batch)))
+		return -EFAULT;
+
+	return 0;
+}
+
+static long
+scmi_tlm_all_de_config_set_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_de_config tcfg = {};
+	bool ena, t_ena;
+	int ret = 0;
+
+	if (copy_from_user(&tcfg, uptr, sizeof(tcfg)))
+		return -EFAULT;
+
+	if (tcfg.reserved)
+		return -EINVAL;
+
+	ena = !!tcfg.enable;
+	t_ena = !!tcfg.t_enable;
+	rinfo = scmi_telemetry_res_info_get(ti->tsp);
+	for (int i = 0; i < rinfo->num_des; i++) {
+		int err;
+
+		err = ti->tsp->ops->state_set(ti->tsp->ph, false,
+					      rinfo->des[i]->info->id,
+					      &ena, &t_ena, NULL, NULL, NULL);
+		if (err) {
+			/* Carry on BUT report last error */
+			ret = err;
+			continue;
+		}
+	}
+
+	return ret;
+}
+
+static long
+scmi_tlm_all_de_config_get_ioctl(const struct scmi_tlm_instance *ti,
+				 unsigned long arg)
+{
+	void __user *uptr = (void __user *)arg;
+	struct scmi_tlm_de_config tcfg = {};
+	bool ena, t_ena;
+	int ret;
+
+	if (copy_from_user(&tcfg, uptr, sizeof(tcfg)))
+		return -EFAULT;
+
+	if (tcfg.reserved)
+		return -EINVAL;
+
+	ena = !!tcfg.enable;
+	t_ena = !!tcfg.t_enable;
+	ret = ti->tsp->ops->state_get(ti->tsp->ph, NULL, &ena, &t_ena,
+				      NULL, NULL, NULL);
+	if (ret)
+		return ret;
+
+	tcfg.enable = !!ena;
+	tcfg.t_enable = !!t_ena;
+	if (copy_to_user(uptr, &tcfg, sizeof(tcfg)))
+		return -EFAULT;
+
+	return 0;
+}
+
+static long
+scmi_tlm_de_config_set_ioctl(const struct scmi_tlm_instance *ti,
+			     unsigned long arg)
+{
+	struct scmi_tlm_de_config *tcfg __free(kvfree) = NULL;
+	int ret, *states __free(kvfree) = NULL;
+	const struct scmi_telemetry_res_info *rinfo;
+	void __user *uptr = (void __user *)arg;
+	struct scmi_tlm_batch batch = {};
+	size_t batch_sz, states_sz;
+
+	rinfo = scmi_telemetry_res_info_get(ti->tsp);
+	ret = scmi_tlm_batch_initialize(uptr, &batch, sizeof(*tcfg),
+					rinfo->num_des, (void **)&tcfg, &batch_sz,
+					&states, &states_sz);
+	if (ret)
+		return ret;
+
+	for (int i = 0; i < batch.num_items; i++) {
+		unsigned int sid, offset;
+		bool ena, t_ena;
+		uuid_t uuid;
+		int ret;
+
+		ena = !!tcfg[i].enable;
+		t_ena = !!tcfg[i].t_enable;
+
+		ret = ti->tsp->ops->state_set(ti->tsp->ph, false,
+					      tcfg[i].id, &ena, &t_ena,
+					      &sid, &offset, &uuid);
+		if (ret) {
+			if (!states)
+				return ret;
+
+			states[i] = ret;
+			continue;
+		}
+
+		tcfg[i].sid = sid;
+		tcfg[i].offset = offset;
+		export_uuid(tcfg[i].uuid, &uuid);
+	}
+
+	return scmi_tlm_batch_finalize(uptr, &batch, tcfg, states, batch_sz,
+				       states_sz);
+}
+
+static long
+scmi_tlm_de_config_get_ioctl(const struct scmi_tlm_instance *ti,
+			     unsigned long arg)
+{
+	struct scmi_tlm_de_config *tcfg __free(kvfree) = NULL;
+	int ret, *states __free(kvfree) = NULL;
+	const struct scmi_telemetry_res_info *rinfo;
+	void __user *uptr = (void __user *)arg;
+	struct scmi_tlm_batch batch = {};
+	size_t batch_sz, states_sz;
+
+	rinfo = scmi_telemetry_res_info_get(ti->tsp);
+	ret = scmi_tlm_batch_initialize(uptr, &batch, sizeof(*tcfg),
+					rinfo->num_des, (void **)&tcfg,
+					&batch_sz, &states, &states_sz);
+	if (ret)
+		return ret;
+
+	for (int i = 0; i < batch.num_items; i++) {
+		unsigned int sid = SCMI_TLM_SHMTI_ID_INVALID, offset = 0;
+		bool ena, t_ena;
+		uuid_t uuid = {};
+
+		ena = !!tcfg[i].enable;
+		t_ena = !!tcfg[i].t_enable;
+		ret = ti->tsp->ops->state_get(ti->tsp->ph, &tcfg[i].id,
+					      &ena, &t_ena, &sid, &offset, &uuid);
+
+		if (ret) {
+			if (!states)
+				return ret;
+
+			states[i] = ret;
+			continue;
+		}
+
+		tcfg[i].enable = ena;
+		tcfg[i].t_enable = t_ena;
+		tcfg[i].sid = sid;
+		tcfg[i].offset = offset;
+		export_uuid(tcfg[i].uuid, &uuid);
+	}
+
+	return scmi_tlm_batch_finalize(uptr, &batch, tcfg, states, batch_sz,
+				       states_sz);
+}
+
+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.flags || dei.reserved)
+		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)
+{
+	struct scmi_tlm_de_info *dei __free(kvfree) = NULL;
+	const struct scmi_telemetry_res_info *rinfo;
+	void __user *uptr = (void __user *)arg;
+	struct scmi_tlm_batch batch = {};
+	size_t batch_sz;
+	int ret;
+
+	rinfo = scmi_telemetry_res_info_get(ti->tsp);
+	ret = scmi_tlm_batch_initialize(uptr, &batch, sizeof(*dei),
+					rinfo->num_des, (void **)&dei, &batch_sz,
+					NULL, NULL);
+	if (ret)
+		return ret;
+
+	/* Passing the array as a param avoids dancing with uptr->intervals */
+	if (batch.num_items)
+		scmi_tlm_to_uapi_des_items(batch.num_items, rinfo, dei);
+	else
+		batch.num_items = rinfo->num_des;
+
+	return scmi_tlm_batch_finalize(uptr, &batch, dei, NULL, batch_sz, 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_BUILD_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 || ginfo.reserved)
+		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;
+
+	if (copy_from_user(&gdesc, uptr, sizeof(gdesc)))
+		return -EFAULT;
+
+	if (gdesc.reserved)
+		return -EINVAL;
+
+	rinfo = scmi_telemetry_res_info_get(ti->tsp);
+	if (gdesc.grp_id >= rinfo->num_groups)
+		return -EINVAL;
+
+	/* Reject only oversized output buffers */
+	if (gdesc.num_des > rinfo->grps_store[gdesc.grp_id].num_des)
+		return -ENOSPC;
+
+	composing_sz = array_size(gdesc.num_des, sizeof(u32));
+	if (composing_sz == SIZE_MAX)
+		return -EOVERFLOW;
+
+	/* A local scratch buffer... */
+	u32 *composing_des __free(kvfree) = kvzalloc(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 */
+	scmi_tlm_to_uapi_composing_des(&gdesc, rinfo, composing_des);
+	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)
+{
+	struct scmi_tlm_grp_info *ginfo __free(kvfree) = NULL;
+	const struct scmi_telemetry_res_info *rinfo;
+	void __user *uptr = (void __user *)arg;
+	struct scmi_tlm_batch batch = {};
+	size_t batch_sz;
+	int ret;
+
+	rinfo = scmi_telemetry_res_info_get(ti->tsp);
+	ret = scmi_tlm_batch_initialize(uptr, &batch, sizeof(*ginfo),
+					rinfo->num_groups, (void **)&ginfo,
+					&batch_sz, NULL, NULL);
+	if (ret)
+		return ret;
+
+	/* Passing the array as a param avoids dancing with uptr->intervals */
+	if (batch.num_items)
+		scmi_tlm_to_uapi_grps_list(batch.num_items, rinfo, ginfo);
+	else
+		batch.num_items = rinfo->num_groups;
+
+	return scmi_tlm_batch_finalize(uptr, &batch, ginfo, NULL, batch_sz, 0);
+}
+
+static long scmi_tlm_des_read_ioctl(const struct scmi_tlm_instance *ti,
+				    unsigned long arg, bool single)
+{
+	const struct scmi_telemetry_res_info *rinfo;
+	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;
+
+	/* Check that a reasonably sized buffer has been requested */
+	rinfo = scmi_telemetry_res_info_get(tsp);
+	if (bulk.num_samples > rinfo->num_des)
+		return -E2BIG;
+
+	if (!SCMI_TLM_READ_IS_GROUP(bulk.flags)) {
+		grp_id = SCMI_TLM_GRP_INVALID;
+	} else {
+		if (bulk.grp_id >= rinfo->num_groups)
+			return -EINVAL;
+
+		grp_id = bulk.grp_id;
+	}
+
+	struct scmi_telemetry_de_sample *samples __free(kvfree) =
+		kvcalloc(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)
+{
+	struct scmi_telemetry_de_sample *samples __free(kvfree) = NULL;
+	int ret, *states __free(kvfree) = NULL;
+	const struct scmi_telemetry_res_info *rinfo;
+	void __user *uptr = (void __user *)arg;
+	struct scmi_tlm_batch batch = {};
+	size_t batch_sz, states_sz;
+
+	rinfo = scmi_telemetry_res_info_get(ti->tsp);
+	ret = scmi_tlm_batch_initialize(uptr, &batch, sizeof(*samples),
+					rinfo->num_des, (void **)&samples,
+					&batch_sz, &states, &states_sz);
+	if (ret)
+		return ret;
+
+	for (int i = 0; i < batch.num_items; i++) {
+		int ret;
+
+		ret = ti->tsp->ops->de_data_read(ti->tsp->ph, &samples[i]);
+		if (ret) {
+			if (!states)
+				return ret;
+
+			states[i] = ret;
+			continue;
+		}
+	}
+
+	return scmi_tlm_batch_finalize(uptr, &batch, samples, states, batch_sz,
+				       states_sz);
+}
+
+static struct scmi_tlm_shmti_ctx *
+scmi_tlm_shmti_ctx_alloc(struct scmi_tlm_instance *ti,
+			 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->ref);
+
+	/* Get hold of the Telemetry instance when mmapping */
+	kref_get(&ti->ref);
+	ctx->ti = ti;
+
+	return ctx;
+}
+
+static void scmi_tlm_shmti_ctx_release(struct kref *kref)
+{
+	struct scmi_tlm_shmti_ctx *ctx = to_shmti_ctx(kref);
+
+	kref_put(&ctx->ti->ref, scmi_tlm_instance_release);
+
+	kfree(ctx);
+}
+
+static struct scmi_tlm_shmti_ctx *
+scmi_tlm_shmti_ctx_get(struct scmi_tlm_shmti_ctx *ctx)
+{
+	kref_get(&ctx->ref);
+
+	return ctx;
+}
+
+static void scmi_tlm_shmti_ctx_put(struct scmi_tlm_shmti_ctx *ctx)
+{
+	kref_put(&ctx->ref, 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 npages, expect, needed, 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 & reject oversized requests */
+	base = ctx->shmti->phys & PAGE_MASK;
+	needed = ctx->shmti->len + ctx->shmti->offset;
+	expect = DIV_ROUND_UP(needed, PAGE_SIZE);
+	npages = req >> PAGE_SHIFT;
+	if (npages > expect)
+		return -EINVAL;
+
+	map_sz = PAGE_ALIGN(needed);
+	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 void
+scmi_tlm_allocate_anon_fds(struct scmi_tlm_instance *ti, unsigned int *num_shmtis,
+			   struct scmi_tlm_shmti_info *shinfo,
+			   struct file **filps)
+{
+	const struct scmi_telemetry_info *info = ti->info;
+	struct device *dev = &ti->tsp->sdev->dev;
+	int idx = 0;
+
+	for (int i = 0; i < *num_shmtis; i++) {
+		struct scmi_telemetry_shmti_info *shmti = info->shmtis[i];
+		struct scmi_tlm_shmti_ctx *ctx;
+		struct file *filp;
+		int fd;
+
+		fd = get_unused_fd_flags(O_CLOEXEC);
+		if (fd < 0) {
+			dev_err(dev, "Failed GET_UNUSED_FD for SID: %u\n",
+				shmti->sid);
+			continue;
+		}
+
+		ctx = scmi_tlm_shmti_ctx_alloc(ti, shmti);
+		if (IS_ERR(ctx)) {
+			dev_err(dev, "Failed CTX_ALLOC for SID: %u\n", shmti->sid);
+			put_unused_fd(fd);
+			continue;
+		}
+
+		filp = anon_inode_getfile(SCMI_TLM_DRIVER, &scmi_tlm_shmti_fops,
+					  ctx, O_RDONLY | O_CLOEXEC);
+		if (IS_ERR(filp)) {
+			scmi_tlm_shmti_ctx_put(ctx);
+			put_unused_fd(fd);
+			dev_err(dev, "Failed ANON GET_FILE for SID: %u\n",
+				shmti->sid);
+			continue;
+		}
+
+		shinfo[idx].sid = shmti->sid;
+		shinfo[idx].fd = fd;
+		shinfo[idx].len = shmti->len;
+		shinfo[idx].offset = shmti->offset;
+
+		filps[idx++] = filp;
+	}
+
+	/* Return effective initialized items */
+	*num_shmtis = idx;
+}
+
+static inline void
+scmi_tlm_anon_fds_free(int num_shmtis, struct scmi_tlm_shmti_info *shinfo,
+		       struct file **filps)
+{
+	for (int i = 0; i < num_shmtis; i++) {
+		scmi_tlm_shmti_ctx_put(filps[i]->private_data);
+		put_unused_fd(shinfo[i].fd);
+		fput(filps[i]);
+	}
+}
+
+static inline void
+scmi_tlm_anon_fds_install(int num_shmtis, struct scmi_tlm_shmti_info *shinfo,
+			  struct file **filps)
+{
+	for (int i = 0; i < num_shmtis; i++)
+		fd_install(shinfo[i].fd, filps[i]);
+}
+
+static long
+scmi_tlm_shmtis_list_get_ioctl(struct scmi_tlm_instance *ti, unsigned long arg)
+{
+	struct scmi_tlm_shmti_info *shinfo __free(kvfree) = NULL;
+	unsigned int max_shmtis = ti->info->base.num_shmtis;
+	void __user *uptr = (void __user *)arg;
+	struct scmi_tlm_batch batch = {};
+	size_t batch_sz;
+	int ret;
+
+	ret = scmi_tlm_batch_initialize(uptr, &batch, sizeof(*shinfo), max_shmtis,
+					(void **)&shinfo, &batch_sz, NULL, NULL);
+	if (ret)
+		return ret;
+
+	if (batch.num_items == 0) {
+		batch.num_items = max_shmtis;
+
+		return scmi_tlm_batch_finalize(uptr, &batch, shinfo, NULL,
+					       batch_sz, 0);
+	}
+
+	struct file **filps __free(kfree) =
+		kcalloc(batch.num_items, sizeof(*filps), GFP_KERNEL);
+	if (!filps)
+		return -ENOMEM;
+
+	/* Passing the array as a param avoids dancing with uptr->items */
+	scmi_tlm_allocate_anon_fds(ti, &batch.num_items, shinfo, filps);
+	ret = scmi_tlm_batch_finalize(uptr, &batch, shinfo, NULL, batch_sz, 0);
+	if (ret) {
+		scmi_tlm_anon_fds_free(batch.num_items, shinfo, filps);
+		return ret;
+	}
+
+	scmi_tlm_anon_fds_install(batch.num_items, shinfo, filps);
+
+	return 0;
+}
+
+static long scmi_tlm_reset_ioctl(const struct scmi_tlm_instance *ti,
+				 unsigned long arg)
+{
+	return ti->tsp->ops->reset(ti->tsp->ph);
+}
+
+static long
+scmi_tlm_uuid_list_get_ioctl(const struct scmi_tlm_instance *ti,
+			     unsigned long arg)
+{
+	struct scmi_tlm_uuid *uuids __free(kvfree) = NULL;
+	void __user *uptr = (void __user *)arg;
+	struct scmi_tlm_batch batch = {};
+	size_t batch_sz;
+	int ret;
+
+	ret = scmi_tlm_batch_initialize(uptr, &batch, sizeof(*uuids),
+					ti->info->num_uuids, (void **)&uuids,
+					&batch_sz, NULL, NULL);
+	if (ret)
+		return ret;
+
+	if (batch.num_items) {
+		for (int j = 0; j < batch.num_items; j++)
+			export_uuid(uuids[j].bytes, ti->info->uuids[j]);
+	} else {
+		batch.num_items = ti->info->num_uuids;
+	}
+
+	return scmi_tlm_batch_finalize(uptr, &batch, uuids, NULL, batch_sz, 0);
+}
+
+static int scmi_tlm_event_subscribe(struct scmi_tlm_instance *ti,
+				    struct scmi_tlm_event *evt)
+{
+	struct eventfd_ctx *ctx;
+	u32 cookie;
+	int ret;
+
+	/* Registering */
+	ctx = eventfd_ctx_fdget(evt->efd);
+	if (IS_ERR(ctx))
+		return PTR_ERR(ctx);
+
+	guard(mutex)(&ti->events_mtx);
+	ret = xa_alloc(&ti->events_xa, &cookie, ctx, scmi_evt_xa_limit,
+		       GFP_KERNEL);
+	if (ret) {
+		eventfd_ctx_put(ctx);
+		return ret;
+	}
+
+	ret = ti->tsp->ops->event_subscribe(ti->tsp->ph, evt->type, ctx);
+	if (ret) {
+		xa_erase(&ti->events_xa, cookie);
+		eventfd_ctx_put(ctx);
+		return ret;
+	}
+
+	evt->cookie = cookie;
+
+	kref_get(&ti->ref);
+
+	return 0;
+}
+
+static void scmi_tlm_event_unsubscribe(struct scmi_tlm_instance *ti,
+				       struct scmi_tlm_event *evt)
+{
+	struct eventfd_ctx *ctx;
+
+	if (!evt->cookie)
+		return;
+
+	guard(mutex)(&ti->events_mtx);
+	ctx = xa_erase(&ti->events_xa, evt->cookie);
+	if (!ctx)
+		return;
+
+	ti->tsp->ops->event_unsubscribe(ti->tsp->ph, evt->type, ctx);
+
+	eventfd_ctx_put(ctx);
+
+	kref_put(&ti->ref, scmi_tlm_instance_release);
+}
+
+static long scmi_tlm_event_subscribe_ioctl(struct scmi_tlm_instance *ti,
+					   unsigned long arg)
+{
+	void __user *uptr = (void __user *)arg;
+	struct scmi_tlm_event evt = {};
+	int ret;
+
+	if (copy_from_user(&evt, uptr, sizeof(evt)))
+		return -EFAULT;
+
+	if (evt.pad || evt.reserved)
+		return -EINVAL;
+
+	/* Reject unknown event types */
+	if (evt.type > SCMI_TLM_EVT_LAST)
+		return -ENODEV;
+
+	if (!evt.cookie) {
+		/* Register new event, return new cookie */
+		ret = scmi_tlm_event_subscribe(ti, &evt);
+		if (ret)
+			return ret;
+
+		if (copy_to_user(uptr, &evt, sizeof(evt))) {
+			scmi_tlm_event_unsubscribe(ti, &evt);
+			return -EFAULT;
+		}
+	} else {
+		/* DE-Register if existent, put context */
+		scmi_tlm_event_unsubscribe(ti, &evt);
+	}
+
+	return 0;
+}
+
+static long scmi_tlm_unlocked_ioctl(struct file *filp, unsigned int cmd,
+				    unsigned long arg)
+{
+	struct scmi_tlm_instance *ti = filp->private_data;
+	bool writable = filp->f_mode & FMODE_WRITE;
+
+	guard(mutex)(&ti->dead_mtx);
+	if (ti->dead)
+		return -ENODEV;
+
+	switch (cmd) {
+	case SCMI_TLM_GET_ABI_INFO:
+		return scmi_tlm_abi_info_get_ioctl(ti, arg);
+	case SCMI_TLM_GET_CFG:
+		return scmi_tlm_config_get_ioctl(ti, arg);
+	case SCMI_TLM_SET_CFG:
+		if (writable)
+			return scmi_tlm_config_set_ioctl(ti, arg);
+		break;
+	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);
+	case SCMI_TLM_SET_DE_CFG:
+		if (writable)
+			return scmi_tlm_de_config_set_ioctl(ti, arg);
+		break;
+	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_DE_READ:
+		return scmi_tlm_de_value_get_ioctl(ti, arg);
+	case SCMI_TLM_GET_ALL_CFG:
+		return scmi_tlm_all_de_config_get_ioctl(ti, arg);
+	case SCMI_TLM_SET_ALL_CFG:
+		if (writable)
+			return scmi_tlm_all_de_config_set_ioctl(ti, arg);
+		break;
+	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_READ:
+		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);
+	case SCMI_TLM_RESET:
+		if (writable)
+			return scmi_tlm_reset_ioctl(ti, arg);
+		break;
+	case SCMI_TLM_GET_UUID_LIST:
+		return scmi_tlm_uuid_list_get_ioctl(ti, arg);
+	case SCMI_TLM_EVENT_SUBSCRIBE:
+		return scmi_tlm_event_subscribe_ioctl(ti, arg);
+	default:
+		return -ENOTTY;
+	}
+
+	return -EPERM;
+}
+
+static int scmi_tlm_open(struct inode *ino, struct file *filp)
+{
+	struct scmi_tlm_instance *ti = cdev_to_instance(ino->i_cdev);
+
+	guard(mutex)(&ti->dead_mtx);
+	if (ti->dead)
+		return -ENODEV;
+
+	kref_get(&ti->ref);
+
+	filp->private_data = ti;
+
+	return nonseekable_open(ino, filp);
+}
+
+static int scmi_tlm_release(struct inode *ino, struct file *filp)
+{
+	struct scmi_tlm_instance *ti = filp->private_data;
+
+	kref_put(&ti->ref, scmi_tlm_instance_release);
+
+	return 0;
+}
+
+static const struct file_operations stlm_fops = {
+	.owner = THIS_MODULE,
+	.open = scmi_tlm_open,
+	.release = scmi_tlm_release,
+	.unlocked_ioctl = scmi_tlm_unlocked_ioctl,
+	.compat_ioctl = compat_ptr_ioctl,
+};
+
+static void scmi_tlm_instance_release(struct kref *ref)
+{
+	struct scmi_tlm_instance *ti = kref_to_instance(ref);
+	unsigned long cookie = SCMI_TLM_EVT_XA_MIN;
+	struct scmi_device *sdev = ti->tsp->sdev;
+	struct eventfd_ctx *ctx;
+
+	dev_dbg(&sdev->dev, "Finalizing Telemetry instance %u\n", ti->id);
+
+	/*
+	 * Any STILL subscribed event is cleaned-up here on when the final
+	 * remove takes effective when the last kref is drop.
+	 * Doing it here instead than in the fops.release, a process could
+	 * subscribe for an event providing its eventfd and then close the
+	 * tlm device and just monitor the events.
+	 */
+	scoped_guard(mutex, &ti->events_mtx) {
+		xa_for_each(&ti->events_xa, cookie, ctx) {
+			xa_erase(&ti->events_xa, cookie);
+			ti->tsp->ops->event_unsubscribe(ti->tsp->ph,
+							SCMI_TLM_EVT_GENERATION,
+							ctx);
+			eventfd_ctx_put(ctx);
+		}
+	}
+
+	xa_destroy(&ti->events_xa);
+
+	sdev->handle->protocol_put(sdev->handle, sdev->protocol_id);
+
+	kfree(ti->tsp);
+	kfree(ti);
+}
+
+static struct scmi_tlm_instance *
+scmi_tlm_instance_init(struct scmi_device *sdev,
+		       const struct scmi_telemetry_proto_ops *ops,
+		       struct scmi_protocol_handle *ph, unsigned int instance_id)
+{
+	struct scmi_tlm_instance *ti __free(kfree) = kzalloc_obj(*ti);
+	if (!ti)
+		return ERR_PTR(-ENOMEM);
+
+	struct scmi_tlm_setup *tsp __free(kfree) = kzalloc_obj(*tsp);
+	if (!tsp)
+		return ERR_PTR(-ENOMEM);
+
+	ti->info = ops->info_get(ph);
+	if (!ti->info)
+		return dev_err_ptr_probe(&sdev->dev,
+					 -EINVAL, "invalid Telemetry info !\n");
+
+	tsp->sdev = sdev;
+	tsp->ops = ops;
+	tsp->ph = ph;
+	ti->tsp = no_free_ptr(tsp);
+
+	ti->id = instance_id;
+	xa_init_flags(&ti->events_xa, XA_FLAGS_ALLOC);
+	mutex_init(&ti->events_mtx);
+
+	mutex_init(&ti->dead_mtx);
+	kref_init(&ti->ref);
+
+	return no_free_ptr(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;
+	unsigned int instance;
+	const void *ops;
+	int ret;
+
+	if (!handle)
+		return -ENODEV;
+
+	instance = handle->id;
+	if (instance >= SCMI_TLM_NUM_DEVS)
+		return -ENOSPC;
+
+	ops = handle->protocol_get(handle, 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);
+
+	ti = scmi_tlm_instance_init(sdev, ops, ph, instance);
+	if (IS_ERR(ti)) {
+		ret = PTR_ERR(ti);
+		goto err_init;
+	}
+
+	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)
+		goto err_cdev;
+
+	tdev = device_create(&stlm_class, NULL, ti->devt, ti, "tlm_%d", ti->id);
+	if (IS_ERR(tdev)) {
+		ret = PTR_ERR(tdev);
+		goto err_tdev;
+	}
+
+	dev_set_drvdata(&sdev->dev, ti);
+
+	return 0;
+
+err_tdev:
+	cdev_del(&ti->cdev);
+err_cdev:
+	kfree(ti);
+err_init:
+	handle->protocol_put(handle, sdev->protocol_id);
+
+	return ret;
+}
+
+static void scmi_telemetry_remove(struct scmi_device *sdev)
+{
+	struct scmi_tlm_instance *ti;
+
+	ti = dev_get_drvdata(&sdev->dev);
+	if (!ti)
+		return;
+
+	scoped_guard(mutex, &ti->dead_mtx)
+		ti->dead = true;
+
+	device_destroy(&stlm_class, ti->devt);
+	cdev_del(&ti->cdev);
+
+	kref_put(&ti->ref, scmi_tlm_instance_release);
+}
+
+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)
+		goto err_class;
+
+	ret = scmi_register(&scmi_telemetry_driver);
+	if (ret)
+		goto err_scmi;
+
+	return 0;
+
+err_scmi:
+	class_unregister(&stlm_class);
+
+err_class:
+	unregister_chrdev_region(stlm_devt, SCMI_TLM_NUM_DEVS);
+
+	return ret;
+}
+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 <[email protected]>");
+MODULE_DESCRIPTION("ARM SCMI Telemetry Driver");
+MODULE_LICENSE("GPL");
-- 
2.54.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.