Merge master.kernel.org:/pub/scm/linux/kernel/git/davej/agpgart
[pandora-kernel.git] / arch / i386 / kernel / microcode.c
index 40b44cc..c8fa137 100644 (file)
@@ -1,7 +1,8 @@
 /*
  *     Intel CPU Microcode Update Driver for Linux
  *
- *     Copyright (C) 2000-2004 Tigran Aivazian
+ *     Copyright (C) 2000-2006 Tigran Aivazian <tigran@aivazian.fsnet.co.uk>
+ *                   2006      Shaohua Li <shaohua.li@intel.com>
  *
  *     This driver allows to upgrade microcode on Intel processors
  *     belonging to IA-32 family - PentiumPro, Pentium II, 
 #include <linux/spinlock.h>
 #include <linux/mm.h>
 #include <linux/mutex.h>
+#include <linux/cpu.h>
+#include <linux/firmware.h>
+#include <linux/platform_device.h>
 
 #include <asm/msr.h>
 #include <asm/uaccess.h>
 #include <asm/processor.h>
 
 MODULE_DESCRIPTION("Intel CPU (IA-32) Microcode Update Driver");
-MODULE_AUTHOR("Tigran Aivazian <tigran@veritas.com>");
+MODULE_AUTHOR("Tigran Aivazian <tigran@aivazian.fsnet.co.uk>");
 MODULE_LICENSE("GPL");
 
-static int verbose;
-module_param(verbose, int, 0644);
-
 #define MICROCODE_VERSION      "1.14a"
 
 #define DEFAULT_UCODE_DATASIZE         (2000)    /* 2000 bytes */
@@ -120,55 +121,40 @@ static DEFINE_SPINLOCK(microcode_update_lock);
 /* no concurrent ->write()s are allowed on /dev/cpu/microcode */
 static DEFINE_MUTEX(microcode_mutex);
 
-static void __user *user_buffer;       /* user area microcode data buffer */
-static unsigned int user_buffer_size;  /* it's size */
-
-typedef enum mc_error_code {
-       MC_SUCCESS      = 0,
-       MC_IGNORED      = 1,
-       MC_NOTFOUND     = 2,
-       MC_MARKED       = 3,
-       MC_ALLOCATED    = 4,
-} mc_error_code_t;
-
 static struct ucode_cpu_info {
+       int valid;
        unsigned int sig;
-       unsigned int pf, orig_pf;
+       unsigned int pf;
        unsigned int rev;
-       unsigned int cksum;
-       mc_error_code_t err;
        microcode_t *mc;
 } ucode_cpu_info[NR_CPUS];
-                               
-static int microcode_open (struct inode *unused1, struct file *unused2)
-{
-       return capable(CAP_SYS_RAWIO) ? 0 : -EPERM;
-}
 
-static void collect_cpu_info (void *unused)
+static void collect_cpu_info(int cpu_num)
 {
-       int cpu_num = smp_processor_id();
        struct cpuinfo_x86 *c = cpu_data + cpu_num;
        struct ucode_cpu_info *uci = ucode_cpu_info + cpu_num;
        unsigned int val[2];
 
-       uci->sig = uci->pf = uci->rev = uci->cksum = 0;
-       uci->err = MC_NOTFOUND; 
+       /* We should bind the task to the CPU */
+       BUG_ON(raw_smp_processor_id() != cpu_num);
+       uci->pf = uci->rev = 0;
        uci->mc = NULL;
+       uci->valid = 1;
 
        if (c->x86_vendor != X86_VENDOR_INTEL || c->x86 < 6 ||
                cpu_has(c, X86_FEATURE_IA64)) {
-               printk(KERN_ERR "microcode: CPU%d not a capable Intel processor\n", cpu_num);
+               printk(KERN_ERR "microcode: CPU%d not a capable Intel "
+                       "processor\n", cpu_num);
+               uci->valid = 0;
                return;
-       } else {
-               uci->sig = cpuid_eax(0x00000001);
+       }
 
-               if ((c->x86_model >= 5) || (c->x86 > 6)) {
-                       /* get processor flags from MSR 0x17 */
-                       rdmsr(MSR_IA32_PLATFORM_ID, val[0], val[1]);
-                       uci->pf = 1 << ((val[1] >> 18) & 7);
-               }
-               uci->orig_pf = uci->pf;
+       uci->sig = cpuid_eax(0x00000001);
+
+       if ((c->x86_model >= 5) || (c->x86 > 6)) {
+               /* get processor flags from MSR 0x17 */
+               rdmsr(MSR_IA32_PLATFORM_ID, val[0], val[1]);
+               uci->pf = 1 << ((val[1] >> 18) & 7);
        }
 
        wrmsr(MSR_IA32_UCODE_REV, 0, 0);
@@ -180,218 +166,159 @@ static void collect_cpu_info (void *unused)
                        uci->sig, uci->pf, uci->rev);
 }
 
