diff options
Diffstat (limited to 'drivers/edac/mce_amd_inj.c')
-rw-r--r-- | drivers/edac/mce_amd_inj.c | 293 |
1 files changed, 191 insertions, 102 deletions
diff --git a/drivers/edac/mce_amd_inj.c b/drivers/edac/mce_amd_inj.c index 5e46a9fea31b..0bd91a802c67 100644 --- a/drivers/edac/mce_amd_inj.c +++ b/drivers/edac/mce_amd_inj.c @@ -1,173 +1,262 @@ /* - * A simple MCE injection facility for testing the MCE decoding code. This - * driver should be built as module so that it can be loaded on production - * kernels for testing purposes. + * A simple MCE injection facility for testing different aspects of the RAS + * code. This driver should be built as module so that it can be loaded + * on production kernels for testing purposes. * * This file may be distributed under the terms of the GNU General Public * License version 2. * - * Copyright (c) 2010: Borislav Petkov <bp@alien8.de> + * Copyright (c) 2010-14: Borislav Petkov <bp@alien8.de> * Advanced Micro Devices Inc. */ #include <linux/kobject.h> +#include <linux/debugfs.h> #include <linux/device.h> -#include <linux/edac.h> #include <linux/module.h> +#include <linux/cpu.h> #include <asm/mce.h> #include "mce_amd.h" -struct edac_mce_attr { - struct attribute attr; - ssize_t (*show) (struct kobject *kobj, struct edac_mce_attr *attr, char *buf); - ssize_t (*store)(struct kobject *kobj, struct edac_mce_attr *attr, - const char *buf, size_t count); -}; - -#define EDAC_MCE_ATTR(_name, _mode, _show, _store) \ -static struct edac_mce_attr mce_attr_##_name = __ATTR(_name, _mode, _show, _store) - -static struct kobject *mce_kobj; - /* * Collect all the MCi_XXX settings */ static struct mce i_mce; +static struct dentry *dfs_inj; -#define MCE_INJECT_STORE(reg) \ -static ssize_t edac_inject_##reg##_store(struct kobject *kobj, \ - struct edac_mce_attr *attr, \ - const char *data, size_t count)\ +#define MCE_INJECT_SET(reg) \ +static int inj_##reg##_set(void *data, u64 val) \ { \ - int ret = 0; \ - unsigned long value; \ - \ - ret = kstrtoul(data, 16, &value); \ - if (ret < 0) \ - printk(KERN_ERR "Error writing MCE " #reg " field.\n"); \ + struct mce *m = (struct mce *)data; \ \ - i_mce.reg = value; \ - \ - return count; \ + m->reg = val; \ + return 0; \ } -MCE_INJECT_STORE(status); -MCE_INJECT_STORE(misc); -MCE_INJECT_STORE(addr); +MCE_INJECT_SET(status); +MCE_INJECT_SET(misc); +MCE_INJECT_SET(addr); -#define MCE_INJECT_SHOW(reg) \ -static ssize_t edac_inject_##reg##_show(struct kobject *kobj, \ - struct edac_mce_attr *attr, \ - char *buf) \ +#define MCE_INJECT_GET(reg) \ +static int inj_##reg##_get(void *data, u64 *val) \ { \ - return sprintf(buf, "0x%016llx\n", i_mce.reg); \ + struct mce *m = (struct mce *)data; \ + \ + *val = m->reg; \ + return 0; \ } -MCE_INJECT_SHOW(status); -MCE_INJECT_SHOW(misc); -MCE_INJECT_SHOW(addr); +MCE_INJECT_GET(status); +MCE_INJECT_GET(misc); +MCE_INJECT_GET(addr); -EDAC_MCE_ATTR(status, 0644, edac_inject_status_show, edac_inject_status_store); -EDAC_MCE_ATTR(misc, 0644, edac_inject_misc_show, edac_inject_misc_store); -EDAC_MCE_ATTR(addr, 0644, edac_inject_addr_show, edac_inject_addr_store); +DEFINE_SIMPLE_ATTRIBUTE(status_fops, inj_status_get, inj_status_set, "%llx\n"); +DEFINE_SIMPLE_ATTRIBUTE(misc_fops, inj_misc_get, inj_misc_set, "%llx\n"); +DEFINE_SIMPLE_ATTRIBUTE(addr_fops, inj_addr_get, inj_addr_set, "%llx\n"); /* - * This denotes into which bank we're injecting and triggers - * the injection, at the same time. + * Caller needs to be make sure this cpu doesn't disappear + * from under us, i.e.: get_cpu/put_cpu. */ -static ssize_t edac_inject_bank_store(struct kobject *kobj, - struct edac_mce_attr *attr, - const char *data, size_t count) +static int toggle_hw_mce_inject(unsigned int cpu, bool enable) { - int ret = 0; - unsigned long value; + u32 l, h; + int err; - ret = kstrtoul(data, 10, &value); - if (ret < 0) { - printk(KERN_ERR "Invalid bank value!\n"); - return -EINVAL; + err = rdmsr_on_cpu(cpu, MSR_K7_HWCR, &l, &h); + if (err) { + pr_err("%s: error reading HWCR\n", __func__); + return err; } - if (value > 5) - if (boot_cpu_data.x86 != 0x15 || value > 6) { - printk(KERN_ERR "Non-existent MCE bank: %lu\n", value); - return -EINVAL; - } + enable ? (l |= BIT(18)) : (l &= ~BIT(18)); - i_mce.bank = value; + err = wrmsr_on_cpu(cpu, MSR_K7_HWCR, l, h); + if (err) + pr_err("%s: error writing HWCR\n", __func__); - amd_decode_mce(NULL, 0, &i_mce); + return err; +} - return count; +static int flags_get(void *data, u64 *val) +{ + struct mce *m = (struct mce *)data; + + *val = m->inject_flags; + + return 0; } -static ssize_t edac_inject_bank_show(struct kobject *kobj, - struct edac_mce_attr *attr, char *buf) +static int flags_set(void *data, u64 val) { - return sprintf(buf, "%d\n", i_mce.bank); + struct mce *m = (struct mce *)data; + + m->inject_flags = (u8)val; + return 0; } -EDAC_MCE_ATTR(bank, 0644, edac_inject_bank_show, edac_inject_bank_store); +DEFINE_SIMPLE_ATTRIBUTE(flags_fops, flags_get, flags_set, "%llu\n"); -static struct edac_mce_attr *sysfs_attrs[] = { &mce_attr_status, &mce_attr_misc, - &mce_attr_addr, &mce_attr_bank -}; +/* + * On which CPU to inject? + */ +MCE_INJECT_GET(extcpu); -static int __init edac_init_mce_inject(void) +static int inj_extcpu_set(void *data, u64 val) { - struct bus_type *edac_subsys = NULL; - int i, err = 0; + struct mce *m = (struct mce *)data; - edac_subsys = edac_get_sysfs_subsys(); - if (!edac_subsys) + if (val >= nr_cpu_ids || !cpu_online(val)) { + pr_err("%s: Invalid CPU: %llu\n", __func__, val); return -EINVAL; + } + m->extcpu = val; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(extcpu_fops, inj_extcpu_get, inj_extcpu_set, "%llu\n"); - mce_kobj = kobject_create_and_add("mce", &edac_subsys->dev_root->kobj); - if (!mce_kobj) { - printk(KERN_ERR "Error creating a mce kset.\n"); - err = -ENOMEM; - goto err_mce_kobj; +static void trigger_mce(void *info) +{ + asm volatile("int $18"); +} + +static void do_inject(void) +{ + u64 mcg_status = 0; + unsigned int cpu = i_mce.extcpu; + u8 b = i_mce.bank; + + if (!(i_mce.inject_flags & MCJ_EXCEPTION)) { + amd_decode_mce(NULL, 0, &i_mce); + return; } - for (i = 0; i < ARRAY_SIZE(sysfs_attrs); i++) { - err = sysfs_create_file(mce_kobj, &sysfs_attrs[i]->attr); - if (err) { - printk(KERN_ERR "Error creating %s in sysfs.\n", - sysfs_attrs[i]->attr.name); - goto err_sysfs_create; + get_online_cpus(); + if (!cpu_online(cpu)) + goto err; + + /* prep MCE global settings for the injection */ + mcg_status = MCG_STATUS_MCIP | MCG_STATUS_EIPV; + + if (!(i_mce.status & MCI_STATUS_PCC)) + mcg_status |= MCG_STATUS_RIPV; + + toggle_hw_mce_inject(cpu, true); + + wrmsr_on_cpu(cpu, MSR_IA32_MCG_STATUS, + (u32)mcg_status, (u32)(mcg_status >> 32)); + + wrmsr_on_cpu(cpu, MSR_IA32_MCx_STATUS(b), + (u32)i_mce.status, (u32)(i_mce.status >> 32)); + + wrmsr_on_cpu(cpu, MSR_IA32_MCx_ADDR(b), + (u32)i_mce.addr, (u32)(i_mce.addr >> 32)); + + wrmsr_on_cpu(cpu, MSR_IA32_MCx_MISC(b), + (u32)i_mce.misc, (u32)(i_mce.misc >> 32)); + + toggle_hw_mce_inject(cpu, false); + + smp_call_function_single(cpu, trigger_mce, NULL, 0); + +err: + put_online_cpus(); + +} + +/* + * This denotes into which bank we're injecting and triggers + * the injection, at the same time. + */ +static int inj_bank_set(void *data, u64 val) +{ + struct mce *m = (struct mce *)data; + + if (val > 5) { + if (boot_cpu_data.x86 != 0x15 || val > 6) { + pr_err("Non-existent MCE bank: %llu\n", val); + return -EINVAL; } } - return 0; -err_sysfs_create: - while (--i >= 0) - sysfs_remove_file(mce_kobj, &sysfs_attrs[i]->attr); + m->bank = val; + do_inject(); - kobject_del(mce_kobj); + return 0; +} -err_mce_kobj: - edac_put_sysfs_subsys(); +static int inj_bank_get(void *data, u64 *val) +{ + struct mce *m = (struct mce *)data; - return err; + *val = m->bank; + return 0; } -static void __exit edac_exit_mce_inject(void) +DEFINE_SIMPLE_ATTRIBUTE(bank_fops, inj_bank_get, inj_bank_set, "%llu\n"); + +struct dfs_node { + char *name; + struct dentry *d; + const struct file_operations *fops; +} dfs_fls[] = { + { .name = "status", .fops = &status_fops }, + { .name = "misc", .fops = &misc_fops }, + { .name = "addr", .fops = &addr_fops }, + { .name = "bank", .fops = &bank_fops }, + { .name = "flags", .fops = &flags_fops }, + { .name = "cpu", .fops = &extcpu_fops }, +}; + +static int __init init_mce_inject(void) { int i; - for (i = 0; i < ARRAY_SIZE(sysfs_attrs); i++) - sysfs_remove_file(mce_kobj, &sysfs_attrs[i]->attr); + dfs_inj = debugfs_create_dir("mce-inject", NULL); + if (!dfs_inj) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(dfs_fls); i++) { + dfs_fls[i].d = debugfs_create_file(dfs_fls[i].name, + S_IRUSR | S_IWUSR, + dfs_inj, + &i_mce, + dfs_fls[i].fops); + + if (!dfs_fls[i].d) + goto err_dfs_add; + } + + return 0; + +err_dfs_add: + while (--i >= 0) + debugfs_remove(dfs_fls[i].d); - kobject_del(mce_kobj); + debugfs_remove(dfs_inj); + dfs_inj = NULL; - edac_put_sysfs_subsys(); + return -ENOMEM; } -module_init(edac_init_mce_inject); -module_exit(edac_exit_mce_inject); +static void __exit exit_mce_inject(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dfs_fls); i++) + debugfs_remove(dfs_fls[i].d); + + memset(&dfs_fls, 0, sizeof(dfs_fls)); + + debugfs_remove(dfs_inj); + dfs_inj = NULL; +} +module_init(init_mce_inject); +module_exit(exit_mce_inject); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Borislav Petkov <bp@alien8.de>"); MODULE_AUTHOR("AMD Inc."); -MODULE_DESCRIPTION("MCE injection facility for testing MCE decoding"); +MODULE_DESCRIPTION("MCE injection facility for RAS testing"); |