misc: amd-sbi: Add support for AMD_SBI IOCTL
authorAkshay Gupta <akshay.gupta@amd.com>
Mon, 28 Apr 2025 06:30:30 +0000 (06:30 +0000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 21 May 2025 12:44:40 +0000 (14:44 +0200)
The present sbrmi module only support reporting power via hwmon.
However, AMD data center range of processors support various
system management functionality using custom protocols defined in
Advanced Platform Management Link (APML) specification.

Register a miscdevice, which creates a device /dev/sbrmiX with an IOCTL
interface for the user space to invoke the APML Mailbox protocol, which
is already defined in sbrmi_mailbox_xfer().

The APML protocols depend on a set of RMI registers. Having an IOCTL
as a single entry point will help in providing synchronization among
these protocols as multiple transactions on RMI register set may
create race condition.
Support for other protocols will be added in subsequent patches.

APML mailbox protocol returns additional error codes written by
SMU firmware in the out-bound register 0x37. These errors include,
invalid core, message not supported over platform and
others. This additional error codes can be used to provide more
details to user space.

Open-sourced and widely used https://github.com/amd/esmi_oob_library
will continue to provide user-space programmable API.

Reviewed-by: Naveen Krishna Chatradhi <naveenkrishna.chatradhi@amd.com>
Signed-off-by: Akshay Gupta <akshay.gupta@amd.com>
Link: https://lore.kernel.org/r/20250428063034.2145566-7-akshay.gupta@amd.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Documentation/misc-devices/index.rst
Documentation/userspace-api/ioctl/ioctl-number.rst
drivers/misc/amd-sbi/rmi-core.c
drivers/misc/amd-sbi/rmi-core.h
drivers/misc/amd-sbi/rmi-hwmon.c
drivers/misc/amd-sbi/rmi-i2c.c
include/uapi/misc/amd-apml.h [new file with mode: 0644]

index 8c5b226..081e794 100644 (file)
@@ -12,6 +12,7 @@ fit into other categories.
    :maxdepth: 2
 
    ad525x_dpot
+   amd-sbi
    apds990x
    bh1770glc
    c2port
index 7a1409e..3191d96 100644 (file)
@@ -397,6 +397,8 @@ Code  Seq#    Include File                                           Comments
                                                                      <mailto:mathieu.desnoyers@efficios.com>
 0xF8  all    arch/x86/include/uapi/asm/amd_hsmp.h                    AMD HSMP EPYC system management interface driver
                                                                      <mailto:nchatrad@amd.com>
+0xF9  00-0F  uapi/misc/amd-apml.h                                   AMD side band system management interface driver
+                                                                     <mailto:naveenkrishna.chatradhi@amd.com>
 0xFD  all    linux/dm-ioctl.h
 0xFE  all    linux/isst_if.h
 ====  =====  ======================================================= ================================================================
index 1d5e255..7d13c04 100644 (file)
@@ -7,7 +7,10 @@
  */
 #include <linux/delay.h>
 #include <linux/err.h>
+#include <linux/fs.h>
 #include <linux/i2c.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
 #include <linux/mutex.h>
 #include <linux/regmap.h>
 #include "rmi-core.h"
 #define TRIGGER_MAILBOX        0x01
 
 int rmi_mailbox_xfer(struct sbrmi_data *data,
-                    struct sbrmi_mailbox_msg *msg)
+                    struct apml_mbox_msg *msg)
 {
-       unsigned int bytes;
+       unsigned int bytes, ec;
        int i, ret;
        int sw_status;
        u8 byte;
 
        mutex_lock(&data->lock);
 
+       msg->fw_ret_code = 0;
+
        /* Indicate firmware a command is to be serviced */
        ret = regmap_write(data->regmap, SBRMI_INBNDMSG7, START_CMD);
        if (ret < 0)
@@ -44,8 +49,8 @@ int rmi_mailbox_xfer(struct sbrmi_data *data,
         * Command Data In[31:0] to SBRMI::InBndMsg_inst[4:1]
         * SBRMI_x3C(MSB):SBRMI_x39(LSB)
         */
-       for (i = 0; i < 4; i++) {
-               byte = (msg->data_in >> i * 8) & 0xff;
+       for (i = 0; i < AMD_SBI_MB_DATA_SIZE; i++) {
+               byte = (msg->mb_in_out >> i * 8) & 0xff;
                ret = regmap_write(data->regmap, SBRMI_INBNDMSG1 + i, byte);
                if (ret < 0)
                        goto exit_unlock;
@@ -69,29 +74,90 @@ int rmi_mailbox_xfer(struct sbrmi_data *data,
        if (ret)
                goto exit_unlock;
 
+       ret = regmap_read(data->regmap, SBRMI_OUTBNDMSG7, &ec);
+       if (ret || ec)
+               goto exit_clear_alert;
        /*
         * For a read operation, the initiator (BMC) reads the firmware
         * response Command Data Out[31:0] from SBRMI::OutBndMsg_inst[4:1]
         * {SBRMI_x34(MSB):SBRMI_x31(LSB)}.
         */
-       if (msg->read) {
-               for (i = 0; i < 4; i++) {
-                       ret = regmap_read(data->regmap,
-                                         SBRMI_OUTBNDMSG1 + i, &bytes);
-                       if (ret < 0)
-                               goto exit_unlock;
-                       msg->data_out |= bytes << i * 8;
-               }
+       for (i = 0; i < AMD_SBI_MB_DATA_SIZE; i++) {
+               ret = regmap_read(data->regmap,
+                                 SBRMI_OUTBNDMSG1 + i, &bytes);
+               if (ret < 0)
+                       break;
+               msg->mb_in_out |= bytes << i * 8;
        }
 
+exit_clear_alert:
        /*
         * BMC must write 1'b1 to SBRMI::Status[SwAlertSts] to clear the
         * ALERT to initiator
         */
        ret = regmap_write(data->regmap, SBRMI_STATUS,
                           sw_status | SW_ALERT_MASK);
-
+       if (ec) {
+               ret = -EPROTOTYPE;
+               msg->fw_ret_code = ec;
+       }
 exit_unlock:
        mutex_unlock(&data->lock);
        return ret;
 }
+
+static int apml_mailbox_xfer(struct sbrmi_data *data, struct apml_mbox_msg __user *arg)
+{
+       struct apml_mbox_msg msg = { 0 };
+       int ret;
+
+       /* Copy the structure from user */
+       if (copy_from_user(&msg, arg, sizeof(struct apml_mbox_msg)))
+               return -EFAULT;
+
+       /* Mailbox protocol */
+       ret = rmi_mailbox_xfer(data, &msg);
+       if (ret && ret != -EPROTOTYPE)
+               return ret;
+
+       return copy_to_user(arg, &msg, sizeof(struct apml_mbox_msg));
+}
+
+static long sbrmi_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+       void __user *argp = (void __user *)arg;
+       struct sbrmi_data *data;
+
+       data = container_of(fp->private_data, struct sbrmi_data, sbrmi_misc_dev);
+       switch (cmd) {
+       case SBRMI_IOCTL_MBOX_CMD:
+               return apml_mailbox_xfer(data, argp);
+       default:
+               return -ENOTTY;
+       }
+}
+
+static const struct file_operations sbrmi_fops = {
+       .owner          = THIS_MODULE,
+       .unlocked_ioctl = sbrmi_ioctl,
+       .compat_ioctl   = compat_ptr_ioctl,
+};
+
+int create_misc_rmi_device(struct sbrmi_data *data,
+                          struct device *dev)
+{
+       data->sbrmi_misc_dev.name       = devm_kasprintf(dev,
+                                                        GFP_KERNEL,
+                                                        "sbrmi-%x",
+                                                        data->dev_static_addr);
+       data->sbrmi_misc_dev.minor      = MISC_DYNAMIC_MINOR;
+       data->sbrmi_misc_dev.fops       = &sbrmi_fops;
+       data->sbrmi_misc_dev.parent     = dev;
+       data->sbrmi_misc_dev.nodename   = devm_kasprintf(dev,
+                                                        GFP_KERNEL,
+                                                        "sbrmi-%x",
+                                                        data->dev_static_addr);
+       data->sbrmi_misc_dev.mode       = 0600;
+
+       return misc_register(&data->sbrmi_misc_dev);
+}
index 3a60283..8ab31c6 100644 (file)
@@ -6,10 +6,12 @@
 #ifndef _SBRMI_CORE_H_
 #define _SBRMI_CORE_H_
 
+#include <linux/miscdevice.h>
 #include <linux/mutex.h>
 #include <linux/i2c.h>
 #include <linux/platform_device.h>
 #include <linux/regmap.h>
+#include <uapi/misc/amd-apml.h>
 
 /* SB-RMI registers */
 enum sbrmi_reg {
@@ -48,19 +50,15 @@ enum sbrmi_msg_id {
 
 /* Each client has this additional data */
 struct sbrmi_data {
+       struct miscdevice sbrmi_misc_dev;
        struct regmap *regmap;
+       /* Mutex locking */
        struct mutex lock;
        u32 pwr_limit_max;
+       u8 dev_static_addr;
 };
 
-struct sbrmi_mailbox_msg {
-       u8 cmd;
-       bool read;
-       u32 data_in;
-       u32 data_out;
-};
-
-int rmi_mailbox_xfer(struct sbrmi_data *data, struct sbrmi_mailbox_msg *msg);
+int rmi_mailbox_xfer(struct sbrmi_data *data, struct apml_mbox_msg *msg);
 #ifdef CONFIG_AMD_SBRMI_HWMON
 int create_hwmon_sensor_device(struct device *dev, struct sbrmi_data *data);
 #else
@@ -69,4 +67,5 @@ static inline int create_hwmon_sensor_device(struct device *dev, struct sbrmi_da
        return 0;
 }
 #endif
+int create_misc_rmi_device(struct sbrmi_data *data, struct device *dev);
 #endif /*_SBRMI_CORE_H_*/
index 720e800..f4f0156 100644 (file)
@@ -6,6 +6,7 @@
  */
 #include <linux/err.h>
 #include <linux/hwmon.h>
+#include <uapi/misc/amd-apml.h>
 #include "rmi-core.h"
 
 /* Do not allow setting negative power limit */
@@ -15,7 +16,7 @@ static int sbrmi_read(struct device *dev, enum hwmon_sensor_types type,
                      u32 attr, int channel, long *val)
 {
        struct sbrmi_data *data = dev_get_drvdata(dev);
-       struct sbrmi_mailbox_msg msg = { 0 };
+       struct apml_mbox_msg msg = { 0 };
        int ret;
 
        if (!data)
@@ -24,7 +25,6 @@ static int sbrmi_read(struct device *dev, enum hwmon_sensor_types type,
        if (type != hwmon_power)
                return -EINVAL;
 
-       msg.read = true;
        switch (attr) {
        case hwmon_power_input:
                msg.cmd = SBRMI_READ_PKG_PWR_CONSUMPTION;
@@ -35,7 +35,7 @@ static int sbrmi_read(struct device *dev, enum hwmon_sensor_types type,
                ret = rmi_mailbox_xfer(data, &msg);
                break;
        case hwmon_power_cap_max:
-               msg.data_out = data->pwr_limit_max;
+               msg.mb_in_out = data->pwr_limit_max;
                ret = 0;
                break;
        default:
@@ -44,7 +44,7 @@ static int sbrmi_read(struct device *dev, enum hwmon_sensor_types type,
        if (ret < 0)
                return ret;
        /* hwmon power attributes are in microWatt */
-       *val = (long)msg.data_out * 1000;
+       *val = (long)msg.mb_in_out * 1000;
        return ret;
 }
 
@@ -52,7 +52,7 @@ static int sbrmi_write(struct device *dev, enum hwmon_sensor_types type,
                       u32 attr, int channel, long val)
 {
        struct sbrmi_data *data = dev_get_drvdata(dev);
-       struct sbrmi_mailbox_msg msg = { 0 };
+       struct apml_mbox_msg msg = { 0 };
 
        if (!data)
                return -ENODEV;
@@ -68,8 +68,7 @@ static int sbrmi_write(struct device *dev, enum hwmon_sensor_types type,
        val = clamp_val(val, SBRMI_PWR_MIN, data->pwr_limit_max);
 
        msg.cmd = SBRMI_WRITE_PKG_PWR_LIMIT;
-       msg.data_in = val;
-       msg.read = false;
+       msg.mb_in_out = val;
 
        return rmi_mailbox_xfer(data, &msg);
 }
index 7a98012..f891f5a 100644 (file)
@@ -38,15 +38,14 @@ static int sbrmi_enable_alert(struct sbrmi_data *data)
 
 static int sbrmi_get_max_pwr_limit(struct sbrmi_data *data)
 {
-       struct sbrmi_mailbox_msg msg = { 0 };
+       struct apml_mbox_msg msg = { 0 };
        int ret;
 
        msg.cmd = SBRMI_READ_PKG_MAX_PWR_LIMIT;
-       msg.read = true;
        ret = rmi_mailbox_xfer(data, &msg);
        if (ret < 0)
                return ret;
-       data->pwr_limit_max = msg.data_out;
+       data->pwr_limit_max = msg.mb_in_out;
 
        return ret;
 }
@@ -81,8 +80,25 @@ static int sbrmi_i2c_probe(struct i2c_client *client)
        if (ret < 0)
                return ret;
 
+       data->dev_static_addr = client->addr;
        dev_set_drvdata(dev, data);
-       return create_hwmon_sensor_device(dev, data);
+
+       ret = create_hwmon_sensor_device(dev, data);
+       if (ret < 0)
+               return ret;
+       return create_misc_rmi_device(data, dev);
+}
+
+static void sbrmi_i2c_remove(struct i2c_client *client)
+{
+       struct sbrmi_data *data = dev_get_drvdata(&client->dev);
+
+       misc_deregister(&data->sbrmi_misc_dev);
+       /* Assign fops and parent of misc dev to NULL */
+       data->sbrmi_misc_dev.fops = NULL;
+       data->sbrmi_misc_dev.parent = NULL;
+       dev_info(&client->dev, "Removed sbrmi-i2c driver\n");
+       return;
 }
 
 static const struct i2c_device_id sbrmi_id[] = {
@@ -105,6 +121,7 @@ static struct i2c_driver sbrmi_driver = {
                .of_match_table = of_match_ptr(sbrmi_of_match),
        },
        .probe = sbrmi_i2c_probe,
+       .remove = sbrmi_i2c_remove,
        .id_table = sbrmi_id,
 };
 
diff --git a/include/uapi/misc/amd-apml.h b/include/uapi/misc/amd-apml.h
new file mode 100644 (file)
index 0000000..a5f086f
--- /dev/null
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Copyright (C) 2021-2024 Advanced Micro Devices, Inc.
+ */
+#ifndef _AMD_APML_H_
+#define _AMD_APML_H_
+
+#include <linux/types.h>
+
+/* Mailbox data size for data_in and data_out */
+#define AMD_SBI_MB_DATA_SIZE           4
+
+struct apml_mbox_msg {
+       /*
+        * Mailbox Message ID
+        */
+       __u32 cmd;
+       /*
+        * [0]...[3] mailbox 32bit input/output data
+        */
+       __u32 mb_in_out;
+       /*
+        * Error code is returned in case of soft mailbox error
+        */
+       __u32 fw_ret_code;
+};
+
+/*
+ * AMD sideband interface base IOCTL
+ */
+#define SB_BASE_IOCTL_NR       0xF9
+
+/**
+ * DOC: SBRMI_IOCTL_MBOX_CMD
+ *
+ * @Parameters
+ *
+ * @struct apml_mbox_msg
+ *     Pointer to the &struct apml_mbox_msg that will contain the protocol
+ *     information
+ *
+ * @Description
+ * IOCTL command for APML messages using generic _IOWR
+ * The IOCTL provides userspace access to AMD sideband mailbox protocol
+ * - Mailbox message read/write(0x0~0xFF)
+ * - returning "-EFAULT" if none of the above
+ * "-EPROTOTYPE" error is returned to provide additional error details
+ */
+#define SBRMI_IOCTL_MBOX_CMD           _IOWR(SB_BASE_IOCTL_NR, 0, struct apml_mbox_msg)
+
+#endif /*_AMD_APML_H_*/