// pal boots a Linux bzImage directly, without firmware or a conventional
// bootloader. "we have a virtual machine manager at home" :)
// https://youtu.be/odouCACH9-w
// https://docs.kernel.org/arch/x86/boot.html
// https://docs.kernel.org/virt/kvm/api.html
#include <errno.h>
#include <fcntl.h>
#include <linux/kvm.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
// absolute byte offsets in the bzImage setup header, from:
// https://docs.kernel.org/arch/x86/boot.html
#define OFFSET_SETUP_SECTS 0x1F1
#define OFFSET_HDR_MAGIC 0x202
#define OFFSET_VERSION 0x206
#define OFFSET_PREF_ADDRESS 0x258
#define OFFSET_INIT_SIZE 0x260
// Guest-physical addresses chosen for the kernel and boot-time structures.
// They do not overlap: the GDT is in low memory, boot_params and the command
// line are below 1 MiB, and the protected-mode kernel begins at 1 MiB.
#define KERNEL_LOAD_ADDR 0x100000
#define BOOT_PARAMS_ADDR 0x10000
#define CMDLINE_ADDR 0x20000
#define GDT_ADDR 0x500
#define GUEST_MEM_SIZE (128ULL * 1024 * 1024)
#define E820_RAM 1
#define COM1_PORT 0x3f8
#define CMOS_INDEX_PORT 0x70
#define CMOS_DATA_PORT 0x71
#define UART_LCR_DLAB 0x80
// Signal handlers may safely set sig_atomic_t flags, but cannot perform the
// terminal reads or KVM ioctls themselves. The main loop consumes these flags
// after KVM_RUN returns with EINTR.
static volatile sig_atomic_t startup_timeout;
static volatile sig_atomic_t stdin_ready;
// Terminal state is process-global so the atexit handler can restore it even
// when an error path calls die().
static struct termios saved_termios;
static int saved_stdin_flags = -1;
static bool terminal_configured;
static void die(const char* msg);
static void handle_alarm(int signo) {
(void)signo;
startup_timeout = 1;
}
static void handle_stdin(int signo) {
(void)signo;
stdin_ready = 1;
}
static void restore_terminal(void) {
if (!terminal_configured)
return;
// Best-effort cleanup: an atexit callback cannot usefully report a failure.
tcsetattr(STDIN_FILENO, TCSANOW, &saved_termios);
if (saved_stdin_flags >= 0)
fcntl(STDIN_FILENO, F_SETFL, saved_stdin_flags);
}
static void set_irq(int vm, uint32_t irq, bool level) {
// KVM_IRQ_LINE drives an input on the in-kernel interrupt controller. COM1
// is conventionally connected to x86 IRQ 4; callers decide when to assert
// and deassert it according to the emulated UART's pending conditions.
struct kvm_irq_level irq_level = {
.irq = irq,
.level = level,
};
if (ioctl(vm, KVM_IRQ_LINE, &irq_level) < 0)
die("KVM_IRQ_LINE");
}
static void die(const char* msg) {
perror(msg);
exit(1);
}
// Linux's zero-page ABI is defined by byte offsets, so these packed structures
// describe an on-wire layout rather than a compiler-native data structure.
// E820 entries tell the kernel which guest-physical ranges are usable RAM.
struct e820_entry {
uint64_t addr;
uint64_t size;
uint32_t type;
} __attribute__((packed));
struct boot_params {
// Preserve the holes from the documented boot_params/zero_page layout. Only
// e820_entries, hdr, and e820_table are accessed by this small loader.
uint8_t _pad0[0x1e8];
uint8_t e820_entries;
uint8_t _pad1[0x1f1 - 0x1e9];
uint8_t hdr[0x290 - 0x1f1];
uint8_t _pad2[0x2d0 - 0x290];
struct e820_entry e820_table[128];
uint8_t _pad3[0x1000 - 0x2d0 - (128 * sizeof(struct e820_entry))];
} __attribute__((packed));
_Static_assert(sizeof(struct boot_params) == 0x1000,
"boot_params must be one page");
struct kernel_image {
uint8_t* data;
size_t file_size;
size_t setup_size;
size_t protected_mode_size;
uint64_t preferred_address;
uint32_t init_size;
};
struct virtual_machine {
int kvm;
int vm;
int vcpu;
uint8_t* mem;
size_t mem_size;
struct kvm_run* run;
size_t run_size;
};
struct uart {
uint8_t regs[8];
uint8_t input_queue[256];
size_t input_head;
size_t input_count;
bool thre_irq_pending;
bool irq_asserted;
};
struct cmos_rtc {
uint8_t index;
};
// load_kernel is a small helper function to read the bzImage file
static struct kernel_image load_kernel(const char* path, bool verbose) {
struct kernel_image kernel = {0};
int fd = open(path, O_RDONLY);
if (fd < 0)
die("open bzImage");
struct stat st;
if (fstat(fd, &st) < 0)
die("fstat");
kernel.file_size = (size_t)st.st_size;
kernel.data = malloc(kernel.file_size);
if (!kernel.data)
die("malloc");
// read the entire bzImage. Since read() may return fewer bytes than
// requested, we keep track of how many bytes have been read and continue
// until the whole file is loaded.
size_t total = 0;
while (total < kernel.file_size) {
ssize_t count = read(fd, kernel.data + total, kernel.file_size - total);
if (count <= 0)
die("read bzImage");
total += (size_t)count;
}
close(fd);
uint8_t setup_sects = kernel.data[OFFSET_SETUP_SECTS];
// if this field is 0, the real value is 4.
if (setup_sects == 0)
setup_sects = 4;
// the real-mode code consists of the boot sector (always one 512-byte sector)
// plus the setup code.
kernel.setup_size = (size_t)(setup_sects + 1) * 512;
uint32_t magic;
// "HdrS" (0x53726448) in little-endian identifies the Linux x86 boot protocol
// header.
memcpy(&magic, &kernel.data[OFFSET_HDR_MAGIC], sizeof(magic));
if (magic != 0x53726448) {
fprintf(stderr, "bad bzImage magic\n");
exit(1);
}
uint16_t version;
memcpy(&version, &kernel.data[OFFSET_VERSION], sizeof(version));
if (version < 0x0206)
fprintf(stderr, "warning: old boot protocol version\n");
// preferred_address represents the preferred physical memory address to load
// the kernel to
memcpy(&kernel.preferred_address, &kernel.data[OFFSET_PREF_ADDRESS],
sizeof(kernel.preferred_address));
// init_size represents the linear memory required during initialization
memcpy(&kernel.init_size, &kernel.data[OFFSET_INIT_SIZE],
sizeof(kernel.init_size));
// protected_mode_size represents the bulk of the kernel image, cf. 1.1 memory
// layout
kernel.protected_mode_size = kernel.file_size - kernel.setup_size;
if (verbose)
fprintf(stderr,
"[I]: setup_size=%zu, protected_mode_size=%zu, "
"preferred_address=%llu, init_size=%u\n",
kernel.setup_size, kernel.protected_mode_size,
(unsigned long long)kernel.preferred_address,
(unsigned int)kernel.init_size);
return kernel;
}
// check_kernel_fits is a small helper function to check the kernel has enough
// memory to work (includes decompressing itself)
static void check_kernel_fits(const struct kernel_image* kernel) {
bool runtime_fits =
kernel->init_size <= GUEST_MEM_SIZE &&
kernel->preferred_address <= GUEST_MEM_SIZE - kernel->init_size;
bool loaded_image_fits =
kernel->protected_mode_size <= GUEST_MEM_SIZE - KERNEL_LOAD_ADDR;
if (!runtime_fits || !loaded_image_fits) {
fprintf(stderr, "kernel does not fit in guest memory\n");
exit(1);
}
}
// create_vm creates a KVM virtual machine, sets up the interrupt controller and
// the programmable interval timer and and allocates and maps guest phyisical
// memory
static struct virtual_machine create_vm(const struct kernel_image* kernel) {
struct virtual_machine machine = {0};
// get an handle to the `dev/kvm` file, everything is a file in linux!!!
machine.kvm = open("/dev/kvm", O_RDWR | O_CLOEXEC);
if (machine.kvm < 0)
die("open /dev/kvm");
// create a virtual machine, with no cpu and no memory
machine.vm = ioctl(machine.kvm, KVM_CREATE_VM, 0);
if (machine.vm < 0)
die("KVM_CREATE_VM");
// create an in-kernel virtual interrupt controller, used whenever a device
// wants CPU attention, used for example by keyboard when inputting something
if (ioctl(machine.vm, KVM_CREATE_IRQCHIP, 0) < 0)
die("KVM_CREATE_IRQCHIP");
// create an in-kernel Programmable Interval Timer, used to schedule
// processes, divide cpu time, implement sleep() and timeouts, it emits
// signals to the interrupt controller created above
struct kvm_pit_config pit = {0};
if (ioctl(machine.vm, KVM_CREATE_PIT2, &pit) < 0)
die("KVM_CREATE_PIT2");
check_kernel_fits(kernel);
// allocate memory for the virtual machine
machine.mem_size = GUEST_MEM_SIZE;
machine.mem = mmap(NULL, machine.mem_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (machine.mem == MAP_FAILED)
die("mmap guest memory");
// set memory region for the virtual machine to the buffer we mmapped above
struct kvm_userspace_memory_region region = {
.slot = 0,
.flags = 0,
.guest_phys_addr = 0,
.memory_size = machine.mem_size,
.userspace_addr = (uint64_t)machine.mem,
};
if (ioctl(machine.vm, KVM_SET_USER_MEMORY_REGION, ®ion) < 0)
die("KVM_SET_USER_MEMORY_REGION");
return machine;
}
static void load_guest_memory(struct virtual_machine* machine,
const struct kernel_image* kernel) {
// Skip the real-mode setup sectors and reproduce the bootloader-created state
// expected by the kernel's direct 32-bit entry point.
memcpy(machine->mem + KERNEL_LOAD_ADDR, kernel->data + kernel->setup_size,
kernel->protected_mode_size);
struct boot_params* bp =
(struct boot_params*)(machine->mem + BOOT_PARAMS_ADDR);
memset(bp, 0, sizeof(*bp));
memcpy(bp->hdr, kernel->data + OFFSET_SETUP_SECTS, sizeof(bp->hdr));
uint8_t* hdr = bp->hdr;
// These indices are absolute setup-header offsets rebased to hdr[0].
hdr[0x210 - OFFSET_SETUP_SECTS] = 0xFF;
hdr[0x211 - OFFSET_SETUP_SECTS] |= 0x01;
uint32_t cmdline_ptr = CMDLINE_ADDR;
memcpy(&hdr[0x228 - OFFSET_SETUP_SECTS], &cmdline_ptr, sizeof(cmdline_ptr));
// Show all kernel messages while booting. The initramfs lowers the live
// console threshold immediately before handing ttyS0 to the shell.
const char* cmdline =
"console=ttyS0,115200 earlycon=uart,io,0x3f8,115200 loglevel=8 "
"rdinit=/init";
memcpy(machine->mem + CMDLINE_ADDR, cmdline, strlen(cmdline) + 1);
bp->e820_entries = 2;
// Leave the conventional PC hole between 0x9fc00 and 1 MiB unadvertised;
// Linux consequently treats it as reserved rather than usable RAM.
bp->e820_table[0] =
(struct e820_entry){.addr = 0, .size = 0x9fc00, .type = E820_RAM};
bp->e820_table[1] = (struct e820_entry){
.addr = KERNEL_LOAD_ADDR,
.size = machine->mem_size - KERNEL_LOAD_ADDR,
.type = E820_RAM,
};
}
static void configure_cpu(struct virtual_machine* machine) {
// Give the vCPU KVM's supported CPUID model, map the shared exit-information
// page, then establish the segment and register state required by boot.rst.
machine->vcpu = ioctl(machine->vm, KVM_CREATE_VCPU, 0);
if (machine->vcpu < 0)
die("KVM_CREATE_VCPU");
const uint32_t max_cpuid_entries = 256;
size_t cpuid_size = sizeof(struct kvm_cpuid2) +
max_cpuid_entries * sizeof(struct kvm_cpuid_entry2);
struct kvm_cpuid2* cpuid = calloc(1, cpuid_size);
if (!cpuid)
die("calloc CPUID");
cpuid->nent = max_cpuid_entries;
if (ioctl(machine->kvm, KVM_GET_SUPPORTED_CPUID, cpuid) < 0)
die("KVM_GET_SUPPORTED_CPUID");
if (ioctl(machine->vcpu, KVM_SET_CPUID2, cpuid) < 0)
die("KVM_SET_CPUID2");
free(cpuid);
int run_size = ioctl(machine->kvm, KVM_GET_VCPU_MMAP_SIZE, 0);
if (run_size < 0)
die("KVM_GET_VCPU_MMAP_SIZE");
machine->run_size = (size_t)run_size;
machine->run = mmap(NULL, machine->run_size, PROT_READ | PROT_WRITE,
MAP_SHARED, machine->vcpu, 0);
if (machine->run == MAP_FAILED)
die("mmap kvm_run");
// ---------- protected mode flat segments ----------
// The 32-bit boot protocol requires a loaded GDT containing __BOOT_CS at
// selector 0x10 and __BOOT_DS at selector 0x18. KVM's cached segment state
// alone is insufficient because the decompressor may reload these selectors.
uint64_t* gdt = (uint64_t*)(machine->mem + GDT_ADDR);
gdt[0] = 0; // selector 0x00: mandatory null descriptor
gdt[1] = 0; // selector 0x08: unused here
gdt[2] = 0x00cf9b000000ffffULL; // 0x10: 32-bit, 4 GiB code
gdt[3] = 0x00cf93000000ffffULL; // 0x18: 32-bit, 4 GiB data
struct kvm_sregs sregs;
if (ioctl(machine->vcpu, KVM_GET_SREGS, &sregs) < 0)
die("KVM_GET_SREGS");
struct kvm_segment flat_code = {
.base = 0,
.limit = 0xFFFFFFFF,
.selector = 0x10,
.type = 0x0B, // execute/read, accessed
.present = 1, // descriptor is valid
.dpl = 0, // kernel privilege level
.db = 1, // default operand/address size is 32 bits
.s = 1, // code/data descriptor, not a system descriptor
.l = 0, // not a 64-bit code segment
.g = 1, // limit is expressed in 4 KiB pages
};
struct kvm_segment flat_data = flat_code;
flat_data.type = 0x03; // read/write, accessed
flat_data.selector = 0x18;
sregs.cs = flat_code;
sregs.ds = flat_data;
sregs.es = flat_data;
sregs.fs = flat_data;
sregs.gs = flat_data;
sregs.ss = flat_data;
sregs.gdt.base = GDT_ADDR;
sregs.gdt.limit = (4 * sizeof(*gdt)) - 1;
// KVM maintains both the descriptor-table state and cached interpretations
// of every segment, so set both before enabling protected mode.
sregs.cr0 |= 1; // CR0.PE: protected-mode enable
if (ioctl(machine->vcpu, KVM_SET_SREGS, &sregs) < 0)
die("KVM_SET_SREGS");
// ---------- Set the Linux 32-bit entry register contract ----------
struct kvm_regs regs = {0};
regs.rip = KERNEL_LOAD_ADDR; // 32-bit entry point per boot.rst
regs.rsi = BOOT_PARAMS_ADDR; // kernel expects boot_params here
regs.rflags = 0x2; // reserved bit only, IF=0
if (ioctl(machine->vcpu, KVM_SET_REGS, ®s) < 0)
die("KVM_SET_REGS");
}
static void configure_terminal(void) {
// Raw, asynchronous terminal input lets keystrokes reach the guest serial
// port immediately. The signal handler only wakes KVM_RUN; drain_stdin does
// the non-signal-safe read work in normal process context.
if (isatty(STDIN_FILENO)) {
// Disable canonical line buffering and echo so each host keystroke can be
// delivered immediately and is printed only if the guest echoes it.
if (tcgetattr(STDIN_FILENO, &saved_termios) < 0)
die("tcgetattr");
struct termios raw = saved_termios;
raw.c_lflag &= (tcflag_t) ~(ICANON | ECHO);
raw.c_cc[VMIN] = 1;
raw.c_cc[VTIME] = 0;
if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) < 0)
die("tcsetattr");
terminal_configured = true;
if (atexit(restore_terminal) != 0)
die("atexit");
struct sigaction stdin_action = {.sa_handler = handle_stdin};
sigemptyset(&stdin_action.sa_mask);
if (sigaction(SIGIO, &stdin_action, NULL) < 0)
die("sigaction SIGIO");
// O_ASYNC asks the kernel to deliver SIGIO when input becomes readable;
// O_NONBLOCK lets the main loop drain everything available without waiting.
saved_stdin_flags = fcntl(STDIN_FILENO, F_GETFL);
if (saved_stdin_flags < 0)
die("fcntl F_GETFL");
if (fcntl(STDIN_FILENO, F_SETOWN, getpid()) < 0)
die("fcntl F_SETOWN");
if (fcntl(STDIN_FILENO, F_SETFL, saved_stdin_flags | O_NONBLOCK | O_ASYNC) <
0)
die("fcntl F_SETFL");
}
}
static void install_alarm(void) {
startup_timeout = 0;
struct sigaction alarm_action = {.sa_handler = handle_alarm};
sigemptyset(&alarm_action.sa_mask);
if (sigaction(SIGALRM, &alarm_action, NULL) < 0)
die("sigaction");
// A malformed boot setup could otherwise stay inside KVM_RUN forever before
// producing observable output. SIGALRM interrupts that first run after 3 s;
// the alarm is cancelled as soon as the guest performs any port I/O.
alarm(3);
}
static void drain_stdin(struct uart* uart) {
if (!stdin_ready)
return;
stdin_ready = 0;
uint8_t incoming[64];
ssize_t received;
while ((received = read(STDIN_FILENO, incoming, sizeof(incoming))) > 0) {
for (ssize_t i = 0;
i < received && uart->input_count < sizeof(uart->input_queue); i++) {
size_t tail =
(uart->input_head + uart->input_count) % sizeof(uart->input_queue);
uart->input_queue[tail] = incoming[i];
uart->input_count++;
}
}
}
static void update_uart_irq(struct virtual_machine* machine,
struct uart* uart) {
// COM1 IRQ 4 is level-triggered here: keep it asserted as long as either an
// enabled receive-data or transmitter-empty condition remains pending.
bool rx_pending = uart->input_count > 0 && (uart->regs[1] & 0x01) != 0;
bool tx_pending = uart->thre_irq_pending && (uart->regs[1] & 0x02) != 0;
bool wanted = rx_pending || tx_pending;
if (wanted != uart->irq_asserted) {
set_irq(machine->vm, 4, wanted);
uart->irq_asserted = wanted;
}
}
static uint8_t to_bcd(int value) {
return (uint8_t)(((value / 10) << 4) | (value % 10));
}
// Without CMOS emulation, the default unhandled-port value 0xff leaves the
// update-in-progress bit set forever. Linux then waits one second before
// printing "Unable to read current time from RTC" during every boot.
static uint8_t read_cmos_rtc(uint8_t index) {
time_t now = time(NULL);
struct tm utc;
if (now == (time_t)-1 || !gmtime_r(&now, &utc))
return 0;
switch (index) {
case 0x00:
return to_bcd(utc.tm_sec);
case 0x02:
return to_bcd(utc.tm_min);
case 0x04:
return to_bcd(utc.tm_hour);
case 0x06:
return to_bcd(utc.tm_wday + 1);
case 0x07:
return to_bcd(utc.tm_mday);
case 0x08:
return to_bcd(utc.tm_mon + 1);
case 0x09:
return to_bcd((utc.tm_year + 1900) % 100);
case 0x0a:
return 0x26; // 32.768 kHz divider, update not in progress.
case 0x0b:
return 0x02; // 24-hour, BCD mode; interrupts disabled.
case 0x0c:
return 0;
case 0x0d:
return 0x80; // RTC battery/time is valid.
case 0x32:
return to_bcd((utc.tm_year + 1900) / 100);
default:
return 0;
}
}
static void emulate_io(struct kvm_run* run,
struct uart* uart,
struct cmos_rtc* rtc) {
// Implement the read-only CMOS clock and byte-wide COM1 accesses. Reads of
// all other ports return all ones, matching the usual no-device bus value.
uint8_t* data = (uint8_t*)run + run->io.data_offset;
for (uint32_t i = 0; i < run->io.count; i++) {
uint16_t port = run->io.port;
uint8_t* value = data + (i * run->io.size);
if (run->io.size == 1 && port == CMOS_INDEX_PORT) {
if (run->io.direction == KVM_EXIT_IO_OUT)
rtc->index = *value & 0x7f;
else
*value = rtc->index;
continue;
}
if (run->io.size == 1 && port == CMOS_DATA_PORT) {
if (run->io.direction == KVM_EXIT_IO_IN)
*value = read_cmos_rtc(rtc->index);
continue; // This minimal RTC ignores writes.
}
if (port < COM1_PORT || port >= COM1_PORT + 8 || run->io.size != 1) {
if (run->io.direction == KVM_EXIT_IO_IN)
memset(value, 0xff, run->io.size);
continue;
}
uint8_t reg = (uint8_t)(port - COM1_PORT);
bool dlab = (uart->regs[3] & UART_LCR_DLAB) != 0;
// DLAB changes registers 0 and 1 into baud-rate divisor latches. Otherwise
// they act as the transmit/receive buffer and interrupt-enable register.
if (run->io.direction == KVM_EXIT_IO_OUT) {
if (reg == 0 && !dlab) {
putchar(*value);
fflush(stdout);
uart->thre_irq_pending = true;
} else if (reg == 1 && !dlab) {
uint8_t old_ier = uart->regs[1];
uart->regs[1] = *value;
if ((*value & 0x02) != 0 && (old_ier & 0x02) == 0)
uart->thre_irq_pending = true;
} else {
uart->regs[reg] = *value;
}
} else if (reg == 0 && !dlab && uart->input_count > 0) {
*value = uart->input_queue[uart->input_head];
uart->input_head = (uart->input_head + 1) % sizeof(uart->input_queue);
uart->input_count--;
} else if (reg == 2) {
// IIR prioritizes receive data over THRE. Reporting THRE acknowledges the
// latched condition until another transmission or enable transition.
if (uart->input_count > 0 && (uart->regs[1] & 0x01) != 0) {
*value = 0x04;
} else if (uart->thre_irq_pending && (uart->regs[1] & 0x02) != 0) {
*value = 0x02;
uart->thre_irq_pending = false;
} else {
*value = 0x01;
}
} else if (reg == 5) {
*value = 0x60 | (uart->input_count > 0 ? 0x01 : 0x00);
} else if (reg == 6) {
*value = 0xb0;
} else {
*value = uart->regs[reg];
}
}
}
static bool handle_exit(struct virtual_machine* machine,
struct uart* uart,
struct cmos_rtc* rtc,
bool* saw_guest_io) {
// Return true for exits after which the vCPU should be entered again, and
// false for terminal exits. HLT is nonterminal because Linux uses it to idle
// until the in-kernel PIT or an injected UART interrupt wakes the vCPU.
struct kvm_run* run = machine->run;
switch (run->exit_reason) {
case KVM_EXIT_IO:
if (!*saw_guest_io) {
*saw_guest_io = true;
alarm(0);
}
emulate_io(run, uart, rtc);
return true;
case KVM_EXIT_HLT:
return true;
case KVM_EXIT_SHUTDOWN:
printf("\n[guest shutdown/triple fault]\n");
return false;
case KVM_EXIT_FAIL_ENTRY:
fprintf(
stderr, "KVM_EXIT_FAIL_ENTRY: reason=0x%llx\n",
(unsigned long long)run->fail_entry.hardware_entry_failure_reason);
return false;
case KVM_EXIT_INTERNAL_ERROR: {
fprintf(stderr, "KVM internal error: suberror=%u ndata=%u\n",
run->internal.suberror, run->internal.ndata);
for (uint32_t i = 0; i < run->internal.ndata; i++)
fprintf(stderr, " data[%u]=0x%016llx\n", i,
(unsigned long long)run->internal.data[i]);
struct kvm_regs regs;
if (ioctl(machine->vcpu, KVM_GET_REGS, ®s) == 0)
fprintf(stderr, " RIP=0x%llx RSP=0x%llx RFLAGS=0x%llx\n",
(unsigned long long)regs.rip, (unsigned long long)regs.rsp,
(unsigned long long)regs.rflags);
return false;
}
case KVM_EXIT_MMIO:
// No MMIO devices exist, but Linux may probe platform ranges. Discard
// writes and return all ones for reads instead of aborting the VM.
if (!run->mmio.is_write)
memset(run->mmio.data, 0xff, run->mmio.len);
return true;
default:
fprintf(stderr, "unhandled exit_reason=%u\n", run->exit_reason);
return false;
}
}
static void report_startup_timeout(int vcpu) {
struct kvm_regs regs;
if (ioctl(vcpu, KVM_GET_REGS, ®s) < 0)
die("KVM_GET_REGS after startup timeout");
fprintf(stderr,
"guest produced no I/O for 3 seconds: RIP=0x%llx RSP=0x%llx "
"RAX=0x%llx RSI=0x%llx\n",
(unsigned long long)regs.rip, (unsigned long long)regs.rsp,
(unsigned long long)regs.rax, (unsigned long long)regs.rsi);
}
static void run_guest(struct virtual_machine* machine) {
// This is the only long-running control loop: move host input into the UART,
// synchronize its interrupt line, enter KVM, and service the resulting exit.
struct uart uart = {0};
struct cmos_rtc rtc = {0};
uart.regs[5] = 0x60;
bool saw_guest_io = false;
configure_terminal();
install_alarm();
for (;;) {
drain_stdin(&uart);
update_uart_irq(machine, &uart);
if (ioctl(machine->vcpu, KVM_RUN, 0) < 0) {
if (errno == EINTR && startup_timeout) {
report_startup_timeout(machine->vcpu);
return;
}
if (errno == EINTR)
continue;
die("KVM_RUN");
}
if (!handle_exit(machine, &uart, &rtc, &saw_guest_io))
return;
}
}
static void destroy_vm(struct virtual_machine* machine) {
munmap(machine->run, machine->run_size);
close(machine->vcpu);
munmap(machine->mem, machine->mem_size);
close(machine->vm);
close(machine->kvm);
}
int main(int argc, char** argv) {
bool verbose = false;
int option;
while ((option = getopt(argc, argv, "v")) != -1) {
switch (option) {
case 'v':
verbose = true;
break;
default:
fprintf(stderr, "usage: %s [-v] <bzImage>\n", argv[0]);
return 1;
}
}
// optind is a global variable used by getopt() to keep track of which command
// line item it should process next
if (optind != argc - 1) {
fprintf(stderr, "usage: %s [-v] <bzImage>\n", argv[0]);
return 1;
}
struct kernel_image kernel = load_kernel(argv[optind], verbose);
struct virtual_machine machine = create_vm(&kernel);
load_guest_memory(&machine, &kernel);
configure_cpu(&machine);
run_guest(&machine);
destroy_vm(&machine);
free(kernel.data);
return 0;
}