-static inline void mark_microcode_update (int cpu_num, microcode_header_t *mc_header, int sig, int pf, int cksum)
+static inline int microcode_update_match(int cpu_num,
+       microcode_header_t *mc_header, int sig, int pf)
 {
        struct ucode_cpu_info *uci = ucode_cpu_info + cpu_num;
 
-       pr_debug("Microcode Found.\n");
-       pr_debug("   Header Revision 0x%x\n", mc_header->hdrver);
-       pr_debug("   Loader Revision 0x%x\n", mc_header->ldrver);
-       pr_debug("   Revision 0x%x \n", mc_header->rev);
-       pr_debug("   Date %x/%x/%x\n",
-               ((mc_header->date >> 24 ) & 0xff),
-               ((mc_header->date >> 16 ) & 0xff),
-               (mc_header->date & 0xFFFF));
-       pr_debug("   Signature 0x%x\n", sig);
-       pr_debug("   Type 0x%x Family 0x%x Model 0x%x Stepping 0x%x\n",
-               ((sig >> 12) & 0x3),
-               ((sig >> 8) & 0xf),
-               ((sig >> 4) & 0xf),
-               ((sig & 0xf)));
-       pr_debug("   Processor Flags 0x%x\n", pf);
-       pr_debug("   Checksum 0x%x\n", cksum);
-
-       if (mc_header->rev < uci->rev) {
-               if (uci->err == MC_NOTFOUND) {
-                       uci->err = MC_IGNORED;
-                       uci->cksum = mc_header->rev;
-               } else if (uci->err == MC_IGNORED && uci->cksum < mc_header->rev)
-                       uci->cksum = mc_header->rev;
-       } else if (mc_header->rev == uci->rev) {
-               if (uci->err < MC_MARKED) {
-                       /* notify the caller of success on this cpu */
-                       uci->err = MC_SUCCESS;
-               }
-       } else if (uci->err != MC_ALLOCATED || mc_header->rev > uci->mc->hdr.rev) {
-               pr_debug("microcode: CPU%d found a matching microcode update with "
-                       " revision 0x%x (current=0x%x)\n", cpu_num, mc_header->rev, uci->rev);
-               uci->cksum = cksum;
-               uci->pf = pf; /* keep the original mc pf for cksum calculation */
-               uci->err = MC_MARKED; /* found the match */
-               for_each_online_cpu(cpu_num) {
-                       if (ucode_cpu_info + cpu_num != uci
-                           && ucode_cpu_info[cpu_num].mc == uci->mc) {
-                               uci->mc = NULL;
-                               break;
-                       }
-               }
-               if (uci->mc != NULL) {
-                       vfree(uci->mc);
-                       uci->mc = NULL;
-               }
-       }
-       return;
+       if (!sigmatch(sig, uci->sig, pf, uci->pf)
+               || mc_header->rev <= uci->rev)
+               return 0;
+       return 1;
 }
 
