1 /* Bare metal PPC KVM app, for libre-soc simulator comparison purposes
2 Copyright (C) 2021 Lauri Kasanen
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.
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.
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/>.
23 #include <linux/kvm.h>
29 #include <sys/ioctl.h>
32 #include <sys/types.h>
35 #define PAGE_SIZE (64 * 1024) // assumption
37 #define RAMSIZE (64 * 1024 * 1024)
38 #define PROGSTART 0x20000000
40 #define MSR_64 (1UL<<63)
41 #define MSR_FP (1UL<<13)
42 #define MSR_LE (1UL<<0)
48 static void nukenewline(char buf
[]) {
50 for (i
= 0; buf
[i
]; i
++) {
58 static void help(const char argv0
[]) {
60 printf("Usage: %s [args] -i file.bin\n\n"
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"
71 "Load and dump may be given multiple times. GPR/FPR are numbered,\n"
72 "SPRs are named.\n", argv0
);
77 static void parseregs(const char name
[], const uint8_t gpr
, struct kvm_regs
*regs
,
78 struct kvm_fpu
*fpregs
) {
80 FILE *f
= fopen(name
, "r");
82 printf("Can't open %s\n", name
);
88 while (fgets(buf
, 256, f
)) {
93 const uint8_t reg
= strtol(buf
, NULL
, 0);
94 const char *ptr
= strchr(buf
+ 1, ' ');
96 printf("Invalid line '%s'\n", buf
);
100 const uint64_t val
= strtol(ptr
, NULL
, 0);
104 regs
->gpr
[reg
] = val
;
106 fpregs
->fpr
[reg
] = val
;
113 static void parsesprs(const char name
[], struct kvm_regs
*regs
) {
115 FILE *f
= fopen(name
, "r");
117 printf("Can't open %s\n", name
);
123 while (fgets(buf
, 256, f
)) {
130 #define check(a) if (!strncasecmp(buf, a, sizeof(a) - 1))
139 printf("Unknown (unimplemented?) SPR register '%s'\n",
144 const char *ptr
= strchr(buf
+ 1, ' ');
146 printf("Invalid line '%s'\n", buf
);
150 const uint64_t val
= strtol(ptr
, NULL
, 0);
163 static void load(const char arg
[], uint8_t *ram
) {
166 const char *ptr
= strchr(arg
, ':');
168 printf("Invalid load\n");
172 const unsigned namelen
= ptr
- arg
;
174 strncpy(name
, arg
, namelen
);
175 name
[namelen
] = '\0';
177 const uint64_t addr
= strtol(ptr
+ 1, NULL
, 0);
179 FILE *f
= fopen(name
, "r");
181 printf("Can't open %s\n", name
);
185 fseek(f
, 0, SEEK_END
);
186 const unsigned len
= ftell(f
);
189 if (addr
+ len
>= RAMSIZE
) {
190 printf("Tried to use too much RAM\n");
194 printf("Loading %s to 0x%lx, %u bytes\n", name
, addr
, len
);
196 if (fread(&ram
[addr
], len
, 1, f
) != 1)
202 static void dump(const char src
[], const uint8_t ram
[]) {
203 const char *ptr
= strchr(src
, ':');
205 printf("Invalid dump\n");
210 memcpy(name
, src
, ptr
- src
);
211 name
[ptr
- src
] = '\0';
214 const uint32_t addr
= strtol(ptr
, NULL
, 0);
216 ptr
= strchr(ptr
, ':');
218 printf("Invalid dump\n");
223 const uint32_t len
= strtol(ptr
, NULL
, 0);
225 //printf("Saving to %s, from %x, len %u\n", name, addr, len);
227 FILE *f
= fopen(name
, "w");
229 printf("Can't open %s\n", name
);
233 if (fwrite(&ram
[addr
], len
, 1, f
) != 1)
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...
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
]
248 if (ioctl(vcpu
, KVM_SET_ONE_REG
, &r
) != 0)
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...
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
]
262 if (ioctl(vcpu
, KVM_GET_ONE_REG
, &r
) != 0)
267 static void disassemble(const char binpath
[], char *disasm
, const unsigned len
) {
270 snprintf(buf
, PATH_MAX
,
271 "objdump -b binary -m powerpc --no-show-raw-insn -D %s",
273 buf
[PATH_MAX
- 1] = '\0';
275 FILE *f
= popen(buf
, "r");
277 printf("Disassembly failed, trace won't have disas\n");
281 while (fgets(buf
, PATH_MAX
, f
)) {
284 const char *ptr
= strchr(buf
, ':');
289 const unsigned addr
= strtol(buf
, NULL
, 16) / 4 * 32;
290 if (addr
/ 32 + 1 >= len
)
294 while (isspace(*ptr
))
297 strncpy(&disasm
[addr
], ptr
, 32);
298 disasm
[addr
+ 31] = '\0';
304 int main(int argc
, char **argv
) {
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' },
317 const char opts
[] = "i:g:f:s:l:d:t:h";
320 struct kvm_regs regs
;
321 struct kvm_sregs sregs
;
322 struct kvm_fpu fpregs
;
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
;
332 if (posix_memalign((void **) &ram
, 64 * 1024, RAMSIZE
))
334 memset(ram
, 0, RAMSIZE
);
336 memset(®s
, 0, sizeof(struct kvm_regs
));
337 memset(&fpregs
, 0, sizeof(struct kvm_fpu
));
341 regs
.msr
= MSR_64
| MSR_FP
| MSR_LE
;
342 regs
.gpr
[1] = 0x8000; // Default stack pointer at 32kb, 20kb free space before vecs
345 const int c
= getopt_long(argc
, argv
, opts
, longopts
, NULL
);
352 printf("Only one binary allowed\n");
356 binary
= fopen(optarg
, "r");
358 printf("Failed to open %s\n", optarg
);
362 binpath
= strdup(optarg
);
365 parseregs(optarg
, 1, ®s
, &fpregs
);
368 parseregs(optarg
, 0, ®s
, &fpregs
);
371 parsesprs(optarg
, ®s
);
377 if (num_dumps
>= MAXDUMPS
) {
378 printf("Too many dumps\n");
382 strncpy(dumps
[num_dumps
], optarg
, PATH_MAX
);
383 dumps
[num_dumps
][PATH_MAX
- 1] = '\0';
388 printf("Only one trace allowed\n");
392 trace
= fopen(optarg
, "w");
394 printf("Failed to open %s\n", optarg
);
409 fseek(binary
, 0, SEEK_END
);
410 const unsigned binlen
= ftell(binary
);
413 printf("Loading binary %u bytes\n", binlen
);
415 const unsigned progmemlen
= (binlen
+ (PAGE_SIZE
- 1)) & ~(PAGE_SIZE
- 1);
417 if (posix_memalign((void **) &progmem
, 64 * 1024, progmemlen
))
420 if (fread(progmem
, binlen
, 1, binary
) != 1)
426 const unsigned disaslen
= binlen
/ 4 + 64;
427 disasm
= calloc(disaslen
, 32);
429 disassemble(binpath
, (char *) disasm
, disaslen
);
432 kvm
= open("/dev/kvm", O_RDWR
| O_CLOEXEC
);
434 printf("Failed to open kvm, perhaps you lack permissions?\n");
439 ret
= ioctl(kvm
, KVM_GET_API_VERSION
, NULL
);
440 if (ret
== -1 || ret
!= 12) abort();
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");
448 vmfd
= ioctl(kvm
, KVM_CREATE_VM
, (unsigned long)0);
450 struct kvm_userspace_memory_region region
= {
452 .guest_phys_addr
= 0,
453 .memory_size
= RAMSIZE
,
454 .userspace_addr
= (uint64_t) ram
,
457 if (ioctl(vmfd
, KVM_SET_USER_MEMORY_REGION
, ®ion
) == -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
, ®ion
) == -1)
467 vcpu
= ioctl(vmfd
, KVM_CREATE_VCPU
, (unsigned long)0);
468 const unsigned vcpulen
= ioctl(kvm
, KVM_GET_VCPU_MMAP_SIZE
, NULL
);
470 run
= mmap(NULL
, vcpulen
, PROT_READ
| PROT_WRITE
, MAP_SHARED
, vcpu
, 0);
472 if (ioctl(vcpu
, KVM_GET_SREGS
, &sregs
) == -1)
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)
479 if (ioctl(vcpu
, KVM_SET_REGS
, ®s
) == -1)
481 setfpregs(vcpu
, &fpregs
);
483 const struct kvm_guest_debug dbg
= {
484 .control
= KVM_GUESTDBG_ENABLE
| KVM_GUESTDBG_SINGLESTEP
486 if (ioctl(vcpu
, KVM_SET_GUEST_DEBUG
, &dbg
) == -1)
490 uint64_t pc
= PROGSTART
;
493 if (ioctl(vcpu
, KVM_RUN
, NULL
) == -1)
496 if (ioctl(vcpu
, KVM_GET_REGS
, ®s
) == -1)
498 //printf("PC %lx LR %lx\n", regs.pc, regs.lr);
500 if (run
->exit_reason
== KVM_EXIT_DEBUG
) {
502 getfpregs(vcpu
, &fpregs
);
504 fprintf(trace
, "%lx: %s\n", pc
, &disasm
[(pc
- PROGSTART
) / 4 * 32]);
505 for (i
= 0; i
< 32; i
++) {
507 fprintf(trace
, "GPR: ");
508 fprintf(trace
, "%08lx", regs
.gpr
[i
]);
510 fprintf(trace
, "\n");
514 for (i
= 0; i
< 32; i
++) {
516 fprintf(trace
, "FPR: ");
517 fprintf(trace
, "%08lx", fpregs
.fpr
[i
]);
519 fprintf(trace
, "\n");
524 } else if (regs
.pc
< PROGSTART
|| regs
.pc
> PROGSTART
+ binlen
) {
525 // Normal exit by blr
528 printf("Unexpected exit because %u\n", run
->exit_reason
);
535 for (i
= 0; i
< num_dumps
; i
++) {
542 free((char *) binpath
);
543 free((char *) disasm
);