| /* | |
| * (c) 2005, 2006 Advanced Micro Devices, Inc. | |
| * Your use of this code is subject to the terms and conditions of the | |
| * GNU general public license version 2. See "COPYING" or | |
| * http://www.gnu.org/licenses/gpl.html | |
| * | |
| * Written by Jacob Shin - AMD, Inc. | |
| * | |
| * Support : jacob.shin@amd.com | |
| * | |
| * April 2006 | |
| * - added support for AMD Family 0x10 processors | |
| * | |
| * All MC4_MISCi registers are shared between multi-cores | |
| */ | |
| #include <linux/cpu.h> | |
| #include <linux/errno.h> | |
| #include <linux/init.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/kobject.h> | |
| #include <linux/notifier.h> | |
| #include <linux/sched.h> | |
| #include <linux/smp.h> | |
| #include <linux/sysdev.h> | |
| #include <linux/sysfs.h> | |
| #include <asm/apic.h> | |
| #include <asm/mce.h> | |
| #include <asm/msr.h> | |
| #include <asm/percpu.h> | |
| #include <asm/idle.h> | |
| #define PFX "mce_threshold: " | |
| #define VERSION "version 1.1.1" | |
| #define NR_BANKS 6 | |
| #define NR_BLOCKS 9 | |
| #define THRESHOLD_MAX 0xFFF | |
| #define INT_TYPE_APIC 0x00020000 | |
| #define MASK_VALID_HI 0x80000000 | |
| #define MASK_LVTOFF_HI 0x00F00000 | |
| #define MASK_COUNT_EN_HI 0x00080000 | |
| #define MASK_INT_TYPE_HI 0x00060000 | |
| #define MASK_OVERFLOW_HI 0x00010000 | |
| #define MASK_ERR_COUNT_HI 0x00000FFF | |
| #define MASK_BLKPTR_LO 0xFF000000 | |
| #define MCG_XBLK_ADDR 0xC0000400 | |
| struct threshold_block { | |
| unsigned int block; | |
| unsigned int bank; | |
| unsigned int cpu; | |
| u32 address; | |
| u16 interrupt_enable; | |
| u16 threshold_limit; | |
| struct kobject kobj; | |
| struct list_head miscj; | |
| }; | |
| /* defaults used early on boot */ | |
| static struct threshold_block threshold_defaults = { | |
| .interrupt_enable = 0, | |
| .threshold_limit = THRESHOLD_MAX, | |
| }; | |
| struct threshold_bank { | |
| struct kobject kobj; | |
| struct threshold_block *blocks; | |
| cpumask_t cpus; | |
| }; | |
| static DEFINE_PER_CPU(struct threshold_bank *, threshold_banks[NR_BANKS]); | |
| #ifdef CONFIG_SMP | |
| static unsigned char shared_bank[NR_BANKS] = { | |
| 0, 0, 0, 0, 1 | |
| }; | |
| #endif | |
| static DEFINE_PER_CPU(unsigned char, bank_map); /* see which banks are on */ | |
| /* | |
| * CPU Initialization | |
| */ | |
| /* must be called with correct cpu affinity */ | |
| static void threshold_restart_bank(struct threshold_block *b, | |
| int reset, u16 old_limit) | |
| { | |
| u32 mci_misc_hi, mci_misc_lo; | |
| rdmsr(b->address, mci_misc_lo, mci_misc_hi); | |
| if (b->threshold_limit < (mci_misc_hi & THRESHOLD_MAX)) | |
| reset = 1; /* limit cannot be lower than err count */ | |
| if (reset) { /* reset err count and overflow bit */ | |
| mci_misc_hi = | |
| (mci_misc_hi & ~(MASK_ERR_COUNT_HI | MASK_OVERFLOW_HI)) | | |
| (THRESHOLD_MAX - b->threshold_limit); | |
| } else if (old_limit) { /* change limit w/o reset */ | |
| int new_count = (mci_misc_hi & THRESHOLD_MAX) + | |
| (old_limit - b->threshold_limit); | |
| mci_misc_hi = (mci_misc_hi & ~MASK_ERR_COUNT_HI) | | |
| (new_count & THRESHOLD_MAX); | |
| } | |
| b->interrupt_enable ? | |
| (mci_misc_hi = (mci_misc_hi & ~MASK_INT_TYPE_HI) | INT_TYPE_APIC) : | |
| (mci_misc_hi &= ~MASK_INT_TYPE_HI); | |
| mci_misc_hi |= MASK_COUNT_EN_HI; | |
| wrmsr(b->address, mci_misc_lo, mci_misc_hi); | |
| } | |
| /* cpu init entry point, called from mce.c with preempt off */ | |
| void __cpuinit mce_amd_feature_init(struct cpuinfo_x86 *c) | |
| { | |
| unsigned int bank, block; | |
| unsigned int cpu = smp_processor_id(); | |
| u32 low = 0, high = 0, address = 0; | |
| for (bank = 0; bank < NR_BANKS; ++bank) { | |
| for (block = 0; block < NR_BLOCKS; ++block) { | |
| if (block == 0) | |
| address = MSR_IA32_MC0_MISC + bank * 4; | |
| else if (block == 1) | |
| address = MCG_XBLK_ADDR | |
| + ((low & MASK_BLKPTR_LO) >> 21); | |
| else | |
| ++address; | |
| if (rdmsr_safe(address, &low, &high)) | |
| continue; | |
| if (!(high & MASK_VALID_HI)) { | |
| if (block) | |
| continue; | |
| else | |
| break; | |
| } | |
| if (!(high & MASK_VALID_HI >> 1) || | |
| (high & MASK_VALID_HI >> 2)) | |
| continue; | |
| if (!block) | |
| per_cpu(bank_map, cpu) |= (1 << bank); | |
| #ifdef CONFIG_SMP | |
| if (shared_bank[bank] && c->cpu_core_id) | |
| break; | |
| #endif | |
| high &= ~MASK_LVTOFF_HI; | |
| high |= K8_APIC_EXT_LVT_ENTRY_THRESHOLD << 20; | |
| wrmsr(address, low, high); | |
| setup_APIC_extened_lvt(K8_APIC_EXT_LVT_ENTRY_THRESHOLD, | |
| THRESHOLD_APIC_VECTOR, | |
| K8_APIC_EXT_INT_MSG_FIX, 0); | |
| threshold_defaults.address = address; | |
| threshold_restart_bank(&threshold_defaults, 0, 0); | |
| } | |
| } | |
| } | |
| /* | |
| * APIC Interrupt Handler | |
| */ | |
| /* | |
| * threshold interrupt handler will service THRESHOLD_APIC_VECTOR. | |
| * the interrupt goes off when error_count reaches threshold_limit. | |
| * the handler will simply log mcelog w/ software defined bank number. | |
| */ | |
| asmlinkage void mce_threshold_interrupt(void) | |
| { | |
| unsigned int bank, block; | |
| struct mce m; | |
| u32 low = 0, high = 0, address = 0; | |
| ack_APIC_irq(); | |
| exit_idle(); | |
| irq_enter(); | |
| memset(&m, 0, sizeof(m)); | |
| rdtscll(m.tsc); | |
| m.cpu = smp_processor_id(); | |
| /* assume first bank caused it */ | |
| for (bank = 0; bank < NR_BANKS; ++bank) { | |
| for (block = 0; block < NR_BLOCKS; ++block) { | |
| if (block == 0) | |
| address = MSR_IA32_MC0_MISC + bank * 4; | |
| else if (block == 1) | |
| address = MCG_XBLK_ADDR | |
| + ((low & MASK_BLKPTR_LO) >> 21); | |
| else | |
| ++address; | |
| if (rdmsr_safe(address, &low, &high)) | |
| continue; | |
| if (!(high & MASK_VALID_HI)) { | |
| if (block) | |
| continue; | |
| else | |
| break; | |
| } | |
| if (!(high & MASK_VALID_HI >> 1) || | |
| (high & MASK_VALID_HI >> 2)) | |
| continue; | |
| if (high & MASK_OVERFLOW_HI) { | |
| rdmsrl(address, m.misc); | |
| rdmsrl(MSR_IA32_MC0_STATUS + bank * 4, | |
| m.status); | |
| m.bank = K8_MCE_THRESHOLD_BASE | |
| + bank * NR_BLOCKS | |
| + block; | |
| mce_log(&m); | |
| goto out; | |
| } | |
| } | |
| } | |
| out: | |
| irq_exit(); | |
| } | |
| /* | |
| * Sysfs Interface | |
| */ | |
| struct threshold_attr { | |
| struct attribute attr; | |
| ssize_t(*show) (struct threshold_block *, char *); | |
| ssize_t(*store) (struct threshold_block *, const char *, size_t count); | |
| }; | |
| static cpumask_t affinity_set(unsigned int cpu) | |
| { | |
| cpumask_t oldmask = current->cpus_allowed; | |
| cpumask_t newmask = CPU_MASK_NONE; | |
| cpu_set(cpu, newmask); | |
| set_cpus_allowed(current, newmask); | |
| return oldmask; | |
| } | |
| static void affinity_restore(cpumask_t oldmask) | |
| { | |
| set_cpus_allowed(current, oldmask); | |
| } | |
| #define SHOW_FIELDS(name) \ | |
| static ssize_t show_ ## name(struct threshold_block * b, char *buf) \ | |
| { \ | |
| return sprintf(buf, "%lx\n", (unsigned long) b->name); \ | |
| } | |
| SHOW_FIELDS(interrupt_enable) | |
| SHOW_FIELDS(threshold_limit) | |
| static ssize_t store_interrupt_enable(struct threshold_block *b, | |
| const char *buf, size_t count) | |
| { | |
| char *end; | |
| cpumask_t oldmask; | |
| unsigned long new = simple_strtoul(buf, &end, 0); | |
| if (end == buf) | |
| return -EINVAL; | |
| b->interrupt_enable = !!new; | |
| oldmask = affinity_set(b->cpu); | |
| threshold_restart_bank(b, 0, 0); | |
| affinity_restore(oldmask); | |
| return end - buf; | |
| } | |
| static ssize_t store_threshold_limit(struct threshold_block *b, | |
| const char *buf, size_t count) | |
| { | |
| char *end; | |
| cpumask_t oldmask; | |
| u16 old; | |
| unsigned long new = simple_strtoul(buf, &end, 0); | |
| if (end == buf) | |
| return -EINVAL; | |
| if (new > THRESHOLD_MAX) | |
| new = THRESHOLD_MAX; | |
| if (new < 1) | |
| new = 1; | |
| old = b->threshold_limit; | |
| b->threshold_limit = new; | |
| oldmask = affinity_set(b->cpu); | |
| threshold_restart_bank(b, 0, old); | |
| affinity_restore(oldmask); | |
| return end - buf; | |
| } | |
| static ssize_t show_error_count(struct threshold_block *b, char *buf) | |
| { | |
| u32 high, low; | |
| cpumask_t oldmask; | |
| oldmask = affinity_set(b->cpu); | |
| rdmsr(b->address, low, high); | |
| affinity_restore(oldmask); | |
| return sprintf(buf, "%x\n", | |
| (high & 0xFFF) - (THRESHOLD_MAX - b->threshold_limit)); | |
| } | |
| static ssize_t store_error_count(struct threshold_block *b, | |
| const char *buf, size_t count) | |
| { | |
| cpumask_t oldmask; | |
| oldmask = affinity_set(b->cpu); | |
| threshold_restart_bank(b, 1, 0); | |
| affinity_restore(oldmask); | |
| return 1; | |
| } | |
| #define THRESHOLD_ATTR(_name,_mode,_show,_store) { \ | |
| .attr = {.name = __stringify(_name), .mode = _mode }, \ | |
| .show = _show, \ | |
| .store = _store, \ | |
| }; | |
| #define RW_ATTR(name) \ | |
| static struct threshold_attr name = \ | |
| THRESHOLD_ATTR(name, 0644, show_## name, store_## name) | |
| RW_ATTR(interrupt_enable); | |
| RW_ATTR(threshold_limit); | |
| RW_ATTR(error_count); | |
| static struct attribute *default_attrs[] = { | |
| &interrupt_enable.attr, | |
| &threshold_limit.attr, | |
| &error_count.attr, | |
| NULL | |
| }; | |
| #define to_block(k) container_of(k, struct threshold_block, kobj) | |
| #define to_attr(a) container_of(a, struct threshold_attr, attr) | |
| static ssize_t show(struct kobject *kobj, struct attribute *attr, char *buf) | |
| { | |
| struct threshold_block *b = to_block(kobj); | |
| struct threshold_attr *a = to_attr(attr); | |
| ssize_t ret; | |
| ret = a->show ? a->show(b, buf) : -EIO; | |
| return ret; | |
| } | |
| static ssize_t store(struct kobject *kobj, struct attribute *attr, | |
| const char *buf, size_t count) | |
| { | |
| struct threshold_block *b = to_block(kobj); | |
| struct threshold_attr *a = to_attr(attr); | |
| ssize_t ret; | |
| ret = a->store ? a->store(b, buf, count) : -EIO; | |
| return ret; | |
| } | |
| static struct sysfs_ops threshold_ops = { | |
| .show = show, | |
| .store = store, | |
| }; | |
| static struct kobj_type threshold_ktype = { | |
| .sysfs_ops = &threshold_ops, | |
| .default_attrs = default_attrs, | |
| }; | |
| static __cpuinit int allocate_threshold_blocks(unsigned int cpu, | |
| unsigned int bank, | |
| unsigned int block, | |
| u32 address) | |
| { | |
| int err; | |
| u32 low, high; | |
| struct threshold_block *b = NULL; | |
| if ((bank >= NR_BANKS) || (block >= NR_BLOCKS)) | |
| return 0; | |
| if (rdmsr_safe(address, &low, &high)) | |
| goto recurse; | |
| if (!(high & MASK_VALID_HI)) { | |
| if (block) | |
| goto recurse; | |
| else | |
| return 0; | |
| } | |
| if (!(high & MASK_VALID_HI >> 1) || | |
| (high & MASK_VALID_HI >> 2)) | |
| goto recurse; | |
| b = kzalloc(sizeof(struct threshold_block), GFP_KERNEL); | |
| if (!b) | |
| return -ENOMEM; | |
| memset(b, 0, sizeof(struct threshold_block)); | |
| b->block = block; | |
| b->bank = bank; | |
| b->cpu = cpu; | |
| b->address = address; | |
| b->interrupt_enable = 0; | |
| b->threshold_limit = THRESHOLD_MAX; | |
| INIT_LIST_HEAD(&b->miscj); | |
| if (per_cpu(threshold_banks, cpu)[bank]->blocks) | |
| list_add(&b->miscj, | |
| &per_cpu(threshold_banks, cpu)[bank]->blocks->miscj); | |
| else | |
| per_cpu(threshold_banks, cpu)[bank]->blocks = b; | |
| kobject_set_name(&b->kobj, "misc%i", block); | |
| b->kobj.parent = &per_cpu(threshold_banks, cpu)[bank]->kobj; | |
| b->kobj.ktype = &threshold_ktype; | |
| err = kobject_register(&b->kobj); | |
| if (err) | |
| goto out_free; | |
| recurse: | |
| if (!block) { | |
| address = (low & MASK_BLKPTR_LO) >> 21; | |
| if (!address) | |
| return 0; | |
| address += MCG_XBLK_ADDR; | |
| } else | |
| ++address; | |
| err = allocate_threshold_blocks(cpu, bank, ++block, address); | |
| if (err) | |
| goto out_free; | |
| return err; | |
| out_free: | |
| if (b) { | |
| kobject_unregister(&b->kobj); | |
| kfree(b); | |
| } | |
| return err; | |
| } | |
| /* symlinks sibling shared banks to first core. first core owns dir/files. */ | |
| static __cpuinit int threshold_create_bank(unsigned int cpu, unsigned int bank) | |
| { | |
| int i, err = 0; | |
| struct threshold_bank *b = NULL; | |
| cpumask_t oldmask = CPU_MASK_NONE; | |
| char name[32]; | |
| sprintf(name, "threshold_bank%i", bank); | |
| #ifdef CONFIG_SMP | |
| if (cpu_data[cpu].cpu_core_id && shared_bank[bank]) { /* symlink */ | |
| i = first_cpu(cpu_core_map[cpu]); | |
| /* first core not up yet */ | |
| if (cpu_data[i].cpu_core_id) | |
| goto out; | |
| /* already linked */ | |
| if (per_cpu(threshold_banks, cpu)[bank]) | |
| goto out; | |
| b = per_cpu(threshold_banks, i)[bank]; | |
| if (!b) | |
| goto out; | |
| err = sysfs_create_link(&per_cpu(device_mce, cpu).kobj, | |
| &b->kobj, name); | |
| if (err) | |
| goto out; | |
| b->cpus = cpu_core_map[cpu]; | |
| per_cpu(threshold_banks, cpu)[bank] = b; | |
| goto out; | |
| } | |
| #endif | |
| b = kzalloc(sizeof(struct threshold_bank), GFP_KERNEL); | |
| if (!b) { | |
| err = -ENOMEM; | |
| goto out; | |
| } | |
| memset(b, 0, sizeof(struct threshold_bank)); | |
| kobject_set_name(&b->kobj, "threshold_bank%i", bank); | |
| b->kobj.parent = &per_cpu(device_mce, cpu).kobj; | |
| #ifndef CONFIG_SMP | |
| b->cpus = CPU_MASK_ALL; | |
| #else | |
| b->cpus = cpu_core_map[cpu]; | |
| #endif | |
| err = kobject_register(&b->kobj); | |
| if (err) | |
| goto out_free; | |
| per_cpu(threshold_banks, cpu)[bank] = b; | |
| oldmask = affinity_set(cpu); | |
| err = allocate_threshold_blocks(cpu, bank, 0, | |
| MSR_IA32_MC0_MISC + bank * 4); | |
| affinity_restore(oldmask); | |
| if (err) | |
| goto out_free; | |
| for_each_cpu_mask(i, b->cpus) { | |
| if (i == cpu) | |
| continue; | |
| err = sysfs_create_link(&per_cpu(device_mce, i).kobj, | |
| &b->kobj, name); | |
| if (err) | |
| goto out; | |
| per_cpu(threshold_banks, i)[bank] = b; | |
| } | |
| goto out; | |
| out_free: | |
| per_cpu(threshold_banks, cpu)[bank] = NULL; | |
| kfree(b); | |
| out: | |
| return err; | |
| } | |
| /* create dir/files for all valid threshold banks */ | |
| static __cpuinit int threshold_create_device(unsigned int cpu) | |
| { | |
| unsigned int bank; | |
| int err = 0; | |
| for (bank = 0; bank < NR_BANKS; ++bank) { | |
| if (!(per_cpu(bank_map, cpu) & 1 << bank)) | |
| continue; | |
| err = threshold_create_bank(cpu, bank); | |
| if (err) | |
| goto out; | |
| } | |
| out: | |
| return err; | |
| } | |
| #ifdef CONFIG_HOTPLUG_CPU | |
| /* | |
| * let's be hotplug friendly. | |
| * in case of multiple core processors, the first core always takes ownership | |
| * of shared sysfs dir/files, and rest of the cores will be symlinked to it. | |
| */ | |
| static void deallocate_threshold_block(unsigned int cpu, | |
| unsigned int bank) | |
| { | |
| struct threshold_block *pos = NULL; | |
| struct threshold_block *tmp = NULL; | |
| struct threshold_bank *head = per_cpu(threshold_banks, cpu)[bank]; | |
| if (!head) | |
| return; | |
| list_for_each_entry_safe(pos, tmp, &head->blocks->miscj, miscj) { | |
| kobject_unregister(&pos->kobj); | |
| list_del(&pos->miscj); | |
| kfree(pos); | |
| } | |
| kfree(per_cpu(threshold_banks, cpu)[bank]->blocks); | |
| per_cpu(threshold_banks, cpu)[bank]->blocks = NULL; | |
| } | |
| static void threshold_remove_bank(unsigned int cpu, int bank) | |
| { | |
| int i = 0; | |
| struct threshold_bank *b; | |
| char name[32]; | |
| b = per_cpu(threshold_banks, cpu)[bank]; | |
| if (!b) | |
| return; | |
| if (!b->blocks) | |
| goto free_out; | |
| sprintf(name, "threshold_bank%i", bank); | |
| /* sibling symlink */ | |
| if (shared_bank[bank] && b->blocks->cpu != cpu) { | |
| sysfs_remove_link(&per_cpu(device_mce, cpu).kobj, name); | |
| per_cpu(threshold_banks, cpu)[bank] = NULL; | |
| return; | |
| } | |
| /* remove all sibling symlinks before unregistering */ | |
| for_each_cpu_mask(i, b->cpus) { | |
| if (i == cpu) | |
| continue; | |
| sysfs_remove_link(&per_cpu(device_mce, i).kobj, name); | |
| per_cpu(threshold_banks, i)[bank] = NULL; | |
| } | |
| deallocate_threshold_block(cpu, bank); | |
| free_out: | |
| kobject_unregister(&b->kobj); | |
| kfree(b); | |
| per_cpu(threshold_banks, cpu)[bank] = NULL; | |
| } | |
| static void threshold_remove_device(unsigned int cpu) | |
| { | |
| unsigned int bank; | |
| for (bank = 0; bank < NR_BANKS; ++bank) { | |
| if (!(per_cpu(bank_map, cpu) & 1 << bank)) | |
| continue; | |
| threshold_remove_bank(cpu, bank); | |
| } | |
| } | |
| /* get notified when a cpu comes on/off */ | |
| static int threshold_cpu_callback(struct notifier_block *nfb, | |
| unsigned long action, void *hcpu) | |
| { | |
| /* cpu was unsigned int to begin with */ | |
| unsigned int cpu = (unsigned long)hcpu; | |
| if (cpu >= NR_CPUS) | |
| goto out; | |
| switch (action) { | |
| case CPU_ONLINE: | |
| threshold_create_device(cpu); | |
| break; | |
| case CPU_DEAD: | |
| threshold_remove_device(cpu); | |
| break; | |
| default: | |
| break; | |
| } | |
| out: | |
| return NOTIFY_OK; | |
| } | |
| static struct notifier_block threshold_cpu_notifier = { | |
| .notifier_call = threshold_cpu_callback, | |
| }; | |
| #endif /* CONFIG_HOTPLUG_CPU */ | |
| static __init int threshold_init_device(void) | |
| { | |
| unsigned lcpu = 0; | |
| /* to hit CPUs online before the notifier is up */ | |
| for_each_online_cpu(lcpu) { | |
| int err = threshold_create_device(lcpu); | |
| if (err) | |
| return err; | |
| } | |
| register_hotcpu_notifier(&threshold_cpu_notifier); | |
| return 0; | |
| } | |
| device_initcall(threshold_init_device); |