forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
377 lines
8.4 KiB
377 lines
8.4 KiB
// 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; |
|
unsigned int timeout_ms; |
|
/* Energy Status Units */ |
|
int energy_units; |
|
int nr_cpus; |
|
int nr_socks; |
|
int core_id; |
|
char (*label)[10]; |
|
}; |
|
|
|
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); |
|
|
|
*str = data->label[channel]; |
|
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; |
|
} |
|
|
|
static void accumulate_delta(struct amd_energy_data *data, |
|
int channel, int cpu, u32 reg) |
|
{ |
|
struct sensor_accumulator *accum; |
|
u64 input; |
|
|
|
mutex_lock(&data->lock); |
|
rdmsrl_safe_on_cpu(cpu, reg, &input); |
|
input &= AMD_ENERGY_MASK; |
|
|
|
accum = &data->accums[channel]; |
|
if (input >= accum->prev_value) |
|
accum->energy_ctr += |
|
input - accum->prev_value; |
|
else |
|
accum->energy_ctr += UINT_MAX - |
|
accum->prev_value + input; |
|
|
|
accum->prev_value = input; |
|
mutex_unlock(&data->lock); |
|
} |
|
|
|
static void read_accumulate(struct amd_energy_data *data) |
|
{ |
|
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); |
|
} |
|
|
|
if (data->core_id >= data->nr_cpus) |
|
data->core_id = 0; |
|
|
|
cpu = data->core_id; |
|
if (cpu_online(cpu)) |
|
accumulate_delta(data, cpu, cpu, ENERGY_CORE_MSR); |
|
|
|
data->core_id++; |
|
} |
|
|
|
static void amd_add_delta(struct amd_energy_data *data, int ch, |
|
int cpu, long *val, u32 reg) |
|
{ |
|
struct sensor_accumulator *accum; |
|
u64 input; |
|
|
|
mutex_lock(&data->lock); |
|
rdmsrl_safe_on_cpu(cpu, reg, &input); |
|
input &= AMD_ENERGY_MASK; |
|
|
|
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; |
|
|
|
/* 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); |
|
u32 reg; |
|
int cpu; |
|
|
|
if (channel >= data->nr_cpus) { |
|
cpu = cpumask_first_and(cpu_online_mask, |
|
cpumask_of_node |
|
(channel - data->nr_cpus)); |
|
reg = ENERGY_PKG_MSR; |
|
} else { |
|
cpu = channel; |
|
if (!cpu_online(cpu)) |
|
return -ENODEV; |
|
|
|
reg = ENERGY_CORE_MSR; |
|
} |
|
amd_add_delta(data, channel, cpu, val, reg); |
|
|
|
return 0; |
|
} |
|
|
|
static umode_t amd_energy_is_visible(const void *_data, |
|
enum hwmon_sensor_types type, |
|
u32 attr, int channel) |
|
{ |
|
return 0440; |
|
} |
|
|
|
static int energy_accumulator(void *p) |
|
{ |
|
struct amd_energy_data *data = (struct amd_energy_data *)p; |
|
unsigned int timeout = data->timeout_ms; |
|
|
|
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; |
|
|
|
schedule_timeout(msecs_to_jiffies(timeout)); |
|
} |
|
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, |
|
enum hwmon_sensor_types type, u32 config) |
|
{ |
|
struct hwmon_channel_info *info = &data->energy_info; |
|
struct sensor_accumulator *accums; |
|
int i, num_siblings, cpus, sockets; |
|
u32 *s_config; |
|
char (*label_l)[10]; |
|
|
|
/* 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 + 1, |
|
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; |
|
|
|
label_l = devm_kcalloc(dev, cpus + sockets, |
|
sizeof(*label_l), GFP_KERNEL); |
|
if (!label_l) |
|
return -ENOMEM; |
|
|
|
info->type = type; |
|
info->config = s_config; |
|
|
|
data->nr_cpus = cpus; |
|
data->nr_socks = sockets; |
|
data->accums = accums; |
|
data->label = label_l; |
|
|
|
for (i = 0; i < cpus + sockets; i++) { |
|
s_config[i] = config; |
|
if (i < cpus) |
|
scnprintf(label_l[i], 10, "Ecore%03u", i); |
|
else |
|
scnprintf(label_l[i], 10, "Esocket%u", (i - cpus)); |
|
} |
|
|
|
s_config[i] = 0; |
|
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; |
|
int ret; |
|
|
|
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; |
|
ret = amd_create_sensor(dev, data, hwmon_energy, |
|
HWMON_E_INPUT | HWMON_E_LABEL); |
|
if (ret) |
|
return ret; |
|
|
|
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); |
|
|
|
/* |
|
* On a system with peak wattage of 250W |
|
* timeout = 2 ^ 32 / 2 ^ energy_units / 250 secs |
|
*/ |
|
data->timeout_ms = 1000 * |
|
BIT(min(28, 31 - data->energy_units)) / 250; |
|
|
|
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 = { |
|
X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x31, NULL), |
|
{} |
|
}; |
|
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); |
|
if (!amd_energy_platdev) { |
|
platform_driver_unregister(&amd_energy_driver); |
|
return -ENOMEM; |
|
} |
|
|
|
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 <[email protected]>"); |
|
MODULE_LICENSE("GPL");
|
|
|