-static int find_matching_ucodes (void) 
+static int microcode_sanity_check(void *mc)
 {
-       int cursor = 0;
-       int error = 0;
-
-       while (cursor + MC_HEADER_SIZE < user_buffer_size) {
-               microcode_header_t mc_header;
-               void *newmc = NULL;
-               int i, sum, cpu_num, allocated_flag, total_size, data_size, ext_table_size;
+       microcode_header_t *mc_header = mc;
+       struct extended_sigtable *ext_header = NULL;
+       struct extended_signature *ext_sig;
+       unsigned long total_size, data_size, ext_table_size;
+       int sum, orig_sum, ext_sigcount = 0, i;
+
+       total_size = get_totalsize(mc_header);
+       data_size = get_datasize(mc_header);
+       if (data_size + MC_HEADER_SIZE > total_size) {
+               printk(KERN_ERR "microcode: error! "
+                       "Bad data size in microcode data file\n");
+               return -EINVAL;
+       }
 
-               if (copy_from_user(&mc_header, user_buffer + cursor, MC_HEADER_SIZE)) {
-                       printk(KERN_ERR "microcode: error! Can not read user data\n");
-                       error = -EFAULT;
-                       goto out;
+       if (mc_header->ldrver != 1 || mc_header->hdrver != 1) {
+               printk(KERN_ERR "microcode: error! "
+                       "Unknown microcode update format\n");
+               return -EINVAL;
+       }
+       ext_table_size = total_size - (MC_HEADER_SIZE + data_size);
+       if (ext_table_size) {
+               if ((ext_table_size < EXT_HEADER_SIZE)
+                || ((ext_table_size - EXT_HEADER_SIZE) % EXT_SIGNATURE_SIZE)) {
+                       printk(KERN_ERR "microcode: error! "
+                               "Small exttable size in microcode data file\n");
+                       return -EINVAL;
                }
-
-               total_size = get_totalsize(&mc_header);
-               if ((cursor + total_size > user_buffer_size) || (total_size < DEFAULT_UCODE_TOTALSIZE)) {
-                       printk(KERN_ERR "microcode: error! Bad data in microcode data file\n");
-                       error = -EINVAL;
-                       goto out;
+               ext_header = mc + MC_HEADER_SIZE + data_size;
+               if (ext_table_size != exttable_size(ext_header)) {
+                       printk(KERN_ERR "microcode: error! "
+                               "Bad exttable size in microcode data file\n");
+                       return -EFAULT;
                }
+               ext_sigcount = ext_header->count;
+       }
 
-               data_size = get_datasize(&mc_header);
-               if ((data_size + MC_HEADER_SIZE > total_size) || (data_size < DEFAULT_UCODE_DATASIZE)) {
-                       printk(KERN_ERR "microcode: error! Bad data in microcode data file\n");
-                       error = -EINVAL;
-                       goto out;
+       /* check extended table checksum */
+       if (ext_table_size) {
+               int ext_table_sum = 0;
+               int *ext_tablep = (int *)ext_header;
+
+               i = ext_table_size / DWSIZE;
+               while (i--)
+                       ext_table_sum += ext_tablep[i];
+               if (ext_table_sum) {
+                       printk(KERN_WARNING "microcode: aborting, "
+                               "bad extended signature table checksum\n");
+                       return -EINVAL;
                }
+       }
 
-               if (mc_header.ldrver != 1 || mc_header.hdrver != 1) {
-                       printk(KERN_ERR "microcode: error! Unknown microcode update format\n");
-                       error = -EINVAL;
-                       goto out;
+       /* calculate the checksum */
+       orig_sum = 0;
+       i = (MC_HEADER_SIZE + data_size) / DWSIZE;
+       while (i--)
+               orig_sum += ((int *)mc)[i];
+       if (orig_sum) {
+               printk(KERN_ERR "microcode: aborting, bad checksum\n");
+               return -EINVAL;
+       }
+       if (!ext_table_size)
+               return 0;
+       /* check extended signature checksum */
+       for (i = 0; i < ext_sigcount; i++) {
+               ext_sig = (struct extended_signature *)((void *)ext_header
+                       + EXT_HEADER_SIZE + EXT_SIGNATURE_SIZE * i);
+               sum = orig_sum
+                       - (mc_header->sig + mc_header->pf + mc_header->cksum)
+                       + (ext_sig->sig + ext_sig->pf + ext_sig->cksum);
+               if (sum) {
+                       printk(KERN_ERR "microcode: aborting, bad checksum\n");
+                       return -EINVAL;
                }
+       }
+       return 0;
+}
 
-               for_each_online_cpu(cpu_num) {
-                       struct ucode_cpu_info *uci = ucode_cpu_info + cpu_num;
-
-                       if (sigmatch(mc_header.sig, uci->sig, mc_header.pf, uci->orig_pf))
-                               mark_microcode_update(cpu_num, &mc_header, mc_header.sig, mc_header.pf, mc_header.cksum);
-               }
+/*
+ * return 0 - no update found
+ * return 1 - found update
+ * return < 0 - error
+ */
+static int get_maching_microcode(void *mc, int cpu)
+{
+       struct ucode_cpu_info *uci = ucode_cpu_info + cpu;
+       microcode_header_t *mc_header = mc;
+       struct extended_sigtable *ext_header;
+       unsigned long total_size = get_totalsize(mc_header);
+       int ext_sigcount, i;
+       struct extended_signature *ext_sig;
+       void *new_mc;
+
+       if (microcode_update_match(cpu, mc_header,
+                       mc_header->sig, mc_header->pf))
+               goto find;
+
+       if (total_size <= get_datasize(mc_header) + MC_HEADER_SIZE)
+               return 0;
+
+       ext_header = (struct extended_sigtable *)(mc +
+                       get_datasize(mc_header) + MC_HEADER_SIZE);
+       ext_sigcount = ext_header->count;
+       ext_sig = (struct extended_signature *)((void *)ext_header
+                       + EXT_HEADER_SIZE);
+       for (i = 0; i < ext_sigcount; i++) {
+               if (microcode_update_match(cpu, mc_header,
+                               ext_sig->sig, ext_sig->pf))
+                       goto find;
+               ext_sig++;
+       }
+       return 0;
+find:
+       pr_debug("microcode: CPU %d found a matching microcode update with"
+               " version 0x%x (current=0x%x)\n", cpu, mc_header->rev,uci->rev);
+       new_mc = vmalloc(total_size);
+       if (!new_mc) {
+               printk(KERN_ERR "microcode: error! Can not allocate memory\n");
+               return -ENOMEM;
+       }
 
-               ext_table_size = total_size - (MC_HEADER_SIZE + data_size);
-               if (ext_table_size) {
-                       struct extended_sigtable ext_header;
-                       struct extended_signature ext_sig;
-                       int ext_sigcount;
+       /* free previous update file */
+       vfree(uci->mc);
 
-                       if ((ext_table_size < EXT_HEADER_SIZE) 
-                                       || ((ext_table_size - EXT_HEADER_SIZE) % EXT_SIGNATURE_SIZE)) {
-                               printk(KERN_ERR "microcode: error! Bad data in microcode data file\n");
-                               error = -EINVAL;
-                               goto out;
-                       }
-                       if (copy_from_user(&ext_header, user_buffer + cursor 
-                                       + MC_HEADER_SIZE + data_size, EXT_HEADER_SIZE)) {
-                               printk(KERN_ERR "microcode: error! Can not read user data\n");
-                               error = -EFAULT;
-                               goto out;
-                       }
-                       if (ext_table_size != exttable_size(&ext_header)) {
-                               printk(KERN_ERR "microcode: error! Bad data in microcode data file\n");
-                               error = -EFAULT;
-                               goto out;
-                       }
-
-                       ext_sigcount = ext_header.count;
-                       
-                       for (i = 0; i < ext_sigcount; i++) {
-                               if (copy_from_user(&ext_sig, user_buffer + cursor + MC_HEADER_SIZE + data_size + EXT_HEADER_SIZE 
-                                               + EXT_SIGNATURE_SIZE * i, EXT_SIGNATURE_SIZE)) {
-                                       printk(KERN_ERR "microcode: error! Can not read user data\n");
-                                       error = -EFAULT;
-                                       goto out;
-                               }
-                               for_each_online_cpu(cpu_num) {
-                                       struct ucode_cpu_info *uci = ucode_cpu_info + cpu_num;
-
-                                       if (sigmatch(ext_sig.sig, uci->sig, ext_sig.pf, uci->orig_pf)) {
-                                               mark_microcode_update(cpu_num, &mc_header, ext_sig.sig, ext_sig.pf, ext_sig.cksum);
-                                       }
-                               }
-                       }
-               }
-               /* now check if any cpu has matched */
-               allocated_flag = 0;
-               sum = 0;
-               for_each_online_cpu(cpu_num) {
-                       if (ucode_cpu_info[cpu_num].err == MC_MARKED) { 
-                               struct ucode_cpu_info *uci = ucode_cpu_info + cpu_num;
-                               if (!allocated_flag) {
-                                       allocated_flag = 1;
-                                       newmc = vmalloc(total_size);
-                                       if (!newmc) {
-                                               printk(KERN_ERR "microcode: error! Can not allocate memory\n");
-                                               error = -ENOMEM;
-                                               goto out;
-                                       }
-                                       if (copy_from_user(newmc + MC_HEADER_SIZE, 
-                                                               user_buffer + cursor + MC_HEADER_SIZE, 
-                                                               total_size - MC_HEADER_SIZE)) {
-                                               printk(KERN_ERR "microcode: error! Can not read user data\n");
-                                               vfree(newmc);
-                                               error = -EFAULT;
-                                               goto out;
-                                       }
-                                       memcpy(newmc, &mc_header, MC_HEADER_SIZE);
-                                       /* check extended table checksum */
-                                       if (ext_table_size) {
-                                               int ext_table_sum = 0;
-                                               int * ext_tablep = (((void *) newmc) + MC_HEADER_SIZE + data_size);
-                                               i = ext_table_size / DWSIZE;
-                                               while (i--) ext_table_sum += ext_tablep[i];
-                                               if (ext_table_sum) {
-                                                       printk(KERN_WARNING "microcode: aborting, bad extended signature table checksum\n");
-                                                       vfree(newmc);
-                                                       error = -EINVAL;
-                                                       goto out;
-                                               }
-                                       }
-
-                                       /* calculate the checksum */
-                                       i = (MC_HEADER_SIZE + data_size) / DWSIZE;
-                                       while (i--) sum += ((int *)newmc)[i];
-                                       sum -= (mc_header.sig + mc_header.pf + mc_header.cksum);
-                               }
-                               ucode_cpu_info[cpu_num].mc = newmc;
-                               ucode_cpu_info[cpu_num].err = MC_ALLOCATED; /* mc updated */
-                               if (sum + uci->sig + uci->pf + uci->cksum != 0) {
-                                       printk(KERN_ERR "microcode: CPU%d aborting, bad checksum\n", cpu_num);
-                                       error = -EINVAL;
-                                       goto out;
-                               }
-                       }
-               }
-               cursor += total_size; /* goto the next update patch */
-       } /* end of while */
-out:
-       return error;
+       memcpy(new_mc, mc, total_size);
+       uci->mc = new_mc;
+       return 1;
 }
 
-static void do_update_one (void * unused)
+static void apply_microcode(int cpu)
 {
        unsigned long flags;
        unsigned int val[2];
-       int cpu_num = smp_processor_id();
+       int cpu_num = raw_smp_processor_id();
        struct ucode_cpu_info *uci = ucode_cpu_info + cpu_num;
 
-       if (uci->mc == NULL) {
-               if (verbose) {
-                       if (uci->err == MC_SUCCESS)
-                               printk(KERN_INFO "microcode: CPU%d already at revision 0x%x\n",
-                                       cpu_num, uci->rev);
-                       else
-                               printk(KERN_INFO "microcode: No new microcode data for CPU%d\n", cpu_num);
-               }
+       /* We should bind the task to the CPU */
+       BUG_ON(cpu_num != cpu);
+
+       if (uci->mc == NULL)
                return;
-       }
 
        /* serialize access to the physical write to MSR 0x79 */
        spin_lock_irqsave(&microcode_update_lock, flags);          
@@ -408,68 +335,107 @@ static void do_update_one (void * unused)
        /* get the current revision from MSR 0x8B */
        rdmsr(MSR_IA32_UCODE_REV, val[0], val[1]);
 
-       /* notify the caller of success on this cpu */
-       uci->err = MC_SUCCESS;
        spin_unlock_irqrestore(&microcode_update_lock, flags);
-       printk(KERN_INFO "microcode: CPU%d updated from revision "
+       if (val[1] != uci->mc->hdr.rev) {
+               printk(KERN_ERR "microcode: CPU%d updated from revision "
+                       "0x%x to 0x%x failed\n", cpu_num, uci->rev, val[1]);
+               return;
+       }
+       pr_debug("microcode: CPU%d updated from revision "
               "0x%x to 0x%x, date = %08x \n", 
               cpu_num, uci->rev, val[1], uci->mc->hdr.date);
-       return;
+       uci->rev = val[1];
 }
 
-static int do_microcode_update (void)
-{
-       int i, error;
+#ifdef CONFIG_MICROCODE_OLD_INTERFACE
+static void __user *user_buffer;       /* user area microcode data buffer */
+static unsigned int user_buffer_size;  /* it's size */
 
-       if (on_each_cpu(collect_cpu_info, NULL, 1, 1) != 0) {
-               printk(KERN_ERR "microcode: Error! Could not run on all processors\n");
-               error = -EIO;
-               goto out;
+static long get_next_ucode(void **mc, long offset)
+{
+       microcode_header_t mc_header;
+       unsigned long total_size;
+
+       /* No more data */
+       if (offset >= user_buffer_size)
+               return 0;
+       if (copy_from_user(&mc_header, user_buffer + offset, MC_HEADER_SIZE)) {
+               printk(KERN_ERR "microcode: error! Can not read user data\n");
+               return -EFAULT;
        }
-
-       if ((error = find_matching_ucodes())) {
-               printk(KERN_ERR "microcode: Error in the microcode data\n");
-               goto out_free;
+       total_size = get_totalsize(&mc_header);
+       if (offset + total_size > user_buffer_size) {
+               printk(KERN_ERR "microcode: error! Bad total size in microcode "
+                               "data file\n");
+               return -EINVAL;
        }
-
-       if (on_each_cpu(do_update_one, NULL, 1, 1) != 0) {
-               printk(KERN_ERR "microcode: Error! Could not run on all processors\n");
-               error = -EIO;
+       *mc = vmalloc(total_size);
+       if (!*mc)
+               return -ENOMEM;
+       if (copy_from_user(*mc, user_buffer + offset, total_size)) {
+               printk(KERN_ERR "microcode: error! Can not read user data\n");
+               vfree(*mc);
+               return -EFAULT;
        }
+       return offset + total_size;
+}
+
+static int do_microcode_update (void)
+{
+       long cursor = 0;
+       int error = 0;
+       void *new_mc;
+       int cpu;
+       cpumask_t old;
+
+       old = current->cpus_allowed;
 
-out_free:
-       for_each_online_cpu(i) {
-               if (ucode_cpu_info[i].mc) {
-                       int j;
-                       void *tmp = ucode_cpu_info[i].mc;
-                       vfree(tmp);
-                       for_each_online_cpu(j) {
-                               if (ucode_cpu_info[j].mc == tmp)
-                                       ucode_cpu_info[j].mc = NULL;
-                       }
+       while ((cursor = get_next_ucode(&new_mc, cursor)) > 0) {
+               error = microcode_sanity_check(new_mc);
+               if (error)
+                       goto out;
+               /*
+                * It's possible the data file has multiple matching ucode,
+                * lets keep searching till the latest version
+                */
+               for_each_online_cpu(cpu) {
+                       struct ucode_cpu_info *uci = ucode_cpu_info + cpu;
+
+                       if (!uci->valid)
+                               continue;
+                       set_cpus_allowed(current, cpumask_of_cpu(cpu));
+                       error = get_maching_microcode(new_mc, cpu);
+                       if (error < 0)
+                               goto out;
+                       if (error == 1)
+                               apply_microcode(cpu);
                }
-               if (ucode_cpu_info[i].err == MC_IGNORED && verbose)
-                       printk(KERN_WARNING "microcode: CPU%d not 'upgrading' to earlier revision"
-                              " 0x%x (current=0x%x)\n", i, ucode_cpu_info[i].cksum, ucode_cpu_info[i].rev);
+               vfree(new_mc);
        }
 out:
+       if (cursor > 0)
+               vfree(new_mc);
+       if (cursor < 0)
+               error = cursor;
+       set_cpus_allowed(current, old);
        return error;
 }
 
+static int microcode_open (struct inode *unused1, struct file *unused2)
+{
+       return capable(CAP_SYS_RAWIO) ? 0 : -EPERM;
+}
+
 static ssize_t microcode_write (struct file *file, const char __user *buf, size_t len, loff_t *ppos)
 {
        ssize_t ret;
 
-       if (len < DEFAULT_UCODE_TOTALSIZE) {
-               printk(KERN_ERR "microcode: not enough data\n"); 
-               return -EINVAL;
-       }
-
        if ((len >> PAGE_SHIFT) > num_physpages) {
                printk(KERN_ERR "microcode: too much data (max %ld pages)\n", num_physpages);
                return -EINVAL;
        }
 
+       lock_cpu_hotplug();
        mutex_lock(&microcode_mutex);
 
        user_buffer = (void __user *) buf;
@@ -480,6 +446,7 @@ static ssize_t microcode_write (struct file *file, const char __user *buf, size_
                ret = (ssize_t)len;
 
        mutex_unlock(&microcode_mutex);
+       unlock_cpu_hotplug();
 
        return ret;
 }
@@ -496,7 +463,7 @@ static struct miscdevice microcode_dev = {
        .fops           = &microcode_fops,
 };
 
-static int __init microcode_init (void)
+static int __init microcode_dev_init (void)
 {
        int error;
 
@@ -508,16 +475,299 @@ static int __init microcode_init (void)
                return error;
        }
 
+       return 0;
+}
+
+static void __exit microcode_dev_exit (void)
+{
+       misc_deregister(&microcode_dev);
+}
+
+MODULE_ALIAS_MISCDEV(MICROCODE_MINOR);
+#else
+#define microcode_dev_init() 0
+#define microcode_dev_exit() do { } while(0)
+#endif
+
+static long get_next_ucode_from_buffer(void **mc, void *buf,
+       unsigned long size, long offset)
+{
+       microcode_header_t *mc_header;
+       unsigned long total_size;
+
+       /* No more data */
+       if (offset >= size)
+               return 0;
+       mc_header = (microcode_header_t *)(buf + offset);
+       total_size = get_totalsize(mc_header);
+
+       if (offset + total_size > size) {
+               printk(KERN_ERR "microcode: error! Bad data in microcode data file\n");
+               return -EINVAL;
+       }
+
+       *mc = vmalloc(total_size);
+       if (!*mc) {
+               printk(KERN_ERR "microcode: error! Can not allocate memory\n");
+               return -ENOMEM;
+       }
+       memcpy(*mc, buf + offset, total_size);
+       return offset + total_size;
+}
+
+/* fake device for request_firmware */
+static struct platform_device *microcode_pdev;
+
+static int cpu_request_microcode(int cpu)
+{
+       char name[30];
+       struct cpuinfo_x86 *c = cpu_data + cpu;
+       const struct firmware *firmware;
+       void *buf;
+       unsigned long size;
+       long offset = 0;
+       int error;
+       void *mc;
+
+       /* We should bind the task to the CPU */
+       BUG_ON(cpu != raw_smp_processor_id());
+       sprintf(name,"intel-ucode/%02x-%02x-%02x",
+               c->x86, c->x86_model, c->x86_mask);
+       error = request_firmware(&firmware, name, &microcode_pdev->dev);
+       if (error) {
+               pr_debug("ucode data file %s load failed\n", name);
+               return error;
+       }
+       buf = (void *)firmware->data;
+       size = firmware->size;
+       while ((offset = get_next_ucode_from_buffer(&mc, buf, size, offset))
+                       > 0) {
+               error = microcode_sanity_check(mc);
+               if (error)
+                       break;
+               error = get_maching_microcode(mc, cpu);
+               if (error < 0)
+                       break;
+               /*
+                * It's possible the data file has multiple matching ucode,
+                * lets keep searching till the latest version
+                */
+               if (error == 1) {
+                       apply_microcode(cpu);
+                       error = 0;
+               }
+               vfree(mc);
+       }
+       if (offset > 0)
+               vfree(mc);
+       if (offset < 0)
+               error = offset;
+       release_firmware(firmware);
+
+       return error;
+}
+
+static void microcode_init_cpu(int cpu)
+{
+       cpumask_t old;
+       struct ucode_cpu_info *uci = ucode_cpu_info + cpu;
+
+       old = current->cpus_allowed;
+
+       set_cpus_allowed(current, cpumask_of_cpu(cpu));
+       mutex_lock(&microcode_mutex);
+       collect_cpu_info(cpu);
+       if (uci->valid && system_state == SYSTEM_RUNNING)
+               cpu_request_microcode(cpu);
+       mutex_unlock(&microcode_mutex);
+       set_cpus_allowed(current, old);
+}
+
+static void microcode_fini_cpu(int cpu)
+{
+       struct ucode_cpu_info *uci = ucode_cpu_info + cpu;
+
+       mutex_lock(&microcode_mutex);
+       uci->valid = 0;
+       vfree(uci->mc);
+       uci->mc = NULL;
+       mutex_unlock(&microcode_mutex);
+}
+
+static ssize_t reload_store(struct sys_device *dev, const char *buf, size_t sz)
+{
+       struct ucode_cpu_info *uci = ucode_cpu_info + dev->id;
+       char *end;
+       unsigned long val = simple_strtoul(buf, &end, 0);
+       int err = 0;
+       int cpu = dev->id;
+
+       if (end == buf)
+               return -EINVAL;
+       if (val == 1) {
+               cpumask_t old;
+
+               old = current->cpus_allowed;
+
+               lock_cpu_hotplug();
+               set_cpus_allowed(current, cpumask_of_cpu(cpu));
+
+               mutex_lock(&microcode_mutex);
+               if (uci->valid)
+                       err = cpu_request_microcode(cpu);
+               mutex_unlock(&microcode_mutex);
+               unlock_cpu_hotplug();
+               set_cpus_allowed(current, old);
+       }
+       if (err)
+               return err;
+       return sz;
+}
+
+static ssize_t version_show(struct sys_device *dev, char *buf)
+{
+       struct ucode_cpu_info *uci = ucode_cpu_info + dev->id;
+
+       return sprintf(buf, "0x%x\n", uci->rev);
+}
+
+static ssize_t pf_show(struct sys_device *dev, char *buf)
+{
+       struct ucode_cpu_info *uci = ucode_cpu_info + dev->id;
+
+       return sprintf(buf, "0x%x\n", uci->pf);
+}
+
+static SYSDEV_ATTR(reload, 0200, NULL, reload_store);
+static SYSDEV_ATTR(version, 0400, version_show, NULL);
+static SYSDEV_ATTR(processor_flags, 0400, pf_show, NULL);
+
+static struct attribute *mc_default_attrs[] = {
+       &attr_reload.attr,
+       &attr_version.attr,
+       &attr_processor_flags.attr,
+       NULL
+};
+
+static struct attribute_group mc_attr_group = {
+       .attrs = mc_default_attrs,
+       .name = "microcode",
+};
+
+static int mc_sysdev_add(struct sys_device *sys_dev)
+{
+       int err, cpu = sys_dev->id;
+       struct ucode_cpu_info *uci = ucode_cpu_info + cpu;
+
+       if (!cpu_online(cpu))
+               return 0;
+
+       pr_debug("Microcode:CPU %d added\n", cpu);
+       memset(uci, 0, sizeof(*uci));
+
+       err = sysfs_create_group(&sys_dev->kobj, &mc_attr_group);
+       if (err)
+               return err;
+
+       microcode_init_cpu(cpu);
+       return 0;
+}
+
+static int mc_sysdev_remove(struct sys_device *sys_dev)
+{
+       int cpu = sys_dev->id;
+
+       if (!cpu_online(cpu))
+               return 0;
+       pr_debug("Microcode:CPU %d removed\n", cpu);
+       microcode_fini_cpu(cpu);
+       sysfs_remove_group(&sys_dev->kobj, &mc_attr_group);
+       return 0;
+}
+
+static int mc_sysdev_resume(struct sys_device *dev)
+{
+       int cpu = dev->id;
+
+       if (!cpu_online(cpu))
+               return 0;
+       pr_debug("Microcode:CPU %d resumed\n", cpu);
+       /* only CPU 0 will apply ucode here */
+       apply_microcode(0);
+       return 0;
+}
+
+static struct sysdev_driver mc_sysdev_driver = {
+       .add = mc_sysdev_add,
+       .remove = mc_sysdev_remove,
+       .resume = mc_sysdev_resume,
+};
+
+static __cpuinit int
+mc_cpu_callback(struct notifier_block *nb, unsigned long action, void *hcpu)
+{
+       unsigned int cpu = (unsigned long)hcpu;
+       struct sys_device *sys_dev;
+
+       sys_dev = get_cpu_sysdev(cpu);
+       switch (action) {
+       case CPU_ONLINE:
+       case CPU_DOWN_FAILED:
+               mc_sysdev_add(sys_dev);
+               break;
+       case CPU_DOWN_PREPARE:
+               mc_sysdev_remove(sys_dev);
+               break;
+       }
+       return NOTIFY_OK;
+}
+
+static struct notifier_block __cpuinitdata mc_cpu_notifier = {
+       .notifier_call = mc_cpu_callback,
+};
+
+static int __init microcode_init (void)
+{
+       int error;
+
+       error = microcode_dev_init();
+       if (error)
+               return error;
+       microcode_pdev = platform_device_register_simple("microcode", -1,
+                                                        NULL, 0);
+       if (IS_ERR(microcode_pdev)) {
+               microcode_dev_exit();
+               return PTR_ERR(microcode_pdev);
+       }
+
+       lock_cpu_hotplug();
+       error = sysdev_driver_register(&cpu_sysdev_class, &mc_sysdev_driver);
+       unlock_cpu_hotplug();
+       if (error) {
+               microcode_dev_exit();
+               platform_device_unregister(microcode_pdev);
+               return error;
+       }
+
+       register_hotcpu_notifier(&mc_cpu_notifier);
+
        printk(KERN_INFO 
-               "IA-32 Microcode Update Driver: v" MICROCODE_VERSION " <tigran@veritas.com>\n");
+               "IA-32 Microcode Update Driver: v" MICROCODE_VERSION " <tigran@aivazian.fsnet.co.uk>\n");
        return 0;
 }
 
 static void __exit microcode_exit (void)
 {
-       misc_deregister(&microcode_dev);
+       microcode_dev_exit();
+
+       unregister_hotcpu_notifier(&mc_cpu_notifier);
+
+       lock_cpu_hotplug();
+       sysdev_driver_unregister(&cpu_sysdev_class, &mc_sysdev_driver);
+       unlock_cpu_hotplug();
+
+       platform_device_unregister(microcode_pdev);
 }
 
 module_init(microcode_init)
 module_exit(microcode_exit)
-MODULE_ALIAS_MISCDEV(MICROCODE_MINOR);