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) |
