#include <linux/ctype.h>
 #include <linux/kmod.h>
 #include <linux/kdebug.h>
+#include <linux/kobject.h>
+#include <linux/sysfs.h>
 #include <asm/processor.h>
 #include <asm/msr.h>
 #include <asm/mce.h>
 #include <asm/idle.h>
 
 #define MISC_MCELOG_MINOR 227
-#define NR_SYSFS_BANKS 6
+
+/*
+ * To support more than 128 would need to escape the predefined
+ * Linux defined extended banks first.
+ */
+#define MAX_NR_BANKS (MCE_EXTENDED_BANK - 1)
 
 atomic_t mce_entry;
 
  */
 static int tolerant = 1;
 static int banks;
-static unsigned long bank[NR_SYSFS_BANKS] = { [0 ... NR_SYSFS_BANKS-1] = ~0UL };
+static u64 *bank;
 static unsigned long notify_user;
 static int rip_msr;
 static int mce_bootlog = -1;
        barrier();
 
        for (i = 0; i < banks; i++) {
-               if (i < NR_SYSFS_BANKS && !bank[i])
+               if (!bank[i])
                        continue;
 
                m.misc = 0;
 /*
  * Initialize Machine Checks for a CPU.
  */
-static void mce_init(void *dummy)
+static int mce_cap_init(void)
 {
        u64 cap;
-       int i;
+       unsigned b;
 
        rdmsrl(MSR_IA32_MCG_CAP, cap);
-       banks = cap & 0xff;
-       if (banks > MCE_EXTENDED_BANK) {
-               banks = MCE_EXTENDED_BANK;
-               printk(KERN_INFO "MCE: warning: using only %d banks\n",
-                      MCE_EXTENDED_BANK);
+       b = cap & 0xff;
+       if (b > MAX_NR_BANKS) {
+               printk(KERN_WARNING
+                      "MCE: Using only %u machine check banks out of %u\n",
+                       MAX_NR_BANKS, b);
+               b = MAX_NR_BANKS;
+       }
+
+       /* Don't support asymmetric configurations today */
+       WARN_ON(banks != 0 && b != banks);
+       banks = b;
+       if (!bank) {
+               bank = kmalloc(banks * sizeof(u64), GFP_KERNEL);
+               if (!bank)
+                       return -ENOMEM;
+               memset(bank, 0xff, banks * sizeof(u64));
        }
+
        /* Use accurate RIP reporting if available. */
        if ((cap & (1<<9)) && ((cap >> 16) & 0xff) >= 9)
                rip_msr = MSR_IA32_MCG_EIP;
 
+       return 0;
+}
+
+static void mce_init(void *dummy)
+{
+       u64 cap;
+       int i;
+
        /* Log the machine checks left over from the previous reset.
           This also clears all registers */
        do_machine_check(NULL, mce_bootlog ? -1 : -2);
 
        set_in_cr4(X86_CR4_MCE);
 
+       rdmsrl(MSR_IA32_MCG_CAP, cap);
        if (cap & MCG_CTL_P)
                wrmsr(MSR_IA32_MCG_CTL, 0xffffffff, 0xffffffff);
 
        for (i = 0; i < banks; i++) {
-               if (i < NR_SYSFS_BANKS)
-                       wrmsrl(MSR_IA32_MC0_CTL+4*i, bank[i]);
-               else
-                       wrmsrl(MSR_IA32_MC0_CTL+4*i, ~0UL);
-
+               wrmsrl(MSR_IA32_MC0_CTL+4*i, bank[i]);
                wrmsrl(MSR_IA32_MC0_STATUS+4*i, 0);
        }
 }
 {
        /* This should be disabled by the BIOS, but isn't always */
        if (c->x86_vendor == X86_VENDOR_AMD) {
-               if(c->x86 == 15)
+               if (c->x86 == 15 && banks > 4)
                        /* disable GART TBL walk error reporting, which trips off
                           incorrectly with the IOMMU & 3ware & Cerberus. */
-                       clear_bit(10, &bank[4]);
+                       clear_bit(10, (unsigned long *)&bank[4]);
                if(c->x86 <= 17 && mce_bootlog < 0)
                        /* Lots of broken BIOS around that don't clear them
                           by default and leave crap in there. Don't log. */
  */
 void __cpuinit mcheck_init(struct cpuinfo_x86 *c)
 {
-       mce_cpu_quirks(c);
-
        if (!mce_available(c))
                return;
 
+       if (mce_cap_init() < 0) {
+               mce_dont_init = 1;
+               return;
+       }
+       mce_cpu_quirks(c);
+
        mce_init(NULL);
        mce_cpu_features(c);
        mce_init_timer();
        }                                                               \
        static SYSDEV_ATTR(name, 0644, show_ ## name, set_ ## name);
 
-/*
- * TBD should generate these dynamically based on number of available banks.
- * Have only 6 contol banks in /sysfs until then.
- */
-ACCESSOR(bank0ctl,bank[0],mce_restart())
-ACCESSOR(bank1ctl,bank[1],mce_restart())
-ACCESSOR(bank2ctl,bank[2],mce_restart())
-ACCESSOR(bank3ctl,bank[3],mce_restart())
-ACCESSOR(bank4ctl,bank[4],mce_restart())
-ACCESSOR(bank5ctl,bank[5],mce_restart())
+static struct sysdev_attribute *bank_attrs;
+
+static ssize_t show_bank(struct sys_device *s, struct sysdev_attribute *attr,
+                        char *buf)
+{
+       u64 b = bank[attr - bank_attrs];
+       return sprintf(buf, "%Lx\n", b);
+}
+
+static ssize_t set_bank(struct sys_device *s, struct sysdev_attribute *attr,
+                       const char *buf, size_t siz)
+{
+       char *end;
+       u64 new = simple_strtoull(buf, &end, 0);
+       if (end == buf)
+               return -EINVAL;
+       bank[attr - bank_attrs] = new;
+       mce_restart();
+       return end-buf;
+}
 
 static ssize_t show_trigger(struct sys_device *s, struct sysdev_attribute *attr,
                                char *buf)
 static SYSDEV_INT_ATTR(tolerant, 0644, tolerant);
 ACCESSOR(check_interval,check_interval,mce_restart())
 static struct sysdev_attribute *mce_attributes[] = {
-       &attr_bank0ctl, &attr_bank1ctl, &attr_bank2ctl,
-       &attr_bank3ctl, &attr_bank4ctl, &attr_bank5ctl,
        &attr_tolerant.attr, &attr_check_interval, &attr_trigger,
        NULL
 };
                if (err)
                        goto error;
        }
+       for (i = 0; i < banks; i++) {
+               err = sysdev_create_file(&per_cpu(device_mce, cpu),
+                                       &bank_attrs[i]);
+               if (err)
+                       goto error2;
+       }
        cpu_set(cpu, mce_device_initialized);
 
        return 0;
+error2:
+       while (--i >= 0) {
+               sysdev_remove_file(&per_cpu(device_mce, cpu),
+                                       &bank_attrs[i]);
+       }
 error:
-       while (i--) {
+       while (--i >= 0) {
                sysdev_remove_file(&per_cpu(device_mce,cpu),
                                   mce_attributes[i]);
        }
        for (i = 0; mce_attributes[i]; i++)
                sysdev_remove_file(&per_cpu(device_mce,cpu),
                        mce_attributes[i]);
+       for (i = 0; i < banks; i++)
+               sysdev_remove_file(&per_cpu(device_mce, cpu),
+                       &bank_attrs[i]);
        sysdev_unregister(&per_cpu(device_mce,cpu));
        cpu_clear(cpu, mce_device_initialized);
 }
        .notifier_call = mce_cpu_callback,
 };
 
+static __init int mce_init_banks(void)
+{
+       int i;
+
+       bank_attrs = kzalloc(sizeof(struct sysdev_attribute) * banks,
+                               GFP_KERNEL);
+       if (!bank_attrs)
+               return -ENOMEM;
+
+       for (i = 0; i < banks; i++) {
+               struct sysdev_attribute *a = &bank_attrs[i];
+               a->attr.name = kasprintf(GFP_KERNEL, "bank%d", i);
+               if (!a->attr.name)
+                       goto nomem;
+               a->attr.mode = 0644;
+               a->show = show_bank;
+               a->store = set_bank;
+       }
+       return 0;
+
+nomem:
+       while (--i >= 0)
+               kfree(bank_attrs[i].attr.name);
+       kfree(bank_attrs);
+       bank_attrs = NULL;
+       return -ENOMEM;
+}
+
 static __init int mce_init_device(void)
 {
        int err;
 
        if (!mce_available(&boot_cpu_data))
                return -EIO;
+
+       err = mce_init_banks();
+       if (err)
+               return err;
+
        err = sysdev_class_register(&mce_sysclass);
        if (err)
                return err;