All of lore.kernel.org
 help / color / mirror / Atom feed
From: Akshay Gupta <Akshay.Gupta@amd.com>
To: <linux-doc@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
	<linux-hwmon@vger.kernel.org>
Cc: <corbet@lwn.net>, <skhan@linuxfoundation.org>,
	<linux@roeck-us.net>, <arnd@arndb.de>,
	<gregkh@linuxfoundation.org>, <NaveenKrishna.Chatradhi@amd.com>,
	<Anand.Umarji@amd.com>, <Akshay.Gupta@amd.com>,
	<Prathima.Lk@amd.com>
Subject: [PATCH v4 6/8] misc: amd-sbi: Add SBTSI ioctl register transfer interface
Date: Fri, 10 Jul 2026 16:46:40 +0530	[thread overview]
Message-ID: <20260710111642.850022-7-Akshay.Gupta@amd.com> (raw)
In-Reply-To: <20260710111642.850022-1-Akshay.Gupta@amd.com>

From: Prathima <Prathima.Lk@amd.com>

Implement IOCTL interface for SB-TSI driver to enable userspace access
to TSI register read/write operations through the AMD Advanced Platform
Management Link (APML) protocol.
Add an ioctl command (SBTSI_IOCTL_REG_XFER_CMD) that accepts a register
address, data byte, and direction flag.
The mutex is taken on the ioctl path here; the hwmon path is placed
under the same lock in the next patch, which completes serialization between
the hwmon and ioctl paths.

Reviewed-by: Akshay Gupta <Akshay.Gupta@amd.com>
Signed-off-by: Prathima <Prathima.Lk@amd.com>
---
Changes since v3:
- Implement a check on the read flag.
- Introduce a mutex lock on "detached" in the sbtsi_misc_unregister() function.
- Relocate the "detached" check to sbtsi_xfer_ioctl to prevent a time-of-check
  to time-of-use race condition if the device is unbound during this ioctl operation.
- Use devm_add_action_or_reset() in place of devm_add_action(), as in event of probe abort,
  it can leave the misc device registered

Changes since v2:
- Address feedback to hide mutex with common guard function
- Separate out mutex patch in hwmon file to next patch, 7/8
- Use file operation, open/release and kref to prevent use-after-free
  if the device is unbound in IOCTL
- Add check for msg.pad
 
Changes since v1:
- Use of devm_mutex_init in place of mutex_init
- Use of guard_mutex in place of mutex_lock()/mutex_unlock()
- Use of devm_add_action_or_reset() for clean removal

 drivers/misc/amd-sbi/tsi-core.c | 126 +++++++++++++++++++++++++++++++-
 drivers/misc/amd-sbi/tsi-core.h |   3 +
 drivers/misc/amd-sbi/tsi.c      |  38 +++++++++-
 include/linux/misc/tsi.h        |  16 ++++
 include/uapi/misc/amd-apml.h    |  23 ++++++
 5 files changed, 202 insertions(+), 4 deletions(-)

diff --git a/drivers/misc/amd-sbi/tsi-core.c b/drivers/misc/amd-sbi/tsi-core.c
index 1c6f37f26d94..5c178702c67a 100644
--- a/drivers/misc/amd-sbi/tsi-core.c
+++ b/drivers/misc/amd-sbi/tsi-core.c
@@ -6,7 +6,11 @@
  * Copyright (C) 2026 Advanced Micro Devices, Inc.
  */
 
+#include <linux/fs.h>
+#include <linux/ioctl.h>
 #include <linux/module.h>
+#include <linux/uaccess.h>
+#include <uapi/misc/amd-apml.h>
 #include "tsi-core.h"
 
 static inline struct sbtsi_i3c_priv *to_sbtsi_i3c_priv(struct sbtsi_data *data)
@@ -14,6 +18,17 @@ static inline struct sbtsi_i3c_priv *to_sbtsi_i3c_priv(struct sbtsi_data *data)
 	return container_of(data, struct sbtsi_i3c_priv, data);
 }
 
+void sbtsi_data_release(struct kref *kref)
+{
+	struct sbtsi_data *data = container_of(kref, struct sbtsi_data, kref);
+
+	mutex_destroy(&data->lock);
+	if (data->is_i3c)
+		kfree(to_sbtsi_i3c_priv(data));
+	else
+		kfree(data);
+}
+
 /* I2C transfer function */
 static int sbtsi_i2c_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read)
 {
@@ -77,7 +92,116 @@ int sbtsi_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read)
 	if (data->is_i3c)
 		return is_read ? sbtsi_i3c_read(data, reg, val)
 			       : sbtsi_i3c_write(data, reg, *val);
-
 	return sbtsi_i2c_xfer(data, reg, val, is_read);
 }
 EXPORT_SYMBOL_GPL(sbtsi_xfer);
