LekKit / RVVM

The RISC-V Virtual Machine
GNU General Public License v3.0
937 stars 65 forks source link

virtio support #149

Open ZLangJIT opened 1 month ago

ZLangJIT commented 1 month ago

hmm https://github.com/sysprog21/semu/pull/34 - implement virtio-gpu + utilize kernel virgl + host virglrenderer

Originally posted by @ZLangJIT in https://github.com/LekKit/RVVM/issues/144#issuecomment-2393013854

attached is a patch that introduces initial support for virtio ported from https://github.com/shengwen-tw/semu/tree/master (from https://github.com/sysprog21/semu/pull/34) to rvvm

currently compiles with minimal warnings


  ██▀███   ██▒   █▓ ██▒   █▓ ███▄ ▄███▓
 ▓██ ▒ ██▒▓██░   █▒▓██░   █▒▓██▒▀█▀ ██▒
 ▓██ ░▄█ ▒ ▓██  █▒░ ▓██  █▒░▓██    ▓██░
 ▒██▀▀█▄    ▒██ █░░  ▒██ █░░▒██    ▒██ 
 ░██▓ ▒██▒   ▒▀█░     ▒▀█░  ▒██▒   ░██▒
 ░ ▒▓ ░▒▓░   ░ █░     ░ █░  ░ ▒░   ░  ░
   ░▒ ░ ▒░   ░ ░░     ░ ░░  ░  ░      ░
   ░░   ░      ░░       ░░  ░      ░   
    ░           ░        ░         ░   
               ░        ░              

Detected OS: Linux
Detected CC: GCC 14.2.0
Target arch: arm64
Version:     RVVM 0.7-af04f3e-dirty

[CC] src/blk_io.c 
[CC] src/dlib.c 
[CC] src/elf_load.c 
[CC] src/fdtlib.c 
[CC] src/gd32.c 
[CC] src/hashmap.c 
[CC] src/main.c 
[CC] src/ringbuf.c 
[CC] src/riscv_cpu.c 
[CC] src/riscv_csr.c 
[CC] src/riscv_hart.c 
[CC] src/riscv_mmu.c 
[CC] src/riscv_priv.c 
[CC] src/rvtimer.c 
[CC] src/rvvm.c 
[CC] src/rvvm_isolation.c 
[CC] src/rvvm_user.c 
[CC] src/spinlock.c 
[CC] src/stacktrace.c 
[CC] src/threading.c 
[CC] src/utils.c 
[CC] src/vma_ops.c 
[CC] src/devices/ata.c 
[CC] src/devices/chardev_term.c 
[CC] src/devices/clint.c 
[CC] src/devices/eth-oc.c 
[CC] src/devices/framebuffer.c 
[CC] src/devices/gpio-sifive.c 
[CC] src/devices/gui_window.c 
[CC] src/devices/hid-keyboard.c 
[CC] src/devices/hid-mouse.c 
[CC] src/devices/i2c-hid.c 
[CC] src/devices/i2c-oc.c 
[CC] src/devices/mtd-physmap.c 
[CC] src/devices/ns16550a.c 
[CC] src/devices/nvme.c 
[CC] src/devices/pci-bus.c 
[CC] src/devices/pci-vfio.c 
[CC] src/devices/plic.c 
[CC] src/devices/ps2-altera.c 
[CC] src/devices/ps2-keyboard.c 
[CC] src/devices/ps2-mouse.c 
[CC] src/devices/rtc-ds1742.c 
[CC] src/devices/rtc-goldfish.c 
[CC] src/devices/rtl8169.c 
[CC] src/devices/syscon.c 
[CC] src/rvjit/rvjit.c 
[CC] src/rvjit/rvjit_emit.c 
[CC] src/devices/x11window_xlib.c 
[CC] src/devices/tap_user.c 
[CC] src/networking.c 
[CC] src/devices/virtio-blk.c 
[CC] src/devices/virtio-gpu.c 
[CC] src/devices/virtio-input.c 
[CC] src/devices/virtio-net.c 
[CC] src/cpu/riscv_interpreter.c 
[CC] src/cpu/riscv64_interpreter.c 
src/devices/virtio-net.c: In function 'virtio_net_try_rx':
src/devices/virtio-net.c:153:12: warning: ISO C90 forbids variable length array 'buffer_iovs' [-Wvla]
  153 |     struct iovec buffer_iovs[buffer_niovs];                               \
      |            ^~~~~
