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