陳禹丞
Virtio is an open standard and framework, which defines virtualization-friendly abstarction for drivers and hardware. It is primarily used in virtualized environments to efficiently virtualize I/O devices, including network adapters, disk contollers, etc.
According to §1.4 in Virtio specification, all in-memory structures are assumed to be without additional padding. That is, all structures shall be enforced with GNU extension __attribute__((packed))
.
The mechanism for bulk data transport on Virtio devices is called a virtqueue. Each device can have zero or more virtqueues. For example, a network device can has two virtqueues for transmitting and receiving data, respectivly.
There are two types of virtqueues:
Each split virtqueue consistes of three parts:
VIRTQ_DEC_F_INDIRECT
feature. Therefore, this note will not cover it.VRING_DESC_F_WRITE
flag is set, the buffer it points to is write-only to the device and read-only otherwise.used_event
shall be neglected if VIRTIO_F_EVENT_IDX
not negotiated.avail_event
shall be neglected if VIRTIO_F_EVENT_IDX
not negotiated.In semu, all memory access are performed using mem_store
、mem_load
and mem_fetch
. Unlike rv32emu, which distinguishes between different access length and provides variation like mmu_write_w/s/b
for different operations.
Additionally, semu does not modularize virtual/physical address translation into a separate function, resulting in huge mem_store
and mem_load
function. In contrast, rv32emu does have mmu_translate
function and also inspects whether the address belongs to the PLIC or UART region within the MMIO_READ
/MMIO_WRITE
function-like macros.
Note that, in semu's implementation of virtio registers, the RV_EXC_LOAD_MISALIGN
exception is raised if the access width is not 4.
Also, ram
in semu is a raw pointer to uint32_t array, but in rv32emu is a pointer to memory_t
:
typedef struct {
uint8_t *membase;
uint64_t mem_size;
} memory_t;
In semu, plic and uart I/O accept only word and byte instructions respectively, and raise RV_EXC_LOAD_MISALIGN
exception if trespassed. However, rv32emu does not verify whether the accessing instruction is word or byte instruction.
void plic_read(hart_t *vm,
plic_state_t *plic,
uint32_t addr,
uint8_t width,
uint32_t *value)
{
switch (width) {
case RV_MEM_LW:
if (!plic_reg_read(plic, addr >> 2, value))
vm_set_exception(vm, RV_EXC_LOAD_FAULT, vm->exc_val);
break;
case RV_MEM_LBU:
case RV_MEM_LB:
case RV_MEM_LHU:
case RV_MEM_LH:
vm_set_exception(vm, RV_EXC_LOAD_MISALIGN, vm->exc_val);
return;
default:
vm_set_exception(vm, RV_EXC_ILLEGAL_INSN, 0);
return;
}
}
Virtio-blk device has been successfully migrated into rv32emu. Currently cleaning up the code.
[ 1.700327] virtio_blk virtio0: 1/0/0 default/read/poll queues
[ 1.702067] virtio_blk virtio0: [vda] 524288 512-byte logical blocks (268 MB/256 MiB)
Currently, virtio-net device in semu does not function properly, and is not covered in this note.
Linking of rv32emu with -O0
flag fails with following message:
$ git diff Makefile
diff --git a/Makefile b/Makefile
index 9921389..ee0e244 100644
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,7 @@ BIN := $(OUT)/rv32emu
CONFIG_FILE := $(OUT)/.config
-include $(CONFIG_FILE)
-CFLAGS = -std=gnu99 -O2 -Wall -Wextra -Werror
+CFLAGS = -std=gnu99 -O0 -Wall -Wextra -Werror
CFLAGS += -Wno-unused-label
CFLAGS += -include src/common.h -Isrc/
$ cc --version
cc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ make BUILD_SYSTEM=1
...
/usr/bin/ld: /tmp/ccQKnLSJ.ltrans0.ltrans.o: in function `fdt_del_node':
<artificial>:(.text+0x25fae): undefined reference to `fdt_node_end_offset_'
collect2: error: ld returned 1 exit status
make: *** [Makefile:314: build/rv32emu] Error 1
See if the latest master
branch remains the above issue. If so, create an issue on GitHub.
otteryc After discussing the linking process and referencing c-linker-loader, we found that Link-Time Optimization (LTO) was the culprit. GCC worked fine after adding the
-fno-lto
flag. I am not quite sure whether it should be reported as an issue on GitHub.
However, clang-18 works perfectly:
$ CC=clang-18 make BUILD_SYSTEM=1
...
CC build/dtc/libfdt/fdt_rw.o
CC build/main.o
CC build/devices/plic.o
CC build/devices/uart.o
LD build/rv32emu
$ echo $?
0
The code from semu accesses the available ring and the used ring by manually offseting, which actually makes the code difficult to read. For instance:
uint32_t vq_used_addr =
queue->QueueUsed + 1 + (new_used % queue->QueueNum) * 2;
ram[vq_used_addr] = buffer_idx; /* virtq_used_elem.id (le32) */
ram[vq_used_addr + 1] = len; /* virtq_used_elem.len (le32) */
queue->last_avail++;
new_used++;