| /* 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)); |
| } |