+
+/*
+ * The mutex protects against concurrent register transfers to the device
+ * over the shared bus.
+ */
+static int sbtsi_xfer_ioctl(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read)
+{
+	guard(sbtsi)(data);
+
+	if (data->detached)
+		return -ENODEV;
+
+	return sbtsi_xfer(data, reg, val, is_read);
+}
+
+static int apml_tsi_reg_xfer(struct sbtsi_data *data,
+			     struct apml_tsi_xfer_msg __user *arg)
+{
+	struct apml_tsi_xfer_msg msg = { 0 };
+	int ret;
+
+	if (copy_from_user(&msg, arg, sizeof(struct apml_tsi_xfer_msg)))
+		return -EFAULT;
+
+	/*
+	 * rflag is a boolean direction flag (0 = write, 1 = read). Reject
+	 * any other value so the upper values stay reserved for future
+	 * extensions instead of being silently treated as a read.
+	 */
+	if (msg.pad || msg.rflag > 1)
+		return -EINVAL;
+
+	ret = sbtsi_xfer_ioctl(data, msg.reg_addr, &msg.data_in_out, msg.rflag);
+
+	if (msg.rflag && !ret) {
+		if (copy_to_user(arg, &msg, sizeof(struct apml_tsi_xfer_msg)))
+			return -EFAULT;
+	}
+	return ret;
+}
+
+static int sbtsi_open(struct inode *inode, struct file *fp)
+{
+	struct sbtsi_data *data;
+
+	data = container_of(fp->private_data, struct sbtsi_data, sbtsi_misc_dev);
+	scoped_guard(sbtsi, data) {
+		if (data->detached)
+			return -ENODEV;
+	}
+
+	kref_get(&data->kref);
+
+	return 0;
+}
+
+static int sbtsi_release(struct inode *inode, struct file *fp)
+{
+	struct sbtsi_data *data;
+
+	data = container_of(fp->private_data, struct sbtsi_data, sbtsi_misc_dev);
+	kref_put(&data->kref, sbtsi_data_release);
+	return 0;
+}
+
+static long sbtsi_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	struct sbtsi_data *data;
+
+	data = container_of(fp->private_data, struct sbtsi_data, sbtsi_misc_dev);
+	switch (cmd) {
+	case SBTSI_IOCTL_REG_XFER_CMD:
+		return apml_tsi_reg_xfer(data, argp);
+	default:
+		return -ENOTTY;
+	}
+}
+
+static const struct file_operations sbtsi_fops = {
+	.owner		= THIS_MODULE,
+	.open		= sbtsi_open,
+	.release	= sbtsi_release,
+	.unlocked_ioctl	= sbtsi_ioctl,
+	.compat_ioctl	= compat_ptr_ioctl,
+};
+
+int create_misc_tsi_device(struct sbtsi_data *data, struct device *dev)
+{
+	int ret;
+
+	data->sbtsi_misc_dev.name = devm_kasprintf(dev, GFP_KERNEL,
+						   "sbtsi-%x", data->dev_addr);
+	if (!data->sbtsi_misc_dev.name)
+		return -ENOMEM;
+	data->sbtsi_misc_dev.minor    = MISC_DYNAMIC_MINOR;
+	data->sbtsi_misc_dev.fops     = &sbtsi_fops;
+	data->sbtsi_misc_dev.parent   = dev;
+	data->sbtsi_misc_dev.nodename = devm_kasprintf(dev, GFP_KERNEL,
+						       "sbtsi-%x", data->dev_addr);
+	if (!data->sbtsi_misc_dev.nodename)
+		return -ENOMEM;
+	data->sbtsi_misc_dev.mode = 0600;
+
+	ret = misc_register(&data->sbtsi_misc_dev);
+	if (ret)
+		return ret;
+
+	return 0;
+}
diff --git a/drivers/misc/amd-sbi/tsi-core.h b/drivers/misc/amd-sbi/tsi-core.h
index 7e8c0e7c3bcf..4cf55c46230e 100644
--- a/drivers/misc/amd-sbi/tsi-core.h
+++ b/drivers/misc/amd-sbi/tsi-core.h
@@ -23,4 +23,7 @@ struct sbtsi_i3c_priv {
 	u8 rx __aligned(ARCH_DMA_MINALIGN);
 };
 
+int create_misc_tsi_device(struct sbtsi_data *data, struct device *dev);
+
+void sbtsi_data_release(struct kref *kref);
 #endif /* _LINUX_TSI_CORE_H_ */
