2022-02-22 10:35:00 +05:30
// 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"
2023-10-10 12:03:09 +00:00
# define DRIVER_VERSION "2.0"
2022-02-22 10:35:00 +05:30
/* 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_MSG_ID 0x3B10534
# define SMN_HSMP_MSG_RESP 0x3B10980
# define SMN_HSMP_MSG_DATA 0x3B109E0
# define HSMP_INDEX_REG 0xc4
# define HSMP_DATA_REG 0xc8
2023-10-10 12:03:08 +00:00
# define HSMP_CDEV_NAME "hsmp_cdev"
# define HSMP_DEVNODE_NAME "hsmp"
2023-10-10 12:03:09 +00:00
# define HSMP_METRICS_TABLE_NAME "metrics_bin"
# define HSMP_ATTR_GRP_NAME_SIZE 10
2022-02-22 10:35:00 +05:30
2023-10-10 12:03:08 +00:00
struct hsmp_socket {
2023-10-10 12:03:09 +00:00
struct bin_attribute hsmp_attr ;
void __iomem * metric_tbl_addr ;
2023-10-10 12:03:08 +00:00
struct semaphore hsmp_sem ;
2023-10-10 12:03:09 +00:00
char name [ HSMP_ATTR_GRP_NAME_SIZE ] ;
2023-10-10 12:03:08 +00:00
u16 sock_ind ;
} ;
struct hsmp_plat_device {
struct miscdevice hsmp_device ;
struct hsmp_socket * sock ;
struct device * dev ;
2023-10-10 12:03:09 +00:00
u32 proto_ver ;
2023-10-10 12:03:08 +00:00
u16 num_sockets ;
} ;
static struct hsmp_plat_device plat_dev ;
2022-02-22 10:35:00 +05:30
static int amd_hsmp_rdwr ( struct pci_dev * root , u32 address ,
u32 * value , bool write )
{
int ret ;
ret = pci_write_config_dword ( root , HSMP_INDEX_REG , address ) ;
if ( ret )
return ret ;
ret = ( write ? pci_write_config_dword ( root , HSMP_DATA_REG , * value )
: pci_read_config_dword ( 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 pci_dev * root , struct hsmp_message * msg )
{
unsigned long timeout , short_sleep ;
u32 mbox_status ;
u32 index ;
int ret ;
/* Clear the status register */
mbox_status = HSMP_STATUS_NOT_READY ;
ret = amd_hsmp_rdwr ( root , SMN_HSMP_MSG_RESP , & 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 ( root , SMN_HSMP_MSG_DATA + ( 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 ( root , SMN_HSMP_MSG_ID , & 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 ( root , SMN_HSMP_MSG_RESP , & 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 ( root , SMN_HSMP_MSG_DATA + ( 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 )
{
2023-10-10 12:03:08 +00:00
struct hsmp_socket * sock = & plat_dev . sock [ msg - > sock_ind ] ;
2022-02-22 10:35:00 +05:30
struct amd_northbridge * nb ;
int ret ;
if ( ! msg )
return - EINVAL ;
nb = node_to_amd_nb ( msg - > sock_ind ) ;
if ( ! nb | | ! nb - > root )
return - ENODEV ;
ret = validate_message ( msg ) ;
if ( ret )
return ret ;
/*
* The time taken by smu operation to complete is between
* 10u s to 1 ms . Sometime it may take more time .
* In SMP system timeout of 100 millisecs should
* be enough for the previous thread to finish the operation
*/
2023-10-10 12:03:08 +00:00
ret = down_timeout ( & sock - > hsmp_sem , msecs_to_jiffies ( HSMP_MSG_TIMEOUT ) ) ;
2022-02-22 10:35:00 +05:30
if ( ret < 0 )
return ret ;
ret = __hsmp_send_message ( nb - > root , msg ) ;
2023-10-10 12:03:08 +00:00
up ( & sock - > hsmp_sem ) ;
2022-02-22 10:35:00 +05:30
return ret ;
}
EXPORT_SYMBOL_GPL ( hsmp_send_message ) ;
static int hsmp_test ( u16 sock_ind , u32 value )
{
struct hsmp_message msg = { 0 } ;
struct amd_northbridge * nb ;
int ret = - ENODEV ;
nb = node_to_amd_nb ( sock_ind ) ;
if ( ! nb | | ! nb - > root )
return 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 ( nb - > root , & msg ) ;
if ( ret )
return ret ;
/* Check the response value */
if ( msg . args [ 0 ] ! = ( value + 1 ) ) {
pr_err ( " 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 ,
} ;
2023-10-10 12:03:09 +00:00
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 ;
/* Do not support lseek(), reads entire metric table */
if ( count < bin_attr - > size ) {
dev_err ( plat_dev . dev , " Wrong buffer size \n " ) ;
return - EINVAL ;
}
if ( ! sock ) {
dev_err ( plat_dev . dev , " Failed to read attribute private data \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 ;
2023-10-19 15:51:22 +02:00
memcpy_fromio ( buf , sock - > metric_tbl_addr , bin_attr - > size ) ;
2023-10-10 12:03:09 +00:00
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 ( plat_dev . dev , " Invalid DRAM address for metric table \n " ) ;
return - ENOMEM ;
}
sock - > metric_tbl_addr = devm_ioremap ( plat_dev . dev , dram_addr ,
sizeof ( struct hsmp_metric_table ) ) ;
if ( ! sock - > metric_tbl_addr ) {
dev_err ( plat_dev . 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 . 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 . 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 . 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 . 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 ;
}
2022-02-22 10:35:00 +05:30
static int hsmp_pltdrv_probe ( struct platform_device * pdev )
{
2023-10-10 12:03:09 +00:00
int ret , i ;
2022-02-22 10:35:00 +05:30
2023-10-10 12:03:08 +00:00
plat_dev . sock = devm_kzalloc ( & pdev - > dev ,
( plat_dev . num_sockets * sizeof ( struct hsmp_socket ) ) ,
GFP_KERNEL ) ;
if ( ! plat_dev . sock )
2022-02-22 10:35:00 +05:30
return - ENOMEM ;
2023-10-10 12:03:08 +00:00
plat_dev . dev = & pdev - > dev ;
2022-02-22 10:35:00 +05:30
2023-10-10 12:03:08 +00:00
for ( i = 0 ; i < plat_dev . num_sockets ; i + + ) {
sema_init ( & plat_dev . sock [ i ] . hsmp_sem , 1 ) ;
plat_dev . sock [ i ] . sock_ind = i ;
}
2022-02-22 10:35:00 +05:30
2023-10-10 12:03:08 +00:00
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 ;
2022-02-22 10:35:00 +05:30
2023-10-10 12:03:09 +00:00
ret = hsmp_cache_proto_ver ( ) ;
if ( ret ) {
dev_err ( plat_dev . dev , " Failed to read HSMP protocol version \n " ) ;
return ret ;
}
ret = hsmp_create_sysfs_interface ( ) ;
if ( ret )
dev_err ( plat_dev . dev , " Failed to create HSMP sysfs interface \n " ) ;
2023-10-10 12:03:08 +00:00
return misc_register ( & plat_dev . hsmp_device ) ;
2022-02-22 10:35:00 +05:30
}
2023-03-02 15:47:06 +01:00
static void hsmp_pltdrv_remove ( struct platform_device * pdev )
2022-02-22 10:35:00 +05:30
{
2023-10-10 12:03:08 +00:00
misc_deregister ( & plat_dev . hsmp_device ) ;
2022-02-22 10:35:00 +05:30
}
static struct platform_driver amd_hsmp_driver = {
. probe = hsmp_pltdrv_probe ,
2023-03-02 15:47:06 +01:00
. remove_new = hsmp_pltdrv_remove ,
2022-02-22 10:35:00 +05:30
. driver = {
. name = DRIVER_NAME ,
} ,
} ;
static struct platform_device * amd_hsmp_platdev ;
static int __init hsmp_plt_init ( void )
{
int ret = - ENODEV ;
int i ;
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
*/
2023-10-10 12:03:08 +00:00
plat_dev . num_sockets = amd_nb_num ( ) ;
if ( plat_dev . num_sockets = = 0 )
2022-02-22 10:35:00 +05:30
return ret ;
/* Test the hsmp interface on each socket */
2023-10-10 12:03:08 +00:00
for ( i = 0 ; i < plat_dev . num_sockets ; i + + ) {
2022-02-22 10:35:00 +05:30
ret = hsmp_test ( i , 0xDEADBEEF ) ;
if ( ret ) {
2023-10-10 12:03:10 +00:00
pr_err ( " HSMP test message failed on Fam:%x model:%x \n " ,
2022-02-22 10:35:00 +05:30
boot_cpu_data . x86 , boot_cpu_data . x86_model ) ;
2023-10-10 12:03:10 +00:00
pr_err ( " Is HSMP disabled in BIOS ? \n " ) ;
return ret ;
2022-02-22 10:35:00 +05:30
}
}
ret = platform_driver_register ( & amd_hsmp_driver ) ;
if ( ret )
return ret ;
2022-09-30 10:59:14 +00:00
amd_hsmp_platdev = platform_device_alloc ( DRIVER_NAME , PLATFORM_DEVID_NONE ) ;
2022-02-22 10:35:00 +05:30
if ( ! amd_hsmp_platdev ) {
ret = - ENOMEM ;
goto drv_unregister ;
}
ret = platform_device_add ( amd_hsmp_platdev ) ;
if ( ret ) {
platform_device_put ( amd_hsmp_platdev ) ;
goto drv_unregister ;
}
return 0 ;
drv_unregister :
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 " ) ;