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