diff --git a/drivers/misc/amd-sbi/tsi.c b/drivers/misc/amd-sbi/tsi.c
index 1530f440a020..f06f417f451c 100644
--- a/drivers/misc/amd-sbi/tsi.c
+++ b/drivers/misc/amd-sbi/tsi.c
@@ -42,6 +42,23 @@ static void sbtsi_unregister_hwmon_adev(void *_adev)
 	auxiliary_device_uninit(adev);
 }
 
+static void sbtsi_misc_unregister(void *arg)
+{
+	struct sbtsi_data *data = arg;
+
+	misc_deregister(&data->sbtsi_misc_dev);
+
+	guard(sbtsi)(data);
+	data->detached = true;
+}
+
+static void sbtsi_driver_unref(void *arg)
+{
+	struct sbtsi_data *data = arg;
+
+	kref_put(&data->kref, sbtsi_data_release);
+}
+
 /*
  * Create and publish an auxiliary device. The hwmon driver in
  * drivers/hwmon/sbtsi_temp.c binds to this device.
@@ -84,6 +101,13 @@ static int sbtsi_probe_common(struct device *dev, struct sbtsi_data *data)
 	u8 val;
 	int err;
 
+	mutex_init(&data->lock);
+	kref_init(&data->kref);
+
+	err = devm_add_action_or_reset(dev, sbtsi_driver_unref, data);
+	if (err)
+		return err;
+
 	err = sbtsi_xfer(data, SBTSI_REG_CONFIG, &val, true);
 	if (err)
 		return err;
@@ -92,7 +116,15 @@ static int sbtsi_probe_common(struct device *dev, struct sbtsi_data *data)
 	data->read_order = FIELD_GET(BIT(SBTSI_CONFIG_READ_ORDER_SHIFT), val);
 
 	dev_set_drvdata(dev, data);
-	return sbtsi_create_hwmon_adev(dev, data->dev_addr);
+	err = sbtsi_create_hwmon_adev(dev, data->dev_addr);
+	if (err < 0)
+		return err;
+
+	err = create_misc_tsi_device(data, dev);
+	if (err)
+		return err;
+
+	return devm_add_action_or_reset(dev, sbtsi_misc_unregister, data);
 }
 
 static int sbtsi_i2c_probe(struct i2c_client *client)
@@ -100,7 +132,7 @@ static int sbtsi_i2c_probe(struct i2c_client *client)
 	struct device *dev = &client->dev;
 	struct sbtsi_data *data;
 
-	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	data = kzalloc_obj(*data);
 	if (!data)
 		return -ENOMEM;
 
@@ -158,7 +190,7 @@ static int sbtsi_i3c_probe(struct i3c_device *i3cdev)
 	if (I3C_PID_INSTANCE_ID(devinfo.pid) != 0)
 		return -ENXIO;
 
-	i3c_priv = devm_kzalloc(dev, sizeof(*i3c_priv), GFP_KERNEL);
+	i3c_priv = kzalloc_obj(*i3c_priv);
 	if (!i3c_priv)
 		return -ENOMEM;
 
diff --git a/include/linux/misc/tsi.h b/include/linux/misc/tsi.h
index 0bdd9d923f92..5273c44688f0 100644
--- a/include/linux/misc/tsi.h
+++ b/include/linux/misc/tsi.h
@@ -8,30 +8,46 @@
 #ifndef _LINUX_MISC_TSI_H_
 #define _LINUX_MISC_TSI_H_
 
+#include <linux/cleanup.h>
 #include <linux/i2c.h>
 #include <linux/i3c/device.h>
+#include <linux/kref.h>
+#include <linux/miscdevice.h>
+#include <linux/mutex.h>
 #include <linux/types.h>
 
 /**
  * struct sbtsi_data - driver private data for an AMD SB-TSI device
  * @client:	underlying I2C client
  * @i3cdev:	underlying I3C device (when using I3C bus)
+ * @sbtsi_misc_dev: miscdevice exposing ioctl interface at /dev/sbtsi-<addr>
+ * @lock:           mutex protecting concurrent access to the device
+ * @kref:      reference count; keeps @sbtsi_data alive while misc fds are open
  * @dev_addr:	I2C/I3C device address, used as the auxiliary device instance id
+ *		and name the misc device node
  * @ext_range_mode:	sensor uses extended temperature range
  * @read_order:	if set, decimal part must be read before integer part
  * @is_i3c:	true when the device is accessed over I3C
+ * @detached:  set on driver unbind; open/ioctl return -ENODEV afterward
  */
 struct sbtsi_data {
 	union {
 		struct i2c_client *client;
 		struct i3c_device *i3cdev;
 	};
+	struct miscdevice sbtsi_misc_dev;
+	struct mutex lock;	/* protects concurrent access to the device */
+	struct kref kref;
 	u8 dev_addr;
 	bool ext_range_mode;
 	bool read_order;
 	bool is_i3c;
+	bool detached;
 };
 
