1
2
3
4
5
6
7
8
9
10
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 <linux/string.h>
19#include <linux/uaccess.h>
20#include <linux/pci.h>
21
22#include <asm/mce.h>
23#include <asm/smp.h>
24#include <asm/amd_nb.h>
25#include <asm/irq_vectors.h>
26
27#include "../kernel/cpu/mcheck/mce-internal.h"
28
29
30
31
32static struct mce i_mce;
33static struct dentry *dfs_inj;
34
35static u8 n_banks;
36
37#define MAX_FLAG_OPT_SIZE 3
38#define NBCFG 0x44
39
40enum injection_type {
41 SW_INJ = 0,
42 HW_INJ,
43 DFR_INT_INJ,
44 THR_INT_INJ,
45 N_INJ_TYPES,
46};
47
48static const char * const flags_options[] = {
49 [SW_INJ] = "sw",
50 [HW_INJ] = "hw",
51 [DFR_INT_INJ] = "df",
52 [THR_INT_INJ] = "th",
53 NULL
54};
55
56
57static enum injection_type inj_type = SW_INJ;
58
59#define MCE_INJECT_SET(reg) \
60static int inj_##reg##_set(void *data, u64 val) \
61{ \
62 struct mce *m = (struct mce *)data; \
63 \
64 m->reg = val; \
65 return 0; \
66}
67
68MCE_INJECT_SET(status);
69MCE_INJECT_SET(misc);
70MCE_INJECT_SET(addr);
71
72#define MCE_INJECT_GET(reg) \
73static int inj_##reg##_get(void *data, u64 *val) \
74{ \
75 struct mce *m = (struct mce *)data; \
76 \
77 *val = m->reg; \
78 return 0; \
79}
80
81MCE_INJECT_GET(status);
82MCE_INJECT_GET(misc);
83MCE_INJECT_GET(addr);
84
85DEFINE_SIMPLE_ATTRIBUTE(status_fops, inj_status_get, inj_status_set, "%llx\n");
86DEFINE_SIMPLE_ATTRIBUTE(misc_fops, inj_misc_get, inj_misc_set, "%llx\n");
87DEFINE_SIMPLE_ATTRIBUTE(addr_fops, inj_addr_get, inj_addr_set, "%llx\n");
88
89
90
91
92
93static int toggle_hw_mce_inject(unsigned int cpu, bool enable)
94{
95 u32 l, h;
96 int err;
97
98 err = rdmsr_on_cpu(cpu, MSR_K7_HWCR, &l, &h);
99 if (err) {
100 pr_err("%s: error reading HWCR\n", __func__);
101 return err;
102 }
103
104 enable ? (l |= BIT(18)) : (l &= ~BIT(18));
105
106 err = wrmsr_on_cpu(cpu, MSR_K7_HWCR, l, h);
107 if (err)
108 pr_err("%s: error writing HWCR\n", __func__);
109
110 return err;
111}
112
113static int __set_inj(const char *buf)
114{
115 int i;
116
117 for (i = 0; i < N_INJ_TYPES; i++) {
118 if (!strncmp(flags_options[i], buf, strlen(flags_options[i]))) {
119 inj_type = i;
120 return 0;
121 }
122 }
123 return -EINVAL;
124}
125
126static ssize_t flags_read(struct file *filp, char __user *ubuf,
127 size_t cnt, loff_t *ppos)
128{
129 char buf[MAX_FLAG_OPT_SIZE];
130 int n;
131
132 n = sprintf(buf, "%s\n", flags_options[inj_type]);
133
134 return simple_read_from_buffer(ubuf, cnt, ppos, buf, n);
135}
136
137static ssize_t flags_write(struct file *filp, const char __user *ubuf,
138 size_t cnt, loff_t *ppos)
139{
140 char buf[MAX_FLAG_OPT_SIZE], *__buf;
141 int err;
142
143 if (cnt > MAX_FLAG_OPT_SIZE)
144 return -EINVAL;
145
146 if (copy_from_user(&buf, ubuf, cnt))
147 return -EFAULT;
148
149 buf[cnt - 1] = 0;
150
151
152 __buf = strstrip(buf);
153
154 err = __set_inj(__buf);
155 if (err) {
156 pr_err("%s: Invalid flags value: %s\n", __func__, __buf);
157 return err;
158 }
159
160 *ppos += cnt;
161
162 return cnt;
163}
164
165static const struct file_operations flags_fops = {
166 .read = flags_read,
167 .write = flags_write,
168 .llseek = generic_file_llseek,
169};
170
171
172
173
174MCE_INJECT_GET(extcpu);
175
176static int inj_extcpu_set(void *data, u64 val)
177{
178 struct mce *m = (struct mce *)data;
179
180 if (val >= nr_cpu_ids || !cpu_online(val)) {
181 pr_err("%s: Invalid CPU: %llu\n", __func__, val);
182 return -EINVAL;
183 }
184 m->extcpu = val;
185 return 0;
186}
187
188DEFINE_SIMPLE_ATTRIBUTE(extcpu_fops, inj_extcpu_get, inj_extcpu_set, "%llu\n");
189
190static void trigger_mce(void *info)
191{
192 asm volatile("int $18");
193}
194
195static void trigger_dfr_int(void *info)
196{
197 asm volatile("int %0" :: "i" (DEFERRED_ERROR_VECTOR));
198}
199
200static void trigger_thr_int(void *info)
201{
202 asm volatile("int %0" :: "i" (THRESHOLD_APIC_VECTOR));
203}
204
205static u32 get_nbc_for_node(int node_id)
206{
207 struct cpuinfo_x86 *c = &boot_cpu_data;
208 u32 cores_per_node;
209
210 cores_per_node = (c->x86_max_cores * smp_num_siblings) / amd_get_nodes_per_socket();
211
212 return cores_per_node * node_id;
213}
214
215static void toggle_nb_mca_mst_cpu(u16 nid)
216{
217 struct pci_dev *F3 = node_to_amd_nb(nid)->misc;
218 u32 val;
219 int err;
220
221 if (!F3)
222 return;
223
224 err = pci_read_config_dword(F3, NBCFG, &val);
225 if (err) {
226 pr_err("%s: Error reading F%dx%03x.\n",
227 __func__, PCI_FUNC(F3->devfn), NBCFG);
228 return;
229 }
230
231 if (val & BIT(27))
232 return;
233
234 pr_err("%s: Set D18F3x44[NbMcaToMstCpuEn] which BIOS hasn't done.\n",
235 __func__);
236
237 val |= BIT(27);
238 err = pci_write_config_dword(F3, NBCFG, val);
239 if (err)
240 pr_err("%s: Error writing F%dx%03x.\n",
241 __func__, PCI_FUNC(F3->devfn), NBCFG);
242}
243
244static void prepare_msrs(void *info)
245{
246 struct mce i_mce = *(struct mce *)info;
247 u8 b = i_mce.bank;
248
249 wrmsrl(MSR_IA32_MCG_STATUS, i_mce.mcgstatus);
250
251 if (boot_cpu_has(X86_FEATURE_SMCA)) {
252 if (i_mce.inject_flags == DFR_INT_INJ) {
253 wrmsrl(MSR_AMD64_SMCA_MCx_DESTAT(b), i_mce.status);
254 wrmsrl(MSR_AMD64_SMCA_MCx_DEADDR(b), i_mce.addr);
255 } else {
256 wrmsrl(MSR_AMD64_SMCA_MCx_STATUS(b), i_mce.status);
257 wrmsrl(MSR_AMD64_SMCA_MCx_ADDR(b), i_mce.addr);
258 }
259
260 wrmsrl(MSR_AMD64_SMCA_MCx_MISC(b), i_mce.misc);
261 } else {
262 wrmsrl(MSR_IA32_MCx_STATUS(b), i_mce.status);
263 wrmsrl(MSR_IA32_MCx_ADDR(b), i_mce.addr);
264 wrmsrl(MSR_IA32_MCx_MISC(b), i_mce.misc);
265 }
266
267}
268
269static void do_inject(void)
270{
271 u64 mcg_status = 0;
272 unsigned int cpu = i_mce.extcpu;
273 u8 b = i_mce.bank;
274
275 if (i_mce.misc)
276 i_mce.status |= MCI_STATUS_MISCV;
277
278 if (inj_type == SW_INJ) {
279 mce_inject_log(&i_mce);
280 return;
281 }
282
283
284 mcg_status = MCG_STATUS_MCIP | MCG_STATUS_EIPV;
285
286 if (!(i_mce.status & MCI_STATUS_PCC))
287 mcg_status |= MCG_STATUS_RIPV;
288
289
290
291
292
293
294 if (inj_type == DFR_INT_INJ) {
295 i_mce.status |= MCI_STATUS_DEFERRED;
296 i_mce.status |= (i_mce.status & ~MCI_STATUS_UC);
297 }
298
299
300
301
302
303
304 if (static_cpu_has(X86_FEATURE_AMD_DCM) && b == 4) {
305 toggle_nb_mca_mst_cpu(amd_get_nb_id(cpu));
306 cpu = get_nbc_for_node(amd_get_nb_id(cpu));
307 }
308
309 get_online_cpus();
310 if (!cpu_online(cpu))
311 goto err;
312
313 toggle_hw_mce_inject(cpu, true);
314
315 i_mce.mcgstatus = mcg_status;
316 i_mce.inject_flags = inj_type;
317 smp_call_function_single(cpu, prepare_msrs, &i_mce, 0);
318
319 toggle_hw_mce_inject(cpu, false);
320
321 switch (inj_type) {
322 case DFR_INT_INJ:
323 smp_call_function_single(cpu, trigger_dfr_int, NULL, 0);
324 break;
325 case THR_INT_INJ:
326 smp_call_function_single(cpu, trigger_thr_int, NULL, 0);
327 break;
328 default:
329 smp_call_function_single(cpu, trigger_mce, NULL, 0);
330 }
331
332err:
333 put_online_cpus();
334
335}
336
337
338
339
340
341static int inj_bank_set(void *data, u64 val)
342{
343 struct mce *m = (struct mce *)data;
344
345 if (val >= n_banks) {
346 pr_err("Non-existent MCE bank: %llu\n", val);
347 return -EINVAL;
348 }
349
350 m->bank = val;
351 do_inject();
352
353 return 0;
354}
355
356MCE_INJECT_GET(bank);
357
358DEFINE_SIMPLE_ATTRIBUTE(bank_fops, inj_bank_get, inj_bank_set, "%llu\n");
359
360static const char readme_msg[] =
361"Description of the files and their usages:\n"
362"\n"
363"Note1: i refers to the bank number below.\n"
364"Note2: See respective BKDGs for the exact bit definitions of the files below\n"
365"as they mirror the hardware registers.\n"
366"\n"
367"status:\t Set MCi_STATUS: the bits in that MSR control the error type and\n"
368"\t attributes of the error which caused the MCE.\n"
369"\n"
370"misc:\t Set MCi_MISC: provide auxiliary info about the error. It is mostly\n"
371"\t used for error thresholding purposes and its validity is indicated by\n"
372"\t MCi_STATUS[MiscV].\n"
373"\n"
374"addr:\t Error address value to be written to MCi_ADDR. Log address information\n"
375"\t associated with the error.\n"
376"\n"
377"cpu:\t The CPU to inject the error on.\n"
378"\n"
379"bank:\t Specify the bank you want to inject the error into: the number of\n"
380"\t banks in a processor varies and is family/model-specific, therefore, the\n"
381"\t supplied value is sanity-checked. Setting the bank value also triggers the\n"
382"\t injection.\n"
383"\n"
384"flags:\t Injection type to be performed. Writing to this file will trigger a\n"
385"\t real machine check, an APIC interrupt or invoke the error decoder routines\n"
386"\t for AMD processors.\n"
387"\n"
388"\t Allowed error injection types:\n"
389"\t - \"sw\": Software error injection. Decode error to a human-readable \n"
390"\t format only. Safe to use.\n"
391"\t - \"hw\": Hardware error injection. Causes the #MC exception handler to \n"
392"\t handle the error. Be warned: might cause system panic if MCi_STATUS[PCC] \n"
393"\t is set. Therefore, consider setting (debugfs_mountpoint)/mce/fake_panic \n"
394"\t before injecting.\n"
395"\t - \"df\": Trigger APIC interrupt for Deferred error. Causes deferred \n"
396"\t error APIC interrupt handler to handle the error if the feature is \n"
397"\t is present in hardware. \n"
398"\t - \"th\": Trigger APIC interrupt for Threshold errors. Causes threshold \n"
399"\t APIC interrupt handler to handle the error. \n"
400"\n";
401
402static ssize_t
403inj_readme_read(struct file *filp, char __user *ubuf,
404 size_t cnt, loff_t *ppos)
405{
406 return simple_read_from_buffer(ubuf, cnt, ppos,
407 readme_msg, strlen(readme_msg));
408}
409
410static const struct file_operations readme_fops = {
411 .read = inj_readme_read,
412};
413
414static struct dfs_node {
415 char *name;
416 struct dentry *d;
417 const struct file_operations *fops;
418 umode_t perm;
419} dfs_fls[] = {
420 { .name = "status", .fops = &status_fops, .perm = S_IRUSR | S_IWUSR },
421 { .name = "misc", .fops = &misc_fops, .perm = S_IRUSR | S_IWUSR },
422 { .name = "addr", .fops = &addr_fops, .perm = S_IRUSR | S_IWUSR },
423 { .name = "bank", .fops = &bank_fops, .perm = S_IRUSR | S_IWUSR },
424 { .name = "flags", .fops = &flags_fops, .perm = S_IRUSR | S_IWUSR },
425 { .name = "cpu", .fops = &extcpu_fops, .perm = S_IRUSR | S_IWUSR },
426 { .name = "README", .fops = &readme_fops, .perm = S_IRUSR | S_IRGRP | S_IROTH },
427};
428
429static int __init init_mce_inject(void)
430{
431 int i;
432 u64 cap;
433
434 rdmsrl(MSR_IA32_MCG_CAP, cap);
435 n_banks = cap & MCG_BANKCNT_MASK;
436
437 dfs_inj = debugfs_create_dir("mce-inject", NULL);
438 if (!dfs_inj)
439 return -EINVAL;
440
441 for (i = 0; i < ARRAY_SIZE(dfs_fls); i++) {
442 dfs_fls[i].d = debugfs_create_file(dfs_fls[i].name,
443 dfs_fls[i].perm,
444 dfs_inj,
445 &i_mce,
446 dfs_fls[i].fops);
447
448 if (!dfs_fls[i].d)
449 goto err_dfs_add;
450 }
451
452 return 0;
453
454err_dfs_add:
455 while (--i >= 0)
456 debugfs_remove(dfs_fls[i].d);
457
458 debugfs_remove(dfs_inj);
459 dfs_inj = NULL;
460
461 return -ENOMEM;
462}
463
464static void __exit exit_mce_inject(void)
465{
466 int i;
467
468 for (i = 0; i < ARRAY_SIZE(dfs_fls); i++)
469 debugfs_remove(dfs_fls[i].d);
470
471 memset(&dfs_fls, 0, sizeof(dfs_fls));
472
473 debugfs_remove(dfs_inj);
474 dfs_inj = NULL;
475}
476module_init(init_mce_inject);
477module_exit(exit_mce_inject);
478
479MODULE_LICENSE("GPL");
480MODULE_AUTHOR("Borislav Petkov <bp@alien8.de>");
481MODULE_AUTHOR("AMD Inc.");
482MODULE_DESCRIPTION("MCE injection facility for RAS testing");
483