Extend the filesystem based interface with special 'control' files that can be used to configure and retrieve SCMI Telemetry data in binary form using the ioctls-based ABI described in uapi/linux/scmi.h. This alternative ABI is meant to provide a more performant access to SCMI Telemetry configuration and data events, without the hassle of navigating the human readable VFS based intwerface. Signed-off-by: Cristian Marussi --- v1 --> v2 - Use new res_get() operation which use new resource accessors - Use new de_lookup() tlm_ops - Using cleanup.h --- .../firmware/arm_scmi/scmi_system_telemetry.c | 404 ++++++++++++++++++ 1 file changed, 404 insertions(+) diff --git a/drivers/firmware/arm_scmi/scmi_system_telemetry.c b/drivers/firmware/arm_scmi/scmi_system_telemetry.c index b48f2d4eecae..721de615bec3 100644 --- a/drivers/firmware/arm_scmi/scmi_system_telemetry.c +++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c @@ -24,6 +24,8 @@ #include #include +#include + #define TLM_FS_MAGIC 0x75C01C80 #define TLM_FS_NAME "stlmfs" #define TLM_FS_MNT "arm_telemetry" @@ -1056,6 +1058,406 @@ DEFINE_TLM_CLASS(grp_available_interval_tlmo, "available_update_intervals_ms", DEFINE_TLM_CLASS(grp_intervals_discrete_tlmo, "intervals_discrete", TLM_IS_GROUP, S_IFREG | S_IRUSR, &intrv_discrete_fops, NULL); +static long +scmi_tlm_info_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned long arg) +{ + const struct scmi_telemetry_info *info = tlmi->priv; + void * __user uptr = (void * __user)arg; + + if (copy_to_user(uptr, &info->base, sizeof(info->base))) + return -EFAULT; + + return 0; +} + +static long +scmi_tlm_intervals_get_ioctl(const struct scmi_tlm_inode *tlmi, + unsigned long arg, bool is_group) +{ + struct scmi_tlm_intervals ivs, *tlm_ivs; + void * __user uptr = (void * __user)arg; + + if (copy_from_user(&ivs, uptr, sizeof(ivs))) + return -EFAULT; + + if (!is_group) { + const struct scmi_telemetry_info *info = tlmi->priv; + + tlm_ivs = info->intervals; + } else { + const struct scmi_telemetry_group *grp = tlmi->priv; + + tlm_ivs = grp->intervals; + } + + if (ivs.num != tlm_ivs->num) + return -EINVAL; + + if (copy_to_user(uptr, tlm_ivs, + sizeof(*tlm_ivs) + sizeof(u32) * ivs.num)) + return -EFAULT; + + return 0; +} + +static long +scmi_tlm_de_config_set_ioctl(const struct scmi_tlm_inode *tlmi, + unsigned long arg, bool all) +{ + const struct scmi_telemetry_res_info *rinfo; + void * __user uptr = (void * __user)arg; + struct scmi_tlm_setup *tsp = tlmi->tsp; + struct scmi_tlm_de_config tcfg = {}; + int ret; + + if (copy_from_user(&tcfg, uptr, sizeof(tcfg))) + return -EFAULT; + + if (!all) + return tsp->ops->state_set(tsp->ph, false, tcfg.id, + (bool *)&tcfg.enable, + (bool *)&tcfg.t_enable); + + rinfo = scmi_telemetry_res_info_get(tsp); + for (int i = 0; i < rinfo->num_des; i++) { + ret = tsp->ops->state_set(tsp->ph, false, + rinfo->des[i]->info->id, + (bool *)&tcfg.enable, + (bool *)&tcfg.t_enable); + if (ret) + return ret; + } + + return 0; +} + +static long +scmi_tlm_de_config_get_ioctl(const struct scmi_tlm_inode *tlmi, + unsigned long arg) +{ + struct scmi_tlm_setup *tsp = tlmi->tsp; + void * __user uptr = (void * __user)arg; + struct scmi_tlm_de_config tcfg = {}; + int ret; + + if (copy_from_user(&tcfg, uptr, sizeof(tcfg))) + return -EFAULT; + + ret = tsp->ops->state_get(tsp->ph, tcfg.id, + (bool *)&tcfg.enable, (bool *)&tcfg.t_enable); + if (ret) + return ret; + + if (copy_to_user(uptr, &tcfg, sizeof(tcfg))) + return -EFAULT; + + return 0; +} + +static long +scmi_tlm_config_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned long arg, + bool is_group) +{ + void * __user uptr = (void * __user)arg; + struct scmi_tlm_config cfg; + + if (!is_group) { + const struct scmi_telemetry_info *info = tlmi->priv; + + cfg.enable = !!info->enabled; + cfg.current_update_interval = info->active_update_interval; + } else { + const struct scmi_telemetry_group *grp = tlmi->priv; + + cfg.enable = !!grp->enabled; + cfg.t_enable = !!grp->tstamp_enabled; + cfg.current_update_interval = grp->active_update_interval; + } + + if (copy_to_user(uptr, &cfg, sizeof(cfg))) + return -EFAULT; + + return 0; +} + +static long +scmi_tlm_config_set_ioctl(const struct scmi_tlm_inode *tlmi, unsigned long arg, + bool is_group) +{ + struct scmi_tlm_setup *tsp = tlmi->tsp; + void * __user uptr = (void * __user)arg; + struct scmi_tlm_config cfg = {}; + bool grp_ignore; + int res_id; + + if (copy_from_user(&cfg, uptr, sizeof(cfg))) + return -EFAULT; + + if (!is_group) { + res_id = SCMI_TLM_GRP_INVALID; + grp_ignore = true; + } else { + const struct scmi_telemetry_group *grp = tlmi->priv; + int ret; + + res_id = grp->info->id; + grp_ignore = false; + ret = tsp->ops->state_set(tsp->ph, true, res_id, + (bool *)&cfg.enable, + (bool *)&cfg.t_enable); + if (ret) + return ret; + } + + return tsp->ops->collection_configure(tsp->ph, res_id, grp_ignore, + (bool *)&cfg.enable, + &cfg.current_update_interval, + NULL); +} + +static long +scmi_tlm_de_info_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned long arg) +{ + struct scmi_tlm_setup *tsp = tlmi->tsp; + void * __user uptr = (void * __user)arg; + const struct scmi_telemetry_de *de; + struct scmi_tlm_de_info dei; + + if (copy_from_user(&dei, uptr, sizeof(dei))) + return -EFAULT; + + de = tsp->ops->de_lookup(tsp->ph, dei.id); + if (!de) + return -EINVAL; + + if (copy_to_user(uptr, de->info, sizeof(*de->info))) + return -EFAULT; + + return 0; +} + +static long +scmi_tlm_des_list_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned long arg) +{ + struct scmi_tlm_setup *tsp = tlmi->tsp; + const struct scmi_telemetry_res_info *rinfo; + void * __user uptr = (void * __user)arg; + struct scmi_tlm_des_list dsl; + + rinfo = scmi_telemetry_res_info_get(tsp); + if (copy_from_user(&dsl, uptr, sizeof(dsl))) + return -EFAULT; + + if (dsl.num_des < rinfo->num_des) + return -EINVAL; + + if (copy_to_user(uptr, &rinfo->num_des, sizeof(rinfo->num_des))) + return -EFAULT; + + if (copy_to_user(uptr + sizeof(rinfo->num_des), rinfo->dei_store, + rinfo->num_des * sizeof(*rinfo->dei_store))) + return -EFAULT; + + return 0; +} + +static long +scmi_tlm_de_value_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned long arg) +{ + struct scmi_tlm_setup *tsp = tlmi->tsp; + void * __user uptr = (void * __user)arg; + struct scmi_tlm_de_sample sample; + int ret; + + if (copy_from_user(&sample, uptr, sizeof(sample))) + return -EFAULT; + + 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_inode *tlmi, unsigned long arg) +{ + const struct scmi_telemetry_group *grp = tlmi->priv; + void * __user uptr = (void * __user)arg; + + if (copy_to_user(uptr, grp->info, sizeof(*grp->info))) + return -EFAULT; + + return 0; +} + +static long +scmi_tlm_grp_desc_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned long arg) +{ + const struct scmi_telemetry_group *grp = tlmi->priv; + unsigned int num_des = grp->info->num_des; + void * __user uptr = (void * __user)arg; + struct scmi_tlm_grp_desc grp_desc; + + if (copy_from_user(&grp_desc, uptr, sizeof(grp_desc))) + return -EFAULT; + + if (grp_desc.num_des < num_des) + return -EINVAL; + + if (copy_to_user(uptr, &num_des, sizeof(num_des))) + return -EFAULT; + + if (copy_to_user(uptr + sizeof(num_des), grp->des, + sizeof(*grp->des) * num_des)) + return -EFAULT; + + return 0; +} + +static long +scmi_tlm_grps_list_get_ioctl(const struct scmi_tlm_inode *tlmi, unsigned long arg) +{ + struct scmi_tlm_setup *tsp = tlmi->tsp; + const struct scmi_telemetry_res_info *rinfo; + void * __user uptr = (void * __user)arg; + struct scmi_tlm_grps_list gsl; + + if (copy_from_user(&gsl, uptr, sizeof(gsl))) + return -EFAULT; + + rinfo = scmi_telemetry_res_info_get(tsp); + if (gsl.num_grps < rinfo->num_groups) + return -EINVAL; + + if (copy_to_user(uptr, &rinfo->num_groups, sizeof(rinfo->num_groups))) + return -EFAULT; + + if (copy_to_user(uptr + sizeof(rinfo->num_groups), rinfo->grps_store, + rinfo->num_groups * sizeof(*rinfo->grps_store))) + return -EFAULT; + + return 0; +} + +static long scmi_tlm_des_read_ioctl(const struct scmi_tlm_inode *tlmi, + unsigned long arg, bool single, + bool is_group) +{ + struct scmi_tlm_setup *tsp = tlmi->tsp; + void * __user uptr = (void * __user)arg; + struct scmi_tlm_data_read bulk; + int ret, grp_id = SCMI_TLM_GRP_INVALID; + + if (copy_from_user(&bulk, uptr, sizeof(bulk))) + return -EFAULT; + + struct scmi_tlm_data_read *bulk_ptr __free(kfree) = + kzalloc(struct_size(bulk_ptr, samples, bulk.num_samples), + GFP_KERNEL); + if (!bulk_ptr) + return -ENOMEM; + + if (is_group) { + const struct scmi_telemetry_group *grp = tlmi->priv; + + grp_id = grp->info->id; + } + + bulk_ptr->num_samples = bulk.num_samples; + if (!single) + ret = tsp->ops->des_bulk_read(tsp->ph, grp_id, + &bulk_ptr->num_samples, + (struct scmi_telemetry_de_sample *)bulk_ptr->samples); + else + ret = tsp->ops->des_sample_get(tsp->ph, grp_id, + &bulk_ptr->num_samples, + (struct scmi_telemetry_de_sample *)bulk_ptr->samples); + if (ret) + return ret; + + if (copy_to_user(uptr, bulk_ptr, sizeof(*bulk_ptr) + + bulk_ptr->num_samples * sizeof(bulk_ptr->samples[0]))) + return -EFAULT; + + return 0; +} + +static long scmi_tlm_unlocked_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct scmi_tlm_inode *tlmi = to_tlm_inode(file_inode(filp)); + bool is_group = IS_GROUP(tlmi->cls->flags); + + switch (cmd) { + case SCMI_TLM_GET_INFO: + if (is_group) + return -EOPNOTSUPP; + return scmi_tlm_info_get_ioctl(tlmi, arg); + case SCMI_TLM_GET_CFG: + return scmi_tlm_config_get_ioctl(tlmi, arg, is_group); + case SCMI_TLM_SET_CFG: + return scmi_tlm_config_set_ioctl(tlmi, arg, is_group); + case SCMI_TLM_GET_INTRVS: + return scmi_tlm_intervals_get_ioctl(tlmi, arg, is_group); + case SCMI_TLM_GET_DE_CFG: + if (is_group) + return -EOPNOTSUPP; + return scmi_tlm_de_config_get_ioctl(tlmi, arg); + case SCMI_TLM_SET_DE_CFG: + if (is_group) + return -EOPNOTSUPP; + return scmi_tlm_de_config_set_ioctl(tlmi, arg, false); + case SCMI_TLM_GET_DE_INFO: + if (is_group) + return -EOPNOTSUPP; + return scmi_tlm_de_info_get_ioctl(tlmi, arg); + case SCMI_TLM_GET_DE_LIST: + if (is_group) + return -EOPNOTSUPP; + return scmi_tlm_des_list_get_ioctl(tlmi, arg); + case SCMI_TLM_GET_DE_VALUE: + if (is_group) + return -EOPNOTSUPP; + return scmi_tlm_de_value_get_ioctl(tlmi, arg); + case SCMI_TLM_SET_ALL_CFG: + return scmi_tlm_de_config_set_ioctl(tlmi, arg, true); + case SCMI_TLM_GET_GRP_LIST: + if (is_group) + return -EOPNOTSUPP; + return scmi_tlm_grps_list_get_ioctl(tlmi, arg); + case SCMI_TLM_GET_GRP_INFO: + if (!is_group) + return -EOPNOTSUPP; + return scmi_tlm_grp_info_get_ioctl(tlmi, arg); + case SCMI_TLM_GET_GRP_DESC: + if (!is_group) + return -EOPNOTSUPP; + return scmi_tlm_grp_desc_get_ioctl(tlmi, arg); + case SCMI_TLM_SINGLE_SAMPLE: + return scmi_tlm_des_read_ioctl(tlmi, arg, true, is_group); + case SCMI_TLM_BULK_READ: + return scmi_tlm_des_read_ioctl(tlmi, arg, false, is_group); + default: + return -ENOTTY; + } +} + +static const struct file_operations scmi_tlm_ctrl_fops = { + .owner = THIS_MODULE, + .open = nonseekable_open, + .unlocked_ioctl = scmi_tlm_unlocked_ioctl, +}; + +DEFINE_TLM_CLASS(ctrl_tlmo, "control", 0, + S_IFREG | S_IRUSR | S_IWUSR, &scmi_tlm_ctrl_fops, NULL); +DEFINE_TLM_CLASS(grp_ctrl_tlmo, "control", TLM_IS_GROUP, + S_IFREG | S_IRUSR | S_IWUSR, &scmi_tlm_ctrl_fops, NULL); + static int scmi_telemetry_groups_initialize(struct scmi_tlm_instance *ti) { const struct scmi_telemetry_res_info *rinfo; @@ -1095,6 +1497,7 @@ static int scmi_telemetry_groups_initialize(struct scmi_tlm_instance *ti) stlmfs_create_dentry(sb, tsp, grp_dir_dentry, &grp_composing_des_tlmo, grp->des_str); + stlmfs_create_dentry(sb, tsp, grp_dir_dentry, &grp_ctrl_tlmo, grp); stlmfs_create_dentry(sb, tsp, grp_dir_dentry, &grp_data_tlmo, grp); stlmfs_create_dentry(sb, tsp, grp_dir_dentry, &grp_single_sample_tlmo, grp); @@ -1279,6 +1682,7 @@ static int scmi_tlm_root_dentries_initialize(struct scmi_tlm_instance *ti) &ti->info->base.version); stlmfs_create_dentry(sb, tsp, ti->top_dentry, &data_tlmo, ti->info); stlmfs_create_dentry(sb, tsp, ti->top_dentry, &single_sample_tlmo, ti->info); + stlmfs_create_dentry(sb, tsp, ti->top_dentry, &ctrl_tlmo, ti->info); ti->des_dentry = stlmfs_create_dentry(sb, tsp, ti->top_dentry, &des_dir_cls, NULL); ti->grps_dentry = -- 2.52.0