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 */
25static struct mce i_mce;
26static struct dentry *dfs_inj;
27
28#define MCE_INJECT_SET(reg)						\
29static 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
37MCE_INJECT_SET(status);
38MCE_INJECT_SET(misc);
39MCE_INJECT_SET(addr);
40
41#define MCE_INJECT_GET(reg)						\
42static 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
50MCE_INJECT_GET(status);
51MCE_INJECT_GET(misc);
52MCE_INJECT_GET(addr);
53
54DEFINE_SIMPLE_ATTRIBUTE(status_fops, inj_status_get, inj_status_set, "%llx\n");
55DEFINE_SIMPLE_ATTRIBUTE(misc_fops, inj_misc_get, inj_misc_set, "%llx\n");
56DEFINE_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 */
62static 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
82static 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
91static 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
99DEFINE_SIMPLE_ATTRIBUTE(flags_fops, flags_get, flags_set, "%llu\n");
100
101/*
102 * On which CPU to inject?
103 */
104MCE_INJECT_GET(extcpu);
105
106static 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
118DEFINE_SIMPLE_ATTRIBUTE(extcpu_fops, inj_extcpu_get, inj_extcpu_set, "%llu\n");
119
120static void trigger_mce(void *info)
121{
122	asm volatile("int $18");
123}
124
125static 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
164err:
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 */
173static 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
190static 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
198DEFINE_SIMPLE_ATTRIBUTE(bank_fops, inj_bank_get, inj_bank_set, "%llu\n");
199
200static 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
213static 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
234err_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
244static 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}
256module_init(init_mce_inject);
257module_exit(exit_mce_inject);
258
259MODULE_LICENSE("GPL");
260MODULE_AUTHOR("Borislav Petkov <bp@alien8.de>");
261MODULE_AUTHOR("AMD Inc.");
262MODULE_DESCRIPTION("MCE injection facility for RAS testing");
263