// SPDX-License-Identifier: GPL-2.0 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include extern unsigned long long vtop(unsigned long long addr, pid_t pid); #define NR_THREADS 2 #define NR_CPUS 2 #define NR_ADDRS 2 #define EINJ_TABLE "/sys/firmware/acpi/tables/EINJ" #define EINJ_AVAIL_TYPE "/sys/kernel/debug/apei/einj/available_error_type" #define EINJ_TYPE "/sys/kernel/debug/apei/einj/error_type" #define EINJ_PARAM1 "/sys/kernel/debug/apei/einj/param1" #define EINJ_PARAM2 "/sys/kernel/debug/apei/einj/param2" #define EINJ_NOTRIGGER "/sys/kernel/debug/apei/einj/notrigger" #define EINJ_INJECT "/sys/kernel/debug/apei/einj/error_inject" struct thr_arg { char *addr; int ac_type; int cpu; char name[32]; sigjmp_buf *s_buf; }; long pagesize; sigjmp_buf recover[NR_THREADS]; pthread_t thread[NR_THREADS]; char *vaddr[NR_ADDRS] = { NULL }; volatile int ready = 0; static int ncpus; static int nmasks; int write_file(char *path, uint64_t val) { FILE *fp; fp = fopen(path, "w"); if (!fp) { fprintf(stderr, "Fail to open %s\n", path); return -1; } fprintf(fp, "0x%lx\n", val); fclose(fp); return 0; } void check_einj_available(void) { if (access(EINJ_TABLE, R_OK) == -1) { fprintf(stderr, "EINJ table isn't supported, please check BIOS setting\n"); exit(1); } if (access(EINJ_AVAIL_TYPE, R_OK) == -1) { fprintf(stderr, "Please check if einj.ko module is installed\n"); exit(1); } } void do_inject(uint64_t addr) { write_file(EINJ_TYPE, 0x10); write_file(EINJ_PARAM1, addr); write_file(EINJ_PARAM2, 0xfffffffffffff000ul); write_file(EINJ_NOTRIGGER, 1); write_file(EINJ_INJECT, 1); } void* thread_func(void *data) { struct thr_arg *ptarg = (struct thr_arg *)data; char buf[256], *err; cpu_set_t cpus; int flag = -1; CPU_ZERO(&cpus); CPU_SET(ptarg->cpu, &cpus); if (sched_setaffinity(0, sizeof(cpu_set_t), &cpus) == -1) { err = strerror_r(errno, buf, 256); fprintf(stderr, "%s failed: sched_setaffinity(%s)\n", ptarg->name, err); return NULL; } if (sigsetjmp(*ptarg->s_buf, 1) == 0) { /* * Wait until master thread tells us to access the data */ while (!ready) /*spin*/; if (ptarg->ac_type == 0) printf("%x\n", *(char *)ptarg->addr); else { int (*func)(void) = (int (*)(void))ptarg->addr; printf("%x\n", func()); } } else { flag = 0; printf("%s: recovered\n", ptarg->name); } if (flag == -1) printf("%s: failed\n", ptarg->name); return NULL; } static unsigned int *get_cpu_mask(int cpu, char *type) { unsigned int bits, *mask; char path[100]; FILE *fp; int c, commas = 0, idx; idx = nmasks; mask = calloc(idx, sizeof *mask); if (!mask) return NULL; sprintf(path, "/sys/devices/system/cpu/cpu%d/topology/%s", cpu, type); fp = fopen(path, "r"); if (!fp) { perror(path); return NULL; } while ((c = fgetc(fp)) != EOF) if (c == ',') commas++; rewind(fp); while (commas > idx - 1) { c = fgetc(fp); if (c == ',') commas--; } while (fscanf(fp, "%x,", &bits) == 1) { mask[--idx] = bits; // printf("mask[%d] = 0x%x\n", idx, mask[idx]); } fclose(fp); if (idx) { fprintf(stderr, "failed to parse %s\n", path); free(mask); return NULL; } return mask; } void pick_same_core_cpu(int *cpu, int first_cpu) { unsigned int *mask; int i; mask = get_cpu_mask(first_cpu, "thread_siblings"); if (!mask) { exit(1); } for (i = 0; i < ncpus; i++) { if (mask[i / 32] & (1 << (i % 32))) { if (i != first_cpu) { cpu[0] = first_cpu; cpu[1] = i; break; } } } if (i == ncpus) { fprintf(stderr, "Failed to find same core CPUs\n"); free(mask); exit(1); } free(mask); } void pick_same_socket_cpu(int *cpu, int first_cpu) { unsigned int *cs_mask; unsigned int *ts_mask; int i; cs_mask = get_cpu_mask(first_cpu, "core_siblings"); if (!cs_mask) exit(1); ts_mask = get_cpu_mask(first_cpu, "thread_siblings"); if (!ts_mask) { free(cs_mask); exit(1); } for (i = 0; i < ncpus; i++) { if ((cs_mask[i / 32] ^ ts_mask[i / 32]) & (1 << (i % 32))) { cpu[0] = first_cpu; cpu[1] = i; break; } } if (i == ncpus) { fprintf(stderr, "Failed to find same socket CPUs\n"); free(cs_mask); free(ts_mask); exit(1); } free(cs_mask); free(ts_mask); } void pick_diff_socket_cpu(int *cpu, int first_cpu) { unsigned int *mask; int *buf; int i; int count = 0; int idx; buf = calloc(ncpus, sizeof *buf); if (!buf) { perror("calloc"); exit(1); } memset(buf, 0, ncpus * sizeof(*buf)); mask = get_cpu_mask(first_cpu, "core_siblings"); if (!mask) exit(1); for (i = 0; i < ncpus; i++) { if (~mask[i / 32] & (1 << (i % 32))) buf[count++] = i; } if (count == 0) { fprintf(stderr, "Failed to find different socket CPUs\n"); free(buf); free(mask); exit(1); } idx = random() % count; cpu[0] = first_cpu; cpu[1] = buf[idx]; free(buf); free(mask); } void pick_cpu(int *cpu, int core_choice) { int first_cpu; first_cpu = random() % ncpus; if (core_choice == 1) { pick_same_core_cpu(cpu, first_cpu); printf("Run on same core CPUs:"); } else if (core_choice == 2) { pick_same_socket_cpu(cpu, first_cpu); printf("Run on same socket CPUs:"); } else { pick_diff_socket_cpu(cpu, first_cpu); printf("Run on different socket CPUs:"); } printf(" cpu0 = %d, cpu1 = %d\n", cpu[0], cpu[1]); } int test_func(void) { volatile int ret = 0; int i; for (i = 0; i < 1000; i++) ret += i; return ret; } void sig_handler(int sig, siginfo_t *si, void *arg) { int i; int flag = 0; for(i = 0; i < NR_THREADS; i++) { if (vaddr[i % NR_ADDRS] && si->si_addr == vaddr[i % NR_ADDRS]) { flag = 1; break; } } if (flag == 0) { printf("The address(%p) in signal is not we wanted\n", si->si_addr); return; } printf("received signal %d, addr %p\n", sig, si->si_addr); for(i = 0; i < NR_THREADS; i++) { if (pthread_equal(pthread_self(), thread[i])) siglongjmp(recover[i], 1); } } void usage(char *str) { printf("Usage: %s [-a] [-c core_choice] [-t access_type] [-h]\n", str); printf("\t-a --- Threads access same error-injected address.\n"); printf("\t If no this option, access different error-injected addresses.\n"); printf("\t-c --- Pick which CPUs to let threads run on.\n"); printf("\t core_choice = 1, threads run on same CPU cores.\n"); printf("\t core_choice = 2, threads run on same socket CPUs but different cores.\n"); printf("\t core_choice = 3, threads run on different socket CPUs, this is default option.\n"); printf("\t-t --- Control which access type to trigger the fault, instruction fetch or data access.\n"); printf("\t there are three group choices: INSTR/INSTR, INSTR/DATA, DATA/DATA,\n"); printf("\t the default is INSTR/DATA.\n"); printf("\t-h --- print this message.\n"); exit(1); } const struct _access_type { int v[2]; const char *k; const char *s; } access_type[] = { {{1,1}, "INSTR/INSTR", "Instruction Fetch/Instruction Fetch"}, {{1,0}, "INSTR/DATA", "Instruction Fetch/Data Access"}, {{0,0}, "DATA/DATA", "Data Access/Data Access"} }; #ifndef ARRAY_SIZE #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) #endif int main(int argc, char *argv[]) { struct sigaction sa = { .sa_sigaction = sig_handler, .sa_flags = SA_SIGINFO }; struct thr_arg targ[NR_THREADS]; int testcpu[NR_CPUS]; uint64_t paddr[NR_ADDRS]; int c, i; int same_addr = 0; /*default: different socket CPUs*/ int core_choice = 3; /*default: INSTR/DATA*/ int idx = 1; pid_t pid; srandom(getpid() * time(0)); if (getuid() != (uid_t)0) { printf("Must be run as root\n"); return 0; } while ((c = getopt(argc, argv, "ac:ht:")) != -1) switch (c) { case 'a': same_addr = 1; break; case 'c': core_choice = atoi(optarg); if (core_choice < 1 || core_choice > 3) usage(argv[0]); break; case 't': for (i = 0; i < ARRAY_SIZE(access_type); i++) { if (strstr(optarg, access_type[i].k)) { idx = i; break; } } if (i == ARRAY_SIZE(access_type)) usage(argv[0]); break; case 'h': default: usage(argv[0]); break; } check_einj_available(); ncpus = sysconf(_SC_NPROCESSORS_CONF); nmasks = (ncpus + 31) / 32; if (ncpus <= 1) { fprintf(stderr, "Improper number of CPUs\n"); return 1; } pagesize = sysconf(_SC_PAGESIZE); pick_cpu(testcpu, core_choice); memset(targ, 0, sizeof(targ)); sigaction(SIGBUS, &sa, NULL); pid = getpid(); for (i = 0; i < NR_ADDRS; i++) { if ((vaddr[i] = mmap(0, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0)) == MAP_FAILED) { perror("mmap"); exit(1); } memcpy(vaddr[i], (void *)test_func, pagesize); if ((paddr[i] = vtop((uint64_t)vaddr[i], pid)) == 0) return 1; printf("Inject memory error at physical address 0x%lx(virt 0x%lx)\n", paddr[i], (uint64_t)vaddr[i]); do_inject(paddr[i]); sleep(1); if (same_addr) break; } printf("Access type: %s\n", access_type[idx].s); for (i = 0; i < NR_THREADS; i++) { targ[i].ac_type = access_type[idx].v[i % 2]; targ[i].cpu = testcpu[i % NR_CPUS]; sprintf(targ[i].name, "thread%d", i); targ[i].s_buf = &recover[i]; if (same_addr) targ[i].addr = vaddr[0]; else targ[i].addr = vaddr[i % NR_ADDRS]; if(pthread_create(&thread[i], NULL, thread_func, &targ[i])) { perror("pthread_create"); return 1; } } /* * Wait a second for children to initialize and * bind to correct CPUs. Then tell them to run. */ sleep(1); ready = 1; for (i = 0; i < NR_THREADS; i++) pthread_join(thread[i], NULL); for (i = 0; i < NR_ADDRS; i++) if (vaddr[i]) munmap(vaddr[i], pagesize); return 0; }