[PATCH v9 21/24] firmware: arm_scmi: Add System Telemetry driver
Cristian Marussi <[email protected]>
| 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]> --- 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