diff options
Diffstat (limited to 'arch/x86/kernel/microcode.c')
-rw-r--r-- | arch/x86/kernel/microcode.c | 155 |
1 files changed, 102 insertions, 53 deletions
diff --git a/arch/x86/kernel/microcode.c b/arch/x86/kernel/microcode.c index ad136ad99cb3..b2f84ce5eed3 100644 --- a/arch/x86/kernel/microcode.c +++ b/arch/x86/kernel/microcode.c | |||
@@ -71,7 +71,7 @@ | |||
71 | * Thanks to Stuart Swales for pointing out this bug. | 71 | * Thanks to Stuart Swales for pointing out this bug. |
72 | */ | 72 | */ |
73 | 73 | ||
74 | /*#define DEBUG pr_debug */ | 74 | /* #define DEBUG pr_debug */ |
75 | #include <linux/capability.h> | 75 | #include <linux/capability.h> |
76 | #include <linux/kernel.h> | 76 | #include <linux/kernel.h> |
77 | #include <linux/init.h> | 77 | #include <linux/init.h> |
@@ -104,8 +104,7 @@ MODULE_LICENSE("GPL"); | |||
104 | struct microcode_ops *microcode_ops; | 104 | struct microcode_ops *microcode_ops; |
105 | 105 | ||
106 | /* no concurrent ->write()s are allowed on /dev/cpu/microcode */ | 106 | /* no concurrent ->write()s are allowed on /dev/cpu/microcode */ |
107 | DEFINE_MUTEX(microcode_mutex); | 107 | static DEFINE_MUTEX(microcode_mutex); |
108 | EXPORT_SYMBOL_GPL(microcode_mutex); | ||
109 | 108 | ||
110 | struct ucode_cpu_info ucode_cpu_info[NR_CPUS]; | 109 | struct ucode_cpu_info ucode_cpu_info[NR_CPUS]; |
111 | EXPORT_SYMBOL_GPL(ucode_cpu_info); | 110 | EXPORT_SYMBOL_GPL(ucode_cpu_info); |
@@ -234,22 +233,6 @@ MODULE_ALIAS_MISCDEV(MICROCODE_MINOR); | |||
234 | struct platform_device *microcode_pdev; | 233 | struct platform_device *microcode_pdev; |
235 | EXPORT_SYMBOL_GPL(microcode_pdev); | 234 | EXPORT_SYMBOL_GPL(microcode_pdev); |
236 | 235 | ||
237 | static void microcode_init_cpu(int cpu, int resume) | ||
238 | { | ||
239 | cpumask_t old; | ||
240 | struct ucode_cpu_info *uci = ucode_cpu_info + cpu; | ||
241 | |||
242 | old = current->cpus_allowed; | ||
243 | |||
244 | set_cpus_allowed_ptr(current, &cpumask_of_cpu(cpu)); | ||
245 | mutex_lock(µcode_mutex); | ||
246 | microcode_ops->collect_cpu_info(cpu); | ||
247 | if (uci->valid && system_state == SYSTEM_RUNNING && !resume) | ||
248 | microcode_ops->cpu_request_microcode(cpu); | ||
249 | mutex_unlock(µcode_mutex); | ||
250 | set_cpus_allowed_ptr(current, &old); | ||
251 | } | ||
252 | |||
253 | static ssize_t reload_store(struct sys_device *dev, | 236 | static ssize_t reload_store(struct sys_device *dev, |
254 | struct sysdev_attribute *attr, | 237 | struct sysdev_attribute *attr, |
255 | const char *buf, size_t sz) | 238 | const char *buf, size_t sz) |
@@ -266,14 +249,15 @@ static ssize_t reload_store(struct sys_device *dev, | |||
266 | cpumask_t old = current->cpus_allowed; | 249 | cpumask_t old = current->cpus_allowed; |
267 | 250 | ||
268 | get_online_cpus(); | 251 | get_online_cpus(); |
269 | set_cpus_allowed_ptr(current, &cpumask_of_cpu(cpu)); | 252 | if (cpu_online(cpu)) { |
270 | 253 | set_cpus_allowed_ptr(current, &cpumask_of_cpu(cpu)); | |
271 | mutex_lock(µcode_mutex); | 254 | mutex_lock(µcode_mutex); |
272 | if (uci->valid) | 255 | if (uci->valid) |
273 | err = microcode_ops->cpu_request_microcode(cpu); | 256 | err = microcode_ops->cpu_request_microcode(cpu); |
274 | mutex_unlock(µcode_mutex); | 257 | mutex_unlock(µcode_mutex); |
258 | set_cpus_allowed_ptr(current, &old); | ||
259 | } | ||
275 | put_online_cpus(); | 260 | put_online_cpus(); |
276 | set_cpus_allowed_ptr(current, &old); | ||
277 | } | 261 | } |
278 | if (err) | 262 | if (err) |
279 | return err; | 263 | return err; |
@@ -285,7 +269,7 @@ static ssize_t version_show(struct sys_device *dev, | |||
285 | { | 269 | { |
286 | struct ucode_cpu_info *uci = ucode_cpu_info + dev->id; | 270 | struct ucode_cpu_info *uci = ucode_cpu_info + dev->id; |
287 | 271 | ||
288 | return sprintf(buf, "0x%x\n", uci->rev); | 272 | return sprintf(buf, "0x%x\n", uci->cpu_sig.rev); |
289 | } | 273 | } |
290 | 274 | ||
291 | static ssize_t pf_show(struct sys_device *dev, | 275 | static ssize_t pf_show(struct sys_device *dev, |
@@ -293,7 +277,7 @@ static ssize_t pf_show(struct sys_device *dev, | |||
293 | { | 277 | { |
294 | struct ucode_cpu_info *uci = ucode_cpu_info + dev->id; | 278 | struct ucode_cpu_info *uci = ucode_cpu_info + dev->id; |
295 | 279 | ||
296 | return sprintf(buf, "0x%x\n", uci->pf); | 280 | return sprintf(buf, "0x%x\n", uci->cpu_sig.pf); |
297 | } | 281 | } |
298 | 282 | ||
299 | static SYSDEV_ATTR(reload, 0200, NULL, reload_store); | 283 | static SYSDEV_ATTR(reload, 0200, NULL, reload_store); |
@@ -312,7 +296,85 @@ static struct attribute_group mc_attr_group = { | |||
312 | .name = "microcode", | 296 | .name = "microcode", |
313 | }; | 297 | }; |
314 | 298 | ||
315 | static int __mc_sysdev_add(struct sys_device *sys_dev, int resume) | 299 | static void microcode_fini_cpu(int cpu) |
300 | { | ||
301 | struct ucode_cpu_info *uci = ucode_cpu_info + cpu; | ||
302 | |||
303 | mutex_lock(µcode_mutex); | ||
304 | microcode_ops->microcode_fini_cpu(cpu); | ||
305 | uci->valid = 0; | ||
306 | mutex_unlock(µcode_mutex); | ||
307 | } | ||
308 | |||
309 | static void collect_cpu_info(int cpu) | ||
310 | { | ||
311 | struct ucode_cpu_info *uci = ucode_cpu_info + cpu; | ||
312 | |||
313 | memset(uci, 0, sizeof(*uci)); | ||
314 | if (!microcode_ops->collect_cpu_info(cpu, &uci->cpu_sig)) | ||
315 | uci->valid = 1; | ||
316 | } | ||
317 | |||
318 | static void microcode_resume_cpu(int cpu) | ||
319 | { | ||
320 | struct ucode_cpu_info *uci = ucode_cpu_info + cpu; | ||
321 | struct cpu_signature nsig; | ||
322 | |||
323 | pr_debug("microcode: CPU%d resumed\n", cpu); | ||
324 | |||
325 | if (!uci->mc.valid_mc) | ||
326 | return; | ||
327 | |||
328 | /* | ||
329 | * Let's verify that the 'cached' ucode does belong | ||
330 | * to this cpu (a bit of paranoia): | ||
331 | */ | ||
332 | if (microcode_ops->collect_cpu_info(cpu, &nsig)) { | ||
333 | microcode_fini_cpu(cpu); | ||
334 | return; | ||
335 | } | ||
336 | |||
337 | if (memcmp(&nsig, &uci->cpu_sig, sizeof(nsig))) { | ||
338 | microcode_fini_cpu(cpu); | ||
339 | /* Should we look for a new ucode here? */ | ||
340 | return; | ||
341 | } | ||
342 | |||
343 | microcode_ops->apply_microcode(cpu); | ||
344 | } | ||
345 | |||
346 | void microcode_update_cpu(int cpu) | ||
347 | { | ||
348 | struct ucode_cpu_info *uci = ucode_cpu_info + cpu; | ||
349 | |||
350 | /* We should bind the task to the CPU */ | ||
351 | BUG_ON(raw_smp_processor_id() != cpu); | ||
352 | |||
353 | mutex_lock(µcode_mutex); | ||
354 | /* | ||
355 | * Check if the system resume is in progress (uci->valid != NULL), | ||
356 | * otherwise just request a firmware: | ||
357 | */ | ||
358 | if (uci->valid) { | ||
359 | microcode_resume_cpu(cpu); | ||
360 | } else { | ||
361 | collect_cpu_info(cpu); | ||
362 | if (uci->valid && system_state == SYSTEM_RUNNING) | ||
363 | microcode_ops->cpu_request_microcode(cpu); | ||
364 | } | ||
365 | mutex_unlock(µcode_mutex); | ||
366 | } | ||
367 | |||
368 | static void microcode_init_cpu(int cpu) | ||
369 | { | ||
370 | cpumask_t old = current->cpus_allowed; | ||
371 | |||
372 | set_cpus_allowed_ptr(current, &cpumask_of_cpu(cpu)); | ||
373 | microcode_update_cpu(cpu); | ||
374 | set_cpus_allowed_ptr(current, &old); | ||
375 | } | ||
376 | |||
377 | static int mc_sysdev_add(struct sys_device *sys_dev) | ||
316 | { | 378 | { |
317 | int err, cpu = sys_dev->id; | 379 | int err, cpu = sys_dev->id; |
318 | struct ucode_cpu_info *uci = ucode_cpu_info + cpu; | 380 | struct ucode_cpu_info *uci = ucode_cpu_info + cpu; |
@@ -327,16 +389,10 @@ static int __mc_sysdev_add(struct sys_device *sys_dev, int resume) | |||
327 | if (err) | 389 | if (err) |
328 | return err; | 390 | return err; |
329 | 391 | ||
330 | microcode_init_cpu(cpu, resume); | 392 | microcode_init_cpu(cpu); |
331 | |||
332 | return 0; | 393 | return 0; |
333 | } | 394 | } |
334 | 395 | ||
335 | static int mc_sysdev_add(struct sys_device *sys_dev) | ||
336 | { | ||
337 | return __mc_sysdev_add(sys_dev, 0); | ||
338 | } | ||
339 | |||
340 | static int mc_sysdev_remove(struct sys_device *sys_dev) | 396 | static int mc_sysdev_remove(struct sys_device *sys_dev) |
341 | { | 397 | { |
342 | int cpu = sys_dev->id; | 398 | int cpu = sys_dev->id; |
@@ -345,7 +401,7 @@ static int mc_sysdev_remove(struct sys_device *sys_dev) | |||
345 | return 0; | 401 | return 0; |
346 | 402 | ||
347 | pr_debug("microcode: CPU%d removed\n", cpu); | 403 | pr_debug("microcode: CPU%d removed\n", cpu); |
348 | microcode_ops->microcode_fini_cpu(cpu); | 404 | microcode_fini_cpu(cpu); |
349 | sysfs_remove_group(&sys_dev->kobj, &mc_attr_group); | 405 | sysfs_remove_group(&sys_dev->kobj, &mc_attr_group); |
350 | return 0; | 406 | return 0; |
351 | } | 407 | } |
@@ -376,33 +432,26 @@ mc_cpu_callback(struct notifier_block *nb, unsigned long action, void *hcpu) | |||
376 | 432 | ||
377 | sys_dev = get_cpu_sysdev(cpu); | 433 | sys_dev = get_cpu_sysdev(cpu); |
378 | switch (action) { | 434 | switch (action) { |
379 | case CPU_UP_CANCELED_FROZEN: | ||
380 | /* The CPU refused to come up during a system resume */ | ||
381 | microcode_ops->microcode_fini_cpu(cpu); | ||
382 | break; | ||
383 | case CPU_ONLINE: | 435 | case CPU_ONLINE: |
384 | case CPU_DOWN_FAILED: | ||
385 | mc_sysdev_add(sys_dev); | ||
386 | break; | ||
387 | case CPU_ONLINE_FROZEN: | 436 | case CPU_ONLINE_FROZEN: |
388 | /* System-wide resume is in progress, try to apply microcode */ | 437 | microcode_init_cpu(cpu); |
389 | if (microcode_ops->apply_microcode_check_cpu(cpu)) { | 438 | case CPU_DOWN_FAILED: |
390 | /* The application of microcode failed */ | ||
391 | microcode_ops->microcode_fini_cpu(cpu); | ||
392 | __mc_sysdev_add(sys_dev, 1); | ||
393 | break; | ||
394 | } | ||
395 | case CPU_DOWN_FAILED_FROZEN: | 439 | case CPU_DOWN_FAILED_FROZEN: |
440 | pr_debug("microcode: CPU%d added\n", cpu); | ||
396 | if (sysfs_create_group(&sys_dev->kobj, &mc_attr_group)) | 441 | if (sysfs_create_group(&sys_dev->kobj, &mc_attr_group)) |
397 | printk(KERN_ERR "microcode: Failed to create the sysfs " | 442 | printk(KERN_ERR "microcode: Failed to create the sysfs " |
398 | "group for CPU%d\n", cpu); | 443 | "group for CPU%d\n", cpu); |
399 | break; | 444 | break; |
400 | case CPU_DOWN_PREPARE: | 445 | case CPU_DOWN_PREPARE: |
401 | mc_sysdev_remove(sys_dev); | ||
402 | break; | ||
403 | case CPU_DOWN_PREPARE_FROZEN: | 446 | case CPU_DOWN_PREPARE_FROZEN: |
404 | /* Suspend is in progress, only remove the interface */ | 447 | /* Suspend is in progress, only remove the interface */ |
405 | sysfs_remove_group(&sys_dev->kobj, &mc_attr_group); | 448 | sysfs_remove_group(&sys_dev->kobj, &mc_attr_group); |
449 | pr_debug("microcode: CPU%d removed\n", cpu); | ||
450 | break; | ||
451 | case CPU_DEAD: | ||
452 | case CPU_UP_CANCELED_FROZEN: | ||
453 | /* The CPU refused to come up during a system resume */ | ||
454 | microcode_fini_cpu(cpu); | ||
406 | break; | 455 | break; |
407 | } | 456 | } |
408 | return NOTIFY_OK; | 457 | return NOTIFY_OK; |