//! K328: isolated modern MMIO v2 QEMU fixture for actual FEATURES_OK readback. //! Legacy v1 cannot provide this signal and is not silently treated as v2. use super::*; use turkos_kernel::{virtio_mmio, virtio_ring}; unsafe fn read(base: u64, offset: u64) -> u32 { unsafe { core::ptr::read_volatile((base + offset) as *const u32) } } unsafe fn write(base: u64, offset: u64, value: u32) { unsafe { core::ptr::write_volatile((base + offset) as *mut u32, value) } } fn fail(reason: &str) -> ! { write_str("TURKOS/VIRTIO-MODERN-ERROR: "); write_line(reason); park() } pub fn smoke_test(boot: &BootData, allocator: &mut memory::FrameAllocator) -> ! { let window = dtb::virtio_page_window( &boot.virtio_mmio, boot.hardware.ram_base, boot.hardware.ram_size, ) .unwrap_or_else(|_| fail("DTB window")); if window.start != 0x0a00_0000 || window.end != 0x0a00_4000 { fail("window span"); } let mut block = None; for node in boot.virtio_mmio.as_slice() { let base = node.base; // SAFETY: K322 validated and MMU mapped all four Device pages. let (magic, version, id) = unsafe { (read(base, 0), read(base, 4), read(base, 8)) }; if virtio_mmio::validate_magic(magic).is_err() || version != 2 { fail("modern magic/version"); } if id == 2 { if block.replace(base).is_some() { fail("duplicate block"); } } else if id != 0 { fail("unexpected attached device"); } } let base = block.unwrap_or_else(|| fail("block device missing")); // SAFETY: +0x010..+0x070 are 32-bit MMIO registers inside the selected // modern v2 device's validated and Device-mapped 0x200-byte slot. let ( host_low, host_high, accepted_low, accepted_high, accepted_status, rejected_status, queue_max, absent_queue_max, ) = unsafe { write(base, 0x70, 0); if read(base, 0x70) != 0 { fail("reset status"); } write(base, 0x70, 1); if read(base, 0x70) != 1 { fail("ack status"); } write(base, 0x70, 3); if read(base, 0x70) != 3 { fail("ack/driver status"); } write(base, 0x14, 0); let host_low = read(base, 0x10); write(base, 0x14, 1); let host_high = read(base, 0x10); let (accepted_low, accepted_high) = virtio_mmio::select_modern_read_only_features(host_low, host_high) .unwrap_or_else(|_| fail("required modern feature absent")); write(base, 0x24, 0); write(base, 0x20, accepted_low); write(base, 0x24, 1); write(base, 0x20, accepted_high); write(base, 0x70, 0x0b); let accepted_status = read(base, 0x70); if accepted_status & 0x0b != 0x0b { fail("valid FEATURES_OK rejected"); } if virtio_mmio::deferred_driver_ready_status(accepted_status) != Ok(0x0f) { fail("accepted status guard"); } write(base, 0x30, 0); let queue_max = read(base, 0x34); if virtio_mmio::validate_queue_max(queue_max).is_err() { fail("queue zero maximum"); } write(base, 0x30, 1); let absent_queue_max = read(base, 0x34); if virtio_mmio::validate_queue_max(absent_queue_max) != Err(virtio_mmio::Error::EmptyQueue) { fail("absent queue nonzero"); } write(base, 0x30, 0); // A fresh reset is mandatory before the second negotiation. With the // fixture's iommu_platform=on, omit its mandatory offered bit 33. if accepted_high & virtio_mmio::MODERN_F_IOMMU_PLATFORM == 0 { fail("IOMMU feature missing from fixture"); } write(base, 0x70, 0); if read(base, 0x70) != 0 { fail("negative fixture precondition"); } write(base, 0x70, 1); if read(base, 0x70) != 1 { fail("negative ack status"); } write(base, 0x70, 3); if read(base, 0x70) != 3 { fail("negative driver status"); } write(base, 0x24, 0); write(base, 0x20, accepted_low); write(base, 0x24, 1); write( base, 0x20, accepted_high & !virtio_mmio::MODERN_F_IOMMU_PLATFORM, ); write(base, 0x70, 0x0b); let rejected_status = read(base, 0x70); if rejected_status & 8 != 0 { fail("unsupported feature accepted"); } if virtio_mmio::deferred_driver_ready_status(rejected_status) != Err(virtio_mmio::Error::NegotiationRejected) { fail("rejected negotiation entered ready path"); } write(base, 0x70, 0); if read(base, 0x70) != 0 { fail("final reset"); } ( host_low, host_high, accepted_low, accepted_high, accepted_status, rejected_status, queue_max, absent_queue_max, ) }; let free_before = allocator.remaining_pages(); let descriptor_frame = allocator .allocate() .unwrap_or_else(|| fail("descriptor frame exhausted")); let desc = virtio_ring::descriptor_page( descriptor_frame, queue_max, boot.hardware.ram_base, boot.hardware.ram_size, ) .unwrap_or_else(|_| fail("descriptor frame geometry")); if allocator.remaining_pages().checked_add(1) != Some(free_before) || desc.bytes != memory::PAGE_SIZE { fail("descriptor ownership accounting"); } // SAFETY: the frame allocator exclusively owns this entire identity-mapped // writable RAM page; no queue register points at it and QueueReady is 0. unsafe { core::ptr::write_bytes(desc.base as *mut u8, 0, memory::PAGE_SIZE as usize) }; // SAFETY: both addresses are in the owned, initialized descriptor page. let zeroed = unsafe { core::ptr::read_volatile(desc.base as *const u64) == 0 && core::ptr::read_volatile((desc.base + desc.bytes - 8) as *const u64) == 0 }; if !zeroed { fail("descriptor zero initialization"); } write_str("TURKOS/VIRTIO-MODERN: BASE=0x"); write_hex(base as usize); write_str(" HOSTLOW=0x"); write_hex(host_low as usize); write_str(" HOSTHIGH=0x"); write_hex(host_high as usize); write_str(" ACCEPTLOW=0x"); write_hex(accepted_low as usize); write_str(" ACCEPTHIGH=0x"); write_hex(accepted_high as usize); write_str(" ACCEPTSTATUS=0x"); write_hex(accepted_status as usize); write_str(" REJECTSTATUS=0x"); write_hex(rejected_status as usize); write_str("\r\n"); write_line("TURKOS/VIRTIO-MODERN: PASS"); write_line( "TURKOS/VIRTIO-STATUS: ACK=0x1 DRIVER=0x3 DEFERRED-READY=0xf NEGATIVE-BLOCKED=0x1 FINAL=0x0", ); write_line("TURKOS/VIRTIO-STATUS: PASS"); write_str("TURKOS/VIRTIO-QUEUE-MAX: Q0=0x"); write_hex(queue_max as usize); write_str(" Q1=0x"); write_hex(absent_queue_max as usize); write_str("\r\n"); write_line("TURKOS/VIRTIO-QUEUE-MAX: PASS"); write_str("TURKOS/VIRTIO-DESC: FRAME=0x"); write_hex(desc.base as usize); write_str(" COUNT=0x"); write_hex(desc.count as usize); write_str(" BYTES=0x"); write_hex(desc.bytes as usize); write_str(" OWNED=0x"); write_hex(1); write_str(" ZEROED=0x"); write_hex(1); write_str(" DEVICE-WRITTEN=0x"); write_hex(0); write_str("\r\n"); write_line("TURKOS/VIRTIO-DESC: PASS"); park() }