Ignore -p
[kvm-minippc.git] / main.c
diff --git a/main.c b/main.c
index 5fe7e279bd1325b203d2977d366ba483bc02e2b3..86181fb7a7d738fe240977ffd0b5f7ce0da316d0 100644 (file)
--- a/main.c
+++ b/main.c
@@ -1,11 +1,60 @@
+/*  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"
@@ -25,8 +74,474 @@ static void help(const char argv0[]) {
        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(&regs, 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, &regs, &fpregs);
+                       break;
+                       case 'f':
+                               parseregs(optarg, 0, &regs, &fpregs);
+                       break;
+                       case 's':
+                               parsesprs(optarg, &regs);
+                       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, &region) == -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, &region) == -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, &regs) == -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, &regs) == -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;
 }