]>
Commit | Line | Data |
---|---|---|
3e135d88 | 1 | /* |
6b44e72a | 2 | * CPU Microcode Update Driver for Linux |
3e135d88 | 3 | * |
6b44e72a BP |
4 | * Copyright (C) 2000-2006 Tigran Aivazian <tigran@aivazian.fsnet.co.uk> |
5 | * 2006 Shaohua Li <shaohua.li@intel.com> | |
6 | * 2013-2015 Borislav Petkov <bp@alien8.de> | |
3e135d88 | 7 | * |
6b44e72a | 8 | * This driver allows to upgrade microcode on x86 processors. |
3e135d88 | 9 | * |
6b44e72a BP |
10 | * This program is free software; you can redistribute it and/or |
11 | * modify it under the terms of the GNU General Public License | |
12 | * as published by the Free Software Foundation; either version | |
13 | * 2 of the License, or (at your option) any later version. | |
3e135d88 | 14 | */ |
f58e1f53 JP |
15 | |
16 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
17 | ||
4bae1967 | 18 | #include <linux/platform_device.h> |
4bae1967 | 19 | #include <linux/miscdevice.h> |
871b72dd | 20 | #include <linux/capability.h> |
4bae1967 IM |
21 | #include <linux/kernel.h> |
22 | #include <linux/module.h> | |
3e135d88 PO |
23 | #include <linux/mutex.h> |
24 | #include <linux/cpu.h> | |
4bae1967 IM |
25 | #include <linux/fs.h> |
26 | #include <linux/mm.h> | |
f3c6ea1b | 27 | #include <linux/syscore_ops.h> |
3e135d88 | 28 | |
3e135d88 | 29 | #include <asm/microcode.h> |
4bae1967 | 30 | #include <asm/processor.h> |
78ff123b | 31 | #include <asm/cpu_device_id.h> |
c93dc84c | 32 | #include <asm/perf_event.h> |
3e135d88 PO |
33 | |
34 | MODULE_DESCRIPTION("Microcode Update Driver"); | |
35 | MODULE_AUTHOR("Tigran Aivazian <tigran@aivazian.fsnet.co.uk>"); | |
36 | MODULE_LICENSE("GPL"); | |
37 | ||
4bae1967 | 38 | #define MICROCODE_VERSION "2.00" |
3e135d88 | 39 | |
4bae1967 | 40 | static struct microcode_ops *microcode_ops; |
3e135d88 | 41 | |
65cef131 BP |
42 | bool dis_ucode_ldr; |
43 | module_param(dis_ucode_ldr, bool, 0); | |
44 | ||
871b72dd DA |
45 | /* |
46 | * Synchronization. | |
47 | * | |
48 | * All non cpu-hotplug-callback call sites use: | |
49 | * | |
50 | * - microcode_mutex to synchronize with each other; | |
51 | * - get/put_online_cpus() to synchronize with | |
52 | * the cpu-hotplug-callback call sites. | |
53 | * | |
54 | * We guarantee that only a single cpu is being | |
55 | * updated at any particular moment of time. | |
56 | */ | |
d45de409 | 57 | static DEFINE_MUTEX(microcode_mutex); |
3e135d88 | 58 | |
4bae1967 | 59 | struct ucode_cpu_info ucode_cpu_info[NR_CPUS]; |
8d86f390 | 60 | EXPORT_SYMBOL_GPL(ucode_cpu_info); |
3e135d88 | 61 | |
871b72dd DA |
62 | /* |
63 | * Operations that are run on a target cpu: | |
64 | */ | |
65 | ||
66 | struct cpu_info_ctx { | |
67 | struct cpu_signature *cpu_sig; | |
68 | int err; | |
69 | }; | |
70 | ||
71 | static void collect_cpu_info_local(void *arg) | |
72 | { | |
73 | struct cpu_info_ctx *ctx = arg; | |
74 | ||
75 | ctx->err = microcode_ops->collect_cpu_info(smp_processor_id(), | |
76 | ctx->cpu_sig); | |
77 | } | |
78 | ||
79 | static int collect_cpu_info_on_target(int cpu, struct cpu_signature *cpu_sig) | |
80 | { | |
81 | struct cpu_info_ctx ctx = { .cpu_sig = cpu_sig, .err = 0 }; | |
82 | int ret; | |
83 | ||
84 | ret = smp_call_function_single(cpu, collect_cpu_info_local, &ctx, 1); | |
85 | if (!ret) | |
86 | ret = ctx.err; | |
87 | ||
88 | return ret; | |
89 | } | |
90 | ||
91 | static int collect_cpu_info(int cpu) | |
92 | { | |
93 | struct ucode_cpu_info *uci = ucode_cpu_info + cpu; | |
94 | int ret; | |
95 | ||
96 | memset(uci, 0, sizeof(*uci)); | |
97 | ||
98 | ret = collect_cpu_info_on_target(cpu, &uci->cpu_sig); | |
99 | if (!ret) | |
100 | uci->valid = 1; | |
101 | ||
102 | return ret; | |
103 | } | |
104 | ||
105 | struct apply_microcode_ctx { | |
106 | int err; | |
107 | }; | |
108 | ||
109 | static void apply_microcode_local(void *arg) | |
110 | { | |
111 | struct apply_microcode_ctx *ctx = arg; | |
112 | ||
113 | ctx->err = microcode_ops->apply_microcode(smp_processor_id()); | |
114 | } | |
115 | ||
116 | static int apply_microcode_on_target(int cpu) | |
117 | { | |
118 | struct apply_microcode_ctx ctx = { .err = 0 }; | |
119 | int ret; | |
120 | ||
121 | ret = smp_call_function_single(cpu, apply_microcode_local, &ctx, 1); | |
122 | if (!ret) | |
123 | ret = ctx.err; | |
124 | ||
125 | return ret; | |
126 | } | |
127 | ||
3e135d88 | 128 | #ifdef CONFIG_MICROCODE_OLD_INTERFACE |
a0a29b62 | 129 | static int do_microcode_update(const void __user *buf, size_t size) |
3e135d88 | 130 | { |
3e135d88 | 131 | int error = 0; |
3e135d88 | 132 | int cpu; |
6f66cbc6 | 133 | |
a0a29b62 DA |
134 | for_each_online_cpu(cpu) { |
135 | struct ucode_cpu_info *uci = ucode_cpu_info + cpu; | |
871b72dd | 136 | enum ucode_state ustate; |
a0a29b62 DA |
137 | |
138 | if (!uci->valid) | |
139 | continue; | |
6f66cbc6 | 140 | |
871b72dd DA |
141 | ustate = microcode_ops->request_microcode_user(cpu, buf, size); |
142 | if (ustate == UCODE_ERROR) { | |
143 | error = -1; | |
144 | break; | |
145 | } else if (ustate == UCODE_OK) | |
146 | apply_microcode_on_target(cpu); | |
3e135d88 | 147 | } |
871b72dd | 148 | |
3e135d88 PO |
149 | return error; |
150 | } | |
151 | ||
3f10940e | 152 | static int microcode_open(struct inode *inode, struct file *file) |
3e135d88 | 153 | { |
3f10940e | 154 | return capable(CAP_SYS_RAWIO) ? nonseekable_open(inode, file) : -EPERM; |
3e135d88 PO |
155 | } |
156 | ||
d33dcb9e PO |
157 | static ssize_t microcode_write(struct file *file, const char __user *buf, |
158 | size_t len, loff_t *ppos) | |
3e135d88 | 159 | { |
871b72dd | 160 | ssize_t ret = -EINVAL; |
3e135d88 | 161 | |
4481374c | 162 | if ((len >> PAGE_SHIFT) > totalram_pages) { |
f58e1f53 | 163 | pr_err("too much data (max %ld pages)\n", totalram_pages); |
871b72dd | 164 | return ret; |
3e135d88 PO |
165 | } |
166 | ||
167 | get_online_cpus(); | |
168 | mutex_lock(µcode_mutex); | |
169 | ||
871b72dd | 170 | if (do_microcode_update(buf, len) == 0) |
3e135d88 PO |
171 | ret = (ssize_t)len; |
172 | ||
e3e45c01 SE |
173 | if (ret > 0) |
174 | perf_check_microcode(); | |
175 | ||
3e135d88 PO |
176 | mutex_unlock(µcode_mutex); |
177 | put_online_cpus(); | |
178 | ||
179 | return ret; | |
180 | } | |
181 | ||
182 | static const struct file_operations microcode_fops = { | |
871b72dd DA |
183 | .owner = THIS_MODULE, |
184 | .write = microcode_write, | |
185 | .open = microcode_open, | |
6038f373 | 186 | .llseek = no_llseek, |
3e135d88 PO |
187 | }; |
188 | ||
189 | static struct miscdevice microcode_dev = { | |
871b72dd DA |
190 | .minor = MICROCODE_MINOR, |
191 | .name = "microcode", | |
e454cea2 | 192 | .nodename = "cpu/microcode", |
871b72dd | 193 | .fops = µcode_fops, |
3e135d88 PO |
194 | }; |
195 | ||
d33dcb9e | 196 | static int __init microcode_dev_init(void) |
3e135d88 PO |
197 | { |
198 | int error; | |
199 | ||
200 | error = misc_register(µcode_dev); | |
201 | if (error) { | |
f58e1f53 | 202 | pr_err("can't misc_register on minor=%d\n", MICROCODE_MINOR); |
3e135d88 PO |
203 | return error; |
204 | } | |
205 | ||
206 | return 0; | |
207 | } | |
208 | ||
bd399063 | 209 | static void __exit microcode_dev_exit(void) |
3e135d88 PO |
210 | { |
211 | misc_deregister(µcode_dev); | |
212 | } | |
213 | ||
214 | MODULE_ALIAS_MISCDEV(MICROCODE_MINOR); | |
578454ff | 215 | MODULE_ALIAS("devname:cpu/microcode"); |
3e135d88 | 216 | #else |
4bae1967 IM |
217 | #define microcode_dev_init() 0 |
218 | #define microcode_dev_exit() do { } while (0) | |
3e135d88 PO |
219 | #endif |
220 | ||
221 | /* fake device for request_firmware */ | |
4bae1967 | 222 | static struct platform_device *microcode_pdev; |
3e135d88 | 223 | |
871b72dd | 224 | static int reload_for_cpu(int cpu) |
af5c820a | 225 | { |
871b72dd | 226 | struct ucode_cpu_info *uci = ucode_cpu_info + cpu; |
4dbf32c3 | 227 | enum ucode_state ustate; |
af5c820a RR |
228 | int err = 0; |
229 | ||
4dbf32c3 BP |
230 | if (!uci->valid) |
231 | return err; | |
871b72dd | 232 | |
48e30685 | 233 | ustate = microcode_ops->request_microcode_fw(cpu, µcode_pdev->dev, true); |
4dbf32c3 BP |
234 | if (ustate == UCODE_OK) |
235 | apply_microcode_on_target(cpu); | |
236 | else | |
237 | if (ustate == UCODE_ERROR) | |
238 | err = -EINVAL; | |
af5c820a RR |
239 | return err; |
240 | } | |
241 | ||
8a25a2fd KS |
242 | static ssize_t reload_store(struct device *dev, |
243 | struct device_attribute *attr, | |
871b72dd | 244 | const char *buf, size_t size) |
3e135d88 | 245 | { |
871b72dd | 246 | unsigned long val; |
c9fc3f77 BP |
247 | int cpu; |
248 | ssize_t ret = 0, tmp_ret; | |
249 | ||
e826abd5 SK |
250 | ret = kstrtoul(buf, 0, &val); |
251 | if (ret) | |
252 | return ret; | |
871b72dd | 253 | |
c9fc3f77 BP |
254 | if (val != 1) |
255 | return size; | |
256 | ||
257 | get_online_cpus(); | |
c93dc84c | 258 | mutex_lock(µcode_mutex); |
c9fc3f77 BP |
259 | for_each_online_cpu(cpu) { |
260 | tmp_ret = reload_for_cpu(cpu); | |
261 | if (tmp_ret != 0) | |
262 | pr_warn("Error reloading microcode on CPU %d\n", cpu); | |
263 | ||
264 | /* save retval of the first encountered reload error */ | |
265 | if (!ret) | |
266 | ret = tmp_ret; | |
3e135d88 | 267 | } |
c93dc84c PZ |
268 | if (!ret) |
269 | perf_check_microcode(); | |
270 | mutex_unlock(µcode_mutex); | |
c9fc3f77 | 271 | put_online_cpus(); |
871b72dd DA |
272 | |
273 | if (!ret) | |
274 | ret = size; | |
275 | ||
276 | return ret; | |
3e135d88 PO |
277 | } |
278 | ||
8a25a2fd KS |
279 | static ssize_t version_show(struct device *dev, |
280 | struct device_attribute *attr, char *buf) | |
3e135d88 PO |
281 | { |
282 | struct ucode_cpu_info *uci = ucode_cpu_info + dev->id; | |
283 | ||
d45de409 | 284 | return sprintf(buf, "0x%x\n", uci->cpu_sig.rev); |
3e135d88 PO |
285 | } |
286 | ||
8a25a2fd KS |
287 | static ssize_t pf_show(struct device *dev, |
288 | struct device_attribute *attr, char *buf) | |
3e135d88 PO |
289 | { |
290 | struct ucode_cpu_info *uci = ucode_cpu_info + dev->id; | |
291 | ||
d45de409 | 292 | return sprintf(buf, "0x%x\n", uci->cpu_sig.pf); |
3e135d88 PO |
293 | } |
294 | ||
8a25a2fd KS |
295 | static DEVICE_ATTR(reload, 0200, NULL, reload_store); |
296 | static DEVICE_ATTR(version, 0400, version_show, NULL); | |
297 | static DEVICE_ATTR(processor_flags, 0400, pf_show, NULL); | |
3e135d88 PO |
298 | |
299 | static struct attribute *mc_default_attrs[] = { | |
8a25a2fd KS |
300 | &dev_attr_version.attr, |
301 | &dev_attr_processor_flags.attr, | |
3e135d88 PO |
302 | NULL |
303 | }; | |
304 | ||
305 | static struct attribute_group mc_attr_group = { | |
871b72dd DA |
306 | .attrs = mc_default_attrs, |
307 | .name = "microcode", | |
3e135d88 PO |
308 | }; |
309 | ||
871b72dd | 310 | static void microcode_fini_cpu(int cpu) |
d45de409 | 311 | { |
d45de409 | 312 | microcode_ops->microcode_fini_cpu(cpu); |
280a9ca5 DA |
313 | } |
314 | ||
871b72dd | 315 | static enum ucode_state microcode_resume_cpu(int cpu) |
d45de409 | 316 | { |
f58e1f53 | 317 | pr_debug("CPU%d updated upon resume\n", cpu); |
bb9d3e47 BP |
318 | |
319 | if (apply_microcode_on_target(cpu)) | |
320 | return UCODE_ERROR; | |
871b72dd DA |
321 | |
322 | return UCODE_OK; | |
d45de409 DA |
323 | } |
324 | ||
48e30685 | 325 | static enum ucode_state microcode_init_cpu(int cpu, bool refresh_fw) |
d45de409 | 326 | { |
871b72dd | 327 | enum ucode_state ustate; |
9cd4d78e FY |
328 | struct ucode_cpu_info *uci = ucode_cpu_info + cpu; |
329 | ||
330 | if (uci && uci->valid) | |
331 | return UCODE_OK; | |
d45de409 | 332 | |
871b72dd DA |
333 | if (collect_cpu_info(cpu)) |
334 | return UCODE_ERROR; | |
d45de409 | 335 | |
871b72dd DA |
336 | /* --dimm. Trigger a delayed update? */ |
337 | if (system_state != SYSTEM_RUNNING) | |
338 | return UCODE_NFOUND; | |
d45de409 | 339 | |
48e30685 BP |
340 | ustate = microcode_ops->request_microcode_fw(cpu, µcode_pdev->dev, |
341 | refresh_fw); | |
d45de409 | 342 | |
871b72dd | 343 | if (ustate == UCODE_OK) { |
f58e1f53 | 344 | pr_debug("CPU%d updated upon init\n", cpu); |
871b72dd | 345 | apply_microcode_on_target(cpu); |
d45de409 DA |
346 | } |
347 | ||
871b72dd | 348 | return ustate; |
d45de409 DA |
349 | } |
350 | ||
871b72dd | 351 | static enum ucode_state microcode_update_cpu(int cpu) |
d45de409 | 352 | { |
871b72dd | 353 | struct ucode_cpu_info *uci = ucode_cpu_info + cpu; |
d45de409 | 354 | |
2f99f5c8 | 355 | if (uci->valid) |
bb9d3e47 | 356 | return microcode_resume_cpu(cpu); |
d45de409 | 357 | |
48e30685 | 358 | return microcode_init_cpu(cpu, false); |
d45de409 DA |
359 | } |
360 | ||
8a25a2fd | 361 | static int mc_device_add(struct device *dev, struct subsys_interface *sif) |
3e135d88 | 362 | { |
8a25a2fd | 363 | int err, cpu = dev->id; |
3e135d88 PO |
364 | |
365 | if (!cpu_online(cpu)) | |
366 | return 0; | |
367 | ||
f58e1f53 | 368 | pr_debug("CPU%d added\n", cpu); |
3e135d88 | 369 | |
8a25a2fd | 370 | err = sysfs_create_group(&dev->kobj, &mc_attr_group); |
3e135d88 PO |
371 | if (err) |
372 | return err; | |
373 | ||
48e30685 | 374 | if (microcode_init_cpu(cpu, true) == UCODE_ERROR) |
6c53cbfc | 375 | return -EINVAL; |
af5c820a RR |
376 | |
377 | return err; | |
3e135d88 PO |
378 | } |
379 | ||
71db87ba | 380 | static void mc_device_remove(struct device *dev, struct subsys_interface *sif) |
3e135d88 | 381 | { |
8a25a2fd | 382 | int cpu = dev->id; |
3e135d88 PO |
383 | |
384 | if (!cpu_online(cpu)) | |
71db87ba | 385 | return; |
3e135d88 | 386 | |
f58e1f53 | 387 | pr_debug("CPU%d removed\n", cpu); |
d45de409 | 388 | microcode_fini_cpu(cpu); |
8a25a2fd | 389 | sysfs_remove_group(&dev->kobj, &mc_attr_group); |
3e135d88 PO |
390 | } |
391 | ||
8a25a2fd KS |
392 | static struct subsys_interface mc_cpu_interface = { |
393 | .name = "microcode", | |
394 | .subsys = &cpu_subsys, | |
395 | .add_dev = mc_device_add, | |
396 | .remove_dev = mc_device_remove, | |
f3c6ea1b RW |
397 | }; |
398 | ||
399 | /** | |
400 | * mc_bp_resume - Update boot CPU microcode during resume. | |
401 | */ | |
402 | static void mc_bp_resume(void) | |
3e135d88 | 403 | { |
f3c6ea1b | 404 | int cpu = smp_processor_id(); |
871b72dd | 405 | struct ucode_cpu_info *uci = ucode_cpu_info + cpu; |
3e135d88 | 406 | |
871b72dd DA |
407 | if (uci->valid && uci->mc) |
408 | microcode_ops->apply_microcode(cpu); | |
fb86b973 | 409 | else if (!uci->mc) |
fbae4ba8 | 410 | reload_early_microcode(); |
3e135d88 PO |
411 | } |
412 | ||
f3c6ea1b RW |
413 | static struct syscore_ops mc_syscore_ops = { |
414 | .resume = mc_bp_resume, | |
3e135d88 PO |
415 | }; |
416 | ||
148f9bb8 | 417 | static int |
3e135d88 PO |
418 | mc_cpu_callback(struct notifier_block *nb, unsigned long action, void *hcpu) |
419 | { | |
420 | unsigned int cpu = (unsigned long)hcpu; | |
8a25a2fd | 421 | struct device *dev; |
3e135d88 | 422 | |
8a25a2fd | 423 | dev = get_cpu_device(cpu); |
09c3f0d8 BP |
424 | |
425 | switch (action & ~CPU_TASKS_FROZEN) { | |
3e135d88 | 426 | case CPU_ONLINE: |
871b72dd | 427 | microcode_update_cpu(cpu); |
f58e1f53 | 428 | pr_debug("CPU%d added\n", cpu); |
09c3f0d8 BP |
429 | /* |
430 | * "break" is missing on purpose here because we want to fall | |
431 | * through in order to create the sysfs group. | |
432 | */ | |
433 | ||
434 | case CPU_DOWN_FAILED: | |
8a25a2fd | 435 | if (sysfs_create_group(&dev->kobj, &mc_attr_group)) |
f58e1f53 | 436 | pr_err("Failed to create group for CPU%d\n", cpu); |
3e135d88 | 437 | break; |
09c3f0d8 | 438 | |
3e135d88 | 439 | case CPU_DOWN_PREPARE: |
3e135d88 | 440 | /* Suspend is in progress, only remove the interface */ |
8a25a2fd | 441 | sysfs_remove_group(&dev->kobj, &mc_attr_group); |
f58e1f53 | 442 | pr_debug("CPU%d removed\n", cpu); |
d45de409 | 443 | break; |
70989449 SB |
444 | |
445 | /* | |
09c3f0d8 BP |
446 | * case CPU_DEAD: |
447 | * | |
70989449 SB |
448 | * When a CPU goes offline, don't free up or invalidate the copy of |
449 | * the microcode in kernel memory, so that we can reuse it when the | |
450 | * CPU comes back online without unnecessarily requesting the userspace | |
451 | * for it again. | |
452 | */ | |
3e135d88 | 453 | } |
09c3f0d8 BP |
454 | |
455 | /* The CPU refused to come up during a system resume */ | |
456 | if (action == CPU_UP_CANCELED_FROZEN) | |
457 | microcode_fini_cpu(cpu); | |
458 | ||
3e135d88 PO |
459 | return NOTIFY_OK; |
460 | } | |
461 | ||
4daa832d | 462 | static struct notifier_block mc_cpu_notifier = { |
4bae1967 | 463 | .notifier_call = mc_cpu_callback, |
3e135d88 PO |
464 | }; |
465 | ||
78ff123b AK |
466 | #ifdef MODULE |
467 | /* Autoload on Intel and AMD systems */ | |
e1b6fc55 | 468 | static const struct x86_cpu_id __initconst microcode_id[] = { |
78ff123b AK |
469 | #ifdef CONFIG_MICROCODE_INTEL |
470 | { X86_VENDOR_INTEL, X86_FAMILY_ANY, X86_MODEL_ANY, }, | |
471 | #endif | |
472 | #ifdef CONFIG_MICROCODE_AMD | |
473 | { X86_VENDOR_AMD, X86_FAMILY_ANY, X86_MODEL_ANY, }, | |
474 | #endif | |
475 | {} | |
476 | }; | |
477 | MODULE_DEVICE_TABLE(x86cpu, microcode_id); | |
478 | #endif | |
479 | ||
3d8986bc BP |
480 | static struct attribute *cpu_root_microcode_attrs[] = { |
481 | &dev_attr_reload.attr, | |
482 | NULL | |
483 | }; | |
484 | ||
485 | static struct attribute_group cpu_root_microcode_group = { | |
486 | .name = "microcode", | |
487 | .attrs = cpu_root_microcode_attrs, | |
488 | }; | |
489 | ||
18dbc916 | 490 | static int __init microcode_init(void) |
3e135d88 | 491 | { |
18dbc916 | 492 | struct cpuinfo_x86 *c = &cpu_data(0); |
3e135d88 PO |
493 | int error; |
494 | ||
a18a0f68 | 495 | if (paravirt_enabled() || dis_ucode_ldr) |
da63865a | 496 | return -EINVAL; |
65cef131 | 497 | |
18dbc916 DA |
498 | if (c->x86_vendor == X86_VENDOR_INTEL) |
499 | microcode_ops = init_intel_microcode(); | |
82b07865 | 500 | else if (c->x86_vendor == X86_VENDOR_AMD) |
18dbc916 | 501 | microcode_ops = init_amd_microcode(); |
283c1f25 | 502 | else |
f58e1f53 | 503 | pr_err("no support for this CPU vendor\n"); |
283c1f25 AH |
504 | |
505 | if (!microcode_ops) | |
18dbc916 | 506 | return -ENODEV; |
3e135d88 | 507 | |
3e135d88 PO |
508 | microcode_pdev = platform_device_register_simple("microcode", -1, |
509 | NULL, 0); | |
bd399063 | 510 | if (IS_ERR(microcode_pdev)) |
3e135d88 | 511 | return PTR_ERR(microcode_pdev); |
3e135d88 PO |
512 | |
513 | get_online_cpus(); | |
871b72dd DA |
514 | mutex_lock(µcode_mutex); |
515 | ||
8a25a2fd | 516 | error = subsys_interface_register(&mc_cpu_interface); |
c93dc84c PZ |
517 | if (!error) |
518 | perf_check_microcode(); | |
871b72dd | 519 | mutex_unlock(µcode_mutex); |
3e135d88 | 520 | put_online_cpus(); |
871b72dd | 521 | |
bd399063 SB |
522 | if (error) |
523 | goto out_pdev; | |
3e135d88 | 524 | |
3d8986bc BP |
525 | error = sysfs_create_group(&cpu_subsys.dev_root->kobj, |
526 | &cpu_root_microcode_group); | |
527 | ||
528 | if (error) { | |
529 | pr_err("Error creating microcode group!\n"); | |
530 | goto out_driver; | |
531 | } | |
532 | ||
871b72dd DA |
533 | error = microcode_dev_init(); |
534 | if (error) | |
3d8986bc | 535 | goto out_ucode_group; |
871b72dd | 536 | |
f3c6ea1b | 537 | register_syscore_ops(&mc_syscore_ops); |
3e135d88 | 538 | register_hotcpu_notifier(&mc_cpu_notifier); |
8d86f390 | 539 | |
871b72dd | 540 | pr_info("Microcode Update Driver: v" MICROCODE_VERSION |
f58e1f53 | 541 | " <tigran@aivazian.fsnet.co.uk>, Peter Oruba\n"); |
8d86f390 | 542 | |
3e135d88 | 543 | return 0; |
bd399063 | 544 | |
3d8986bc BP |
545 | out_ucode_group: |
546 | sysfs_remove_group(&cpu_subsys.dev_root->kobj, | |
547 | &cpu_root_microcode_group); | |
548 | ||
549 | out_driver: | |
bd399063 SB |
550 | get_online_cpus(); |
551 | mutex_lock(µcode_mutex); | |
552 | ||
ff4b8a57 | 553 | subsys_interface_unregister(&mc_cpu_interface); |
bd399063 SB |
554 | |
555 | mutex_unlock(µcode_mutex); | |
556 | put_online_cpus(); | |
557 | ||
3d8986bc | 558 | out_pdev: |
bd399063 SB |
559 | platform_device_unregister(microcode_pdev); |
560 | return error; | |
561 | ||
3e135d88 | 562 | } |
871b72dd | 563 | module_init(microcode_init); |
3e135d88 | 564 | |
18dbc916 | 565 | static void __exit microcode_exit(void) |
3e135d88 | 566 | { |
f72c1a57 BP |
567 | struct cpuinfo_x86 *c = &cpu_data(0); |
568 | ||
3e135d88 PO |
569 | microcode_dev_exit(); |
570 | ||
571 | unregister_hotcpu_notifier(&mc_cpu_notifier); | |
4ac5fc6a | 572 | unregister_syscore_ops(&mc_syscore_ops); |
3e135d88 | 573 | |
3d8986bc BP |
574 | sysfs_remove_group(&cpu_subsys.dev_root->kobj, |
575 | &cpu_root_microcode_group); | |
576 | ||
3e135d88 | 577 | get_online_cpus(); |
871b72dd DA |
578 | mutex_lock(µcode_mutex); |
579 | ||
8a25a2fd | 580 | subsys_interface_unregister(&mc_cpu_interface); |
871b72dd DA |
581 | |
582 | mutex_unlock(µcode_mutex); | |
3e135d88 PO |
583 | put_online_cpus(); |
584 | ||
585 | platform_device_unregister(microcode_pdev); | |
3e135d88 | 586 | |
8d86f390 PO |
587 | microcode_ops = NULL; |
588 | ||
f72c1a57 BP |
589 | if (c->x86_vendor == X86_VENDOR_AMD) |
590 | exit_amd_microcode(); | |
591 | ||
871b72dd | 592 | pr_info("Microcode Update Driver: v" MICROCODE_VERSION " removed.\n"); |
8d86f390 | 593 | } |
18dbc916 | 594 | module_exit(microcode_exit); |