2

Background: I'm using QEMU to virtualize xv6-riscv on top of WSL2. I'm looking to create some sort of clean OS shutdown process reminiscent of Linux's exit command. Currently I'm using Ctrl-a x to kill qemu, but I want to be able to do this programmatically from within the OS.

I know that the Linux exit command simply closes the current shell, which means I need a kernelspace way of detecting if there's any active shells left and a way of signalling to QEMU that it's turning off. I have full access to the kernel for recompiling, but I can't use external C libraries because there are none. Any solution I develop has to be basically from-scratch.

Thus, my question: What does an operating system need to do to tell a RISC-V QEMU instance that it's turning off? How do you exit QEMU itself from within the OS? This second point is important specifically because I'm instantiating QEMU from a Makefile in a CLI, and I'd like to have access to the terminal after the OS terminates, rather than have QEMU sitting there emulating a RISC-V with nothing on it.

note: This question is different from this other one because I'm asking about a guest OS initiated shutdown rather than a host initiated ACPI shutdown, as well as diving much deeper into the internals of QEMU and operating systems in general.

1 Answer 1

3

TL;DR

From the supervisor mode:

(*(volatile uint32 *) 0x100000) = 0x5555; 

You also need to change the kernel page table to map the address 0x100000. You can see this commit for full implementation.

Long answer

Unfortunately, the documentation for QEMU RISC-V emulator is poor. So we need to dive into the code. We know from the xv6's Makefile that it is using QEMU's Generic Virtual Platform (virt) in order to virtualize the xv6.

The virt.c file from QEMU's source code contains the implementation of the QEMU's Generic Virtual Platform. If we search poweroff in that file, we will end up with this code snippet:

name = g_strdup_printf("/poweroff"); qemu_fdt_add_subnode(ms->fdt, name); qemu_fdt_setprop_string(ms->fdt, name, "compatible", "syscon-poweroff"); qemu_fdt_setprop_cell(ms->fdt, name, "regmap", test_phandle); qemu_fdt_setprop_cell(ms->fdt, name, "offset", 0x0); qemu_fdt_setprop_cell(ms->fdt, name, "value", FINISHER_PASS); g_free(name); 

In a nutshell, this code snippet is telling the QEMU that if FINISHER_PASS is written to the 0th index of test_phandle register map, then poweroff the QEMU. If we look a few lines above it, we will realize that upon writing FINISHER_RESET we can reset the QEMU. A few lines before we can observe this line indicates the memory mapped location in the guest that we should write the FINISHER_PASS in:

qemu_fdt_setprop_cells(ms->fdt, name, "reg", 0x0, memmap[VIRT_TEST].base, 0x0, memmap[VIRT_TEST].size); 

If we search VIRT_TEST in the same file, we come up with this line:

[VIRT_TEST] = { 0x100000, 0x1000 }, 

So the location in which we should write FINISHER_PASS in is 0x100000. Also, if we search FINISHER_PASS and FINISHER_RESET in the whole project, we can see their values in this file:

enum { FINISHER_FAIL = 0x3333, FINISHER_PASS = 0x5555, FINISHER_RESET = 0x7777 }; 

Long story short, we currently know that to poweroff an operating system, we have to write 0x5555 into address 0x100000. You might think that you can reboot the operating system by writing 0x7777 in the same address but I couldn't do it for some reason... Let me know if you have found a reason!

Now we shall implement a syscall to poweroff xv6. To get started, we first must change the page table of the kernel in order to map the virtual address 0x100000 to physical address 0x100000 (you can map other virtual addresses if you have implemented ASLR). I did it by adding the following line to vm.c and function kvmmake:

kvmmap(kpgtbl, 0x100000, 0x100000, PGSIZE, PTE_R | PTE_W); 

We use the page size because the size of the area is 0x1000 bytes according to QEMU's source.

After that, we simply add a syscall to the operating system. I did it like this:

// Power off QEMU static uint64 sys_poweroff(void) { printf("Powering off...\n"); (*(volatile uint32 *) 0x100000) = 0x5555; panic("sys_poweroff"); } 

Add this syscall to the syscall table and now we are good to go!

You can then write a simple user program in order to use the syscall:

#include "kernel/types.h" #include "kernel/stat.h" #include "user/user.h" int main(int argc, char *argv[]) { poweroff(); exit(1); // never reached } 

Add this user program to Makefile and then you can use this application to poweroff the operating systems.

As I've said, you can see this commit for full implementation.

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.