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