├── Kbuild ├── dkms.conf ├── Makefile ├── README.md └── amd_energy.c /Kbuild: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | # 3 | # Kbuild for AMD Energy driver 4 | # 5 | # Copyright (C) 2021 Advanced Micro Devices, Inc. 6 | # 7 | 8 | obj-m := amd_energy.o 9 | -------------------------------------------------------------------------------- /dkms.conf: -------------------------------------------------------------------------------- 1 | PACKAGE_NAME="amd_energy" 2 | PACKAGE_VERSION="1.0" 3 | BUILT_MODULE_NAME="amd_energy" 4 | DEST_MODULE_LOCATION="/updates/dkms" 5 | REMAKE_INITRD="yes" 6 | AUTOINSTALL="yes" 7 | MAKE="make default KVER=${kernelver}" 8 | CLEAN="make clean" 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | # 3 | # Makefile for AMD Energy driver 4 | # 5 | # Copyright (C) 2021 Advanced Micro Devices, Inc. 6 | # 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License version 2 9 | 10 | # If KDIR is not specified, assume the development source link 11 | # is in the modules directory for the running kernel 12 | KDIR ?= /lib/modules/`uname -r`/build 13 | 14 | default: 15 | export CONFIG_SENSOR_AMD_ENERGY=m; \ 16 | $(MAKE) -C $(KDIR) M=$$PWD modules 17 | 18 | modules: default 19 | 20 | modules_install: 21 | $(MAKE) -C $(KDIR) M=$$PWD modules_install 22 | 23 | clean: 24 | $(MAKE) -C $(KDIR) M=$$PWD clean 25 | 26 | help: 27 | @echo "\nThe following make targets are supported:\n" 28 | @echo "default\t\tBuild the driver module (or if no make target is supplied)" 29 | @echo "modules\t\tSame as default" 30 | @echo "modules_install\tBuild and install the driver module" 31 | @echo "clean" 32 | @echo 33 | 34 | .PHONY: default modules modules_install clean help 35 | 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | .. SPDX-License-Identifier: GPL-2.0 2 | 3 | Kernel driver amd_energy 4 | ========================== 5 | 6 | Supported chips: 7 | 8 | * AMD Family 17h Processors: Model 30h 9 | 10 | * AMD Family 19h Processors: Model 00h, 01h, 10h, 11h, 30h, 90h, and A0h 11 | 12 | * AMD Family 1Ah Processors: Model 00h, 02h, 11h, 10h 13 | 14 | Prefix: 'amd_energy' 15 | 16 | Addresses used: RAPL MSRs 17 | 18 | Datasheets: 19 | 20 | - Processor Programming Reference (PPR) for AMD Family 17h Model 01h, Revision B1 Processors 21 | 22 | https://developer.amd.com/wp-content/resources/55570-B1_PUB.zip 23 | 24 | - Preliminary Processor Programming Reference (PPR) for AMD Family 17h Model 31h, Revision B0 Processors 25 | 26 | https://developer.amd.com/wp-content/resources/56176_ppr_Family_17h_Model_71h_B0_pub_Rev_3.06.zip 27 | 28 | - Preliminary Processor Programming Reference (PPR) for AMD Family 19h Model 1h, Revision B1 Processors 29 | 30 | https://www.amd.com/system/files/TechDocs/55898_pub.zip 31 | 32 | Author: Naveen Krishna Chatradhi 33 | 34 | Security: CVE-2020-12912 35 | ------------------------------ 36 | 37 | Refer 2020 tab in https://www.amd.com/en/corporate/product-security#paragraph-313561 for details 38 | 39 | Description 40 | ----------- 41 | 42 | The Energy driver exposes the energy counters that are 43 | reported via the Running Average Power Limit (RAPL) 44 | Model-specific Registers (MSRs) via the hardware monitor 45 | (HWMON) sysfs interface. 46 | 47 | 1. Power, Energy and Time Units 48 | MSR_RAPL_POWER_UNIT/ C001_0299: 49 | shared with all cores in the socket 50 | 51 | 2. Energy consumed by each Core 52 | MSR_CORE_ENERGY_STATUS/ C001_029A: 53 | 32-bitRO, Accumulator, core-level power reporting 54 | 55 | 3. Energy consumed by Socket 56 | MSR_PACKAGE_ENERGY_STATUS/ C001_029B: 57 | 32-bitRO, Accumulator, socket-level power reporting, 58 | shared with all cores in socket 59 | 60 | These registers are updated every 1ms and cleared on 61 | reset of the system. 62 | 63 | Note: If SMT is enabled, Linux enumerates all threads as cpus. 64 | Since, the energy status registers are accessed at core level, 65 | reading those registers from the sibling threads would result 66 | in duplicate values. Hence, energy counter entries are not 67 | populated for the siblings. 68 | 69 | Energy Calculation 70 | ------------------ 71 | 72 | Energy information (in Joules) is based on the multiplier, 73 | 1/2^ESU; where ESU is an unsigned integer read from 74 | MSR_RAPL_POWER_UNIT register. Default value is 10000b, 75 | indicating energy status unit is 15.3 micro-Joules increment. 76 | 77 | Reported values are scaled as per the formula 78 | 79 | scaled value = ((1/2^ESU) * (Raw value) * 1000000UL) in uJoules 80 | 81 | Users calculate power for a given domain by calculating 82 | dEnergy/dTime for that domain. 83 | 84 | Energy accumulation 85 | -------------------------- 86 | 87 | Current, Socket energy status register is 32bit, assuming a 240W 88 | 2P system, the register would wrap around in 89 | 90 | 2^32*15.3 e-6/240 * 2 = 547.60833024 secs to wrap(~9 mins) 91 | 92 | The Core energy register may wrap around after several days. 93 | 94 | To improve the wrap around time, a kernel thread is implemented 95 | to accumulate the socket energy counters and one core energy counter 96 | per run to a respective 64-bit counter. The kernel thread starts 97 | running during probe, wakes up every 100 secs and stops running 98 | when driver is removed. 99 | 100 | Frequency of the accumulator thread is set during the probe 101 | based on the chosen energy unit resolution. For example 102 | A. fine grain (1.625 micro J) 103 | B. course grain (0.125 milli J) 104 | 105 | A socket and core energy read would return the current register 106 | value added to the respective energy accumulator. 107 | 108 | On newer EPYC CPUs with 64bit RAPL energy MSRs, software accumulation 109 | of energy counters is not required. Hence, accumulation is enabled 110 | only on select EPYC CPUs with 32bit RAPL MSRs. 111 | 112 | Sysfs attributes 113 | ---------------- 114 | 115 | =============== ======== ===================================== 116 | Attribute Label Description 117 | =============== ======== ===================================== 118 | 119 | * For index N between [1] and [nr_cpus] 120 | 121 | =============== ======== ====================================== 122 | energy[N]_input EcoreX Core Energy X = [0] to [nr_cpus - 1] 123 | Measured input core energy 124 | =============== ======== ====================================== 125 | 126 | * For N between [nr_cpus] and [nr_cpus + nr_socks] 127 | 128 | =============== ======== ====================================== 129 | energy[N]_input EsocketX Socket Energy X = [0] to [nr_socks -1] 130 | Measured input socket energy 131 | =============== ======== ====================================== 132 | 133 | Note: To address CVE-2020-12912, the visibility of the energy[N]_input 134 | attributes is restricted to owner and groups only. 135 | 136 | Build and Install 137 | ----------------- 138 | 139 | Kernel development packages for the running kernel need to be installed 140 | prior to building the Energy module. A Makefile is provided which should 141 | work with most kernel source trees. 142 | 143 | To build the kernel module: 144 | 145 | #> make 146 | 147 | To install the kernel module: 148 | 149 | #> sudo make modules_install 150 | 151 | To clean the kernel module build directory: 152 | 153 | #> make clean 154 | 155 | 156 | Loading 157 | ------- 158 | 159 | If the Energy module was installed you should use the modprobe command to 160 | load the module. 161 | 162 | #> sudo modprobe amd_energy 163 | 164 | The Energy module can also be loaded using insmod if the module was not 165 | installed: 166 | 167 | The Energy module can also be loaded using insmod if the module was not 168 | installed: 169 | 170 | #> sudo insmod ./amd_energy.ko 171 | 172 | 173 | DKMS support 174 | ------------ 175 | 176 | Building Module with running version of kernel 177 | 178 | Add the module to DKMS tree: 179 | #> sudo dkms add ../amd_energy 180 | 181 | Build the module using DKMS: 182 | #> sudo dkms build -m amd_energy/1.0 183 | 184 | Install the module using DKMS: 185 | #> sudo dkms install --force amd_energy/1.0 186 | 187 | Load the module: 188 | #> sudo modprobe amd_energy 189 | 190 | Building Module with specific version of kernel 191 | 192 | Add the module to DKMS tree: 193 | #> sudo dkms add ../amd_energy 194 | 195 | Build the module using DKMS: 196 | #> sudo dkms build amd_energy/1.0 -k linux_version 197 | 198 | Install the module using DKMS: 199 | #> sudo dkms install --force amd_energy/1.0 -k linux_version 200 | Module is built: /lib/modules/linux_version/updates/dkms/ 201 | 202 | Notes: It is required to have specific linux version header in /usr/src 203 | 204 | To remove module from dkms tree 205 | #> sudo dkms remove -m amd_energy/1.0 --all 206 | #> sudo rm -rf /usr/src/amd_energy-1.0/ 207 | -------------------------------------------------------------------------------- /amd_energy.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | 3 | /* 4 | * Copyright (C) 2020 Advanced Micro Devices, Inc. 5 | */ 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | #define DRV_MODULE_DESCRIPTION "AMD energy driver" 29 | #define DRV_MODULE_VERSION "1.0" 30 | 31 | MODULE_VERSION(DRV_MODULE_VERSION); 32 | 33 | #define DRVNAME "amd_energy" 34 | 35 | #define ENERGY_PWR_UNIT_MSR 0xC0010299 36 | #define ENERGY_CORE_MSR 0xC001029A 37 | #define ENERGY_PKG_MSR 0xC001029B 38 | 39 | #define AMD_ENERGY_UNIT_MASK 0x01F00 40 | #define AMD_ENERGY_MASK 0xFFFFFFFF 41 | 42 | struct sensor_accumulator { 43 | u64 energy_ctr; 44 | u64 prev_value; 45 | }; 46 | 47 | struct amd_energy_data { 48 | struct hwmon_channel_info energy_info; 49 | const struct hwmon_channel_info *info[2]; 50 | struct hwmon_chip_info chip; 51 | struct task_struct *wrap_accumulate; 52 | /* Lock around the accumulator */ 53 | struct mutex lock; 54 | /* An accumulator for each core and socket */ 55 | struct sensor_accumulator *accums; 56 | unsigned int timeout_ms; 57 | /* Energy Status Units */ 58 | int energy_units; 59 | int nr_cpus; 60 | int nr_socks; 61 | int core_id; 62 | char (*label)[10]; 63 | bool do_not_accum; 64 | }; 65 | 66 | static int amd_energy_read_labels(struct device *dev, 67 | enum hwmon_sensor_types type, 68 | u32 attr, int channel, 69 | const char **str) 70 | { 71 | struct amd_energy_data *data = dev_get_drvdata(dev); 72 | 73 | *str = data->label[channel]; 74 | return 0; 75 | } 76 | 77 | static void get_energy_units(struct amd_energy_data *data) 78 | { 79 | u64 rapl_units; 80 | 81 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 16, 0) 82 | rdmsrl_safe(ENERGY_PWR_UNIT_MSR, &rapl_units); 83 | #else 84 | rdmsrq_safe(ENERGY_PWR_UNIT_MSR, &rapl_units); 85 | #endif 86 | data->energy_units = (rapl_units & AMD_ENERGY_UNIT_MASK) >> 8; 87 | } 88 | 89 | static void accumulate_delta(struct amd_energy_data *data, 90 | int channel, int cpu, u32 reg) 91 | { 92 | struct sensor_accumulator *accum; 93 | u64 input; 94 | 95 | mutex_lock(&data->lock); 96 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 16, 0) 97 | rdmsrl_safe_on_cpu(cpu, reg, &input); 98 | #else 99 | rdmsrq_safe_on_cpu(cpu, reg, &input); 100 | #endif 101 | input &= AMD_ENERGY_MASK; 102 | 103 | accum = &data->accums[channel]; 104 | if (input >= accum->prev_value) 105 | accum->energy_ctr += 106 | input - accum->prev_value; 107 | else 108 | accum->energy_ctr += UINT_MAX - 109 | accum->prev_value + input; 110 | 111 | accum->prev_value = input; 112 | mutex_unlock(&data->lock); 113 | } 114 | 115 | static void read_accumulate(struct amd_energy_data *data) 116 | { 117 | int sock, scpu, cpu; 118 | 119 | for (sock = 0; sock < data->nr_socks; sock++) { 120 | scpu = cpumask_first_and(cpu_online_mask, 121 | topology_die_cpumask 122 | ((data->nr_cpus/data->nr_socks) * sock )); 123 | 124 | accumulate_delta(data, data->nr_cpus + sock, 125 | scpu, ENERGY_PKG_MSR); 126 | } 127 | 128 | if (data->core_id >= data->nr_cpus) 129 | data->core_id = 0; 130 | 131 | cpu = data->core_id; 132 | if (cpu_online(cpu)) 133 | accumulate_delta(data, cpu, cpu, ENERGY_CORE_MSR); 134 | 135 | data->core_id++; 136 | } 137 | 138 | static void amd_add_delta(struct amd_energy_data *data, int ch, 139 | int cpu, long *val, u32 reg) 140 | { 141 | struct sensor_accumulator *accum; 142 | u64 input; 143 | 144 | mutex_lock(&data->lock); 145 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 16, 0) 146 | rdmsrl_safe_on_cpu(cpu, reg, &input); 147 | #else 148 | rdmsrq_safe_on_cpu(cpu, reg, &input); 149 | #endif 150 | 151 | if (!data->do_not_accum) { 152 | input &= AMD_ENERGY_MASK; 153 | accum = &data->accums[ch]; 154 | if (input >= accum->prev_value) 155 | input += accum->energy_ctr - 156 | accum->prev_value; 157 | else 158 | input += UINT_MAX - accum->prev_value + 159 | accum->energy_ctr; 160 | } 161 | 162 | /* Energy consumed = (1/(2^ESU) * RAW * 1000000UL) μJoules */ 163 | *val = div64_ul(input * 1000000UL, BIT(data->energy_units)); 164 | 165 | mutex_unlock(&data->lock); 166 | } 167 | 168 | static int amd_energy_read(struct device *dev, 169 | enum hwmon_sensor_types type, 170 | u32 attr, int channel, long *val) 171 | { 172 | struct amd_energy_data *data = dev_get_drvdata(dev); 173 | u32 reg; 174 | int cpu; 175 | 176 | if (channel >= data->nr_cpus) { 177 | cpu = cpumask_first_and(cpu_online_mask, 178 | topology_die_cpumask 179 | ((data->nr_cpus/data->nr_socks) * 180 | (channel - data->nr_cpus))); 181 | reg = ENERGY_PKG_MSR; 182 | } else { 183 | cpu = channel; 184 | if (!cpu_online(cpu)) 185 | return -ENODEV; 186 | 187 | reg = ENERGY_CORE_MSR; 188 | } 189 | amd_add_delta(data, channel, cpu, val, reg); 190 | 191 | return 0; 192 | } 193 | 194 | static umode_t amd_energy_is_visible(const void *_data, 195 | enum hwmon_sensor_types type, 196 | u32 attr, int channel) 197 | { 198 | return 0440; 199 | } 200 | 201 | static int energy_accumulator(void *p) 202 | { 203 | struct amd_energy_data *data = (struct amd_energy_data *)p; 204 | unsigned int timeout = data->timeout_ms; 205 | 206 | while (!kthread_should_stop()) { 207 | /* 208 | * Ignoring the conditions such as 209 | * cpu being offline or rdmsr failure 210 | */ 211 | read_accumulate(data); 212 | 213 | set_current_state(TASK_INTERRUPTIBLE); 214 | if (kthread_should_stop()) 215 | break; 216 | 217 | schedule_timeout(msecs_to_jiffies(timeout)); 218 | } 219 | return 0; 220 | } 221 | 222 | static const struct hwmon_ops amd_energy_ops = { 223 | .is_visible = amd_energy_is_visible, 224 | .read = amd_energy_read, 225 | .read_string = amd_energy_read_labels, 226 | }; 227 | 228 | static int amd_create_sensor(struct device *dev, 229 | struct amd_energy_data *data, 230 | enum hwmon_sensor_types type, u32 config) 231 | { 232 | struct hwmon_channel_info *info = &data->energy_info; 233 | struct sensor_accumulator *accums; 234 | int i, num_siblings, cpus, sockets; 235 | u32 *s_config; 236 | char (*label_l)[10]; 237 | 238 | /* Identify the number of siblings per core */ 239 | num_siblings = ((cpuid_ebx(0x8000001e) >> 8) & 0xff) + 1; 240 | 241 | /* 242 | * Energy counter register is accessed at core level. 243 | * Hence, filterout the siblings. 244 | */ 245 | cpus = num_present_cpus() / num_siblings; 246 | 247 | /* 248 | * c->x86_max_cores is the linux count of physical cores 249 | * total physical cores/ core per socket gives total number of sockets. 250 | */ 251 | #if LINUX_VERSION_CODE <= KERNEL_VERSION(6, 9, 0) 252 | struct cpuinfo_x86 *c = &boot_cpu_data; 253 | sockets = cpus / c->x86_max_cores; 254 | #else 255 | sockets = cpus / topology_num_cores_per_package(); 256 | #endif 257 | 258 | s_config = devm_kcalloc(dev, cpus + sockets + 1, 259 | sizeof(u32), GFP_KERNEL); 260 | if (!s_config) 261 | return -ENOMEM; 262 | 263 | accums = devm_kcalloc(dev, cpus + sockets, 264 | sizeof(struct sensor_accumulator), 265 | GFP_KERNEL); 266 | if (!accums) 267 | return -ENOMEM; 268 | 269 | label_l = devm_kcalloc(dev, cpus + sockets, 270 | sizeof(*label_l), GFP_KERNEL); 271 | if (!label_l) 272 | return -ENOMEM; 273 | 274 | info->type = type; 275 | info->config = s_config; 276 | 277 | data->nr_cpus = cpus; 278 | data->nr_socks = sockets; 279 | data->accums = accums; 280 | data->label = label_l; 281 | 282 | for (i = 0; i < cpus + sockets; i++) { 283 | s_config[i] = config; 284 | if (i < cpus) 285 | scnprintf(label_l[i], 10, "Ecore%03u", i); 286 | else 287 | scnprintf(label_l[i], 10, "Esocket%u", (i - cpus)); 288 | } 289 | 290 | s_config[i] = 0; 291 | return 0; 292 | } 293 | 294 | static const struct x86_cpu_id bit32_rapl_cpus[] = { 295 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x31, NULL), 296 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x01, NULL), 297 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x30, NULL), 298 | {} 299 | }; 300 | 301 | static int amd_energy_probe(struct platform_device *pdev) 302 | { 303 | struct device *hwmon_dev; 304 | struct amd_energy_data *data; 305 | struct device *dev = &pdev->dev; 306 | int ret; 307 | 308 | data = devm_kzalloc(dev, 309 | sizeof(struct amd_energy_data), GFP_KERNEL); 310 | if (!data) 311 | return -ENOMEM; 312 | 313 | data->chip.ops = &amd_energy_ops; 314 | data->chip.info = data->info; 315 | 316 | dev_set_drvdata(dev, data); 317 | /* Populate per-core energy reporting */ 318 | data->info[0] = &data->energy_info; 319 | ret = amd_create_sensor(dev, data, hwmon_energy, 320 | HWMON_E_INPUT | HWMON_E_LABEL); 321 | if (ret) 322 | return ret; 323 | 324 | mutex_init(&data->lock); 325 | get_energy_units(data); 326 | 327 | hwmon_dev = devm_hwmon_device_register_with_info(dev, DRVNAME, 328 | data, 329 | &data->chip, 330 | NULL); 331 | if (IS_ERR(hwmon_dev)) 332 | return PTR_ERR(hwmon_dev); 333 | 334 | /* 335 | * On a system with peak wattage of 250W 336 | * timeout = 2 ^ 32 / 2 ^ energy_units / 250 secs 337 | */ 338 | data->timeout_ms = 1000 * 339 | BIT(min(28, 31 - data->energy_units)) / 250; 340 | 341 | /* 342 | * For AMD platforms with 64-bit RAPL MSR registers, accumulation 343 | * of the energy counters are not necessary. 344 | */ 345 | if (!x86_match_cpu(bit32_rapl_cpus)) { 346 | if (boot_cpu_data.x86 == 0x19 && 347 | boot_cpu_data.x86_model == 0x0) { 348 | data->do_not_accum = false; 349 | } else { 350 | data->do_not_accum = true; 351 | return 0; 352 | } 353 | } 354 | 355 | data->wrap_accumulate = kthread_run(energy_accumulator, data, 356 | "%s", dev_name(hwmon_dev)); 357 | return PTR_ERR_OR_ZERO(data->wrap_accumulate); 358 | } 359 | 360 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 11, 0) 361 | static void amd_energy_remove(struct platform_device *pdev) 362 | #else 363 | static int amd_energy_remove(struct platform_device *pdev) 364 | #endif 365 | { 366 | struct amd_energy_data *data = dev_get_drvdata(&pdev->dev); 367 | 368 | if (data && data->wrap_accumulate) 369 | kthread_stop(data->wrap_accumulate); 370 | 371 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) 372 | return 0; 373 | #endif 374 | } 375 | 376 | static const struct platform_device_id amd_energy_ids[] = { 377 | { .name = DRVNAME, }, 378 | {} 379 | }; 380 | MODULE_DEVICE_TABLE(platform, amd_energy_ids); 381 | 382 | static struct platform_driver amd_energy_driver = { 383 | .probe = amd_energy_probe, 384 | .remove = amd_energy_remove, 385 | .id_table = amd_energy_ids, 386 | .driver = { 387 | .name = DRVNAME, 388 | }, 389 | }; 390 | 391 | static struct platform_device *amd_energy_platdev; 392 | 393 | static const struct x86_cpu_id cpu_ids[] __initconst = { 394 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x31, NULL), 395 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x01, NULL), 396 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x10, NULL), 397 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x30, NULL), 398 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x11, NULL), 399 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0xA0, NULL), 400 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x90, NULL), 401 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x1A, 0x10, NULL), 402 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x1A, 0x02, NULL), 403 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x1A, 0x11, NULL), 404 | {} 405 | }; 406 | MODULE_DEVICE_TABLE(x86cpu, cpu_ids); 407 | 408 | static int __init amd_energy_init(void) 409 | { 410 | int ret; 411 | 412 | if (!x86_match_cpu(cpu_ids)) { 413 | if (!((boot_cpu_data.x86 == 0x1A || boot_cpu_data.x86 == 0x19) && 414 | (boot_cpu_data.x86_model == 0x0))) 415 | return -ENODEV; 416 | } 417 | 418 | ret = platform_driver_register(&amd_energy_driver); 419 | if (ret) 420 | return ret; 421 | 422 | amd_energy_platdev = platform_device_alloc(DRVNAME, 0); 423 | if (!amd_energy_platdev) { 424 | platform_driver_unregister(&amd_energy_driver); 425 | return -ENOMEM; 426 | } 427 | 428 | ret = platform_device_add(amd_energy_platdev); 429 | if (ret) { 430 | platform_device_put(amd_energy_platdev); 431 | platform_driver_unregister(&amd_energy_driver); 432 | return ret; 433 | } 434 | 435 | return ret; 436 | } 437 | 438 | static void __exit amd_energy_exit(void) 439 | { 440 | platform_device_unregister(amd_energy_platdev); 441 | platform_driver_unregister(&amd_energy_driver); 442 | } 443 | 444 | module_init(amd_energy_init); 445 | module_exit(amd_energy_exit); 446 | 447 | MODULE_DESCRIPTION("Driver for AMD Energy reporting from RAPL MSR via HWMON interface"); 448 | MODULE_AUTHOR("Naveen Krishna Chatradhi "); 449 | MODULE_LICENSE("GPL"); 450 | --------------------------------------------------------------------------------