|  | // SPDX-License-Identifier: GPL-2.0 | 
|  |  | 
|  | /* | 
|  | * Copyright (C) 2015 Intel Corporation | 
|  | * Author: Tony Luck | 
|  | */ | 
|  |  | 
|  | #include "einj.h" | 
|  |  | 
|  | struct error_type error_types[] = { | 
|  | { 0x00000001, "Processor Correctable",}, | 
|  | { 0x00000002, "Processor Uncorrectable non-fatal",}, | 
|  | { 0x00000004, "Processor Uncorrectable fatal",}, | 
|  | { 0x00000008, "Memory Correctable",}, | 
|  | { 0x00000010, "Memory Uncorrectable non-fatal",}, | 
|  | { 0x00000020, "Memory Uncorrectable fatal",}, | 
|  | { 0x00000040, "PCI Express Correctable",}, | 
|  | { 0x00000080, "PCI Express Uncorrectable non-fatal",}, | 
|  | { 0x00000100, "PCI Express Uncorrectable fatal",}, | 
|  | { 0x00000200, "Platform Correctabl",}, | 
|  | { 0x00000400, "Platform Uncorrectable non-fatal",}, | 
|  | { 0x00000800, "Platform Uncorrectable fatal",}, | 
|  | { -1, NULL } | 
|  | }; | 
|  |  | 
|  | int check_errortype_available(char *file, unsigned long long val) | 
|  | { | 
|  | FILE *fp; | 
|  | int ret = -1; | 
|  | unsigned long long available_error_type; | 
|  |  | 
|  | if (strcmp(file, EINJ_ETYPE) != 0) | 
|  | return 0; | 
|  |  | 
|  | fp = fopen(EINJ_ETYPE_AVAILABLE, "r"); | 
|  | if (!fp) { | 
|  | fprintf(stderr, "%s: cannot open '%s'\n", progname, file); | 
|  | exit(1); | 
|  | } | 
|  |  | 
|  | while (fscanf(fp, "%llx%*[^\n]", &available_error_type) == 1) { | 
|  | if (val == available_error_type) { | 
|  | ret = 0; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | fclose(fp); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | void wfile(char *file, unsigned long long val) | 
|  | { | 
|  | FILE *fp; | 
|  |  | 
|  | if (check_errortype_available(file, val) != 0) { | 
|  | fprintf(stderr, "%s: no support for error type: 0x%llx\n", progname, val); | 
|  | exit(1); | 
|  | } | 
|  |  | 
|  | #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0) | 
|  | if (!strcmp(file, EINJ_FLAGS)) | 
|  | return; | 
|  | #endif | 
|  |  | 
|  | fp = fopen(file, "w"); | 
|  | if (fp == NULL) { | 
|  | fprintf(stderr, "%s: cannot open '%s'\n", progname, file); | 
|  | exit(1); | 
|  | } | 
|  | fprintf(fp, "0x%llx\n", val); | 
|  | if (fclose(fp) == EOF) { | 
|  | fprintf(stderr, "%s: write error on '%s'\n", progname, file); | 
|  | exit(1); | 
|  | } | 
|  | } | 
|  |  | 
|  | int is_einj_support(void) | 
|  | { | 
|  | if (access("/sys/firmware/acpi/tables/EINJ", R_OK) == -1) { | 
|  | fprintf(stderr, "%s: Error injection not supported, check your BIOS settings\n", progname); | 
|  | return 0; | 
|  | } | 
|  | if (access(EINJ_NOTRIGGER, R_OK|W_OK) == -1) { | 
|  | fprintf(stderr, "%s: Is the einj.ko module loaded?\n", progname); | 
|  | return 0; | 
|  | } | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | int is_privileged(void) | 
|  | { | 
|  | if (getuid() != 0) { | 
|  | fprintf(stderr, "%s: must be root to run error injection tests\n", progname); | 
|  | return 0; | 
|  | } | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | #if defined(__aarch64__) | 
|  | #define DEFAULT_CACHE_LINE_SIZE 64 | 
|  | static size_t get_cache_line_size(void) | 
|  | { | 
|  | static size_t cachelinesize; | 
|  | FILE *file = NULL; | 
|  |  | 
|  | if (cachelinesize) | 
|  | return cachelinesize; | 
|  |  | 
|  | file = fopen("/sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size", "r"); | 
|  | if (file && fscanf(file, "%zu", &cachelinesize) == 1) | 
|  | fclose(file); | 
|  | else | 
|  | cachelinesize = DEFAULT_CACHE_LINE_SIZE; | 
|  |  | 
|  | return cachelinesize; | 
|  | } | 
|  |  | 
|  | static void clear_cache(void *ptr, size_t size) | 
|  | { | 
|  | uintptr_t start = (uintptr_t)ptr; | 
|  | uintptr_t end = start + size; | 
|  | size_t cachelinesize; | 
|  |  | 
|  | if (!ptr || size == 0) | 
|  | return; | 
|  |  | 
|  | cachelinesize = get_cache_line_size(); | 
|  | start &= ~(cachelinesize - 1); | 
|  | while (start < end) { | 
|  | asm volatile("dc civac, %0" : : "r"(start) : "memory"); | 
|  | start += cachelinesize; | 
|  | } | 
|  | asm volatile("dsb sy" : : : "memory"); | 
|  | sleep(1); | 
|  | } | 
|  | #else | 
|  | static inline void clear_cache(void *ptr, size_t size) { } | 
|  | #endif | 
|  |  | 
|  | void inject_mem_uc(unsigned long long addr, void *vaddr, int notrigger) | 
|  | { | 
|  | PRINT_INJECTING; | 
|  |  | 
|  | if (Sflag) { | 
|  | vaddr = (void *)((long)vaddr & ~(pagesize - 1)); | 
|  | madvise(vaddr, pagesize, MADV_HWPOISON); | 
|  | return; | 
|  | } | 
|  |  | 
|  | wfile(EINJ_ETYPE, 0x10); | 
|  | wfile(EINJ_ADDR, addr); | 
|  | wfile(EINJ_MASK, ~0x0ul); | 
|  | wfile(EINJ_FLAGS, 2); | 
|  | wfile(EINJ_NOTRIGGER, notrigger); | 
|  | wfile(EINJ_DOIT, 1); | 
|  |  | 
|  | clear_cache(vaddr, pagesize); | 
|  | } | 
|  |  | 
|  | char *lookup_type_name(unsigned long long type) | 
|  | { | 
|  | struct error_type *t; | 
|  |  | 
|  | for (t = error_types; t->type_name; t++) | 
|  | if (t->type == type) | 
|  | return t->type_name; | 
|  | fprintf(stderr, "%s: unknown type '%x'\n", progname, type); | 
|  | exit(1); | 
|  | } |