mce_amd_inj.c 6.5 KB
Newer Older
B
Borislav Petkov 已提交
1
/*
2 3 4
 * A simple MCE injection facility for testing different aspects of the RAS
 * code. This driver should be built as module so that it can be loaded
 * on production kernels for testing purposes.
B
Borislav Petkov 已提交
5 6 7 8
 *
 * This file may be distributed under the terms of the GNU General Public
 * License version 2.
 *
9
 * Copyright (c) 2010-14:  Borislav Petkov <bp@alien8.de>
B
Borislav Petkov 已提交
10 11 12 13
 *			Advanced Micro Devices Inc.
 */

#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 19
#include <linux/string.h>
#include <linux/uaccess.h>
B
Borislav Petkov 已提交
20 21
#include <asm/mce.h>

B
Borislav Petkov 已提交
22
#include "mce_amd.h"
B
Borislav Petkov 已提交
23 24 25 26 27

/*
 * Collect all the MCi_XXX settings
 */
static struct mce i_mce;
28
static struct dentry *dfs_inj;
B
Borislav Petkov 已提交
29

30 31
static u8 n_banks;

32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
#define MAX_FLAG_OPT_SIZE	3

enum injection_type {
	SW_INJ = 0,	/* SW injection, simply decode the error */
	HW_INJ,		/* Trigger a #MC */
	N_INJ_TYPES,
};

static const char * const flags_options[] = {
	[SW_INJ] = "sw",
	[HW_INJ] = "hw",
	NULL
};

/* Set default injection to SW_INJ */
enum injection_type inj_type = SW_INJ;

49 50
#define MCE_INJECT_SET(reg)						\
static int inj_##reg##_set(void *data, u64 val)				\
B
Borislav Petkov 已提交
51
{									\
52
	struct mce *m = (struct mce *)data;				\
B
Borislav Petkov 已提交
53
									\
54 55
	m->reg = val;							\
	return 0;							\
B
Borislav Petkov 已提交
56 57
}

58 59 60
MCE_INJECT_SET(status);
MCE_INJECT_SET(misc);
MCE_INJECT_SET(addr);
B
Borislav Petkov 已提交
61

62 63
#define MCE_INJECT_GET(reg)						\
static int inj_##reg##_get(void *data, u64 *val)			\
B
Borislav Petkov 已提交
64
{									\
65 66 67 68
	struct mce *m = (struct mce *)data;				\
									\
	*val = m->reg;							\
	return 0;							\
B
Borislav Petkov 已提交
69 70
}

71 72 73
MCE_INJECT_GET(status);
MCE_INJECT_GET(misc);
MCE_INJECT_GET(addr);
B
Borislav Petkov 已提交
74

75 76 77
DEFINE_SIMPLE_ATTRIBUTE(status_fops, inj_status_get, inj_status_set, "%llx\n");
DEFINE_SIMPLE_ATTRIBUTE(misc_fops, inj_misc_get, inj_misc_set, "%llx\n");
DEFINE_SIMPLE_ATTRIBUTE(addr_fops, inj_addr_get, inj_addr_set, "%llx\n");
B
Borislav Petkov 已提交
78

79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
/*
 * Caller needs to be make sure this cpu doesn't disappear
 * from under us, i.e.: get_cpu/put_cpu.
 */
static int toggle_hw_mce_inject(unsigned int cpu, bool enable)
{
	u32 l, h;
	int err;

	err = rdmsr_on_cpu(cpu, MSR_K7_HWCR, &l, &h);
	if (err) {
		pr_err("%s: error reading HWCR\n", __func__);
		return err;
	}

	enable ? (l |= BIT(18)) : (l &= ~BIT(18));

	err = wrmsr_on_cpu(cpu, MSR_K7_HWCR, l, h);
	if (err)
		pr_err("%s: error writing HWCR\n", __func__);

	return err;
}

103
static int __set_inj(const char *buf)
104
{
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
	int i;

	for (i = 0; i < N_INJ_TYPES; i++) {
		if (!strncmp(flags_options[i], buf, strlen(flags_options[i]))) {
			inj_type = i;
			return 0;
		}
	}
	return -EINVAL;
}

