2a0c829fef3dc096e2b280699ff9941d303f7bd6
[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 <linux/string.h>
19 #include <linux/uaccess.h>
20 #include <asm/mce.h>
21
22 #include "mce_amd.h"
23
24 /*
25  * Collect all the MCi_XXX settings
26  */
27 static struct mce i_mce;
28 static struct dentry *dfs_inj;
29
30 static u8 n_banks;
31
32 #define MAX_FLAG_OPT_SIZE       3
33
34 enum injection_type {
35         SW_INJ = 0,     /* SW injection, simply decode the error */
36         HW_INJ,         /* Trigger a #MC */
37         N_INJ_TYPES,
38 };
39
40 static const char * const flags_options[] = {
41         [SW_INJ] = "sw",
42         [HW_INJ] = "hw",
43         NULL
44 };
45
46 /* Set default injection to SW_INJ */
47 enum injection_type inj_type = SW_INJ;
48
49 #define MCE_INJECT_SET(reg)                                             \
50 static int inj_##reg##_set(void *data, u64 val)                         \
51 {                                                                       \
52         struct mce *m = (struct mce *)data;                             \
53                                                                         \
54         m->reg = val;                                                   \
55         return 0;                                                       \
56 }
57
58 MCE_INJECT_SET(status);
59 MCE_INJECT_SET(misc);
60 MCE_INJECT_SET(addr);
61
62 #define MCE_INJECT_GET(reg)                                             \
63 static int inj_##reg##_get(void *data, u64 *val)                        \
64 {                                                                       \
65         struct mce *m = (struct mce *)data;                             \
66                                                                         \
67         *val = m->reg;                                                  \
68         return 0;                                                       \
69 }
70
71 MCE_INJECT_GET(status);
72 MCE_INJECT_GET(misc);
73 MCE_INJECT_GET(addr);
74
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");
78
79 /*
80  * Caller needs to be make sure this cpu doesn't disappear
81  * from under us, i.e.: get_cpu/put_cpu.
82  */
83 static int toggle_hw_mce_inject(unsigned int cpu, bool enable)
84 {
85         u32 l, h;
86         int err;
87
88         err = rdmsr_on_cpu(cpu, MSR_K7_HWCR, &l, &h);
89         if (err) {
90                 pr_err("%s: error reading HWCR\n", __func__);
91                 return err;
92         }
93
94         enable ? (l |= BIT(18)) : (l &= ~BIT(18));
95
96         err = wrmsr_on_cpu(cpu, MSR_K7_HWCR, l, h);
97         if (err)
98                 pr_err("%s: error writing HWCR\n", __func__);
99
100         return err;
101 }
102
103 static int __set_inj(const char *buf)
104 {
105         int i;
106
107         for (i = 0; i < N_INJ_TYPES; i++) {
108                 if (!strncmp(flags_options[i], buf, strlen(flags_options[i]))) {
109                         inj_type = i;
110                         return 0;
111                 }
112         }
113         return -EINVAL;
114 }
115
116 static ssize_t flags_read(struct file *filp, char __user *ubuf,
117                           size_t cnt, loff_t *ppos)
118 {
119         char buf[MAX_FLAG_OPT_SIZE];
120         int n;
121
122         n = sprintf(buf, "%s\n", flags_options[inj_type]);
123
124         return simple_read_from_buffer(ubuf, cnt, ppos, buf, n);
125 }
126
127 static ssize_t flags_write(struct file *filp, const char __user *ubuf,
128                            size_t cnt, loff_t *ppos)
129 {
130         char buf[MAX_FLAG_OPT_SIZE], *__buf;
131         int err;
132         size_t ret;
133
134         if (cnt > MAX_FLAG_OPT_SIZE)
135                 cnt = MAX_FLAG_OPT_SIZE;
136
137         ret = cnt;
138
139         if (copy_from_user(&buf, ubuf, cnt))
140                 return -EFAULT;
141
142         buf[cnt - 1] = 0;
143
144         /* strip whitespace */
145         __buf = strstrip(buf);
146
147         err = __set_inj(__buf);
148         if (err) {
149                 pr_err("%s: Invalid flags value: %s\n", __func__, __buf);
150                 return err;
151         }
152
153         *ppos += ret;
154
155         return ret;
156 }
157
158 static const struct file_operations flags_fops = {
159         .read           = flags_read,
160         .write          = flags_write,
161         .llseek         = generic_file_llseek,
162 };
163
164 /*
165  * On which CPU to inject?
166  */
167 MCE_INJECT_GET(extcpu);
168
169 static int inj_extcpu_set(void *data, u64 val)
170 {
171         struct mce *m = (struct mce *)data;
172
173         if (val >= nr_cpu_ids || !cpu_online(val)) {
174                 pr_err("%s: Invalid CPU: %llu\n", __func__, val);
175                 return -EINVAL;
176         }
177         m->extcpu = val;
178         return 0;
179 }
180
181 DEFINE_SIMPLE_ATTRIBUTE(extcpu_fops, inj_extcpu_get, inj_extcpu_set, "%llu\n");
182
183 static void trigger_mce(void *info)
184 {
185         asm volatile("int $18");
186 }
187
188 static void do_inject(void)
189 {
190         u64 mcg_status = 0;
191         unsigned int cpu = i_mce.extcpu;
192         u8 b = i_mce.bank;
193
194         if (inj_type == SW_INJ) {
195                 amd_decode_mce(NULL, 0, &i_mce);
196                 return;
197         }
198
199         get_online_cpus();
200         if (!cpu_online(cpu))
201                 goto err;
202
203         /* prep MCE global settings for the injection */
204         mcg_status = MCG_STATUS_MCIP | MCG_STATUS_EIPV;
205
206         if (!(i_mce.status & MCI_STATUS_PCC))
207                 mcg_status |= MCG_STATUS_RIPV;
208
209         toggle_hw_mce_inject(cpu, true);
210
211         wrmsr_on_cpu(cpu, MSR_IA32_MCG_STATUS,
212                      (u32)mcg_status, (u32)(mcg_status >> 32));
213
214         wrmsr_on_cpu(cpu, MSR_IA32_MCx_STATUS(b),
215                      (u32)i_mce.status, (u32)(i_mce.status >> 32));
216
217         wrmsr_on_cpu(cpu, MSR_IA32_MCx_ADDR(b),
218                      (u32)i_mce.addr, (u32)(i_mce.addr >> 32));
219
220         wrmsr_on_cpu(cpu, MSR_IA32_MCx_MISC(b),
221                      (u32)i_mce.misc, (u32)(i_mce.misc >> 32));
222
223         toggle_hw_mce_inject(cpu, false);
224
225         smp_call_function_single(cpu, trigger_mce, NULL, 0);
226
227 err:
228         put_online_cpus();
229
230 }
231
232 /*
233  * This denotes into which bank we're injecting and triggers
234  * the injection, at the same time.
235  */
236 static int inj_bank_set(void *data, u64 val)
237 {
238         struct mce *m = (struct mce *)data;
239
240         if (val >= n_banks) {
241                 pr_err("Non-existent MCE bank: %llu\n", val);
242                 return -EINVAL;
243         }
244
245         m->bank = val;
246         do_inject();
247
248         return 0;
249 }
250
251 MCE_INJECT_GET(bank);
252
253 DEFINE_SIMPLE_ATTRIBUTE(bank_fops, inj_bank_get, inj_bank_set, "%llu\n");
254
255 static struct dfs_node {
256         char *name;
257         struct dentry *d;
258         const struct file_operations *fops;
259 } dfs_fls[] = {
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 },
266 };
267
268 static int __init init_mce_inject(void)
269 {
270         int i;
271         u64 cap;
272
273         rdmsrl(MSR_IA32_MCG_CAP, cap);
274         n_banks = cap & MCG_BANKCNT_MASK;
275
276         dfs_inj = debugfs_create_dir("mce-inject", NULL);
277         if (!dfs_inj)
278                 return -EINVAL;
279
280         for (i = 0; i < ARRAY_SIZE(dfs_fls); i++) {
281                 dfs_fls[i].d = debugfs_create_file(dfs_fls[i].name,
282                                                     S_IRUSR | S_IWUSR,
283                                                     dfs_inj,
284                                                     &i_mce,
285                                                     dfs_fls[i].fops);
286
287                 if (!dfs_fls[i].d)
288                         goto err_dfs_add;
289         }
290
291         return 0;
292
293 err_dfs_add:
294         while (--i >= 0)
295                 debugfs_remove(dfs_fls[i].d);
296
297         debugfs_remove(dfs_inj);
298         dfs_inj = NULL;
299
300         return -ENOMEM;
301 }
302
303 static void __exit exit_mce_inject(void)
304 {
305         int i;
306
307         for (i = 0; i < ARRAY_SIZE(dfs_fls); i++)
308                 debugfs_remove(dfs_fls[i].d);
309
310         memset(&dfs_fls, 0, sizeof(dfs_fls));
311
312         debugfs_remove(dfs_inj);
313         dfs_inj = NULL;
314 }
315 module_init(init_mce_inject);
316 module_exit(exit_mce_inject);
317
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");