[PATCH v7 20/23] firmware: arm_scmi: Add System Telemetry driver
Cristian Marussi <[email protected]> Sun, 2 Aug 2026 15:56:15 +0100
| Newsgroups | org.kernel.vger.arm-scmi,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-doc,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]> --- 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 | 1515 +++++++++++++++++ 3 files changed, 1526 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..75edbd137188 --- /dev/null +++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c @@ -0,0 +1,1515 @@ +// 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/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 + * @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: An embedded CDEV structure. + * @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. + * @events_xa: An XArray holding Telemetry events currently subscribed by this + * instance of the driver. + */ +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; +}; + +#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 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; + out->num_intervals = 0; + 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]); + + /* Count returned items */ + out->num_intervals++; + } +} + +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_list(struct scmi_tlm_des_list *out, + const struct scmi_telemetry_res_info *in, + struct scmi_tlm_de_info *des) +{ + out->num_des = 0; + for (int i = 0; i < in->num_des; i++) { + scmi_tlm_to_uapi_de_info(&des[i], &in->dei_store[i]); + + /* Count returned items */ + out->num_des++; + } +} + +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(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; + + out->num_grps = 0; + for (int i = 0; i < in->num_groups; i++) { + scmi_tlm_to_uapi_grp_info(&out_ginfo[i], &in->grps_store[i]); + + /* Count returned items */ + out->num_grps++; + } +} + +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_grp_info *ginfo = &in->grps_store[out->grp_id]; + struct scmi_telemetry_group *grp = &in->grps[out->grp_id]; + + out->num_des = 0; + for (int i = 0; i < ginfo->num_des; i++) { + composing_des[i] = in->des[grp->des[i]]->info->id; + + /* Count returned items */ + out->num_des++; + } +} + +/** + * 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 undersized buffers */ + if (ivs.num_intervals < tlm_ivs->num_intervals) + return -ENOSPC; + + /* 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 */ + 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 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; + + 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++) { + ret = ti->tsp->ops->state_set(ti->tsp->ph, false, + rinfo->des[i]->info->id, + &ena, &t_ena, NULL, NULL, NULL); + if (ret) + return ret; + } + + return 0; +} + +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) +{ + void __user *uptr = (void __user *)arg; + struct scmi_tlm_batch batch; + int *states = NULL; + + if (copy_from_user(&batch, uptr, sizeof(batch))) + return -EFAULT; + + if (batch.reserved) + return -EINVAL; + + if (batch.item_sz != sizeof(struct scmi_tlm_de_config)) + return -EINVAL; + + struct scmi_tlm_de_config *tcfg __free(kfree) = + kcalloc(batch.num_items, sizeof(*tcfg), GFP_KERNEL); + if (!tcfg) + return -ENOMEM; + + /* Read all the requested configs */ + if (copy_from_user(tcfg, u64_to_user_ptr(batch.items), + batch.num_items * batch.item_sz)) + return -EFAULT; + + /* Is per-read status required ? */ + if (batch.states) { + int *states_arr __free(kfree) = + kcalloc(batch.num_items, sizeof(*states_arr), + GFP_KERNEL); + if (!states_arr) + return -ENOMEM; + + if (copy_from_user(states_arr, u64_to_user_ptr(batch.states), + batch.num_items * sizeof(*states_arr))) + return -EFAULT; + + states = no_free_ptr(states_arr); + } + + 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); + } + + if (copy_to_user(u64_to_user_ptr(batch.items), tcfg, + batch.num_items * batch.item_sz)) + return -EFAULT; + + if (batch.states) { + if (copy_to_user(u64_to_user_ptr(batch.states), states, + batch.num_items * sizeof(*states))) + return -EFAULT; + + kfree(states); + } + + if (copy_to_user(uptr, &batch, sizeof(batch))) + return -EFAULT; + + return 0; +} + +static long +scmi_tlm_de_config_get_ioctl(const struct scmi_tlm_instance *ti, + unsigned long arg) +{ + void __user *uptr = (void __user *)arg; + struct scmi_tlm_batch batch; + int ret, *states = NULL; + + if (copy_from_user(&batch, uptr, sizeof(batch))) + return -EFAULT; + + if (batch.reserved) + return -EINVAL; + + if (batch.item_sz != sizeof(struct scmi_tlm_de_config)) + return -EINVAL; + + struct scmi_tlm_de_config *tcfg __free(kfree) = + kcalloc(batch.num_items, sizeof(*tcfg), GFP_KERNEL); + if (!tcfg) + return -ENOMEM; + + /* Read all the requested configs */ + if (copy_from_user(tcfg, u64_to_user_ptr(batch.items), + batch.num_items * batch.item_sz)) + return -EFAULT; + + /* Is per-read status required ? */ + if (batch.states) { + int *states_arr __free(kfree) = + kcalloc(batch.num_items, sizeof(*states_arr), + GFP_KERNEL); + if (!states_arr) + return -ENOMEM; + + if (copy_from_user(states_arr, u64_to_user_ptr(batch.states), + batch.num_items * sizeof(*states_arr))) + return -EFAULT; + + states = no_free_ptr(states_arr); + } + + for (int i = 0; i < batch.num_items; i++) { + unsigned int sid, offset; + 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); + } + + if (copy_to_user(u64_to_user_ptr(batch.items), tcfg, + batch.num_items * batch.item_sz)) + return -EFAULT; + + if (batch.states) { + if (copy_to_user(u64_to_user_ptr(batch.states), states, + batch.num_items * sizeof(*states))) + return -EFAULT; + + kfree(states); + } + + if (copy_to_user(uptr, &batch, sizeof(batch))) + 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.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) +{ + const struct scmi_telemetry_res_info *rinfo; + void __user *uptr = (void __user *)arg; + struct scmi_tlm_des_list dsl; + size_t dsl_des_sz; + + if (copy_from_user(&dsl, uptr, sizeof(dsl))) + return -EFAULT; + + if (dsl.pad) + return -EINVAL; + + rinfo = scmi_telemetry_res_info_get(ti->tsp); + /* Reject only undersized buffers */ + if (dsl.num_des < rinfo->num_des) + return -ENOSPC; + + /* 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 */ + scmi_tlm_to_uapi_des_list(&dsl, rinfo, dsl_des); + 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_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 undersized buffers */ + if (gdesc.num_des < rinfo->grps_store[gdesc.grp_id].num_des) + return -ENOSPC; + + /* 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 */ + 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) +{ + const struct scmi_telemetry_res_info *rinfo; + void __user *uptr = (void __user *)arg; + struct scmi_tlm_grps_list gsl; + size_t ginfo_sz; + + if (copy_from_user(&gsl, uptr, sizeof(gsl))) + return -EFAULT; + + if (gsl.pad) + return -EINVAL; + + rinfo = scmi_telemetry_res_info_get(ti->tsp); + /* Reject only undersized buffers */ + if (gsl.num_grps < rinfo->num_groups) + return -ENOSPC; + + /* 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; + + scmi_tlm_to_uapi_grps_list(&gsl, rinfo, ginfo); + 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_batch batch; + int *states = NULL; + + if (copy_from_user(&batch, uptr, sizeof(batch))) + return -EFAULT; + + if (batch.reserved) + return -EINVAL; + + if (batch.item_sz != sizeof(struct scmi_tlm_de_sample)) + return -EINVAL; + + struct scmi_telemetry_de_sample *samples __free(kfree) = + kcalloc(batch.num_items, sizeof(*samples), GFP_KERNEL); + if (!samples) + return -ENOMEM; + + /* Read all the requested samples */ + if (copy_from_user(samples, u64_to_user_ptr(batch.items), + batch.num_items * batch.item_sz)) + return -EFAULT; + + /* Is per-read status required ? */ + if (batch.states) { + int *states_arr __free(kfree) = + kcalloc(batch.num_items, sizeof(*states_arr), + GFP_KERNEL); + if (!states_arr) + return -ENOMEM; + + if (copy_from_user(states_arr, u64_to_user_ptr(batch.states), + batch.num_items * sizeof(*states_arr))) + return -EFAULT; + + states = no_free_ptr(states_arr); + } + + for (int i = 0; i < batch.num_items; i++) { + int ret; + + ret = tsp->ops->de_data_read(tsp->ph, &samples[i]); + if (ret) { + if (!states) + return ret; + + states[i] = ret; + continue; + } + } + + if (copy_to_user(u64_to_user_ptr(batch.items), samples, + batch.num_items * batch.item_sz)) + return -EFAULT; + + if (batch.states) { + if (copy_to_user(u64_to_user_ptr(batch.states), states, + batch.num_items * sizeof(*states))) + return -EFAULT; + + kfree(states); + } + + 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 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(const struct scmi_tlm_instance *ti, + struct scmi_tlm_shmtis_list *out, + struct scmi_tlm_shmti_info *shinfo) +{ + const struct scmi_telemetry_info *info = ti->info; + struct device *dev = ti->tsp->dev; + int idx = 0; + + out->num_shmtis = 0; + for (int i = 0; i < info->base.num_shmtis; i++) { + struct scmi_telemetry_shmti_info *shmti = info->shmtis[i]; + struct scmi_tlm_shmti_ctx *ctx; + int fd; + + ctx = scmi_tlm_shmti_ctx_alloc(shmti); + if (IS_ERR(ctx)) { + dev_err(dev, "Failed 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); + dev_err(dev, "Failed ANON INODE on 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; + + /* Count returned items */ + out->num_shmtis++; + idx++; + } +} + +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; + + if (copy_from_user(&ssl, uptr, sizeof(ssl))) + return -EFAULT; + + if (ssl.pad) + return -EINVAL; + + /* Reject only undersized buffers */ + if (ssl.num_shmtis < ti->info->base.num_shmtis) + return -ENOSPC; + + /* 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; + + scmi_tlm_allocate_anon_fds(ti, &ssl, shinfo); + 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_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) +{ + void __user *uptr = (void __user *)arg; + struct scmi_tlm_uuid_list udl; + size_t uuids_sz; + + if (copy_from_user(&udl, uptr, sizeof(udl))) + return -EFAULT; + + if (udl.pad) + return -EINVAL; + + if (!udl.num_uuids) { + /* Return the effective number of populated UUIDs */ + udl.num_uuids = ti->info->num_uuids; + + if (copy_to_user(uptr, &udl, sizeof(udl))) + return -EFAULT; + + return 0; + } + + /* Check that the requested size is acceptable */ + if (udl.num_uuids > ti->info->num_uuids) + return -ENOSPC; + + /* A local scratch buffer... */ + uuids_sz = udl.num_uuids * sizeof(struct scmi_tlm_uuid); + struct scmi_tlm_uuid *uuids __free(kfree) = kzalloc(uuids_sz, GFP_KERNEL); + if (!uuids) + return -ENOMEM; + + if (copy_from_user(uuids, u64_to_user_ptr(udl.uuids), uuids_sz)) + return -EFAULT; + + for (int j = 0; j < udl.num_uuids; j++) + export_uuid(uuids[j].bytes, ti->info->uuids[j]); + + if (copy_to_user(u64_to_user_ptr(udl.uuids), uuids, uuids_sz)) + return -EFAULT; + + if (copy_to_user(uptr, &udl, sizeof(udl))) + return -EFAULT; + + return 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); + + 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; + + 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; + + 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); +} + +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 only undersized buffers */ + 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 inode *ino = file_inode(filp); + struct scmi_tlm_instance *ti = to_instance(ino->i_cdev); + bool writable = filp->f_mode & FMODE_WRITE; + + 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 const struct file_operations stlm_fops = { + .owner = THIS_MODULE, + .open = nonseekable_open, + .unlocked_ioctl = scmi_tlm_unlocked_ioctl, + .compat_ioctl = compat_ptr_ioctl, +}; + +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; + xa_init_flags(&ti->events_xa, XA_FLAGS_ALLOC); + + 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; + struct eventfd_ctx *ctx; + unsigned long cookie = SCMI_TLM_EVT_XA_MIN; + + ti = dev_get_drvdata(&sdev->dev); + if (!ti) + return; + + /* PUT still unregistered CTX */ + 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); + + device_destroy(&stlm_class, ti->devt); + cdev_del(&ti->cdev); +} + +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