aboutsummaryrefslogtreecommitdiffstats
path: root/inject.c
blob: a9c344f26f4dd8c8dda26f12dac887de3f041d5b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
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
/* Copyright (c) 2008 by Intel Corp.
   Inject machine checks into kernel for testing.

   mce-inject is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public
   License as published by the Free Software Foundation; version
   2.

   mce-inject is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should find a copy of v2 of the GNU General Public License somewhere
   on your Linux system; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

   Authors:
        Andi Kleen
	Ying Huang
*/
#define _GNU_SOURCE 1
#include <stdio.h>
#include <sys/fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

#include "mce.h"
#include "inject.h"
#include "parser.h"
#include "util.h"

#define MAX_CPU_NUM	1024

static int cpu_num;
/* map from cpu index to cpu id */
static int cpu_map[MAX_CPU_NUM];
static struct mce **cpu_mce;

void init_cpu_info(void)
{
	FILE *f = fopen("/proc/cpuinfo", "r");
	char *line = NULL;
	size_t linesz = 0;

	if (!f)
		err("opening of /proc/cpuinfo");

	while (getdelim(&line, &linesz, '\n', f) > 0) {
		unsigned cpu;
		if (sscanf(line, "processor : %u\n", &cpu) == 1)
			cpu_map[cpu_num++] = cpu;
	}
	free(line);
	fclose(f);

	if (!cpu_num)
		err("geting cpu ids from /proc/cpuinfo");
}

void init_inject(void)
{
	cpu_mce = calloc(cpu_num, sizeof(struct mce *));
}

static inline int cpu_id_to_index(int id)
{
	int i;

	for (i = 0; i < cpu_num; i++)
		if (cpu_map[i] == id)
			return i;
	err("invalid cpu id");
	return -1;
}

static void write_mce(int fd, struct mce *m)
{
	int n = write(fd, m, sizeof(struct mce));
	if (n <= 0) 
		err("Injecting mce on /dev/mcelog");
	if (n < sizeof(struct mce)) { 
		fprintf(stderr, "Short mce write %d: kernel does not match?\n",
			n);
	}
}

struct thread { 	
	struct thread *next;
	pthread_t thr;
	struct mce *m;
	struct mce otherm;
	int fd;
	int monarch;
};

volatile int blocked;

static void *injector(void *data)
{
	struct thread *t = (struct thread *)data;
	
	while (blocked)
		barrier();

	if (!t->monarch) {
		int i;
		for (i = 0; i < 1000000; i++);
	}

	write_mce(t->fd, t->m);
	return NULL;
}

/* Simulate machine check broadcast.  */
void do_inject_mce(int fd, struct mce *m)
{
	int i;
	struct mce otherm;
	struct thread *tlist = NULL;

	memset(&otherm, 0, sizeof(struct mce));
	// make sure to trigger exception on the secondaries
	otherm.mcgstatus = m->mcgstatus & MCG_STATUS_MCIP;
	otherm.status = m->status & MCI_STATUS_UC;

	blocked = 1;
	barrier();

 	for (i = 0; i < cpu_num; i++) {
 		unsigned cpu = cpu_map[i];
 		struct thread *t;
 		pthread_attr_t attr;
 		cpu_set_t aset;
 
 		NEW(t);
 		if (cpu == m->cpu) {
 			t->m = m;
 			t->monarch = 1;
 		} else if (cpu_mce[i])
 			t->m = cpu_mce[i];
 		else if (mce_flags & MCE_NOBROADCAST)
 			continue;
 		else {
 			t->m = &t->otherm;
  			t->otherm = otherm;
  			t->otherm.cpu = cpu;
  		}
 		t->fd = fd;
 		t->next = tlist;
 		tlist = t;
 
 		pthread_attr_init(&attr);
 		CPU_ZERO(&aset);
 		CPU_SET(cpu, &aset);
 		if (pthread_attr_setaffinity_np(&attr, sizeof(aset), &aset))
 			err("pthread_attr_setaffinity");
 		if (pthread_create(&t->thr, &attr, injector, t))
 			err("pthread_create");
  	}

	/* could wait here for the threads to start up, but the kernel timeout should
	   be long enough to catch slow ones */

	barrier();
	blocked = 0;

	while (tlist) { 
		struct thread *next = tlist->next;
		pthread_join(tlist->thr, NULL);
		free(tlist);
		tlist = next;
	}
}

void inject_mce(struct mce *m)
{
	int inject_fd;

	if (mce_flags & MCE_HOLD) {
		int cpu_index = cpu_id_to_index(m->cpu);
		struct mce *nm;
		NEW(nm);
		*nm = *m;
		cpu_mce[cpu_index] = nm;
		return;
	}

	inject_fd = open("/dev/mcelog", O_RDWR);
	if (inject_fd < 0) 
		err("opening of /dev/mcelog");
	if (!(m->status & MCI_STATUS_UC))
		mce_flags |= MCE_NOBROADCAST;
	do_inject_mce(inject_fd, m);
	close(inject_fd);
}

void dump_mce(struct mce *m)
{
	printf("CPU %d\n", m->cpu);
	printf("BANK %d\n", m->bank);
	printf("TSC 0x%Lx\n", m->tsc);
	printf("RIP 0x%02x:0x%Lx\n", m->cs, m->ip);
	printf("MISC 0x%Lx\n", m->misc);
	printf("ADDR 0x%Lx\n", m->addr);
	printf("STATUS 0x%Lx\n", m->status);
	printf("MCGSTATUS 0x%Lx\n\n", m->mcgstatus);
}

void submit_mce(struct mce *m)
{
	if (do_dump)
		dump_mce(m);
	else
		inject_mce(m);
}

void init_mce(struct mce *m)
{
	memset(m, 0, sizeof(struct mce));
}