1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Intel Performance and Energy Bias Hint support. 4 * 5 * Copyright (C) 2019 Intel Corporation 6 * 7 * Author: 8 * Rafael J. Wysocki <[email protected]> 9 */ 10 11 #include <linux/cpuhotplug.h> 12 #include <linux/kernel.h> 13 #include <linux/syscore_ops.h> 14 15 #include <asm/cpufeature.h> 16 #include <asm/msr.h> 17 18 /** 19 * DOC: overview 20 * 21 * The Performance and Energy Bias Hint (EPB) allows software to specify its 22 * preference with respect to the power-performance tradeoffs present in the 23 * processor. Generally, the EPB is expected to be set by user space through 24 * the generic MSR interface (with the help of the x86_energy_perf_policy tool), 25 * but there are two reasons for the kernel to touch it. 26 * 27 * First, there are systems where the platform firmware resets the EPB during 28 * system-wide transitions from sleep states back into the working state 29 * effectively causing the previous EPB updates by user space to be lost. 30 * Thus the kernel needs to save the current EPB values for all CPUs during 31 * system-wide transitions to sleep states and restore them on the way back to 32 * the working state. That can be achieved by saving EPB for secondary CPUs 33 * when they are taken offline during transitions into system sleep states and 34 * for the boot CPU in a syscore suspend operation, so that it can be restored 35 * for the boot CPU in a syscore resume operation and for the other CPUs when 36 * they are brought back online. However, CPUs that are already offline when 37 * a system-wide PM transition is started are not taken offline again, but their 38 * EPB values may still be reset by the platform firmware during the transition, 39 * so in fact it is necessary to save the EPB of any CPU taken offline and to 40 * restore it when the given CPU goes back online at all times. 41 * 42 * Second, on many systems the initial EPB value coming from the platform 43 * firmware is 0 ('performance') and at least on some of them that is because 44 * the platform firmware does not initialize EPB at all with the assumption that 45 * the OS will do that anyway. That sometimes is problematic, as it may cause 46 * the system battery to drain too fast, for example, so it is better to adjust 47 * it on CPU bring-up and if the initial EPB value for a given CPU is 0, the 48 * kernel changes it to 6 ('normal'). 49 */ 50 51 static DEFINE_PER_CPU(u8, saved_epb); 52 53 #define EPB_MASK 0x0fULL 54 #define EPB_SAVED 0x10ULL 55 56 static int intel_epb_save(void) 57 { 58 u64 epb; 59 60 rdmsrl(MSR_IA32_ENERGY_PERF_BIAS, epb); 61 /* 62 * Ensure that saved_epb will always be nonzero after this write even if 63 * the EPB value read from the MSR is 0. 64 */ 65 this_cpu_write(saved_epb, (epb & EPB_MASK) | EPB_SAVED); 66 67 return 0; 68 } 69 70 static void intel_epb_restore(void) 71 { 72 u64 val = this_cpu_read(saved_epb); 73 u64 epb; 74 75 rdmsrl(MSR_IA32_ENERGY_PERF_BIAS, epb); 76 if (val) { 77 val &= EPB_MASK; 78 } else { 79 /* 80 * Because intel_epb_save() has not run for the current CPU yet, 81 * it is going online for the first time, so if its EPB value is 82 * 0 ('performance') at this point, assume that it has not been 83 * initialized by the platform firmware and set it to 6 84 * ('normal'). 85 */ 86 val = epb & EPB_MASK; 87 if (val == ENERGY_PERF_BIAS_PERFORMANCE) { 88 val = ENERGY_PERF_BIAS_NORMAL; 89 pr_warn_once("ENERGY_PERF_BIAS: Set to 'normal', was 'performance'\n"); 90 } 91 } 92 wrmsrl(MSR_IA32_ENERGY_PERF_BIAS, (epb & ~EPB_MASK) | val); 93 } 94 95 static struct syscore_ops intel_epb_syscore_ops = { 96 .suspend = intel_epb_save, 97 .resume = intel_epb_restore, 98 }; 99 100 static int intel_epb_online(unsigned int cpu) 101 { 102 intel_epb_restore(); 103 return 0; 104 } 105 106 static int intel_epb_offline(unsigned int cpu) 107 { 108 return intel_epb_save(); 109 } 110 111 static __init int intel_epb_init(void) 112 { 113 int ret; 114 115 if (!boot_cpu_has(X86_FEATURE_EPB)) 116 return -ENODEV; 117 118 ret = cpuhp_setup_state(CPUHP_AP_X86_INTEL_EPB_ONLINE, 119 "x86/intel/epb:online", intel_epb_online, 120 intel_epb_offline); 121 if (ret < 0) 122 goto err_out_online; 123 124 register_syscore_ops(&intel_epb_syscore_ops); 125 return 0; 126 127 err_out_online: 128 cpuhp_remove_state(CPUHP_AP_X86_INTEL_EPB_ONLINE); 129 return ret; 130 } 131 subsys_initcall(intel_epb_init); 132