From 924baede9e84e0c33e040d61566001bafe89b8ae Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 18 Oct 2026 04:42:13 +0200 Subject: [PATCH] =?UTF-8?q?fix(adapter-riscv):=20fix=20VirtIO=20block=20dr?= =?UTF-8?q?iver=20=E2=80=94=20scan=20all=20MMIO=20slots,=20handle=20legacy?= =?UTF-8?q?=20mode,=20fix=20virtqueue=20layout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VirtIO now properly scans 8 MMIO slots, detects legacy vs modern mode, uses correct virtqueue page alignment for legacy, and reads sectors. Fixed disk image script to force FAT16 (mformat was creating FAT32). init.elf successfully loaded from FAT16 disk via VirtIO on QEMU. --- crates/adapter-riscv/src/hw/virtio_blk.rs | 201 +++++++++++++--------- scripts/make-disk-image.sh | 133 ++++---------- 2 files changed, 158 insertions(+), 176 deletions(-) diff --git a/crates/adapter-riscv/src/hw/virtio_blk.rs b/crates/adapter-riscv/src/hw/virtio_blk.rs index ecf3e8c..6f0558f 100644 --- a/crates/adapter-riscv/src/hw/virtio_blk.rs +++ b/crates/adapter-riscv/src/hw/virtio_blk.rs @@ -1,6 +1,8 @@ -const VIRTIO_MMIO_BASE: usize = 0x10001000; const VIRTIO_MAGIC: u32 = 0x74726976; const VIRTIO_BLK_DEVICE_ID: u32 = 2; +const VIRTIO_MMIO_FIRST: usize = 0x10001000; +const VIRTIO_MMIO_STRIDE: usize = 0x1000; +const VIRTIO_MMIO_SLOTS: usize = 8; const VIRTIO_STATUS_ACK: u32 = 1; const VIRTIO_STATUS_DRIVER: u32 = 2; @@ -9,8 +11,20 @@ const VIRTIO_STATUS_DRIVER_OK: u32 = 4; const QUEUE_SIZE: usize = 8; const SECTOR_SIZE: usize = 512; +const PAGE_SIZE: usize = 4096; -#[repr(C, align(16))] +// Legacy VirtIO layout: used ring must start at page boundary after avail ring. +// descs: 8 * 16 = 128 bytes, avail: 22 bytes → total 150 → pad to 4096. +#[repr(C, align(4096))] +struct VirtqMem { + descs: [VirtqDesc; QUEUE_SIZE], + avail: VirtqAvail, + _pad: [u8; 3946], + used: VirtqUsed, +} + +#[repr(C)] +#[derive(Clone, Copy)] struct VirtqDesc { addr: u64, len: u32, @@ -18,14 +32,15 @@ struct VirtqDesc { next: u16, } -#[repr(C, align(2))] +#[repr(C)] struct VirtqAvail { flags: u16, idx: u16, ring: [u16; QUEUE_SIZE], + used_event: u16, } -#[repr(C, align(4))] +#[repr(C)] struct VirtqUsed { flags: u16, idx: u16, @@ -33,6 +48,7 @@ struct VirtqUsed { } #[repr(C)] +#[derive(Clone, Copy)] struct VirtqUsedElem { id: u32, len: u32, @@ -45,16 +61,14 @@ struct VirtioBlkReqHeader { sector: u64, } -static mut DESCS: [VirtqDesc; QUEUE_SIZE] = [const { - VirtqDesc { addr: 0, len: 0, flags: 0, next: 0 } -}; QUEUE_SIZE]; - -static mut AVAIL: VirtqAvail = VirtqAvail { - flags: 0, idx: 0, ring: [0; QUEUE_SIZE], -}; - -static mut USED: VirtqUsed = VirtqUsed { - flags: 0, idx: 0, ring: [const { VirtqUsedElem { id: 0, len: 0 } }; QUEUE_SIZE], +static mut VQUEUE: VirtqMem = VirtqMem { + descs: [const { VirtqDesc { addr: 0, len: 0, flags: 0, next: 0 } }; QUEUE_SIZE], + avail: VirtqAvail { flags: 0, idx: 0, ring: [0; QUEUE_SIZE], used_event: 0 }, + _pad: [0; 3946], + used: VirtqUsed { + flags: 0, idx: 0, + ring: [const { VirtqUsedElem { id: 0, len: 0 } }; QUEUE_SIZE], + }, }; static mut REQ_HEADER: VirtioBlkReqHeader = VirtioBlkReqHeader { @@ -63,108 +77,137 @@ static mut REQ_HEADER: VirtioBlkReqHeader = VirtioBlkReqHeader { static mut REQ_STATUS: u8 = 0xFF; -fn mmio_read(offset: usize) -> u32 { - unsafe { core::ptr::read_volatile((VIRTIO_MMIO_BASE + offset) as *const u32) } +fn mmio_read(base: usize, offset: usize) -> u32 { + unsafe { core::ptr::read_volatile((base + offset) as *const u32) } } -fn mmio_write(offset: usize, val: u32) { - unsafe { core::ptr::write_volatile((VIRTIO_MMIO_BASE + offset) as *mut u32, val) } +fn mmio_write(base: usize, offset: usize, val: u32) { + unsafe { core::ptr::write_volatile((base + offset) as *mut u32, val) } } pub struct VirtioBlk { - initialized: bool, + base: usize, + legacy: bool, } impl VirtioBlk { pub fn probe() -> Option { - let magic = mmio_read(0x000); - let device_id = mmio_read(0x008); - - if magic != VIRTIO_MAGIC || device_id != VIRTIO_BLK_DEVICE_ID { - return None; + for slot in 0..VIRTIO_MMIO_SLOTS { + let base = VIRTIO_MMIO_FIRST + slot * VIRTIO_MMIO_STRIDE; + let magic = mmio_read(base, 0x000); + let device_id = mmio_read(base, 0x008); + if magic == VIRTIO_MAGIC && device_id == VIRTIO_BLK_DEVICE_ID { + return Self::init_at(base); + } } + None + } + + fn init_at(base: usize) -> Option { + let version = mmio_read(base, 0x004); + let legacy = version == 1; // Reset - mmio_write(0x070, 0); + mmio_write(base, 0x070, 0); + while mmio_read(base, 0x070) != 0 {} - // Acknowledge - mmio_write(0x070, VIRTIO_STATUS_ACK); - mmio_write(0x070, VIRTIO_STATUS_ACK | VIRTIO_STATUS_DRIVER); + // Acknowledge + Driver + mmio_write(base, 0x070, VIRTIO_STATUS_ACK); + mmio_write(base, 0x070, mmio_read(base, 0x070) | VIRTIO_STATUS_DRIVER); - // No feature negotiation needed for basic read - mmio_write(0x024, 0); // DriverFeaturesSel = 0 - mmio_write(0x020, 0); // DriverFeatures = 0 - mmio_write(0x070, VIRTIO_STATUS_ACK | VIRTIO_STATUS_DRIVER | VIRTIO_STATUS_FEATURES_OK); - - let status = mmio_read(0x070); - if status & VIRTIO_STATUS_FEATURES_OK == 0 { - return None; + if legacy { + // Legacy: set guest page size + mmio_write(base, 0x028, PAGE_SIZE as u32); } - // Set up queue 0 - mmio_write(0x030, 0); // QueueSel = 0 - mmio_write(0x038, QUEUE_SIZE as u32); // QueueNum + // Feature negotiation + mmio_write(base, 0x014, 0); + let _features = mmio_read(base, 0x010); + mmio_write(base, 0x024, 0); + mmio_write(base, 0x020, 0); + + if !legacy { + mmio_write(base, 0x070, mmio_read(base, 0x070) | VIRTIO_STATUS_FEATURES_OK); + if mmio_read(base, 0x070) & VIRTIO_STATUS_FEATURES_OK == 0 { + return None; + } + } + + // Queue setup + mmio_write(base, 0x030, 0); + let queue_max = mmio_read(base, 0x034); + if queue_max == 0 { return None; } + let queue_num = (QUEUE_SIZE as u32).min(queue_max); + mmio_write(base, 0x038, queue_num); unsafe { - let desc_addr = core::ptr::addr_of!(DESCS) as u64; - let avail_addr = core::ptr::addr_of!(AVAIL) as u64; - let used_addr = core::ptr::addr_of!(USED) as u64; + let vq_addr = core::ptr::addr_of!(VQUEUE) as usize; - mmio_write(0x080, desc_addr as u32); - mmio_write(0x084, (desc_addr >> 32) as u32); - mmio_write(0x090, avail_addr as u32); - mmio_write(0x094, (avail_addr >> 32) as u32); - mmio_write(0x0A0, used_addr as u32); - mmio_write(0x0A4, (used_addr >> 32) as u32); + if legacy { + mmio_write(base, 0x040, (vq_addr / PAGE_SIZE) as u32); + } else { + let desc_addr = vq_addr as u64; + let avail_addr = (vq_addr + core::mem::offset_of!(VirtqMem, avail)) as u64; + let used_addr = (vq_addr + core::mem::offset_of!(VirtqMem, used)) as u64; + + mmio_write(base, 0x080, desc_addr as u32); + mmio_write(base, 0x084, (desc_addr >> 32) as u32); + mmio_write(base, 0x090, avail_addr as u32); + mmio_write(base, 0x094, (avail_addr >> 32) as u32); + mmio_write(base, 0x0A0, used_addr as u32); + mmio_write(base, 0x0A4, (used_addr >> 32) as u32); + mmio_write(base, 0x044, 1); + } } - mmio_write(0x044, 1); // QueueReady - mmio_write(0x070, VIRTIO_STATUS_ACK | VIRTIO_STATUS_DRIVER | VIRTIO_STATUS_FEATURES_OK | VIRTIO_STATUS_DRIVER_OK); + mmio_write(base, 0x070, mmio_read(base, 0x070) | VIRTIO_STATUS_DRIVER_OK); - Some(Self { initialized: true }) + Some(Self { base, legacy }) } pub fn read_sector(&mut self, sector: u32, buf: &mut [u8; SECTOR_SIZE]) -> bool { - if !self.initialized { return false; } - unsafe { - REQ_HEADER.req_type = 0; // read + REQ_HEADER.req_type = 0; REQ_HEADER.reserved = 0; REQ_HEADER.sector = sector as u64; REQ_STATUS = 0xFF; - // Descriptor 0: header (device reads) - DESCS[0].addr = core::ptr::addr_of!(REQ_HEADER) as u64; - DESCS[0].len = 16; - DESCS[0].flags = 1; // NEXT - DESCS[0].next = 1; + VQUEUE.descs[0] = VirtqDesc { + addr: core::ptr::addr_of!(REQ_HEADER) as u64, + len: 16, + flags: 1, // NEXT + next: 1, + }; + VQUEUE.descs[1] = VirtqDesc { + addr: buf.as_ptr() as u64, + len: SECTOR_SIZE as u32, + flags: 2 | 1, // WRITE | NEXT + next: 2, + }; + VQUEUE.descs[2] = VirtqDesc { + addr: core::ptr::addr_of!(REQ_STATUS) as u64, + len: 1, + flags: 2, // WRITE + next: 0, + }; - // Descriptor 1: data buffer (device writes) - DESCS[1].addr = buf.as_ptr() as u64; - DESCS[1].len = SECTOR_SIZE as u32; - DESCS[1].flags = 2 | 1; // WRITE | NEXT - DESCS[1].next = 2; - - // Descriptor 2: status (device writes) - DESCS[2].addr = core::ptr::addr_of!(REQ_STATUS) as u64; - DESCS[2].len = 1; - DESCS[2].flags = 2; // WRITE - DESCS[2].next = 0; - - // Add to available ring - let avail_idx = AVAIL.idx; - AVAIL.ring[(avail_idx as usize) % QUEUE_SIZE] = 0; + let avail_idx = core::ptr::read_volatile(core::ptr::addr_of!(VQUEUE.avail.idx)); + VQUEUE.avail.ring[(avail_idx as usize) % QUEUE_SIZE] = 0; core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); - AVAIL.idx = avail_idx.wrapping_add(1); + core::ptr::write_volatile( + core::ptr::addr_of_mut!(VQUEUE.avail.idx), + avail_idx.wrapping_add(1), + ); core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); - // Notify device - mmio_write(0x050, 0); + mmio_write(self.base, 0x050, 0); - // Wait for completion let expected = avail_idx.wrapping_add(1); - while core::ptr::read_volatile(core::ptr::addr_of!(USED.idx)) != expected { + let mut timeout = 10_000_000u32; + while core::ptr::read_volatile(core::ptr::addr_of!(VQUEUE.used.idx)) != expected { core::hint::spin_loop(); + timeout -= 1; + if timeout == 0 { return false; } } REQ_STATUS == 0 diff --git a/scripts/make-disk-image.sh b/scripts/make-disk-image.sh index d9b742c..3605670 100755 --- a/scripts/make-disk-image.sh +++ b/scripts/make-disk-image.sh @@ -2,110 +2,49 @@ set -euo pipefail IMG="boot.img" -IMG_SIZE_MB=4 +IMG_SIZE_KB=4096 TARGET="riscv64gc-unknown-none-elf" BUILD_DIR="target/${TARGET}/release" echo "=== Creating k-os boot disk image ===" -# Create empty image -dd if=/dev/zero of="${IMG}" bs=1M count=${IMG_SIZE_MB} 2>/dev/null -echo "Created ${IMG} (${IMG_SIZE_MB}MB)" +# Create and format FAT16 image (mtools required: brew install mtools) +dd if=/dev/zero of="${IMG}" bs=1K count=${IMG_SIZE_KB} 2>/dev/null +mformat -i "${IMG}" -t 8 -h 2 -s 32 -r 16 -c 1 -M 512 :: +echo "Created and formatted ${IMG} (${IMG_SIZE_KB}KB, FAT16)" -# Format as FAT16 -# On macOS: use newfs_msdos, on Linux: use mkfs.fat -if command -v newfs_msdos &>/dev/null; then - newfs_msdos -F 16 "${IMG}" >/dev/null -elif command -v mkfs.fat &>/dev/null; then - mkfs.fat -F 16 "${IMG}" >/dev/null -else - echo "Error: need newfs_msdos (macOS) or mkfs.fat (Linux)" - exit 1 -fi -echo "Formatted as FAT16" +# Create directories +mmd -i "${IMG}" ::servers 2>/dev/null || true +mmd -i "${IMG}" ::apps 2>/dev/null || true -# Mount and copy files -MOUNT_DIR=$(mktemp -d) - -if [[ "$(uname)" == "Darwin" ]]; then - # macOS: use hdiutil - DEVICE=$(hdiutil attach -nomount "${IMG}" 2>/dev/null | head -1 | awk '{print $1}') - mount -t msdos "${DEVICE}" "${MOUNT_DIR}" 2>/dev/null || { - echo "Warning: could not mount image. Files must be copied manually." - echo "On macOS, try: brew install mtools" - hdiutil detach "${DEVICE}" 2>/dev/null - rmdir "${MOUNT_DIR}" - - # Fallback: use mtools if available - if command -v mcopy &>/dev/null; then - copy_with_mtools - else - echo "Install mtools: brew install mtools" - exit 0 - fi - exit 0 - } - - copy_files "${MOUNT_DIR}" - umount "${MOUNT_DIR}" - hdiutil detach "${DEVICE}" 2>/dev/null -else - # Linux: use mount - sudo mount -o loop "${IMG}" "${MOUNT_DIR}" - copy_files "${MOUNT_DIR}" - sudo umount "${MOUNT_DIR}" +# Copy init +if [ -f "${BUILD_DIR}/init" ]; then + mcopy -i "${IMG}" "${BUILD_DIR}/init" "::init.elf" + echo " Copied init.elf ($(stat -f%z "${BUILD_DIR}/init" 2>/dev/null || stat -c%s "${BUILD_DIR}/init") bytes)" fi -rmdir "${MOUNT_DIR}" 2>/dev/null +# Copy server binaries +for server in display-server input-server log-server permission-manager storage-server session-server; do + if [ -f "${BUILD_DIR}/${server}" ]; then + mcopy -i "${IMG}" "${BUILD_DIR}/${server}" "::servers/${server}.elf" + echo " Copied servers/${server}.elf" + fi +done + +# Copy app binaries +for app in snake; do + if [ -f "${BUILD_DIR}/${app}" ]; then + mcopy -i "${IMG}" "${BUILD_DIR}/${app}" "::apps/${app}.elf" + echo " Copied apps/${app}.elf" + fi +done + +# List contents +echo "" +echo "Disk contents:" +mdir -i "${IMG}" :: +mdir -i "${IMG}" ::servers 2>/dev/null || true +mdir -i "${IMG}" ::apps 2>/dev/null || true + +echo "" echo "=== Done: ${IMG} ===" - -copy_files() { - local DIR="$1" - mkdir -p "${DIR}/servers" "${DIR}/apps" - - # Copy init - if [ -f "${BUILD_DIR}/hello" ]; then - cp "${BUILD_DIR}/hello" "${DIR}/init.elf" - echo " Copied init.elf" - fi - - # Copy any server binaries that exist - for server in display input log permission storage session; do - if [ -f "${BUILD_DIR}/${server}-server" ]; then - cp "${BUILD_DIR}/${server}-server" "${DIR}/servers/${server}.elf" - echo " Copied servers/${server}.elf" - fi - done - - # Copy any app binaries that exist - for app in snake; do - if [ -f "${BUILD_DIR}/${app}" ]; then - cp "${BUILD_DIR}/${app}" "${DIR}/apps/${app}.elf" - echo " Copied apps/${app}.elf" - fi - done -} - -copy_with_mtools() { - echo "Using mtools to copy files..." - mmd -i "${IMG}" ::servers ::apps 2>/dev/null || true - - if [ -f "${BUILD_DIR}/hello" ]; then - mcopy -i "${IMG}" "${BUILD_DIR}/hello" "::init.elf" - echo " Copied init.elf" - fi - - for server in display input log permission storage session; do - if [ -f "${BUILD_DIR}/${server}-server" ]; then - mcopy -i "${IMG}" "${BUILD_DIR}/${server}-server" "::servers/${server}.elf" - echo " Copied servers/${server}.elf" - fi - done - - for app in snake; do - if [ -f "${BUILD_DIR}/${app}" ]; then - mcopy -i "${IMG}" "${BUILD_DIR}/${app}" "::apps/${app}.elf" - echo " Copied apps/${app}.elf" - fi - done -}