diff options
-rw-r--r-- | arch/x86/Kconfig | 2 | ||||
-rw-r--r-- | arch/x86/Makefile | 2 | ||||
-rw-r--r-- | arch/x86/include/asm/processor.h | 5 | ||||
-rw-r--r-- | arch/x86/kernel/cpu/mcheck/mce-inject.c | 542 | ||||
-rw-r--r-- | arch/x86/ras/Kconfig | 11 | ||||
-rw-r--r-- | arch/x86/ras/Makefile | 2 | ||||
-rw-r--r-- | arch/x86/ras/mce_amd_inj.c | 492 |
7 files changed, 532 insertions, 524 deletions
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 0efb4c9497bc..4371b6b5cbe4 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig | |||
@@ -1082,7 +1082,7 @@ config X86_MCE_THRESHOLD | |||
1082 | def_bool y | 1082 | def_bool y |
1083 | 1083 | ||
1084 | config X86_MCE_INJECT | 1084 | config X86_MCE_INJECT |
1085 | depends on X86_MCE && X86_LOCAL_APIC && X86_MCELOG_LEGACY | 1085 | depends on X86_MCE && X86_LOCAL_APIC && DEBUG_FS |
1086 | tristate "Machine check injector support" | 1086 | tristate "Machine check injector support" |
1087 | ---help--- | 1087 | ---help--- |
1088 | Provide support for injecting machine checks for testing purposes. | 1088 | Provide support for injecting machine checks for testing purposes. |
diff --git a/arch/x86/Makefile b/arch/x86/Makefile index bf240b920473..ad2db82e9953 100644 --- a/arch/x86/Makefile +++ b/arch/x86/Makefile | |||
@@ -257,8 +257,6 @@ drivers-$(CONFIG_PM) += arch/x86/power/ | |||
257 | 257 | ||
258 | drivers-$(CONFIG_FB) += arch/x86/video/ | 258 | drivers-$(CONFIG_FB) += arch/x86/video/ |
259 | 259 | ||
260 | drivers-$(CONFIG_RAS) += arch/x86/ras/ | ||
261 | |||
262 | #### | 260 | #### |
263 | # boot loader support. Several targets are kept for legacy purposes | 261 | # boot loader support. Several targets are kept for legacy purposes |
264 | 262 | ||
diff --git a/arch/x86/include/asm/processor.h b/arch/x86/include/asm/processor.h index 3cada998a402..71f6fba95aa6 100644 --- a/arch/x86/include/asm/processor.h +++ b/arch/x86/include/asm/processor.h | |||
@@ -901,8 +901,13 @@ static inline int mpx_disable_management(void) | |||
901 | } | 901 | } |
902 | #endif /* CONFIG_X86_INTEL_MPX */ | 902 | #endif /* CONFIG_X86_INTEL_MPX */ |
903 | 903 | ||
904 | #ifdef CONFIG_CPU_SUP_AMD | ||
904 | extern u16 amd_get_nb_id(int cpu); | 905 | extern u16 amd_get_nb_id(int cpu); |
905 | extern u32 amd_get_nodes_per_socket(void); | 906 | extern u32 amd_get_nodes_per_socket(void); |
907 | #else | ||
908 | static inline u16 amd_get_nb_id(int cpu) { return 0; } | ||
909 | static inline u32 amd_get_nodes_per_socket(void) { return 0; } | ||
910 | #endif | ||
906 | 911 | ||
907 | static inline uint32_t hypervisor_cpuid_base(const char *sig, uint32_t leaves) | 912 | static inline uint32_t hypervisor_cpuid_base(const char *sig, uint32_t leaves) |
908 | { | 913 | { |
diff --git a/arch/x86/kernel/cpu/mcheck/mce-inject.c b/arch/x86/kernel/cpu/mcheck/mce-inject.c index 99165b206df3..7170186938e5 100644 --- a/arch/x86/kernel/cpu/mcheck/mce-inject.c +++ b/arch/x86/kernel/cpu/mcheck/mce-inject.c | |||
@@ -10,23 +10,108 @@ | |||
10 | * Authors: | 10 | * Authors: |
11 | * Andi Kleen | 11 | * Andi Kleen |
12 | * Ying Huang | 12 | * Ying Huang |
13 | * | ||
14 | * The AMD part (from mce_amd_inj.c): a simple MCE injection facility | ||
15 | * for testing different aspects of the RAS code. This driver should be | ||
16 | * built as module so that it can be loaded on production kernels for | ||
17 | * testing purposes. | ||
18 | * | ||
19 | * This file may be distributed under the terms of the GNU General Public | ||
20 | * License version 2. | ||
21 | * | ||
22 | * Copyright (c) 2010-17: Borislav Petkov <bp@alien8.de> | ||
23 | * Advanced Micro Devices Inc. | ||
13 | */ | 24 | */ |
14 | #include <linux/uaccess.h> | 25 | |
15 | #include <linux/module.h> | 26 | #include <linux/cpu.h> |
16 | #include <linux/timer.h> | 27 | #include <linux/debugfs.h> |
17 | #include <linux/kernel.h> | 28 | #include <linux/device.h> |
18 | #include <linux/string.h> | ||
19 | #include <linux/fs.h> | 29 | #include <linux/fs.h> |
20 | #include <linux/preempt.h> | 30 | #include <linux/gfp.h> |
21 | #include <linux/smp.h> | ||
22 | #include <linux/notifier.h> | ||
23 | #include <linux/kdebug.h> | 31 | #include <linux/kdebug.h> |
24 | #include <linux/cpu.h> | 32 | #include <linux/kernel.h> |
33 | #include <linux/kobject.h> | ||
34 | #include <linux/module.h> | ||
35 | #include <linux/notifier.h> | ||
36 | #include <linux/pci.h> | ||
37 | #include <linux/preempt.h> | ||
25 | #include <linux/sched.h> | 38 | #include <linux/sched.h> |
26 | #include <linux/gfp.h> | 39 | #include <linux/smp.h> |
27 | #include <asm/mce.h> | 40 | #include <linux/string.h> |
41 | #include <linux/timer.h> | ||
42 | #include <linux/uaccess.h> | ||
43 | |||
44 | #include <asm/amd_nb.h> | ||
28 | #include <asm/apic.h> | 45 | #include <asm/apic.h> |
46 | #include <asm/irq_vectors.h> | ||
47 | #include <asm/mce.h> | ||
29 | #include <asm/nmi.h> | 48 | #include <asm/nmi.h> |
49 | #include <asm/smp.h> | ||
50 | |||
51 | #include "mce-internal.h" | ||
52 | |||
53 | /* | ||
54 | * Collect all the MCi_XXX settings | ||
55 | */ | ||
56 | static struct mce i_mce; | ||
57 | static struct dentry *dfs_inj; | ||
58 | |||
59 | static u8 n_banks; | ||
60 | |||
61 | #define MAX_FLAG_OPT_SIZE 3 | ||
62 | #define NBCFG 0x44 | ||
63 | |||
64 | enum injection_type { | ||
65 | SW_INJ = 0, /* SW injection, simply decode the error */ | ||
66 | HW_INJ, /* Trigger a #MC */ | ||
67 | DFR_INT_INJ, /* Trigger Deferred error interrupt */ | ||
68 | THR_INT_INJ, /* Trigger threshold interrupt */ | ||
69 | N_INJ_TYPES, | ||
70 | }; | ||
71 | |||
72 | static const char * const flags_options[] = { | ||
73 | [SW_INJ] = "sw", | ||
74 | [HW_INJ] = "hw", | ||
75 | [DFR_INT_INJ] = "df", | ||
76 | [THR_INT_INJ] = "th", | ||
77 | NULL | ||
78 | }; | ||
79 | |||
80 | /* Set default injection to SW_INJ */ | ||
81 | static enum injection_type inj_type = SW_INJ; | ||
82 | |||
83 | #define MCE_INJECT_SET(reg) \ | ||
84 | static int inj_##reg##_set(void *data, u64 val) \ | ||
85 | { \ | ||
86 | struct mce *m = (struct mce *)data; \ | ||
87 | \ | ||
88 | m->reg = val; \ | ||
89 | return 0; \ | ||
90 | } | ||
91 | |||
92 | MCE_INJECT_SET(status); | ||
93 | MCE_INJECT_SET(misc); | ||
94 | MCE_INJECT_SET(addr); | ||
95 | MCE_INJECT_SET(synd); | ||
96 | |||
97 | #define MCE_INJECT_GET(reg) \ | ||
98 | static int inj_##reg##_get(void *data, u64 *val) \ | ||
99 | { \ | ||
100 | struct mce *m = (struct mce *)data; \ | ||
101 | \ | ||
102 | *val = m->reg; \ | ||
103 | return 0; \ | ||
104 | } | ||
105 | |||
106 | MCE_INJECT_GET(status); | ||
107 | MCE_INJECT_GET(misc); | ||
108 | MCE_INJECT_GET(addr); | ||
109 | MCE_INJECT_GET(synd); | ||
110 | |||
111 | DEFINE_SIMPLE_ATTRIBUTE(status_fops, inj_status_get, inj_status_set, "%llx\n"); | ||
112 | DEFINE_SIMPLE_ATTRIBUTE(misc_fops, inj_misc_get, inj_misc_set, "%llx\n"); | ||
113 | DEFINE_SIMPLE_ATTRIBUTE(addr_fops, inj_addr_get, inj_addr_set, "%llx\n"); | ||
114 | DEFINE_SIMPLE_ATTRIBUTE(synd_fops, inj_synd_get, inj_synd_set, "%llx\n"); | ||
30 | 115 | ||
31 | /* Update fake mce registers on current CPU. */ | 116 | /* Update fake mce registers on current CPU. */ |
32 | static void inject_mce(struct mce *m) | 117 | static void inject_mce(struct mce *m) |
@@ -143,7 +228,7 @@ static int raise_local(void) | |||
143 | return ret; | 228 | return ret; |
144 | } | 229 | } |
145 | 230 | ||
146 | static void raise_mce(struct mce *m) | 231 | static void __maybe_unused raise_mce(struct mce *m) |
147 | { | 232 | { |
148 | int context = MCJ_CTX(m->inject_flags); | 233 | int context = MCJ_CTX(m->inject_flags); |
149 | 234 | ||
@@ -198,6 +283,7 @@ static void raise_mce(struct mce *m) | |||
198 | } | 283 | } |
199 | } | 284 | } |
200 | 285 | ||
286 | #ifdef CONFIG_X86_MCELOG_LEGACY | ||
201 | /* Error injection interface */ | 287 | /* Error injection interface */ |
202 | static ssize_t mce_write(struct file *filp, const char __user *ubuf, | 288 | static ssize_t mce_write(struct file *filp, const char __user *ubuf, |
203 | size_t usize, loff_t *off) | 289 | size_t usize, loff_t *off) |
@@ -232,21 +318,445 @@ static ssize_t mce_write(struct file *filp, const char __user *ubuf, | |||
232 | mutex_unlock(&mce_inject_mutex); | 318 | mutex_unlock(&mce_inject_mutex); |
233 | return usize; | 319 | return usize; |
234 | } | 320 | } |
321 | #endif /* CONFIG_X86_MCELOG_LEGACY */ | ||
322 | |||
323 | /* | ||
324 | * Caller needs to be make sure this cpu doesn't disappear | ||
325 | * from under us, i.e.: get_cpu/put_cpu. | ||
326 | */ | ||
327 | static int toggle_hw_mce_inject(unsigned int cpu, bool enable) | ||
328 | { | ||
329 | u32 l, h; | ||
330 | int err; | ||
331 | |||
332 | err = rdmsr_on_cpu(cpu, MSR_K7_HWCR, &l, &h); | ||
333 | if (err) { | ||
334 | pr_err("%s: error reading HWCR\n", __func__); | ||
335 | return err; | ||
336 | } | ||
337 | |||
338 | enable ? (l |= BIT(18)) : (l &= ~BIT(18)); | ||
339 | |||
340 | err = wrmsr_on_cpu(cpu, MSR_K7_HWCR, l, h); | ||
341 | if (err) | ||
342 | pr_err("%s: error writing HWCR\n", __func__); | ||
343 | |||
344 | return err; | ||
345 | } | ||
346 | |||
347 | static int __set_inj(const char *buf) | ||
348 | { | ||
349 | int i; | ||
350 | |||
351 | for (i = 0; i < N_INJ_TYPES; i++) { | ||
352 | if (!strncmp(flags_options[i], buf, strlen(flags_options[i]))) { | ||
353 | inj_type = i; | ||
354 | return 0; | ||
355 | } | ||
356 | } | ||
357 | return -EINVAL; | ||
358 | } | ||
359 | |||
360 | static ssize_t flags_read(struct file *filp, char __user *ubuf, | ||
361 | size_t cnt, loff_t *ppos) | ||
362 | { | ||
363 | char buf[MAX_FLAG_OPT_SIZE]; | ||
364 | int n; | ||
365 | |||
366 | n = sprintf(buf, "%s\n", flags_options[inj_type]); | ||
367 | |||
368 | return simple_read_from_buffer(ubuf, cnt, ppos, buf, n); | ||
369 | } | ||
370 | |||
371 | static ssize_t flags_write(struct file *filp, const char __user *ubuf, | ||
372 | size_t cnt, loff_t *ppos) | ||
373 | { | ||
374 | char buf[MAX_FLAG_OPT_SIZE], *__buf; | ||
375 | int err; | ||
376 | |||
377 | if (cnt > MAX_FLAG_OPT_SIZE) | ||
378 | return -EINVAL; | ||
379 | |||
380 | if (copy_from_user(&buf, ubuf, cnt)) | ||
381 | return -EFAULT; | ||
382 | |||
383 | buf[cnt - 1] = 0; | ||
384 | |||
385 | /* strip whitespace */ | ||
386 | __buf = strstrip(buf); | ||
387 | |||
388 | err = __set_inj(__buf); | ||
389 | if (err) { | ||
390 | pr_err("%s: Invalid flags value: %s\n", __func__, __buf); | ||
391 | return err; | ||
392 | } | ||
393 | |||
394 | *ppos += cnt; | ||
395 | |||
396 | return cnt; | ||
397 | } | ||
398 | |||
399 | static const struct file_operations flags_fops = { | ||
400 | .read = flags_read, | ||
401 | .write = flags_write, | ||
402 | .llseek = generic_file_llseek, | ||
403 | }; | ||
404 | |||
405 | /* | ||
406 | * On which CPU to inject? | ||
407 | */ | ||
408 | MCE_INJECT_GET(extcpu); | ||
409 | |||
410 | static int inj_extcpu_set(void *data, u64 val) | ||
411 | { | ||
412 | struct mce *m = (struct mce *)data; | ||
413 | |||
414 | if (val >= nr_cpu_ids || !cpu_online(val)) { | ||
415 | pr_err("%s: Invalid CPU: %llu\n", __func__, val); | ||
416 | return -EINVAL; | ||
417 | } | ||
418 | m->extcpu = val; | ||
419 | return 0; | ||
420 | } | ||
421 | |||
422 | DEFINE_SIMPLE_ATTRIBUTE(extcpu_fops, inj_extcpu_get, inj_extcpu_set, "%llu\n"); | ||
423 | |||
424 | static void trigger_mce(void *info) | ||
425 | { | ||
426 | asm volatile("int $18"); | ||
427 | } | ||
428 | |||
429 | static void trigger_dfr_int(void *info) | ||
430 | { | ||
431 | asm volatile("int %0" :: "i" (DEFERRED_ERROR_VECTOR)); | ||
432 | } | ||
433 | |||
434 | static void trigger_thr_int(void *info) | ||
435 | { | ||
436 | asm volatile("int %0" :: "i" (THRESHOLD_APIC_VECTOR)); | ||
437 | } | ||
438 | |||
439 | static u32 get_nbc_for_node(int node_id) | ||
440 | { | ||
441 | struct cpuinfo_x86 *c = &boot_cpu_data; | ||
442 | u32 cores_per_node; | ||
443 | |||
444 | cores_per_node = (c->x86_max_cores * smp_num_siblings) / amd_get_nodes_per_socket(); | ||
445 | |||
446 | return cores_per_node * node_id; | ||
447 | } | ||
448 | |||
449 | static void toggle_nb_mca_mst_cpu(u16 nid) | ||
450 | { | ||
451 | struct amd_northbridge *nb; | ||
452 | struct pci_dev *F3; | ||
453 | u32 val; | ||
454 | int err; | ||
455 | |||
456 | nb = node_to_amd_nb(nid); | ||
457 | if (!nb) | ||
458 | return; | ||
459 | |||
460 | F3 = nb->misc; | ||
461 | if (!F3) | ||
462 | return; | ||
463 | |||
464 | err = pci_read_config_dword(F3, NBCFG, &val); | ||
465 | if (err) { | ||
466 | pr_err("%s: Error reading F%dx%03x.\n", | ||
467 | __func__, PCI_FUNC(F3->devfn), NBCFG); | ||
468 | return; | ||
469 | } | ||
470 | |||
471 | if (val & BIT(27)) | ||
472 | return; | ||
473 | |||
474 | pr_err("%s: Set D18F3x44[NbMcaToMstCpuEn] which BIOS hasn't done.\n", | ||
475 | __func__); | ||
476 | |||
477 | val |= BIT(27); | ||
478 | err = pci_write_config_dword(F3, NBCFG, val); | ||
479 | if (err) | ||
480 | pr_err("%s: Error writing F%dx%03x.\n", | ||
481 | __func__, PCI_FUNC(F3->devfn), NBCFG); | ||
482 | } | ||
235 | 483 | ||
236 | static int inject_init(void) | 484 | static void prepare_msrs(void *info) |
237 | { | 485 | { |
486 | struct mce m = *(struct mce *)info; | ||
487 | u8 b = m.bank; | ||
488 | |||
489 | wrmsrl(MSR_IA32_MCG_STATUS, m.mcgstatus); | ||
490 | |||
491 | if (boot_cpu_has(X86_FEATURE_SMCA)) { | ||
492 | if (m.inject_flags == DFR_INT_INJ) { | ||
493 | wrmsrl(MSR_AMD64_SMCA_MCx_DESTAT(b), m.status); | ||
494 | wrmsrl(MSR_AMD64_SMCA_MCx_DEADDR(b), m.addr); | ||
495 | } else { | ||
496 | wrmsrl(MSR_AMD64_SMCA_MCx_STATUS(b), m.status); | ||
497 | wrmsrl(MSR_AMD64_SMCA_MCx_ADDR(b), m.addr); | ||
498 | } | ||
499 | |||
500 | wrmsrl(MSR_AMD64_SMCA_MCx_MISC(b), m.misc); | ||
501 | wrmsrl(MSR_AMD64_SMCA_MCx_SYND(b), m.synd); | ||
502 | } else { | ||
503 | wrmsrl(MSR_IA32_MCx_STATUS(b), m.status); | ||
504 | wrmsrl(MSR_IA32_MCx_ADDR(b), m.addr); | ||
505 | wrmsrl(MSR_IA32_MCx_MISC(b), m.misc); | ||
506 | } | ||
507 | } | ||
508 | |||
509 | static void do_inject(void) | ||
510 | { | ||
511 | u64 mcg_status = 0; | ||
512 | unsigned int cpu = i_mce.extcpu; | ||
513 | u8 b = i_mce.bank; | ||
514 | |||
515 | rdtscll(i_mce.tsc); | ||
516 | |||
517 | if (i_mce.misc) | ||
518 | i_mce.status |= MCI_STATUS_MISCV; | ||
519 | |||
520 | if (i_mce.synd) | ||
521 | i_mce.status |= MCI_STATUS_SYNDV; | ||
522 | |||
523 | if (inj_type == SW_INJ) { | ||
524 | mce_inject_log(&i_mce); | ||
525 | return; | ||
526 | } | ||
527 | |||
528 | /* prep MCE global settings for the injection */ | ||
529 | mcg_status = MCG_STATUS_MCIP | MCG_STATUS_EIPV; | ||
530 | |||
531 | if (!(i_mce.status & MCI_STATUS_PCC)) | ||
532 | mcg_status |= MCG_STATUS_RIPV; | ||
533 | |||
534 | /* | ||
535 | * Ensure necessary status bits for deferred errors: | ||
536 | * - MCx_STATUS[Deferred]: make sure it is a deferred error | ||
537 | * - MCx_STATUS[UC] cleared: deferred errors are _not_ UC | ||
538 | */ | ||
539 | if (inj_type == DFR_INT_INJ) { | ||
540 | i_mce.status |= MCI_STATUS_DEFERRED; | ||
541 | i_mce.status |= (i_mce.status & ~MCI_STATUS_UC); | ||
542 | } | ||
543 | |||
544 | /* | ||
545 | * For multi node CPUs, logging and reporting of bank 4 errors happens | ||
546 | * only on the node base core. Refer to D18F3x44[NbMcaToMstCpuEn] for | ||
547 | * Fam10h and later BKDGs. | ||
548 | */ | ||
549 | if (static_cpu_has(X86_FEATURE_AMD_DCM) && | ||
550 | b == 4 && | ||
551 | boot_cpu_data.x86 < 0x17) { | ||
552 | toggle_nb_mca_mst_cpu(amd_get_nb_id(cpu)); | ||
553 | cpu = get_nbc_for_node(amd_get_nb_id(cpu)); | ||
554 | } | ||
555 | |||
556 | get_online_cpus(); | ||
557 | if (!cpu_online(cpu)) | ||
558 | goto err; | ||
559 | |||
560 | toggle_hw_mce_inject(cpu, true); | ||
561 | |||
562 | i_mce.mcgstatus = mcg_status; | ||
563 | i_mce.inject_flags = inj_type; | ||
564 | smp_call_function_single(cpu, prepare_msrs, &i_mce, 0); | ||
565 | |||
566 | toggle_hw_mce_inject(cpu, false); | ||
567 | |||
568 | switch (inj_type) { | ||
569 | case DFR_INT_INJ: | ||
570 | smp_call_function_single(cpu, trigger_dfr_int, NULL, 0); | ||
571 | break; | ||
572 | case THR_INT_INJ: | ||
573 | smp_call_function_single(cpu, trigger_thr_int, NULL, 0); | ||
574 | break; | ||
575 | default: | ||
576 | smp_call_function_single(cpu, trigger_mce, NULL, 0); | ||
577 | } | ||
578 | |||
579 | err: | ||
580 | put_online_cpus(); | ||
581 | |||
582 | } | ||
583 | |||
584 | /* | ||
585 | * This denotes into which bank we're injecting and triggers | ||
586 | * the injection, at the same time. | ||
587 | */ | ||
588 | static int inj_bank_set(void *data, u64 val) | ||
589 | { | ||
590 | struct mce *m = (struct mce *)data; | ||
591 | |||
592 | if (val >= n_banks) { | ||
593 | pr_err("Non-existent MCE bank: %llu\n", val); | ||
594 | return -EINVAL; | ||
595 | } | ||
596 | |||
597 | m->bank = val; | ||
598 | do_inject(); | ||
599 | |||
600 | return 0; | ||
601 | } | ||
602 | |||
603 | MCE_INJECT_GET(bank); | ||
604 | |||
605 | DEFINE_SIMPLE_ATTRIBUTE(bank_fops, inj_bank_get, inj_bank_set, "%llu\n"); | ||
606 | |||
607 | static const char readme_msg[] = | ||
608 | "Description of the files and their usages:\n" | ||
609 | "\n" | ||
610 | "Note1: i refers to the bank number below.\n" | ||
611 | "Note2: See respective BKDGs for the exact bit definitions of the files below\n" | ||
612 | "as they mirror the hardware registers.\n" | ||
613 | "\n" | ||
614 | "status:\t Set MCi_STATUS: the bits in that MSR control the error type and\n" | ||
615 | "\t attributes of the error which caused the MCE.\n" | ||
616 | "\n" | ||
617 | "misc:\t Set MCi_MISC: provide auxiliary info about the error. It is mostly\n" | ||
618 | "\t used for error thresholding purposes and its validity is indicated by\n" | ||
619 | "\t MCi_STATUS[MiscV].\n" | ||
620 | "\n" | ||
621 | "synd:\t Set MCi_SYND: provide syndrome info about the error. Only valid on\n" | ||
622 | "\t Scalable MCA systems, and its validity is indicated by MCi_STATUS[SyndV].\n" | ||
623 | "\n" | ||
624 | "addr:\t Error address value to be written to MCi_ADDR. Log address information\n" | ||
625 | "\t associated with the error.\n" | ||
626 | "\n" | ||
627 | "cpu:\t The CPU to inject the error on.\n" | ||
628 | "\n" | ||
629 | "bank:\t Specify the bank you want to inject the error into: the number of\n" | ||
630 | "\t banks in a processor varies and is family/model-specific, therefore, the\n" | ||
631 | "\t supplied value is sanity-checked. Setting the bank value also triggers the\n" | ||
632 | "\t injection.\n" | ||
633 | "\n" | ||
634 | "flags:\t Injection type to be performed. Writing to this file will trigger a\n" | ||
635 | "\t real machine check, an APIC interrupt or invoke the error decoder routines\n" | ||
636 | "\t for AMD processors.\n" | ||
637 | "\n" | ||
638 | "\t Allowed error injection types:\n" | ||
639 | "\t - \"sw\": Software error injection. Decode error to a human-readable \n" | ||
640 | "\t format only. Safe to use.\n" | ||
641 | "\t - \"hw\": Hardware error injection. Causes the #MC exception handler to \n" | ||
642 | "\t handle the error. Be warned: might cause system panic if MCi_STATUS[PCC] \n" | ||
643 | "\t is set. Therefore, consider setting (debugfs_mountpoint)/mce/fake_panic \n" | ||
644 | "\t before injecting.\n" | ||
645 | "\t - \"df\": Trigger APIC interrupt for Deferred error. Causes deferred \n" | ||
646 | "\t error APIC interrupt handler to handle the error if the feature is \n" | ||
647 | "\t is present in hardware. \n" | ||
648 | "\t - \"th\": Trigger APIC interrupt for Threshold errors. Causes threshold \n" | ||
649 | "\t APIC interrupt handler to handle the error. \n" | ||
650 | "\n"; | ||
651 | |||
652 | static ssize_t | ||
653 | inj_readme_read(struct file *filp, char __user *ubuf, | ||
654 | size_t cnt, loff_t *ppos) | ||
655 | { | ||
656 | return simple_read_from_buffer(ubuf, cnt, ppos, | ||
657 | readme_msg, strlen(readme_msg)); | ||
658 | } | ||
659 | |||
660 | static const struct file_operations readme_fops = { | ||
661 | .read = inj_readme_read, | ||
662 | }; | ||
663 | |||
664 | static struct dfs_node { | ||
665 | char *name; | ||
666 | struct dentry *d; | ||
667 | const struct file_operations *fops; | ||
668 | umode_t perm; | ||
669 | } dfs_fls[] = { | ||
670 | { .name = "status", .fops = &status_fops, .perm = S_IRUSR | S_IWUSR }, | ||
671 | { .name = "misc", .fops = &misc_fops, .perm = S_IRUSR | S_IWUSR }, | ||
672 | { .name = "addr", .fops = &addr_fops, .perm = S_IRUSR | S_IWUSR }, | ||
673 | { .name = "synd", .fops = &synd_fops, .perm = S_IRUSR | S_IWUSR }, | ||
674 | { .name = "bank", .fops = &bank_fops, .perm = S_IRUSR | S_IWUSR }, | ||
675 | { .name = "flags", .fops = &flags_fops, .perm = S_IRUSR | S_IWUSR }, | ||
676 | { .name = "cpu", .fops = &extcpu_fops, .perm = S_IRUSR | S_IWUSR }, | ||
677 | { .name = "README", .fops = &readme_fops, .perm = S_IRUSR | S_IRGRP | S_IROTH }, | ||
678 | }; | ||
679 | |||
680 | static int __init debugfs_init(void) | ||
681 | { | ||
682 | unsigned int i; | ||
683 | u64 cap; | ||
684 | |||
685 | rdmsrl(MSR_IA32_MCG_CAP, cap); | ||
686 | n_banks = cap & MCG_BANKCNT_MASK; | ||
687 | |||
688 | dfs_inj = debugfs_create_dir("mce-inject", NULL); | ||
689 | if (!dfs_inj) | ||
690 | return -EINVAL; | ||
691 | |||
692 | for (i = 0; i < ARRAY_SIZE(dfs_fls); i++) { | ||
693 | dfs_fls[i].d = debugfs_create_file(dfs_fls[i].name, | ||
694 | dfs_fls[i].perm, | ||
695 | dfs_inj, | ||
696 | &i_mce, | ||
697 | dfs_fls[i].fops); | ||
698 | |||
699 | if (!dfs_fls[i].d) | ||
700 | goto err_dfs_add; | ||
701 | } | ||
702 | |||
703 | return 0; | ||
704 | |||
705 | err_dfs_add: | ||
706 | while (i-- > 0) | ||
707 | debugfs_remove(dfs_fls[i].d); | ||
708 | |||
709 | debugfs_remove(dfs_inj); | ||
710 | dfs_inj = NULL; | ||
711 | |||
712 | return -ENODEV; | ||
713 | } | ||
714 | |||
715 | static int __init inject_init(void) | ||
716 | { | ||
717 | int err; | ||
718 | |||
238 | if (!alloc_cpumask_var(&mce_inject_cpumask, GFP_KERNEL)) | 719 | if (!alloc_cpumask_var(&mce_inject_cpumask, GFP_KERNEL)) |
239 | return -ENOMEM; | 720 | return -ENOMEM; |
721 | |||
722 | #ifdef CONFIG_X86_MCELOG_LEGACY | ||
723 | register_mce_write_callback(mce_write); | ||
724 | #endif | ||
725 | |||
726 | register_nmi_handler(NMI_LOCAL, mce_raise_notify, 0, "mce_notify"); | ||
727 | |||
728 | err = debugfs_init(); | ||
729 | if (err) { | ||
730 | free_cpumask_var(mce_inject_cpumask); | ||
731 | return err; | ||
732 | } | ||
733 | |||
240 | pr_info("Machine check injector initialized\n"); | 734 | pr_info("Machine check injector initialized\n"); |
241 | register_mce_write_callback(mce_write); | 735 | |
242 | register_nmi_handler(NMI_LOCAL, mce_raise_notify, 0, | ||
243 | "mce_notify"); | ||
244 | return 0; | 736 | return 0; |
245 | } | 737 | } |
246 | 738 | ||
247 | module_init(inject_init); | 739 | module_init(inject_init); |
740 | |||
248 | /* | 741 | /* |
249 | * Cannot tolerate unloading currently because we cannot | 742 | * Cannot tolerate unloading currently because we cannot |
250 | * guarantee all openers of mce_chrdev will get a reference to us. | 743 | * guarantee all openers of mce_chrdev will get a reference to us. |
251 | */ | 744 | */ |
745 | #ifndef CONFIG_X86_MCELOG_LEGACY | ||
746 | static void __exit inject_exit(void) | ||
747 | { | ||
748 | |||
749 | debugfs_remove_recursive(dfs_inj); | ||
750 | dfs_inj = NULL; | ||
751 | |||
752 | memset(&dfs_fls, 0, sizeof(dfs_fls)); | ||
753 | |||
754 | unregister_nmi_handler(NMI_LOCAL, "mce_notify"); | ||
755 | |||
756 | free_cpumask_var(mce_inject_cpumask); | ||
757 | } | ||
758 | |||
759 | module_exit(inject_exit); | ||
760 | #endif | ||
761 | |||
252 | MODULE_LICENSE("GPL"); | 762 | MODULE_LICENSE("GPL"); |
diff --git a/arch/x86/ras/Kconfig b/arch/x86/ras/Kconfig index 2a2d89d39af6..bb026699ad19 100644 --- a/arch/x86/ras/Kconfig +++ b/arch/x86/ras/Kconfig | |||
@@ -1,13 +1,3 @@ | |||
1 | config MCE_AMD_INJ | ||
2 | tristate "Simple MCE injection interface for AMD processors" | ||
3 | depends on RAS && X86_MCE && DEBUG_FS && AMD_NB | ||
4 | default n | ||
5 | help | ||
6 | This is a simple debugfs interface to inject MCEs and test different | ||
7 | aspects of the MCE handling code. | ||
8 | |||
9 | WARNING: Do not even assume this interface is staying stable! | ||
10 | |||
11 | config RAS_CEC | 1 | config RAS_CEC |
12 | bool "Correctable Errors Collector" | 2 | bool "Correctable Errors Collector" |
13 | depends on X86_MCE && MEMORY_FAILURE && DEBUG_FS | 3 | depends on X86_MCE && MEMORY_FAILURE && DEBUG_FS |
@@ -20,4 +10,3 @@ config RAS_CEC | |||
20 | 10 | ||
21 | Bear in mind that this is absolutely useless if your platform doesn't | 11 | Bear in mind that this is absolutely useless if your platform doesn't |
22 | have ECC DIMMs and doesn't have DRAM ECC checking enabled in the BIOS. | 12 | have ECC DIMMs and doesn't have DRAM ECC checking enabled in the BIOS. |
23 | |||
diff --git a/arch/x86/ras/Makefile b/arch/x86/ras/Makefile deleted file mode 100644 index 5f94546db280..000000000000 --- a/arch/x86/ras/Makefile +++ /dev/null | |||
@@ -1,2 +0,0 @@ | |||
1 | obj-$(CONFIG_MCE_AMD_INJ) += mce_amd_inj.o | ||
2 | |||
diff --git a/arch/x86/ras/mce_amd_inj.c b/arch/x86/ras/mce_amd_inj.c deleted file mode 100644 index 8730c2882fff..000000000000 --- a/arch/x86/ras/mce_amd_inj.c +++ /dev/null | |||
@@ -1,492 +0,0 @@ | |||
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-15: 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 <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 | * Collect all the MCi_XXX settings | ||
31 | */ | ||
32 | static struct mce i_mce; | ||
33 | static struct dentry *dfs_inj; | ||
34 | |||
35 | static u8 n_banks; | ||
36 | |||
37 | #define MAX_FLAG_OPT_SIZE 3 | ||
38 | #define NBCFG 0x44 | ||
39 | |||
40 | enum injection_type { | ||
41 | SW_INJ = 0, /* SW injection, simply decode the error */ | ||
42 | HW_INJ, /* Trigger a #MC */ | ||
43 | DFR_INT_INJ, /* Trigger Deferred error interrupt */ | ||
44 | THR_INT_INJ, /* Trigger threshold interrupt */ | ||
45 | N_INJ_TYPES, | ||
46 | }; | ||
47 | |||
48 | static 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 | /* Set default injection to SW_INJ */ | ||
57 | static enum injection_type inj_type = SW_INJ; | ||
58 | |||
59 | #define MCE_INJECT_SET(reg) \ | ||
60 | static 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 | |||
68 | MCE_INJECT_SET(status); | ||
69 | MCE_INJECT_SET(misc); | ||
70 | MCE_INJECT_SET(addr); | ||
71 | MCE_INJECT_SET(synd); | ||
72 | |||
73 | #define MCE_INJECT_GET(reg) \ | ||
74 | static int inj_##reg##_get(void *data, u64 *val) \ | ||
75 | { \ | ||
76 | struct mce *m = (struct mce *)data; \ | ||
77 | \ | ||
78 | *val = m->reg; \ | ||
79 | return 0; \ | ||
80 | } | ||
81 | |||
82 | MCE_INJECT_GET(status); | ||
83 | MCE_INJECT_GET(misc); | ||
84 | MCE_INJECT_GET(addr); | ||
85 | MCE_INJECT_GET(synd); | ||
86 | |||
87 | DEFINE_SIMPLE_ATTRIBUTE(status_fops, inj_status_get, inj_status_set, "%llx\n"); | ||
88 | DEFINE_SIMPLE_ATTRIBUTE(misc_fops, inj_misc_get, inj_misc_set, "%llx\n"); | ||
89 | DEFINE_SIMPLE_ATTRIBUTE(addr_fops, inj_addr_get, inj_addr_set, "%llx\n"); | ||
90 | DEFINE_SIMPLE_ATTRIBUTE(synd_fops, inj_synd_get, inj_synd_set, "%llx\n"); | ||
91 | |||
92 | /* | ||
93 | * Caller needs to be make sure this cpu doesn't disappear | ||
94 | * from under us, i.e.: get_cpu/put_cpu. | ||
95 | */ | ||
96 | static int toggle_hw_mce_inject(unsigned int cpu, bool enable) | ||
97 | { | ||
98 | u32 l, h; | ||
99 | int err; | ||
100 | |||
101 | err = rdmsr_on_cpu(cpu, MSR_K7_HWCR, &l, &h); | ||
102 | if (err) { | ||
103 | pr_err("%s: error reading HWCR\n", __func__); | ||
104 | return err; | ||
105 | } | ||
106 | |||
107 | enable ? (l |= BIT(18)) : (l &= ~BIT(18)); | ||
108 | |||
109 | err = wrmsr_on_cpu(cpu, MSR_K7_HWCR, l, h); | ||
110 | if (err) | ||
111 | pr_err("%s: error writing HWCR\n", __func__); | ||
112 | |||
113 | return err; | ||
114 | } | ||
115 | |||
116 | static int __set_inj(const char *buf) | ||
117 | { | ||
118 | int i; | ||
119 | |||
120 | for (i = 0; i < N_INJ_TYPES; i++) { | ||
121 | if (!strncmp(flags_options[i], buf, strlen(flags_options[i]))) { | ||
122 | inj_type = i; | ||
123 | return 0; | ||
124 | } | ||
125 | } | ||
126 | return -EINVAL; | ||
127 | } | ||
128 | |||
129 | static ssize_t flags_read(struct file *filp, char __user *ubuf, | ||
130 | size_t cnt, loff_t *ppos) | ||
131 | { | ||
132 | char buf[MAX_FLAG_OPT_SIZE]; | ||
133 | int n; | ||
134 | |||
135 | n = sprintf(buf, "%s\n", flags_options[inj_type]); | ||
136 | |||
137 | return simple_read_from_buffer(ubuf, cnt, ppos, buf, n); | ||
138 | } | ||
139 | |||
140 | static ssize_t flags_write(struct file *filp, const char __user *ubuf, | ||
141 | size_t cnt, loff_t *ppos) | ||
142 | { | ||
143 | char buf[MAX_FLAG_OPT_SIZE], *__buf; | ||
144 | int err; | ||
145 | |||
146 | if (cnt > MAX_FLAG_OPT_SIZE) | ||
147 | return -EINVAL; | ||
148 | |||
149 | if (copy_from_user(&buf, ubuf, cnt)) | ||
150 | return -EFAULT; | ||
151 | |||
152 | buf[cnt - 1] = 0; | ||
153 | |||
154 | /* strip whitespace */ | ||
155 | __buf = strstrip(buf); | ||
156 | |||
157 | err = __set_inj(__buf); | ||
158 | if (err) { | ||
159 | pr_err("%s: Invalid flags value: %s\n", __func__, __buf); | ||
160 | return err; | ||
161 | } | ||
162 | |||
163 | *ppos += cnt; | ||
164 | |||
165 | return cnt; | ||
166 | } | ||
167 | |||
168 | static const struct file_operations flags_fops = { | ||
169 | .read = flags_read, | ||
170 | .write = flags_write, | ||
171 | .llseek = generic_file_llseek, | ||
172 | }; | ||
173 | |||
174 | /* | ||
175 | * On which CPU to inject? | ||
176 | */ | ||
177 | MCE_INJECT_GET(extcpu); | ||
178 | |||
179 | static int inj_extcpu_set(void *data, u64 val) | ||
180 | { | ||
181 | struct mce *m = (struct mce *)data; | ||
182 | |||
183 | if (val >= nr_cpu_ids || !cpu_online(val)) { | ||
184 | pr_err("%s: Invalid CPU: %llu\n", __func__, val); | ||
185 | return -EINVAL; | ||
186 | } | ||
187 | m->extcpu = val; | ||
188 | return 0; | ||
189 | } | ||
190 | |||
191 | DEFINE_SIMPLE_ATTRIBUTE(extcpu_fops, inj_extcpu_get, inj_extcpu_set, "%llu\n"); | ||
192 | |||
193 | static void trigger_mce(void *info) | ||
194 | { | ||
195 | asm volatile("int $18"); | ||
196 | } | ||
197 | |||
198 | static void trigger_dfr_int(void *info) | ||
199 | { | ||
200 | asm volatile("int %0" :: "i" (DEFERRED_ERROR_VECTOR)); | ||
201 | } | ||
202 | |||
203 | static void trigger_thr_int(void *info) | ||
204 | { | ||
205 | asm volatile("int %0" :: "i" (THRESHOLD_APIC_VECTOR)); | ||
206 | } | ||
207 | |||
208 | static u32 get_nbc_for_node(int node_id) | ||
209 | { | ||
210 | struct cpuinfo_x86 *c = &boot_cpu_data; | ||
211 | u32 cores_per_node; | ||
212 | |||
213 | cores_per_node = (c->x86_max_cores * smp_num_siblings) / amd_get_nodes_per_socket(); | ||
214 | |||
215 | return cores_per_node * node_id; | ||
216 | } | ||
217 | |||
218 | static void toggle_nb_mca_mst_cpu(u16 nid) | ||
219 | { | ||
220 | struct pci_dev *F3 = node_to_amd_nb(nid)->misc; | ||
221 | u32 val; | ||
222 | int err; | ||
223 | |||
224 | if (!F3) | ||
225 | return; | ||
226 | |||
227 | err = pci_read_config_dword(F3, NBCFG, &val); | ||
228 | if (err) { | ||
229 | pr_err("%s: Error reading F%dx%03x.\n", | ||
230 | __func__, PCI_FUNC(F3->devfn), NBCFG); | ||
231 | return; | ||
232 | } | ||
233 | |||
234 | if (val & BIT(27)) | ||
235 | return; | ||
236 | |||
237 | pr_err("%s: Set D18F3x44[NbMcaToMstCpuEn] which BIOS hasn't done.\n", | ||
238 | __func__); | ||
239 | |||
240 | val |= BIT(27); | ||
241 | err = pci_write_config_dword(F3, NBCFG, val); | ||
242 | if (err) | ||
243 | pr_err("%s: Error writing F%dx%03x.\n", | ||
244 | __func__, PCI_FUNC(F3->devfn), NBCFG); | ||
245 | } | ||
246 | |||
247 | static void prepare_msrs(void *info) | ||
248 | { | ||
249 | struct mce m = *(struct mce *)info; | ||
250 | u8 b = m.bank; | ||
251 | |||
252 | wrmsrl(MSR_IA32_MCG_STATUS, m.mcgstatus); | ||
253 | |||
254 | if (boot_cpu_has(X86_FEATURE_SMCA)) { | ||
255 | if (m.inject_flags == DFR_INT_INJ) { | ||
256 | wrmsrl(MSR_AMD64_SMCA_MCx_DESTAT(b), m.status); | ||
257 | wrmsrl(MSR_AMD64_SMCA_MCx_DEADDR(b), m.addr); | ||
258 | } else { | ||
259 | wrmsrl(MSR_AMD64_SMCA_MCx_STATUS(b), m.status); | ||
260 | wrmsrl(MSR_AMD64_SMCA_MCx_ADDR(b), m.addr); | ||
261 | } | ||
262 | |||
263 | wrmsrl(MSR_AMD64_SMCA_MCx_MISC(b), m.misc); | ||
264 | wrmsrl(MSR_AMD64_SMCA_MCx_SYND(b), m.synd); | ||
265 | } else { | ||
266 | wrmsrl(MSR_IA32_MCx_STATUS(b), m.status); | ||
267 | wrmsrl(MSR_IA32_MCx_ADDR(b), m.addr); | ||
268 | wrmsrl(MSR_IA32_MCx_MISC(b), m.misc); | ||
269 | } | ||
270 | } | ||
271 | |||
272 | static void do_inject(void) | ||
273 | { | ||
274 | u64 mcg_status = 0; | ||
275 | unsigned int cpu = i_mce.extcpu; | ||
276 | u8 b = i_mce.bank; | ||
277 | |||
278 | rdtscll(i_mce.tsc); | ||
279 | |||
280 | if (i_mce.misc) | ||
281 | i_mce.status |= MCI_STATUS_MISCV; | ||
282 | |||
283 | if (i_mce.synd) | ||
284 | i_mce.status |= MCI_STATUS_SYNDV; | ||
285 | |||
286 | if (inj_type == SW_INJ) { | ||
287 | mce_inject_log(&i_mce); | ||
288 | return; | ||
289 | } | ||
290 | |||
291 | /* prep MCE global settings for the injection */ | ||
292 | mcg_status = MCG_STATUS_MCIP | MCG_STATUS_EIPV; | ||
293 | |||
294 | if (!(i_mce.status & MCI_STATUS_PCC)) | ||
295 | mcg_status |= MCG_STATUS_RIPV; | ||
296 | |||
297 | /* | ||
298 | * Ensure necessary status bits for deferred errors: | ||
299 | * - MCx_STATUS[Deferred]: make sure it is a deferred error | ||
300 | * - MCx_STATUS[UC] cleared: deferred errors are _not_ UC | ||
301 | */ | ||
302 | if (inj_type == DFR_INT_INJ) { | ||
303 | i_mce.status |= MCI_STATUS_DEFERRED; | ||
304 | i_mce.status |= (i_mce.status & ~MCI_STATUS_UC); | ||
305 | } | ||
306 | |||
307 | /* | ||
308 | * For multi node CPUs, logging and reporting of bank 4 errors happens | ||
309 | * only on the node base core. Refer to D18F3x44[NbMcaToMstCpuEn] for | ||
310 | * Fam10h and later BKDGs. | ||
311 | */ | ||
312 | if (static_cpu_has(X86_FEATURE_AMD_DCM) && | ||
313 | b == 4 && | ||
314 | boot_cpu_data.x86 < 0x17) { | ||
315 | toggle_nb_mca_mst_cpu(amd_get_nb_id(cpu)); | ||
316 | cpu = get_nbc_for_node(amd_get_nb_id(cpu)); | ||
317 | } | ||
318 | |||
319 | get_online_cpus(); | ||
320 | if (!cpu_online(cpu)) | ||
321 | goto err; | ||
322 | |||
323 | toggle_hw_mce_inject(cpu, true); | ||
324 | |||
325 | i_mce.mcgstatus = mcg_status; | ||
326 | i_mce.inject_flags = inj_type; | ||
327 | smp_call_function_single(cpu, prepare_msrs, &i_mce, 0); | ||
328 | |||
329 | toggle_hw_mce_inject(cpu, false); | ||
330 | |||
331 | switch (inj_type) { | ||
332 | case DFR_INT_INJ: | ||
333 | smp_call_function_single(cpu, trigger_dfr_int, NULL, 0); | ||
334 | break; | ||
335 | case THR_INT_INJ: | ||
336 | smp_call_function_single(cpu, trigger_thr_int, NULL, 0); | ||
337 | break; | ||
338 | default: | ||
339 | smp_call_function_single(cpu, trigger_mce, NULL, 0); | ||
340 | } | ||
341 | |||
342 | err: | ||
343 | put_online_cpus(); | ||
344 | |||
345 | } | ||
346 | |||
347 | /* | ||
348 | * This denotes into which bank we're injecting and triggers | ||
349 | * the injection, at the same time. | ||
350 | */ | ||
351 | static int inj_bank_set(void *data, u64 val) | ||
352 | { | ||
353 | struct mce *m = (struct mce *)data; | ||
354 | |||
355 | if (val >= n_banks) { | ||
356 | pr_err("Non-existent MCE bank: %llu\n", val); | ||
357 | return -EINVAL; | ||
358 | } | ||
359 | |||
360 | m->bank = val; | ||
361 | do_inject(); | ||
362 | |||
363 | return 0; | ||
364 | } | ||
365 | |||
366 | MCE_INJECT_GET(bank); | ||
367 | |||
368 | DEFINE_SIMPLE_ATTRIBUTE(bank_fops, inj_bank_get, inj_bank_set, "%llu\n"); | ||
369 | |||
370 | static const char readme_msg[] = | ||
371 | "Description of the files and their usages:\n" | ||
372 | "\n" | ||
373 | "Note1: i refers to the bank number below.\n" | ||
374 | "Note2: See respective BKDGs for the exact bit definitions of the files below\n" | ||
375 | "as they mirror the hardware registers.\n" | ||
376 | "\n" | ||
377 | "status:\t Set MCi_STATUS: the bits in that MSR control the error type and\n" | ||
378 | "\t attributes of the error which caused the MCE.\n" | ||
379 | "\n" | ||
380 | "misc:\t Set MCi_MISC: provide auxiliary info about the error. It is mostly\n" | ||
381 | "\t used for error thresholding purposes and its validity is indicated by\n" | ||
382 | "\t MCi_STATUS[MiscV].\n" | ||
383 | "\n" | ||
384 | "synd:\t Set MCi_SYND: provide syndrome info about the error. Only valid on\n" | ||
385 | "\t Scalable MCA systems, and its validity is indicated by MCi_STATUS[SyndV].\n" | ||
386 | "\n" | ||
387 | "addr:\t Error address value to be written to MCi_ADDR. Log address information\n" | ||
388 | "\t associated with the error.\n" | ||
389 | "\n" | ||
390 | "cpu:\t The CPU to inject the error on.\n" | ||
391 | "\n" | ||
392 | "bank:\t Specify the bank you want to inject the error into: the number of\n" | ||
393 | "\t banks in a processor varies and is family/model-specific, therefore, the\n" | ||
394 | "\t supplied value is sanity-checked. Setting the bank value also triggers the\n" | ||
395 | "\t injection.\n" | ||
396 | "\n" | ||
397 | "flags:\t Injection type to be performed. Writing to this file will trigger a\n" | ||
398 | "\t real machine check, an APIC interrupt or invoke the error decoder routines\n" | ||
399 | "\t for AMD processors.\n" | ||
400 | "\n" | ||
401 | "\t Allowed error injection types:\n" | ||
402 | "\t - \"sw\": Software error injection. Decode error to a human-readable \n" | ||
403 | "\t format only. Safe to use.\n" | ||
404 | "\t - \"hw\": Hardware error injection. Causes the #MC exception handler to \n" | ||
405 | "\t handle the error. Be warned: might cause system panic if MCi_STATUS[PCC] \n" | ||
406 | "\t is set. Therefore, consider setting (debugfs_mountpoint)/mce/fake_panic \n" | ||
407 | "\t before injecting.\n" | ||
408 | "\t - \"df\": Trigger APIC interrupt for Deferred error. Causes deferred \n" | ||
409 | "\t error APIC interrupt handler to handle the error if the feature is \n" | ||
410 | "\t is present in hardware. \n" | ||
411 | "\t - \"th\": Trigger APIC interrupt for Threshold errors. Causes threshold \n" | ||
412 | "\t APIC interrupt handler to handle the error. \n" | ||
413 | "\n"; | ||
414 | |||
415 | static ssize_t | ||
416 | inj_readme_read(struct file *filp, char __user *ubuf, | ||
417 | size_t cnt, loff_t *ppos) | ||
418 | { | ||
419 | return simple_read_from_buffer(ubuf, cnt, ppos, | ||
420 | readme_msg, strlen(readme_msg)); | ||
421 | } | ||
422 | |||
423 | static const struct file_operations readme_fops = { | ||
424 | .read = inj_readme_read, | ||
425 | }; | ||
426 | |||
427 | static struct dfs_node { | ||
428 | char *name; | ||
429 | struct dentry *d; | ||
430 | const struct file_operations *fops; | ||
431 | umode_t perm; | ||
432 | } dfs_fls[] = { | ||
433 | { .name = "status", .fops = &status_fops, .perm = S_IRUSR | S_IWUSR }, | ||
434 | { .name = "misc", .fops = &misc_fops, .perm = S_IRUSR | S_IWUSR }, | ||
435 | { .name = "addr", .fops = &addr_fops, .perm = S_IRUSR | S_IWUSR }, | ||
436 | { .name = "synd", .fops = &synd_fops, .perm = S_IRUSR | S_IWUSR }, | ||
437 | { .name = "bank", .fops = &bank_fops, .perm = S_IRUSR | S_IWUSR }, | ||
438 | { .name = "flags", .fops = &flags_fops, .perm = S_IRUSR | S_IWUSR }, | ||
439 | { .name = "cpu", .fops = &extcpu_fops, .perm = S_IRUSR | S_IWUSR }, | ||
440 | { .name = "README", .fops = &readme_fops, .perm = S_IRUSR | S_IRGRP | S_IROTH }, | ||
441 | }; | ||
442 | |||
443 | static int __init init_mce_inject(void) | ||
444 | { | ||
445 | unsigned int i; | ||
446 | u64 cap; | ||
447 | |||
448 | rdmsrl(MSR_IA32_MCG_CAP, cap); | ||
449 | n_banks = cap & MCG_BANKCNT_MASK; | ||
450 | |||
451 | dfs_inj = debugfs_create_dir("mce-inject", NULL); | ||
452 | if (!dfs_inj) | ||
453 | return -EINVAL; | ||
454 | |||
455 | for (i = 0; i < ARRAY_SIZE(dfs_fls); i++) { | ||
456 | dfs_fls[i].d = debugfs_create_file(dfs_fls[i].name, | ||
457 | dfs_fls[i].perm, | ||
458 | dfs_inj, | ||
459 | &i_mce, | ||
460 | dfs_fls[i].fops); | ||
461 | |||
462 | if (!dfs_fls[i].d) | ||
463 | goto err_dfs_add; | ||
464 | } | ||
465 | |||
466 | return 0; | ||
467 | |||
468 | err_dfs_add: | ||
469 | while (i-- > 0) | ||
470 | debugfs_remove(dfs_fls[i].d); | ||
471 | |||
472 | debugfs_remove(dfs_inj); | ||
473 | dfs_inj = NULL; | ||
474 | |||
475 | return -ENODEV; | ||
476 | } | ||
477 | |||
478 | static void __exit exit_mce_inject(void) | ||
479 | { | ||
480 | |||
481 | debugfs_remove_recursive(dfs_inj); | ||
482 | dfs_inj = NULL; | ||
483 | |||
484 | memset(&dfs_fls, 0, sizeof(dfs_fls)); | ||
485 | } | ||
486 | module_init(init_mce_inject); | ||
487 | module_exit(exit_mce_inject); | ||
488 | |||
489 | MODULE_LICENSE("GPL"); | ||
490 | MODULE_AUTHOR("Borislav Petkov <bp@alien8.de>"); | ||
491 | MODULE_AUTHOR("AMD Inc."); | ||
492 | MODULE_DESCRIPTION("MCE injection facility for RAS testing"); | ||