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.
Reviewed-by: Akshay Gupta <Akshay.Gupta@amd.com>
Signed-off-by: Prathima <Prathima.Lk@amd.com>
---
drivers/misc/amd-sbi/tsi-core.c | 86 ++++++++++++++++++++++++++++++++-
drivers/misc/amd-sbi/tsi-core.h | 5 ++
drivers/misc/amd-sbi/tsi.c | 37 ++++++++++++--
include/uapi/misc/amd-apml.h | 23 +++++++++
4 files changed, 146 insertions(+), 5 deletions(-)
diff --git a/drivers/misc/amd-sbi/tsi-core.c b/drivers/misc/amd-sbi/tsi-core.c
index fcb7fcf87a55..080cf1f368df 100644
--- a/drivers/misc/amd-sbi/tsi-core.c
+++ b/drivers/misc/amd-sbi/tsi-core.c
@@ -6,6 +6,10 @@
* Copyright (c) 2020, Kun Yi <kunyi@google.com>
*/
+#include <linux/fs.h>
+#include <linux/ioctl.h>
+#include <linux/uaccess.h>
+#include <uapi/misc/amd-apml.h>
#include "tsi-core.h"
/* I3C read transfer function */
@@ -56,9 +60,87 @@ static int sbtsi_i2c_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read
/* Unified transfer function for I2C and I3C access */
int sbtsi_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read)
{
+ int ret;
+
+ mutex_lock(&data->lock);
if (data->is_i3c)
- return is_read ? sbtsi_i3c_read(data, reg, val)
+ ret = is_read ? sbtsi_i3c_read(data, reg, val)
: sbtsi_i3c_write(data, reg, val);
+ else
+ ret = sbtsi_i2c_xfer(data, reg, val, is_read);
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+static int apml_tsi_reg_xfer(struct sbtsi_data *data,
+ struct apml_tsi_xfer_msg __user *arg)
+{
+ struct apml_tsi_xfer_msg msg = { 0 };
+ u8 val;
+ int ret;
+
+ /* Copy the structure from user */
+ if (copy_from_user(&msg, arg, sizeof(struct apml_tsi_xfer_msg)))
+ return -EFAULT;
+
+ if (msg.rflag) {
+ /* Read operation */
+ ret = sbtsi_xfer(data, msg.reg_addr, &val, true);
+ if (!ret)
+ msg.data_in_out = val;
+ } else {
+ /* Write operation */
+ ret = sbtsi_xfer(data, msg.reg_addr, &msg.data_in_out, false);
+ }
+
+ if (msg.rflag && !ret) {
+ if (copy_to_user(arg, &msg, sizeof(struct apml_tsi_xfer_msg)))
+ return -EFAULT;
+ }
+ return ret;
+}
+
+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,
+ .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 sbtsi_i2c_xfer(data, reg, val, is_read);
+ return ret;
}
diff --git a/drivers/misc/amd-sbi/tsi-core.h b/drivers/misc/amd-sbi/tsi-core.h
index a4ded17942c9..d130ceadde04 100644
--- a/drivers/misc/amd-sbi/tsi-core.h
+++ b/drivers/misc/amd-sbi/tsi-core.h
@@ -9,6 +9,7 @@
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
+#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/types.h>
@@ -21,12 +22,16 @@ struct sbtsi_data {
struct i2c_client *client;
struct i3c_device *i3cdev;
};
+ struct miscdevice sbtsi_misc_dev;
+ struct mutex lock; /* serializes SBTSI register access */
bool ext_range_mode;
bool read_order;
bool is_i3c;
+ u8 dev_addr;
};
int sbtsi_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read);
+int create_misc_tsi_device(struct sbtsi_data *data, struct device *dev);
#ifdef CONFIG_AMD_SBTSI_HWMON
int create_sbtsi_hwmon_sensor_device(struct device *dev, struct sbtsi_data *data);
diff --git a/drivers/misc/amd-sbi/tsi.c b/drivers/misc/amd-sbi/tsi.c
index d7a8237fc4fd..6e321064a3f3 100644
--- a/drivers/misc/amd-sbi/tsi.c
+++ b/drivers/misc/amd-sbi/tsi.c
@@ -38,6 +38,7 @@ static int sbtsi_i2c_probe(struct i2c_client *client)
data->is_i3c = false;
data->client = client;
+ mutex_init(&data->lock);
err = i2c_smbus_read_byte_data(data->client, SBTSI_REG_CONFIG);
if (err < 0)
return err;
@@ -45,7 +46,21 @@ static int sbtsi_i2c_probe(struct i2c_client *client)
data->read_order = FIELD_GET(BIT(SBTSI_CONFIG_READ_ORDER_SHIFT), err);
dev_set_drvdata(dev, data);
- return create_sbtsi_hwmon_sensor_device(dev, data);
+ err = create_sbtsi_hwmon_sensor_device(dev, data);
+ if (err < 0)
+ return err;
+ data->dev_addr = client->addr;
+ return create_misc_tsi_device(data, dev);
+}
+
+static void sbtsi_i2c_remove(struct i2c_client *client)
+{
+ struct sbtsi_data *data = dev_get_drvdata(&client->dev);
+
+ if (data)
+ misc_deregister(&data->sbtsi_misc_dev);
+
+ dev_info(&client->dev, "Removed sbtsi driver\n");
}
static const struct i2c_device_id sbtsi_id[] = {
@@ -68,6 +83,7 @@ static struct i2c_driver sbtsi_driver = {
.of_match_table = of_match_ptr(sbtsi_of_match),
},
.probe = sbtsi_i2c_probe,
+ .remove = sbtsi_i2c_remove,
.id_table = sbtsi_id,
};
@@ -90,7 +106,7 @@ static int sbtsi_i3c_probe(struct i3c_device *i3cdev)
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
-
+ mutex_init(&data->lock);
data->i3cdev = i3cdev;
data->is_i3c = true;
@@ -102,7 +118,21 @@ static int sbtsi_i3c_probe(struct i3c_device *i3cdev)
data->read_order = FIELD_GET(BIT(SBTSI_CONFIG_READ_ORDER_SHIFT), val);
dev_set_drvdata(dev, data);
- return create_sbtsi_hwmon_sensor_device(dev, data);
+ err = create_sbtsi_hwmon_sensor_device(dev, data);
+ if (err < 0)
+ return err;
+ data->dev_addr = i3cdev->desc->info.dyn_addr;
+ return create_misc_tsi_device(data, dev);
+}
+
+static void sbtsi_i3c_remove(struct i3c_device *i3cdev)
+{
+ struct sbtsi_data *data = dev_get_drvdata(&i3cdev->dev);
+
+ if (data)
+ misc_deregister(&data->sbtsi_misc_dev);
+
+ dev_info(&i3cdev->dev, "Removed sbtsi-i3c driver\n");
}
static const struct i3c_device_id sbtsi_i3c_id[] = {
@@ -121,6 +151,7 @@ static struct i3c_driver sbtsi_i3c_driver = {
.name = "sbtsi-i3c",
},
.probe = sbtsi_i3c_probe,
+ .remove = sbtsi_i3c_remove,
.id_table = sbtsi_i3c_id,
};
diff --git a/include/uapi/misc/amd-apml.h b/include/uapi/misc/amd-apml.h
index 745b3338fc06..199532b8e64a 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
On Mon, Mar 23, 2026 at 04:38:10PM +0530, Akshay Gupta wrote: > + dev_info(&client->dev, "Removed sbtsi driver\n"); When drivers work properly, they are quiet. No need for this kernel log spam. And were are all of these new ioctls documented and where is the userspace tools that use them? thanks, greg k-h
© 2016 - 2026 Red Hat, Inc.