2010-09-03 00:33:24 +08:00
|
|
|
/*
|
2014-11-22 18:09:12 +08:00
|
|
|
* 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.
|
2010-09-03 00:33:24 +08:00
|
|
|
*
|
|
|
|
* This file may be distributed under the terms of the GNU General Public
|
|
|
|
* License version 2.
|
|
|
|
*
|
2014-11-22 18:09:12 +08:00
|
|
|
* Copyright (c) 2010-14: Borislav Petkov <bp@alien8.de>
|
2010-09-03 00:33:24 +08:00
|
|
|
* Advanced Micro Devices Inc.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/kobject.h>
|
2014-11-22 18:09:12 +08:00
|
|
|
#include <linux/debugfs.h>
|
2012-01-23 00:23:42 +08:00
|
|
|
#include <linux/device.h>
|
2011-07-04 01:37:56 +08:00
|
|
|
#include <linux/module.h>
|
2010-09-03 00:33:24 +08:00
|
|
|
#include <asm/mce.h>
|
|
|
|
|
2010-09-27 21:30:39 +08:00
|
|
|
#include "mce_amd.h"
|
2010-09-03 00:33:24 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Collect all the MCi_XXX settings
|
|
|
|
*/
|
|
|
|
static struct mce i_mce;
|
2014-11-22 18:09:12 +08:00
|
|
|
static struct dentry *dfs_inj;
|
2010-09-03 00:33:24 +08:00
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
#define MCE_INJECT_SET(reg) \
|
|
|
|
static int inj_##reg##_set(void *data, u64 val) \
|
2010-09-03 00:33:24 +08:00
|
|
|
{ \
|
2014-11-22 18:09:12 +08:00
|
|
|
struct mce *m = (struct mce *)data; \
|
2010-09-03 00:33:24 +08:00
|
|
|
\
|
2014-11-22 18:09:12 +08:00
|
|
|
m->reg = val; \
|
|
|
|
return 0; \
|
2010-09-03 00:33:24 +08:00
|
|
|
}
|
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
MCE_INJECT_SET(status);
|
|
|
|
MCE_INJECT_SET(misc);
|
|
|
|
MCE_INJECT_SET(addr);
|
2010-09-03 00:33:24 +08:00
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
#define MCE_INJECT_GET(reg) \
|
|
|
|
static int inj_##reg##_get(void *data, u64 *val) \
|
2010-09-03 00:33:24 +08:00
|
|
|
{ \
|
2014-11-22 18:09:12 +08:00
|
|
|
struct mce *m = (struct mce *)data; \
|
|
|
|
\
|
|
|
|
*val = m->reg; \
|
|
|
|
return 0; \
|
2010-09-03 00:33:24 +08:00
|
|
|
}
|
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
MCE_INJECT_GET(status);
|
|
|
|
MCE_INJECT_GET(misc);
|
|
|
|
MCE_INJECT_GET(addr);
|
2010-09-03 00:33:24 +08:00
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
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");
|
2010-09-03 00:33:24 +08:00
|
|
|
|
2014-11-22 18:22:35 +08:00
|
|
|
/*
|
|
|
|
* Caller needs to be make sure this cpu doesn't disappear
|
|
|
|
* from under us, i.e.: get_cpu/put_cpu.
|
|
|
|
*/
|
|
|
|
static int toggle_hw_mce_inject(unsigned int cpu, bool enable)
|
|
|
|
{
|
|
|
|
u32 l, h;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = rdmsr_on_cpu(cpu, MSR_K7_HWCR, &l, &h);
|
|
|
|
if (err) {
|
|
|
|
pr_err("%s: error reading HWCR\n", __func__);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
enable ? (l |= BIT(18)) : (l &= ~BIT(18));
|
|
|
|
|
|
|
|
err = wrmsr_on_cpu(cpu, MSR_K7_HWCR, l, h);
|
|
|
|
if (err)
|
|
|
|
pr_err("%s: error writing HWCR\n", __func__);
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2010-09-03 00:33:24 +08:00
|
|
|
/*
|
|
|
|
* This denotes into which bank we're injecting and triggers
|
|
|
|
* the injection, at the same time.
|
|
|
|
*/
|
2014-11-22 18:09:12 +08:00
|
|
|
static int inj_bank_set(void *data, u64 val)
|
2010-09-03 00:33:24 +08:00
|
|
|
{
|
2014-11-22 18:09:12 +08:00
|
|
|
struct mce *m = (struct mce *)data;
|
2010-09-03 00:33:24 +08:00
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
if (val > 5) {
|
|
|
|
if (boot_cpu_data.x86 != 0x15 || val > 6) {
|
|
|
|
pr_err("Non-existent MCE bank: %llu\n", val);
|
2010-11-10 02:41:49 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2014-11-22 18:09:12 +08:00
|
|
|
}
|
2010-09-03 00:33:24 +08:00
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
m->bank = val;
|
2010-09-03 00:33:24 +08:00
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
amd_decode_mce(NULL, 0, m);
|
2010-09-03 00:33:24 +08:00
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
return 0;
|
2010-09-03 00:33:24 +08:00
|
|
|
}
|
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
static int inj_bank_get(void *data, u64 *val)
|
2010-09-03 00:33:24 +08:00
|
|
|
{
|
2014-11-22 18:09:12 +08:00
|
|
|
struct mce *m = (struct mce *)data;
|
2010-09-03 00:33:24 +08:00
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
*val = m->bank;
|
|
|
|
return 0;
|
|
|
|
}
|
2010-09-03 00:33:24 +08:00
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
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 },
|
2010-09-03 00:33:24 +08:00
|
|
|
};
|
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
static int __init init_mce_inject(void)
|
2010-09-03 00:33:24 +08:00
|
|
|
{
|
2014-11-22 18:09:12 +08:00
|
|
|
int i;
|
2010-09-03 00:33:24 +08:00
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
dfs_inj = debugfs_create_dir("mce-inject", NULL);
|
|
|
|
if (!dfs_inj)
|
2010-09-03 00:33:24 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
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);
|
2010-09-03 00:33:24 +08:00
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
if (!dfs_fls[i].d)
|
|
|
|
goto err_dfs_add;
|
2010-09-03 00:33:24 +08:00
|
|
|
}
|
2014-11-22 18:09:12 +08:00
|
|
|
|
2010-09-03 00:33:24 +08:00
|
|
|
return 0;
|
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
err_dfs_add:
|
2010-11-19 11:05:43 +08:00
|
|
|
while (--i >= 0)
|
2014-11-22 18:09:12 +08:00
|
|
|
debugfs_remove(dfs_fls[i].d);
|
2010-09-03 00:33:24 +08:00
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
debugfs_remove(dfs_inj);
|
|
|
|
dfs_inj = NULL;
|
2010-09-03 00:33:24 +08:00
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
return -ENOMEM;
|
2010-09-03 00:33:24 +08:00
|
|
|
}
|
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
static void __exit exit_mce_inject(void)
|
2010-09-03 00:33:24 +08:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
for (i = 0; i < ARRAY_SIZE(dfs_fls); i++)
|
|
|
|
debugfs_remove(dfs_fls[i].d);
|
2010-09-03 00:33:24 +08:00
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
memset(&dfs_fls, 0, sizeof(dfs_fls));
|
2010-09-03 00:33:24 +08:00
|
|
|
|
2014-11-22 18:09:12 +08:00
|
|
|
debugfs_remove(dfs_inj);
|
|
|
|
dfs_inj = NULL;
|
2010-09-03 00:33:24 +08:00
|
|
|
}
|
2014-11-22 18:09:12 +08:00
|
|
|
module_init(init_mce_inject);
|
|
|
|
module_exit(exit_mce_inject);
|
2010-09-03 00:33:24 +08:00
|
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|
2012-10-30 01:40:09 +08:00
|
|
|
MODULE_AUTHOR("Borislav Petkov <bp@alien8.de>");
|
2010-09-03 00:33:24 +08:00
|
|
|
MODULE_AUTHOR("AMD Inc.");
|
2014-11-22 18:09:12 +08:00
|
|
|
MODULE_DESCRIPTION("MCE injection facility for RAS testing");
|