blob: bfcf0655513a75f3616559fb242442fa56eeaa3f [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Set up to get zapped by a machine check (injected elsewhere)
* To use this test case please ensure your SUT(System Under Test)
* can support MCE/SRAR.
*
* This file is released under the GPLv2.
*
* Copyright (C) 2012-2015 Intel corporation
*
* Author:
* Tony Luck <tony.luck@intel.com>
* Gong Chen <gong.chen@intel.com>
* Wen Jin <wenx.jin@intel.com>
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <getopt.h>
#include <errno.h>
/*
* Definition of /proc/pid/pagemap
* Bits 0-54 page frame number (PFN) if present
* Bits 0-4 swap type if swapped
* Bits 5-54 swap offset if swapped
* Bits 55-60 page shift, the bits definition is legacy.
* Bit 61 reserved for future use
* Bit 62 page swapped
* Bit 63 page present
*/
struct pagemaps {
unsigned long long pfn:55;
unsigned long long pgshift:6; /*legacy*/
unsigned long long rsvd:1;
unsigned long long swapped:1;
unsigned long long present:1;
};
static int pagesize;
/*
* dummyfunc size should be less than one page after complied,
* otherwise, caller will not return from this function
*/
void dummyfunc(void)
{
int fatarray[64];
fatarray[0] = 0xdeadbeaf;
fatarray[8] = 0xdeadbeaf;
fatarray[16] = 0xdeadbeaf;
fatarray[32] = 0xdeadbeaf;
}
/*
* get information about address from /proc/{pid}/pagemap
*/
unsigned long long vtop(unsigned long long addr, pid_t pid)
{
struct pagemaps pinfo;
unsigned int pinfo_size = sizeof(pinfo);
unsigned long long offset = addr / pagesize * pinfo_size;
int fd, pgmask;
char pagemapname[64];
sprintf(pagemapname, "/proc/%d/pagemap", pid);
fd = open(pagemapname, O_RDONLY);
if (fd == -1) {
perror(pagemapname);
return 0;
}
if (pread(fd, (void *)&pinfo, pinfo_size, offset) != pinfo_size) {
perror(pagemapname);
close(fd);
return 0;
}
close(fd);
pgmask = pagesize - 1;
return (pinfo.pfn * pagesize) | (addr & pgmask);
}
static void usage(void)
{
printf(
"victim [options]\n"
" -a|--address vaddr=val, pid=val Translate process virtual address into physical address\n"
" -d|--data Inject data error(DCU error) under user context\n"
" -i|--instruction Inject instruction error(IFU error) under user context\n"
" -k|--kick 0/1 Kick off trigger. Auto(0), Manual(1, by default)\n"
" -p|--pfa help to test memory PFA(Predictive Failure Analysis) function\n"
" -h|--help Show this usage message\n"
);
}
static const struct option opts[] = {
{ "address" , 1, NULL, 'a' },
{ "data" , 0, NULL, 'd' },
{ "instruction" , 0, NULL, 'i' },
{ "help" , 0, NULL, 'h' },
{ "kick" , 1, NULL, 'k' },
{ "pfa" , 0, NULL, 'p' },
{ NULL , 0, NULL, 0 }
};
static void pfa_helper(char *p, pid_t pid, unsigned long long old_phys)
{
int i;
int total;
unsigned long long new_phys;
for (;;) {
for (i = 0; i < pagesize; i += sizeof(int)) {
total += *(int*)(p + i);
*(int*)(p + i) = total;
}
new_phys = vtop((unsigned long long)p, pid);
if (old_phys == new_phys) {
for (i = 0; i < pagesize; i += sizeof(int)) {
total += *(int*)(p + i);
*(int*)(p + i) = i;
}
sleep(2);
new_phys = vtop((unsigned long long)p, pid);
if (old_phys != new_phys) {
printf("Page was replaced. New physical address = 0x%llx\n", new_phys);
fflush(stdout);
old_phys = new_phys;
}
} else {
printf("Page was replaced. New physical address = 0x%llx\n", new_phys);
fflush(stdout);
old_phys = new_phys;
}
}
}
static int parse_addr_subopts(char *subopts, unsigned long long *virt,
pid_t *pid)
{
int err = 0;
int index;
enum {
I_VADDR = 0,
I_PID
};
char *const token[] = {
[I_VADDR] = "vaddr",
[I_PID] = "pid",
NULL
};
char *p = subopts;
char *subval;
char *svaddr;
char *spid;
while (*p != '\0' && !err) {
index = getsubopt(&p, token, &subval);
switch (index) {
case I_VADDR:
if (subval != NULL) {
svaddr = subval;
break;
} else {
fprintf(stderr,
"miss value for %s\n",
token[I_VADDR]);
err++;
continue;
}
case I_PID:
if (subval != NULL) {
spid = subval;
break;
} else {
fprintf(stderr,
"miss value for %s\n",
token[I_PID]);
err++;
continue;
}
default:
err++;
break;
}
}
if (err > 0) {
usage();
return 1;
}
errno = 0;
*virt = strtoull(svaddr, NULL, 0);
if ((*virt == 0 && svaddr[0] != '0') || errno != 0) {
fprintf(stderr, "Invalid virtual address: %s\n",
svaddr);
return 1;
}
errno = 0;
*pid = strtoul(spid, NULL, 0);
if ((*pid == 0 && spid[0] != '0') || errno != 0) {
fprintf(stderr, "Invalid process pid number: %s\n",
spid);
return 1;
}
return 0;
}
/*
* The "SRAR DCU" test case failed on a CLX-AP server. It's root caused
* that the gcc v8.2.1 optimized out the access to the injected location.
*
* If keep "total" as a local, even mark it "volatile", the gcc v8.2.1
* still optimizes out the memory access. Therefore, move the "total" from
* being a local variable to a global to avoid such optimization.
*/
long total;
int main(int argc, char **argv)
{
unsigned long long virt, phys;
char *buf;
int c, i;
int iflag = 0, dflag = 0;
int kick = 1;
int pfa = 0;
pid_t pid;
const char *trigger = "./trigger_start";
const char *trigger_flag = "trigger";
int fd;
int count = 100;
char trigger_buf[16];
char answer[16];
time_t now;
if (argc <= 1) {
usage();
return 0;
}
pagesize = getpagesize();
while ((c = getopt_long(argc, argv, "a:dihk:p", opts, NULL)) != -1) {
switch (c) {
case 'a':
if (parse_addr_subopts(optarg, &virt, &pid) == 0) {
phys = vtop(virt, pid);
printf("physical address of (%d,0x%llx) = 0x%llx\n",
pid, virt, phys);
return 0;
}
return 1;
case 'd':
dflag = 1;
break;
case 'i':
iflag = 1;
break;
case 'k':
errno = 0;
kick = strtol(optarg, NULL, 0);
if ((kick == 0 && optarg[0] != '0') || errno != 0) {
fprintf(stderr, "Invalid parameter: %s\n", optarg);
return 1;
}
if (kick != 0 && kick != 1) {
fprintf(stderr, "Invalid parameter: %s\n", optarg);
return 1;
}
break;
case 'p':
pfa = 1;
break;
case 'h':
default:
usage();
return 0;
}
}
/* The MAP_LOCKED flag should not be used here, because it will cause a failure
* in KVM mce-inject test. But to prevent the mapped buffer from being swapped out,
* the system under test should not run in a heavy load environment.
*/
buf = mmap(NULL, pagesize, PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
if (buf == MAP_FAILED) {
fprintf(stderr, "Can't get a single page of memory!\n");
return 1;
}
memset(buf, '*', pagesize);
pid = getpid();
phys = vtop((unsigned long long)buf, pid);
if (phys == 0) {
fprintf(stderr, "Can't get physical address of the page!\n");
return 1;
}
if (iflag)
memcpy(buf, (void *)dummyfunc, pagesize);
printf("physical address of (0x%llx) = 0x%llx\n",
(unsigned long long)buf, phys);
fflush(stdout);
if (pfa == 1)
pfa_helper(buf, pid, phys);
if (kick == 0) {
errno = 0;
if (unlink(trigger) < 0 && errno != ENOENT) {
fprintf(stderr, "fail to remove trigger file\n");
return 1;
}
memset(trigger_buf, 0, sizeof(trigger_buf));
while (--count) {
if ((fd = open(trigger, O_RDONLY)) < 0) {
sleep(1);
continue;
}
if (read(fd, trigger_buf, sizeof(trigger_buf)) > 0 &&
strstr(trigger_buf, trigger_flag) != NULL) {
break;
}
sleep(1);
}
if (count == 0) {
fprintf(stderr,
"Timeout to get trigger flag file\n");
return 1;
}
} else {
printf("Hit any key to trigger error: ");
fflush(stdout);
read(0, answer, 16);
now = time(NULL);
printf("Access time at %s\n", ctime(&now));
}
if (iflag) {
void (*f)(void) = (void (*)(void))buf;
while (1) f();
}
if (dflag) {
while (1) {
for (i = 0; i < pagesize; i += sizeof(int))
total += *(int *)(buf + i);
}
}
return 0;
}