src/devices/virtio-net.c:187:13: note: in expansion of macro 'VNET_BUFFER_TO_IOV'
  187 |             VNET_BUFFER_TO_IOV(READ)                                           \
      |             ^~~~~~~~~~~~~~~~~~
src/devices/virtio-net.c:228:1: note: in expansion of macro 'VNET_GENERATE_QUEUE_HANDLER'
  228 | VNET_GENERATE_QUEUE_HANDLER(rx, read, VNET_QUEUE_RX, true)
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~
src/devices/virtio-net.c: In function 'virtio_net_try_tx':
src/devices/virtio-net.c:153:12: warning: ISO C90 forbids variable length array 'buffer_iovs' [-Wvla]
  153 |     struct iovec buffer_iovs[buffer_niovs];                               \
      |            ^~~~~
src/devices/virtio-net.c:187:13: note: in expansion of macro 'VNET_BUFFER_TO_IOV'
  187 |             VNET_BUFFER_TO_IOV(READ)                                           \
      |             ^~~~~~~~~~~~~~~~~~
src/devices/virtio-net.c:229:1: note: in expansion of macro 'VNET_GENERATE_QUEUE_HANDLER'
  229 | VNET_GENERATE_QUEUE_HANDLER(tx, write, VNET_QUEUE_TX, false)
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from src/devices/virtio-gpu.c:16:
src/devices/virtio-gpu.c: In function 'acquire_vgpu_resource_2d':
src/devices/virtio_list.h:6:29: warning: pointer of type 'void *' used in arithmetic [-Wpointer-arith]
    6 |     ((type *) ((void *) ptr - offsetof(type, member)))
      |                             ^
src/devices/virtio_list.h:8:39: note: in expansion of macro 'container_of'
    8 | #define list_entry(ptr, type, member) container_of(ptr, type, member)
      |                                       ^~~~~~~~~~~~
src/devices/virtio_list.h:11:5: note: in expansion of macro 'list_entry'
   11 |     list_entry((ptr)->next, type, member)
      |     ^~~~~~~~~~
