diff options
Diffstat (limited to 'arch/i386/kernel/microcode.c')
-rw-r--r-- | arch/i386/kernel/microcode.c | 116 |
1 files changed, 116 insertions, 0 deletions
diff --git a/arch/i386/kernel/microcode.c b/arch/i386/kernel/microcode.c index 00bdc3dd693e..f8f1ec5f26c1 100644 --- a/arch/i386/kernel/microcode.c +++ b/arch/i386/kernel/microcode.c | |||
@@ -83,6 +83,9 @@ | |||
83 | #include <linux/spinlock.h> | 83 | #include <linux/spinlock.h> |
84 | #include <linux/mm.h> | 84 | #include <linux/mm.h> |
85 | #include <linux/mutex.h> | 85 | #include <linux/mutex.h> |
86 | #include <linux/cpu.h> | ||
87 | #include <linux/firmware.h> | ||
88 | #include <linux/platform_device.h> | ||
86 | 89 | ||
87 | #include <asm/msr.h> | 90 | #include <asm/msr.h> |
88 | #include <asm/uaccess.h> | 91 | #include <asm/uaccess.h> |
@@ -493,6 +496,112 @@ MODULE_ALIAS_MISCDEV(MICROCODE_MINOR); | |||
493 | #define microcode_dev_exit() do { } while(0) | 496 | #define microcode_dev_exit() do { } while(0) |
494 | #endif | 497 | #endif |
495 | 498 | ||
499 | static long get_next_ucode_from_buffer(void **mc, void *buf, | ||
500 | unsigned long size, long offset) | ||
501 | { | ||
502 | microcode_header_t *mc_header; | ||
503 | unsigned long total_size; | ||
504 | |||
505 | /* No more data */ | ||
506 | if (offset >= size) | ||
507 | return 0; | ||
508 | mc_header = (microcode_header_t *)(buf + offset); | ||
509 | total_size = get_totalsize(mc_header); | ||
510 | |||
511 | if ((offset + total_size > size) | ||
512 | || (total_size < DEFAULT_UCODE_TOTALSIZE)) { | ||
513 | printk(KERN_ERR "microcode: error! Bad data in microcode data file\n"); | ||
514 | return -EINVAL; | ||
515 | } | ||
516 | |||
517 | *mc = vmalloc(total_size); | ||
518 | if (!*mc) { | ||
519 | printk(KERN_ERR "microcode: error! Can not allocate memory\n"); | ||
520 | return -ENOMEM; | ||
521 | } | ||
522 | memcpy(*mc, buf + offset, total_size); | ||
523 | return offset + total_size; | ||
524 | } | ||
525 | |||
526 | /* fake device for request_firmware */ | ||
527 | static struct platform_device *microcode_pdev; | ||
528 | |||
529 | static int cpu_request_microcode(int cpu) | ||
530 | { | ||
531 | char name[30]; | ||
532 | struct cpuinfo_x86 *c = cpu_data + cpu; | ||
533 | const struct firmware *firmware; | ||
534 | void * buf; | ||
535 | unsigned long size; | ||
536 | long offset = 0; | ||
537 | int error; | ||
538 | void *mc; | ||
539 | |||
540 | /* We should bind the task to the CPU */ | ||
541 | BUG_ON(cpu != raw_smp_processor_id()); | ||
542 | sprintf(name,"intel-ucode/%02x-%02x-%02x", | ||
543 | c->x86, c->x86_model, c->x86_mask); | ||
544 | error = request_firmware(&firmware, name, µcode_pdev->dev); | ||
545 | if (error) { | ||
546 | pr_debug("ucode data file %s load failed\n", name); | ||
547 | return error; | ||
548 | } | ||
549 | buf = (void *)firmware->data; | ||
550 | size = firmware->size; | ||
551 | while ((offset = get_next_ucode_from_buffer(&mc, buf, size, offset)) | ||
552 | > 0) { | ||
553 | error = microcode_sanity_check(mc); | ||
554 | if (error) | ||
555 | break; | ||
556 | error = get_maching_microcode(mc, cpu); | ||
557 | if (error < 0) | ||
558 | break; | ||
559 | /* | ||
560 | * It's possible the data file has multiple matching ucode, | ||
561 | * lets keep searching till the latest version | ||
562 | */ | ||
563 | if (error == 1) { | ||
564 | apply_microcode(cpu); | ||
565 | error = 0; | ||
566 | } | ||
567 | vfree(mc); | ||
568 | } | ||
569 | if (offset > 0) | ||
570 | vfree(mc); | ||
571 | if (offset < 0) | ||
572 | error = offset; | ||
573 | release_firmware(firmware); | ||
574 | |||
575 | return error; | ||
576 | } | ||
577 | |||
578 | static void microcode_init_cpu(int cpu) | ||
579 | { | ||
580 | cpumask_t old; | ||
581 | struct ucode_cpu_info *uci = ucode_cpu_info + cpu; | ||
582 | |||
583 | old = current->cpus_allowed; | ||
584 | |||
585 | set_cpus_allowed(current, cpumask_of_cpu(cpu)); | ||
586 | mutex_lock(µcode_mutex); | ||
587 | collect_cpu_info(cpu); | ||
588 | if (uci->valid) | ||
589 | cpu_request_microcode(cpu); | ||
590 | mutex_unlock(µcode_mutex); | ||
591 | set_cpus_allowed(current, old); | ||
592 | } | ||
593 | |||
594 | static void microcode_fini_cpu(int cpu) | ||
595 | { | ||
596 | struct ucode_cpu_info *uci = ucode_cpu_info + cpu; | ||
597 | |||
598 | mutex_lock(µcode_mutex); | ||
599 | uci->valid = 0; | ||
600 | vfree(uci->mc); | ||
601 | uci->mc = NULL; | ||
602 | mutex_unlock(µcode_mutex); | ||
603 | } | ||
604 | |||
496 | static int __init microcode_init (void) | 605 | static int __init microcode_init (void) |
497 | { | 606 | { |
498 | int error; | 607 | int error; |
@@ -500,6 +609,12 @@ static int __init microcode_init (void) | |||
500 | error = microcode_dev_init(); | 609 | error = microcode_dev_init(); |
501 | if (error) | 610 | if (error) |
502 | return error; | 611 | return error; |
612 | microcode_pdev = platform_device_register_simple("microcode", -1, | ||
613 | NULL, 0); | ||
614 | if (IS_ERR(microcode_pdev)) { | ||
615 | microcode_dev_exit(); | ||
616 | return PTR_ERR(microcode_pdev); | ||
617 | } | ||
503 | 618 | ||
504 | printk(KERN_INFO | 619 | printk(KERN_INFO |
505 | "IA-32 Microcode Update Driver: v" MICROCODE_VERSION " <tigran@veritas.com>\n"); | 620 | "IA-32 Microcode Update Driver: v" MICROCODE_VERSION " <tigran@veritas.com>\n"); |
@@ -509,6 +624,7 @@ static int __init microcode_init (void) | |||
509 | static void __exit microcode_exit (void) | 624 | static void __exit microcode_exit (void) |
510 | { | 625 | { |
511 | microcode_dev_exit(); | 626 | microcode_dev_exit(); |
627 | platform_device_unregister(microcode_pdev); | ||
512 | } | 628 | } |
513 | 629 | ||
514 | module_init(microcode_init) | 630 | module_init(microcode_init) |