Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 | // SPDX-License-Identifier: GPL-1.0-or-later /* * AMD Processor P-state Frequency Driver Unit Test * * Copyright (C) 2022 Advanced Micro Devices, Inc. All Rights Reserved. * * Author: Meng Li <li.meng@amd.com> * * The AMD P-State Unit Test is a test module for testing the amd-pstate * driver. 1) It can help all users to verify their processor support * (SBIOS/Firmware or Hardware). 2) Kernel can have a basic function * test to avoid the kernel regression during the update. 3) We can * introduce more functional or performance tests to align the result * together, it will benefit power and performance scale optimization. * * This driver implements basic framework with plans to enhance it with * additional test cases to improve the depth and coverage of the test. * * See Documentation/admin-guide/pm/amd-pstate.rst Unit Tests for * amd-pstate to get more detail. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/kernel.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/fs.h> #include <linux/amd-pstate.h> #include <acpi/cppc_acpi.h> /* * Abbreviations: * amd_pstate_ut: used as a shortform for AMD P-State unit test. * It helps to keep variable names smaller, simpler */ enum amd_pstate_ut_result { AMD_PSTATE_UT_RESULT_PASS, AMD_PSTATE_UT_RESULT_FAIL, }; struct amd_pstate_ut_struct { const char *name; void (*func)(u32 index); enum amd_pstate_ut_result result; }; /* * Kernel module for testing the AMD P-State unit test */ static void amd_pstate_ut_acpi_cpc_valid(u32 index); static void amd_pstate_ut_check_enabled(u32 index); static void amd_pstate_ut_check_perf(u32 index); static void amd_pstate_ut_check_freq(u32 index); static struct amd_pstate_ut_struct amd_pstate_ut_cases[] = { {"amd_pstate_ut_acpi_cpc_valid", amd_pstate_ut_acpi_cpc_valid }, {"amd_pstate_ut_check_enabled", amd_pstate_ut_check_enabled }, {"amd_pstate_ut_check_perf", amd_pstate_ut_check_perf }, {"amd_pstate_ut_check_freq", amd_pstate_ut_check_freq } }; static bool get_shared_mem(void) { bool result = false; if (!boot_cpu_has(X86_FEATURE_CPPC)) result = true; return result; } /* * check the _CPC object is present in SBIOS. */ static void amd_pstate_ut_acpi_cpc_valid(u32 index) { if (acpi_cpc_valid()) amd_pstate_ut_cases[index].result = AMD_PSTATE_UT_RESULT_PASS; else { amd_pstate_ut_cases[index].result = AMD_PSTATE_UT_RESULT_FAIL; pr_err("%s the _CPC object is not present in SBIOS!\n", __func__); } } static void amd_pstate_ut_pstate_enable(u32 index) { int ret = 0; u64 cppc_enable = 0; ret = rdmsrl_safe(MSR_AMD_CPPC_ENABLE, &cppc_enable); if (ret) { amd_pstate_ut_cases[index].result = AMD_PSTATE_UT_RESULT_FAIL; pr_err("%s rdmsrl_safe MSR_AMD_CPPC_ENABLE ret=%d error!\n", __func__, ret); return; } if (cppc_enable) amd_pstate_ut_cases[index].result = AMD_PSTATE_UT_RESULT_PASS; else { amd_pstate_ut_cases[index].result = AMD_PSTATE_UT_RESULT_FAIL; pr_err("%s amd pstate must be enabled!\n", __func__); } } /* * check if amd pstate is enabled */ static void amd_pstate_ut_check_enabled(u32 index) { if (get_shared_mem()) amd_pstate_ut_cases[index].result = AMD_PSTATE_UT_RESULT_PASS; else amd_pstate_ut_pstate_enable(index); } /* * check if performance values are reasonable. * highest_perf >= nominal_perf > lowest_nonlinear_perf > lowest_perf > 0 */ static void amd_pstate_ut_check_perf(u32 index) { int cpu = 0, ret = 0; u32 highest_perf = 0, nominal_perf = 0, lowest_nonlinear_perf = 0, lowest_perf = 0; u64 cap1 = 0; struct cppc_perf_caps cppc_perf; struct cpufreq_policy *policy = NULL; struct amd_cpudata *cpudata = NULL; highest_perf = amd_get_highest_perf(); for_each_possible_cpu(cpu) { policy = cpufreq_cpu_get(cpu); if (!policy) break; cpudata = policy->driver_data; if (get_shared_mem()) { ret = cppc_get_perf_caps(cpu, &cppc_perf); if (ret) { amd_pstate_ut_cases[index].result = AMD_PSTATE_UT_RESULT_FAIL; pr_err("%s cppc_get_perf_caps ret=%d error!\n", __func__, ret); goto skip_test; } nominal_perf = cppc_perf.nominal_perf; lowest_nonlinear_perf = cppc_perf.lowest_nonlinear_perf; lowest_perf = cppc_perf.lowest_perf; } else { ret = rdmsrl_safe_on_cpu(cpu, MSR_AMD_CPPC_CAP1, &cap1); if (ret) { amd_pstate_ut_cases[index].result = AMD_PSTATE_UT_RESULT_FAIL; pr_err("%s read CPPC_CAP1 ret=%d error!\n", __func__, ret); goto skip_test; } nominal_perf = AMD_CPPC_NOMINAL_PERF(cap1); lowest_nonlinear_perf = AMD_CPPC_LOWNONLIN_PERF(cap1); lowest_perf = AMD_CPPC_LOWEST_PERF(cap1); } if ((highest_perf != READ_ONCE(cpudata->highest_perf)) || (nominal_perf != READ_ONCE(cpudata->nominal_perf)) || (lowest_nonlinear_perf != READ_ONCE(cpudata->lowest_nonlinear_perf)) || (lowest_perf != READ_ONCE(cpudata->lowest_perf))) { amd_pstate_ut_cases[index].result = AMD_PSTATE_UT_RESULT_FAIL; pr_err("%s cpu%d highest=%d %d nominal=%d %d lowest_nonlinear=%d %d lowest=%d %d, they should be equal!\n", __func__, cpu, highest_perf, cpudata->highest_perf, nominal_perf, cpudata->nominal_perf, lowest_nonlinear_perf, cpudata->lowest_nonlinear_perf, lowest_perf, cpudata->lowest_perf); goto skip_test; } if (!((highest_perf >= nominal_perf) && (nominal_perf > lowest_nonlinear_perf) && (lowest_nonlinear_perf > lowest_perf) && (lowest_perf > 0))) { amd_pstate_ut_cases[index].result = AMD_PSTATE_UT_RESULT_FAIL; pr_err("%s cpu%d highest=%d >= nominal=%d > lowest_nonlinear=%d > lowest=%d > 0, the formula is incorrect!\n", __func__, cpu, highest_perf, nominal_perf, lowest_nonlinear_perf, lowest_perf); goto skip_test; } cpufreq_cpu_put(policy); } amd_pstate_ut_cases[index].result = AMD_PSTATE_UT_RESULT_PASS; return; skip_test: cpufreq_cpu_put(policy); } /* * Check if frequency values are reasonable. * max_freq >= nominal_freq > lowest_nonlinear_freq > min_freq > 0 * check max freq when set support boost mode. */ static void amd_pstate_ut_check_freq(u32 index) { int cpu = 0; struct cpufreq_policy *policy = NULL; struct amd_cpudata *cpudata = NULL; for_each_possible_cpu(cpu) { policy = cpufreq_cpu_get(cpu); if (!policy) break; cpudata = policy->driver_data; if (!((cpudata->max_freq >= cpudata->nominal_freq) && (cpudata->nominal_freq > cpudata->lowest_nonlinear_freq) && (cpudata->lowest_nonlinear_freq > cpudata->min_freq) && (cpudata->min_freq > 0))) { amd_pstate_ut_cases[index].result = AMD_PSTATE_UT_RESULT_FAIL; pr_err("%s cpu%d max=%d >= nominal=%d > lowest_nonlinear=%d > min=%d > 0, the formula is incorrect!\n", __func__, cpu, cpudata->max_freq, cpudata->nominal_freq, cpudata->lowest_nonlinear_freq, cpudata->min_freq); goto skip_test; } if (cpudata->min_freq != policy->min) { amd_pstate_ut_cases[index].result = AMD_PSTATE_UT_RESULT_FAIL; pr_err("%s cpu%d cpudata_min_freq=%d policy_min=%d, they should be equal!\n", __func__, cpu, cpudata->min_freq, policy->min); goto skip_test; } if (cpudata->boost_supported) { if ((policy->max == cpudata->max_freq) || (policy->max == cpudata->nominal_freq)) amd_pstate_ut_cases[index].result = AMD_PSTATE_UT_RESULT_PASS; else { amd_pstate_ut_cases[index].result = AMD_PSTATE_UT_RESULT_FAIL; pr_err("%s cpu%d policy_max=%d should be equal cpu_max=%d or cpu_nominal=%d !\n", __func__, cpu, policy->max, cpudata->max_freq, cpudata->nominal_freq); goto skip_test; } } else { amd_pstate_ut_cases[index].result = AMD_PSTATE_UT_RESULT_FAIL; pr_err("%s cpu%d must support boost!\n", __func__, cpu); goto skip_test; } cpufreq_cpu_put(policy); } amd_pstate_ut_cases[index].result = AMD_PSTATE_UT_RESULT_PASS; return; skip_test: cpufreq_cpu_put(policy); } static int __init amd_pstate_ut_init(void) { u32 i = 0, arr_size = ARRAY_SIZE(amd_pstate_ut_cases); for (i = 0; i < arr_size; i++) { amd_pstate_ut_cases[i].func(i); switch (amd_pstate_ut_cases[i].result) { case AMD_PSTATE_UT_RESULT_PASS: pr_info("%-4d %-20s\t success!\n", i+1, amd_pstate_ut_cases[i].name); break; case AMD_PSTATE_UT_RESULT_FAIL: default: pr_info("%-4d %-20s\t fail!\n", i+1, amd_pstate_ut_cases[i].name); break; } } return 0; } static void __exit amd_pstate_ut_exit(void) { } module_init(amd_pstate_ut_init); module_exit(amd_pstate_ut_exit); MODULE_AUTHOR("Meng Li <li.meng@amd.com>"); MODULE_DESCRIPTION("AMD P-state driver Test module"); MODULE_LICENSE("GPL"); |