+DEFINE_GUARD(sbtsi, struct sbtsi_data *, mutex_lock(&_T->lock),
+	     mutex_unlock(&_T->lock))
+
 /*
  * Name of the auxiliary device published on the auxiliary bus by the core
  * driver.  The full device name is "amd-sbtsi.temp-sensor.<id>". where
diff --git a/include/uapi/misc/amd-apml.h b/include/uapi/misc/amd-apml.h
index 745b3338fc06..8a85f79b0938 100644
--- a/include/uapi/misc/amd-apml.h
+++ b/include/uapi/misc/amd-apml.h
@@ -73,6 +73,13 @@ struct apml_reg_xfer_msg {
 	__u8 rflag;
 };
 
+struct apml_tsi_xfer_msg {
+	__u8 reg_addr;		/* TSI register address offset */
+	__u8 data_in_out;	/* Register data for read/write */
+	__u8 rflag;		/* Register read or write */
+	__u8 pad;		/* Explicit padding */
+};
+
 /*
  * AMD sideband interface base IOCTL
  */
@@ -149,4 +156,20 @@ struct apml_reg_xfer_msg {
  */
 #define SBRMI_IOCTL_REG_XFER_CMD	_IOWR(SB_BASE_IOCTL_NR, 3, struct apml_reg_xfer_msg)
 
+/**
+ * DOC: SBTSI_IOCTL_REG_XFER_CMD
+ *
+ * @Parameters
+ *
+ * @struct apml_tsi_xfer_msg
+ *	Pointer to the &struct apml_tsi_xfer_msg that will contain the protocol
+ *	information
+ *
+ * @Description
+ * IOCTL command for APML TSI messages using generic _IOWR
+ * The IOCTL provides userspace access to AMD sideband TSI register xfer protocol
+ * - TSI protocol to read/write temperature sensor registers
+ */
+#define SBTSI_IOCTL_REG_XFER_CMD	_IOWR(SB_BASE_IOCTL_NR, 4, struct apml_tsi_xfer_msg)
+
 #endif /*_AMD_APML_H_*/
-- 
2.34.1


  parent reply	other threads:[~2026-07-10 11:17 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-10 11:16 [PATCH v4 0/8] misc: amd-sbi: Refactor SBTSI driver with I3C support and ioctl interface Akshay Gupta
2026-07-10 11:16 ` [PATCH v4 1/8] hwmon/misc: amd-sbi: Move core sbtsi support from hwmon to misc Akshay Gupta
2026-07-10 11:26   ` sashiko-bot
2026-07-10 14:26   ` Guenter Roeck
2026-07-10 11:16 ` [PATCH v4 2/8] hwmon: sbtsi_temp: Refactor temperature register access into helpers Akshay Gupta
2026-07-10 11:30   ` sashiko-bot
2026-07-10 14:27   ` Guenter Roeck
2026-07-10 11:16 ` [PATCH v4 3/8] hwmon/misc: amd-sbi: Move sbtsi register transfer to core abstraction Akshay Gupta
2026-07-10 11:25   ` sashiko-bot
2026-07-10 14:28   ` Guenter Roeck
2026-07-10 11:16 ` [PATCH v4 4/8] misc: amd-sbi: Consolidate Common SBTSI Probe Path Akshay Gupta
2026-07-10 11:25   ` sashiko-bot
2026-07-10 11:16 ` [PATCH v4 5/8] misc: amd-sbi: Add support for SB-TSI over I3C Akshay Gupta
2026-07-10 11:34   ` sashiko-bot
2026-07-10 11:16 ` Akshay Gupta [this message]
2026-07-10 11:28   ` [PATCH v4 6/8] misc: amd-sbi: Add SBTSI ioctl register transfer interface sashiko-bot
2026-07-10 11:16 ` [PATCH v4 7/8] hwmon: Add mutex protecting for sbtsi read/write through hwmon Akshay Gupta
2026-07-10 11:37   ` sashiko-bot
2026-07-10 14:28   ` Guenter Roeck
2026-07-10 11:16 ` [PATCH v4 8/8] docs: misc: amd-sbi: Document SBTSI userspace interface Akshay Gupta
2026-07-10 11:35   ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260710111642.850022-7-Akshay.Gupta@amd.com \
    --to=akshay.gupta@amd.com \
    --cc=Anand.Umarji@amd.com \
    --cc=NaveenKrishna.Chatradhi@amd.com \
    --cc=Prathima.Lk@amd.com \
    --cc=arnd@arndb.de \
    --cc=corbet@lwn.net \
    --cc=gregkh@linuxfoundation.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-hwmon@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux@roeck-us.net \
    --cc=skhan@linuxfoundation.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.