src/devices/virtio_list.h:29:16: note: in expansion of macro 'list_first_entry'
   29 |     for (pos = list_first_entry(head, __typeof__(*pos), member); \
      |                ^~~~~~~~~~~~~~~~
src/devices/virtio-gpu.c:259:5: note: in expansion of macro 'list_for_each_entry'
  259 |     list_for_each_entry (res_2d, &vgpu_res_2d_list, list) {
      |     ^~~~~~~~~~~~~~~~~~~
src/devices/virtio_list.h:6:29: warning: pointer of type 'void *' used in arithmetic [-Wpointer-arith]
    6 |     ((type *) ((void *) ptr - offsetof(type, member)))
      |                             ^
src/devices/virtio_list.h:8:39: note: in expansion of macro 'container_of'
    8 | #define list_entry(ptr, type, member) container_of(ptr, type, member)
      |                                       ^~~~~~~~~~~~
src/devices/virtio_list.h:17:5: note: in expansion of macro 'list_entry'
   17 |     list_entry((pos)->member.next, typeof(*(pos)), member)
      |     ^~~~~~~~~~
src/devices/virtio_list.h:30:40: note: in expansion of macro 'list_next_entry'
   30 |          &pos->member != (head); pos = list_next_entry(pos, member))
      |                                        ^~~~~~~~~~~~~~~
src/devices/virtio-gpu.c:259:5: note: in expansion of macro 'list_for_each_entry'
  259 |     list_for_each_entry (res_2d, &vgpu_res_2d_list, list) {
      |     ^~~~~~~~~~~~~~~~~~~
src/devices/virtio-gpu.c: In function 'virtio_gpu_update_status':
src/devices/virtio-gpu.c:313:33: warning: invalid application of 'sizeof' to a void type [-Wpointer-arith]
  313 |     memset(vgpu->priv, 0, sizeof(*vgpu->priv));
      |                                 ^
src/devices/virtio_list.h:6:29: warning: pointer of type 'void *' used in arithmetic [-Wpointer-arith]
    6 |     ((type *) ((void *) ptr - offsetof(type, member)))
      |                             ^
src/devices/virtio_list.h:8:39: note: in expansion of macro 'container_of'
    8 | #define list_entry(ptr, type, member) container_of(ptr, type, member)
      |                                       ^~~~~~~~~~~~
src/devices/virtio-gpu.c:323:13: note: in expansion of macro 'list_entry'
  323 |             list_entry(curr, struct vgpu_resource_2d, list);
      |             ^~~~~~~~~~
src/devices/virtio-gpu.c: In function 'virtio_gpu_init':
src/devices/virtio-gpu.c:1403:23: warning: 'calloc' sizes specified with 'sizeof' in the earlier argument and not in the later argument [-Wcalloc-transposed-args]
 1403 |         calloc(sizeof(struct vgpu_scanout_info), VIRTIO_GPU_MAX_SCANOUTS);
      |                       ^~~~~~
src/devices/virtio-gpu.c:1403:23: note: earlier argument should specify number of elements, later size of each element
src/devices/virtio_list.h: At top level:
src/devices/virtio_list.h:74:13: warning: 'list_del_init' defined but not used [-Wunused-function]
   74 | static void list_del_init(struct list_head *entry)
      |             ^~~~~~~~~~~~~
src/devices/virtio_list.h:54:12: warning: 'list_is_last' defined but not used [-Wunused-function]
   54 | static int list_is_last(const struct list_head *list,
      |            ^~~~~~~~~~~~
In file included from src/devices/virtio-input.c:17:
src/devices/virtio_list.h:74:13: warning: 'list_del_init' defined but not used [-Wunused-function]
   74 | static void list_del_init(struct list_head *entry)
      |             ^~~~~~~~~~~~~
src/devices/virtio_list.h:54:12: warning: 'list_is_last' defined but not used [-Wunused-function]
   54 | static int list_is_last(const struct list_head *list,
      |            ^~~~~~~~~~~~
[LD] debug.linux.arm64/rvvm_arm64 
ZLangJIT commented 1 month ago

note however virtio is not hooked up to rvvm itself yet

also im not sure what the rvvm version of these would be as some exist but others dont

enum {
    RV_MEM_LB = 0b000,
    RV_MEM_LH = 0b001,
    RV_MEM_LW = 0b010,
    RV_MEM_LBU = 0b100,
    RV_MEM_LHU = 0b101,
    RV_MEM_SB = 0b000,
    RV_MEM_SH = 0b001,
    RV_MEM_SW = 0b010,
};
LekKit commented 1 month ago

Load/store memory instructions specifics should be ignored from device perspective.

RVVM MMIO API can be seen in src/rvvmlib.h, and is well documented.

ZLangJIT commented 1 month ago

for such specifics we have

void virtio_blk_read(rvvm_hart_t *vm,
                     virtio_blk_state_t *vblk,
                     uint32_t addr,
                     uint8_t width,
                     uint32_t *value)
{
    switch (width) {
    case RV_MEM_LW:
        if (!virtio_blk_reg_read(vblk, addr >> 2, value))
            riscv_trap(vm, TRAP_LOAD_FAULT, vm->registers[REGISTER_PC]);
        break;
    case RV_MEM_LBU:
    case RV_MEM_LB:
    case RV_MEM_LHU:
    case RV_MEM_LH:
        riscv_trap(vm, TRAP_LOAD_MISALIGN, vm->registers[REGISTER_PC]);
        return;
    default:
        riscv_trap(vm, TRAP_ILL_INSTR, 0);
        return;
    }
}

void virtio_blk_write(rvvm_hart_t *vm,
                      virtio_blk_state_t *vblk,
                      uint32_t addr,
                      uint8_t width,
                      uint32_t value)
{
    switch (width) {
    case RV_MEM_SW:
        if (!virtio_blk_reg_write(vblk, addr >> 2, value))
            riscv_trap(vm, TRAP_STORE_FAULT, vm->registers[REGISTER_PC]);
        break;
    case RV_MEM_SB:
    case RV_MEM_SH:
        riscv_trap(vm, TRAP_STORE_MISALIGN, vm->registers[REGISTER_PC]);
        return;
    default:
        riscv_trap(vm, TRAP_ILL_INSTR, 0);
        return;
    }
}
void virtio_gpu_read(rvvm_hart_t *vm,
                     virtio_gpu_state_t *vgpu,
                     uint32_t addr,
                     uint8_t width,
                     uint32_t *value)
{
    switch (width) {
    case RV_MEM_LW:
        if (!virtio_gpu_reg_read(vgpu, addr >> 2, value))
            riscv_trap(vm, TRAP_LOAD_FAULT, vm->registers[REGISTER_PC]);
        break;
    case RV_MEM_LBU:
    case RV_MEM_LB:
    case RV_MEM_LHU:
    case RV_MEM_LH:
        riscv_trap(vm, TRAP_LOAD_MISALIGN, vm->registers[REGISTER_PC]);
        return;
    default:
        riscv_trap(vm, TRAP_ILL_INSTR, 0);
        return;
    }
}

void virtio_gpu_write(rvvm_hart_t *vm,
                      virtio_gpu_state_t *vgpu,
                      uint32_t addr,
                      uint8_t width,
                      uint32_t value)
{
    switch (width) {
    case RV_MEM_SW:
        if (!virtio_gpu_reg_write(vgpu, addr >> 2, value))
            riscv_trap(vm, TRAP_STORE_FAULT, vm->registers[REGISTER_PC]);
        break;
    case RV_MEM_SB:
    case RV_MEM_SH:
        riscv_trap(vm, TRAP_STORE_MISALIGN, vm->registers[REGISTER_PC]);
        return;
    default:
        riscv_trap(vm, TRAP_ILL_INSTR, 0);
        return;
    }
}
void virtio_input_read(rvvm_hart_t *vm,
                       virtio_input_state_t *vinput,
                       uint32_t addr,
                       uint8_t width,
                       uint32_t *value)
{
    pthread_mutex_lock(&virtio_input_mutex);

    /* XXX: 4-byte alignment (i.e., addr >> 2) is removed due to the per
    byte accessing */
    switch (width) {
    case RV_MEM_LW:
        if (!virtio_input_reg_read(vinput, addr, value, 4))
            riscv_trap(vm, TRAP_LOAD_FAULT, vm->registers[REGISTER_PC]);
        break;
    case RV_MEM_LBU:
    case RV_MEM_LB:
    case RV_MEM_LHU:
    case RV_MEM_LH:
        /*FIXME: virtio-input driver need to access device config register per
         * byte. the following code that derived from other virtio devices'
         * implementation will cause kernel panic */
        // riscv_trap(vm, TRAP_LOAD_MISALIGN, vm->registers[REGISTER_PC]);
#if 1
        // printf("read addr: 0x%x, width: %d\n", addr, width);
        if (!virtio_input_reg_read(vinput, addr, value, 1))
            riscv_trap(vm, TRAP_LOAD_FAULT, vm->registers[REGISTER_PC]);
#endif
        break;
    default:
        riscv_trap(vm, TRAP_ILL_INSTR, 0);
        break;
    }

    pthread_mutex_unlock(&virtio_input_mutex);
}

void virtio_input_write(rvvm_hart_t *vm,
                        virtio_input_state_t *vinput,
                        uint32_t addr,
                        uint8_t width,
                        uint32_t value)
{
    pthread_mutex_lock(&virtio_input_mutex);

    /* XXX: 4-byte alignment (i.e., addr >> 2) is removed due to the per
    byte accessing */
    switch (width) {
    case RV_MEM_SW:
        if (!virtio_input_reg_write(vinput, addr, value))
            riscv_trap(vm, TRAP_STORE_FAULT, vm->registers[REGISTER_PC]);
        break;
    case RV_MEM_SB:
    case RV_MEM_SH:
        /* FIXME: virtio-input driver need to access device config register per
         * byte. the following code that derived from other virtio devices'
         * implementation will cause kernel panic */
        // riscv_trap(vm, TRAP_STORE_MISALIGN, vm->registers[REGISTER_PC]);
#if 1
        // printf("read addr: 0x%x, width: %d\n", addr, width);
        if (!virtio_input_reg_write(vinput, addr, value))
            riscv_trap(vm, TRAP_STORE_FAULT, vm->registers[REGISTER_PC]);
#endif
        break;
    default:
        riscv_trap(vm, TRAP_ILL_INSTR, 0);
        break;
    }

    pthread_mutex_unlock(&virtio_input_mutex);
}
void virtio_net_read(rvvm_hart_t *vm,
                     virtio_net_state_t *vnet,
                     uint32_t addr,
                     uint8_t width,
                     uint32_t *value)
{
    switch (width) {
    case RV_MEM_LW:
        if (!virtio_net_reg_read(vnet, addr >> 2, value))
            riscv_trap(vm, TRAP_LOAD_FAULT, vm->registers[REGISTER_PC]);
        break;
    case RV_MEM_LBU:
    case RV_MEM_LB:
    case RV_MEM_LHU:
    case RV_MEM_LH:
        riscv_trap(vm, TRAP_LOAD_MISALIGN, vm->registers[REGISTER_PC]);
        return;
    default:
        riscv_trap(vm, TRAP_ILL_INSTR, 0);
        return;
    }
}

void virtio_net_write(rvvm_hart_t *vm,
                      virtio_net_state_t *vnet,
                      uint32_t addr,
                      uint8_t width,
                      uint32_t value)
{
    switch (width) {
    case RV_MEM_SW:
        if (!virtio_net_reg_write(vnet, addr >> 2, value))
            riscv_trap(vm, TRAP_STORE_FAULT, vm->registers[REGISTER_PC]);
        break;
    case RV_MEM_SB:
    case RV_MEM_SH:
        riscv_trap(vm, TRAP_STORE_MISALIGN, vm->registers[REGISTER_PC]);
        return;
    default:
        riscv_trap(vm, TRAP_ILL_INSTR, 0);
        return;
    }
}
LekKit commented 1 month ago

Please see the RVVM API and only then try to adapt anything.

Devices should not touch CPU specifics directly, like load/store instruction opcodes, registers. Devices should not trap the CPU manually either, it is done by the MMU subsystem.

If semu project did it that way it's silly and should not be replicated here.

ZLangJIT commented 1 month ago

Please see the RVVM API and only then try to adapt anything.

Devices should not touch CPU specifics directly, like load/store instruction opcodes, registers. Devices should not trap the CPU manually either, it is done by the MMU subsystem.

If semu project did it that way it's silly and should not be replicated here.

yea, but i will need to trace exactly how such api's get called so we can then decide how best to adapt each one

ZLangJIT commented 1 month ago

in main.c of semu/bb98469f47b09324561970e91c49a58ab038a176 we currently have these references of virtio and virgl

#if SEMU_HAS(VIRTIONET)
static void emu_update_vnet_interrupts(vm_t *vm)
{
    emu_state_t *data = PRIV(vm->hart[0]);
    if (data->vnet.InterruptStatus)
        data->plic.active |= IRQ_VNET_BIT;
    else
        data->plic.active &= ~IRQ_VNET_BIT;
    plic_update_interrupts(vm, &data->plic);
}
#endif

#if SEMU_HAS(VIRTIOBLK)
static void emu_update_vblk_interrupts(vm_t *vm)
{
    emu_state_t *data = PRIV(vm->hart[0]);
    if (data->vblk.InterruptStatus)
        data->plic.active |= IRQ_VBLK_BIT;
    else
        data->plic.active &= ~IRQ_VBLK_BIT;
    plic_update_interrupts(vm, &data->plic);
}
#endif

#if SEMU_HAS(VIRTIOGPU)
static void emu_update_vgpu_interrupts(vm_t *vm)
{
    emu_state_t *data = PRIV(vm->hart[0]);
    if (data->vgpu.InterruptStatus)
        data->plic.active |= IRQ_VGPU_BIT;
    else
        data->plic.active &= ~IRQ_VGPU_BIT;
    plic_update_interrupts(vm, &data->plic);
}
#endif

#if SEMU_HAS(VIRTIOINPUT)
static void emu_update_vinput_keyboard_interrupts(vm_t *vm)
{
    emu_state_t *data = PRIV(vm->hart[0]);
    if (data->vkeyboard.InterruptStatus)
        data->plic.active |= IRQ_VINPUT_KEYBOARD_BIT;
    else
        data->plic.active &= ~IRQ_VINPUT_KEYBOARD_BIT;
    plic_update_interrupts(vm, &data->plic);
}

static void emu_update_vinput_mouse_interrupts(vm_t *vm)
{
    emu_state_t *data = PRIV(vm->hart[0]);
    if (data->vmouse.InterruptStatus)
        data->plic.active |= IRQ_VINPUT_MOUSE_BIT;
    else
        data->plic.active &= ~IRQ_VINPUT_MOUSE_BIT;
    plic_update_interrupts(vm, &data->plic);
}
#endif

// ...

static void mem_load(hart_t *hart,
                     uint32_t addr,
                     uint8_t width,
                     uint32_t *value)
{
    emu_state_t *data = PRIV(hart);
    /* RAM at 0x00000000 + RAM_SIZE */
    if (addr < RAM_SIZE) {
        ram_read(hart, data->ram, addr, width, value);
        return;
    }

    if ((addr >> 28) == 0xF) { /* MMIO at 0xF_______ */
        /* 256 regions of 1MiB */
        switch ((addr >> 20) & MASK(8)) {
        case 0x0:
        case 0x2: /* PLIC (0 - 0x3F) */
            plic_read(hart, &data->plic, addr & 0x3FFFFFF, width, value);
            plic_update_interrupts(hart->vm, &data->plic);
            return;
        case 0x40: /* UART */
            u8250_read(hart, &data->uart, addr & 0xFFFFF, width, value);
            emu_update_uart_interrupts(hart->vm);
            return;
#if SEMU_HAS(VIRTIONET)
        case 0x41: /* virtio-net */
            virtio_net_read(hart, &data->vnet, addr & 0xFFFFF, width, value);
            emu_update_vnet_interrupts(hart->vm);
            return;
#endif
#if SEMU_HAS(VIRTIOBLK)
        case 0x42: /* virtio-blk */
            virtio_blk_read(hart, &data->vblk, addr & 0xFFFFF, width, value);
            emu_update_vblk_interrupts(hart->vm);
            return;
#endif
        case 0x43: /* clint */
            clint_read(hart, &data->clint, addr & 0xFFFFF, width, value);
            clint_update_interrupts(hart, &data->clint);
            return;
#if SEMU_HAS(VIRTIOGPU)
        case 0x44: /* virtio-gpu */
            virtio_gpu_read(hart, &data->vgpu, addr & 0xFFFFF, width, value);
            emu_update_vgpu_interrupts(hart->vm);
            return;
#endif
#if SEMU_HAS(VIRTIOINPUT)
        case 0x45: /* virtio-input keyboard */
            virtio_input_read(hart, &data->vkeyboard, addr & 0xFFFFF, width,
                              value);
            emu_update_vinput_keyboard_interrupts(hart->vm);
            return;
        case 0x46: /* virtio-input mouse */
            virtio_input_read(hart, &data->vmouse, addr & 0xFFFFF, width,
                              value);
            emu_update_vinput_mouse_interrupts(hart->vm);
            return;
#endif
        }
    }
    vm_set_exception(hart, RV_EXC_LOAD_FAULT, hart->exc_val);
}

static void mem_store(hart_t *hart,
                      uint32_t addr,
                      uint8_t width,
                      uint32_t value)
{
    emu_state_t *data = PRIV(hart);
    /* RAM at 0x00000000 + RAM_SIZE */
    if (addr < RAM_SIZE) {
        ram_write(hart, data->ram, addr, width, value);
        return;
    }

    if ((addr >> 28) == 0xF) { /* MMIO at 0xF_______ */
        /* 256 regions of 1MiB */
        switch ((addr >> 20) & MASK(8)) {
        case 0x0:
        case 0x2: /* PLIC (0 - 0x3F) */
            plic_write(hart, &data->plic, addr & 0x3FFFFFF, width, value);
            plic_update_interrupts(hart->vm, &data->plic);
            return;
        case 0x40: /* UART */
            u8250_write(hart, &data->uart, addr & 0xFFFFF, width, value);
            emu_update_uart_interrupts(hart->vm);
            return;
#if SEMU_HAS(VIRTIONET)
        case 0x41: /* virtio-net */
            virtio_net_write(hart, &data->vnet, addr & 0xFFFFF, width, value);
            emu_update_vnet_interrupts(hart->vm);
            return;
#endif
#if SEMU_HAS(VIRTIOBLK)
        case 0x42: /* virtio-blk */
            virtio_blk_write(hart, &data->vblk, addr & 0xFFFFF, width, value);
            emu_update_vblk_interrupts(hart->vm);
            return;
#endif
        case 0x43: /* clint */
            clint_write(hart, &data->clint, addr & 0xFFFFF, width, value);
            clint_update_interrupts(hart, &data->clint);
            return;
#if SEMU_HAS(VIRTIOGPU)
        case 0x44: /* virtio-gpu */
            virtio_gpu_write(hart, &data->vgpu, addr & 0xFFFFF, width, value);
            emu_update_vgpu_interrupts(hart->vm);
            return;
#endif
#if SEMU_HAS(VIRTIOINPUT)
        case 0x45: /* virtio-input */
            virtio_input_write(hart, &data->vkeyboard, addr & 0xFFFFF, width,
                               value);
            emu_update_vinput_keyboard_interrupts(hart->vm);
            return;
        case 0x46: /* virtio-input mouse */
            virtio_input_write(hart, &data->vmouse, addr & 0xFFFFF, width,
                               value);
            emu_update_vinput_mouse_interrupts(hart->vm);
            return;
#endif
        }
    }
    vm_set_exception(hart, RV_EXC_STORE_FAULT, hart->exc_val);
}

// ...

static int semu_start(int argc, char **argv)
{
    char *kernel_file;
    char *dtb_file;
    char *initrd_file;
    char *disk_file;
    int hart_count = 1;
    handle_options(argc, argv, &kernel_file, &dtb_file, &initrd_file,
                   &disk_file, &hart_count);

    /* Initialize the emulator */
    emu_state_t emu;
    memset(&emu, 0, sizeof(emu));
    semu_timer_init(&emu.clint.mtime, CLOCK_FREQ);

    /* Set up RAM */
    emu.ram = mmap(NULL, RAM_SIZE, PROT_READ | PROT_WRITE,
                   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (emu.ram == MAP_FAILED) {
        fprintf(stderr, "Could not map RAM\n");
        return 2;
    }
    assert(!(((uintptr_t) emu.ram) & 0b11));

    /* *-----------------------------------------*
     * |              Memory layout              |
     * *----------------*----------------*-------*
     * |  kernel image  |  initrd image  |  dtb  |
     * *----------------*----------------*-------*
     */
    char *ram_loc = (char *) emu.ram;
    /* Load Linux kernel image */
    map_file(&ram_loc, kernel_file);
    /* Load at last 1 MiB to prevent kernel from overwriting it */
    uint32_t dtb_addr = RAM_SIZE - DTB_SIZE; /* Device tree */
    ram_loc = ((char *) emu.ram) + dtb_addr;
    map_file(&ram_loc, dtb_file);
    /* Load optional initrd image at last 8 MiB before the dtb region to
     * prevent kernel from overwritting it
     */
    if (initrd_file) {
        uint32_t initrd_addr = dtb_addr - INITRD_SIZE; /* Init RAM disk */
        ram_loc = ((char *) emu.ram) + initrd_addr;
        map_file(&ram_loc, initrd_file);
    }

    /* Hook for unmapping files */
    atexit(unmap_files);

    /* Set up RISC-V harts */
    vm_t vm = {
        .n_hart = hart_count,
        .hart = malloc(sizeof(hart_t *) * vm.n_hart),
    };
    for (uint32_t i = 0; i < vm.n_hart; i++) {
        hart_t *newhart = malloc(sizeof(hart_t));
        INIT_HART(newhart, &emu, i);
        newhart->x_regs[RV_R_A0] = i;
        newhart->x_regs[RV_R_A1] = dtb_addr;
        if (i == 0)
            newhart->hsm_status = SBI_HSM_STATE_STARTED;

        newhart->vm = &vm;
        vm.hart[i] = newhart;
    }

    /* Set up peripherals */
    emu.uart.in_fd = 0, emu.uart.out_fd = 1;
    capture_keyboard_input(); /* set up uart */
#if SEMU_HAS(VIRTIONET)
    if (!virtio_net_init(&(emu.vnet)))
        fprintf(stderr, "No virtio-net functioned\n");
    emu.vnet.ram = emu.ram;
#endif
#if SEMU_HAS(VIRTIOBLK)
    emu.vblk.ram = emu.ram;
    emu.disk = virtio_blk_init(&(emu.vblk), disk_file);
#endif
#if SEMU_HAS(VIRTIOINPUT)
    emu.vkeyboard.ram = emu.ram;
    virtio_input_init(&(emu.vkeyboard));

    emu.vmouse.ram = emu.ram;
    virtio_input_init(&(emu.vmouse));
#endif
#if SEMU_HAS(VIRTIOGPU)
    semu_virgl_init();

    emu.vgpu.ram = emu.ram;
    virtio_gpu_init(&(emu.vgpu));
    virtio_gpu_add_scanout(&(emu.vgpu), 1024, 768);
    window_init();
#endif

    /* Emulate */
    uint32_t peripheral_update_ctr = 0;
    while (!emu.stopped) {
        for (uint32_t i = 0; i < vm.n_hart; i++) {
            if (peripheral_update_ctr-- == 0) {
                peripheral_update_ctr = 64;

                u8250_check_ready(&emu.uart);
                if (emu.uart.in_ready)
                    emu_update_uart_interrupts(&vm);

#if SEMU_HAS(VIRTIONET)
                virtio_net_refresh_queue(&emu.vnet);
                if (emu.vnet.InterruptStatus)
                    emu_update_vnet_interrupts(&vm);
#endif

#if SEMU_HAS(VIRTIOBLK)
                if (emu.vblk.InterruptStatus)
                    emu_update_vblk_interrupts(&vm);
#endif

#if SEMU_HAS(VIRTIOGPU)
                if (emu.vgpu.InterruptStatus)
                    emu_update_vgpu_interrupts(&vm);
#endif

#if SEMU_HAS(VIRTIOINPUT)
                if (emu.vkeyboard.InterruptStatus)
                    emu_update_vinput_keyboard_interrupts(&vm);

                if (emu.vmouse.InterruptStatus)
                    emu_update_vinput_mouse_interrupts(&vm);
#endif
            }

            emu_update_timer_interrupt(vm.hart[i]);

            vm_step(vm.hart[i]);
            if (likely(!vm.hart[i]->error))
                continue;

            if (vm.hart[i]->error == ERR_EXCEPTION &&
                vm.hart[i]->exc_cause == RV_EXC_ECALL_S) {
                handle_sbi_ecall(vm.hart[i]);
                continue;
            }

            if (vm.hart[i]->error == ERR_EXCEPTION) {
                hart_trap(vm.hart[i]);
                continue;
            }

            vm_error_report(vm.hart[i]);
            return 2;
        }
    }

    /* unreachable */
    return 0;
}

int main(int argc, char **argv)
{
    return semu_start(argc, argv);
}
ZLangJIT commented 1 month ago
ZLangJIT commented 1 month ago

ok i just finished getting RVVM and its deps building for android (again) in the correct order

previously when building i got format nor supported when linking rvvm and friends

so i had to rely on my build_root utility (also add meson support to it and fix some bugs in it) to get it all to correctly compile and install

additionally i had to add install steps and find*.cmake scripts to rvvm in order to support finding rvvm deps (one can set RVVM__ROOTFS variable when building to change the location at which rvvm looks for deps if they are not installed to /) and installation of rvvm via cmake --install path/to/rvvm/build_dir

additionally i added a remote shell (tmate) to my build.yaml (currently commented out) for attempting to debug gradle cmake build failures

ZLangJIT commented 1 month ago

https://github.com/onnokort/semu-c64/commit/21dc47de085fb4dbf55d78de42f9e7927122a8f5 seems to be a very simple example of how to add a virtio-blk device (implementation) to semu