mirror_ubuntu-kernels/drivers/platform/x86/amd/hsmp.c
Suma Hegde ce08d3570a
platform/x86/amd/hsmp: Move dev from platdev to hsmp_socket
On an ACPI enabled platforms the probe is called for each socket
and the struct dev is different for each socket. This change
will help in handling both ACPI and non-ACPI platforms.

Also change pr_err() to dev_err() API.

Signed-off-by: Suma Hegde <suma.hegde@amd.com>
Signed-off-by: Naveen Krishna Chatradhi <nchatrad@amd.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Link: https://lore.kernel.org/r/20240106022532.1746932-5-suma.hegde@amd.com
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
2024-01-31 10:26:12 +02:00

640 lines
16 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* AMD HSMP Platform Driver
* Copyright (c) 2022, AMD.
* All Rights Reserved.
*
* This file provides a device implementation for HSMP interface
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <asm/amd_hsmp.h>
#include <asm/amd_nb.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/semaphore.h>
#define DRIVER_NAME "amd_hsmp"
#define DRIVER_VERSION "2.0"
/* HSMP Status / Error codes */
#define HSMP_STATUS_NOT_READY 0x00
#define HSMP_STATUS_OK 0x01
#define HSMP_ERR_INVALID_MSG 0xFE
#define HSMP_ERR_INVALID_INPUT 0xFF
/* Timeout in millsec */
#define HSMP_MSG_TIMEOUT 100
#define HSMP_SHORT_SLEEP 1
#define HSMP_WR true
#define HSMP_RD false
/*
* To access specific HSMP mailbox register, s/w writes the SMN address of HSMP mailbox
* register into the SMN_INDEX register, and reads/writes the SMN_DATA reg.
* Below are required SMN address for HSMP Mailbox register offsets in SMU address space
*/
#define SMN_HSMP_BASE 0x3B00000
#define SMN_HSMP_MSG_ID 0x0010534
#define SMN_HSMP_MSG_RESP 0x0010980
#define SMN_HSMP_MSG_DATA 0x00109E0
#define HSMP_INDEX_REG 0xc4
#define HSMP_DATA_REG 0xc8
#define HSMP_CDEV_NAME "hsmp_cdev"
#define HSMP_DEVNODE_NAME "hsmp"
#define HSMP_METRICS_TABLE_NAME "metrics_bin"
#define HSMP_ATTR_GRP_NAME_SIZE 10
struct hsmp_mbaddr_info {
u32 base_addr;
u32 msg_id_off;
u32 msg_resp_off;
u32 msg_arg_off;
u32 size;
};
struct hsmp_socket {
struct bin_attribute hsmp_attr;
struct hsmp_mbaddr_info mbinfo;
void __iomem *metric_tbl_addr;
struct semaphore hsmp_sem;
char name[HSMP_ATTR_GRP_NAME_SIZE];
struct pci_dev *root;
struct device *dev;
u16 sock_ind;
};
struct hsmp_plat_device {
struct miscdevice hsmp_device;
struct hsmp_socket *sock;
u32 proto_ver;
u16 num_sockets;
};
static struct hsmp_plat_device plat_dev;
static int amd_hsmp_rdwr(struct hsmp_socket *sock, u32 offset,
u32 *value, bool write)
{
int ret;
if (!sock->root)
return -ENODEV;
ret = pci_write_config_dword(sock->root, HSMP_INDEX_REG,
sock->mbinfo.base_addr + offset);
if (ret)
return ret;
ret = (write ? pci_write_config_dword(sock->root, HSMP_DATA_REG, *value)
: pci_read_config_dword(sock->root, HSMP_DATA_REG, value));
return ret;
}
/*
* Send a message to the HSMP port via PCI-e config space registers.
*
* The caller is expected to zero out any unused arguments.
* If a response is expected, the number of response words should be greater than 0.
*
* Returns 0 for success and populates the requested number of arguments.
* Returns a negative error code for failure.
*/
static int __hsmp_send_message(struct hsmp_socket *sock, struct hsmp_message *msg)
{
struct hsmp_mbaddr_info *mbinfo;
unsigned long timeout, short_sleep;
u32 mbox_status;
u32 index;
int ret;
mbinfo = &sock->mbinfo;
/* Clear the status register */
mbox_status = HSMP_STATUS_NOT_READY;
ret = amd_hsmp_rdwr(sock, mbinfo->msg_resp_off, &mbox_status, HSMP_WR);
if (ret) {
pr_err("Error %d clearing mailbox status register\n", ret);
return ret;
}
index = 0;
/* Write any message arguments */
while (index < msg->num_args) {
ret = amd_hsmp_rdwr(sock, mbinfo->msg_arg_off + (index << 2),
&msg->args[index], HSMP_WR);
if (ret) {
pr_err("Error %d writing message argument %d\n", ret, index);
return ret;
}
index++;
}
/* Write the message ID which starts the operation */
ret = amd_hsmp_rdwr(sock, mbinfo->msg_id_off, &msg->msg_id, HSMP_WR);
if (ret) {
pr_err("Error %d writing message ID %u\n", ret, msg->msg_id);
return ret;
}
/*
* Depending on when the trigger write completes relative to the SMU
* firmware 1 ms cycle, the operation may take from tens of us to 1 ms
* to complete. Some operations may take more. Therefore we will try
* a few short duration sleeps and switch to long sleeps if we don't
* succeed quickly.
*/
short_sleep = jiffies + msecs_to_jiffies(HSMP_SHORT_SLEEP);
timeout = jiffies + msecs_to_jiffies(HSMP_MSG_TIMEOUT);
while (time_before(jiffies, timeout)) {
ret = amd_hsmp_rdwr(sock, mbinfo->msg_resp_off, &mbox_status, HSMP_RD);
if (ret) {
pr_err("Error %d reading mailbox status\n", ret);
return ret;
}
if (mbox_status != HSMP_STATUS_NOT_READY)
break;
if (time_before(jiffies, short_sleep))
usleep_range(50, 100);
else
usleep_range(1000, 2000);
}
if (unlikely(mbox_status == HSMP_STATUS_NOT_READY)) {
return -ETIMEDOUT;
} else if (unlikely(mbox_status == HSMP_ERR_INVALID_MSG)) {
return -ENOMSG;
} else if (unlikely(mbox_status == HSMP_ERR_INVALID_INPUT)) {
return -EINVAL;
} else if (unlikely(mbox_status != HSMP_STATUS_OK)) {
pr_err("Message ID %u unknown failure (status = 0x%X)\n",
msg->msg_id, mbox_status);
return -EIO;
}
/*
* SMU has responded OK. Read response data.
* SMU reads the input arguments from eight 32 bit registers starting
* from SMN_HSMP_MSG_DATA and writes the response data to the same
* SMN_HSMP_MSG_DATA address.
* We copy the response data if any, back to the args[].
*/
index = 0;
while (index < msg->response_sz) {
ret = amd_hsmp_rdwr(sock, mbinfo->msg_arg_off + (index << 2),
&msg->args[index], HSMP_RD);
if (ret) {
pr_err("Error %d reading response %u for message ID:%u\n",
ret, index, msg->msg_id);
break;
}
index++;
}
return ret;
}
static int validate_message(struct hsmp_message *msg)
{
/* msg_id against valid range of message IDs */
if (msg->msg_id < HSMP_TEST || msg->msg_id >= HSMP_MSG_ID_MAX)
return -ENOMSG;
/* msg_id is a reserved message ID */
if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_RSVD)
return -ENOMSG;
/* num_args and response_sz against the HSMP spec */
if (msg->num_args != hsmp_msg_desc_table[msg->msg_id].num_args ||
msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz)
return -EINVAL;
return 0;
}
int hsmp_send_message(struct hsmp_message *msg)
{
struct hsmp_socket *sock;
int ret;
if (!msg)
return -EINVAL;
ret = validate_message(msg);
if (ret)
return ret;
if (!plat_dev.sock || msg->sock_ind >= plat_dev.num_sockets)
return -ENODEV;
sock = &plat_dev.sock[msg->sock_ind];
/*
* The time taken by smu operation to complete is between
* 10us to 1ms. Sometime it may take more time.
* In SMP system timeout of 100 millisecs should
* be enough for the previous thread to finish the operation
*/
ret = down_timeout(&sock->hsmp_sem, msecs_to_jiffies(HSMP_MSG_TIMEOUT));
if (ret < 0)
return ret;
ret = __hsmp_send_message(sock, msg);
up(&sock->hsmp_sem);
return ret;
}
EXPORT_SYMBOL_GPL(hsmp_send_message);
static int hsmp_test(u16 sock_ind, u32 value)
{
struct hsmp_message msg = { 0 };
int ret;
/*
* Test the hsmp port by performing TEST command. The test message
* takes one argument and returns the value of that argument + 1.
*/
msg.msg_id = HSMP_TEST;
msg.num_args = 1;
msg.response_sz = 1;
msg.args[0] = value;
msg.sock_ind = sock_ind;
ret = hsmp_send_message(&msg);
if (ret)
return ret;
/* Check the response value */
if (msg.args[0] != (value + 1)) {
dev_err(plat_dev.sock[sock_ind].dev,
"Socket %d test message failed, Expected 0x%08X, received 0x%08X\n",
sock_ind, (value + 1), msg.args[0]);
return -EBADE;
}
return ret;
}
static long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
{
int __user *arguser = (int __user *)arg;
struct hsmp_message msg = { 0 };
int ret;
if (copy_struct_from_user(&msg, sizeof(msg), arguser, sizeof(struct hsmp_message)))
return -EFAULT;
/*
* Check msg_id is within the range of supported msg ids
* i.e within the array bounds of hsmp_msg_desc_table
*/
if (msg.msg_id < HSMP_TEST || msg.msg_id >= HSMP_MSG_ID_MAX)
return -ENOMSG;
switch (fp->f_mode & (FMODE_WRITE | FMODE_READ)) {
case FMODE_WRITE:
/*
* Device is opened in O_WRONLY mode
* Execute only set/configure commands
*/
if (hsmp_msg_desc_table[msg.msg_id].type != HSMP_SET)
return -EINVAL;
break;
case FMODE_READ:
/*
* Device is opened in O_RDONLY mode
* Execute only get/monitor commands
*/
if (hsmp_msg_desc_table[msg.msg_id].type != HSMP_GET)
return -EINVAL;
break;
case FMODE_READ | FMODE_WRITE:
/*
* Device is opened in O_RDWR mode
* Execute both get/monitor and set/configure commands
*/
break;
default:
return -EINVAL;
}
ret = hsmp_send_message(&msg);
if (ret)
return ret;
if (hsmp_msg_desc_table[msg.msg_id].response_sz > 0) {
/* Copy results back to user for get/monitor commands */
if (copy_to_user(arguser, &msg, sizeof(struct hsmp_message)))
return -EFAULT;
}
return 0;
}
static const struct file_operations hsmp_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = hsmp_ioctl,
.compat_ioctl = hsmp_ioctl,
};
static ssize_t hsmp_metric_tbl_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buf,
loff_t off, size_t count)
{
struct hsmp_socket *sock = bin_attr->private;
struct hsmp_message msg = { 0 };
int ret;
if (!sock)
return -EINVAL;
/* Do not support lseek(), reads entire metric table */
if (count < bin_attr->size) {
dev_err(sock->dev, "Wrong buffer size\n");
return -EINVAL;
}
msg.msg_id = HSMP_GET_METRIC_TABLE;
msg.sock_ind = sock->sock_ind;
ret = hsmp_send_message(&msg);
if (ret)
return ret;
memcpy_fromio(buf, sock->metric_tbl_addr, bin_attr->size);
return bin_attr->size;
}
static int hsmp_get_tbl_dram_base(u16 sock_ind)
{
struct hsmp_socket *sock = &plat_dev.sock[sock_ind];
struct hsmp_message msg = { 0 };
phys_addr_t dram_addr;
int ret;
msg.sock_ind = sock_ind;
msg.response_sz = hsmp_msg_desc_table[HSMP_GET_METRIC_TABLE_DRAM_ADDR].response_sz;
msg.msg_id = HSMP_GET_METRIC_TABLE_DRAM_ADDR;
ret = hsmp_send_message(&msg);
if (ret)
return ret;
/*
* calculate the metric table DRAM address from lower and upper 32 bits
* sent from SMU and ioremap it to virtual address.
*/
dram_addr = msg.args[0] | ((u64)(msg.args[1]) << 32);
if (!dram_addr) {
dev_err(sock->dev, "Invalid DRAM address for metric table\n");
return -ENOMEM;
}
sock->metric_tbl_addr = devm_ioremap(sock->dev, dram_addr,
sizeof(struct hsmp_metric_table));
if (!sock->metric_tbl_addr) {
dev_err(sock->dev, "Failed to ioremap metric table addr\n");
return -ENOMEM;
}
return 0;
}
static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj,
struct bin_attribute *battr, int id)
{
if (plat_dev.proto_ver == HSMP_PROTO_VER6)
return battr->attr.mode;
else
return 0;
}
static int hsmp_init_metric_tbl_bin_attr(struct bin_attribute **hattrs, u16 sock_ind)
{
struct bin_attribute *hattr = &plat_dev.sock[sock_ind].hsmp_attr;
sysfs_bin_attr_init(hattr);
hattr->attr.name = HSMP_METRICS_TABLE_NAME;
hattr->attr.mode = 0444;
hattr->read = hsmp_metric_tbl_read;
hattr->size = sizeof(struct hsmp_metric_table);
hattr->private = &plat_dev.sock[sock_ind];
hattrs[0] = hattr;
if (plat_dev.proto_ver == HSMP_PROTO_VER6)
return (hsmp_get_tbl_dram_base(sock_ind));
else
return 0;
}
/* One bin sysfs for metrics table*/
#define NUM_HSMP_ATTRS 1
static int hsmp_create_sysfs_interface(void)
{
const struct attribute_group **hsmp_attr_grps;
struct bin_attribute **hsmp_bin_attrs;
struct attribute_group *attr_grp;
int ret;
u16 i;
/* String formatting is currently limited to u8 sockets */
if (WARN_ON(plat_dev.num_sockets > U8_MAX))
return -ERANGE;
hsmp_attr_grps = devm_kzalloc(plat_dev.sock[0].dev, sizeof(struct attribute_group *) *
(plat_dev.num_sockets + 1), GFP_KERNEL);
if (!hsmp_attr_grps)
return -ENOMEM;
/* Create a sysfs directory for each socket */
for (i = 0; i < plat_dev.num_sockets; i++) {
attr_grp = devm_kzalloc(plat_dev.sock[i].dev, sizeof(struct attribute_group),
GFP_KERNEL);
if (!attr_grp)
return -ENOMEM;
snprintf(plat_dev.sock[i].name, HSMP_ATTR_GRP_NAME_SIZE, "socket%u", (u8)i);
attr_grp->name = plat_dev.sock[i].name;
/* Null terminated list of attributes */
hsmp_bin_attrs = devm_kzalloc(plat_dev.sock[i].dev, sizeof(struct bin_attribute *) *
(NUM_HSMP_ATTRS + 1), GFP_KERNEL);
if (!hsmp_bin_attrs)
return -ENOMEM;
attr_grp->bin_attrs = hsmp_bin_attrs;
attr_grp->is_bin_visible = hsmp_is_sock_attr_visible;
hsmp_attr_grps[i] = attr_grp;
/* Now create the leaf nodes */
ret = hsmp_init_metric_tbl_bin_attr(hsmp_bin_attrs, i);
if (ret)
return ret;
}
return devm_device_add_groups(plat_dev.sock[0].dev, hsmp_attr_grps);
}
static int hsmp_cache_proto_ver(void)
{
struct hsmp_message msg = { 0 };
int ret;
msg.msg_id = HSMP_GET_PROTO_VER;
msg.sock_ind = 0;
msg.response_sz = hsmp_msg_desc_table[HSMP_GET_PROTO_VER].response_sz;
ret = hsmp_send_message(&msg);
if (!ret)
plat_dev.proto_ver = msg.args[0];
return ret;
}
static int init_platform_device(struct device *dev)
{
struct hsmp_socket *sock;
int ret, i;
for (i = 0; i < plat_dev.num_sockets; i++) {
if (!node_to_amd_nb(i))
return -ENODEV;
sock = &plat_dev.sock[i];
sock->root = node_to_amd_nb(i)->root;
sock->sock_ind = i;
sock->dev = dev;
sock->mbinfo.base_addr = SMN_HSMP_BASE;
sock->mbinfo.msg_id_off = SMN_HSMP_MSG_ID;
sock->mbinfo.msg_resp_off = SMN_HSMP_MSG_RESP;
sock->mbinfo.msg_arg_off = SMN_HSMP_MSG_DATA;
sema_init(&sock->hsmp_sem, 1);
/* Test the hsmp interface on each socket */
ret = hsmp_test(i, 0xDEADBEEF);
if (ret) {
dev_err(dev, "HSMP test message failed on Fam:%x model:%x\n",
boot_cpu_data.x86, boot_cpu_data.x86_model);
dev_err(dev, "Is HSMP disabled in BIOS ?\n");
return ret;
}
}
return 0;
}
static int hsmp_pltdrv_probe(struct platform_device *pdev)
{
int ret;
plat_dev.sock = devm_kzalloc(&pdev->dev,
(plat_dev.num_sockets * sizeof(struct hsmp_socket)),
GFP_KERNEL);
if (!plat_dev.sock)
return -ENOMEM;
ret = init_platform_device(&pdev->dev);
if (ret)
return ret;
plat_dev.hsmp_device.name = HSMP_CDEV_NAME;
plat_dev.hsmp_device.minor = MISC_DYNAMIC_MINOR;
plat_dev.hsmp_device.fops = &hsmp_fops;
plat_dev.hsmp_device.parent = &pdev->dev;
plat_dev.hsmp_device.nodename = HSMP_DEVNODE_NAME;
plat_dev.hsmp_device.mode = 0644;
ret = hsmp_cache_proto_ver();
if (ret) {
dev_err(&pdev->dev, "Failed to read HSMP protocol version\n");
return ret;
}
ret = hsmp_create_sysfs_interface();
if (ret)
dev_err(&pdev->dev, "Failed to create HSMP sysfs interface\n");
return misc_register(&plat_dev.hsmp_device);
}
static void hsmp_pltdrv_remove(struct platform_device *pdev)
{
misc_deregister(&plat_dev.hsmp_device);
}
static struct platform_driver amd_hsmp_driver = {
.probe = hsmp_pltdrv_probe,
.remove_new = hsmp_pltdrv_remove,
.driver = {
.name = DRIVER_NAME,
},
};
static struct platform_device *amd_hsmp_platdev;
static int hsmp_plat_dev_register(void)
{
int ret;
amd_hsmp_platdev = platform_device_alloc(DRIVER_NAME, PLATFORM_DEVID_NONE);
if (!amd_hsmp_platdev)
return -ENOMEM;
ret = platform_device_add(amd_hsmp_platdev);
if (ret)
platform_device_put(amd_hsmp_platdev);
return ret;
}
static int __init hsmp_plt_init(void)
{
int ret = -ENODEV;
if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD || boot_cpu_data.x86 < 0x19) {
pr_err("HSMP is not supported on Family:%x model:%x\n",
boot_cpu_data.x86, boot_cpu_data.x86_model);
return ret;
}
/*
* amd_nb_num() returns number of SMN/DF interfaces present in the system
* if we have N SMN/DF interfaces that ideally means N sockets
*/
plat_dev.num_sockets = amd_nb_num();
if (plat_dev.num_sockets == 0)
return ret;
ret = platform_driver_register(&amd_hsmp_driver);
if (ret)
return ret;
ret = hsmp_plat_dev_register();
if (ret)
platform_driver_unregister(&amd_hsmp_driver);
return ret;
}
static void __exit hsmp_plt_exit(void)
{
platform_device_unregister(amd_hsmp_platdev);
platform_driver_unregister(&amd_hsmp_driver);
}
device_initcall(hsmp_plt_init);
module_exit(hsmp_plt_exit);
MODULE_DESCRIPTION("AMD HSMP Platform Interface Driver");
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL v2");