+/* Bare metal PPC KVM app, for libre-soc simulator comparison purposes
+ Copyright (C) 2021 Lauri Kasanen
+
+ This program 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 3 of the License.
+
+ This program 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 have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
#define _GNU_SOURCE
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
#include <getopt.h>
+#include <linux/kvm.h>
+#include <limits.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <unistd.h>
+#define PAGE_SIZE (64 * 1024) // assumption
+#define MAXDUMPS 10
+#define RAMSIZE (64 * 1024 * 1024)
+#define PROGSTART 0x20000000
+
+#define MSR_64 (1UL<<63)
+#define MSR_FP (1UL<<13)
+#define MSR_LE (1UL<<0)
+
+enum {
+ SPR_LR,
+};
+
+static void nukenewline(char buf[]) {
+ unsigned i;
+ for (i = 0; buf[i]; i++) {
+ if (buf[i] == '\n') {
+ buf[i] = '\0';
+ break;
+ }
+ }
+}
+
static void help(const char argv0[]) {
printf("Usage: %s [args] -i file.bin\n\n"
exit(0);
}
+static void parseregs(const char name[], const uint8_t gpr, struct kvm_regs *regs,
+ struct kvm_fpu *fpregs) {
+
+ FILE *f = fopen(name, "r");
+ if (!f) {
+ printf("Can't open %s\n", name);
+ exit(1);
+ }
+
+ char buf[256];
+
+ while (fgets(buf, 256, f)) {
+ if (buf[0] == '#')
+ continue;
+ nukenewline(buf);
+
+ const uint8_t reg = strtol(buf, NULL, 0);
+ const char *ptr = strchr(buf + 1, ' ');
+ if (!ptr) {
+ printf("Invalid line '%s'\n", buf);
+ continue;
+ }
+
+ const uint64_t val = strtol(ptr, NULL, 0);
+
+ // apply reg
+ if (gpr) {
+ regs->gpr[reg] = val;
+ } else {
+ fpregs->fpr[reg] = val;
+ }
+ }
+
+ fclose(f);
+}
+
+static void parsesprs(const char name[], struct kvm_regs *regs) {
+
+ FILE *f = fopen(name, "r");
+ if (!f) {
+ printf("Can't open %s\n", name);
+ exit(1);
+ }
+
+ char buf[256];
+
+ while (fgets(buf, 256, f)) {
+ if (buf[0] == '#')
+ continue;
+ nukenewline(buf);
+
+ uint8_t reg = 0xff;
+
+ #define check(a) if (!strncasecmp(buf, a, sizeof(a) - 1))
+
+ check("LR:") {
+ reg = SPR_LR;
+ }
+
+ #undef check
+
+ if (reg == 0xff) {
+ printf("Unknown (unimplemented?) SPR register '%s'\n",
+ buf);
+ continue;
+ }
+
+ const char *ptr = strchr(buf + 1, ' ');
+ if (!ptr) {
+ printf("Invalid line '%s'\n", buf);
+ continue;
+ }
+
+ const uint64_t val = strtol(ptr, NULL, 0);
+
+ // apply reg
+ switch (reg) {
+ case SPR_LR:
+ regs->lr = val;
+ break;
+ }
+ }
+
+ fclose(f);
+}
+
+static void load(const char arg[], uint8_t *ram) {
+
+ char name[PATH_MAX];
+ const char *ptr = strchr(arg, ':');
+ if (!ptr) {
+ printf("Invalid load\n");
+ exit(1);
+ }
+
+ const unsigned namelen = ptr - arg;
+
+ strncpy(name, arg, namelen);
+ name[namelen] = '\0';
+
+ const uint64_t addr = strtol(ptr + 1, NULL, 0);
+
+ FILE *f = fopen(name, "r");
+ if (!f) {
+ printf("Can't open %s\n", name);
+ exit(1);
+ }
+
+ fseek(f, 0, SEEK_END);
+ const unsigned len = ftell(f);
+ rewind(f);
+
+ if (addr + len >= RAMSIZE) {
+ printf("Tried to use too much RAM\n");
+ exit(1);
+ }
+
+ printf("Loading %s to 0x%lx, %u bytes\n", name, addr, len);
+
+ if (fread(&ram[addr], len, 1, f) != 1)
+ abort();
+
+ fclose(f);
+}
+
+static void dump(const char src[], const uint8_t ram[]) {
+ const char *ptr = strchr(src, ':');
+ if (!ptr) {
+ printf("Invalid dump\n");
+ exit(1);
+ }
+
+ char name[PATH_MAX];
+ memcpy(name, src, ptr - src);
+ name[ptr - src] = '\0';
+
+ ptr++;
+ const uint32_t addr = strtol(ptr, NULL, 0);
+
+ ptr = strchr(ptr, ':');
+ if (!ptr) {
+ printf("Invalid dump\n");
+ exit(1);
+ }
+
+ ptr++;
+ const uint32_t len = strtol(ptr, NULL, 0);
+
+ //printf("Saving to %s, from %x, len %u\n", name, addr, len);
+
+ FILE *f = fopen(name, "w");
+ if (!f) {
+ printf("Can't open %s\n", name);
+ exit(1);
+ }
+
+ if (fwrite(&ram[addr], len, 1, f) != 1)
+ abort();
+
+ fclose(f);
+}
+
+static void setfpregs(const int vcpu, struct kvm_fpu *fpregs) {
+ // KVM_[SG]ET_FPU isn't supported on PPC, we have to move individual regs...
+ unsigned i;
+ for (i = 0; i < 32; i++) {
+ struct kvm_one_reg r = {
+ .id = KVM_REG_PPC_FPR(i),
+ .addr = (uint64_t) &fpregs->fpr[i]
+ };
+
+ if (ioctl(vcpu, KVM_SET_ONE_REG, &r) != 0)
+ abort();
+ }
+}
+
+static void getfpregs(const int vcpu, struct kvm_fpu *fpregs) {
+ // KVM_[SG]ET_FPU isn't supported on PPC, we have to move individual regs...
+ unsigned i;
+ for (i = 0; i < 32; i++) {
+ struct kvm_one_reg r = {
+ .id = KVM_REG_PPC_FPR(i),
+ .addr = (uint64_t) &fpregs->fpr[i]
+ };
+
+ if (ioctl(vcpu, KVM_GET_ONE_REG, &r) != 0)
+ abort();
+ }
+}
+
+static void disassemble(const char binpath[], char *disasm, const unsigned len) {
+
+ char buf[PATH_MAX];
+ snprintf(buf, PATH_MAX,
+ "objdump -b binary -m powerpc --no-show-raw-insn -D %s",
+ binpath);
+ buf[PATH_MAX - 1] = '\0';
+
+ FILE *f = popen(buf, "r");
+ if (!f) {
+ printf("Disassembly failed, trace won't have disas\n");
+ return;
+ }
+
+ while (fgets(buf, PATH_MAX, f)) {
+ if (buf[0] != ' ')
+ continue;
+ const char *ptr = strchr(buf, ':');
+ if (!ptr)
+ continue;
+ nukenewline(buf);
+
+ const unsigned addr = strtol(buf, NULL, 16) / 4 * 32;
+ if (addr / 32 + 1 >= len)
+ abort();
+
+ ptr++;
+ while (isspace(*ptr))
+ ptr++;
+
+ strncpy(&disasm[addr], ptr, 32);
+ disasm[addr + 31] = '\0';
+ }
+
+ pclose(f);
+}
+
int main(int argc, char **argv) {
+ const struct option longopts[] = {
+ { "binary", 1, NULL, 'i' },
+ { "intregs", 1, NULL, 'g' },
+ { "fpregs", 1, NULL, 'f' },
+ { "spregs", 1, NULL, 's' },
+ { "load", 1, NULL, 'l' },
+ { "dump", 1, NULL, 'd' },
+ { "trace", 1, NULL, 't' },
+ { "help", 0, NULL, 'h' },
+ { NULL, 0, NULL, 0 }
+ };
+ const char opts[] = "i:g:f:s:l:d:t:hp:";
+
+ struct kvm_run *run;
+ struct kvm_regs regs;
+ struct kvm_sregs sregs;
+ struct kvm_fpu fpregs;
+ int kvm, vmfd, vcpu;
+ FILE *binary = NULL, *trace = NULL;
+ char dumps[MAXDUMPS][PATH_MAX];
+ unsigned num_dumps = 0;
+ uint8_t *ram, *progmem;
+ const char *binpath = NULL;
+ const char *disasm = NULL;
+
+ // Yes, we're frugal
+ if (posix_memalign((void **) &ram, 64 * 1024, RAMSIZE))
+ abort();
+ memset(ram, 0, RAMSIZE);
+
+ memset(®s, 0, sizeof(struct kvm_regs));
+ memset(&fpregs, 0, sizeof(struct kvm_fpu));
+
+ regs.lr = -1;
+ regs.pc = PROGSTART;
+ regs.msr = MSR_64 | MSR_FP | MSR_LE;
+ regs.gpr[1] = 0x8000; // Default stack pointer at 32kb, 20kb free space before vecs
+
+ while (1) {
+ const int c = getopt_long(argc, argv, opts, longopts, NULL);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'i':
+ if (binary) {
+ printf("Only one binary allowed\n");
+ return 1;
+ }
+
+ binary = fopen(optarg, "r");
+ if (!binary) {
+ printf("Failed to open %s\n", optarg);
+ return 1;
+ }
+
+ binpath = strdup(optarg);
+ break;
+ case 'g':
+ parseregs(optarg, 1, ®s, &fpregs);
+ break;
+ case 'f':
+ parseregs(optarg, 0, ®s, &fpregs);
+ break;
+ case 's':
+ parsesprs(optarg, ®s);
+ break;
+ case 'l':
+ load(optarg, ram);
+ break;
+ case 'd':
+ if (num_dumps >= MAXDUMPS) {
+ printf("Too many dumps\n");
+ return 1;
+ }
+
+ strncpy(dumps[num_dumps], optarg, PATH_MAX);
+ dumps[num_dumps][PATH_MAX - 1] = '\0';
+ num_dumps++;
+ break;
+ case 't':
+ if (trace) {
+ printf("Only one trace allowed\n");
+ return 1;
+ }
+
+ trace = fopen(optarg, "w");
+ if (!trace) {
+ printf("Failed to open %s\n", optarg);
+ return 1;
+ }
+ break;
+ case 'p': // ignored
+ break;
+ case 'h':
+ default:
+ help(argv[0]);
+ break;
+ }
+ }
+
+ if (!binary) {
+ help(argv[0]);
+ }
+
+ fseek(binary, 0, SEEK_END);
+ const unsigned binlen = ftell(binary);
+ rewind(binary);
+
+ printf("Loading binary %u bytes\n", binlen);
+
+ const unsigned progmemlen = (binlen + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
+
+ if (posix_memalign((void **) &progmem, 64 * 1024, progmemlen))
+ abort();
+
+ if (fread(progmem, binlen, 1, binary) != 1)
+ abort();
+ fclose(binary);
+
+
+ if (trace) {
+ const unsigned disaslen = binlen / 4 + 64;
+ disasm = calloc(disaslen, 32);
+
+ disassemble(binpath, (char *) disasm, disaslen);
+ }
+
+ kvm = open("/dev/kvm", O_RDWR | O_CLOEXEC);
+ if (kvm < 0) {
+ printf("Failed to open kvm, perhaps you lack permissions?\n");
+ return 1;
+ }
+
+ int ret;
+ ret = ioctl(kvm, KVM_GET_API_VERSION, NULL);
+ if (ret == -1 || ret != 12) abort();
+
+ ret = ioctl(kvm, KVM_CHECK_EXTENSION, KVM_CAP_PPC_GUEST_DEBUG_SSTEP);
+ if (ret == -1 || !ret) {
+ printf("This system lacks single-stepping!\n");
+ return 1;
+ }
+
+ vmfd = ioctl(kvm, KVM_CREATE_VM, (unsigned long)0);
+
+ struct kvm_userspace_memory_region region = {
+ .slot = 0,
+ .guest_phys_addr = 0,
+ .memory_size = RAMSIZE,
+ .userspace_addr = (uint64_t) ram,
+ .flags = 0
+ };
+ if (ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, ®ion) == -1)
+ abort();
+
+ region.slot = 1;
+ region.guest_phys_addr = PROGSTART;
+ region.memory_size = progmemlen;
+ region.userspace_addr = (uint64_t) progmem;
+ if (ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, ®ion) == -1)
+ abort();
+
+ vcpu = ioctl(vmfd, KVM_CREATE_VCPU, (unsigned long)0);
+ const unsigned vcpulen = ioctl(kvm, KVM_GET_VCPU_MMAP_SIZE, NULL);
+
+ run = mmap(NULL, vcpulen, PROT_READ | PROT_WRITE, MAP_SHARED, vcpu, 0);
+
+ if (ioctl(vcpu, KVM_GET_SREGS, &sregs) == -1)
+ abort();
+ // It defaults to host cpu on POWER5+, which is what we want.
+ // http://pearpc.sourceforge.net/pvr.html
+ if (ioctl(vcpu, KVM_SET_SREGS, &sregs) == -1)
+ abort();
+
+ if (ioctl(vcpu, KVM_SET_REGS, ®s) == -1)
+ abort();
+ setfpregs(vcpu, &fpregs);
+
+ const struct kvm_guest_debug dbg = {
+ .control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_SINGLESTEP
+ };
+ if (ioctl(vcpu, KVM_SET_GUEST_DEBUG, &dbg) == -1)
+ abort();
+
+ // Runtime
+ uint64_t pc = PROGSTART;
+ unsigned i;
+ while (1) {
+ if (ioctl(vcpu, KVM_RUN, NULL) == -1)
+ abort();
+
+ if (ioctl(vcpu, KVM_GET_REGS, ®s) == -1)
+ abort();
+ //printf("PC %lx LR %lx\n", regs.pc, regs.lr);
+
+ if (run->exit_reason == KVM_EXIT_DEBUG) {
+ if (trace) {
+ getfpregs(vcpu, &fpregs);
+
+ fprintf(trace, "%lx: %s\n", pc, &disasm[(pc - PROGSTART) / 4 * 32]);
+ for (i = 0; i < 32; i++) {
+ if (i % 8 == 0)
+ fprintf(trace, "GPR: ");
+ fprintf(trace, "%08lx", regs.gpr[i]);
+ if (i % 8 == 7)
+ fprintf(trace, "\n");
+ else
+ fprintf(trace, " ");
+ }
+ for (i = 0; i < 32; i++) {
+ if (i % 8 == 0)
+ fprintf(trace, "FPR: ");
+ fprintf(trace, "%08lx", fpregs.fpr[i]);
+ if (i % 8 == 7)
+ fprintf(trace, "\n");
+ else
+ fprintf(trace, " ");
+ }
+ }
+ } else if (regs.pc < PROGSTART || regs.pc > PROGSTART + binlen) {
+ // Normal exit by blr
+ break;
+ } else {
+ printf("Unexpected exit because %u\n", run->exit_reason);
+ break;
+ }
+
+ pc = regs.pc;
+ }
+
+ for (i = 0; i < num_dumps; i++) {
+ dump(dumps[i], ram);
+ }
+ close(kvm);
+ free(progmem);
+ free(ram);
+ free((char *) binpath);
+ free((char *) disasm);
return 0;
}