Skip to content
Snippets Groups Projects
Select Git revision
  • 74a721bbb3208860899bc6ee10a737ac53393837
  • main default protected
2 results

vmm_main.c

Blame
  • vmm_main.c 8.24 KiB
    #include <SDL2/SDL_keycode.h>
    #include <SDL2/SDL_render.h>
    #include <err.h>
    #include <stdbool.h>
    #include <fcntl.h>
    #include <linux/kvm.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/ioctl.h>
    #include <sys/mman.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <pthread.h>
    #include <semaphore.h>
    
    #include "gfx.h"
    #include "handler.h"
    #include "operation.h"
    #include "shared/hypercall_params.h"
    
    // -- DEFINE --
    
    #define KVM_API_VERSION   12
    #define RAM_SIZE          (512 * 1024)
    
    #define SEM_THREAD  0
    
    sem_t sem_gfx;
    uint32_t width_gfx;
    uint32_t height_gfx;
    gfx_context_t *ctxt;
    sprite_t sprites[MAX_SPRITES];
    uint32_t last_key_pressed;
    
    char *disk_path;
    
    static uint8_t *mem, *shared_buf;
    static struct kvm_run *run;
    static int vcpufd;
    
    // -- THREAD --
    
    static void *thread_hypercall() {
    
        bool done = false;
        while (!done) {
    
            if (ioctl(vcpufd, KVM_RUN, NULL) == -1) err(1, "KVM_RUN");
    
            state_handler_t handler = STATE_HANDLERS[run->exit_reason];
    
            if (handler) {
    
                handler(run, shared_buf, mem, &done);
            }
            else {
    
                errx(1, "exit_reason = 0x%x", run->exit_reason);
            }
        }
    
        return NULL;
    }
    
    // -- MAIN --
    
    int main(int argc, char* argv[])
    {
        int kvmfd, vmfd;
        struct kvm_sregs sregs;
        int32_t mmap_size;
    
        // -- 0. Handle Args --
        if (argc != 3) err(1, "Number of args invalide");
        char *file_path = argv[1];
        disk_path = argv[2];
    
        // -- 1. Create a KVM device --
        kvmfd = open("/dev/kvm", O_RDWR | O_CLOEXEC); // get file descriptor
        if (kvmfd == -1) err(1, "/dev/kvm");
    
        // check stable version of the API
        int version = ioctl(kvmfd, KVM_GET_API_VERSION, NULL);
        if (version == -1) err(1, "KVM_GET_API_VERSION");
        if (version != KVM_API_VERSION) errx(1, "KVM_GET_API_VERSION %d, expected 12", version);
    
        // -- 2. Create a VM --
        vmfd = ioctl(kvmfd, KVM_CREATE_VM, (unsigned long)0);
        if (vmfd == -1) err(1, "KVM_CREATE_VM");
    
        // -- 3.1 Allocate RAM for the VM --
        mem = mmap(NULL, RAM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); // memory to hold the code
        if (!mem) err(1, "allocating guest memory");
    
        // -- 3.2 Map allocated RAM into the VM’s address space --
        struct kvm_userspace_memory_region region_mem = {
            .slot = 0,
            .guest_phys_addr = 0,
            .memory_size = RAM_SIZE,
            .userspace_addr = (uint64_t)mem,
            .flags = 0,
        };
    
        int memreg_err = ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &region_mem);
        if (memreg_err == -1) err(1, "KVM_SET_USER_MEMORY_REGION");
    
        // -- 4.1 Allocate shared buffer for the VM and guest --
        shared_buf = mmap(NULL, HYPERCALL_BUFF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); // memory to hold the code
        if (!shared_buf) err(1, "allocating shared buffer memory");
    
        // -- 4.2 Map allocated shared buffer into the VM’s address space --
        struct kvm_userspace_memory_region region_shared_buf = {
            .slot = 1,
            .guest_phys_addr = HYPERCALL_SHARED_ADDR,
            .memory_size = HYPERCALL_BUFF_SIZE,
            .userspace_addr = (uint64_t)shared_buf,
            .flags = 0,
        };
    
        int sharedbufreg_err = ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &region_shared_buf);
        if (sharedbufreg_err == -1) err(1, "KVM_SET_USER_MEMORY_REGION");
    
        // -- 5. Load guest OS into VM’s RAM --
        // Open the binary file
        int binfd = open(file_path, O_RDONLY);
        if (binfd < 0) err(1, "Error opening guestOS binary file");
    
        // Get the file size using fstat
        struct stat st;
        if (fstat(binfd, &st) < 0) err(1, "Error getting file size");
    
        // Check if the file size exceeds the available memory
        if (st.st_size > RAM_SIZE) err(1, "Error: GuestOS binary size exceeds 512KB\n");
    
        // Read the file into the provided memory (mem)
        ssize_t bytes_read = read(binfd, mem, st.st_size);
        if (bytes_read < 0) err(1, "Error reading guestOS binary file");
    
        // Check if all bytes were read
        if (bytes_read != st.st_size) err(1, "Error: Only %ld of %ld bytes read from file\n", bytes_read, st.st_size);
    
        // Close the file descriptor
        close(binfd);
    
        // -- 5.5. Create virtual PIC (Programmable Interrupt Controller) --
        if (ioctl(vmfd, KVM_CREATE_IRQCHIP, NULL) < 0)
            err(1, "Error: creating the vPIC\n");
    
        struct kvm_irqchip irqchip = {
            .chip_id = 0,       // 0 = PIC1, 1 = PIC2, 2 = IOAPIC
        };
    
        // retrieve the PIC registers
        if (ioctl(vmfd, KVM_GET_IRQCHIP, &irqchip) < 0)
            err(1, "Error: retrieving the PIC registers\n");
    
        // configure the PIC to have auto-acknoledge from the guest
        irqchip.chip.pic.auto_eoi = 1;
    
        // set the PIC registers
        if (ioctl(vmfd, KVM_SET_IRQCHIP, &irqchip) < 0)
            err(1, "Error: setting the PIC registers\n");
    
        // -- 6.1. Create a vCPU --
        vcpufd = ioctl(vmfd, KVM_CREATE_VCPU, (unsigned long)0);
        if (vcpufd == -1) err(1, "KVM_CREATE_VCPU");
    
        // map the shared kvm_run structure and following data
        mmap_size = ioctl(kvmfd, KVM_GET_VCPU_MMAP_SIZE, NULL);
        if (mmap_size == -1) err(1, "KVM_GET_VCPU_MMAP_SIZE");
        if (mmap_size < (int32_t)sizeof(*run)) errx(1, "KVM_GET_VCPU_MMAP_SIZE unexpectedly small");
    
        // get memory-mapped file
        run = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, vcpufd, 0);
        if (!run) err(1, "mmap vcpu");
    
        // -- 6.2. Initialize vCPU registers --
        int sregs_err = ioctl(vcpufd, KVM_GET_SREGS, &sregs);
        if (sregs_err == -1) err(1, "KVM_GET_SREGS");
    
        // initialize segment registers to zero, via a read-modify-write of sregs
        sregs.cs.base = 0; sregs.cs.selector = 0;
        sregs.ds.base = 0; sregs.ds.selector = 0;
        sregs.es.base = 0; sregs.es.selector = 0;
        sregs.ss.base = 0; sregs.ss.selector = 0;
    
        sregs_err = ioctl(vcpufd, KVM_SET_SREGS, &sregs);
        if (sregs_err == -1) err(1, "KVM_SET_SREGS");
    
        // initialize pointer and flags
        struct kvm_regs regs = {
            .rip = 0,               // instruction pointer -> beginning of OS's code
            .rsp = RAM_SIZE,        // stack pointer -> top of RAM
            .rflags = 0x2,          // flags required by x86 architecture
        };
    
        int regs_err = ioctl(vcpufd, KVM_SET_REGS, &regs);
        if (regs_err == -1) err(1, "KVM_SET_REGS");
    
        // -- 7. Run the vCPU --
    
        sem_init(&sem_gfx, SEM_THREAD, 0);
    
        pthread_t t_hypercall;
        pthread_create(&t_hypercall, NULL, thread_hypercall, NULL);
    
        sem_wait(&sem_gfx);
    
        // -- 8. Manage gfx --
    
        memset(sprites, 0, sizeof(sprites));
    
        ctxt = gfx_create("Basic Example", width_gfx, height_gfx);
        if (!ctxt) err(1, "KVM_SET_REGS");
    
        bool done = false;
        while (!done) {
    
            gfx_background_update(ctxt);
    
            // - handle key -
            SDL_Keycode key = gfx_keypressed();
            if (key != 0) {
    
                if (key == SDLK_ESCAPE) {
    
                    done = true;
                }
                else {
    
                    // - write key code to guest -
                    last_key_pressed = (uint32_t)key;
    
                    // WARNING: no need to do it in emulation, but whatever...
                    op_keyboard_pv_send_code(shared_buf, last_key_pressed);
    
                    // - trigger interrupt -
                    struct kvm_irq_level irq_level;
                    irq_level.irq = 1; // 1 -> key interrupt
    
                    // drive signal -> up (1)
                    irq_level.level = 1;
                    if (ioctl(vmfd, KVM_IRQ_LINE, &irq_level) < 0)
                        err(1, "Error: driving irq signal up\n");
    
                    // drive signal -> down (0)
                    irq_level.level = 0;
                    if (ioctl(vmfd, KVM_IRQ_LINE, &irq_level) < 0)
                        err(1, "Error: driving irq signal down\n");
                }
            }
    
            // - handle sprites -
            for (uint8_t i = 0; i < MAX_SPRITES; i++) {
    
                if (sprites[i].toggle == 1) {
    
                    if (sprites[i].tex == NULL)
                        sprites[i].tex = gfx_sprite_create(ctxt, sprites[i].data, sprites[i].width, sprites[i].height);
    
                    gfx_sprite_render(ctxt, sprites[i].tex, sprites[i].x, sprites[i].y, sprites[i].width, sprites[i].height);
                }
            }
    
            gfx_present(ctxt);
        }
    
        // -- X. Finalize --
        pthread_join(t_hypercall, NULL);
    
        gfx_destroy(ctxt);
        sem_destroy(&sem_gfx);
    
        return 0;
    }