2020-05-19 18:50:09 +03:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright ( C ) 2020 Advanced Micro Devices , Inc .
*/
# include <asm/cpu_device_id.h>
# include <linux/bits.h>
# include <linux/cpu.h>
# include <linux/cpumask.h>
# include <linux/delay.h>
# include <linux/device.h>
# include <linux/hwmon.h>
# include <linux/kernel.h>
# include <linux/kthread.h>
# include <linux/list.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/processor.h>
# include <linux/platform_device.h>
# include <linux/sched.h>
# include <linux/slab.h>
# include <linux/topology.h>
# include <linux/types.h>
# define DRVNAME "amd_energy"
# define ENERGY_PWR_UNIT_MSR 0xC0010299
# define ENERGY_CORE_MSR 0xC001029A
# define ENERGY_PKG_MSR 0xC001029B
# define AMD_ENERGY_UNIT_MASK 0x01F00
# define AMD_ENERGY_MASK 0xFFFFFFFF
struct sensor_accumulator {
u64 energy_ctr ;
u64 prev_value ;
} ;
struct amd_energy_data {
struct hwmon_channel_info energy_info ;
const struct hwmon_channel_info * info [ 2 ] ;
struct hwmon_chip_info chip ;
struct task_struct * wrap_accumulate ;
/* Lock around the accumulator */
struct mutex lock ;
/* An accumulator for each core and socket */
struct sensor_accumulator * accums ;
2020-09-29 13:53:20 +03:00
unsigned int timeout_ms ;
2020-05-19 18:50:09 +03:00
/* Energy Status Units */
2020-09-29 13:53:20 +03:00
int energy_units ;
2020-05-19 18:50:09 +03:00
int nr_cpus ;
int nr_socks ;
int core_id ;
2020-09-29 13:53:19 +03:00
char ( * label ) [ 10 ] ;
2020-05-19 18:50:09 +03:00
} ;
static int amd_energy_read_labels ( struct device * dev ,
enum hwmon_sensor_types type ,
u32 attr , int channel ,
const char * * str )
{
struct amd_energy_data * data = dev_get_drvdata ( dev ) ;
2020-09-29 13:53:19 +03:00
* str = data - > label [ channel ] ;
2020-05-19 18:50:09 +03:00
return 0 ;
}
static void get_energy_units ( struct amd_energy_data * data )
{
u64 rapl_units ;
rdmsrl_safe ( ENERGY_PWR_UNIT_MSR , & rapl_units ) ;
data - > energy_units = ( rapl_units & AMD_ENERGY_UNIT_MASK ) > > 8 ;
}
2020-09-29 13:53:21 +03:00
static void accumulate_delta ( struct amd_energy_data * data ,
int channel , int cpu , u32 reg )
2020-05-19 18:50:09 +03:00
{
2020-09-29 13:53:21 +03:00
struct sensor_accumulator * accum ;
2020-05-19 18:50:09 +03:00
u64 input ;
mutex_lock ( & data - > lock ) ;
2020-09-29 13:53:21 +03:00
rdmsrl_safe_on_cpu ( cpu , reg , & input ) ;
2020-05-19 18:50:09 +03:00
input & = AMD_ENERGY_MASK ;
2020-09-29 13:53:21 +03:00
accum = & data - > accums [ channel ] ;
if ( input > = accum - > prev_value )
accum - > energy_ctr + =
input - accum - > prev_value ;
2020-05-19 18:50:09 +03:00
else
2020-09-29 13:53:21 +03:00
accum - > energy_ctr + = UINT_MAX -
accum - > prev_value + input ;
2020-05-19 18:50:09 +03:00
2020-09-29 13:53:21 +03:00
accum - > prev_value = input ;
2020-05-19 18:50:09 +03:00
mutex_unlock ( & data - > lock ) ;
}
2020-09-29 13:53:21 +03:00
static void read_accumulate ( struct amd_energy_data * data )
2020-05-19 18:50:09 +03:00
{
2020-09-29 13:53:21 +03:00
int sock , scpu , cpu ;
for ( sock = 0 ; sock < data - > nr_socks ; sock + + ) {
scpu = cpumask_first_and ( cpu_online_mask ,
cpumask_of_node ( sock ) ) ;
accumulate_delta ( data , data - > nr_cpus + sock ,
scpu , ENERGY_PKG_MSR ) ;
}
2020-05-19 18:50:09 +03:00
if ( data - > core_id > = data - > nr_cpus )
data - > core_id = 0 ;
cpu = data - > core_id ;
2020-09-29 13:53:21 +03:00
if ( cpu_online ( cpu ) )
accumulate_delta ( data , cpu , cpu , ENERGY_CORE_MSR ) ;
2020-05-19 18:50:09 +03:00
data - > core_id + + ;
}
static void amd_add_delta ( struct amd_energy_data * data , int ch ,
2020-09-29 13:53:21 +03:00
int cpu , long * val , u32 reg )
2020-05-19 18:50:09 +03:00
{
2020-09-29 13:53:21 +03:00
struct sensor_accumulator * accum ;
2020-05-19 18:50:09 +03:00
u64 input ;
mutex_lock ( & data - > lock ) ;
2020-09-29 13:53:21 +03:00
rdmsrl_safe_on_cpu ( cpu , reg , & input ) ;
input & = AMD_ENERGY_MASK ;
2020-05-19 18:50:09 +03:00
2020-09-29 13:53:21 +03:00
accum = & data - > accums [ ch ] ;
if ( input > = accum - > prev_value )
input + = accum - > energy_ctr -
accum - > prev_value ;
else
input + = UINT_MAX - accum - > prev_value +
accum - > energy_ctr ;
2020-05-19 18:50:09 +03:00
/* Energy consumed = (1/(2^ESU) * RAW * 1000000UL) μJoules */
* val = div64_ul ( input * 1000000UL , BIT ( data - > energy_units ) ) ;
mutex_unlock ( & data - > lock ) ;
}
static int amd_energy_read ( struct device * dev ,
enum hwmon_sensor_types type ,
u32 attr , int channel , long * val )
{
struct amd_energy_data * data = dev_get_drvdata ( dev ) ;
2020-09-29 13:53:21 +03:00
u32 reg ;
2020-05-19 18:50:09 +03:00
int cpu ;
if ( channel > = data - > nr_cpus ) {
cpu = cpumask_first_and ( cpu_online_mask ,
cpumask_of_node
( channel - data - > nr_cpus ) ) ;
2020-09-29 13:53:21 +03:00
reg = ENERGY_PKG_MSR ;
2020-05-19 18:50:09 +03:00
} else {
cpu = channel ;
if ( ! cpu_online ( cpu ) )
return - ENODEV ;
2020-09-29 13:53:21 +03:00
reg = ENERGY_CORE_MSR ;
2020-05-19 18:50:09 +03:00
}
2020-09-29 13:53:21 +03:00
amd_add_delta ( data , channel , cpu , val , reg ) ;
2020-05-19 18:50:09 +03:00
return 0 ;
}
static umode_t amd_energy_is_visible ( const void * _data ,
enum hwmon_sensor_types type ,
u32 attr , int channel )
{
2020-11-12 20:21:59 +03:00
return 0440 ;
2020-05-19 18:50:09 +03:00
}
static int energy_accumulator ( void * p )
{
struct amd_energy_data * data = ( struct amd_energy_data * ) p ;
2020-09-29 13:53:20 +03:00
unsigned int timeout = data - > timeout_ms ;
2020-05-19 18:50:09 +03:00
while ( ! kthread_should_stop ( ) ) {
/*
* Ignoring the conditions such as
* cpu being offline or rdmsr failure
*/
read_accumulate ( data ) ;
set_current_state ( TASK_INTERRUPTIBLE ) ;
if ( kthread_should_stop ( ) )
break ;
2020-09-29 13:53:20 +03:00
schedule_timeout ( msecs_to_jiffies ( timeout ) ) ;
2020-05-19 18:50:09 +03:00
}
return 0 ;
}
static const struct hwmon_ops amd_energy_ops = {
. is_visible = amd_energy_is_visible ,
. read = amd_energy_read ,
. read_string = amd_energy_read_labels ,
} ;
static int amd_create_sensor ( struct device * dev ,
struct amd_energy_data * data ,
2020-09-29 13:53:21 +03:00
enum hwmon_sensor_types type , u32 config )
2020-05-19 18:50:09 +03:00
{
struct hwmon_channel_info * info = & data - > energy_info ;
struct sensor_accumulator * accums ;
int i , num_siblings , cpus , sockets ;
u32 * s_config ;
2020-09-29 13:53:19 +03:00
char ( * label_l ) [ 10 ] ;
2020-05-19 18:50:09 +03:00
/* Identify the number of siblings per core */
num_siblings = ( ( cpuid_ebx ( 0x8000001e ) > > 8 ) & 0xff ) + 1 ;
sockets = num_possible_nodes ( ) ;
/*
* Energy counter register is accessed at core level .
* Hence , filterout the siblings .
*/
cpus = num_present_cpus ( ) / num_siblings ;
s_config = devm_kcalloc ( dev , cpus + sockets ,
sizeof ( u32 ) , GFP_KERNEL ) ;
if ( ! s_config )
return - ENOMEM ;
accums = devm_kcalloc ( dev , cpus + sockets ,
sizeof ( struct sensor_accumulator ) ,
GFP_KERNEL ) ;
if ( ! accums )
return - ENOMEM ;
2020-09-29 13:53:19 +03:00
label_l = devm_kcalloc ( dev , cpus + sockets ,
sizeof ( * label_l ) , GFP_KERNEL ) ;
if ( ! label_l )
return - ENOMEM ;
2020-05-19 18:50:09 +03:00
info - > type = type ;
info - > config = s_config ;
data - > nr_cpus = cpus ;
data - > nr_socks = sockets ;
data - > accums = accums ;
2020-09-29 13:53:19 +03:00
data - > label = label_l ;
2020-05-19 18:50:09 +03:00
for ( i = 0 ; i < cpus + sockets ; i + + ) {
s_config [ i ] = config ;
if ( i < cpus )
2020-09-29 13:53:19 +03:00
scnprintf ( label_l [ i ] , 10 , " Ecore%03u " , i ) ;
2020-05-19 18:50:09 +03:00
else
2020-09-29 13:53:19 +03:00
scnprintf ( label_l [ i ] , 10 , " Esocket%u " , ( i - cpus ) ) ;
2020-05-19 18:50:09 +03:00
}
return 0 ;
}
static int amd_energy_probe ( struct platform_device * pdev )
{
struct device * hwmon_dev ;
struct amd_energy_data * data ;
struct device * dev = & pdev - > dev ;
2020-09-29 13:53:21 +03:00
int ret ;
2020-05-19 18:50:09 +03:00
data = devm_kzalloc ( dev ,
sizeof ( struct amd_energy_data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
data - > chip . ops = & amd_energy_ops ;
data - > chip . info = data - > info ;
dev_set_drvdata ( dev , data ) ;
/* Populate per-core energy reporting */
data - > info [ 0 ] = & data - > energy_info ;
2020-09-29 13:53:21 +03:00
ret = amd_create_sensor ( dev , data , hwmon_energy ,
HWMON_E_INPUT | HWMON_E_LABEL ) ;
if ( ret )
return ret ;
2020-05-19 18:50:09 +03:00
mutex_init ( & data - > lock ) ;
get_energy_units ( data ) ;
hwmon_dev = devm_hwmon_device_register_with_info ( dev , DRVNAME ,
data ,
& data - > chip ,
NULL ) ;
if ( IS_ERR ( hwmon_dev ) )
return PTR_ERR ( hwmon_dev ) ;
2020-09-29 13:53:20 +03:00
/*
* On a system with peak wattage of 250 W
* timeout = 2 ^ 32 / 2 ^ energy_units / 250 secs
*/
data - > timeout_ms = 1000 *
BIT ( min ( 28 , 31 - data - > energy_units ) ) / 250 ;
2020-05-19 18:50:09 +03:00
data - > wrap_accumulate = kthread_run ( energy_accumulator , data ,
" %s " , dev_name ( hwmon_dev ) ) ;
return PTR_ERR_OR_ZERO ( data - > wrap_accumulate ) ;
}
static int amd_energy_remove ( struct platform_device * pdev )
{
struct amd_energy_data * data = dev_get_drvdata ( & pdev - > dev ) ;
if ( data & & data - > wrap_accumulate )
kthread_stop ( data - > wrap_accumulate ) ;
return 0 ;
}
static const struct platform_device_id amd_energy_ids [ ] = {
{ . name = DRVNAME , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( platform , amd_energy_ids ) ;
static struct platform_driver amd_energy_driver = {
. probe = amd_energy_probe ,
. remove = amd_energy_remove ,
. id_table = amd_energy_ids ,
. driver = {
. name = DRVNAME ,
} ,
} ;
static struct platform_device * amd_energy_platdev ;
static const struct x86_cpu_id cpu_ids [ ] __initconst = {
2020-07-06 20:17:15 +03:00
X86_MATCH_VENDOR_FAM_MODEL ( AMD , 0x17 , 0x31 , NULL ) ,
2020-11-19 21:42:45 +03:00
X86_MATCH_VENDOR_FAM_MODEL ( AMD , 0x19 , 0x01 , NULL ) ,
2020-05-19 18:50:09 +03:00
{ }
} ;
MODULE_DEVICE_TABLE ( x86cpu , cpu_ids ) ;
static int __init amd_energy_init ( void )
{
int ret ;
if ( ! x86_match_cpu ( cpu_ids ) )
return - ENODEV ;
ret = platform_driver_register ( & amd_energy_driver ) ;
if ( ret )
return ret ;
amd_energy_platdev = platform_device_alloc ( DRVNAME , 0 ) ;
2020-05-27 05:24:17 +03:00
if ( ! amd_energy_platdev ) {
platform_driver_unregister ( & amd_energy_driver ) ;
2020-05-19 18:50:09 +03:00
return - ENOMEM ;
2020-05-27 05:24:17 +03:00
}
2020-05-19 18:50:09 +03:00
ret = platform_device_add ( amd_energy_platdev ) ;
if ( ret ) {
platform_device_put ( amd_energy_platdev ) ;
platform_driver_unregister ( & amd_energy_driver ) ;
return ret ;
}
return ret ;
}
static void __exit amd_energy_exit ( void )
{
platform_device_unregister ( amd_energy_platdev ) ;
platform_driver_unregister ( & amd_energy_driver ) ;
}
module_init ( amd_energy_init ) ;
module_exit ( amd_energy_exit ) ;
MODULE_DESCRIPTION ( " Driver for AMD Energy reporting from RAPL MSR via HWMON interface " ) ;
MODULE_AUTHOR ( " Naveen Krishna Chatradhi <nchatrad@amd.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;