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