fix(adapter-riscv): fix VirtIO block driver — scan all MMIO slots, handle legacy mode, fix virtqueue layout

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.
This commit is contained in:
2026-10-18 04:42:13 +02:00
parent 7649cb0b18
commit 924baede9e
2 changed files with 158 additions and 176 deletions

View File

@@ -1,6 +1,8 @@
const VIRTIO_MMIO_BASE: usize = 0x10001000;
const VIRTIO_MAGIC: u32 = 0x74726976; const VIRTIO_MAGIC: u32 = 0x74726976;
const VIRTIO_BLK_DEVICE_ID: u32 = 2; 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_ACK: u32 = 1;
const VIRTIO_STATUS_DRIVER: u32 = 2; const VIRTIO_STATUS_DRIVER: u32 = 2;
@@ -9,8 +11,20 @@ const VIRTIO_STATUS_DRIVER_OK: u32 = 4;
const QUEUE_SIZE: usize = 8; const QUEUE_SIZE: usize = 8;
const SECTOR_SIZE: usize = 512; 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 { struct VirtqDesc {
addr: u64, addr: u64,
len: u32, len: u32,
@@ -18,14 +32,15 @@ struct VirtqDesc {
next: u16, next: u16,
} }
#[repr(C, align(2))] #[repr(C)]
struct VirtqAvail { struct VirtqAvail {
flags: u16, flags: u16,
idx: u16, idx: u16,
ring: [u16; QUEUE_SIZE], ring: [u16; QUEUE_SIZE],
used_event: u16,
} }
#[repr(C, align(4))] #[repr(C)]
struct VirtqUsed { struct VirtqUsed {
flags: u16, flags: u16,
idx: u16, idx: u16,
@@ -33,6 +48,7 @@ struct VirtqUsed {
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy)]
struct VirtqUsedElem { struct VirtqUsedElem {
id: u32, id: u32,
len: u32, len: u32,
@@ -45,16 +61,14 @@ struct VirtioBlkReqHeader {
sector: u64, sector: u64,
} }
static mut DESCS: [VirtqDesc; QUEUE_SIZE] = [const { static mut VQUEUE: VirtqMem = VirtqMem {
VirtqDesc { addr: 0, len: 0, flags: 0, next: 0 } descs: [const { VirtqDesc { addr: 0, len: 0, flags: 0, next: 0 } }; QUEUE_SIZE],
}; QUEUE_SIZE]; avail: VirtqAvail { flags: 0, idx: 0, ring: [0; QUEUE_SIZE], used_event: 0 },
_pad: [0; 3946],
static mut AVAIL: VirtqAvail = VirtqAvail { used: VirtqUsed {
flags: 0, idx: 0, ring: [0; QUEUE_SIZE], flags: 0, idx: 0,
}; ring: [const { VirtqUsedElem { id: 0, len: 0 } }; QUEUE_SIZE],
},
static mut USED: VirtqUsed = VirtqUsed {
flags: 0, idx: 0, ring: [const { VirtqUsedElem { id: 0, len: 0 } }; QUEUE_SIZE],
}; };
static mut REQ_HEADER: VirtioBlkReqHeader = VirtioBlkReqHeader { static mut REQ_HEADER: VirtioBlkReqHeader = VirtioBlkReqHeader {
@@ -63,108 +77,137 @@ static mut REQ_HEADER: VirtioBlkReqHeader = VirtioBlkReqHeader {
static mut REQ_STATUS: u8 = 0xFF; static mut REQ_STATUS: u8 = 0xFF;
fn mmio_read(offset: usize) -> u32 { fn mmio_read(base: usize, offset: usize) -> u32 {
unsafe { core::ptr::read_volatile((VIRTIO_MMIO_BASE + offset) as *const u32) } unsafe { core::ptr::read_volatile((base + offset) as *const u32) }
} }
fn mmio_write(offset: usize, val: u32) { fn mmio_write(base: usize, offset: usize, val: u32) {
unsafe { core::ptr::write_volatile((VIRTIO_MMIO_BASE + offset) as *mut u32, val) } unsafe { core::ptr::write_volatile((base + offset) as *mut u32, val) }
} }
pub struct VirtioBlk { pub struct VirtioBlk {
initialized: bool, base: usize,
legacy: bool,
} }
impl VirtioBlk { impl VirtioBlk {
pub fn probe() -> Option<Self> { pub fn probe() -> Option<Self> {
let magic = mmio_read(0x000); for slot in 0..VIRTIO_MMIO_SLOTS {
let device_id = mmio_read(0x008); let base = VIRTIO_MMIO_FIRST + slot * VIRTIO_MMIO_STRIDE;
let magic = mmio_read(base, 0x000);
if magic != VIRTIO_MAGIC || device_id != VIRTIO_BLK_DEVICE_ID { let device_id = mmio_read(base, 0x008);
return None; if magic == VIRTIO_MAGIC && device_id == VIRTIO_BLK_DEVICE_ID {
return Self::init_at(base);
}
} }
None
}
fn init_at(base: usize) -> Option<Self> {
let version = mmio_read(base, 0x004);
let legacy = version == 1;
// Reset // Reset
mmio_write(0x070, 0); mmio_write(base, 0x070, 0);
while mmio_read(base, 0x070) != 0 {}
// Acknowledge // Acknowledge + Driver
mmio_write(0x070, VIRTIO_STATUS_ACK); mmio_write(base, 0x070, VIRTIO_STATUS_ACK);
mmio_write(0x070, VIRTIO_STATUS_ACK | VIRTIO_STATUS_DRIVER); mmio_write(base, 0x070, mmio_read(base, 0x070) | VIRTIO_STATUS_DRIVER);
// No feature negotiation needed for basic read if legacy {
mmio_write(0x024, 0); // DriverFeaturesSel = 0 // Legacy: set guest page size
mmio_write(0x020, 0); // DriverFeatures = 0 mmio_write(base, 0x028, PAGE_SIZE as u32);
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;
} }
// Set up queue 0 // Feature negotiation
mmio_write(0x030, 0); // QueueSel = 0 mmio_write(base, 0x014, 0);
mmio_write(0x038, QUEUE_SIZE as u32); // QueueNum 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 { unsafe {
let desc_addr = core::ptr::addr_of!(DESCS) as u64; let vq_addr = core::ptr::addr_of!(VQUEUE) as usize;
let avail_addr = core::ptr::addr_of!(AVAIL) as u64;
let used_addr = core::ptr::addr_of!(USED) as u64;
mmio_write(0x080, desc_addr as u32); if legacy {
mmio_write(0x084, (desc_addr >> 32) as u32); mmio_write(base, 0x040, (vq_addr / PAGE_SIZE) as u32);
mmio_write(0x090, avail_addr as u32); } else {
mmio_write(0x094, (avail_addr >> 32) as u32); let desc_addr = vq_addr as u64;
mmio_write(0x0A0, used_addr as u32); let avail_addr = (vq_addr + core::mem::offset_of!(VirtqMem, avail)) as u64;
mmio_write(0x0A4, (used_addr >> 32) as u32); 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(base, 0x070, mmio_read(base, 0x070) | VIRTIO_STATUS_DRIVER_OK);
mmio_write(0x070, VIRTIO_STATUS_ACK | VIRTIO_STATUS_DRIVER | VIRTIO_STATUS_FEATURES_OK | 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 { pub fn read_sector(&mut self, sector: u32, buf: &mut [u8; SECTOR_SIZE]) -> bool {
if !self.initialized { return false; }
unsafe { unsafe {
REQ_HEADER.req_type = 0; // read REQ_HEADER.req_type = 0;
REQ_HEADER.reserved = 0; REQ_HEADER.reserved = 0;
REQ_HEADER.sector = sector as u64; REQ_HEADER.sector = sector as u64;
REQ_STATUS = 0xFF; REQ_STATUS = 0xFF;
// Descriptor 0: header (device reads) VQUEUE.descs[0] = VirtqDesc {
DESCS[0].addr = core::ptr::addr_of!(REQ_HEADER) as u64; addr: core::ptr::addr_of!(REQ_HEADER) as u64,
DESCS[0].len = 16; len: 16,
DESCS[0].flags = 1; // NEXT flags: 1, // NEXT
DESCS[0].next = 1; 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) let avail_idx = core::ptr::read_volatile(core::ptr::addr_of!(VQUEUE.avail.idx));
DESCS[1].addr = buf.as_ptr() as u64; VQUEUE.avail.ring[(avail_idx as usize) % QUEUE_SIZE] = 0;
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;
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); 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); core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
// Notify device mmio_write(self.base, 0x050, 0);
mmio_write(0x050, 0);
// Wait for completion
let expected = avail_idx.wrapping_add(1); 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(); core::hint::spin_loop();
timeout -= 1;
if timeout == 0 { return false; }
} }
REQ_STATUS == 0 REQ_STATUS == 0

View File

@@ -2,110 +2,49 @@
set -euo pipefail set -euo pipefail
IMG="boot.img" IMG="boot.img"
IMG_SIZE_MB=4 IMG_SIZE_KB=4096
TARGET="riscv64gc-unknown-none-elf" TARGET="riscv64gc-unknown-none-elf"
BUILD_DIR="target/${TARGET}/release" BUILD_DIR="target/${TARGET}/release"
echo "=== Creating k-os boot disk image ===" echo "=== Creating k-os boot disk image ==="
# Create empty image # Create and format FAT16 image (mtools required: brew install mtools)
dd if=/dev/zero of="${IMG}" bs=1M count=${IMG_SIZE_MB} 2>/dev/null dd if=/dev/zero of="${IMG}" bs=1K count=${IMG_SIZE_KB} 2>/dev/null
echo "Created ${IMG} (${IMG_SIZE_MB}MB)" 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 # Create directories
# On macOS: use newfs_msdos, on Linux: use mkfs.fat mmd -i "${IMG}" ::servers 2>/dev/null || true
if command -v newfs_msdos &>/dev/null; then mmd -i "${IMG}" ::apps 2>/dev/null || true
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"
# Mount and copy files # Copy init
MOUNT_DIR=$(mktemp -d) if [ -f "${BUILD_DIR}/init" ]; then
mcopy -i "${IMG}" "${BUILD_DIR}/init" "::init.elf"
if [[ "$(uname)" == "Darwin" ]]; then echo " Copied init.elf ($(stat -f%z "${BUILD_DIR}/init" 2>/dev/null || stat -c%s "${BUILD_DIR}/init") bytes)"
# 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}"
fi 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} ===" 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
}