From nobody Tue Feb 10 07:40:30 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id B58B1396D3B; Wed, 14 Jan 2026 11:47:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768391284; cv=none; b=kRlfpqu2TKQlXZeiMTWy7jKgRvuG1RiQXbLTQTlTJfwOxS3J3YatfiSS/FuXzPOMdQ1lkd4BSGSdbV6wO6qB2W4Kcs9/IaKdegV7TnGhVCvtTjlT1+Kv0UECe0+L79D6f5pz0dk3sS+vsslfPP44aqKC2BY2+FZ5eYCc7D9c4Hk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768391284; c=relaxed/simple; bh=nFMLkI+0iEJ6nEDbeT6P9ptIaNNpYN9xERbZGiTubRI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=SQ76d2HxlHaEeg5Ylm31t27B9CB4GOWr+vLYPDPrWZY23sBvuldj+kPNG9VCMnTZp2ehQI3flUpmqZ5WgChLZKJgTQQmtOn0otAd3mGgrEEBzzvCdoGHR6iuHG8CnF6owdtJOsIYBnHP6lRDheLrNLK8h34B7YFq0abtG4rsnYM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 562BF339; Wed, 14 Jan 2026 03:47:45 -0800 (PST) Received: from pluto.fritz.box (usa-sjc-mx-foss1.foss.arm.com [172.31.20.19]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id 95BB33F59E; Wed, 14 Jan 2026 03:47:48 -0800 (PST) From: Cristian Marussi To: linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, arm-scmi@vger.kernel.org, linux-fsdevel@vger.kernel.org Cc: sudeep.holla@arm.com, james.quinlan@broadcom.com, f.fainelli@gmail.com, vincent.guittot@linaro.org, etienne.carriere@st.com, peng.fan@oss.nxp.com, michal.simek@amd.com, dan.carpenter@linaro.org, d-gole@ti.com, jonathan.cameron@huawei.com, elif.topuz@arm.com, lukasz.luba@arm.com, philip.radford@arm.com, souvik.chakravarty@arm.com, Cristian Marussi Subject: [PATCH v2 08/17] firmware: arm_scmi: Add System Telemetry driver Date: Wed, 14 Jan 2026 11:46:12 +0000 Message-ID: <20260114114638.2290765-9-cristian.marussi@arm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260114114638.2290765-1-cristian.marussi@arm.com> References: <20260114114638.2290765-1-cristian.marussi@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add a new SCMI System Telemetry driver which gathers platform Telemetry data through the new the SCMI Telemetry protocol and expose all of the discovered Telemetry data events on a dedicated pseudo-filesystem that can be used to interactively configure SCMI Telemetry and access its provided data. Signed-off-by: Cristian Marussi --- v1 --> v2 - Harden System Telemetry writes, DO report errors - New 'secs[, ]' for current_interval_update_ms - Use new mount_api based on fs_context - Use new res_get() operation to make use of new accessors - Move des/groups enumeration to mount time - Support partial out-of-spec FW lacking some cmds (best effort) - Reworked init/exit sequence - Using dev_err_probe - Reworked probing races handling - Avoid disabling telemetry on module removal and drop remove() code --- drivers/firmware/arm_scmi/Kconfig | 10 + drivers/firmware/arm_scmi/Makefile | 1 + .../firmware/arm_scmi/scmi_system_telemetry.c | 1436 +++++++++++++++++ 3 files changed, 1447 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 e3fb36825978..9e51b3cd0c93 100644 --- a/drivers/firmware/arm_scmi/Kconfig +++ b/drivers/firmware/arm_scmi/Kconfig @@ -99,4 +99,14 @@ config ARM_SCMI_POWER_CONTROL called scmi_power_control. Note this may needed early in boot to catch early shutdown/reboot SCMI requests. =20 +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) +=3D scmi-core.o obj-$(CONFIG_ARM_SCMI_PROTOCOL) +=3D scmi-module.o =20 obj-$(CONFIG_ARM_SCMI_POWER_CONTROL) +=3D scmi_power_control.o +obj-$(CONFIG_ARM_SCMI_SYSTEM_TELEMETRY) +=3D scmi_system_telemetry.o diff --git a/drivers/firmware/arm_scmi/scmi_system_telemetry.c b/drivers/fi= rmware/arm_scmi/scmi_system_telemetry.c new file mode 100644 index 000000000000..b48f2d4eecae --- /dev/null +++ b/drivers/firmware/arm_scmi/scmi_system_telemetry.c @@ -0,0 +1,1436 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SCMI - System Telemetry Driver + * + * Copyright (C) 2026 ARM Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TLM_FS_MAGIC 0x75C01C80 +#define TLM_FS_NAME "stlmfs" +#define TLM_FS_MNT "arm_telemetry" + +#define MAX_AVAILABLE_INTERV_CHAR_LENGTH 25 +#define MAX_BULK_LINE_CHAR_LENGTH 64 + +static struct kmem_cache *stlmfs_inode_cachep; + +static DEFINE_MUTEX(stlmfs_mtx); +static struct super_block *stlmfs_sb; + +static atomic_t scmi_tlm_instance_count =3D ATOMIC_INIT(0); + +struct scmi_tlm_setup; + +struct scmi_tlm_priv { + char *buf; + size_t buf_sz; + int buf_len; + int (*bulk_retrieve)(struct scmi_tlm_setup *tsp, + int res_id, int *num_samples, + struct scmi_telemetry_de_sample *samples); +}; + +/** + * struct scmi_tlm_buffer - Output Telemetry buffer descriptor + * @used: Current number of used bytes in @buf + * @buf: Actual buffer for output data + * + * This describes an output buffer which will be made available to each r/w + * entry file_operations. + */ +struct scmi_tlm_buffer { + size_t used; +#define SCMI_TLM_MAX_BUF_SZ 128 + unsigned char buf[SCMI_TLM_MAX_BUF_SZ]; +}; + +/** + * 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 __private *rinfo; + const struct scmi_telemetry_proto_ops *ops; +}; + +/** + * struct scmi_tlm_class - Telemetry class descriptor + * @name: A string to be used for filesystem dentry name. + * @mode: Filesystem mode mask. + * @flags: Optional misc flags that can slighly modify provided @f_op beha= viour; + * this way the same @scmi_tlm_class can be used to describe multiple + * entries in the filesystem whose @f_op behaviour is very similar. + * @f_op: Optional file ops attached to this object. Used to initialized i= nodes. + * @i_op: Optional inode ops attached to this object. Used to initialize i= nodes. + * + * This structure describes a class of telemetry entities that will be + * associated with filesystem inodes having the same behaviour, i.e. the s= ame + * @f_op and @i_op: this way it will be possible to statically define a se= t of + * common descriptors to describe all the possible behaviours and then lin= k it + * to the effective inodes that will be created to support the set of DEs + * effectively discovered at run-time via SCMI. + */ +struct scmi_tlm_class { + const char *name; + umode_t mode; + int flags; +#define TLM_IS_STATE BIT(0) +#define TLM_IS_GROUP BIT(1) +#define TLM_IS_DYNAMIC BIT(2) +#define IS_STATE(_f) ((_f) & TLM_IS_STATE) +#define IS_GROUP(_f) ((_f) & TLM_IS_GROUP) +#define IS_DYNAMIC(_f) ((_f) & TLM_IS_DYNAMIC) + const struct file_operations *f_op; + const struct inode_operations *i_op; +}; + +#define TLM_ANON_CLASS(_n, _f, _m, _fo, _io) \ + { \ + .name =3D _n, \ + .flags =3D _f, \ + .f_op =3D _fo, \ + .i_op =3D _io, \ + .mode =3D _m, \ + } + +#define DEFINE_TLM_CLASS(_tag, _ns, _fl, _mo, _fop, _iop) \ + static const struct scmi_tlm_class _tag =3D \ + TLM_ANON_CLASS(_ns, _fl, _mo, _fop, _iop) + +/** + * struct scmi_tlm_inode - Telemetry node descriptor + * @tsp: A reference to a structure holding data needed to interact with + * the SCMI instance associated to this inode. + * @cls: A reference to the @scmi_tlm_class describing the behaviour of th= is + * inode. + * @priv: Generic private data reference. + * @de: SCMI DE data reference. + * @grp: SCMI Group data reference. + * @info: SCMI instance information data reference. + * @vfs_inode: The embedded VFS inode that will be initialized and plugged + * into the live filesystem at mount time. + * + * This structure is used to describe each SCMI Telemetry entity discovered + * at probe time, store its related SCMI data, and link to the proper + * telemetry class @scmi_tlm_class. + */ +struct scmi_tlm_inode { + struct scmi_tlm_setup *tsp; + const struct scmi_tlm_class *cls; + union { + const void *priv; + const struct scmi_telemetry_de *de; + const struct scmi_telemetry_group *grp; + const struct scmi_telemetry_info *info; + }; + struct inode vfs_inode; +}; + +#define to_tlm_inode(t) container_of(t, struct scmi_tlm_inode, vfs_inode) + +#define MAX_INST_NAME 32 + +#define TOP_NODES_NUM 32 +#define NODES_PER_DE_NUM 12 +#define NODES_PER_GRP_NUM 9 + +/** + * struct scmi_tlm_instance - Telemetry instance descriptor + * @id: Progressive number identifying this probed instance; it will be us= ed + * to name the top node at the root of this instance. + * @res_enumerated: A flag to indicate if full resources enumeration has b= een + * successfully performed. + * @name: Name to be used for the top root node of the instance. (tlm_) + * @node: A node to link this in the list of all instances. + * @sb: A reference to the current super_block. + * @tsp: A reference to the SCMI instance data. + * @top_cls: A class to represent the top node behaviour. + * @top_dentry: A reference to the top dentry for this instance. + * @des_dentry: A reference to the DES dentry for this instance. + * @grps_dentry: A reference to the groups dentry for this instance. + * @info: A handy reference to this instance SCMI Telemetry info data. + * + */ +struct scmi_tlm_instance { + int id; + bool res_enumerated; + char name[MAX_INST_NAME]; + struct list_head node; + struct super_block *sb; + struct scmi_tlm_setup *tsp; + struct scmi_tlm_class top_cls; + struct dentry *top_dentry; + struct dentry *des_dentry; + struct dentry *grps_dentry; + const struct scmi_telemetry_info *info; +}; + +static int scmi_telemetry_instance_register(struct super_block *sb, + struct scmi_tlm_instance *ti); + +static LIST_HEAD(scmi_telemetry_instances); + +static struct inode *stlmfs_get_inode(struct super_block *sb) +{ + struct inode *inode =3D new_inode(sb); + + if (inode) { + inode->i_ino =3D get_next_ino(); + simple_inode_init_ts(inode); + } + + return inode; +} + +static int stlmfs_failed_creating(struct dentry *dentry) +{ + simple_done_creating(dentry); + + return -ENOMEM; +} + +static struct dentry * +stlmfs_create_dentry(struct super_block *sb, struct scmi_tlm_setup *tsp, + struct dentry *parent, const struct scmi_tlm_class *cls, + const void *priv) +{ + struct scmi_tlm_inode *tlmi; + struct dentry *dentry; + struct inode *inode; + + if (!parent) + parent =3D sb->s_root; + + if (IS_ERR(parent)) + return parent; + + dentry =3D simple_start_creating(parent, cls->name); + if (IS_ERR(dentry)) + return dentry; + + inode =3D stlmfs_get_inode(sb); + if (unlikely(!inode)) { + dev_err(tsp->dev, + "out of free dentries, cannot create '%s'", + cls->name); + return ERR_PTR(stlmfs_failed_creating(dentry)); + } + + if (S_ISDIR(cls->mode)) { + inode->i_op =3D cls->i_op ?: &simple_dir_inode_operations; + inode->i_fop =3D cls->f_op ?: &simple_dir_operations; + } else { + inode->i_op =3D cls->i_op ?: &simple_dir_inode_operations; + inode->i_fop =3D cls->f_op; + } + + inode->i_mode =3D cls->mode; + inode_init_owner(&nop_mnt_idmap, inode, NULL, inode->i_mode); + inode->i_private =3D (void *)priv; + + tlmi =3D to_tlm_inode(inode); + + tlmi->cls =3D cls; + tlmi->tsp =3D tsp; + tlmi->priv =3D priv; + + d_make_persistent(dentry, inode); + + simple_done_creating(dentry); + + return dentry; +} + +static inline int +__scmi_tlm_generic_open(struct inode *ino, struct file *filp, + int (*bulk_op)(struct scmi_tlm_setup *tsp, + int res_id, int *num_samples, + struct scmi_telemetry_de_sample *samples)) +{ + struct scmi_tlm_priv *tp; + + tp =3D kzalloc(sizeof(*tp), GFP_KERNEL); + if (!tp) + return -ENOMEM; + + tp->bulk_retrieve =3D bulk_op; + + filp->private_data =3D tp; + + return nonseekable_open(ino, filp); +} + +static int scmi_tlm_priv_release(struct inode *ino, struct file *filp) +{ + struct scmi_tlm_priv *tp =3D filp->private_data; + + kfree(tp->buf); + kfree(tp); + + return 0; +} + +static inline const struct scmi_telemetry_res_info * +scmi_telemetry_res_info_get(struct scmi_tlm_setup *tsp) +{ + const struct scmi_telemetry_res_info *rinfo; + + if (tsp->rinfo) + return ACCESS_PRIVATE(tsp, rinfo); + + rinfo =3D tsp->ops->res_get(tsp->ph); + /* Cache the retrieved resource info value */ + smp_store_mb(tsp->rinfo, rinfo); + + return rinfo; +} + +static ssize_t scmi_tlm_all_des_write(struct file *filp, + const char __user *buf, + size_t count, loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + struct scmi_tlm_setup *tsp =3D tlmi->tsp; + const struct scmi_tlm_class *cls =3D tlmi->cls; + bool enable; + int ret; + + ret =3D kstrtobool_from_user(buf, count, &enable); + if (ret) + return ret; + + /* When !IS_STATE imply that is a tstamp_enable operation */ + if (IS_STATE(cls->flags) && !enable) { + ret =3D tsp->ops->all_disable(tsp->ph, false); + if (ret) + return ret; + } else { + const struct scmi_telemetry_res_info *rinfo; + + rinfo =3D scmi_telemetry_res_info_get(tsp); + if (!rinfo) + return -ENODEV; + + for (int i =3D 0; i < rinfo->num_des; i++) { + ret =3D tsp->ops->state_set(tsp->ph, false, + rinfo->des[i]->info->id, + IS_STATE(cls->flags) ? &enable : NULL, + !IS_STATE(cls->flags) ? &enable : NULL); + if (ret) + return ret; + } + } + + return count; +} + +static const struct file_operations all_des_fops =3D { + .open =3D nonseekable_open, + .write =3D scmi_tlm_all_des_write, +}; + +static ssize_t scmi_tlm_obj_enable_write(struct file *filp, + const char __user *buf, + size_t count, loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + struct scmi_tlm_setup *tsp =3D tlmi->tsp; + const struct scmi_tlm_class *cls =3D tlmi->cls; + bool enabled, is_group =3D IS_GROUP(cls->flags); + int ret, res_id; + + ret =3D kstrtobool_from_user(buf, count, &enabled); + if (ret) + return ret; + + res_id =3D !is_group ? tlmi->de->info->id : tlmi->grp->info->id; + ret =3D tsp->ops->state_set(tsp->ph, is_group, res_id, + IS_STATE(cls->flags) ? &enabled : NULL, + !IS_STATE(cls->flags) ? &enabled : NULL); + if (ret) + return ret; + + return count; +} + +static ssize_t scmi_tlm_obj_enable_read(struct file *filp, char __user *bu= f, + size_t count, loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + const bool *enabled_state, *tstamp_enabled_state; + char o_buf[2]; + bool enabled; + + if (!IS_GROUP(tlmi->cls->flags)) { + enabled_state =3D &tlmi->de->enabled; + tstamp_enabled_state =3D &tlmi->de->tstamp_enabled; + } else { + enabled_state =3D &tlmi->grp->enabled; + tstamp_enabled_state =3D &tlmi->grp->tstamp_enabled; + } + + enabled =3D IS_STATE(tlmi->cls->flags) ? *enabled_state : *tstamp_enabled= _state; + o_buf[0] =3D enabled ? 'Y' : 'N'; + o_buf[1] =3D '\n'; + + return simple_read_from_buffer(buf, count, ppos, o_buf, 2); +} + +static const struct file_operations obj_enable_fops =3D { + .open =3D nonseekable_open, + .write =3D scmi_tlm_obj_enable_write, + .read =3D scmi_tlm_obj_enable_read, +}; + +static int scmi_tlm_open(struct inode *ino, struct file *filp) +{ + struct scmi_tlm_buffer *data; + + /* Allocate some per-open buffer */ + data =3D kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + filp->private_data =3D data; + + return nonseekable_open(ino, filp); +} + +static int scmi_tlm_release(struct inode *ino, struct file *filp) +{ + kfree(filp->private_data); + + return 0; +} + +static ssize_t +scmi_tlm_update_interval_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + struct scmi_tlm_buffer *data =3D filp->private_data; + unsigned int active_update_interval; + + if (!data) + return 0; + + if (!IS_GROUP(tlmi->cls->flags)) + active_update_interval =3D tlmi->info->active_update_interval; + else + active_update_interval =3D tlmi->grp->active_update_interval; + + if (!data->used) + data->used =3D + scnprintf(data->buf, SCMI_TLM_MAX_BUF_SZ, "%u, %d\n", + SCMI_TLM_GET_UPDATE_INTERVAL_SECS(active_update_interval), + SCMI_TLM_GET_UPDATE_INTERVAL_EXP(active_update_interval)); + + return simple_read_from_buffer(buf, count, ppos, data->buf, data->used); +} + +static ssize_t +scmi_tlm_update_interval_write(struct file *filp, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + struct scmi_tlm_setup *tsp =3D tlmi->tsp; + struct scmi_tlm_buffer *data =3D filp->private_data; + bool is_group =3D IS_GROUP(tlmi->cls->flags); + unsigned int update_interval_ms =3D 0, secs =3D 0; + int ret, grp_id, exp =3D -3; + char *p, *token; + + if (count >=3D SCMI_TLM_MAX_BUF_SZ) + return -ENOSPC; + + if (copy_from_user(data->buf, buf, count)) + return -EFAULT; + + p =3D data->buf; + token =3D strsep(&p, ","); + if (!token || iscntrl(token[0])) + return -EINVAL; + + ret =3D kstrtouint(strim(token), 0, &secs); + if (ret) + return ret; + + if (p) { + token =3D p; + if (!token || iscntrl(token[0])) + return -EINVAL; + + ret =3D kstrtoint(strim(token), 0, &exp); + if (ret) + return ret; + } + + update_interval_ms =3D SCMI_TLM_BUILD_UPDATE_INTERVAL(secs, exp); + + grp_id =3D !is_group ? SCMI_TLM_GRP_INVALID : tlmi->grp->info->id; + ret =3D tsp->ops->collection_configure(tsp->ph, grp_id, !is_group, NULL, + &update_interval_ms, NULL); + if (ret) + return ret; + + return count; +} + +static const struct file_operations current_interval_fops =3D { + .open =3D scmi_tlm_open, + .read =3D scmi_tlm_update_interval_read, + .write =3D scmi_tlm_update_interval_write, + .release =3D scmi_tlm_release, +}; + +static ssize_t scmi_tlm_de_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + struct scmi_tlm_setup *tsp =3D tlmi->tsp; + struct scmi_tlm_buffer *data =3D filp->private_data; + int ret; + + if (!data) + return 0; + + if (!data->used) { + struct scmi_telemetry_de_sample sample; + + sample.id =3D tlmi->de->info->id; + ret =3D tsp->ops->de_data_read(tsp->ph, &sample); + if (ret) + return ret; + + data->used =3D scnprintf(data->buf, SCMI_TLM_MAX_BUF_SZ, + "%llu: %016llX\n", sample.tstamp, + sample.val); + } + + return simple_read_from_buffer(buf, count, ppos, data->buf, data->used); +} + +static const struct file_operations de_read_fops =3D { + .open =3D scmi_tlm_open, + .read =3D scmi_tlm_de_read, + .release =3D scmi_tlm_release, +}; + +static ssize_t +scmi_tlm_enable_read(struct file *filp, char __user *buf, size_t count, + loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + char o_buf[2]; + + o_buf[0] =3D tlmi->info->enabled ? 'Y' : 'N'; + o_buf[1] =3D '\n'; + + return simple_read_from_buffer(buf, count, ppos, o_buf, 2); +} + +static ssize_t +scmi_tlm_enable_write(struct file *filp, const char __user *buf, size_t co= unt, + loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + enum scmi_telemetry_collection mode =3D SCMI_TLM_ONDEMAND; + struct scmi_tlm_setup *tsp =3D tlmi->tsp; + bool enabled; + int ret; + + ret =3D kstrtobool_from_user(buf, count, &enabled); + if (ret) + return ret; + + ret =3D tsp->ops->collection_configure(tsp->ph, SCMI_TLM_GRP_INVALID, tru= e, + &enabled, NULL, &mode); + if (ret) + return ret; + + return count; +} + +static const struct file_operations tlm_enable_fops =3D { + .open =3D nonseekable_open, + .read =3D scmi_tlm_enable_read, + .write =3D scmi_tlm_enable_write, +}; + +static ssize_t +scmi_tlm_intrv_discrete_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + bool discrete; + char o_buf[2]; + + discrete =3D !IS_GROUP(tlmi->cls->flags) ? + tlmi->info->intervals->discrete : tlmi->grp->intervals->discrete; + + o_buf[0] =3D discrete ? 'Y' : 'N'; + o_buf[1] =3D '\n'; + + return simple_read_from_buffer(buf, count, ppos, o_buf, 2); +} + +static const struct file_operations intrv_discrete_fops =3D { + .open =3D nonseekable_open, + .read =3D scmi_tlm_intrv_discrete_read, +}; + +static ssize_t +scmi_tlm_reset_write(struct file *filp, const char __user *buf, size_t cou= nt, + loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + int ret; + + ret =3D tlmi->tsp->ops->reset(tlmi->tsp->ph); + if (ret) + return ret; + + return count; +} + +static const struct file_operations reset_fops =3D { + .open =3D nonseekable_open, + .write =3D scmi_tlm_reset_write, +}; + +static int sa_u32_get(void *data, u64 *val) +{ + *val =3D *(u32 *)data; + return 0; +} + +static int sa_u32_set(void *data, u64 val) +{ + *(u32 *)data =3D val; + return 0; +} + +static int sa_u32_open(struct inode *ino, struct file *filp) +{ + return simple_attr_open(ino, filp, sa_u32_get, sa_u32_set, "%u\n"); +} + +static int sa_s32_open(struct inode *ino, struct file *filp) +{ + return simple_attr_open(ino, filp, sa_u32_get, sa_u32_set, "%d\n"); +} + +static int sa_x32_open(struct inode *ino, struct file *filp) +{ + return simple_attr_open(ino, filp, sa_u32_get, sa_u32_set, "0x%X\n"); +} + +static const struct file_operations sa_x32_ro_fops =3D { + .open =3D sa_x32_open, + .read =3D simple_attr_read, + .release =3D simple_attr_release, +}; + +static const struct file_operations sa_u32_ro_fops =3D { + .open =3D sa_u32_open, + .read =3D simple_attr_read, + .release =3D simple_attr_release, +}; + +static const struct file_operations sa_s32_ro_fops =3D { + .open =3D sa_s32_open, + .read =3D simple_attr_read, + .release =3D simple_attr_release, +}; + +static ssize_t +scmi_de_impl_version_read(struct file *filp, char __user *buf, size_t coun= t, + loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + struct scmi_tlm_buffer *data =3D filp->private_data; + + if (!data) + return 0; + + if (!data->used) + data->used =3D scnprintf(data->buf, SCMI_TLM_MAX_BUF_SZ, + "%pUL\n", tlmi->info->base.de_impl_version); + + return simple_read_from_buffer(buf, count, ppos, data->buf, data->used); +} + +static const struct file_operations de_impl_vers_fops =3D { + .open =3D scmi_tlm_open, + .read =3D scmi_de_impl_version_read, + .release =3D scmi_tlm_release, +}; + +static ssize_t scmi_string_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct scmi_tlm_priv *tp =3D filp->private_data; + + /* + * Note that tp->buf is a scratch buffer, filled once, used to support + * multiple chunked read and freed in scmi_tlm_priv_release. + */ + if (!tp->buf) { + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + const char *str =3D tlmi->priv; + + tp->buf =3D kasprintf(GFP_KERNEL, "%s\n", str); + if (!tp->buf) + return -ENOMEM; + + tp->buf_len =3D strlen(tp->buf) + 1; + } + + return simple_read_from_buffer(buf, count, ppos, tp->buf, tp->buf_len); +} + +static int scmi_tlm_priv_open(struct inode *ino, struct file *filp) +{ + return __scmi_tlm_generic_open(ino, filp, NULL); +} + +static const struct file_operations string_ro_fops =3D { + .open =3D scmi_tlm_priv_open, + .read =3D scmi_string_read, + .release =3D scmi_tlm_priv_release, +}; + +static ssize_t scmi_available_interv_read(struct file *filp, char __user *= buf, + size_t count, loff_t *ppos) +{ + struct scmi_tlm_priv *tp =3D filp->private_data; + + /* + * Note that tp->buf is a scratch buffer, filled once, used to support + * multiple chunked read and freed in scmi_tlm_priv_release. + */ + if (!tp->buf) { + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + struct scmi_tlm_intervals *intervals; + int len =3D 0; + + intervals =3D !IS_GROUP(tlmi->cls->flags) ? + tlmi->info->intervals : tlmi->grp->intervals; + tp->buf_len =3D intervals->num * MAX_AVAILABLE_INTERV_CHAR_LENGTH; + tp->buf =3D kzalloc(tp->buf_len, GFP_KERNEL); + if (!tp->buf) + return -ENOMEM; + + for (int i =3D 0; i < intervals->num; i++) { + u32 ivl; + + ivl =3D intervals->update_intervals[i]; + len +=3D scnprintf(tp->buf + len, tp->buf_len - len, + "%u,%d ", + SCMI_TLM_GET_UPDATE_INTERVAL_SECS(ivl), + SCMI_TLM_GET_UPDATE_INTERVAL_EXP(ivl)); + } + tp->buf[len - 1] =3D '\n'; + } + + return simple_read_from_buffer(buf, count, ppos, tp->buf, tp->buf_len); +} + +static const struct file_operations available_interv_fops =3D { + .open =3D scmi_tlm_priv_open, + .read =3D scmi_available_interv_read, + .release =3D scmi_tlm_priv_release, +}; + +static const struct scmi_tlm_class tlm_tops[] =3D { + TLM_ANON_CLASS("all_des_enable", TLM_IS_STATE, + S_IFREG | S_IWUSR, &all_des_fops, NULL), + TLM_ANON_CLASS("all_des_tstamp_enable", 0, + S_IFREG | S_IWUSR, &all_des_fops, NULL), + TLM_ANON_CLASS("current_update_interval_ms", 0, + S_IFREG | S_IRUSR | S_IWUSR, ¤t_interval_fops, NULL), + TLM_ANON_CLASS("intervals_discrete", 0, + S_IFREG | S_IRUSR, &intrv_discrete_fops, NULL), + TLM_ANON_CLASS("available_update_intervals_ms", 0, + S_IFREG | S_IRUSR, &available_interv_fops, NULL), + TLM_ANON_CLASS("de_implementation_version", 0, + S_IFREG | S_IRUSR, &de_impl_vers_fops, NULL), + TLM_ANON_CLASS("tlm_enable", 0, + S_IFREG | S_IRUSR | S_IWUSR, &tlm_enable_fops, NULL), + TLM_ANON_CLASS(NULL, 0, 0, NULL, NULL), +}; + +DEFINE_TLM_CLASS(reset_tlmo, "reset", 0, S_IFREG | S_IWUSR, &reset_fops, N= ULL); + +DEFINE_TLM_CLASS(des_dir_cls, "des", 0, + S_IFDIR | S_IRWXU, NULL, NULL); +DEFINE_TLM_CLASS(name_tlmo, "name", 0, + S_IFREG | S_IRUSR, &string_ro_fops, NULL); +DEFINE_TLM_CLASS(ena_tlmo, "enable", TLM_IS_STATE, + S_IFREG | S_IRUSR | S_IWUSR, &obj_enable_fops, NULL); +DEFINE_TLM_CLASS(tstamp_ena_tlmo, "tstamp_enable", 0, + S_IFREG | S_IRUSR | S_IWUSR, &obj_enable_fops, NULL); +DEFINE_TLM_CLASS(type_tlmo, "type", 0, + S_IFREG | S_IRUSR, &sa_u32_ro_fops, NULL); +DEFINE_TLM_CLASS(unit_tlmo, "unit", 0, + S_IFREG | S_IRUSR, &sa_u32_ro_fops, NULL); +DEFINE_TLM_CLASS(unit_exp_tlmo, "unit_exp", 0, + S_IFREG | S_IRUSR, &sa_s32_ro_fops, NULL); +DEFINE_TLM_CLASS(instance_id_tlmo, "instance_id", 0, + S_IFREG | S_IRUSR, &sa_u32_ro_fops, NULL); +DEFINE_TLM_CLASS(compo_type_tlmo, "compo_type", 0, + S_IFREG | S_IRUSR, &sa_u32_ro_fops, NULL); +DEFINE_TLM_CLASS(compo_inst_id_tlmo, "compo_instance_id", 0, + S_IFREG | S_IRUSR, &sa_u32_ro_fops, NULL); +DEFINE_TLM_CLASS(tstamp_exp_tlmo, "tstamp_exp", 0, + S_IFREG | S_IRUSR, &sa_s32_ro_fops, NULL); +DEFINE_TLM_CLASS(persistent_tlmo, "persistent", 0, + S_IFREG | S_IRUSR, &sa_u32_ro_fops, NULL); +DEFINE_TLM_CLASS(value_tlmo, "value", 0, + S_IFREG | S_IRUSR, &de_read_fops, NULL); + +static int scmi_telemetry_de_populate(struct super_block *sb, + struct scmi_tlm_setup *tsp, + struct dentry *parent, + const struct scmi_telemetry_de *de, + bool fully_enumerated) +{ + struct scmi_tlm_de_info *dei =3D de->info; + + stlmfs_create_dentry(sb, tsp, parent, &ena_tlmo, de); + stlmfs_create_dentry(sb, tsp, parent, &value_tlmo, de); + if (!fully_enumerated) + return 0; + + if (de->name_support) + stlmfs_create_dentry(sb, tsp, parent, &name_tlmo, dei->name); + + if (de->tstamp_support) { + stlmfs_create_dentry(sb, tsp, parent, &tstamp_ena_tlmo, de); + stlmfs_create_dentry(sb, tsp, parent, &tstamp_exp_tlmo, + &dei->tstamp_exp); + } + + stlmfs_create_dentry(sb, tsp, parent, &type_tlmo, &dei->type); + stlmfs_create_dentry(sb, tsp, parent, &unit_tlmo, &dei->unit); + stlmfs_create_dentry(sb, tsp, parent, &unit_exp_tlmo, &dei->unit_exp); + stlmfs_create_dentry(sb, tsp, parent, &instance_id_tlmo, &dei->instance_i= d); + stlmfs_create_dentry(sb, tsp, parent, &compo_type_tlmo, &dei->compo_type); + stlmfs_create_dentry(sb, tsp, parent, &compo_inst_id_tlmo, + &dei->compo_instance_id); + stlmfs_create_dentry(sb, tsp, parent, &persistent_tlmo, &dei->persistent); + + return 0; +} + +static int +scmi_telemetry_des_lazy_enumerate(struct scmi_tlm_instance *ti, + const struct scmi_telemetry_res_info *rinfo) +{ + struct scmi_tlm_setup *tsp =3D ti->tsp; + struct super_block *sb =3D ti->sb; + + for (int i =3D 0; i < rinfo->num_des; i++) { + const struct scmi_telemetry_de *de =3D rinfo->des[i]; + struct dentry *de_dir_dentry; + int ret; + + struct scmi_tlm_class *de_tlm_cls __free(kfree) =3D + kzalloc(sizeof(*de_tlm_cls), GFP_KERNEL); + if (!de_tlm_cls) + return -ENOMEM; + + de_tlm_cls->name =3D kasprintf(GFP_KERNEL, "0x%08X", de->info->id); + if (!de_tlm_cls->name) + return -ENOMEM; + + de_tlm_cls->mode =3D S_IFDIR | S_IRWXU; + de_tlm_cls->flags =3D TLM_IS_DYNAMIC; + de_dir_dentry =3D stlmfs_create_dentry(sb, tsp, ti->des_dentry, + de_tlm_cls, de); + + ret =3D scmi_telemetry_de_populate(sb, tsp, de_dir_dentry, de, + rinfo->fully_enumerated); + if (ret) + return ret; + + retain_and_null_ptr(de_tlm_cls); + } + + ti->res_enumerated =3D true; + + dev_info(tsp->dev, "Found %d Telemetry DE resources.\n", rinfo->num_des); + + return 0; +} + +static int scmi_telemetry_des_initialize(struct scmi_tlm_instance *ti) +{ + const struct scmi_telemetry_res_info *rinfo; + + rinfo =3D scmi_telemetry_res_info_get(ti->tsp); + if (!rinfo) + return -ENODEV; + + return scmi_telemetry_des_lazy_enumerate(ti, rinfo); +} + +DEFINE_TLM_CLASS(version_tlmo, "version", 0, + S_IFREG | S_IRUSR, &sa_x32_ro_fops, NULL); + +static int scmi_tlm_bulk_on_demand(struct scmi_tlm_setup *tsp, + int res_id, int *num_samples, + struct scmi_telemetry_de_sample *samples) +{ + return tsp->ops->des_bulk_read(tsp->ph, res_id, num_samples, samples); +} + +static int scmi_tlm_data_open(struct inode *ino, struct file *filp) +{ + return __scmi_tlm_generic_open(ino, filp, scmi_tlm_bulk_on_demand); +} + +static int scmi_tlm_buffer_fill(struct device *dev, char *buf, size_t size, + int *len, int num, + struct scmi_telemetry_de_sample *samples) +{ + int idx, bytes =3D 0; + + /* Loop till there space for the next line */ + for (idx =3D 0; idx < num && size - bytes >=3D MAX_BULK_LINE_CHAR_LENGTH;= idx++) { + bytes +=3D scnprintf(buf + bytes, size - bytes, + "0x%08X %llu %016llX\n", samples[idx].id, + samples[idx].tstamp, samples[idx].val); + } + + if (idx < num) { + dev_err(dev, "Bulk buffer truncated !\n"); + return -ENOSPC; + } + + if (len) + *len =3D bytes; + + return 0; +} + +static int scmi_tlm_bulk_buffer_allocate_and_fill(struct scmi_tlm_inode *t= lmi, + struct scmi_tlm_priv *tp) +{ + struct scmi_tlm_setup *tsp =3D tlmi->tsp; + const struct scmi_tlm_class *cls =3D tlmi->cls; + struct scmi_telemetry_de_sample *samples; + bool is_group =3D IS_GROUP(cls->flags); + int ret, num_samples, res_id; + + num_samples =3D !is_group ? tlmi->info->base.num_des : + tlmi->grp->info->num_des; + tp->buf_sz =3D num_samples * MAX_BULK_LINE_CHAR_LENGTH; + tp->buf =3D kzalloc(tp->buf_sz, GFP_KERNEL); + if (!tp->buf) + return -ENOMEM; + + res_id =3D is_group ? tlmi->grp->info->id : SCMI_TLM_GRP_INVALID; + samples =3D kcalloc(num_samples, sizeof(*samples), GFP_KERNEL); + if (!samples) { + kfree(tp->buf); + return -ENOMEM; + } + + ret =3D tp->bulk_retrieve(tsp, res_id, &num_samples, samples); + if (ret) { + kfree(tp->buf); + kfree(samples); + return ret; + } + + /* + * Note that tp->buf is a scratch buffer, filled once, used to support + * multiple chunked read and freed in scmi_tlm_priv_release. + */ + ret =3D scmi_tlm_buffer_fill(tsp->dev, tp->buf, tp->buf_sz, &tp->buf_len, + num_samples, samples); + kfree(samples); + + return ret; +} + +static ssize_t scmi_tlm_generic_data_read(struct file *filp, char __user *= buf, + size_t count, loff_t *ppos) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(file_inode(filp)); + struct scmi_tlm_priv *tp =3D filp->private_data; + int ret; + + if (!tp->buf) { + ret =3D scmi_tlm_bulk_buffer_allocate_and_fill(tlmi, tp); + if (ret) + return ret; + } + + return simple_read_from_buffer(buf, count, ppos, tp->buf, tp->buf_len); +} + +static const struct file_operations scmi_tlm_data_fops =3D { + .owner =3D THIS_MODULE, + .open =3D scmi_tlm_data_open, + .read =3D scmi_tlm_generic_data_read, + .release =3D scmi_tlm_priv_release, +}; + +DEFINE_TLM_CLASS(data_tlmo, "des_bulk_read", 0, + S_IFREG | S_IRUSR, &scmi_tlm_data_fops, NULL); + +static int scmi_tlm_bulk_single_read(struct scmi_tlm_setup *tsp, + int res_id, int *num_samples, + struct scmi_telemetry_de_sample *samples) +{ + return tsp->ops->des_sample_get(tsp->ph, res_id, num_samples, samples); +} + +static int scmi_tlm_single_read_open(struct inode *ino, struct file *filp) +{ + return __scmi_tlm_generic_open(ino, filp, scmi_tlm_bulk_single_read); +} + +static const struct file_operations scmi_tlm_single_sample_fops =3D { + .owner =3D THIS_MODULE, + .open =3D scmi_tlm_single_read_open, + .read =3D scmi_tlm_generic_data_read, + .release =3D scmi_tlm_priv_release, +}; + +DEFINE_TLM_CLASS(single_sample_tlmo, "des_single_sample_read", 0, + S_IFREG | S_IRUSR, &scmi_tlm_single_sample_fops, NULL); + +static const struct scmi_tlm_class tlm_grps[] =3D { + TLM_ANON_CLASS("enable", TLM_IS_STATE | TLM_IS_GROUP, + S_IFREG | S_IRUSR | S_IWUSR, &obj_enable_fops, NULL), + TLM_ANON_CLASS("tstamp_enable", TLM_IS_GROUP, + S_IFREG | S_IRUSR | S_IWUSR, &obj_enable_fops, NULL), + TLM_ANON_CLASS(NULL, 0, 0, NULL, NULL), +}; + +DEFINE_TLM_CLASS(grp_data_tlmo, "des_bulk_read", TLM_IS_GROUP, + S_IFREG | S_IRUSR, &scmi_tlm_data_fops, NULL); + +DEFINE_TLM_CLASS(groups_dir_cls, "groups", 0, S_IFDIR | S_IRWXU, NULL, NUL= L); + +DEFINE_TLM_CLASS(grp_single_sample_tlmo, "des_single_sample_read", TLM_IS_= GROUP, + S_IFREG | S_IRUSR, &scmi_tlm_single_sample_fops, NULL); + +DEFINE_TLM_CLASS(grp_composing_des_tlmo, "composing_des", TLM_IS_GROUP, + S_IFREG | S_IRUSR, &string_ro_fops, NULL); + +DEFINE_TLM_CLASS(grp_current_interval_tlmo, "current_update_interval_ms", + TLM_IS_GROUP, S_IFREG | S_IRUSR | S_IWUSR, + ¤t_interval_fops, NULL); + +DEFINE_TLM_CLASS(grp_available_interval_tlmo, "available_update_intervals_= ms", + TLM_IS_GROUP, S_IFREG | S_IRUSR, &available_interv_fops, NULL); + +DEFINE_TLM_CLASS(grp_intervals_discrete_tlmo, "intervals_discrete", + TLM_IS_GROUP, S_IFREG | S_IRUSR, &intrv_discrete_fops, NULL); + +static int scmi_telemetry_groups_initialize(struct scmi_tlm_instance *ti) +{ + const struct scmi_telemetry_res_info *rinfo; + struct scmi_tlm_setup *tsp =3D ti->tsp; + struct super_block *sb =3D ti->sb; + struct device *dev =3D tsp->dev; + struct dentry *grp_dir_dentry; + + if (ti->info->base.num_groups =3D=3D 0) + return 0; + + rinfo =3D scmi_telemetry_res_info_get(tsp); + if (!rinfo) + return -ENODEV; + + for (int i =3D 0; i < rinfo->num_groups; i++) { + const struct scmi_telemetry_group *grp =3D &rinfo->grps[i]; + + struct scmi_tlm_class *grp_tlm_cls __free(kfree) =3D + kzalloc(sizeof(*grp_tlm_cls), GFP_KERNEL); + if (!grp_tlm_cls) + return -ENOMEM; + + grp_tlm_cls->name =3D kasprintf(GFP_KERNEL, "%u", grp->info->id); + if (!grp_tlm_cls->name) + return -ENOMEM; + + grp_tlm_cls->mode =3D S_IFDIR | S_IRWXU; + grp_tlm_cls->flags =3D TLM_IS_DYNAMIC; + + grp_dir_dentry =3D stlmfs_create_dentry(sb, tsp, ti->grps_dentry, + grp_tlm_cls, grp); + + for (const struct scmi_tlm_class *gto =3D tlm_grps; gto->name; gto++) + stlmfs_create_dentry(sb, tsp, grp_dir_dentry, gto, grp); + + stlmfs_create_dentry(sb, tsp, grp_dir_dentry, + &grp_composing_des_tlmo, grp->des_str); + + 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); + + if (ti->info->per_group_config_support) { + stlmfs_create_dentry(sb, tsp, grp_dir_dentry, + &grp_current_interval_tlmo, grp); + stlmfs_create_dentry(sb, tsp, grp_dir_dentry, + &grp_available_interval_tlmo, grp); + stlmfs_create_dentry(sb, tsp, grp_dir_dentry, + &grp_intervals_discrete_tlmo, grp); + } + + retain_and_null_ptr(grp_tlm_cls); + } + + dev_info(dev, "Found %d Telemetry GROUPS resources.\n", + rinfo->num_groups); + + return 0; +} + +static struct scmi_tlm_instance *scmi_tlm_init(struct scmi_tlm_setup *tsp, + int instance_id) +{ + struct device *dev =3D tsp->dev; + struct scmi_tlm_instance *ti; + + ti =3D devm_kzalloc(dev, sizeof(*ti), GFP_KERNEL); + if (!ti) + return ERR_PTR(-ENOMEM); + + ti->info =3D tsp->ops->info_get(tsp->ph); + if (!ti->info) + return dev_err_ptr_probe(dev, + -EINVAL, "invalid Telemetry info !\n"); + + ti->id =3D instance_id; + ti->tsp =3D tsp; + + return ti; +} + +static int scmi_telemetry_probe(struct scmi_device *sdev) +{ + const struct scmi_handle *handle =3D sdev->handle; + struct scmi_protocol_handle *ph; + struct device *dev =3D &sdev->dev; + struct scmi_tlm_instance *ti; + struct scmi_tlm_setup *tsp; + struct super_block *sb; + const void *ops; + + if (!handle) + return -ENODEV; + + ops =3D 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 =3D devm_kzalloc(dev, sizeof(*tsp), GFP_KERNEL); + if (!tsp) + return -ENOMEM; + + tsp->dev =3D dev; + tsp->ops =3D ops; + tsp->ph =3D ph; + + ti =3D scmi_tlm_init(tsp, atomic_fetch_inc(&scmi_tlm_instance_count)); + if (IS_ERR(ti)) + return PTR_ERR(ti); + + mutex_lock(&stlmfs_mtx); + list_add(&ti->node, &scmi_telemetry_instances); + sb =3D stlmfs_sb; + mutex_unlock(&stlmfs_mtx); + + /* + * If the file system was already mounted by the time this + * instance was probed, register explicitly, since the list + * has been scanned already. + */ + if (sb) { + int ret; + + ret =3D scmi_telemetry_instance_register(sb, ti); + if (ret) { + dev_err(dev, "Failed to register instance %u at probe.\n", + ti->id); + return ret; + } + } + + return 0; +} + +static const struct scmi_device_id scmi_id_table[] =3D { + { SCMI_PROTOCOL_TELEMETRY, "telemetry" }, + { } +}; +MODULE_DEVICE_TABLE(scmi, scmi_id_table); + +static struct scmi_driver scmi_telemetry_driver =3D { + .name =3D "scmi-telemetry-driver", + .probe =3D scmi_telemetry_probe, + .id_table =3D scmi_id_table, +}; + +static struct inode *stlmfs_alloc_inode(struct super_block *sb) +{ + struct scmi_tlm_inode *tlmi; + + tlmi =3D alloc_inode_sb(sb, stlmfs_inode_cachep, GFP_KERNEL); + if (!tlmi) + return NULL; + + tlmi->cls =3D NULL; + + return &tlmi->vfs_inode; +} + +static void stlmfs_free_inode(struct inode *inode) +{ + struct scmi_tlm_inode *tlmi =3D to_tlm_inode(inode); + + if (tlmi->cls && IS_DYNAMIC(tlmi->cls->flags)) { + kfree(tlmi->cls->name); + kfree(tlmi->cls); + } + + kmem_cache_free(stlmfs_inode_cachep, tlmi); +} + +static const struct super_operations tlm_sops =3D { + .statfs =3D simple_statfs, + .alloc_inode =3D stlmfs_alloc_inode, + .free_inode =3D stlmfs_free_inode, +}; + +static struct dentry *stlmfs_create_root_dentry(struct super_block *sb) +{ + struct dentry *dentry; + struct inode *inode; + + inode =3D stlmfs_get_inode(sb); + if (!inode) + return ERR_PTR(-ENOMEM); + + inode->i_op =3D &simple_dir_inode_operations; + inode->i_fop =3D &simple_dir_operations; + inode_init_owner(&nop_mnt_idmap, inode, NULL, S_IFDIR | S_IRWXU); + + dentry =3D d_make_root(inode); + if (!dentry) + return ERR_PTR(-ENOMEM); + + return dentry; +} + +static int scmi_tlm_root_dentries_initialize(struct scmi_tlm_instance *ti) +{ + struct scmi_tlm_setup *tsp =3D ti->tsp; + struct super_block *sb =3D ti->sb; + + scnprintf(ti->name, MAX_INST_NAME, "tlm_%d", ti->id); + + /* Allocate top instance node */ + ti->top_cls.name =3D ti->name; + ti->top_cls.mode =3D S_IFDIR | S_IRWXU; + + /* Create the root of this instance */ + ti->top_dentry =3D stlmfs_create_dentry(sb, tsp, sb->s_root, &ti->top_cls= , NULL); + for (const struct scmi_tlm_class *tlmo =3D tlm_tops; tlmo->name; tlmo++) + stlmfs_create_dentry(sb, tsp, ti->top_dentry, tlmo, ti->info); + + if (ti->info->reset_support) + stlmfs_create_dentry(sb, tsp, ti->top_dentry, &reset_tlmo, NULL); + + stlmfs_create_dentry(sb, tsp, ti->top_dentry, &version_tlmo, + &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->in= fo); + ti->des_dentry =3D + stlmfs_create_dentry(sb, tsp, ti->top_dentry, &des_dir_cls, NULL); + ti->grps_dentry =3D + stlmfs_create_dentry(sb, tsp, ti->top_dentry, &groups_dir_cls, NULL); + + return 0; +} + +static int scmi_telemetry_instance_register(struct super_block *sb, + struct scmi_tlm_instance *ti) +{ + int ret; + + ti->sb =3D sb; + ret =3D scmi_tlm_root_dentries_initialize(ti); + if (ret) + return ret; + + ret =3D scmi_telemetry_des_initialize(ti); + if (ret) + return ret; + + ret =3D scmi_telemetry_groups_initialize(ti); + if (ret) { + dev_warn(ti->tsp->dev, + "Failed to initialize groups for instance %s.\n", + ti->top_cls.name); + } + + return 0; +} + +static int stlmfs_fill_super(struct super_block *sb, struct fs_context *fc) +{ + struct scmi_tlm_instance *ti; + struct dentry *root_dentry; + int ret; + + sb->s_magic =3D TLM_FS_MAGIC; + sb->s_blocksize =3D PAGE_SIZE; + sb->s_blocksize_bits =3D PAGE_SHIFT; + sb->s_op =3D &tlm_sops; + + root_dentry =3D stlmfs_create_root_dentry(sb); + if (IS_ERR(root_dentry)) + return PTR_ERR(root_dentry); + + sb->s_root =3D root_dentry; + + mutex_lock(&stlmfs_mtx); + list_for_each_entry(ti, &scmi_telemetry_instances, node) { + mutex_unlock(&stlmfs_mtx); + ret =3D scmi_telemetry_instance_register(sb, ti); + if (ret) + dev_err(ti->tsp->dev, + "Failed to register instance %u.\n", ti->id); + mutex_lock(&stlmfs_mtx); + } + stlmfs_sb =3D sb; + mutex_unlock(&stlmfs_mtx); + + return 0; +} + +static int stlmfs_get_tree(struct fs_context *fc) +{ + return get_tree_single(fc, stlmfs_fill_super); +} + +static const struct fs_context_operations stlmfs_fc_ops =3D { + .get_tree =3D stlmfs_get_tree, +}; + +static int stlmfs_init_fs_context(struct fs_context *fc) +{ + fc->ops =3D &stlmfs_fc_ops; + + return 0; +} + +static void stlmfs_kill_sb(struct super_block *sb) +{ + kill_anon_super(sb); +} + +static struct file_system_type scmi_telemetry_fs =3D { + .owner =3D THIS_MODULE, + .name =3D TLM_FS_NAME, + .kill_sb =3D stlmfs_kill_sb, + .init_fs_context =3D stlmfs_init_fs_context, + .fs_flags =3D 0, +}; + +static void stlmfs_init_once(void *arg) +{ + struct scmi_tlm_inode *tlmi =3D arg; + + inode_init_once(&tlmi->vfs_inode); +} + +static int __init scmi_telemetry_init(void) +{ + int ret; + + ret =3D sysfs_create_mount_point(fs_kobj, TLM_FS_MNT); + if (ret && ret !=3D -EEXIST) + return ret; + + stlmfs_inode_cachep =3D kmem_cache_create("stlmfs_inode_cache", + sizeof(struct scmi_tlm_inode), 0, + SLAB_RECLAIM_ACCOUNT | SLAB_ACCOUNT, + stlmfs_init_once); + if (!stlmfs_inode_cachep) { + ret =3D -ENOMEM; + goto out_mnt; + } + + ret =3D register_filesystem(&scmi_telemetry_fs); + if (ret) + goto out_kmem; + + ret =3D scmi_register(&scmi_telemetry_driver); + if (ret) + goto out_reg; + + return 0; + +out_reg: + unregister_filesystem(&scmi_telemetry_fs); +out_kmem: + kmem_cache_destroy(stlmfs_inode_cachep); +out_mnt: + sysfs_remove_mount_point(fs_kobj, TLM_FS_MNT); + + return ret; +} +module_init(scmi_telemetry_init); + +static void __exit scmi_telemetry_exit(void) +{ + int ret; + + scmi_unregister(&scmi_telemetry_driver); + ret =3D unregister_filesystem(&scmi_telemetry_fs); + if (ret) + pr_err("Failed to unregister %s\n", TLM_FS_NAME); + + sysfs_remove_mount_point(fs_kobj, TLM_FS_MNT); + kmem_cache_destroy(stlmfs_inode_cachep); +} +module_exit(scmi_telemetry_exit); + +MODULE_AUTHOR("Cristian Marussi "); +MODULE_DESCRIPTION("ARM SCMI Telemetry Driver"); +MODULE_LICENSE("GPL"); --=20 2.52.0