static ssize_t flags_read(struct file *filp, char __user *ubuf,
			  size_t cnt, loff_t *ppos)
{
	char buf[MAX_FLAG_OPT_SIZE];
	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 128
static ssize_t flags_write(struct file *filp, const char __user *ubuf,
			   size_t cnt, loff_t *ppos)
129
{
130 131 132
	char buf[MAX_FLAG_OPT_SIZE], *__buf;
	int err;
	size_t ret;
133

134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
	if (cnt > MAX_FLAG_OPT_SIZE)
		cnt = MAX_FLAG_OPT_SIZE;

	ret = cnt;

	if (copy_from_user(&buf, ubuf, cnt))
		return -EFAULT;

	buf[cnt - 1] = 0;

	/* strip whitespace */
	__buf = strstrip(buf);

	err = __set_inj(__buf);
	if (err) {
		pr_err("%s: Invalid flags value: %s\n", __func__, __buf);
		return err;
	}

	*ppos += ret;

	return ret;
156 157
}

158 159 160 161 162
static const struct file_operations flags_fops = {
	.read           = flags_read,
	.write          = flags_write,
	.llseek         = generic_file_llseek,
};
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182

/*
 * On which CPU to inject?
 */
MCE_INJECT_GET(extcpu);

static int inj_extcpu_set(void *data, u64 val)
{
	struct mce *m = (struct mce *)data;

	if (val >= nr_cpu_ids || !cpu_online(val)) {
		pr_err("%s: Invalid CPU: %llu\n", __func__, val);
		return -EINVAL;
	}
	m->extcpu = val;
	return 0;
}

DEFINE_SIMPLE_ATTRIBUTE(extcpu_fops, inj_extcpu_get, inj_extcpu_set, "%llu\n");

183 184 185 186 187 188 189 190 191 192 193
static void trigger_mce(void *info)
{
	asm volatile("int $18");
}

static void do_inject(void)
{
	u64 mcg_status = 0;
	unsigned int cpu = i_mce.extcpu;
	u8 b = i_mce.bank;

194
	if (inj_type == SW_INJ) {
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
		amd_decode_mce(NULL, 0, &i_mce);
		return;
	}

	get_online_cpus();
	if (!cpu_online(cpu))
		goto err;

	/* prep MCE global settings for the injection */
	mcg_status = MCG_STATUS_MCIP | MCG_STATUS_EIPV;

	if (!(i_mce.status & MCI_STATUS_PCC))
		mcg_status |= MCG_STATUS_RIPV;

	toggle_hw_mce_inject(cpu, true);

	wrmsr_on_cpu(cpu, MSR_IA32_MCG_STATUS,
		     (u32)mcg_status, (u32)(mcg_status >> 32));

	wrmsr_on_cpu(cpu, MSR_IA32_MCx_STATUS(b),
		     (u32)i_mce.status, (u32)(i_mce.status >> 32));

	wrmsr_on_cpu(cpu, MSR_IA32_MCx_ADDR(b),
		     (u32)i_mce.addr, (u32)(i_mce.addr >> 32));

	wrmsr_on_cpu(cpu, MSR_IA32_MCx_MISC(b),
		     (u32)i_mce.misc, (u32)(i_mce.misc >> 32));

	toggle_hw_mce_inject(cpu, false);

	smp_call_function_single(cpu, trigger_mce, NULL, 0);

err:
	put_online_cpus();

}

B
Borislav Petkov 已提交
232 233 234 235
/*
 * This denotes into which bank we're injecting and triggers
 * the injection, at the same time.
 */
236
static int inj_bank_set(void *data, u64 val)
B
Borislav Petkov 已提交
237
{
238
	struct mce *m = (struct mce *)data;
B
Borislav Petkov 已提交
239

240 241 242
	if (val >= n_banks) {
		pr_err("Non-existent MCE bank: %llu\n", val);
		return -EINVAL;
243
	}
B
Borislav Petkov 已提交
244

245
	m->bank = val;
246
	do_inject();
B
Borislav Petkov 已提交
247

248
	return 0;
B
Borislav Petkov 已提交
249 250
}

251
MCE_INJECT_GET(bank);
B
Borislav Petkov 已提交
252

253 254
DEFINE_SIMPLE_ATTRIBUTE(bank_fops, inj_bank_get, inj_bank_set, "%llu\n");

255
static struct dfs_node {
256 257 258 259 260 261 262 263
	char *name;
	struct dentry *d;
	const struct file_operations *fops;
} dfs_fls[] = {
	{ .name = "status",	.fops = &status_fops },
	{ .name = "misc",	.fops = &misc_fops },
	{ .name = "addr",	.fops = &addr_fops },
	{ .name = "bank",	.fops = &bank_fops },
264 265
	{ .name = "flags",	.fops = &flags_fops },
	{ .name = "cpu",	.fops = &extcpu_fops },
B
Borislav Petkov 已提交
266 267
};

268
static int __init init_mce_inject(void)
B
Borislav Petkov 已提交
269
{
270
	int i;
271 272 273 274
	u64 cap;

	rdmsrl(MSR_IA32_MCG_CAP, cap);
	n_banks = cap & MCG_BANKCNT_MASK;
B
Borislav Petkov 已提交
275

276 277
	dfs_inj = debugfs_create_dir("mce-inject", NULL);
	if (!dfs_inj)
B
Borislav Petkov 已提交
278 279
		return -EINVAL;

280 281 282 283 284 285
	for (i = 0; i < ARRAY_SIZE(dfs_fls); i++) {
		dfs_fls[i].d = debugfs_create_file(dfs_fls[i].name,
						    S_IRUSR | S_IWUSR,
						    dfs_inj,
						    &i_mce,
						    dfs_fls[i].fops);
B
Borislav Petkov 已提交
286

287 288
		if (!dfs_fls[i].d)
			goto err_dfs_add;
B
Borislav Petkov 已提交
289
	}
290

B
Borislav Petkov 已提交
291 292
	return 0;

293
err_dfs_add:
294
	while (--i >= 0)
295
		debugfs_remove(dfs_fls[i].d);
B
Borislav Petkov 已提交
296

297 298
	debugfs_remove(dfs_inj);
	dfs_inj = NULL;
B
Borislav Petkov 已提交
299

300
	return -ENOMEM;
B
Borislav Petkov 已提交
301 302
}

303
static void __exit exit_mce_inject(void)
B
Borislav Petkov 已提交
304 305 306
{
	int i;

307 308
	for (i = 0; i < ARRAY_SIZE(dfs_fls); i++)
		debugfs_remove(dfs_fls[i].d);
B
Borislav Petkov 已提交
309

310
	memset(&dfs_fls, 0, sizeof(dfs_fls));
B
Borislav Petkov 已提交
311

312 313
	debugfs_remove(dfs_inj);
	dfs_inj = NULL;
B
Borislav Petkov 已提交
314
}
315 316
module_init(init_mce_inject);
module_exit(exit_mce_inject);
B
Borislav Petkov 已提交
317 318

MODULE_LICENSE("GPL");
319
MODULE_AUTHOR("Borislav Petkov <bp@alien8.de>");
B
Borislav Petkov 已提交
320
MODULE_AUTHOR("AMD Inc.");
321
MODULE_DESCRIPTION("MCE injection facility for RAS testing");