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