diff options
| author | Muhammad Bilal <meatuni001@gmail.com> | 2026-07-28 17:54:55 +0500 |
|---|---|---|
| committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2026-09-01 12:15:16 +0200 |
| commit | ff917923f4fb9c83717ba135ee47d7e4c1567bb7 (patch) | |
| tree | c38fd8fcf58e1937b17fdbc6518b6f8b797fc15a /drivers/misc/amd-sbi | |
| download | linux-stable-ff917923f4fb9c83717ba135ee47d7e4c1567bb7.tar.gz linux-stable-ff917923f4fb9c83717ba135ee47d7e4c1567bb7.zip | |
staging: rtl8723bs: fix OOB read in rtw_action_frame_parse()grafted
rtw_action_frame_parse() takes a frame_len parameter but never
actually checks it before indexing into the frame body:
const u8 *frame_body = frame + sizeof(struct ieee80211_hdr_3addr);
...
c = frame_body[0];
...
a = frame_body[1];
frame_body already points 24 bytes (sizeof(struct
ieee80211_hdr_3addr)) into frame, so reading frame_body[0] and
frame_body[1] requires frame_len >= 26. A management action frame
shorter than that (e.g. exactly 24 bytes, the minimum a malicious
peer can send) causes a 1-2 byte out-of-bounds read.
This is reachable from rtw_cfg80211_monitor_if_xmit_entry() and
cfg80211_rtw_mgmt_tx() in ioctl_cfg80211.c, both of which pass
attacker/user-influenced frame buffers and lengths straight through.
Add the missing length check before frame_body is dereferenced.
Fixes: 554c0a3abf216 ("staging: Add rtl8723bs sdio wifi driver")
Cc: stable@vger.kernel.org
Signed-off-by: Muhammad Bilal <meatuni001@gmail.com>
Link: https://patch.msgid.link/20260728125456.32359-3-meatuni001@gmail.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/misc/amd-sbi')
| -rw-r--r-- | drivers/misc/amd-sbi/Kconfig | 35 | ||||
| -rw-r--r-- | drivers/misc/amd-sbi/Makefile | 7 | ||||
| -rw-r--r-- | drivers/misc/amd-sbi/rmi-core.c | 598 | ||||
| -rw-r--r-- | drivers/misc/amd-sbi/rmi-core.h | 74 | ||||
| -rw-r--r-- | drivers/misc/amd-sbi/rmi-hwmon.c | 120 | ||||
| -rw-r--r-- | drivers/misc/amd-sbi/rmi-i2c.c | 247 | ||||
| -rw-r--r-- | drivers/misc/amd-sbi/tsi-core.c | 207 | ||||
| -rw-r--r-- | drivers/misc/amd-sbi/tsi-core.h | 29 | ||||
| -rw-r--r-- | drivers/misc/amd-sbi/tsi.c | 234 |
9 files changed, 1551 insertions, 0 deletions
diff --git a/drivers/misc/amd-sbi/Kconfig b/drivers/misc/amd-sbi/Kconfig new file mode 100644 index 000000000..1a96b71f8 --- /dev/null +++ b/drivers/misc/amd-sbi/Kconfig @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: GPL-2.0-only +config AMD_SBRMI_I2C + tristate "AMD side band RMI support" + depends on I3C_OR_I2C + depends on ARM || ARM64 || COMPILE_TEST + select REGMAP_I2C + select REGMAP_I3C if I3C + help + Side band RMI over I2C/I3C support for AMD out of band management. + This driver is intended to run on the BMC, not the managed node. + + This driver can also be built as a module. If so, the module will + be called sbrmi-i2c. + +config AMD_SBRMI_HWMON + bool "SBRMI hardware monitoring" + depends on AMD_SBRMI_I2C && HWMON + depends on !(AMD_SBRMI_I2C=y && HWMON=m) + help + This provides support for RMI device hardware monitoring. If enabled, + a hardware monitoring device will be created for each socket in + the system. + +config AMD_SBTSI + tristate "AMD side band TSI support" + depends on I3C_OR_I2C + depends on ARM || ARM64 || COMPILE_TEST + select AUXILIARY_BUS + help + Enables support for the AMD SB-TSI (Side Band Temperature Sensor + Interface) driver, which provides access to emulated CPU temperature + sensors on AMD SoCs via an I2C/I3C connected BMC device. + + This driver can also be built as a module. If so, the module will + be called sbtsi. diff --git a/drivers/misc/amd-sbi/Makefile b/drivers/misc/amd-sbi/Makefile new file mode 100644 index 000000000..ce9321f5c --- /dev/null +++ b/drivers/misc/amd-sbi/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +sbrmi-i2c-objs += rmi-i2c.o rmi-core.o +sbrmi-i2c-$(CONFIG_AMD_SBRMI_HWMON) += rmi-hwmon.o +obj-$(CONFIG_AMD_SBRMI_I2C) += sbrmi-i2c.o +# SBTSI Configuration +sbtsi-objs += tsi.o tsi-core.o +obj-$(CONFIG_AMD_SBTSI) += sbtsi.o diff --git a/drivers/misc/amd-sbi/rmi-core.c b/drivers/misc/amd-sbi/rmi-core.c new file mode 100644 index 000000000..95c910910 --- /dev/null +++ b/drivers/misc/amd-sbi/rmi-core.c @@ -0,0 +1,598 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * sbrmi-core.c - file defining SB-RMI protocols compliant + * AMD SoC device. + * + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ +#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" + +/* Mask for Status Register bit[1] */ +#define SW_ALERT_MASK 0x2 +/* Mask to check H/W Alert status bit */ +#define HW_ALERT_MASK 0x80 + +/* Software Interrupt for triggering */ +#define START_CMD 0x80 +#define TRIGGER_MAILBOX 0x01 + +/* Default message lengths as per APML command protocol */ +/* CPUID */ +#define CPUID_RD_DATA_LEN 0x8 +#define CPUID_WR_DATA_LEN 0x8 +#define CPUID_WR_DATA_LEN_EXT 0x9 +#define CPUID_RD_REG_LEN 0xa +#define CPUID_WR_REG_LEN 0x9 +#define CPUID_WR_REG_LEN_EXT 0xa +/* MSR */ +#define MSR_RD_REG_LEN 0xa +#define MSR_WR_REG_LEN 0x8 +#define MSR_WR_REG_LEN_EXT 0x9 +#define MSR_RD_DATA_LEN 0x8 +#define MSR_WR_DATA_LEN 0x7 +#define MSR_WR_DATA_LEN_EXT 0x8 + +/* CPUID MSR Command Ids */ +#define CPUID_MCA_CMD 0x73 +#define RD_CPUID_CMD 0x91 +#define RD_MCA_CMD 0x86 + +/* CPUID MCAMSR mask & index */ +#define CPUID_MCA_THRD_INDEX 32 +#define CPUID_MCA_FUNC_MASK GENMASK(31, 0) +#define CPUID_EXT_FUNC_INDEX 48 + +/* input for bulk write to CPUID protocol */ +struct cpu_msr_indata { + u8 wr_len; /* const value */ + u8 rd_len; /* const value */ + u8 proto_cmd; /* const value */ + u8 thread; /* thread number */ + union { + u8 reg_offset[4]; /* input value */ + u32 value; + } __packed; + u8 ext; /* extended function */ +}; + +/* input for bulk write to CPUID protocol for REV 0x21 */ +struct cpu_msr_indata_ext { + u8 wr_len; /* const value */ + u8 rd_len; /* const value */ + u8 proto_cmd; /* const value */ + u8 thread_lo; /* thread number low */ + u8 thread_hi; /* thread number high */ + union { + u8 reg_offset[4]; /* input value */ + u32 value; + } __packed; + u8 ext; /* extended function */ +}; + +/* output for bulk read from CPUID protocol */ +struct cpu_msr_outdata { + u8 num_bytes; /* number of bytes return */ + u8 status; /* Protocol status code */ + union { + u64 value; + u8 reg_data[8]; + } __packed; +}; + +static inline void prepare_cpuid_input_message(struct cpu_msr_indata *input, + u8 thread_id, u32 func, + u8 ext_func) +{ + input->rd_len = CPUID_RD_DATA_LEN; + input->wr_len = CPUID_WR_DATA_LEN; + input->proto_cmd = RD_CPUID_CMD; + input->thread = thread_id << 1; + input->value = func; + input->ext = ext_func; +} + +static inline void prepare_cpuid_input_message_ext(struct cpu_msr_indata_ext *input, + u16 thread_id, u32 func, + u8 ext_func) +{ + input->rd_len = CPUID_RD_DATA_LEN; + input->wr_len = CPUID_WR_DATA_LEN_EXT; + input->proto_cmd = RD_CPUID_CMD; + input->thread_lo = (thread_id & 0xFF) << 1; + input->thread_hi = thread_id >> 8; + input->value = func; + input->ext = ext_func; +} + +static inline void prepare_mca_msr_input_message(struct cpu_msr_indata *input, + u8 thread_id, u32 data_in) +{ + input->rd_len = MSR_RD_DATA_LEN; + input->wr_len = MSR_WR_DATA_LEN; + input->proto_cmd = RD_MCA_CMD; + input->thread = thread_id << 1; + input->value = data_in; +} + +static inline void prepare_mca_msr_input_message_ext(struct cpu_msr_indata_ext *input, + u16 thread_id, u32 data_in) +{ + input->rd_len = MSR_RD_DATA_LEN; + input->wr_len = MSR_WR_DATA_LEN_EXT; + input->proto_cmd = RD_MCA_CMD; + input->thread_lo = (thread_id & 0xFF) << 1; + input->thread_hi = thread_id >> 8; + input->value = data_in; +} + +static int sbrmi_get_rev(struct sbrmi_data *data) +{ + unsigned int rev; + u16 offset = SBRMI_REV; + int ret; + + ret = regmap_read(data->regmap, offset, &rev); + if (ret < 0) + return ret; + + data->rev = rev; + return 0; +} + +static int rmi_cpuid_input(struct sbrmi_data *data, struct apml_cpuid_msg *msg, + u16 thread) +{ + struct cpu_msr_indata input = {0}; + int val = 0, ret; + + /* Thread > 127, Thread128 CS register, 1'b1 needs to be set to 1 */ + if (thread > 127) { + thread -= 128; + val = 1; + } + + ret = regmap_write(data->regmap, SBRMI_THREAD128CS, val); + if (ret < 0) + return ret; + + prepare_cpuid_input_message(&input, thread, + msg->cpu_in_out & CPUID_MCA_FUNC_MASK, + msg->cpu_in_out >> CPUID_EXT_FUNC_INDEX); + + return regmap_bulk_write(data->regmap, CPUID_MCA_CMD, + &input, CPUID_WR_REG_LEN); +} + +static int rmi_cpuid_input_ext(struct sbrmi_data *data, struct apml_cpuid_msg *msg, + u16 thread) +{ + struct cpu_msr_indata_ext input = {0}; + + prepare_cpuid_input_message_ext(&input, thread, + msg->cpu_in_out & CPUID_MCA_FUNC_MASK, + msg->cpu_in_out >> CPUID_EXT_FUNC_INDEX); + + return regmap_bulk_write(data->regmap, CPUID_MCA_CMD, + &input, CPUID_WR_REG_LEN_EXT); +} + +/* Read CPUID function protocol */ +static int rmi_cpuid_read(struct sbrmi_data *data, + struct apml_cpuid_msg *msg) +{ + struct cpu_msr_outdata output = {0}; + int ret, hw_status; + u16 thread; + + mutex_lock(&data->lock); + /* cache the rev value to identify if protocol is supported or not */ + if (!data->rev) { + ret = sbrmi_get_rev(data); + if (ret < 0) + goto exit_unlock; + } + + /* Extract thread from the input msg structure */ + thread = msg->cpu_in_out >> CPUID_MCA_THRD_INDEX; + + switch (data->rev) { + case 0x10: + /* CPUID protocol for REV 0x10 is not supported*/ + ret = -EOPNOTSUPP; + goto exit_unlock; + case 0x20: + ret = rmi_cpuid_input(data, msg, thread); + if (ret) + goto exit_unlock; + break; + case 0x21: + case 0x31: + ret = rmi_cpuid_input_ext(data, msg, thread); + if (ret) + goto exit_unlock; + break; + default: + ret = -EOPNOTSUPP; + goto exit_unlock; + } + + /* + * For RMI Rev 0x20, new h/w status bit is introduced. which is used + * by firmware to indicate completion of commands (0x71, 0x72, 0x73). + * wait for the status bit to be set by the hardware before + * reading the data out. + */ + ret = regmap_read_poll_timeout(data->regmap, SBRMI_STATUS, hw_status, + hw_status & HW_ALERT_MASK, 500, 2000000); + if (ret) + goto exit_unlock; + + ret = regmap_bulk_read(data->regmap, CPUID_MCA_CMD, + &output, CPUID_RD_REG_LEN); + if (ret < 0) + goto exit_unlock; + + ret = regmap_write(data->regmap, SBRMI_STATUS, + HW_ALERT_MASK); + if (ret < 0) + goto exit_unlock; + + if (output.num_bytes != CPUID_RD_REG_LEN - 1) { + ret = -EMSGSIZE; + goto exit_unlock; + } + if (output.status) { + ret = -EPROTOTYPE; + msg->fw_ret_code = output.status; + goto exit_unlock; + } + msg->cpu_in_out = output.value; +exit_unlock: + if (ret < 0) + msg->cpu_in_out = 0; + mutex_unlock(&data->lock); + return ret; +} + +static int rmi_mcamsr_input(struct sbrmi_data *data, struct apml_mcamsr_msg *msg, + u16 thread) +{ + struct cpu_msr_indata input = {0}; + int val = 0, ret; + + /* Thread > 127, Thread128 CS register, 1'b1 needs to be set to 1 */ + if (thread > 127) { + thread -= 128; + val = 1; + } + + ret = regmap_write(data->regmap, SBRMI_THREAD128CS, val); + if (ret < 0) + return ret; + + prepare_mca_msr_input_message(&input, thread, + msg->mcamsr_in_out & CPUID_MCA_FUNC_MASK); + + return regmap_bulk_write(data->regmap, CPUID_MCA_CMD, + &input, MSR_WR_REG_LEN); +} + +static int rmi_mcamsr_input_ext(struct sbrmi_data *data, struct apml_mcamsr_msg *msg, + u16 thread) +{ + struct cpu_msr_indata_ext input = {0}; + + prepare_mca_msr_input_message_ext(&input, thread, + msg->mcamsr_in_out & CPUID_MCA_FUNC_MASK); + + return regmap_bulk_write(data->regmap, CPUID_MCA_CMD, + &input, MSR_WR_REG_LEN_EXT); +} + +/* MCA MSR protocol */ +static int rmi_mca_msr_read(struct sbrmi_data *data, + struct apml_mcamsr_msg *msg) +{ + struct cpu_msr_outdata output = {0}; + int ret; + int hw_status; + u16 thread; + + mutex_lock(&data->lock); + /* cache the rev value to identify if protocol is supported or not */ + if (!data->rev) { + ret = sbrmi_get_rev(data); + if (ret < 0) + goto exit_unlock; + } + + /* Extract thread from the input msg structure */ + thread = msg->mcamsr_in_out >> CPUID_MCA_THRD_INDEX; + + switch (data->rev) { + case 0x10: + /* MCAMSR protocol for REV 0x10 is not supported*/ + ret = -EOPNOTSUPP; + goto exit_unlock; + case 0x20: + ret = rmi_mcamsr_input(data, msg, thread); + if (ret) + goto exit_unlock; + break; + case 0x21: + case 0x31: + ret = rmi_mcamsr_input_ext(data, msg, thread); + if (ret) + goto exit_unlock; + break; + default: + ret = -EOPNOTSUPP; + goto exit_unlock; + } + + /* + * For RMI Rev 0x20, new h/w status bit is introduced. which is used + * by firmware to indicate completion of commands (0x71, 0x72, 0x73). + * wait for the status bit to be set by the hardware before + * reading the data out. + */ + ret = regmap_read_poll_timeout(data->regmap, SBRMI_STATUS, hw_status, + hw_status & HW_ALERT_MASK, 500, 2000000); + if (ret) + goto exit_unlock; + + ret = regmap_bulk_read(data->regmap, CPUID_MCA_CMD, + &output, MSR_RD_REG_LEN); + if (ret < 0) + goto exit_unlock; + + ret = regmap_write(data->regmap, SBRMI_STATUS, + HW_ALERT_MASK); + if (ret < 0) + goto exit_unlock; + + if (output.num_bytes != MSR_RD_REG_LEN - 1) { + ret = -EMSGSIZE; + goto exit_unlock; + } + if (output.status) { + ret = -EPROTOTYPE; + msg->fw_ret_code = output.status; + goto exit_unlock; + } + msg->mcamsr_in_out = output.value; + +exit_unlock: + mutex_unlock(&data->lock); + return ret; +} + +int rmi_mailbox_xfer(struct sbrmi_data *data, + struct apml_mbox_msg *msg) +{ + 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) + goto exit_unlock; + + /* Write the command to SBRMI::InBndMsg_inst0 */ + ret = regmap_write(data->regmap, SBRMI_INBNDMSG0, msg->cmd); + if (ret < 0) + goto exit_unlock; + + /* + * For both read and write the initiator (BMC) writes + * Command Data In[31:0] to SBRMI::InBndMsg_inst[4:1] + * SBRMI_x3C(MSB):SBRMI_x39(LSB) + */ + 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; + } + + /* + * Write 0x01 to SBRMI::SoftwareInterrupt to notify firmware to + * perform the requested read or write command + */ + ret = regmap_write(data->regmap, SBRMI_SW_INTERRUPT, TRIGGER_MAILBOX); + if (ret < 0) + goto exit_unlock; + + /* + * Firmware will write SBRMI::Status[SwAlertSts]=1 to generate + * an ALERT (if enabled) to initiator (BMC) to indicate completion + * of the requested command + */ + ret = regmap_read_poll_timeout(data->regmap, SBRMI_STATUS, sw_status, + sw_status & SW_ALERT_MASK, 500, 2000000); + if (ret) + goto exit_unlock; + + ret = regmap_read(data->regmap, SBRMI_OUTBNDMSG7, &ec); + if (ret || ec) + goto exit_clear_alert; + + /* Clear the input value before updating the output data */ + msg->mb_in_out = 0; + + /* + * 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)}. + */ + 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_rmi_reg_xfer(struct sbrmi_data *data, + struct apml_reg_xfer_msg __user *arg) +{ + struct apml_reg_xfer_msg msg = { 0 }; + unsigned int data_read; + int ret; + + /* Copy the structure from user */ + if (copy_from_user(&msg, arg, sizeof(struct apml_reg_xfer_msg))) + return -EFAULT; + + mutex_lock(&data->lock); + if (msg.rflag) { + ret = regmap_read(data->regmap, msg.reg_addr, &data_read); + if (!ret) + msg.data_in_out = data_read; + } else { + ret = regmap_write(data->regmap, msg.reg_addr, msg.data_in_out); + } + + mutex_unlock(&data->lock); + + if (msg.rflag && !ret) + if (copy_to_user(arg, &msg, sizeof(struct apml_reg_xfer_msg))) + return -EFAULT; + 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; + + if (copy_to_user(arg, &msg, sizeof(struct apml_mbox_msg))) + return -EFAULT; + return ret; +} + +static int apml_cpuid_xfer(struct sbrmi_data *data, struct apml_cpuid_msg __user *arg) +{ + struct apml_cpuid_msg msg = { 0 }; + int ret; + + /* Copy the structure from user */ + if (copy_from_user(&msg, arg, sizeof(struct apml_cpuid_msg))) + return -EFAULT; + + /* CPUID Protocol */ + ret = rmi_cpuid_read(data, &msg); + if (ret && ret != -EPROTOTYPE) + return ret; + + if (copy_to_user(arg, &msg, sizeof(struct apml_cpuid_msg))) + return -EFAULT; + return ret; +} + +static int apml_mcamsr_xfer(struct sbrmi_data *data, struct apml_mcamsr_msg __user *arg) +{ + struct apml_mcamsr_msg msg = { 0 }; + int ret; + + /* Copy the structure from user */ + if (copy_from_user(&msg, arg, sizeof(struct apml_mcamsr_msg))) + return -EFAULT; + + /* MCAMSR Protocol */ + ret = rmi_mca_msr_read(data, &msg); + if (ret && ret != -EPROTOTYPE) + return ret; + + if (copy_to_user(arg, &msg, sizeof(struct apml_mcamsr_msg))) + return -EFAULT; + return ret; +} + +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); + case SBRMI_IOCTL_CPUID_CMD: + return apml_cpuid_xfer(data, argp); + case SBRMI_IOCTL_MCAMSR_CMD: + return apml_mcamsr_xfer(data, argp); + case SBRMI_IOCTL_REG_XFER_CMD: + return apml_rmi_reg_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); + if (!data->sbrmi_misc_dev.name) + return -ENOMEM; + 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); + if (!data->sbrmi_misc_dev.nodename) + return -ENOMEM; + data->sbrmi_misc_dev.mode = 0600; + + return misc_register(&data->sbrmi_misc_dev); +} diff --git a/drivers/misc/amd-sbi/rmi-core.h b/drivers/misc/amd-sbi/rmi-core.h new file mode 100644 index 000000000..975ae858e --- /dev/null +++ b/drivers/misc/amd-sbi/rmi-core.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#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 { + SBRMI_REV, + SBRMI_CTRL, + SBRMI_STATUS, + SBRMI_OUTBNDMSG0 = 0x30, + SBRMI_OUTBNDMSG1, + SBRMI_OUTBNDMSG2, + SBRMI_OUTBNDMSG3, + SBRMI_OUTBNDMSG4, + SBRMI_OUTBNDMSG5, + SBRMI_OUTBNDMSG6, + SBRMI_OUTBNDMSG7, + SBRMI_INBNDMSG0, + SBRMI_INBNDMSG1, + SBRMI_INBNDMSG2, + SBRMI_INBNDMSG3, + SBRMI_INBNDMSG4, + SBRMI_INBNDMSG5, + SBRMI_INBNDMSG6, + SBRMI_INBNDMSG7, + SBRMI_SW_INTERRUPT, + SBRMI_THREAD128CS = 0x4b, +}; + +/* + * SB-RMI supports soft mailbox service request to MP1 (power management + * firmware) through SBRMI inbound/outbound message registers. + * SB-RMI message IDs + */ +enum sbrmi_msg_id { + SBRMI_READ_PKG_PWR_CONSUMPTION = 0x1, + SBRMI_WRITE_PKG_PWR_LIMIT, + SBRMI_READ_PKG_PWR_LIMIT, + SBRMI_READ_PKG_MAX_PWR_LIMIT, +}; + +/* 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; + u8 rev; +}; + +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 +static inline int create_hwmon_sensor_device(struct device *dev, struct sbrmi_data *data) +{ + return 0; +} +#endif +int create_misc_rmi_device(struct sbrmi_data *data, struct device *dev); +#endif /*_SBRMI_CORE_H_*/ diff --git a/drivers/misc/amd-sbi/rmi-hwmon.c b/drivers/misc/amd-sbi/rmi-hwmon.c new file mode 100644 index 000000000..f4f015605 --- /dev/null +++ b/drivers/misc/amd-sbi/rmi-hwmon.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * rmi-hwmon.c - hwmon sensor support for side band RMI + * + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ +#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 */ +#define SBRMI_PWR_MIN 0 + +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 apml_mbox_msg msg = { 0 }; + int ret; + + if (!data) + return -ENODEV; + + if (type != hwmon_power) + return -EINVAL; + + switch (attr) { + case hwmon_power_input: + msg.cmd = SBRMI_READ_PKG_PWR_CONSUMPTION; + ret = rmi_mailbox_xfer(data, &msg); + break; + case hwmon_power_cap: + msg.cmd = SBRMI_READ_PKG_PWR_LIMIT; + ret = rmi_mailbox_xfer(data, &msg); + break; + case hwmon_power_cap_max: + msg.mb_in_out = data->pwr_limit_max; + ret = 0; + break; + default: + return -EINVAL; + } + if (ret < 0) + return ret; + /* hwmon power attributes are in microWatt */ + *val = (long)msg.mb_in_out * 1000; + return ret; +} + +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 apml_mbox_msg msg = { 0 }; + + if (!data) + return -ENODEV; + + if (type != hwmon_power && attr != hwmon_power_cap) + return -EINVAL; + /* + * hwmon power attributes are in microWatt + * mailbox read/write is in mWatt + */ + val /= 1000; + + val = clamp_val(val, SBRMI_PWR_MIN, data->pwr_limit_max); + + msg.cmd = SBRMI_WRITE_PKG_PWR_LIMIT; + msg.mb_in_out = val; + + return rmi_mailbox_xfer(data, &msg); +} + +static umode_t sbrmi_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_power: + switch (attr) { + case hwmon_power_input: + case hwmon_power_cap_max: + return 0444; + case hwmon_power_cap: + return 0644; + } + break; + default: + break; + } + return 0; +} + +static const struct hwmon_channel_info * const sbrmi_info[] = { + HWMON_CHANNEL_INFO(power, + HWMON_P_INPUT | HWMON_P_CAP | HWMON_P_CAP_MAX), + NULL +}; + +static const struct hwmon_ops sbrmi_hwmon_ops = { + .is_visible = sbrmi_is_visible, + .read = sbrmi_read, + .write = sbrmi_write, +}; + +static const struct hwmon_chip_info sbrmi_chip_info = { + .ops = &sbrmi_hwmon_ops, + .info = sbrmi_info, +}; + +int create_hwmon_sensor_device(struct device *dev, struct sbrmi_data *data) +{ + struct device *hwmon_dev; + + hwmon_dev = devm_hwmon_device_register_with_info(dev, "sbrmi", data, + &sbrmi_chip_info, NULL); + return PTR_ERR_OR_ZERO(hwmon_dev); +} diff --git a/drivers/misc/amd-sbi/rmi-i2c.c b/drivers/misc/amd-sbi/rmi-i2c.c new file mode 100644 index 000000000..463a9a757 --- /dev/null +++ b/drivers/misc/amd-sbi/rmi-i2c.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * rmi-i2c.c - Side band RMI over I2C support for AMD out + * of band management + * + * Copyright (C) 2024 Advanced Micro Devices, Inc. + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/i3c/device.h> +#include <linux/i3c/master.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include "rmi-core.h" + +#define REV_TWO_BYTE_ADDR 0x21 + +static int sbrmi_enable_alert(struct sbrmi_data *data) +{ + int ctrl, ret; + + /* + * Enable the SB-RMI Software alert status + * by writing 0 to bit 4 of Control register(0x1) + */ + ret = regmap_read(data->regmap, SBRMI_CTRL, &ctrl); + if (ret < 0) + return ret; + + if (ctrl & 0x10) { + ctrl &= ~0x10; + return regmap_write(data->regmap, SBRMI_CTRL, ctrl); + } + + return 0; +} + +static int sbrmi_get_max_pwr_limit(struct sbrmi_data *data) +{ + struct apml_mbox_msg msg = { 0 }; + int ret; + + msg.cmd = SBRMI_READ_PKG_MAX_PWR_LIMIT; + ret = rmi_mailbox_xfer(data, &msg); + if (ret < 0) + return ret; + data->pwr_limit_max = msg.mb_in_out; + + return ret; +} + +static int sbrmi_common_probe(struct device *dev, struct regmap *regmap, uint8_t address) +{ + struct sbrmi_data *data; + int ret; + + data = devm_kzalloc(dev, sizeof(struct sbrmi_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->regmap = regmap; + mutex_init(&data->lock); + + /* Enable alert for SB-RMI sequence */ + ret = sbrmi_enable_alert(data); + if (ret < 0) + return ret; + + /* Cache maximum power limit */ + ret = sbrmi_get_max_pwr_limit(data); + if (ret < 0) + return ret; + + data->dev_static_addr = address; + + dev_set_drvdata(dev, data); + + ret = create_hwmon_sensor_device(dev, data); + if (ret < 0) + return ret; + return create_misc_rmi_device(data, dev); +} + +static struct regmap_config sbrmi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static struct regmap_config sbrmi_regmap_config_ext = { + .reg_bits = 16, + .val_bits = 8, + .reg_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +static int sbrmi_i2c_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct regmap *regmap; + int rev, ret; + + regmap = devm_regmap_init_i2c(client, &sbrmi_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + ret = regmap_read(regmap, SBRMI_REV, &rev); + if (ret) + return ret; + + /* + * For Turin and newer platforms, revision is 0x21 or later. This is + * to identify the two byte register address size. However, one + * byte transaction can be successful. + * Verify if revision is 0x21 or later, if yes, switch to 2 byte + * address size. + * Continuously using 1 byte address for revision 0x21 or later can lead + * to bus corruption. + */ + if (rev >= REV_TWO_BYTE_ADDR) { + regmap = devm_regmap_init_i2c(client, &sbrmi_regmap_config_ext); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + } + return sbrmi_common_probe(dev, regmap, client->addr); +} + +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[] = { + { .name = "sbrmi-i2c" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sbrmi_id); + +static const struct of_device_id __maybe_unused sbrmi_of_match[] = { + { + .compatible = "amd,sbrmi", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, sbrmi_of_match); + +static struct i2c_driver sbrmi_driver = { + .driver = { + .name = "sbrmi-i2c", + .of_match_table = of_match_ptr(sbrmi_of_match), + }, + .probe = sbrmi_i2c_probe, + .remove = sbrmi_i2c_remove, + .id_table = sbrmi_id, +}; + +static int sbrmi_i3c_probe(struct i3c_device *i3cdev) +{ + struct device *dev = i3cdev_to_dev(i3cdev); + struct regmap *regmap; + int rev, ret; + + /* + * AMD OOB devices are distinguished by their Instance ID. + * For SBRMI, the Instance ID is 1. Since the device ID match + * does not account for the Instance ID, the following check + * ensures that only the SBRMI device is probed, excluding + * other OOB devices. + */ + if (I3C_PID_INSTANCE_ID(i3cdev->desc->info.pid) != 1) + return -ENXIO; + + regmap = devm_regmap_init_i3c(i3cdev, &sbrmi_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + ret = regmap_read(regmap, SBRMI_REV, &rev); + if (ret) + return ret; + + /* + * For Turin and newer platforms, revision is 0x21 or later. This is + * to identify the two byte register address size. However, one + * byte transaction can be successful. + * Verify if revision is 0x21 or later, if yes, switch to 2 byte + * address size. + * Continuously using 1 byte address for revision 0x21 or later can lead + * to bus corruption. + */ + if (rev >= REV_TWO_BYTE_ADDR) { + regmap = devm_regmap_init_i3c(i3cdev, &sbrmi_regmap_config_ext); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + } + + /* + * AMD APML I3C devices support static address. + * If static address is defined, dynamic address is same as static address. + * In case static address is not defined, I3C master controller defined + * dynamic address is used. + */ + return sbrmi_common_probe(dev, regmap, i3cdev->desc->info.dyn_addr); +} + +static void sbrmi_i3c_remove(struct i3c_device *i3cdev) +{ + struct sbrmi_data *data = dev_get_drvdata(&i3cdev->dev); + + misc_deregister(&data->sbrmi_misc_dev); +} + +static const struct i3c_device_id sbrmi_i3c_id[] = { + /* PID for AMD SBRMI device */ + I3C_DEVICE_EXTRA_INFO(0x112, 0x0, 0x2, NULL), + I3C_DEVICE_EXTRA_INFO(0x0, 0x0, 0x118, NULL), /* Socket:0, Venice */ + I3C_DEVICE_EXTRA_INFO(0x0, 0x100, 0x118, NULL), /* Socket:1, Venice */ + I3C_DEVICE_EXTRA_INFO(0x112, 0x0, 0x119, NULL), /* Socket:0, Venice */ + I3C_DEVICE_EXTRA_INFO(0x112, 0x100, 0x119, NULL), /* Socket:1, Venice */ + { } +}; +MODULE_DEVICE_TABLE(i3c, sbrmi_i3c_id); + +static struct i3c_driver sbrmi_i3c_driver = { + .driver = { + .name = "sbrmi-i3c", + }, + .probe = sbrmi_i3c_probe, + .remove = sbrmi_i3c_remove, + .id_table = sbrmi_i3c_id, +}; + +module_i3c_i2c_driver(sbrmi_i3c_driver, &sbrmi_driver); + +MODULE_AUTHOR("Akshay Gupta <akshay.gupta@amd.com>"); +MODULE_AUTHOR("Naveen Krishna Chatradhi <naveenkrishna.chatradhi@amd.com>"); +MODULE_DESCRIPTION("Hwmon driver for AMD SB-RMI emulated sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/amd-sbi/tsi-core.c b/drivers/misc/amd-sbi/tsi-core.c new file mode 100644 index 000000000..5c178702c --- /dev/null +++ b/drivers/misc/amd-sbi/tsi-core.c @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * tsi-core.c - file defining SB-TSI protocols compliant + * AMD SoC device. + * + * 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) +{ + 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) +{ + if (is_read) { + int ret = i2c_smbus_read_byte_data(data->client, reg); + + if (ret < 0) + return ret; + *val = ret; + return 0; + } + return i2c_smbus_write_byte_data(data->client, reg, *val); +} + +/* I3C read transfer function */ +static int sbtsi_i3c_read(struct sbtsi_data *data, u8 reg, u8 *val) +{ + struct sbtsi_i3c_priv *priv = to_sbtsi_i3c_priv(data); + struct i3c_xfer xfers[2] = { }; + int ret; + + priv->tx[0] = reg; + + /* Write the register address (DMA_TO_DEVICE). */ + xfers[0].rnw = false; + xfers[0].len = 1; + xfers[0].data.out = priv->tx; + + /* Read the data byte into a separate buffer (DMA_FROM_DEVICE). */ + xfers[1].rnw = true; + xfers[1].len = 1; + xfers[1].data.in = &priv->rx; + + ret = i3c_device_do_xfers(data->i3cdev, xfers, 2, I3C_SDR); + if (ret) + return ret; + + *val = priv->rx; + return ret; +} + +/* I3C write transfer function */ +static int sbtsi_i3c_write(struct sbtsi_data *data, u8 reg, u8 val) +{ + struct sbtsi_i3c_priv *priv = to_sbtsi_i3c_priv(data); + struct i3c_xfer xfers = { + .rnw = false, + .len = 2, + .data.out = priv->tx, + }; + + priv->tx[0] = reg; + priv->tx[1] = val; + + return i3c_device_do_xfers(data->i3cdev, &xfers, 1, I3C_SDR); +} + +/* Unified transfer function for I2C and I3C access */ +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 new file mode 100644 index 000000000..4cf55c462 --- /dev/null +++ b/drivers/misc/amd-sbi/tsi-core.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * AMD SBTSI core driver private definitions. + * + * Copyright (C) 2026 Advanced Micro Devices, Inc. + */ + +#ifndef _LINUX_TSI_CORE_H_ +#define _LINUX_TSI_CORE_H_ + +#include <linux/cache.h> +#include <linux/misc/tsi.h> + +/** + * struct sbtsi_i3c_priv - per-device state for I3C SBTSI (includes DMA-safe buffers) + * @data: public device state exposed via dev_set_drvdata() + * @tx: outgoing I3C bytes (DMA_TO_DEVICE); [0] register address, [1] value + * @rx: incoming I3C data byte (DMA_FROM_DEVICE) + */ +struct sbtsi_i3c_priv { + struct sbtsi_data data; + u8 tx[2]; + 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 new file mode 100644 index 000000000..f06f417f4 --- /dev/null +++ b/drivers/misc/amd-sbi/tsi.c @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * tsi.c - AMD SBTSI I2C/I3C core driver. Probes the SBTSI device over I2C/I3C + * and publishes an auxiliary device on the auxiliary bus. + * + * Copyright (C) 2026 Advanced Micro Devices, Inc. + */ + +#include <linux/auxiliary_bus.h> +#include <linux/bitfield.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/slab.h> +#include "tsi-core.h" + +#define SBTSI_REG_CONFIG 0x03 /* RO */ + +/* + * Bit for reporting value with temperature measurement range. + * bit == 0: Use default temperature range (0C to 255.875C). + * bit == 1: Use extended temperature range (-49C to +206.875C). + */ +#define SBTSI_CONFIG_EXT_RANGE_SHIFT 2 + +/* + * ReadOrder bit specifies the reading order of integer and decimal part of + * CPU temperature for atomic reads. If bit == 0, reading integer part triggers + * latching of the decimal part, so integer part should be read first. + */ +#define SBTSI_CONFIG_READ_ORDER_SHIFT 5 + +static void sbtsi_adev_release(struct device *dev) +{ + kfree(to_auxiliary_dev(dev)); +} + +static void sbtsi_unregister_hwmon_adev(void *_adev) +{ + struct auxiliary_device *adev = _adev; + + auxiliary_device_delete(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. + * + * @dev: I2C device (parent of the auxiliary device) + * @dev_addr: I2C address — used as the auxiliary device instance ID so that + * each socket gets a unique name. + */ +static int sbtsi_create_hwmon_adev(struct device *dev, u8 dev_addr) +{ + struct auxiliary_device *adev; + int ret; + + adev = kzalloc_obj(*adev); + if (!adev) + return -ENOMEM; + + adev->name = AMD_SBTSI_AUX_HWMON; + adev->id = dev_addr; + adev->dev.parent = dev; + adev->dev.release = sbtsi_adev_release; + + ret = auxiliary_device_init(adev); + if (ret) { + kfree(adev); + return ret; + } + + ret = __auxiliary_device_add(adev, AMD_SBTSI_ADEV); + if (ret) { + auxiliary_device_uninit(adev); + return ret; + } + + return devm_add_action_or_reset(dev, sbtsi_unregister_hwmon_adev, adev); +} + +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; + + data->ext_range_mode = FIELD_GET(BIT(SBTSI_CONFIG_EXT_RANGE_SHIFT), val); + data->read_order = FIELD_GET(BIT(SBTSI_CONFIG_READ_ORDER_SHIFT), val); + + dev_set_drvdata(dev, data); + 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) +{ + struct device *dev = &client->dev; + struct sbtsi_data *data; + + data = kzalloc_obj(*data); + if (!data) + return -ENOMEM; + + data->is_i3c = false; + data->client = client; + + /* In a multi-socket system, devices that are otherwise identical do not + * share the same static address; each instance resides at a unique I2C + * client address on the same or different bus. Use the I2C client + * address as the auxiliary device instance ID to ensure each socket + * receives a distinct auxiliary device name. + */ + data->dev_addr = client->addr; + return sbtsi_probe_common(dev, data); +} + +static const struct i2c_device_id sbtsi_id[] = { + { .name = "sbtsi" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sbtsi_id); + +static const struct of_device_id __maybe_unused sbtsi_of_match[] = { + { + .compatible = "amd,sbtsi", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, sbtsi_of_match); + +static struct i2c_driver sbtsi_driver = { + .driver = { + .name = "sbtsi-i2c", + .of_match_table = of_match_ptr(sbtsi_of_match), + }, + .probe = sbtsi_i2c_probe, + .id_table = sbtsi_id, +}; + +static int sbtsi_i3c_probe(struct i3c_device *i3cdev) +{ + struct device *dev = i3cdev_to_dev(i3cdev); + struct i3c_device_info devinfo; + struct sbtsi_i3c_priv *i3c_priv; + struct sbtsi_data *data; + + /* + * AMD OOB devices differ on basis of Instance ID, + * for SBTSI, instance ID is 0. + * As the device Id match is not on basis of Instance ID, + * add the below check to probe the SBTSI device only and + * not other OOB devices. + */ + i3c_device_get_info(i3cdev, &devinfo); + if (I3C_PID_INSTANCE_ID(devinfo.pid) != 0) + return -ENXIO; + + i3c_priv = kzalloc_obj(*i3c_priv); + if (!i3c_priv) + return -ENOMEM; + + data = &i3c_priv->data; + data->i3cdev = i3cdev; + data->is_i3c = true; + /* + * In a multi-socket system, otherwise identical devices do not share + * the same address; each instance is enumerated with a distinct dynamic + * (assigned) address on the I3C bus. Use that address (passed in as + * dev_addr) as the auxiliary device instance ID so that every socket + * gets a unique auxiliary device name. + */ + data->dev_addr = devinfo.dyn_addr; + + return sbtsi_probe_common(dev, data); +} + +static const struct i3c_device_id sbtsi_i3c_id[] = { + /* PID for AMD SBTSI device */ + I3C_DEVICE_EXTRA_INFO(0x112, 0x0, 0x1, NULL), /* Socket:0, Turin and Genoa */ + I3C_DEVICE_EXTRA_INFO(0x0, 0x0, 0x118, NULL), /* Socket:0, Venice */ + I3C_DEVICE_EXTRA_INFO(0x0, 0x100, 0x118, NULL), /* Socket:1, Venice */ + I3C_DEVICE_EXTRA_INFO(0x112, 0x0, 0x119, NULL), /* Socket:0, Venice */ + I3C_DEVICE_EXTRA_INFO(0x112, 0x100, 0x119, NULL), /* Socket:1, Venice */ + {} +}; +MODULE_DEVICE_TABLE(i3c, sbtsi_i3c_id); + +static struct i3c_driver sbtsi_i3c_driver = { + .driver = { + .name = "sbtsi-i3c", + }, + .probe = sbtsi_i3c_probe, + .id_table = sbtsi_i3c_id, +}; + +module_i3c_i2c_driver(sbtsi_i3c_driver, &sbtsi_driver); + +MODULE_DESCRIPTION("AMD SB-TSI I2C/I3C core driver"); +MODULE_LICENSE("GPL"); |
