2 * A simple MCE injection facility for testing different aspects of the RAS
3 * code. This driver should be built as module so that it can be loaded
4 * on production kernels for testing purposes.
6 * This file may be distributed under the terms of the GNU General Public
9 * Copyright (c) 2010-14: Borislav Petkov <bp@alien8.de>
10 * Advanced Micro Devices Inc.
13 #include <linux/kobject.h>
14 #include <linux/debugfs.h>
15 #include <linux/device.h>
16 #include <linux/module.h>
17 #include <linux/cpu.h>
18 #include <linux/string.h>
19 #include <linux/uaccess.h>
25 * Collect all the MCi_XXX settings
27 static struct mce i_mce;
28 static struct dentry *dfs_inj;
32 #define MAX_FLAG_OPT_SIZE 3
35 SW_INJ = 0, /* SW injection, simply decode the error */
36 HW_INJ, /* Trigger a #MC */
40 static const char * const flags_options[] = {
46 /* Set default injection to SW_INJ */
47 enum injection_type inj_type = SW_INJ;
49 #define MCE_INJECT_SET(reg) \
50 static int inj_##reg##_set(void *data, u64 val) \
52 struct mce *m = (struct mce *)data; \
58 MCE_INJECT_SET(status);
62 #define MCE_INJECT_GET(reg) \
63 static int inj_##reg##_get(void *data, u64 *val) \
65 struct mce *m = (struct mce *)data; \
71 MCE_INJECT_GET(status);
75 DEFINE_SIMPLE_ATTRIBUTE(status_fops, inj_status_get, inj_status_set, "%llx\n");
76 DEFINE_SIMPLE_ATTRIBUTE(misc_fops, inj_misc_get, inj_misc_set, "%llx\n");
77 DEFINE_SIMPLE_ATTRIBUTE(addr_fops, inj_addr_get, inj_addr_set, "%llx\n");
80 * Caller needs to be make sure this cpu doesn't disappear
81 * from under us, i.e.: get_cpu/put_cpu.
83 static int toggle_hw_mce_inject(unsigned int cpu, bool enable)
88 err = rdmsr_on_cpu(cpu, MSR_K7_HWCR, &l, &h);
90 pr_err("%s: error reading HWCR\n", __func__);
94 enable ? (l |= BIT(18)) : (l &= ~BIT(18));
96 err = wrmsr_on_cpu(cpu, MSR_K7_HWCR, l, h);
98 pr_err("%s: error writing HWCR\n", __func__);
103 static int __set_inj(const char *buf)
107 for (i = 0; i < N_INJ_TYPES; i++) {
108 if (!strncmp(flags_options[i], buf, strlen(flags_options[i]))) {
116 static ssize_t flags_read(struct file *filp, char __user *ubuf,
117 size_t cnt, loff_t *ppos)
119 char buf[MAX_FLAG_OPT_SIZE];
122 n = sprintf(buf, "%s\n", flags_options[inj_type]);
124 return simple_read_from_buffer(ubuf, cnt, ppos, buf, n);
127 static ssize_t flags_write(struct file *filp, const char __user *ubuf,
128 size_t cnt, loff_t *ppos)
130 char buf[MAX_FLAG_OPT_SIZE], *__buf;
134 if (cnt > MAX_FLAG_OPT_SIZE)
135 cnt = MAX_FLAG_OPT_SIZE;
139 if (copy_from_user(&buf, ubuf, cnt))
144 /* strip whitespace */
145 __buf = strstrip(buf);
147 err = __set_inj(__buf);
149 pr_err("%s: Invalid flags value: %s\n", __func__, __buf);
158 static const struct file_operations flags_fops = {
160 .write = flags_write,
161 .llseek = generic_file_llseek,
165 * On which CPU to inject?
167 MCE_INJECT_GET(extcpu);
169 static int inj_extcpu_set(void *data, u64 val)
171 struct mce *m = (struct mce *)data;
173 if (val >= nr_cpu_ids || !cpu_online(val)) {
174 pr_err("%s: Invalid CPU: %llu\n", __func__, val);
181 DEFINE_SIMPLE_ATTRIBUTE(extcpu_fops, inj_extcpu_get, inj_extcpu_set, "%llu\n");
183 static void trigger_mce(void *info)
185 asm volatile("int $18");
188 static void do_inject(void)
191 unsigned int cpu = i_mce.extcpu;
194 if (inj_type == SW_INJ) {
195 amd_decode_mce(NULL, 0, &i_mce);
200 if (!cpu_online(cpu))
203 /* prep MCE global settings for the injection */
204 mcg_status = MCG_STATUS_MCIP | MCG_STATUS_EIPV;
206 if (!(i_mce.status & MCI_STATUS_PCC))
207 mcg_status |= MCG_STATUS_RIPV;
209 toggle_hw_mce_inject(cpu, true);
211 wrmsr_on_cpu(cpu, MSR_IA32_MCG_STATUS,
212 (u32)mcg_status, (u32)(mcg_status >> 32));
214 wrmsr_on_cpu(cpu, MSR_IA32_MCx_STATUS(b),
215 (u32)i_mce.status, (u32)(i_mce.status >> 32));
217 wrmsr_on_cpu(cpu, MSR_IA32_MCx_ADDR(b),
218 (u32)i_mce.addr, (u32)(i_mce.addr >> 32));
220 wrmsr_on_cpu(cpu, MSR_IA32_MCx_MISC(b),
221 (u32)i_mce.misc, (u32)(i_mce.misc >> 32));
223 toggle_hw_mce_inject(cpu, false);
225 smp_call_function_single(cpu, trigger_mce, NULL, 0);
233 * This denotes into which bank we're injecting and triggers
234 * the injection, at the same time.
236 static int inj_bank_set(void *data, u64 val)
238 struct mce *m = (struct mce *)data;
240 if (val >= n_banks) {
241 pr_err("Non-existent MCE bank: %llu\n", val);
251 MCE_INJECT_GET(bank);
253 DEFINE_SIMPLE_ATTRIBUTE(bank_fops, inj_bank_get, inj_bank_set, "%llu\n");
255 static struct dfs_node {
258 const struct file_operations *fops;
260 { .name = "status", .fops = &status_fops },
261 { .name = "misc", .fops = &misc_fops },
262 { .name = "addr", .fops = &addr_fops },
263 { .name = "bank", .fops = &bank_fops },
264 { .name = "flags", .fops = &flags_fops },
265 { .name = "cpu", .fops = &extcpu_fops },
268 static int __init init_mce_inject(void)
273 rdmsrl(MSR_IA32_MCG_CAP, cap);
274 n_banks = cap & MCG_BANKCNT_MASK;
276 dfs_inj = debugfs_create_dir("mce-inject", NULL);
280 for (i = 0; i < ARRAY_SIZE(dfs_fls); i++) {
281 dfs_fls[i].d = debugfs_create_file(dfs_fls[i].name,
295 debugfs_remove(dfs_fls[i].d);
297 debugfs_remove(dfs_inj);
303 static void __exit exit_mce_inject(void)
307 for (i = 0; i < ARRAY_SIZE(dfs_fls); i++)
308 debugfs_remove(dfs_fls[i].d);
310 memset(&dfs_fls, 0, sizeof(dfs_fls));
312 debugfs_remove(dfs_inj);
315 module_init(init_mce_inject);
316 module_exit(exit_mce_inject);
318 MODULE_LICENSE("GPL");
319 MODULE_AUTHOR("Borislav Petkov <bp@alien8.de>");
320 MODULE_AUTHOR("AMD Inc.");
321 MODULE_DESCRIPTION("MCE injection facility for RAS testing");