244 lines · 6.9 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * Intel Performance and Energy Bias Hint support. * * Copyright (C) 2019 Intel Corporation * * Author: *	Rafael J. Wysocki <rafael.j.wysocki@intel.com> */ #include <linux/cpuhotplug.h>#include <linux/cpu.h>#include <linux/device.h>#include <linux/kernel.h>#include <linux/string.h>#include <linux/syscore_ops.h>#include <linux/pm.h> #include <asm/cpu_device_id.h>#include <asm/cpufeature.h>#include <asm/msr.h> /** * DOC: overview * * The Performance and Energy Bias Hint (EPB) allows software to specify its * preference with respect to the power-performance tradeoffs present in the * processor.  Generally, the EPB is expected to be set by user space (directly * via sysfs or with the help of the x86_energy_perf_policy tool), but there are * two reasons for the kernel to update it. * * First, there are systems where the platform firmware resets the EPB during * system-wide transitions from sleep states back into the working state * effectively causing the previous EPB updates by user space to be lost. * Thus the kernel needs to save the current EPB values for all CPUs during * system-wide transitions to sleep states and restore them on the way back to * the working state.  That can be achieved by saving EPB for secondary CPUs * when they are taken offline during transitions into system sleep states and * for the boot CPU in a syscore suspend operation, so that it can be restored * for the boot CPU in a syscore resume operation and for the other CPUs when * they are brought back online.  However, CPUs that are already offline when * a system-wide PM transition is started are not taken offline again, but their * EPB values may still be reset by the platform firmware during the transition, * so in fact it is necessary to save the EPB of any CPU taken offline and to * restore it when the given CPU goes back online at all times. * * Second, on many systems the initial EPB value coming from the platform * firmware is 0 ('performance') and at least on some of them that is because * the platform firmware does not initialize EPB at all with the assumption that * the OS will do that anyway.  That sometimes is problematic, as it may cause * the system battery to drain too fast, for example, so it is better to adjust * it on CPU bring-up and if the initial EPB value for a given CPU is 0, the * kernel changes it to 6 ('normal'). */ static DEFINE_PER_CPU(u8, saved_epb); #define EPB_MASK	0x0fULL#define EPB_SAVED	0x10ULL#define MAX_EPB		EPB_MASK enum energy_perf_value_index {	EPB_INDEX_PERFORMANCE,	EPB_INDEX_BALANCE_PERFORMANCE,	EPB_INDEX_NORMAL,	EPB_INDEX_BALANCE_POWERSAVE,	EPB_INDEX_POWERSAVE,}; static u8 energ_perf_values[] = {	[EPB_INDEX_PERFORMANCE] = ENERGY_PERF_BIAS_PERFORMANCE,	[EPB_INDEX_BALANCE_PERFORMANCE] = ENERGY_PERF_BIAS_BALANCE_PERFORMANCE,	[EPB_INDEX_NORMAL] = ENERGY_PERF_BIAS_NORMAL,	[EPB_INDEX_BALANCE_POWERSAVE] = ENERGY_PERF_BIAS_BALANCE_POWERSAVE,	[EPB_INDEX_POWERSAVE] = ENERGY_PERF_BIAS_POWERSAVE,}; static int intel_epb_save(void *data){	u64 epb; 	rdmsrq(MSR_IA32_ENERGY_PERF_BIAS, epb);	/*	 * Ensure that saved_epb will always be nonzero after this write even if	 * the EPB value read from the MSR is 0.	 */	this_cpu_write(saved_epb, (epb & EPB_MASK) | EPB_SAVED); 	return 0;} static void intel_epb_restore(void *data){	u64 val = this_cpu_read(saved_epb);	u64 epb; 	rdmsrq(MSR_IA32_ENERGY_PERF_BIAS, epb);	if (val) {		val &= EPB_MASK;	} else {		/*		 * Because intel_epb_save() has not run for the current CPU yet,		 * it is going online for the first time, so if its EPB value is		 * 0 ('performance') at this point, assume that it has not been		 * initialized by the platform firmware and set it to 6		 * ('normal').		 */		val = epb & EPB_MASK;		if (val == ENERGY_PERF_BIAS_PERFORMANCE) {			val = energ_perf_values[EPB_INDEX_NORMAL];			pr_warn_once("ENERGY_PERF_BIAS: Set to 'normal', was 'performance'\n");		}	}	wrmsrq(MSR_IA32_ENERGY_PERF_BIAS, (epb & ~EPB_MASK) | val);} static const struct syscore_ops intel_epb_syscore_ops = {	.suspend = intel_epb_save,	.resume = intel_epb_restore,}; static struct syscore intel_epb_syscore = {	.ops = &intel_epb_syscore_ops,}; static const char * const energy_perf_strings[] = {	[EPB_INDEX_PERFORMANCE] = "performance",	[EPB_INDEX_BALANCE_PERFORMANCE] = "balance-performance",	[EPB_INDEX_NORMAL] = "normal",	[EPB_INDEX_BALANCE_POWERSAVE] = "balance-power",	[EPB_INDEX_POWERSAVE] = "power",}; static ssize_t energy_perf_bias_show(struct device *dev,				     struct device_attribute *attr,				     char *buf){	unsigned int cpu = dev->id;	u64 epb;	int ret; 	ret = rdmsrq_on_cpu(cpu, MSR_IA32_ENERGY_PERF_BIAS, &epb);	if (ret < 0)		return ret; 	return sprintf(buf, "%llu\n", epb);} static ssize_t energy_perf_bias_store(struct device *dev,				      struct device_attribute *attr,				      const char *buf, size_t count){	unsigned int cpu = dev->id;	u64 epb, val;	int ret; 	ret = __sysfs_match_string(energy_perf_strings,				   ARRAY_SIZE(energy_perf_strings), buf);	if (ret >= 0)		val = energ_perf_values[ret];	else if (kstrtou64(buf, 0, &val) || val > MAX_EPB)		return -EINVAL; 	ret = rdmsrq_on_cpu(cpu, MSR_IA32_ENERGY_PERF_BIAS, &epb);	if (ret < 0)		return ret; 	ret = wrmsrq_on_cpu(cpu, MSR_IA32_ENERGY_PERF_BIAS,			    (epb & ~EPB_MASK) | val);	if (ret < 0)		return ret; 	return count;} static DEVICE_ATTR_RW(energy_perf_bias); static struct attribute *intel_epb_attrs[] = {	&dev_attr_energy_perf_bias.attr,	NULL}; static const struct attribute_group intel_epb_attr_group = {	.name = power_group_name,	.attrs =  intel_epb_attrs}; static int intel_epb_online(unsigned int cpu){	struct device *cpu_dev = get_cpu_device(cpu); 	intel_epb_restore(NULL);	if (!cpuhp_tasks_frozen)		sysfs_merge_group(&cpu_dev->kobj, &intel_epb_attr_group); 	return 0;} static int intel_epb_offline(unsigned int cpu){	struct device *cpu_dev = get_cpu_device(cpu); 	if (!cpuhp_tasks_frozen)		sysfs_unmerge_group(&cpu_dev->kobj, &intel_epb_attr_group); 	intel_epb_save(NULL);	return 0;} static const struct x86_cpu_id intel_epb_normal[] = {	X86_MATCH_VFM(INTEL_ALDERLAKE_L,		      ENERGY_PERF_BIAS_NORMAL_POWERSAVE),	X86_MATCH_VFM(INTEL_ATOM_GRACEMONT,		      ENERGY_PERF_BIAS_NORMAL_POWERSAVE),	X86_MATCH_VFM(INTEL_RAPTORLAKE_P,		      ENERGY_PERF_BIAS_NORMAL_POWERSAVE),	{}}; static __init int intel_epb_init(void){	const struct x86_cpu_id *id = x86_match_cpu(intel_epb_normal);	int ret; 	if (!boot_cpu_has(X86_FEATURE_EPB))		return -ENODEV; 	if (id)		energ_perf_values[EPB_INDEX_NORMAL] = id->driver_data; 	ret = cpuhp_setup_state(CPUHP_AP_X86_INTEL_EPB_ONLINE,				"x86/intel/epb:online", intel_epb_online,				intel_epb_offline);	if (ret < 0)		goto err_out_online; 	register_syscore(&intel_epb_syscore);	return 0; err_out_online:	cpuhp_remove_state(CPUHP_AP_X86_INTEL_EPB_ONLINE);	return ret;}late_initcall(intel_epb_init);

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t