Set a default SP
[kvm-minippc.git] / main.c
1 /* Bare metal PPC KVM app, for libre-soc simulator comparison purposes
2 Copyright (C) 2021 Lauri Kasanen
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, version 3 of the License.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17 #define _GNU_SOURCE
18
19 #include <ctype.h>
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <getopt.h>
23 #include <linux/kvm.h>
24 #include <limits.h>
25 #include <stdio.h>
26 #include <stdint.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <sys/ioctl.h>
30 #include <sys/mman.h>
31 #include <sys/stat.h>
32 #include <sys/types.h>
33 #include <unistd.h>
34
35 #define PAGE_SIZE (64 * 1024) // assumption
36 #define MAXDUMPS 10
37 #define RAMSIZE (64 * 1024 * 1024)
38 #define PROGSTART 0x20000000
39
40 #define MSR_64 (1UL<<63)
41 #define MSR_FP (1UL<<13)
42 #define MSR_LE (1UL<<0)
43
44 enum {
45 SPR_LR,
46 };
47
48 static void nukenewline(char buf[]) {
49 unsigned i;
50 for (i = 0; buf[i]; i++) {
51 if (buf[i] == '\n') {
52 buf[i] = '\0';
53 break;
54 }
55 }
56 }
57
58 static void help(const char argv0[]) {
59
60 printf("Usage: %s [args] -i file.bin\n\n"
61
62 "-i --binary file Raw, bare metal executable\n"
63 "-g --intregs file Text file setting up GPRs\n"
64 "-f --fpregs file Text file setting up FPRs\n"
65 "-s --spregs file Text file setting up SPRs (unnecessary if only LR is needed)\n"
66 "-l --load file:addr Load this binary to RAM\n"
67 "-d --dump file:addr:len Save this RAM area after running\n"
68 "-t --trace file Save a full trace to this file\n"
69 "-h --help This help\n\n"
70
71 "Load and dump may be given multiple times. GPR/FPR are numbered,\n"
72 "SPRs are named.\n", argv0);
73
74 exit(0);
75 }
76
77 static void parseregs(const char name[], const uint8_t gpr, struct kvm_regs *regs,
78 struct kvm_fpu *fpregs) {
79
80 FILE *f = fopen(name, "r");
81 if (!f) {
82 printf("Can't open %s\n", name);
83 exit(1);
84 }
85
86 char buf[256];
87
88 while (fgets(buf, 256, f)) {
89 if (buf[0] == '#')
90 continue;
91 nukenewline(buf);
92
93 const uint8_t reg = strtol(buf, NULL, 0);
94 const char *ptr = strchr(buf + 1, ' ');
95 if (!ptr) {
96 printf("Invalid line '%s'\n", buf);
97 continue;
98 }
99
100 const uint64_t val = strtol(ptr, NULL, 0);
101
102 // apply reg
103 if (gpr) {
104 regs->gpr[reg] = val;
105 } else {
106 fpregs->fpr[reg] = val;
107 }
108 }
109
110 fclose(f);
111 }
112
113 static void parsesprs(const char name[], struct kvm_regs *regs) {
114
115 FILE *f = fopen(name, "r");
116 if (!f) {
117 printf("Can't open %s\n", name);
118 exit(1);
119 }
120
121 char buf[256];
122
123 while (fgets(buf, 256, f)) {
124 if (buf[0] == '#')
125 continue;
126 nukenewline(buf);
127
128 uint8_t reg = 0xff;
129
130 #define check(a) if (!strncasecmp(buf, a, sizeof(a) - 1))
131
132 check("LR:") {
133 reg = SPR_LR;
134 }
135
136 #undef check
137
138 if (reg == 0xff) {
139 printf("Unknown (unimplemented?) SPR register '%s'\n",
140 buf);
141 continue;
142 }
143
144 const char *ptr = strchr(buf + 1, ' ');
145 if (!ptr) {
146 printf("Invalid line '%s'\n", buf);
147 continue;
148 }
149
150 const uint64_t val = strtol(ptr, NULL, 0);
151
152 // apply reg
153 switch (reg) {
154 case SPR_LR:
155 regs->lr = val;
156 break;
157 }
158 }
159
160 fclose(f);
161 }
162
163 static void load(const char arg[], uint8_t *ram) {
164
165 char name[PATH_MAX];
166 const char *ptr = strchr(arg, ':');
167 if (!ptr) {
168 printf("Invalid load\n");
169 exit(1);
170 }
171
172 const unsigned namelen = ptr - arg;
173
174 strncpy(name, arg, namelen);
175 name[namelen] = '\0';
176
177 const uint64_t addr = strtol(ptr + 1, NULL, 0);
178
179 FILE *f = fopen(name, "r");
180 if (!f) {
181 printf("Can't open %s\n", name);
182 exit(1);
183 }
184
185 fseek(f, 0, SEEK_END);
186 const unsigned len = ftell(f);
187 rewind(f);
188
189 if (addr + len >= RAMSIZE) {
190 printf("Tried to use too much RAM\n");
191 exit(1);
192 }
193
194 printf("Loading %s to 0x%lx, %u bytes\n", name, addr, len);
195
196 if (fread(&ram[addr], len, 1, f) != 1)
197 abort();
198
199 fclose(f);
200 }
201
202 static void dump(const char src[], const uint8_t ram[]) {
203 const char *ptr = strchr(src, ':');
204 if (!ptr) {
205 printf("Invalid dump\n");
206 exit(1);
207 }
208
209 char name[PATH_MAX];
210 memcpy(name, src, ptr - src);
211 name[ptr - src] = '\0';
212
213 ptr++;
214 const uint32_t addr = strtol(ptr, NULL, 0);
215
216 ptr = strchr(ptr, ':');
217 if (!ptr) {
218 printf("Invalid dump\n");
219 exit(1);
220 }
221
222 ptr++;
223 const uint32_t len = strtol(ptr, NULL, 0);
224
225 //printf("Saving to %s, from %x, len %u\n", name, addr, len);
226
227 FILE *f = fopen(name, "w");
228 if (!f) {
229 printf("Can't open %s\n", name);
230 exit(1);
231 }
232
233 if (fwrite(&ram[addr], len, 1, f) != 1)
234 abort();
235
236 fclose(f);
237 }
238
239 static void setfpregs(const int vcpu, struct kvm_fpu *fpregs) {
240 // KVM_[SG]ET_FPU isn't supported on PPC, we have to move individual regs...
241 unsigned i;
242 for (i = 0; i < 32; i++) {
243 struct kvm_one_reg r = {
244 .id = KVM_REG_PPC_FPR(i),
245 .addr = (uint64_t) &fpregs->fpr[i]
246 };
247
248 if (ioctl(vcpu, KVM_SET_ONE_REG, &r) != 0)
249 abort();
250 }
251 }
252
253 static void getfpregs(const int vcpu, struct kvm_fpu *fpregs) {
254 // KVM_[SG]ET_FPU isn't supported on PPC, we have to move individual regs...
255 unsigned i;
256 for (i = 0; i < 32; i++) {
257 struct kvm_one_reg r = {
258 .id = KVM_REG_PPC_FPR(i),
259 .addr = (uint64_t) &fpregs->fpr[i]
260 };
261
262 if (ioctl(vcpu, KVM_GET_ONE_REG, &r) != 0)
263 abort();
264 }
265 }
266
267 static void disassemble(const char binpath[], char *disasm, const unsigned len) {
268
269 char buf[PATH_MAX];
270 snprintf(buf, PATH_MAX,
271 "objdump -b binary -m powerpc --no-show-raw-insn -D %s",
272 binpath);
273 buf[PATH_MAX - 1] = '\0';
274
275 FILE *f = popen(buf, "r");
276 if (!f) {
277 printf("Disassembly failed, trace won't have disas\n");
278 return;
279 }
280
281 while (fgets(buf, PATH_MAX, f)) {
282 if (buf[0] != ' ')
283 continue;
284 const char *ptr = strchr(buf, ':');
285 if (!ptr)
286 continue;
287 nukenewline(buf);
288
289 const unsigned addr = strtol(buf, NULL, 16) / 4 * 32;
290 if (addr / 32 + 1 >= len)
291 abort();
292
293 ptr++;
294 while (isspace(*ptr))
295 ptr++;
296
297 strncpy(&disasm[addr], ptr, 32);
298 disasm[addr + 31] = '\0';
299 }
300
301 pclose(f);
302 }
303
304 int main(int argc, char **argv) {
305
306 const struct option longopts[] = {
307 { "binary", 1, NULL, 'i' },
308 { "intregs", 1, NULL, 'g' },
309 { "fpregs", 1, NULL, 'f' },
310 { "spregs", 1, NULL, 's' },
311 { "load", 1, NULL, 'l' },
312 { "dump", 1, NULL, 'd' },
313 { "trace", 1, NULL, 't' },
314 { "help", 0, NULL, 'h' },
315 { NULL, 0, NULL, 0 }
316 };
317 const char opts[] = "i:g:f:s:l:d:t:h";
318
319 struct kvm_run *run;
320 struct kvm_regs regs;
321 struct kvm_sregs sregs;
322 struct kvm_fpu fpregs;
323 int kvm, vmfd, vcpu;
324 FILE *binary = NULL, *trace = NULL;
325 char dumps[MAXDUMPS][PATH_MAX];
326 unsigned num_dumps = 0;
327 uint8_t *ram, *progmem;
328 const char *binpath = NULL;
329 const char *disasm = NULL;
330
331 // Yes, we're frugal
332 if (posix_memalign((void **) &ram, 64 * 1024, RAMSIZE))
333 abort();
334 memset(ram, 0, RAMSIZE);
335
336 memset(&regs, 0, sizeof(struct kvm_regs));
337 memset(&fpregs, 0, sizeof(struct kvm_fpu));
338
339 regs.lr = -1;
340 regs.pc = PROGSTART;
341 regs.msr = MSR_64 | MSR_FP | MSR_LE;
342 regs.gpr[1] = 0x8000; // Default stack pointer at 32kb, 20kb free space before vecs
343
344 while (1) {
345 const int c = getopt_long(argc, argv, opts, longopts, NULL);
346 if (c == -1)
347 break;
348
349 switch (c) {
350 case 'i':
351 if (binary) {
352 printf("Only one binary allowed\n");
353 return 1;
354 }
355
356 binary = fopen(optarg, "r");
357 if (!binary) {
358 printf("Failed to open %s\n", optarg);
359 return 1;
360 }
361
362 binpath = strdup(optarg);
363 break;
364 case 'g':
365 parseregs(optarg, 1, &regs, &fpregs);
366 break;
367 case 'f':
368 parseregs(optarg, 0, &regs, &fpregs);
369 break;
370 case 's':
371 parsesprs(optarg, &regs);
372 break;
373 case 'l':
374 load(optarg, ram);
375 break;
376 case 'd':
377 if (num_dumps >= MAXDUMPS) {
378 printf("Too many dumps\n");
379 return 1;
380 }
381
382 strncpy(dumps[num_dumps], optarg, PATH_MAX);
383 dumps[num_dumps][PATH_MAX - 1] = '\0';
384 num_dumps++;
385 break;
386 case 't':
387 if (trace) {
388 printf("Only one trace allowed\n");
389 return 1;
390 }
391
392 trace = fopen(optarg, "w");
393 if (!trace) {
394 printf("Failed to open %s\n", optarg);
395 return 1;
396 }
397 break;
398 case 'h':
399 default:
400 help(argv[0]);
401 break;
402 }
403 }
404
405 if (!binary) {
406 help(argv[0]);
407 }
408
409 fseek(binary, 0, SEEK_END);
410 const unsigned binlen = ftell(binary);
411 rewind(binary);
412
413 printf("Loading binary %u bytes\n", binlen);
414
415 const unsigned progmemlen = (binlen + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
416
417 if (posix_memalign((void **) &progmem, 64 * 1024, progmemlen))
418 abort();
419
420 if (fread(progmem, binlen, 1, binary) != 1)
421 abort();
422 fclose(binary);
423
424
425 if (trace) {
426 const unsigned disaslen = binlen / 4 + 64;
427 disasm = calloc(disaslen, 32);
428
429 disassemble(binpath, (char *) disasm, disaslen);
430 }
431
432 kvm = open("/dev/kvm", O_RDWR | O_CLOEXEC);
433 if (kvm < 0) {
434 printf("Failed to open kvm, perhaps you lack permissions?\n");
435 return 1;
436 }
437
438 int ret;
439 ret = ioctl(kvm, KVM_GET_API_VERSION, NULL);
440 if (ret == -1 || ret != 12) abort();
441
442 ret = ioctl(kvm, KVM_CHECK_EXTENSION, KVM_CAP_PPC_GUEST_DEBUG_SSTEP);
443 if (ret == -1 || !ret) {
444 printf("This system lacks single-stepping!\n");
445 return 1;
446 }
447
448 vmfd = ioctl(kvm, KVM_CREATE_VM, (unsigned long)0);
449
450 struct kvm_userspace_memory_region region = {
451 .slot = 0,
452 .guest_phys_addr = 0,
453 .memory_size = RAMSIZE,
454 .userspace_addr = (uint64_t) ram,
455 .flags = 0
456 };
457 if (ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &region) == -1)
458 abort();
459
460 region.slot = 1;
461 region.guest_phys_addr = PROGSTART;
462 region.memory_size = progmemlen;
463 region.userspace_addr = (uint64_t) progmem;
464 if (ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &region) == -1)
465 abort();
466
467 vcpu = ioctl(vmfd, KVM_CREATE_VCPU, (unsigned long)0);
468 const unsigned vcpulen = ioctl(kvm, KVM_GET_VCPU_MMAP_SIZE, NULL);
469
470 run = mmap(NULL, vcpulen, PROT_READ | PROT_WRITE, MAP_SHARED, vcpu, 0);
471
472 if (ioctl(vcpu, KVM_GET_SREGS, &sregs) == -1)
473 abort();
474 // It defaults to host cpu on POWER5+, which is what we want.
475 // http://pearpc.sourceforge.net/pvr.html
476 if (ioctl(vcpu, KVM_SET_SREGS, &sregs) == -1)
477 abort();
478
479 if (ioctl(vcpu, KVM_SET_REGS, &regs) == -1)
480 abort();
481 setfpregs(vcpu, &fpregs);
482
483 const struct kvm_guest_debug dbg = {
484 .control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_SINGLESTEP
485 };
486 if (ioctl(vcpu, KVM_SET_GUEST_DEBUG, &dbg) == -1)
487 abort();
488
489 // Runtime
490 uint64_t pc = PROGSTART;
491 unsigned i;
492 while (1) {
493 if (ioctl(vcpu, KVM_RUN, NULL) == -1)
494 abort();
495
496 if (ioctl(vcpu, KVM_GET_REGS, &regs) == -1)
497 abort();
498 //printf("PC %lx LR %lx\n", regs.pc, regs.lr);
499
500 if (run->exit_reason == KVM_EXIT_DEBUG) {
501 if (trace) {
502 getfpregs(vcpu, &fpregs);
503
504 fprintf(trace, "%lx: %s\n", pc, &disasm[(pc - PROGSTART) / 4 * 32]);
505 for (i = 0; i < 32; i++) {
506 if (i % 8 == 0)
507 fprintf(trace, "GPR: ");
508 fprintf(trace, "%08lx", regs.gpr[i]);
509 if (i % 8 == 7)
510 fprintf(trace, "\n");
511 else
512 fprintf(trace, " ");
513 }
514 for (i = 0; i < 32; i++) {
515 if (i % 8 == 0)
516 fprintf(trace, "FPR: ");
517 fprintf(trace, "%08lx", fpregs.fpr[i]);
518 if (i % 8 == 7)
519 fprintf(trace, "\n");
520 else
521 fprintf(trace, " ");
522 }
523 }
524 } else if (regs.pc < PROGSTART || regs.pc > PROGSTART + binlen) {
525 // Normal exit by blr
526 break;
527 } else {
528 printf("Unexpected exit because %u\n", run->exit_reason);
529 break;
530 }
531
532 pc = regs.pc;
533 }
534
535 for (i = 0; i < num_dumps; i++) {
536 dump(dumps[i], ram);
537 }
538
539 close(kvm);
540 free(progmem);
541 free(ram);
542 free((char *) binpath);
543 free((char *) disasm);
544 return 0;
545 }