1 /*
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.
5  *
6  * This file may be distributed under the terms of the GNU General Public
7  * License version 2.
8  *
9  * Copyright (c) 2010-14:  Borislav Petkov <bp@alien8.de>
10  *			Advanced Micro Devices Inc.
11  */
12 
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 <asm/mce.h>
19 
20 #include "mce_amd.h"
21 
22 /*
23  * Collect all the MCi_XXX settings
24  */
25 static struct mce i_mce;
26 static struct dentry *dfs_inj;
27 
28 #define MCE_INJECT_SET(reg)						\
29 static int inj_##reg##_set(void *data, u64 val)				\
30 {									\
31 	struct mce *m = (struct mce *)data;				\
32 									\
33 	m->reg = val;							\
34 	return 0;							\
35 }
36 
37 MCE_INJECT_SET(status);
38 MCE_INJECT_SET(misc);
39 MCE_INJECT_SET(addr);
40 
41 #define MCE_INJECT_GET(reg)						\
42 static int inj_##reg##_get(void *data, u64 *val)			\
43 {									\
44 	struct mce *m = (struct mce *)data;				\
45 									\
46 	*val = m->reg;							\
47 	return 0;							\
48 }
49 
50 MCE_INJECT_GET(status);
51 MCE_INJECT_GET(misc);
52 MCE_INJECT_GET(addr);
53 
54 DEFINE_SIMPLE_ATTRIBUTE(status_fops, inj_status_get, inj_status_set, "%llx\n");
55 DEFINE_SIMPLE_ATTRIBUTE(misc_fops, inj_misc_get, inj_misc_set, "%llx\n");
56 DEFINE_SIMPLE_ATTRIBUTE(addr_fops, inj_addr_get, inj_addr_set, "%llx\n");
57 
58 /*
59  * Caller needs to be make sure this cpu doesn't disappear
60  * from under us, i.e.: get_cpu/put_cpu.
61  */
toggle_hw_mce_inject(unsigned int cpu,bool enable)62 static int toggle_hw_mce_inject(unsigned int cpu, bool enable)
63 {
64 	u32 l, h;
65 	int err;
66 
67 	err = rdmsr_on_cpu(cpu, MSR_K7_HWCR, &l, &h);
68 	if (err) {
69 		pr_err("%s: error reading HWCR\n", __func__);
70 		return err;
71 	}
72 
73 	enable ? (l |= BIT(18)) : (l &= ~BIT(18));
74 
75 	err = wrmsr_on_cpu(cpu, MSR_K7_HWCR, l, h);
76 	if (err)
77 		pr_err("%s: error writing HWCR\n", __func__);
78 
79 	return err;
80 }
81 
flags_get(void * data,u64 * val)82 static int flags_get(void *data, u64 *val)
83 {
84 	struct mce *m = (struct mce *)data;
85 
86 	*val = m->inject_flags;
87 
88 	return 0;
89 }
90 
flags_set(void * data,u64 val)91 static int flags_set(void *data, u64 val)
92 {
93 	struct mce *m = (struct mce *)data;
94 
95 	m->inject_flags = (u8)val;
96 	return 0;
97 }
98 
99 DEFINE_SIMPLE_ATTRIBUTE(flags_fops, flags_get, flags_set, "%llu\n");
100 
101 /*
102  * On which CPU to inject?
103  */
104 MCE_INJECT_GET(extcpu);
105 
inj_extcpu_set(void * data,u64 val)106 static int inj_extcpu_set(void *data, u64 val)
107 {
108 	struct mce *m = (struct mce *)data;
109 
110 	if (val >= nr_cpu_ids || !cpu_online(val)) {
111 		pr_err("%s: Invalid CPU: %llu\n", __func__, val);
112 		return -EINVAL;
113 	}
114 	m->extcpu = val;
115 	return 0;
116 }
117 
118 DEFINE_SIMPLE_ATTRIBUTE(extcpu_fops, inj_extcpu_get, inj_extcpu_set, "%llu\n");
119 
trigger_mce(void * info)120 static void trigger_mce(void *info)
121 {
122 	asm volatile("int $18");
123 }
124 
do_inject(void)125 static void do_inject(void)
126 {
127 	u64 mcg_status = 0;
128 	unsigned int cpu = i_mce.extcpu;
129 	u8 b = i_mce.bank;
130 
131 	if (!(i_mce.inject_flags & MCJ_EXCEPTION)) {
132 		amd_decode_mce(NULL, 0, &i_mce);
133 		return;
134 	}
135 
136 	get_online_cpus();
137 	if (!cpu_online(cpu))
138 		goto err;
139 
140 	/* prep MCE global settings for the injection */
141 	mcg_status = MCG_STATUS_MCIP | MCG_STATUS_EIPV;
142 
143 	if (!(i_mce.status & MCI_STATUS_PCC))
144 		mcg_status |= MCG_STATUS_RIPV;
145 
146 	toggle_hw_mce_inject(cpu, true);
147 
148 	wrmsr_on_cpu(cpu, MSR_IA32_MCG_STATUS,
149 		     (u32)mcg_status, (u32)(mcg_status >> 32));
150 
151 	wrmsr_on_cpu(cpu, MSR_IA32_MCx_STATUS(b),
152 		     (u32)i_mce.status, (u32)(i_mce.status >> 32));
153 
154 	wrmsr_on_cpu(cpu, MSR_IA32_MCx_ADDR(b),
155 		     (u32)i_mce.addr, (u32)(i_mce.addr >> 32));
156 
157 	wrmsr_on_cpu(cpu, MSR_IA32_MCx_MISC(b),
158 		     (u32)i_mce.misc, (u32)(i_mce.misc >> 32));
159 
160 	toggle_hw_mce_inject(cpu, false);
161 
162 	smp_call_function_single(cpu, trigger_mce, NULL, 0);
163 
164 err:
165 	put_online_cpus();
166 
167 }
168 
169 /*
170  * This denotes into which bank we're injecting and triggers
171  * the injection, at the same time.
172  */
inj_bank_set(void * data,u64 val)173 static int inj_bank_set(void *data, u64 val)
174 {
175 	struct mce *m = (struct mce *)data;
176 
177 	if (val > 5) {
178 		if (boot_cpu_data.x86 != 0x15 || val > 6) {
179 			pr_err("Non-existent MCE bank: %llu\n", val);
180 			return -EINVAL;
181 		}
182 	}
183 
184 	m->bank = val;
185 	do_inject();
186 
187 	return 0;
188 }
189 
inj_bank_get(void * data,u64 * val)190 static int inj_bank_get(void *data, u64 *val)
191 {
192 	struct mce *m = (struct mce *)data;
193 
194 	*val = m->bank;
195 	return 0;
196 }
197 
198 DEFINE_SIMPLE_ATTRIBUTE(bank_fops, inj_bank_get, inj_bank_set, "%llu\n");
199 
200 static struct dfs_node {
201 	char *name;
202 	struct dentry *d;
203 	const struct file_operations *fops;
204 } dfs_fls[] = {
205 	{ .name = "status",	.fops = &status_fops },
206 	{ .name = "misc",	.fops = &misc_fops },
207 	{ .name = "addr",	.fops = &addr_fops },
208 	{ .name = "bank",	.fops = &bank_fops },
209 	{ .name = "flags",	.fops = &flags_fops },
210 	{ .name = "cpu",	.fops = &extcpu_fops },
211 };
212 
init_mce_inject(void)213 static int __init init_mce_inject(void)
214 {
215 	int i;
216 
217 	dfs_inj = debugfs_create_dir("mce-inject", NULL);
218 	if (!dfs_inj)
219 		return -EINVAL;
220 
221 	for (i = 0; i < ARRAY_SIZE(dfs_fls); i++) {
222 		dfs_fls[i].d = debugfs_create_file(dfs_fls[i].name,
223 						    S_IRUSR | S_IWUSR,
224 						    dfs_inj,
225 						    &i_mce,
226 						    dfs_fls[i].fops);
227 
228 		if (!dfs_fls[i].d)
229 			goto err_dfs_add;
230 	}
231 
232 	return 0;
233 
234 err_dfs_add:
235 	while (--i >= 0)
236 		debugfs_remove(dfs_fls[i].d);
237 
238 	debugfs_remove(dfs_inj);
239 	dfs_inj = NULL;
240 
241 	return -ENOMEM;
242 }
243 
exit_mce_inject(void)244 static void __exit exit_mce_inject(void)
245 {
246 	int i;
247 
248 	for (i = 0; i < ARRAY_SIZE(dfs_fls); i++)
249 		debugfs_remove(dfs_fls[i].d);
250 
251 	memset(&dfs_fls, 0, sizeof(dfs_fls));
252 
253 	debugfs_remove(dfs_inj);
254 	dfs_inj = NULL;
255 }
256 module_init(init_mce_inject);
257 module_exit(exit_mce_inject);
258 
259 MODULE_LICENSE("GPL");
260 MODULE_AUTHOR("Borislav Petkov <bp@alien8.de>");
261 MODULE_AUTHOR("AMD Inc.");
262 MODULE_DESCRIPTION("MCE injection facility for RAS testing");
263