Add dumping
[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 <errno.h>
20 #include <fcntl.h>
21 #include <getopt.h>
22 #include <linux/kvm.h>
23 #include <limits.h>
24 #include <stdio.h>
25 #include <stdint.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/ioctl.h>
29 #include <sys/mman.h>
30 #include <sys/stat.h>
31 #include <sys/types.h>
32 #include <unistd.h>
33
34 #define PAGE_SIZE (64 * 1024) // assumption
35 #define MAXDUMPS 10
36 #define RAMSIZE (64 * 1024 * 1024)
37 #define PROGSTART 0x20000000
38
39 #define MSR_64 (1UL<<63)
40 #define MSR_LE (1UL<<0)
41
42 enum {
43 SPR_LR,
44 };
45
46 static void nukenewline(char buf[]) {
47 unsigned i;
48 for (i = 0; buf[i]; i++) {
49 if (buf[i] == '\n') {
50 buf[i] = '\0';
51 break;
52 }
53 }
54 }
55
56 static void help(const char argv0[]) {
57
58 printf("Usage: %s [args] -i file.bin\n\n"
59
60 "-i --binary file Raw, bare metal executable\n"
61 "-g --intregs file Text file setting up GPRs\n"
62 "-f --fpregs file Text file setting up FPRs\n"
63 "-s --spregs file Text file setting up SPRs (unnecessary if only LR is needed)\n"
64 "-l --load file:addr Load this binary to RAM\n"
65 "-d --dump file:addr:len Save this RAM area after running\n"
66 "-t --trace file Save a full trace to this file\n"
67 "-h --help This help\n\n"
68
69 "Load and dump may be given multiple times. GPR/FPR are numbered,\n"
70 "SPRs are named.\n", argv0);
71
72 exit(0);
73 }
74
75 static void parseregs(const char name[], const uint8_t gpr, struct kvm_regs *regs,
76 struct kvm_fpu *fpregs) {
77
78 FILE *f = fopen(name, "r");
79 if (!f) {
80 printf("Can't open %s\n", name);
81 exit(1);
82 }
83
84 char buf[256];
85
86 while (fgets(buf, 256, f)) {
87 if (buf[0] == '#')
88 continue;
89 nukenewline(buf);
90
91 const uint8_t reg = strtol(buf, NULL, 0);
92 const char *ptr = strchr(buf + 1, ' ');
93 if (!ptr) {
94 printf("Invalid line '%s'\n", buf);
95 continue;
96 }
97
98 const uint64_t val = strtol(ptr, NULL, 0);
99
100 // apply reg
101 if (gpr) {
102 regs->gpr[reg] = val;
103 } else {
104 fpregs->fpr[reg] = val;
105 }
106 }
107
108 fclose(f);
109 }
110
111 static void parsesprs(const char name[], struct kvm_regs *regs) {
112
113 FILE *f = fopen(name, "r");
114 if (!f) {
115 printf("Can't open %s\n", name);
116 exit(1);
117 }
118
119 char buf[256];
120
121 while (fgets(buf, 256, f)) {
122 if (buf[0] == '#')
123 continue;
124 nukenewline(buf);
125
126 uint8_t reg = 0xff;
127
128 #define check(a) if (!strncasecmp(buf, a, sizeof(a) - 1))
129
130 check("LR:") {
131 reg = SPR_LR;
132 }
133
134 #undef check
135
136 if (reg == 0xff) {
137 printf("Unknown (unimplemented?) SPR register '%s'\n",
138 buf);
139 continue;
140 }
141
142 const char *ptr = strchr(buf + 1, ' ');
143 if (!ptr) {
144 printf("Invalid line '%s'\n", buf);
145 continue;
146 }
147
148 const uint64_t val = strtol(ptr, NULL, 0);
149
150 // apply reg
151 switch (reg) {
152 case SPR_LR:
153 regs->lr = val;
154 break;
155 }
156 }
157
158 fclose(f);
159 }
160
161 static void load(const char arg[], uint8_t *ram) {
162
163 char name[PATH_MAX];
164 const char *ptr = strchr(arg, ':');
165 if (!ptr) {
166 printf("Invalid load\n");
167 exit(1);
168 }
169
170 const unsigned namelen = ptr - arg;
171
172 strncpy(name, arg, namelen);
173 name[namelen] = '\0';
174
175 const uint64_t addr = strtol(ptr + 1, NULL, 0);
176
177 FILE *f = fopen(name, "r");
178 if (!f) {
179 printf("Can't open %s\n", name);
180 exit(1);
181 }
182
183 fseek(f, 0, SEEK_END);
184 const unsigned len = ftell(f);
185 rewind(f);
186
187 if (addr + len >= RAMSIZE) {
188 printf("Tried to use too much RAM\n");
189 exit(1);
190 }
191
192 printf("Loading %s to 0x%lx, %u bytes\n", name, addr, len);
193
194 if (fread(&ram[addr], len, 1, f) != 1)
195 abort();
196
197 fclose(f);
198 }
199
200 static void dump(const char src[], const uint8_t ram[]) {
201 const char *ptr = strchr(src, ':');
202 if (!ptr) {
203 printf("Invalid dump\n");
204 exit(1);
205 }
206
207 char name[PATH_MAX];
208 memcpy(name, src, ptr - src);
209 name[ptr - src] = '\0';
210
211 ptr++;
212 const uint32_t addr = strtol(ptr, NULL, 0);
213
214 ptr = strchr(ptr, ':');
215 if (!ptr) {
216 printf("Invalid dump\n");
217 exit(1);
218 }
219
220 ptr++;
221 const uint32_t len = strtol(ptr, NULL, 0);
222
223 //printf("Saving to %s, from %x, len %u\n", name, addr, len);
224
225 FILE *f = fopen(name, "w");
226 if (!f) {
227 printf("Can't open %s\n", name);
228 exit(1);
229 }
230
231 if (fwrite(&ram[addr], len, 1, f) != 1)
232 abort();
233
234 fclose(f);
235 }
236
237 static void setfpregs(const int vcpu, struct kvm_fpu *fpregs) {
238 // KVM_[SG]ET_FPU isn't supported on PPC, we have to move individual regs...
239 unsigned i;
240 for (i = 0; i < 32; i++) {
241 struct kvm_one_reg r = {
242 .id = KVM_REG_PPC_FPR(i),
243 .addr = (uint64_t) &fpregs->fpr[i]
244 };
245
246 if (ioctl(vcpu, KVM_SET_ONE_REG, &r) != 0)
247 abort();
248 }
249 }
250
251 static void getfpregs(const int vcpu, struct kvm_fpu *fpregs) {
252 // KVM_[SG]ET_FPU isn't supported on PPC, we have to move individual regs...
253 unsigned i;
254 for (i = 0; i < 32; i++) {
255 struct kvm_one_reg r = {
256 .id = KVM_REG_PPC_FPR(i),
257 .addr = (uint64_t) &fpregs->fpr[i]
258 };
259
260 if (ioctl(vcpu, KVM_GET_ONE_REG, &r) != 0)
261 abort();
262 }
263 }
264
265 int main(int argc, char **argv) {
266
267 const struct option longopts[] = {
268 { "binary", 1, NULL, 'i' },
269 { "intregs", 1, NULL, 'g' },
270 { "fpregs", 1, NULL, 'f' },
271 { "spregs", 1, NULL, 's' },
272 { "load", 1, NULL, 'l' },
273 { "dump", 1, NULL, 'd' },
274 { "trace", 1, NULL, 't' },
275 { "help", 0, NULL, 'h' },
276 { NULL, 0, NULL, 0 }
277 };
278 const char opts[] = "i:g:f:s:l:d:t:h";
279
280 struct kvm_run *run;
281 struct kvm_regs regs;
282 struct kvm_sregs sregs;
283 struct kvm_fpu fpregs;
284 int kvm, vmfd, vcpu;
285 FILE *binary = NULL, *trace = NULL;
286 char dumps[MAXDUMPS][PATH_MAX];
287 unsigned num_dumps = 0;
288 uint8_t *ram, *progmem;
289 const char *binpath;
290
291 // Yes, we're frugal
292 if (posix_memalign((void **) &ram, 64 * 1024, RAMSIZE))
293 abort();
294 memset(ram, 0, RAMSIZE);
295
296 memset(&regs, 0, sizeof(struct kvm_regs));
297 memset(&fpregs, 0, sizeof(struct kvm_fpu));
298
299 regs.lr = -1;
300 regs.pc = PROGSTART;
301 regs.msr = MSR_64 | MSR_LE;
302
303 while (1) {
304 const int c = getopt_long(argc, argv, opts, longopts, NULL);
305 if (c == -1)
306 break;
307
308 switch (c) {
309 case 'i':
310 if (binary) {
311 printf("Only one binary allowed\n");
312 return 1;
313 }
314
315 binary = fopen(optarg, "r");
316 if (!binary) {
317 printf("Failed to open %s\n", optarg);
318 return 1;
319 }
320
321 binpath = strdup(optarg);
322 break;
323 case 'g':
324 parseregs(optarg, 1, &regs, &fpregs);
325 break;
326 case 'f':
327 parseregs(optarg, 0, &regs, &fpregs);
328 break;
329 case 's':
330 parsesprs(optarg, &regs);
331 break;
332 case 'l':
333 load(optarg, ram);
334 break;
335 case 'd':
336 if (num_dumps >= MAXDUMPS) {
337 printf("Too many dumps\n");
338 return 1;
339 }
340
341 strncpy(dumps[num_dumps], optarg, PATH_MAX);
342 dumps[num_dumps][PATH_MAX - 1] = '\0';
343 num_dumps++;
344 break;
345 case 't':
346 if (trace) {
347 printf("Only one trace allowed\n");
348 return 1;
349 }
350
351 trace = fopen(optarg, "w");
352 if (!trace) {
353 printf("Failed to open %s\n", optarg);
354 return 1;
355 }
356 break;
357 case 'h':
358 default:
359 help(argv[0]);
360 break;
361 }
362 }
363
364 if (!binary) {
365 help(argv[0]);
366 }
367
368 fseek(binary, 0, SEEK_END);
369 const unsigned binlen = ftell(binary);
370 rewind(binary);
371
372 printf("Loading binary %u bytes\n", binlen);
373
374 const unsigned progmemlen = (binlen + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
375
376 if (posix_memalign((void **) &progmem, 64 * 1024, progmemlen))
377 abort();
378
379 if (fread(progmem, binlen, 1, binary) != 1)
380 abort();
381 fclose(binary);
382
383 kvm = open("/dev/kvm", O_RDWR | O_CLOEXEC);
384 if (kvm < 0) {
385 printf("Failed to open kvm, perhaps you lack permissions?\n");
386 return 1;
387 }
388
389 int ret;
390 ret = ioctl(kvm, KVM_GET_API_VERSION, NULL);
391 if (ret == -1 || ret != 12) abort();
392
393 ret = ioctl(kvm, KVM_CHECK_EXTENSION, KVM_CAP_PPC_GUEST_DEBUG_SSTEP);
394 if (ret == -1 || !ret) {
395 printf("This system lacks single-stepping!\n");
396 return 1;
397 }
398
399 vmfd = ioctl(kvm, KVM_CREATE_VM, (unsigned long)0);
400
401 struct kvm_userspace_memory_region region = {
402 .slot = 0,
403 .guest_phys_addr = 0,
404 .memory_size = RAMSIZE,
405 .userspace_addr = (uint64_t) ram,
406 .flags = 0
407 };
408 if (ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &region) == -1)
409 abort();
410
411 region.slot = 1;
412 region.guest_phys_addr = PROGSTART;
413 region.memory_size = progmemlen;
414 region.userspace_addr = (uint64_t) progmem;
415 if (ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &region) == -1)
416 abort();
417
418 vcpu = ioctl(vmfd, KVM_CREATE_VCPU, (unsigned long)0);
419 const unsigned vcpulen = ioctl(kvm, KVM_GET_VCPU_MMAP_SIZE, NULL);
420
421 run = mmap(NULL, vcpulen, PROT_READ | PROT_WRITE, MAP_SHARED, vcpu, 0);
422
423 if (ioctl(vcpu, KVM_GET_SREGS, &sregs) == -1)
424 abort();
425 // It defaults to host cpu on POWER5+, which is what we want.
426 // http://pearpc.sourceforge.net/pvr.html
427 if (ioctl(vcpu, KVM_SET_SREGS, &sregs) == -1)
428 abort();
429
430 if (ioctl(vcpu, KVM_SET_REGS, &regs) == -1)
431 abort();
432 setfpregs(vcpu, &fpregs);
433
434 const struct kvm_guest_debug dbg = {
435 .control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_SINGLESTEP
436 };
437 if (ioctl(vcpu, KVM_SET_GUEST_DEBUG, &dbg) == -1)
438 abort();
439
440 // Runtime
441 while (1) {
442 if (ioctl(vcpu, KVM_RUN, NULL) == -1)
443 abort();
444 printf("Exited because %u\n", run->exit_reason);
445 }
446
447 unsigned i;
448 for (i = 0; i < num_dumps; i++) {
449 dump(dumps[i], ram);
450 }
451
452 close(kvm);
453 free(progmem);
454 free(ram);
455 free((char *) binpath);
456 return 0;
457 }