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 -}