//! Isolated modern-MMIO split-queue data-path experiment. The read-only raw //! disk fixture has sector 0 bytes i ^ 0xa5; no production driver is implied. use super::*; #[cfg(any( feature = "virtio-write-descriptor-test", all( not(feature = "virtio-disk-absence-test"), not(feature = "virtio-write-gate-test") ) ))] use core::arch::asm; #[cfg(feature = "turkfs-format-test")] use turkos_kernel::turkfs; use turkos_kernel::virtio_mmio; #[cfg(feature = "virtio-metadata-order-test")] #[path = "virtio_metadata_probe.rs"] mod metadata_probe; #[cfg(feature = "virtio-io-retry-test")] #[path = "virtio_retry_probe.rs"] mod retry_probe; #[cfg(any( feature = "virtio-write-header-test", all( not(feature = "virtio-disk-absence-test"), not(feature = "virtio-write-gate-test"), not(feature = "virtio-metadata-order-test"), not(feature = "virtio-io-retry-test") ) ))] use turkos_kernel::virtio_ring; // This single-CPU, IRQ-masked probe enters once. The output canary is not DMA // memory; keeping it static protects the finite EL1 boot stack. #[cfg(feature = "turkfs-format-test")] static mut TURKFS_TAIL_OUTPUT: [u8; turkfs::BLOCK_BYTES + 2] = [0xa5; turkfs::BLOCK_BYTES + 2]; #[cfg(feature = "turkfs-format-test")] struct TurkFsScratch { superblock: [u8; turkfs::BLOCK_BYTES], root_block: [u8; turkfs::BLOCK_BYTES], inode_block: [u8; turkfs::BLOCK_BYTES], file_block: [u8; turkfs::BLOCK_BYTES], multi_data: [u8; 2 * turkfs::BLOCK_BYTES], } // Each field is disjoint and borrowed only in this once-only smoke test. // Large DMA-result copies must not consume the guarded EL1 boot stack. #[cfg(feature = "turkfs-format-test")] static mut TURKFS_SCRATCH: TurkFsScratch = TurkFsScratch { superblock: [0; turkfs::BLOCK_BYTES], root_block: [0; turkfs::BLOCK_BYTES], inode_block: [0; turkfs::BLOCK_BYTES], file_block: [0; turkfs::BLOCK_BYTES], multi_data: [0; 2 * turkfs::BLOCK_BYTES], }; unsafe fn read32(base: u64, offset: u64) -> u32 { unsafe { core::ptr::read_volatile((base + offset) as *const u32) } } #[cfg(not(feature = "virtio-disk-absence-test"))] unsafe fn write32(base: u64, offset: u64, value: u32) { unsafe { core::ptr::write_volatile((base + offset) as *mut u32, value) } } #[cfg(any( feature = "virtio-write-descriptor-test", all( not(feature = "virtio-disk-absence-test"), not(feature = "virtio-write-gate-test") ) ))] unsafe fn write64_register_pair(base: u64, low: u64, address: u64) { unsafe { write32(base, low, address as u32); write32(base, low + 4, (address >> 32) as u32); } } #[cfg(any( feature = "virtio-write-descriptor-test", all( not(feature = "virtio-disk-absence-test"), not(feature = "virtio-write-gate-test") ) ))] fn counter() -> u64 { let value: u64; // SAFETY: CNTPCT_EL0 is readable at EL1 in this QEMU virt fixture. unsafe { asm!("mrs {value}, CNTPCT_EL0", value = out(reg) value, options(nomem, nostack, preserves_flags)) }; value } fn fail(reason: &str) -> ! { write_str("TURKOS/VIRTIO-QUEUE-ERROR: "); write_line(reason); park() } #[cfg(any( feature = "virtio-write-readback-test", feature = "virtio-flush-reopen-test" ))] fn write_readback_smoke(base: u64, boot: &BootData, allocator: &mut memory::FrameAllocator) -> ! { // A fresh lease and a new GET request follow the completed OUT and reset. // Comparing the old write buffer would not prove that the device returned data. let free_before = allocator.remaining_pages(); let mut lease = virtio_ring::DmaLease::allocate(allocator, boot.hardware.ram_base, boot.hardware.ram_size) .unwrap_or_else(|_| fail("readback DMA lease")); let [desc, avail, used, request] = lease.frames().unwrap_or_else(|_| fail("readback frames")); virtio_ring::split_ring_pages( desc, avail, used, 8, boot.hardware.ram_base, boot.hardware.ram_size, ) .unwrap_or_else(|_| fail("readback ring geometry")); let header = virtio_ring::dma_subregion(request, 0, 16) .unwrap_or_else(|_| fail("readback header range")); let status_addr = virtio_ring::dma_subregion(request, 16, 1) .unwrap_or_else(|_| fail("readback status range")); let data = virtio_ring::dma_subregion(request, 512, 512) .unwrap_or_else(|_| fail("readback data range")); let chain = virtio_ring::read_descriptor_chain(header, data, status_addr); if chain.map(|entry| u16::from_le(entry.flags_le)) != [ virtio_ring::DESC_F_NEXT, virtio_ring::DESC_F_NEXT | virtio_ring::DESC_F_WRITE, virtio_ring::DESC_F_WRITE, ] { fail("readback descriptor direction"); } unsafe { for frame in [desc, avail, used, request] { core::ptr::write_bytes(frame as *mut u8, 0, memory::PAGE_SIZE as usize); } for (index, descriptor) in chain.into_iter().enumerate() { core::ptr::write_volatile( (desc as *mut virtio_ring::Descriptor).add(index), descriptor, ); } core::ptr::write_volatile( header as *mut virtio_ring::BlockReadHeader, virtio_ring::block_read_header(2), ); core::ptr::write_bytes(data as *mut u8, 0x5a, 512); core::ptr::write_volatile(status_addr as *mut u8, 0xff); core::ptr::write_volatile((avail + 4) as *mut u16, 0u16.to_le()); write32(base, 0x70, 0); if read32(base, 0x70) != 0 || read32(base, 0x44) != 0 { fail("readback reset"); } write32(base, 0x70, 3); write32(base, 0x14, 0); let offered_low = read32(base, 0x10); write32(base, 0x14, 1); let offered_high = read32(base, 0x10); #[cfg(feature = "virtio-flush-reopen-test")] let (accepted_low, accepted_high) = virtio_mmio::select_modern_read_only_features(offered_low, offered_high) .unwrap_or_else(|_| fail("reopen requires read-only modern device")); #[cfg(not(feature = "virtio-flush-reopen-test"))] let (accepted_low, accepted_high) = virtio_mmio::select_experimental_write_features(offered_low, offered_high) .unwrap_or_else(|_| fail("readback modern feature subset")); write32(base, 0x24, 0); write32(base, 0x20, accepted_low); write32(base, 0x24, 1); write32(base, 0x20, accepted_high); write32(base, 0x70, 0x0b); if read32(base, 0x70) != 0x0b { fail("readback FEATURES_OK"); } write32(base, 0x30, 0); if read32(base, 0x34) < 8 || read32(base, 0x44) != 0 { fail("readback queue capacity"); } write32(base, 0x38, 8); write64_register_pair(base, 0x80, desc); write64_register_pair(base, 0x90, avail); write64_register_pair(base, 0xa0, used); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x44, 1); if read32(base, 0x44) != 1 { fail("readback QueueReady"); } lease .mark_ready() .unwrap_or_else(|_| fail("readback lease ready")); write32(base, 0x70, 0x0f); if read32(base, 0x70) != 0x0f { fail("readback DRIVER_OK"); } core::ptr::write_volatile((avail + 2) as *mut u16, 1u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x50, 0); } let frequency: u64; unsafe { asm!("mrs {value}, CNTFRQ_EL0", value = out(reg) frequency, options(nomem, nostack, preserves_flags)) }; let deadline = virtio_ring::deadline_after(counter(), frequency) .unwrap_or_else(|_| fail("readback deadline")); let used_index = loop { let index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if index != 0 { break index; } if virtio_ring::deadline_expired(counter(), deadline) { fail("readback completion timeout"); } core::hint::spin_loop(); }; let (used_id, used_len, status) = unsafe { asm!("dmb oshld", options(nostack, preserves_flags)); ( u32::from_le(core::ptr::read_volatile((used + 4) as *const u32)), u32::from_le(core::ptr::read_volatile((used + 8) as *const u32)), core::ptr::read_volatile(status_addr as *const u8), ) }; if used_index != 1 || used_id != 0 || virtio_ring::validate_read_completion_length(used_len, 512) != Ok(()) || virtio_ring::read_block_status(status) != Ok(()) { fail("write readback completion/status"); } let mut sum = 0usize; #[cfg(feature = "virtio-partial-record-test")] let mut mismatch = None; for index in 0..512usize { let byte = unsafe { core::ptr::read_volatile((data + index as u64) as *const u8) }; #[cfg(feature = "virtio-two-write-reopen-test")] let expected = virtio_ring::two_write_final_pattern_byte(index) .unwrap_or_else(|_| fail("second write pattern bounds")); #[cfg(not(feature = "virtio-two-write-reopen-test"))] let expected = virtio_ring::write_test_pattern_byte(index) .unwrap_or_else(|_| fail("readback pattern bounds")); if byte != expected { #[cfg(feature = "virtio-partial-record-test")] { mismatch = Some((index, expected, byte)); break; } #[cfg(not(feature = "virtio-partial-record-test"))] fail("write readback byte mismatch"); } sum += byte as usize; } #[cfg(feature = "virtio-partial-record-test")] if let Some((offset, expected, actual)) = mismatch { unsafe { write32(base, 0x70, 0); if read32(base, 0x70) != 0 || read32(base, 0x44) != 0 { fail("partial record queue reset"); } } lease .mark_reset() .unwrap_or_else(|_| fail("partial record lease reset")); lease .release(allocator) .unwrap_or_else(|_| fail("partial record lease release")); if allocator.remaining_pages() != free_before { fail("partial record DMA frames leaked"); } write_str("TURKOS/PARTIAL-RECORD: OFFSET=0x"); write_hex(offset); write_str(" EXPECTED=0x"); write_hex(expected as usize); write_str(" ACTUAL=0x"); write_hex(actual as usize); write_str(" DMA-RESTORED=0x1\r\n"); fail("partial record mismatch"); } if sum != 0xff00 { fail("write readback checksum"); } unsafe { write32(base, 0x70, 0); if read32(base, 0x70) != 0 || read32(base, 0x44) != 0 { fail("readback final reset"); } } lease .mark_reset() .unwrap_or_else(|_| fail("readback lease reset")); lease .release(allocator) .unwrap_or_else(|_| fail("readback lease release")); if allocator.remaining_pages() != free_before { fail("readback DMA frames leaked"); } #[cfg(feature = "virtio-two-write-reopen-test")] write_str("TURKOS/TWO-WRITE-READ: SECTOR=0x"); #[cfg(all( feature = "virtio-flush-reopen-test", not(feature = "virtio-two-write-reopen-test") ))] write_str("TURKOS/REOPEN-READ: SECTOR=0x"); #[cfg(not(feature = "virtio-flush-reopen-test"))] write_str("TURKOS/WRITE-READBACK: SECTOR=0x"); write_hex(2); write_str(" USEDIDX=0x"); write_hex(used_index as usize); write_str(" USEDID=0x"); write_hex(used_id as usize); write_str(" USEDLEN=0x"); write_hex(used_len as usize); write_str(" STATUS=0x"); write_hex(status as usize); write_str(" MATCH=0x1 SUM=0x"); write_hex(sum); write_str(" DMA-RESTORED=0x1\r\n"); #[cfg(feature = "virtio-two-write-reopen-test")] write_line("TURKOS/TWO-WRITE-READ: PASS"); #[cfg(all( feature = "virtio-flush-reopen-test", not(feature = "virtio-two-write-reopen-test") ))] write_line("TURKOS/REOPEN-READ: PASS"); #[cfg(not(feature = "virtio-flush-reopen-test"))] write_line("TURKOS/WRITE-READBACK: PASS"); park() } #[cfg(feature = "virtio-write-descriptor-test")] fn write_descriptor_smoke(base: u64, boot: &BootData, allocator: &mut memory::FrameAllocator) -> ! { // K384's only writable target is the host-created disposable qcow2 copy. // K385 injects EIO on a separate raw derivative; K386 reads back in a // second queue epoch. Neither path infers flush or power-loss durability. let free_before = allocator.remaining_pages(); let mut lease = virtio_ring::DmaLease::allocate(allocator, boot.hardware.ram_base, boot.hardware.ram_size) .unwrap_or_else(|_| fail("write DMA lease")); let [desc, avail, used, request] = lease.frames().unwrap_or_else(|_| fail("write DMA frames")); virtio_ring::split_ring_pages( desc, avail, used, 8, boot.hardware.ram_base, boot.hardware.ram_size, ) .unwrap_or_else(|_| fail("write split ring geometry")); let header_address = virtio_ring::dma_subregion(request, 0, 16).unwrap_or_else(|_| fail("write header range")); let status_address = virtio_ring::dma_subregion(request, 16, 1).unwrap_or_else(|_| fail("write status range")); let data_address = virtio_ring::dma_subregion(request, 512, 512).unwrap_or_else(|_| fail("write data range")); let chain = virtio_ring::write_descriptor_chain(header_address, data_address, status_address); if virtio_ring::validate_write_descriptor_chain(&chain) != Ok(()) { fail("write descriptor direction"); } #[cfg(feature = "virtio-flush-request-test")] let flush_negotiated: bool; // SAFETY: the four leased identity-mapped RAM frames remain exclusively // owned until reset. QEMU is not allowed to see the ring before Ready. unsafe { for frame in [desc, avail, used, request] { core::ptr::write_bytes(frame as *mut u8, 0, memory::PAGE_SIZE as usize); } for (index, descriptor) in chain.into_iter().enumerate() { core::ptr::write_volatile( (desc as *mut virtio_ring::Descriptor).add(index), descriptor, ); } core::ptr::write_volatile( header_address as *mut virtio_ring::BlockReadHeader, virtio_ring::block_write_header(2), ); for index in 0..512usize { core::ptr::write_volatile((data_address + index as u64) as *mut u8, index as u8 ^ 0xa7); } core::ptr::write_volatile(status_address as *mut u8, 0xff); core::ptr::write_volatile((avail + 4) as *mut u16, 0u16.to_le()); let observed = [ core::ptr::read_volatile(desc as *const virtio_ring::Descriptor), core::ptr::read_volatile((desc as *const virtio_ring::Descriptor).add(1)), core::ptr::read_volatile((desc as *const virtio_ring::Descriptor).add(2)), ]; if virtio_ring::validate_write_descriptor_chain(&observed) != Ok(()) || u32::from_le(core::ptr::read_volatile(header_address as *const u32)) != 1 || core::ptr::read_volatile(status_address as *const u8) != 0xff { fail("write DMA request preflight"); } write32(base, 0x70, 0); if read32(base, 0x70) != 0 { fail("write queue reset"); } write32(base, 0x70, 3); write32(base, 0x14, 0); let offered_low = read32(base, 0x10); write32(base, 0x14, 1); let offered_high = read32(base, 0x10); #[cfg(feature = "virtio-flush-request-test")] let (accepted_low, accepted_high) = virtio_mmio::select_experimental_flush_features(offered_low, offered_high) .unwrap_or_else(|_| fail("write queue requires FLUSH feature")); #[cfg(not(feature = "virtio-flush-request-test"))] let (accepted_low, accepted_high) = virtio_mmio::select_experimental_write_features(offered_low, offered_high) .unwrap_or_else(|_| fail("write queue feature subset")); write32(base, 0x24, 0); write32(base, 0x20, accepted_low); write32(base, 0x24, 1); write32(base, 0x20, accepted_high); write32(base, 0x70, 0x0b); if read32(base, 0x70) != 0x0b { fail("write queue FEATURES_OK"); } #[cfg(feature = "virtio-flush-request-test")] { flush_negotiated = accepted_low & virtio_mmio::BLK_F_FLUSH != 0; } write32(base, 0x30, 0); if read32(base, 0x34) < 8 || read32(base, 0x44) != 0 { fail("write queue capacity/ready"); } write32(base, 0x38, 8); write64_register_pair(base, 0x80, desc); write64_register_pair(base, 0x90, avail); write64_register_pair(base, 0xa0, used); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x44, 1); if read32(base, 0x44) != 1 { fail("write QueueReady readback"); } lease .mark_ready() .unwrap_or_else(|_| fail("write DMA lease ready")); write32(base, 0x70, 0x0f); if read32(base, 0x70) != 0x0f { fail("write DRIVER_OK readback"); } core::ptr::write_volatile((avail + 2) as *mut u16, 1u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x50, 0); } let frequency: u64; unsafe { asm!("mrs {value}, CNTFRQ_EL0", value = out(reg) frequency, options(nomem, nostack, preserves_flags)) }; if cfg!(feature = "virtio-write-cancel-test") { // Cancellation is only a caller decision while this OUT is still // exposed. It cannot revoke device ownership or promise the sector // was not written. The lease remains active until MMIO reset reads 0. let timeout = virtio_ring::deadline_after(counter(), frequency / 4) .unwrap_or_else(|_| fail("write cancel deadline")); loop { let used_index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if used_index != 0 { fail("write cancel request completed before delay"); } if virtio_ring::deadline_expired(counter(), timeout) { break; } core::hint::spin_loop(); } let before_reset = allocator.remaining_pages(); let (available, used_index, pending_status) = unsafe { ( u16::from_le(core::ptr::read_volatile((avail + 2) as *const u16)), u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)), core::ptr::read_volatile(status_address as *const u8), ) }; if (available, used_index, pending_status) != (1, 0, 0xff) || lease.release(allocator) != Err(virtio_ring::Error::Active) || allocator.remaining_pages() != before_reset || before_reset.checked_add(4) != Some(free_before) { fail("write cancel active DMA ownership"); } unsafe { write32(base, 0x70, 0); } let reset_deadline = virtio_ring::deadline_after(counter(), frequency) .unwrap_or_else(|_| fail("write cancel reset deadline")); loop { let (status, ready) = unsafe { (read32(base, 0x70), read32(base, 0x44)) }; if status == 0 && ready == 0 { break; } if virtio_ring::deadline_expired(counter(), reset_deadline) { fail("write cancel reset acknowledgement timeout"); } core::hint::spin_loop(); } lease .mark_reset() .unwrap_or_else(|_| fail("write cancel lease reset")); lease .release(allocator) .unwrap_or_else(|_| fail("write cancel DMA release")); if allocator.remaining_pages() != free_before { fail("write cancel DMA frames leaked"); } write_str("TURKOS/WRITE-CANCEL: AVAILIDX=0x"); write_hex(available as usize); write_str(" USEDIDX=0x"); write_hex(used_index as usize); write_str(" PENDING=0x"); write_hex(pending_status as usize); write_str(" RELEASE-BLOCKED=0x1 RESET-ACK=0x1 DMA-RESTORED=0x1 DISK-OUTCOME=UNKNOWN\r\n"); write_line("TURKOS/WRITE-CANCEL: PASS"); park() } let deadline = virtio_ring::deadline_after(counter(), frequency) .unwrap_or_else(|_| fail("write completion deadline")); let used_index = loop { let index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if index != 0 { break index; } if virtio_ring::deadline_expired(counter(), deadline) { fail("write completion timeout"); } core::hint::spin_loop(); }; // SAFETY: completion is observed before reading the device-written used // entry/status. Request bytes remain leased until the device is reset. let (used_id, used_len, status) = unsafe { asm!("dmb oshld", options(nostack, preserves_flags)); ( u32::from_le(core::ptr::read_volatile((used + 4) as *const u32)), u32::from_le(core::ptr::read_volatile((used + 8) as *const u32)), core::ptr::read_volatile(status_address as *const u8), ) }; let completion = virtio_ring::validate_write_completion(used_index, used_id, used_len, status); #[cfg(feature = "virtio-write-status-test")] if completion != Err(virtio_ring::Error::IoError) { fail("expected backend write EIO status"); } #[cfg(not(feature = "virtio-write-status-test"))] if completion != Ok(()) { fail("write completion identity/status"); } let mut sum = 0usize; for index in 0..512usize { let byte = unsafe { core::ptr::read_volatile((data_address + index as u64) as *const u8) }; if byte != index as u8 ^ 0xa7 { fail("write input DMA buffer changed"); } sum += byte as usize; } if sum != 0xff00 { fail("write input checksum"); } if cfg!(feature = "virtio-crash-after-first-test") { write_str("TURKOS/CRASH-POINT: PHASE=AFTER-FIRST FIRST-USEDIDX=0x"); write_hex(used_index as usize); write_str(" SECOND-AVAILIDX=0x1 FLUSH-AVAILIDX=0x1\r\n"); park() } #[cfg(feature = "virtio-two-write-order-test")] let (second_used_index, second_used_id, second_used_len, second_status) = { // The first OUT is fully used before its descriptors and payload are // reused. Available slot 1 publishes the second OUT on the same LBA. unsafe { for index in 0..512usize { core::ptr::write_volatile( (data_address + index as u64) as *mut u8, virtio_ring::two_write_final_pattern_byte(index) .unwrap_or_else(|_| fail("second write pattern bounds")), ); } core::ptr::write_volatile(status_address as *mut u8, 0xff); core::ptr::write_volatile((avail + 6) as *mut u16, 0u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); core::ptr::write_volatile((avail + 2) as *mut u16, 2u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x50, 0); } let deadline = virtio_ring::deadline_after(counter(), frequency) .unwrap_or_else(|_| fail("second write deadline")); let second_index = loop { let current = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if current >= 2 { break current; } if virtio_ring::deadline_expired(counter(), deadline) { fail("second write timeout"); } core::hint::spin_loop(); }; let (id, length, result) = unsafe { asm!("dmb oshld", options(nostack, preserves_flags)); ( u32::from_le(core::ptr::read_volatile((used + 12) as *const u32)), u32::from_le(core::ptr::read_volatile((used + 16) as *const u32)), core::ptr::read_volatile(status_address as *const u8), ) }; if second_index != 2 || id != 0 || length != 1 || result != 0 { fail("second write completion identity/status"); } let mut second_sum = 0usize; for index in 0..512usize { let byte = unsafe { core::ptr::read_volatile((data_address + index as u64) as *const u8) }; if byte != virtio_ring::two_write_final_pattern_byte(index) .unwrap_or_else(|_| fail("second write pattern bounds")) { fail("second write input DMA buffer changed"); } second_sum += byte as usize; } if second_sum != 0xff00 { fail("second write input checksum"); } write_str("TURKOS/TWO-WRITE-SECOND: FIRST-USEDIDX=0x"); write_hex(used_index as usize); write_str(" SECOND-USEDIDX=0x"); write_hex(second_index as usize); write_str(" SECOND-ID=0x"); write_hex(id as usize); write_str(" SECOND-LEN=0x"); write_hex(length as usize); write_str(" SECOND-STATUS=0x"); write_hex(result as usize); write_str(" FIRST-XOR=0xa7 SECOND-XOR=0x3c SUM=0x"); write_hex(second_sum); write_str("\r\n"); if cfg!(feature = "virtio-crash-after-second-test") { write_str("TURKOS/CRASH-POINT: PHASE=AFTER-SECOND FIRST-USEDIDX=0x"); write_hex(used_index as usize); write_str(" SECOND-USEDIDX=0x"); write_hex(second_index as usize); write_str(" FLUSH-AVAILIDX=0x2\r\n"); park() } (second_index, id, length, result) }; #[cfg(feature = "virtio-flush-request-test")] let (flush_used_index, flush_used_id, flush_used_len, flush_status) = { #[cfg(feature = "virtio-two-write-order-test")] let last_write_status = second_status; #[cfg(not(feature = "virtio-two-write-order-test"))] let last_write_status = status; if virtio_ring::allow_flush_after_write(last_write_status, flush_negotiated) != Ok(()) { fail("flush submitted before successful write"); } let before_flush_avail = unsafe { u16::from_le(core::ptr::read_volatile((avail + 2) as *const u16)) }; #[cfg(feature = "virtio-two-write-order-test")] let expected_before_flush = 2; #[cfg(not(feature = "virtio-two-write-order-test"))] let expected_before_flush = 1; if before_flush_avail != expected_before_flush { fail("flush available index before write completion"); } write_str("TURKOS/FLUSH-ORDER: WRITE-USEDIDX=0x"); write_hex(used_index as usize); write_str(" WRITE-STATUS=0x"); write_hex(status as usize); write_str(" FLUSH-AVAILIDX=0x"); write_hex(before_flush_avail as usize); write_str("\r\n"); let flush_header = virtio_ring::dma_subregion(request, 1024, 16) .unwrap_or_else(|_| fail("flush header range")); let flush_status_addr = virtio_ring::dma_subregion(request, 1040, 1) .unwrap_or_else(|_| fail("flush status range")); let chain = virtio_ring::flush_descriptor_chain(flush_header, flush_status_addr); unsafe { for (index, descriptor) in chain.into_iter().enumerate() { core::ptr::write_volatile( (desc as *mut virtio_ring::Descriptor).add(index + 3), descriptor, ); } core::ptr::write_volatile( flush_header as *mut virtio_ring::BlockReadHeader, virtio_ring::block_flush_header(), ); core::ptr::write_volatile(flush_status_addr as *mut u8, 0xff); #[cfg(feature = "virtio-two-write-order-test")] core::ptr::write_volatile((avail + 8) as *mut u16, 3u16.to_le()); #[cfg(not(feature = "virtio-two-write-order-test"))] core::ptr::write_volatile((avail + 6) as *mut u16, 3u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); #[cfg(feature = "virtio-two-write-order-test")] core::ptr::write_volatile((avail + 2) as *mut u16, 3u16.to_le()); #[cfg(not(feature = "virtio-two-write-order-test"))] core::ptr::write_volatile((avail + 2) as *mut u16, 2u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x50, 0); } let deadline = virtio_ring::deadline_after(counter(), frequency) .unwrap_or_else(|_| fail("flush completion deadline")); let index = loop { let current = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if current >= expected_before_flush + 1 { break current; } if virtio_ring::deadline_expired(counter(), deadline) { fail("flush completion timeout"); } core::hint::spin_loop(); }; let (id, length, result) = unsafe { asm!("dmb oshld", options(nostack, preserves_flags)); ( u32::from_le(core::ptr::read_volatile( (used + 4 + (expected_before_flush as u64) * 8) as *const u32, )), u32::from_le(core::ptr::read_volatile( (used + 8 + (expected_before_flush as u64) * 8) as *const u32, )), core::ptr::read_volatile(flush_status_addr as *const u8), ) }; if index != expected_before_flush + 1 || id != 3 || length != 1 || virtio_ring::read_block_status(result) != Ok(()) { fail("flush completion identity/status"); } (index, id, length, result) }; unsafe { write32(base, 0x70, 0); if read32(base, 0x70) != 0 || read32(base, 0x44) != 0 { fail("write queue final reset"); } } lease .mark_reset() .unwrap_or_else(|_| fail("write DMA lease reset")); lease .release(allocator) .unwrap_or_else(|_| fail("write DMA lease release")); if allocator.remaining_pages() != free_before { fail("write DMA frames leaked"); } #[cfg(feature = "virtio-write-status-test")] { write_str("TURKOS/WRITE-STATUS: SECTOR=0x"); write_hex(2); write_str(" USEDIDX=0x"); write_hex(used_index as usize); write_str(" USEDID=0x"); write_hex(used_id as usize); write_str(" USEDLEN=0x"); write_hex(used_len as usize); write_str(" STATUS=0x"); write_hex(status as usize); write_str(" CLASS=EIO DATA-ACCEPTED=0x0 SUM=0x"); write_hex(sum); write_str(" DMA-RESTORED=0x1\r\n"); write_line("TURKOS/WRITE-STATUS: PASS"); park() } #[cfg(not(feature = "virtio-write-status-test"))] { write_str("TURKOS/WRITE-DESC: TYPE=0x"); write_hex(1); write_str(" SECTOR=0x"); write_hex(2); write_str(" DATA-FLAGS=0x"); write_hex(virtio_ring::DESC_F_NEXT as usize); write_str(" STATUS-FLAGS=0x"); write_hex(virtio_ring::DESC_F_WRITE as usize); write_str(" USEDIDX=0x"); write_hex(used_index as usize); write_str(" USEDID=0x"); write_hex(used_id as usize); write_str(" USEDLEN=0x"); write_hex(used_len as usize); write_str(" STATUS=0x"); write_hex(status as usize); write_str(" SUM=0x"); write_hex(sum); write_str(" DMA-RESTORED=0x"); write_hex(1); write_str("\r\n"); write_line("TURKOS/WRITE-DESC: PASS"); #[cfg(feature = "virtio-flush-request-test")] { write_str("TURKOS/FLUSH-REQUEST: WRITE-USEDIDX=0x"); write_hex(used_index as usize); write_str(" WRITE-STATUS=0x"); write_hex(status as usize); write_str(" FLUSH-USEDIDX=0x"); write_hex(flush_used_index as usize); write_str(" FLUSH-ID=0x"); write_hex(flush_used_id as usize); write_str(" FLUSH-LEN=0x"); write_hex(flush_used_len as usize); write_str(" FLUSH-STATUS=0x"); write_hex(flush_status as usize); write_str(" ORDERED=0x1 DMA-RESTORED=0x1\r\n"); write_line("TURKOS/FLUSH-REQUEST: PASS"); #[cfg(feature = "virtio-two-write-order-test")] { write_str("TURKOS/TWO-WRITE-ORDER: FIRST-USEDIDX=0x"); write_hex(used_index as usize); write_str(" SECOND-USEDIDX=0x"); write_hex(second_used_index as usize); write_str(" SECOND-ID=0x"); write_hex(second_used_id as usize); write_str(" SECOND-LEN=0x"); write_hex(second_used_len as usize); write_str(" SECOND-STATUS=0x"); write_hex(second_status as usize); write_str(" FLUSH-USEDIDX=0x"); write_hex(flush_used_index as usize); write_str(" FLUSH-STATUS=0x"); write_hex(flush_status as usize); write_str(" DMA-RESTORED=0x1\r\n"); write_line("TURKOS/TWO-WRITE-ORDER: PASS"); } park() } #[cfg(feature = "virtio-write-readback-test")] write_readback_smoke(base, boot, allocator); #[cfg(not(any( feature = "virtio-write-readback-test", feature = "virtio-flush-request-test" )))] park() } } #[cfg(all( feature = "virtio-flush-gate-test", not(feature = "virtio-flush-request-test") ))] fn flush_gate_smoke(base: u64, allocator: &mut memory::FrameAllocator) -> ! { // K387 only measures a disposable device's feature contract. No FLUSH // request is submitted and no durability guarantee is inferred here. let free_before = allocator.remaining_pages(); unsafe { write32(base, 0x70, 0); if read32(base, 0x70) != 0 || read32(base, 0x44) != 0 { fail("flush gate initial reset"); } write32(base, 0x70, 3); write32(base, 0x14, 0); let offered_low = read32(base, 0x10); write32(base, 0x14, 1); let offered_high = read32(base, 0x10); let supported = offered_low & virtio_mmio::BLK_F_FLUSH != 0; let selected = virtio_mmio::select_experimental_flush_features(offered_low, offered_high); let mut accepted = false; if supported { let (low, high) = selected.unwrap_or_else(|_| fail("flush gate feature subset")); write32(base, 0x24, 0); write32(base, 0x20, low); write32(base, 0x24, 1); write32(base, 0x20, high); write32(base, 0x70, 0x0b); if read32(base, 0x70) != 0x0b { fail("flush gate FEATURES_OK readback"); } accepted = true; } else if selected != Err(virtio_mmio::Error::MissingRequiredFeature) { fail("unsupported flush was accepted"); } if read32(base, 0x44) != 0 || allocator.remaining_pages() != free_before { fail("flush gate unexpectedly allocated queue/DMA"); } write32(base, 0x70, 0); if read32(base, 0x70) != 0 || read32(base, 0x44) != 0 { fail("flush gate final reset"); } write_str("TURKOS/FLUSH-GATE: OFFERED=0x"); write_hex(supported as usize); write_str(" ACCEPTED=0x"); write_hex(accepted as usize); write_str(" FEATURES-OK=0x"); write_hex(accepted as usize); write_str(" READY=0x0 DMA=0x0 REQUESTS=0x0 RESET=0x1 GUARANTEE=0x0\r\n"); write_line("TURKOS/FLUSH-GATE: PASS"); park() } } #[cfg(feature = "virtio-write-gate-test")] fn write_gate_smoke(base: u64, _boot: &BootData, allocator: &mut memory::FrameAllocator) -> ! { let free_before = allocator.remaining_pages(); // K381 stops at FEATURES_OK. No queue allocation, request header, // descriptor publication, notify or disk write exists on this branch. unsafe { write32(base, 0x70, 0); if read32(base, 0x70) != 0 { fail("write gate reset"); } write32(base, 0x70, 3); // ACKNOWLEDGE | DRIVER if read32(base, 0x70) != 3 { fail("write gate driver status"); } write32(base, 0x14, 0); let offered_low = read32(base, 0x10); write32(base, 0x14, 1); let offered_high = read32(base, 0x10); let (accepted_low, accepted_high) = virtio_mmio::select_experimental_write_features(offered_low, offered_high) .unwrap_or_else(|_| fail("write gate requires writable modern device")); write32(base, 0x24, 0); write32(base, 0x20, accepted_low); write32(base, 0x24, 1); write32(base, 0x20, accepted_high); write32(base, 0x70, 0x0b); // FEATURES_OK, no DRIVER_OK if read32(base, 0x70) != 0x0b || read32(base, 0x44) != 0 || allocator.remaining_pages() != free_before { fail("write gate negotiation or queue changed"); } write32(base, 0x70, 0); if read32(base, 0x70) != 0 || read32(base, 0x44) != 0 { fail("write gate final reset"); } write_str("TURKOS/WRITE-GATE: RO=0x"); write_hex((offered_low & virtio_mmio::BLK_F_RO) as usize); write_str(" LOW=0x"); write_hex(accepted_low as usize); write_str(" HIGH=0x"); write_hex(accepted_high as usize); write_str(" FEATURES-OK=0x"); write_hex(1); write_str(" READY=0x"); write_hex(0); write_str(" DMA=0x"); write_hex((free_before - allocator.remaining_pages()) as usize); write_str(" REQUESTS=0x"); write_hex(0); write_str(" RESET=0x"); write_hex(1); write_str("\r\n"); write_line("TURKOS/WRITE-GATE: PASS"); #[cfg(feature = "virtio-write-header-test")] { // K383 prepares one sector's OUT header only in this nested // experiment image. No DMA lease, descriptor, or notify follows. let header = virtio_ring::block_write_header(2); let bytes = core::slice::from_raw_parts( (&header as *const virtio_ring::BlockReadHeader).cast::(), 16, ); if bytes != [1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0] || allocator.remaining_pages() != free_before { fail("write header wire bytes"); } write_str("TURKOS/WRITE-HEADER: TYPE=0x"); write_hex(u32::from_le(header.type_le) as usize); write_str(" RESERVED=0x"); write_hex(u32::from_le(header.reserved_le) as usize); write_str(" SECTOR=0x"); write_hex(u64::from_le(header.sector_le) as usize); write_str(" BYTES=0x"); write_hex(bytes.len()); write_str(" QUEUE=0x"); write_hex(0); write_str(" REQUESTS=0x"); write_hex(0); write_str("\r\n"); write_line("TURKOS/WRITE-HEADER: PASS"); } } #[cfg(all( feature = "virtio-flush-gate-test", not(feature = "virtio-flush-request-test") ))] flush_gate_smoke(base, allocator); #[cfg(feature = "virtio-write-descriptor-test")] write_descriptor_smoke(base, _boot, allocator); #[cfg(not(any( feature = "virtio-write-descriptor-test", feature = "virtio-flush-gate-test" )))] 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("MMIO window"); } let mut block = None; #[cfg(feature = "virtio-disk-absence-test")] let mut observed_ids = [0u32; 32]; #[cfg(feature = "virtio-disk-absence-test")] let free_before_scan = allocator.remaining_pages(); for (index, node) in boot.virtio_mmio.as_slice().iter().enumerate() { #[cfg(not(feature = "virtio-disk-absence-test"))] let _ = index; let base = node.base; // SAFETY: DTB range validation and MMU Device mapping precede entry. let (magic, version, id) = unsafe { (read32(base, 0), read32(base, 4), read32(base, 8)) }; if magic != virtio_mmio::MAGIC || version != 2 { fail("modern transport identity"); } if id == 2 { if block.replace(base).is_some() { fail("duplicate block device"); } } else if id != 0 && !(cfg!(feature = "virtio-disk-absence-test") && id == 4) { fail("unexpected device"); } #[cfg(feature = "virtio-disk-absence-test")] { if index >= observed_ids.len() { fail("too many virtio MMIO slots for absence fixture"); } observed_ids[index] = id; } } #[cfg(feature = "virtio-disk-absence-test")] { let count = boot.virtio_mmio.as_slice().len(); let summary = virtio_mmio::classify_block_absence(&observed_ids[..count]) .unwrap_or_else(|_| fail("block device unexpectedly present")); if block.is_some() || allocator.remaining_pages() != free_before_scan { fail("disk absence changed allocator state"); } write_line("TURKOS/SERIAL-ALIVE: DISK-OPTIONAL"); write_str("TURKOS/VIRTIO-NO-DISK: SLOTS=0x"); write_hex(summary.slots); write_str(" EMPTY=0x"); write_hex(summary.empty); write_str(" OTHER=0x"); write_hex(summary.other_devices); write_str(" BLOCK=0x"); write_hex(0); write_str(" DMA-PAGES=0x"); write_hex(0); write_str(" FREE-BEFORE=0x"); write_hex(free_before_scan as usize); write_str(" FREE-AFTER=0x"); write_hex(allocator.remaining_pages() as usize); write_str(" SERIAL=0x"); write_hex(1); write_str("\r\n"); write_line("TURKOS/VIRTIO-NO-DISK: PASS"); park() } #[cfg(feature = "virtio-metadata-order-test")] metadata_probe::smoke_test( block.unwrap_or_else(|| fail("block device missing")), boot, allocator, ); #[cfg(feature = "virtio-io-retry-test")] retry_probe::smoke_test( block.unwrap_or_else(|| fail("block device missing")), boot, allocator, ); #[cfg(feature = "virtio-write-gate-test")] { write_gate_smoke( block.unwrap_or_else(|| fail("block device missing")), boot, allocator, ) } #[cfg(feature = "virtio-flush-reopen-test")] write_readback_smoke( block.unwrap_or_else(|| fail("block device missing")), boot, allocator, ); #[cfg(all( not(feature = "virtio-disk-absence-test"), not(feature = "virtio-write-gate-test"), not(feature = "virtio-flush-reopen-test"), not(feature = "virtio-metadata-order-test"), not(feature = "virtio-io-retry-test") ))] { let base = block.unwrap_or_else(|| fail("block device missing")); // Modern virtio-blk config starts at MMIO +0x100 with a little-endian // 64-bit capacity measured in 512-byte sectors. let capacity_sectors = unsafe { u64::from(read32(base, 0x100)) | (u64::from(read32(base, 0x104)) << 32) }; let capacity_bytes = virtio_mmio::capacity_bytes(capacity_sectors) .unwrap_or_else(|_| fail("zero or overflowing block capacity")); if virtio_mmio::sector_byte_offset(1, capacity_sectors) != Ok(512) { fail("second sector outside capacity"); } write_str("TURKOS/VIRTIO-CAPACITY: SECTORS=0x"); write_hex(capacity_sectors as usize); write_str(" BYTES=0x"); write_hex(capacity_bytes as usize); write_str(" LOGICAL-SECTOR=0x"); write_hex(512); write_str(" LAST=0x"); write_hex((capacity_sectors - 1) as usize); write_str("\r\n"); let free_before = allocator.remaining_pages(); let mut lease = virtio_ring::DmaLease::allocate( allocator, boot.hardware.ram_base, boot.hardware.ram_size, ) .unwrap_or_else(|_| fail("DMA frame lease")); let [desc, avail, used, request] = lease.frames().unwrap_or_else(|_| fail("DMA frames")); let rings = virtio_ring::split_ring_pages( desc, avail, used, 256, boot.hardware.ram_base, boot.hardware.ram_size, ) .unwrap_or_else(|_| fail("split ring geometry")); virtio_ring::descriptor_page(request, 1, boot.hardware.ram_base, boot.hardware.ram_size) .unwrap_or_else(|_| fail("request RAM frame")); if request == desc || request == avail || request == used || allocator.remaining_pages().checked_add(4) != Some(free_before) || rings.available_bytes != 516 || rings.used_bytes != 2052 { fail("split ring ownership"); } let header_address = virtio_ring::dma_subregion(request, 0, 16).unwrap_or_else(|_| fail("header DMA range")); let status_address = virtio_ring::dma_subregion(request, 16, 1).unwrap_or_else(|_| fail("status DMA range")); let data_address = virtio_ring::dma_subregion(request, 512, 512) .unwrap_or_else(|_| fail("sector DMA range")); let mut admission = virtio_ring::QueueAdmission::new(rings.count as u16) .unwrap_or_else(|_| fail("queue admission capacity")); if admission.reserve(3) != Ok(0) { fail("first descriptor chain admission"); } // SAFETY: all four distinct 4 KiB frames are exclusively owned, mapped as // normal writable identity RAM, and are not yet exposed to the device. unsafe { for frame in [desc, avail, used, request] { core::ptr::write_bytes(frame as *mut u8, 0, memory::PAGE_SIZE as usize); } let first = desc as *mut virtio_ring::Descriptor; let chain = virtio_ring::read_descriptor_chain(header_address, data_address, status_address); for (index, descriptor) in chain.into_iter().enumerate() { core::ptr::write_volatile(first.add(index), descriptor); } core::ptr::write_volatile( request as *mut virtio_ring::BlockReadHeader, virtio_ring::block_read_header(0), ); core::ptr::write_volatile(status_address as *mut u8, 0xff); // pending core::ptr::write_volatile((avail + 4) as *mut u16, 0u16.to_le()); // descriptor 0 } let request_header = unsafe { core::ptr::read_volatile(request as *const virtio_ring::BlockReadHeader) }; let request_data_desc = unsafe { core::ptr::read_volatile((desc as *const virtio_ring::Descriptor).add(1)) }; let request_status_desc = unsafe { core::ptr::read_volatile((desc as *const virtio_ring::Descriptor).add(2)) }; let status_pending = unsafe { core::ptr::read_volatile(status_address as *const u8) }; if u32::from_le(request_header.type_le) != 0 || request_header.reserved_le != 0 || u64::from_le(request_header.sector_le) != 0 || u32::from_le(request_data_desc.len_le) != 512 || u16::from_le(request_data_desc.flags_le) != (virtio_ring::DESC_F_NEXT | virtio_ring::DESC_F_WRITE) || u32::from_le(request_status_desc.len_le) != 1 || u16::from_le(request_status_desc.flags_le) != virtio_ring::DESC_F_WRITE || status_pending != 0xff { fail("read request descriptor contract"); } // Preflight all remaining descriptor credits while the real three-entry // chain stays in owned RAM. No 256-request I/O workload is implied. let first = desc as *const virtio_ring::Descriptor; let before = unsafe { [ core::ptr::read_volatile(first), core::ptr::read_volatile(first.add(1)), core::ptr::read_volatile(first.add(2)), ] }; if admission.reserve(253) != Ok(3) || admission.reserve(1) != Err(virtio_ring::Error::Full) || admission.occupied() != 256 || admission.remaining() != 0 { fail("full queue descriptor admission"); } let after = unsafe { [ core::ptr::read_volatile(first), core::ptr::read_volatile(first.add(1)), core::ptr::read_volatile(first.add(2)), ] }; if before != after { fail("full queue overwrote live descriptors"); } // SAFETY: all register offsets are modern-v2 aligned 32-bit controls in // the selected, Device-mapped 0x200-byte MMIO slot. let first_features = unsafe { write32(base, 0x70, 0); if read32(base, 0x70) != 0 { fail("reset"); } write32(base, 0x70, 1); if read32(base, 0x70) != 1 { fail("ack"); } write32(base, 0x70, 3); if read32(base, 0x70) != 3 { fail("driver"); } write32(base, 0x14, 0); let offered_low = read32(base, 0x10); write32(base, 0x14, 1); let offered_high = read32(base, 0x10); if offered_high & virtio_mmio::MODERN_F_IOMMU_PLATFORM != 0 { fail("IOMMU fixture unexpectedly enabled"); } let (accepted_low, accepted_high) = virtio_mmio::select_modern_read_only_features(offered_low, offered_high) .unwrap_or_else(|_| fail("read-only modern feature missing")); write32(base, 0x24, 0); write32(base, 0x20, accepted_low); write32(base, 0x24, 1); write32(base, 0x20, accepted_high); write32(base, 0x70, 0x0b); if read32(base, 0x70) != 0x0b { fail("FEATURES_OK"); } // K359: RO is a negotiated device contract, not just a host file // option. Reject the write opcode before QueueReady or notify. let emitted_type = virtio_mmio::read_only_request_type( virtio_mmio::BlockOperation::Read, accepted_low, ) .unwrap_or_else(|_| fail("read-only read type")); if offered_low & virtio_mmio::BLK_F_RO == 0 || accepted_low != virtio_mmio::BLK_F_RO || virtio_mmio::read_only_request_type( virtio_mmio::BlockOperation::Write, accepted_low, ) != Err(virtio_mmio::Error::ReadOnly) || emitted_type != 0 || u32::from_le(core::ptr::read_volatile(header_address as *const u32)) != emitted_type || u16::from_le(core::ptr::read_volatile((avail + 2) as *const u16)) != 0 || u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) != 0 || read32(base, 0x44) != 0 { fail("read-only write preflight"); } write_str("TURKOS/VIRTIO-RO-GATE: OFFERED=0x"); write_hex(1); write_str(" ACCEPTED=0x"); write_hex(1); write_str(" REQUESTED-WRITE-TYPE=0x"); write_hex(1); write_str(" WRITE-REJECTED=0x"); write_hex(1); write_str(" EMITTED-TYPE=0x"); write_hex(emitted_type as usize); write_str(" AVAILIDX=0x"); write_hex(0); write_str(" USEDIDX=0x"); write_hex(0); write_str(" READY=0x"); write_hex(0); write_str("\r\n"); write_line("TURKOS/VIRTIO-RO-GATE: PASS"); write32(base, 0x30, 0); if read32(base, 0x44) != 0 || read32(base, 0x34) != 256 { fail("queue precondition"); } write32(base, 0x38, 256); write64_register_pair(base, 0x80, desc); write64_register_pair(base, 0x90, avail); write64_register_pair(base, 0xa0, used); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x44, 1); if read32(base, 0x44) != 1 { fail("QueueReady readback"); } lease .mark_ready() .unwrap_or_else(|_| fail("DMA lease ready")); if lease.release(allocator) != Err(virtio_ring::Error::Active) { fail("active DMA frames released"); } let tags = lease .tagged_frames() .unwrap_or_else(|_| fail("DMA role tags")); if tags.map(|tag| tag.role) != [ virtio_ring::DmaFrameRole::Descriptor, virtio_ring::DmaFrameRole::Available, virtio_ring::DmaFrameRole::Used, virtio_ring::DmaFrameRole::RequestData, ] || tags.map(|tag| tag.address) != [desc, avail, used, request] || tags.iter().any(|tag| !tag.live_queue) { fail("DMA role handoff mismatch"); } write_str("TURKOS/VIRTIO-DMA-TAGS: DESC=0x"); write_hex(tags[0].address as usize); write_str(" AVAIL=0x"); write_hex(tags[1].address as usize); write_str(" USED=0x"); write_hex(tags[2].address as usize); write_str(" REQUEST=0x"); write_hex(tags[3].address as usize); write_str(" STATE=DEVICE\r\n"); #[cfg(not(feature = "virtio-queue-timeout-test"))] { write32(base, 0x70, 0x0f); if read32(base, 0x70) != 0x0f { fail("DRIVER_OK readback"); } } #[cfg(feature = "virtio-queue-timeout-test")] if read32(base, 0x70) != 0x0b { fail("negative fixture must remain before DRIVER_OK"); } core::ptr::write_volatile((avail + 2) as *mut u16, 1u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); #[cfg(not(feature = "virtio-queue-timeout-test"))] write32(base, 0x50, 0); (offered_low, offered_high, accepted_low, accepted_high) }; write_str("TURKOS/VIRTIO-TRACE: OFFEREDLOW=0x"); write_hex(first_features.0 as usize); write_str(" OFFEREDHIGH=0x"); write_hex(first_features.1 as usize); write_str(" ACCEPTEDLOW=0x"); write_hex(first_features.2 as usize); write_str(" ACCEPTEDHIGH=0x"); write_hex(first_features.3 as usize); write_str(" DESC=0x"); write_hex(desc as usize); write_str(" AVAIL=0x"); write_hex(avail as usize); write_str(" USED=0x"); write_hex(used as usize); write_str(" QNUM=0x"); write_hex(rings.count as usize); write_str(" DRIVERSTATUS=0x"); write_hex(if cfg!(feature = "virtio-queue-timeout-test") { 0x0b } else { 0x0f }); write_str("\r\n"); let frequency: u64; // SAFETY: physical counter frequency is readable at EL1. unsafe { asm!("mrs {value}, CNTFRQ_EL0", value = out(reg) frequency, options(nomem, nostack, preserves_flags)) }; let start = counter(); let interval = if cfg!(feature = "virtio-queue-timeout-test") { frequency / 4 } else { frequency .checked_mul(2) .unwrap_or_else(|| fail("timeout interval overflow")) }; let deadline = virtio_ring::deadline_after(start, interval) .unwrap_or_else(|_| fail("timeout deadline overflow")); let used_index = loop { // SAFETY: the owned used ring remains mapped while QEMU owns the queue. let index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if index != 0 { if cfg!(feature = "virtio-queue-timeout-test") { fail("completion without notification"); } break index; } let now = counter(); if virtio_ring::deadline_expired(now, deadline) { #[cfg(feature = "virtio-queue-timeout-test")] { // The available entry was published, but the test deliberately // withheld QueueNotify. This proves the guest's timeout exit, // not a physical-device failure mode. let pending = unsafe { core::ptr::read_volatile(status_address as *const u8) }; if pending != 0xff { fail("unnotified request status changed"); } write_str("TURKOS/VIRTIO-TIMEOUT: START=0x"); write_hex(start as usize); write_str(" DEADLINE=0x"); write_hex(deadline as usize); write_str(" NOW=0x"); write_hex(now as usize); write_str(" FREQ=0x"); write_hex(frequency as usize); write_str(" USEDIDX=0x"); write_hex(index as usize); write_str(" STATUS=0x"); write_hex(pending as usize); write_str(" NOTIFIED=0x"); write_hex(0); write_str(" DRIVERSTATUS=0x"); write_hex(0x0b); write_str("\r\n"); unsafe { write32(base, 0x70, 0); if read32(base, 0x70) != 0 || read32(base, 0x44) != 0 { fail("timeout reset readback"); } } lease .mark_reset() .unwrap_or_else(|_| fail("timeout lease reset")); lease .release(allocator) .unwrap_or_else(|_| fail("timeout lease release")); if allocator.remaining_pages() != free_before { fail("timeout DMA frames leaked"); } write_line("TURKOS/VIRTIO-TIMEOUT: PASS"); park() } #[cfg(not(feature = "virtio-queue-timeout-test"))] fail("completion timeout"); } core::hint::spin_loop(); }; // SAFETY: pair with device writes before reading used entry and payload. unsafe { asm!("dmb oshld", options(nostack, preserves_flags)) }; // SAFETY: used entry and request bytes are inside owned frames. let (used_id, used_len, status) = unsafe { ( u32::from_le(core::ptr::read_volatile((used + 4) as *const u32)), u32::from_le(core::ptr::read_volatile((used + 8) as *const u32)), core::ptr::read_volatile(status_address as *const u8), ) }; if cfg!(feature = "virtio-partial-completion-test") { // The real device completed all 512 bytes first. Deliberately // corrupt only our owned used-ring length to model a 256-byte // completion record. This is not evidence of physical half-DMA. if used_index != 1 || used_id != 0 || used_len != 513 || status != 0 { fail("partial fixture needs a real full completion"); } unsafe { core::ptr::write_volatile((used + 8) as *mut u32, 257u32.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); } let short_len = unsafe { u32::from_le(core::ptr::read_volatile((used + 8) as *const u32)) }; let held_pages = allocator.remaining_pages(); if short_len != 257 || virtio_ring::validate_read_completion_length(short_len, 512) != Err(virtio_ring::Error::ShortResponse) || lease.release(allocator) != Err(virtio_ring::Error::Active) || allocator.remaining_pages() != held_pages || held_pages.checked_add(4) != Some(free_before) { fail("partial completion exposed data or released active DMA"); } unsafe { write32(base, 0x70, 0); if read32(base, 0x70) != 0 || read32(base, 0x44) != 0 { fail("partial completion queue reset"); } } lease .mark_reset() .unwrap_or_else(|_| fail("partial lease reset")); lease .release(allocator) .unwrap_or_else(|_| fail("partial lease release")); if allocator.remaining_pages() != free_before { fail("partial completion leaked DMA pages"); } write_str("TURKOS/DMA-PARTIAL: ACTUAL=0x"); write_hex(used_len as usize); write_str(" INJECTED=0x"); write_hex(short_len as usize); write_str(" USEDIDX=0x"); write_hex(used_index as usize); write_str(" STATUS=0x"); write_hex(status as usize); write_str(" FREE-BEFORE=0x"); write_hex(free_before as usize); write_str(" FREE-HELD=0x"); write_hex(held_pages as usize); write_str(" FREE-AFTER=0x"); write_hex(allocator.remaining_pages() as usize); write_str( " CLASS=SHORT DATA-ACCEPTED=0x0 ACTIVE-RELEASE=BLOCKED OWNED=0x4 RESET-ACK=0x1 RESTORED=0x4\r\n", ); write_line("TURKOS/DMA-PARTIAL: PASS"); park(); } if used_index != 1 || used_id != 0 || used_len != 513 || status != 0 { fail("used completion identity/status"); } let mut checksum = 0u64; let mut observed_sector = [0u8; 512]; for i in 0..512usize { // SAFETY: the 512-byte data buffer is entirely inside owned request RAM. let byte = unsafe { core::ptr::read_volatile((data_address + i as u64) as *const u8) }; if byte != ((i as u8) ^ 0xa5) { fail("sector payload mismatch"); } observed_sector[i] = byte; checksum += u64::from(byte); } let sector_hash = turkos_kernel::program_hash::hex_lower(&virtio_ring::sector_sha256(&observed_sector)); if sector_hash != *b"755929613d018a9cb308fd873b7b5b53078961f9be477d3650fa0710b350ad80" { fail("sector SHA-256 mismatch"); } // The used-ring write should also raise modern MMIO interrupt status bit // 0. Acknowledge only that source; never clear configuration change bit 1. let interrupt_before = unsafe { read32(base, 0x60) }; let interrupt_ack = virtio_mmio::used_interrupt_ack_mask(interrupt_before) .unwrap_or_else(|_| fail("unexpected interrupt source")); unsafe { write32(base, 0x64, interrupt_ack) }; let interrupt_after = unsafe { read32(base, 0x60) }; if interrupt_after & 1 != 0 || interrupt_after & 2 != interrupt_before & 2 { fail("used interrupt acknowledge"); } write_str("TURKOS/VIRTIO-QUEUE: DESC=0x"); write_hex(desc as usize); write_str(" AVAIL=0x"); write_hex(avail as usize); write_str(" USED=0x"); write_hex(used as usize); write_str(" REQUEST=0x"); write_hex(request as usize); write_str(" QNUM=0x"); write_hex(rings.count as usize); write_str(" AVAILBYTES=0x"); write_hex(rings.available_bytes as usize); write_str(" USEDBYTES=0x"); write_hex(rings.used_bytes as usize); write_str(" READY=0x"); write_hex(1); write_str(" DRIVERSTATUS=0x"); write_hex(0x0f); write_str(" AVAILIDX=0x"); write_hex(1); write_str(" USEDIDX=0x"); write_hex(used_index as usize); write_str(" USEDID=0x"); write_hex(used_id as usize); write_str(" USEDLEN=0x"); write_hex(used_len as usize); write_str(" STATUS=0x"); write_hex(status as usize); write_str(" SUM=0x"); write_hex(checksum as usize); write_str("\r\n"); write_str("TURKOS/VIRTIO-DMA-ADDR: HEADER=0x"); write_hex(header_address as usize); write_str(" DATA=0x"); write_hex(data_address as usize); write_str(" STATUS=0x"); write_hex(status_address as usize); write_str("\r\n"); write_line("TURKOS/VIRTIO-DMA-ADDR: PASS"); write_str("TURKOS/VIRTIO-BLK-REQ: TYPE=0x"); write_hex(u32::from_le(request_header.type_le) as usize); write_str(" SECTOR=0x"); write_hex(u64::from_le(request_header.sector_le) as usize); write_str(" HEADER-BYTES=0x"); write_hex(core::mem::size_of::()); write_str(" DATA-FLAGS=0x"); write_hex(u16::from_le(request_data_desc.flags_le) as usize); write_str(" DATA-BYTES=0x"); write_hex(u32::from_le(request_data_desc.len_le) as usize); write_str(" STATUS-FLAGS=0x"); write_hex(u16::from_le(request_status_desc.flags_le) as usize); write_str(" STATUS-BYTES=0x"); write_hex(u32::from_le(request_status_desc.len_le) as usize); write_str(" STATUS-BEFORE=0x"); write_hex(status_pending as usize); write_str(" STATUS-AFTER=0x"); write_hex(status as usize); write_str("\r\n"); write_line("TURKOS/VIRTIO-BLK-REQ: PASS"); write_str("TURKOS/VIRTIO-SECTOR-HASH: LBA=0x"); write_hex(0); write_str(" BYTES=0x"); write_hex(observed_sector.len()); write_str(" SHA256="); write_str( core::str::from_utf8(§or_hash).unwrap_or_else(|_| fail("sector hash encoding")), ); write_str("\r\n"); write_line("TURKOS/VIRTIO-SECTOR-HASH: PASS"); write_str("TURKOS/VIRTIO-IRQSTATUS: BEFORE=0x"); write_hex(interrupt_before as usize); write_str(" ACK=0x"); write_hex(interrupt_ack as usize); write_str(" AFTER=0x"); write_hex(interrupt_after as usize); write_str("\r\n"); write_line("TURKOS/VIRTIO-IRQSTATUS: PASS"); write_line( "TURKOS/VIRTIO-FULL: CAP=0x100 OCCUPIED=0x100 REMAINING=0x0 BLOCKED=0x1 DESC-UNCHANGED=0x1", ); write_line("TURKOS/VIRTIO-FULL: PASS"); // K339: reset the completed queue while retaining all four leased DMA // frames, scrub stale ring/request bytes, and build a fresh sector-1 read. unsafe { write32(base, 0x70, 0); if read32(base, 0x70) != 0 || read32(base, 0x44) != 0 { fail("requeue first reset readback"); } } lease .mark_reset() .unwrap_or_else(|_| fail("requeue lease reset")); admission.reset_epoch_after_device_reset(); if admission.reserve(3) != Ok(0) { fail("requeue descriptor admission"); } unsafe { for frame in [desc, avail, used, request] { core::ptr::write_bytes(frame as *mut u8, 0, memory::PAGE_SIZE as usize); } } let stale_cleared = unsafe { core::ptr::read_volatile(desc as *const u64) == 0 && core::ptr::read_volatile((avail + 2) as *const u16) == 0 && core::ptr::read_volatile((used + 2) as *const u16) == 0 && core::ptr::read_volatile(data_address as *const u8) == 0 }; if !stale_cleared { fail("old queue bytes survived reset"); } unsafe { let first = desc as *mut virtio_ring::Descriptor; let chain = virtio_ring::read_descriptor_chain(header_address, data_address, status_address); for (index, descriptor) in chain.into_iter().enumerate() { core::ptr::write_volatile(first.add(index), descriptor); } core::ptr::write_volatile( request as *mut virtio_ring::BlockReadHeader, virtio_ring::block_read_header(1), ); core::ptr::write_volatile(status_address as *mut u8, 0xff); core::ptr::write_volatile((avail + 4) as *mut u16, 0u16.to_le()); write32(base, 0x70, 1); write32(base, 0x70, 3); write32(base, 0x14, 0); let offered_low = read32(base, 0x10); write32(base, 0x14, 1); let offered_high = read32(base, 0x10); let (accepted_low, accepted_high) = virtio_mmio::select_modern_read_only_features(offered_low, offered_high) .unwrap_or_else(|_| fail("requeue feature subset")); write32(base, 0x24, 0); write32(base, 0x20, accepted_low); write32(base, 0x24, 1); write32(base, 0x20, accepted_high); write32(base, 0x70, 0x0b); if read32(base, 0x70) != 0x0b { fail("requeue FEATURES_OK"); } write32(base, 0x30, 0); if read32(base, 0x44) != 0 || read32(base, 0x34) != 256 { fail("requeue initial queue state"); } write32(base, 0x38, 256); write64_register_pair(base, 0x80, desc); write64_register_pair(base, 0x90, avail); write64_register_pair(base, 0xa0, used); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x44, 1); if read32(base, 0x44) != 1 { fail("requeue QueueReady"); } lease .mark_ready() .unwrap_or_else(|_| fail("requeue lease ready")); write32(base, 0x70, 0x0f); if read32(base, 0x70) != 0x0f { fail("requeue DRIVER_OK"); } core::ptr::write_volatile((avail + 2) as *mut u16, 1u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x50, 0); } let second_deadline = virtio_ring::deadline_after( counter(), frequency .checked_mul(2) .unwrap_or_else(|| fail("requeue timeout interval")), ) .unwrap_or_else(|_| fail("requeue timeout deadline")); let second_used_index = loop { let index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if index != 0 { break index; } if virtio_ring::deadline_expired(counter(), second_deadline) { fail("requeue completion timeout"); } core::hint::spin_loop(); }; unsafe { asm!("dmb oshld", options(nostack, preserves_flags)) }; let (second_id, second_len, second_status) = unsafe { ( u32::from_le(core::ptr::read_volatile((used + 4) as *const u32)), u32::from_le(core::ptr::read_volatile((used + 8) as *const u32)), core::ptr::read_volatile(status_address as *const u8), ) }; if second_used_index != 1 || second_id != 0 || second_len != 513 || second_status != 0 { fail("requeue used completion identity/status"); } let mut second_checksum = 0u64; for i in 0..512usize { let byte = unsafe { core::ptr::read_volatile((data_address + i as u64) as *const u8) }; if byte != ((i as u8) ^ 0x5a) { fail("requeue sector payload mismatch"); } second_checksum += u64::from(byte); } write_str("TURKOS/VIRTIO-REQUEUE: FIRST-USED=0x"); write_hex(used_index as usize); write_str(" STALE-CLEARED=0x"); write_hex(1); write_str(" SECOND-USED=0x"); write_hex(second_used_index as usize); write_str(" SECOND-STATUS=0x"); write_hex(second_status as usize); write_str(" SECOND-SUM=0x"); write_hex(second_checksum as usize); write_str("\r\n"); write_line("TURKOS/VIRTIO-REQUEUE: PASS"); // K348: reset before attempting an invalid end-exclusive LBA. The // rejected request must not consume credits, publish an available entry, // make a queue ready, or notify the device. unsafe { write32(base, 0x70, 0); if read32(base, 0x70) != 0 || read32(base, 0x44) != 0 { fail("boundary reset readback"); } } lease .mark_reset() .unwrap_or_else(|_| fail("boundary lease reset")); admission.reset_epoch_after_device_reset(); unsafe { for frame in [desc, avail, used, request] { core::ptr::write_bytes(frame as *mut u8, 0, memory::PAGE_SIZE as usize); } } let invalid = virtio_mmio::sector_byte_offset(capacity_sectors, capacity_sectors); let untouched = unsafe { core::ptr::read_volatile((avail + 2) as *const u16) == 0 && core::ptr::read_volatile((used + 2) as *const u16) == 0 && read32(base, 0x44) == 0 && read32(base, 0x70) == 0 }; if invalid != Err(virtio_mmio::Error::SectorOutsideCapacity) || !untouched || admission.occupied() != 0 { fail("outside-capacity LBA reached queue"); } write_str("TURKOS/VIRTIO-LBA-BOUND: REJECTED=0x"); write_hex(capacity_sectors as usize); write_str(" AVAILIDX=0x"); write_hex(0); write_str(" USEDIDX=0x"); write_hex(0); write_str(" READY=0x"); write_hex(0); write_str(" STATUS=0x"); write_hex(0); write_str(" CREDITS=0x"); write_hex(admission.occupied() as usize); write_str("\r\n"); write_line("TURKOS/VIRTIO-LBA-BOUND: PASS"); // K349: even an end-exclusive start is a valid empty range. The request // stops here: no descriptor, available entry, QueueReady or notification. if virtio_mmio::checked_read_span(capacity_sectors, 0, capacity_sectors) != Ok(None) { fail("zero-sector policy"); } let zero_untouched = unsafe { core::ptr::read_volatile(desc as *const u64) == 0 && core::ptr::read_volatile((avail + 2) as *const u16) == 0 && core::ptr::read_volatile((used + 2) as *const u16) == 0 && read32(base, 0x44) == 0 && read32(base, 0x70) == 0 }; if !zero_untouched || admission.occupied() != 0 { fail("zero-sector read mutated queue"); } write_str("TURKOS/VIRTIO-ZERO-READ: START=0x"); write_hex(capacity_sectors as usize); write_str(" COUNT=0x"); write_hex(0); write_str(" DESCRIPTOR=0x"); write_hex(0); write_str(" AVAILIDX=0x"); write_hex(0); write_str(" USEDIDX=0x"); write_hex(0); write_str(" READY=0x"); write_hex(0); write_str(" STATUS=0x"); write_hex(0); write_str(" CREDITS=0x"); write_hex(admission.occupied() as usize); write_str("\r\n"); write_line("TURKOS/VIRTIO-ZERO-READ: PASS"); // K347: the immediately preceding LBA must be a real successful read, // not merely an arithmetic preflight. Keep the four frames exclusively // leased until the third device completion and final reset. let last_lba = capacity_sectors - 1; if virtio_mmio::sector_byte_offset(last_lba, capacity_sectors) != Ok(capacity_bytes - 512) || admission.reserve(3) != Ok(0) { fail("last-sector preflight"); } unsafe { let first = desc as *mut virtio_ring::Descriptor; let chain = virtio_ring::read_descriptor_chain(header_address, data_address, status_address); for (index, descriptor) in chain.into_iter().enumerate() { core::ptr::write_volatile(first.add(index), descriptor); } core::ptr::write_volatile( request as *mut virtio_ring::BlockReadHeader, virtio_ring::block_read_header(last_lba), ); core::ptr::write_volatile(status_address as *mut u8, 0xff); core::ptr::write_volatile((avail + 4) as *mut u16, 0u16.to_le()); write32(base, 0x70, 1); write32(base, 0x70, 3); write32(base, 0x14, 0); let offered_low = read32(base, 0x10); write32(base, 0x14, 1); let offered_high = read32(base, 0x10); let (accepted_low, accepted_high) = virtio_mmio::select_modern_read_only_features(offered_low, offered_high) .unwrap_or_else(|_| fail("last-sector feature subset")); write32(base, 0x24, 0); write32(base, 0x20, accepted_low); write32(base, 0x24, 1); write32(base, 0x20, accepted_high); write32(base, 0x70, 0x0b); if read32(base, 0x70) != 0x0b { fail("last-sector FEATURES_OK"); } write32(base, 0x30, 0); if read32(base, 0x44) != 0 || read32(base, 0x34) != 256 { fail("last-sector initial queue state"); } write32(base, 0x38, 256); write64_register_pair(base, 0x80, desc); write64_register_pair(base, 0x90, avail); write64_register_pair(base, 0xa0, used); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x44, 1); if read32(base, 0x44) != 1 { fail("last-sector QueueReady"); } lease .mark_ready() .unwrap_or_else(|_| fail("last-sector lease ready")); write32(base, 0x70, 0x0f); if read32(base, 0x70) != 0x0f { fail("last-sector DRIVER_OK"); } core::ptr::write_volatile((avail + 2) as *mut u16, 1u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x50, 0); } let last_deadline = virtio_ring::deadline_after( counter(), frequency .checked_mul(2) .unwrap_or_else(|| fail("last-sector timeout interval")), ) .unwrap_or_else(|_| fail("last-sector timeout deadline")); let last_used_index = loop { let index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if index != 0 { break index; } if virtio_ring::deadline_expired(counter(), last_deadline) { fail("last-sector completion timeout"); } core::hint::spin_loop(); }; unsafe { asm!("dmb oshld", options(nostack, preserves_flags)) }; let (last_id, last_len, last_status) = unsafe { ( u32::from_le(core::ptr::read_volatile((used + 4) as *const u32)), u32::from_le(core::ptr::read_volatile((used + 8) as *const u32)), core::ptr::read_volatile(status_address as *const u8), ) }; if last_used_index != 1 || last_id != 0 || last_len != 513 || last_status != 0 { fail("last-sector used completion identity/status"); } let mut last_checksum = 0u64; for i in 0..512usize { let byte = unsafe { core::ptr::read_volatile((data_address + i as u64) as *const u8) }; if byte != ((i as u8) ^ 0x3c) { fail("last-sector payload mismatch"); } last_checksum += u64::from(byte); } write_str("TURKOS/VIRTIO-LAST: LBA=0x"); write_hex(last_lba as usize); write_str(" OFFSET=0x"); write_hex((capacity_bytes - 512) as usize); write_str(" USEDIDX=0x"); write_hex(last_used_index as usize); write_str(" USEDID=0x"); write_hex(last_id as usize); write_str(" USEDLEN=0x"); write_hex(last_len as usize); write_str(" STATUS=0x"); write_hex(last_status as usize); write_str(" SUM=0x"); write_hex(last_checksum as usize); write_str("\r\n"); write_line("TURKOS/VIRTIO-LAST: PASS"); // K350: a fourth queue epoch submits one two-sector read. Both the // descriptor length and used-ring length must cover precisely 1024 data // bytes; a byte beyond the data buffer is guarded against overrun. unsafe { write32(base, 0x70, 0); if read32(base, 0x70) != 0 || read32(base, 0x44) != 0 { fail("multi-sector reset readback"); } } lease .mark_reset() .unwrap_or_else(|_| fail("multi-sector lease reset")); admission.reset_epoch_after_device_reset(); let (span_offset, span_bytes) = virtio_mmio::checked_read_span(2, 2, capacity_sectors) .unwrap_or_else(|_| fail("multi-sector span")) .unwrap_or_else(|| fail("multi-sector empty span")); if span_offset != 1024 || span_bytes != 1024 || virtio_ring::dma_subregion(request, 512, span_bytes) != Ok(data_address) || admission.reserve(3) != Ok(0) { fail("multi-sector preflight"); } unsafe { for frame in [desc, avail, used, request] { core::ptr::write_bytes(frame as *mut u8, 0, memory::PAGE_SIZE as usize); } let chain = virtio_ring::read_descriptor_chain_with_len( header_address, data_address, status_address, 1024, ) .unwrap_or_else(|_| fail("multi-sector descriptor length")); let first = desc as *mut virtio_ring::Descriptor; for (index, descriptor) in chain.into_iter().enumerate() { core::ptr::write_volatile(first.add(index), descriptor); } core::ptr::write_volatile( request as *mut virtio_ring::BlockReadHeader, virtio_ring::block_read_header(2), ); core::ptr::write_volatile(status_address as *mut u8, 0xff); core::ptr::write_volatile((data_address + 1024) as *mut u8, 0xcc); core::ptr::write_volatile((avail + 4) as *mut u16, 0u16.to_le()); if u32::from_le(core::ptr::read_volatile(first.add(1)).len_le) != 1024 { fail("multi-sector descriptor readback"); } write32(base, 0x70, 1); write32(base, 0x70, 3); write32(base, 0x14, 0); let offered_low = read32(base, 0x10); write32(base, 0x14, 1); let offered_high = read32(base, 0x10); let (accepted_low, accepted_high) = virtio_mmio::select_modern_read_only_features(offered_low, offered_high) .unwrap_or_else(|_| fail("multi-sector feature subset")); write32(base, 0x24, 0); write32(base, 0x20, accepted_low); write32(base, 0x24, 1); write32(base, 0x20, accepted_high); write32(base, 0x70, 0x0b); if read32(base, 0x70) != 0x0b { fail("multi-sector FEATURES_OK"); } write32(base, 0x30, 0); if read32(base, 0x44) != 0 || read32(base, 0x34) != 256 { fail("multi-sector initial queue state"); } write32(base, 0x38, 256); write64_register_pair(base, 0x80, desc); write64_register_pair(base, 0x90, avail); write64_register_pair(base, 0xa0, used); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x44, 1); if read32(base, 0x44) != 1 { fail("multi-sector QueueReady"); } lease .mark_ready() .unwrap_or_else(|_| fail("multi-sector lease ready")); write32(base, 0x70, 0x0f); if read32(base, 0x70) != 0x0f { fail("multi-sector DRIVER_OK"); } core::ptr::write_volatile((avail + 2) as *mut u16, 1u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x50, 0); } let multi_start = counter(); let multi_interval = if cfg!(feature = "virtio-active-timeout-test") { frequency / 4 } else { frequency .checked_mul(2) .unwrap_or_else(|| fail("multi-sector timeout interval")) }; let multi_deadline = virtio_ring::deadline_after(multi_start, multi_interval) .unwrap_or_else(|_| fail("multi-sector timeout deadline")); let multi_used_index = loop { let index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if index != 0 { if cfg!(feature = "virtio-active-timeout-test") { fail("active request completed before timeout"); } break index; } let now = counter(); if virtio_ring::active_completion_state(index, now, multi_deadline) == Err(virtio_ring::Error::Timeout) { #[cfg(feature = "virtio-active-timeout-test")] { // A completion racing with the timer wins. Never return DMA // pages while the notified request is still device-owned. unsafe { asm!("dmb oshld", options(nostack, preserves_flags)) }; let final_used = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if final_used != 0 { fail("active request raced timeout"); } let pending = unsafe { core::ptr::read_volatile(status_address as *const u8) }; let published = unsafe { u16::from_le(core::ptr::read_volatile((avail + 2) as *const u16)) }; let (ready, device_status) = unsafe { (read32(base, 0x44), read32(base, 0x70)) }; if pending != 0xff || published != 1 || ready != 1 || device_status != 0x0f || allocator.remaining_pages().checked_add(4) != Some(free_before) || lease.release(allocator) != Err(virtio_ring::Error::Active) { fail("active timeout ownership/state"); } write_str("TURKOS/VIRTIO-ACTIVE-TIMEOUT: START=0x"); write_hex(multi_start as usize); write_str(" DEADLINE=0x"); write_hex(multi_deadline as usize); write_str(" NOW=0x"); write_hex(now as usize); write_str(" FREQ=0x"); write_hex(frequency as usize); write_str(" AVAILIDX=0x"); write_hex(published as usize); write_str(" USEDIDX=0x"); write_hex(final_used as usize); write_str(" PENDING=0x"); write_hex(pending as usize); write_str(" READY=0x"); write_hex(ready as usize); write_str(" DRIVERSTATUS=0x"); write_hex(device_status as usize); write_str(" NOTIFIED=0x"); write_hex(1); write_str(" RELEASE-BLOCKED=0x"); write_hex(1); write_str("\r\n"); write_line("TURKOS/VIRTIO-ACTIVE-TIMEOUT: PASS"); park() } #[cfg(not(feature = "virtio-active-timeout-test"))] fail("multi-sector completion timeout"); } core::hint::spin_loop(); }; unsafe { asm!("dmb oshld", options(nostack, preserves_flags)) }; let (multi_id, multi_len, multi_status, multi_guard) = unsafe { ( u32::from_le(core::ptr::read_volatile((used + 4) as *const u32)), u32::from_le(core::ptr::read_volatile((used + 8) as *const u32)), core::ptr::read_volatile(status_address as *const u8), core::ptr::read_volatile((data_address + 1024) as *const u8), ) }; #[cfg(feature = "virtio-io-error-test")] { if multi_used_index != 1 || multi_id != 0 || virtio_ring::read_block_status(multi_status) != Err(virtio_ring::Error::IoError) || multi_len != 1025 || multi_guard != 0xcc { fail("expected backend EIO status and completion"); } write_str("TURKOS/VIRTIO-IOERR: LBA=0x"); write_hex(2); write_str(" USEDIDX=0x"); write_hex(multi_used_index as usize); write_str(" USEDID=0x"); write_hex(multi_id as usize); write_str(" USEDLEN=0x"); write_hex(multi_len as usize); write_str(" STATUS=0x"); write_hex(multi_status as usize); write_str(" CLASS=EIO\r\n"); unsafe { write32(base, 0x70, 0); if read32(base, 0x70) != 0 || read32(base, 0x44) != 0 { fail("I/O error reset readback"); } } lease .mark_reset() .unwrap_or_else(|_| fail("I/O error lease reset")); lease .release(allocator) .unwrap_or_else(|_| fail("I/O error lease release")); if allocator.remaining_pages() != free_before { fail("I/O error DMA frames leaked"); } write_line("TURKOS/VIRTIO-IOERR: RELEASED=0x0000000000000004"); write_line("TURKOS/VIRTIO-IOERR: PASS"); park() } #[cfg(not(feature = "virtio-io-error-test"))] { if multi_used_index != 1 || multi_id != 0 || virtio_ring::validate_read_completion_length(multi_len, 1024) != Ok(()) || virtio_ring::read_block_status(multi_status) != Ok(()) || multi_guard != 0xcc { fail("multi-sector used length/status/guard"); } let mut multi_checksum = 0u64; for i in 0..1024usize { let byte = unsafe { core::ptr::read_volatile((data_address + i as u64) as *const u8) }; let expected = if i < 512 { (i as u8) ^ 0x96 } else { (i as u8) ^ 0x69 }; if byte != expected { fail("multi-sector payload mismatch"); } multi_checksum += u64::from(byte); } write_str("TURKOS/VIRTIO-MULTI: LBA=0x"); write_hex(2); write_str(" SECTORS=0x"); write_hex(2); write_str(" DATA-BYTES=0x"); write_hex(span_bytes as usize); write_str(" USEDIDX=0x"); write_hex(multi_used_index as usize); write_str(" USEDID=0x"); write_hex(multi_id as usize); write_str(" USEDLEN=0x"); write_hex(multi_len as usize); write_str(" STATUS=0x"); write_hex(multi_status as usize); write_str(" SUM=0x"); write_hex(multi_checksum as usize); write_str(" GUARD=0x"); write_hex(multi_guard as usize); write_str("\r\n"); write_line("TURKOS/VIRTIO-MULTI: PASS"); // K351: QEMU supplied a complete response above. Feed a deliberately // truncated used.len through the same acceptance function the live path // just used. This is metadata fault injection, not a claim that QEMU // produced a short response. let injected_len = multi_len - 1; if injected_len != 1024 || virtio_ring::validate_read_completion_length(injected_len, 1024) != Err(virtio_ring::Error::ShortResponse) { fail("injected short completion accepted"); } write_str("TURKOS/VIRTIO-SHORT: ACTUAL=0x"); write_hex(multi_len as usize); write_str(" INJECTED=0x"); write_hex(injected_len as usize); write_str(" EXPECTED=0x"); write_hex(1025); write_str(" REJECTED=0x"); write_hex(1); write_str("\r\n"); write_line("TURKOS/VIRTIO-SHORT: PASS"); // K354: unlike the 256-credit admission preflight, this is an actual // QueueNum=4 device epoch. One split-data read owns all four entries; // a second read cannot take credits or mutate the first chain. unsafe { write32(base, 0x70, 0); if read32(base, 0x70) != 0 || read32(base, 0x44) != 0 { fail("four-entry queue reset readback"); } } lease .mark_reset() .unwrap_or_else(|_| fail("four-entry lease reset")); let small_rings = virtio_ring::split_ring_pages( desc, avail, used, 4, boot.hardware.ram_base, boot.hardware.ram_size, ) .unwrap_or_else(|_| fail("four-entry ring geometry")); let second_data_address = virtio_ring::dma_subregion(request, 1024, 512) .unwrap_or_else(|_| fail("four-entry second data DMA range")); if small_rings.available_bytes != 12 || small_rings.used_bytes != 36 || virtio_mmio::checked_read_span(4, 2, capacity_sectors) != Ok(Some((2048, 1024))) { fail("four-entry read preflight"); } let mut small_admission = virtio_ring::QueueAdmission::new(4) .unwrap_or_else(|_| fail("four-entry admission capacity")); if small_admission.reserve(4) != Ok(0) { fail("four-entry first read admission"); } unsafe { for frame in [desc, avail, used, request] { core::ptr::write_bytes(frame as *mut u8, 0, memory::PAGE_SIZE as usize); } let chain = virtio_ring::read_descriptor_chain_split_data( header_address, data_address, second_data_address, status_address, ); let descriptors = desc as *mut virtio_ring::Descriptor; for (index, descriptor) in chain.into_iter().enumerate() { core::ptr::write_volatile(descriptors.add(index), descriptor); } core::ptr::write_volatile( request as *mut virtio_ring::BlockReadHeader, virtio_ring::block_read_header(4), ); core::ptr::write_volatile(status_address as *mut u8, 0xff); core::ptr::write_volatile((second_data_address + 512) as *mut u8, 0xcc); core::ptr::write_volatile((avail + 4) as *mut u16, 0u16.to_le()); } let descriptors = desc as *const virtio_ring::Descriptor; let before = core::array::from_fn::<_, 4, _>(|index| unsafe { core::ptr::read_volatile(descriptors.add(index)) }); if small_admission.reserve(3) != Err(virtio_ring::Error::Full) || small_admission.occupied() != 4 || small_admission.remaining() != 0 { fail("second read admitted into full device queue"); } let after = core::array::from_fn::<_, 4, _>(|index| unsafe { core::ptr::read_volatile(descriptors.add(index)) }); if before != after { fail("rejected read overwrote device descriptors"); } unsafe { write32(base, 0x70, 1); write32(base, 0x70, 3); write32(base, 0x14, 0); let offered_low = read32(base, 0x10); write32(base, 0x14, 1); let offered_high = read32(base, 0x10); let (accepted_low, accepted_high) = virtio_mmio::select_modern_read_only_features(offered_low, offered_high) .unwrap_or_else(|_| fail("four-entry feature subset")); write32(base, 0x24, 0); write32(base, 0x20, accepted_low); write32(base, 0x24, 1); write32(base, 0x20, accepted_high); write32(base, 0x70, 0x0b); if read32(base, 0x70) != 0x0b { fail("four-entry FEATURES_OK"); } write32(base, 0x30, 0); if read32(base, 0x44) != 0 || read32(base, 0x34) < 4 { fail("four-entry initial queue state"); } write32(base, 0x38, 4); write64_register_pair(base, 0x80, desc); write64_register_pair(base, 0x90, avail); write64_register_pair(base, 0xa0, used); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x44, 1); if read32(base, 0x44) != 1 { fail("four-entry QueueReady"); } lease .mark_ready() .unwrap_or_else(|_| fail("four-entry lease ready")); write32(base, 0x70, 0x0f); if read32(base, 0x70) != 0x0f { fail("four-entry DRIVER_OK"); } core::ptr::write_volatile((avail + 2) as *mut u16, 1u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x50, 0); } let small_deadline = virtio_ring::deadline_after( counter(), frequency .checked_mul(2) .unwrap_or_else(|| fail("four-entry timeout interval")), ) .unwrap_or_else(|_| fail("four-entry timeout deadline")); let small_used_index = loop { let index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if index != 0 { break index; } if virtio_ring::deadline_expired(counter(), small_deadline) { fail("four-entry completion timeout"); } core::hint::spin_loop(); }; unsafe { asm!("dmb oshld", options(nostack, preserves_flags)) }; let (small_id, small_len, small_status, small_guard) = unsafe { ( u32::from_le(core::ptr::read_volatile((used + 4) as *const u32)), u32::from_le(core::ptr::read_volatile((used + 8) as *const u32)), core::ptr::read_volatile(status_address as *const u8), core::ptr::read_volatile((second_data_address + 512) as *const u8), ) }; if small_used_index != 1 || small_id != 0 || virtio_ring::validate_read_completion_length(small_len, 1024) != Ok(()) || virtio_ring::read_block_status(small_status) != Ok(()) || small_guard != 0xcc { fail("four-entry read completion identity/status/guard"); } let mut small_checksum = 0u64; for i in 0..1024usize { let address = if i < 512 { data_address + i as u64 } else { second_data_address + (i - 512) as u64 }; let byte = unsafe { core::ptr::read_volatile(address as *const u8) }; let expected = if i < 512 { (i as u8) ^ 0x12 } else { (i as u8) ^ 0xed }; if byte != expected { fail("four-entry read payload mismatch"); } small_checksum += u64::from(byte); } write_str("TURKOS/VIRTIO-FULL-READ: QNUM=0x"); write_hex(4); write_str(" OCCUPIED=0x"); write_hex(small_admission.occupied() as usize); write_str(" REMAINING=0x"); write_hex(small_admission.remaining() as usize); write_str(" BLOCKED=0x"); write_hex(1); write_str(" DESC-UNCHANGED=0x"); write_hex(1); write_str(" AVAILIDX=0x"); write_hex(1); write_str(" USEDIDX=0x"); write_hex(small_used_index as usize); write_str(" USEDID=0x"); write_hex(small_id as usize); write_str(" USEDLEN=0x"); write_hex(small_len as usize); write_str(" STATUS=0x"); write_hex(small_status as usize); write_str(" SUM=0x"); write_hex(small_checksum as usize); write_str(" GUARD=0x"); write_hex(small_guard as usize); write_str("\r\n"); write_line("TURKOS/VIRTIO-FULL-READ: PASS"); // K355: publish two independent three-descriptor reads in one // available-index update. Both are eligible for device ownership // before the driver waits for either completion. unsafe { write32(base, 0x70, 0); if read32(base, 0x70) != 0 || read32(base, 0x44) != 0 { fail("two-read queue reset readback"); } } lease .mark_reset() .unwrap_or_else(|_| fail("two-read lease reset")); let paired_rings = virtio_ring::split_ring_pages( desc, avail, used, 8, boot.hardware.ram_base, boot.hardware.ram_size, ) .unwrap_or_else(|_| fail("two-read ring geometry")); let header_b = virtio_ring::dma_subregion(request, 32, 16) .unwrap_or_else(|_| fail("second header DMA range")); let status_b = virtio_ring::dma_subregion(request, 48, 1) .unwrap_or_else(|_| fail("second status DMA range")); if paired_rings.available_bytes != 20 || paired_rings.used_bytes != 68 || virtio_mmio::checked_read_span(6, 1, capacity_sectors) != Ok(Some((3072, 512))) || virtio_mmio::checked_read_span(7, 1, capacity_sectors) != Ok(Some((3584, 512))) { fail("two-read preflight"); } let mut paired_admission = virtio_ring::QueueAdmission::new(8) .unwrap_or_else(|_| fail("two-read admission capacity")); let first_head = paired_admission .reserve(3) .unwrap_or_else(|_| fail("first parallel read admission")); let second_head = paired_admission .reserve(3) .unwrap_or_else(|_| fail("second parallel read admission")); if first_head != 0 || second_head != 3 || paired_admission.remaining() != 2 { fail("two-read distinct descriptor reservations"); } unsafe { for frame in [desc, avail, used, request] { core::ptr::write_bytes(frame as *mut u8, 0, memory::PAGE_SIZE as usize); } let chain_a = virtio_ring::read_descriptor_chain_at( first_head, header_address, data_address, status_address, ) .unwrap_or_else(|_| fail("first parallel descriptor chain")); let chain_b = virtio_ring::read_descriptor_chain_at( second_head, header_b, second_data_address, status_b, ) .unwrap_or_else(|_| fail("second parallel descriptor chain")); let table = desc as *mut virtio_ring::Descriptor; for (index, descriptor) in chain_a.into_iter().enumerate() { core::ptr::write_volatile(table.add(index), descriptor); } for (index, descriptor) in chain_b.into_iter().enumerate() { core::ptr::write_volatile(table.add(index + 3), descriptor); } core::ptr::write_volatile( header_address as *mut virtio_ring::BlockReadHeader, virtio_ring::block_read_header(6), ); core::ptr::write_volatile( header_b as *mut virtio_ring::BlockReadHeader, virtio_ring::block_read_header(7), ); core::ptr::write_volatile(status_address as *mut u8, 0xff); core::ptr::write_volatile(status_b as *mut u8, 0xff); core::ptr::write_volatile((second_data_address + 512) as *mut u8, 0xcc); core::ptr::write_volatile((avail + 4) as *mut u16, first_head.to_le()); core::ptr::write_volatile((avail + 6) as *mut u16, second_head.to_le()); if u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) != 0 { fail("two-read used ring not fresh"); } write32(base, 0x70, 1); write32(base, 0x70, 3); write32(base, 0x14, 0); let offered_low = read32(base, 0x10); write32(base, 0x14, 1); let offered_high = read32(base, 0x10); let (accepted_low, accepted_high) = virtio_mmio::select_modern_read_only_features(offered_low, offered_high) .unwrap_or_else(|_| fail("two-read feature subset")); write32(base, 0x24, 0); write32(base, 0x20, accepted_low); write32(base, 0x24, 1); write32(base, 0x20, accepted_high); write32(base, 0x70, 0x0b); if read32(base, 0x70) != 0x0b { fail("two-read FEATURES_OK"); } write32(base, 0x30, 0); if read32(base, 0x44) != 0 || read32(base, 0x34) < 8 { fail("two-read initial queue state"); } write32(base, 0x38, 8); write64_register_pair(base, 0x80, desc); write64_register_pair(base, 0x90, avail); write64_register_pair(base, 0xa0, used); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x44, 1); if read32(base, 0x44) != 1 { fail("two-read QueueReady"); } lease .mark_ready() .unwrap_or_else(|_| fail("two-read lease ready")); write32(base, 0x70, 0x0f); if read32(base, 0x70) != 0x0f { fail("two-read DRIVER_OK"); } asm!("dmb oshst", options(nostack, preserves_flags)); core::ptr::write_volatile((avail + 2) as *mut u16, 2u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x50, 0); } let paired_deadline = virtio_ring::deadline_after( counter(), frequency .checked_mul(2) .unwrap_or_else(|| fail("two-read timeout interval")), ) .unwrap_or_else(|_| fail("two-read timeout deadline")); let paired_used_index = loop { let index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if index >= 2 { break index; } if virtio_ring::deadline_expired(counter(), paired_deadline) { fail("two-read completion timeout"); } core::hint::spin_loop(); }; unsafe { asm!("dmb oshld", options(nostack, preserves_flags)) }; let (id_a, len_a, id_b, len_b, status_a, status_second, paired_guard) = unsafe { ( u32::from_le(core::ptr::read_volatile((used + 4) as *const u32)), u32::from_le(core::ptr::read_volatile((used + 8) as *const u32)), u32::from_le(core::ptr::read_volatile((used + 12) as *const u32)), u32::from_le(core::ptr::read_volatile((used + 16) as *const u32)), core::ptr::read_volatile(status_address as *const u8), core::ptr::read_volatile(status_b as *const u8), core::ptr::read_volatile((second_data_address + 512) as *const u8), ) }; if paired_used_index != 2 || virtio_ring::validate_two_completion_heads( [id_a, id_b], [first_head, second_head], ) != Ok(()) || virtio_ring::validate_read_completion_length(len_a, 512) != Ok(()) || virtio_ring::validate_read_completion_length(len_b, 512) != Ok(()) || virtio_ring::read_block_status(status_a) != Ok(()) || virtio_ring::read_block_status(status_second) != Ok(()) || paired_guard != 0xcc { fail("two-read completion identity/length/status/guard"); } let mut sum_a = 0u64; let mut sum_b = 0u64; for i in 0..512usize { let byte_a = unsafe { core::ptr::read_volatile((data_address + i as u64) as *const u8) }; let byte_b = unsafe { core::ptr::read_volatile((second_data_address + i as u64) as *const u8) }; if byte_a != ((i as u8) ^ 0x34) || byte_b != ((i as u8) ^ 0xcb) { fail("two-read payload mismatch"); } sum_a += u64::from(byte_a); sum_b += u64::from(byte_b); } write_str("TURKOS/VIRTIO-TWO-READ: QNUM=0x"); write_hex(8); write_str(" HEAD0=0x"); write_hex(first_head as usize); write_str(" HEAD1=0x"); write_hex(second_head as usize); write_str(" AVAILIDX=0x"); write_hex(2); write_str(" USEDIDX=0x"); write_hex(paired_used_index as usize); write_str(" USEDID0=0x"); write_hex(id_a as usize); write_str(" USEDLEN0=0x"); write_hex(len_a as usize); write_str(" USEDID1=0x"); write_hex(id_b as usize); write_str(" USEDLEN1=0x"); write_hex(len_b as usize); write_str(" STATUS0=0x"); write_hex(status_a as usize); write_str(" STATUS1=0x"); write_hex(status_second as usize); write_str(" SUM0=0x"); write_hex(sum_a as usize); write_str(" SUM1=0x"); write_hex(sum_b as usize); write_str(" GUARD=0x"); write_hex(paired_guard as usize); write_str("\r\n"); write_line("TURKOS/VIRTIO-TWO-READ: PASS"); // K356: use the same acceptance path as the live used-ring IDs. // These two corrupt IDs are local metadata injections, not QEMU output. let foreign = 6u32; if virtio_ring::validate_two_completion_heads( [foreign, id_b], [first_head, second_head], ) != Err(virtio_ring::Error::InvalidCompletion) || virtio_ring::validate_two_completion_heads( [id_a, id_a], [first_head, second_head], ) != Err(virtio_ring::Error::InvalidCompletion) { fail("foreign or duplicate used descriptor accepted"); } write_str("TURKOS/VIRTIO-USED-ID: ACTUAL0=0x"); write_hex(id_a as usize); write_str(" ACTUAL1=0x"); write_hex(id_b as usize); write_str(" FOREIGN=0x"); write_hex(foreign as usize); write_str(" DUPLICATE=0x"); write_hex(id_a as usize); write_str(" FOREIGN-REJECTED=0x"); write_hex(1); write_str(" DUPLICATE-REJECTED=0x"); write_hex(1); write_str("\r\n"); write_line("TURKOS/VIRTIO-USED-ID: PASS"); // K357: both previous reads filled disjoint regions of this frame. // Reset the device before touching DMA memory, scrub all 4096 bytes, // then prove a new one-sector read leaves no old bytes elsewhere. unsafe { write32(base, 0x70, 0); if read32(base, 0x70) != 0 || read32(base, 0x44) != 0 { fail("DMA scrub reset readback"); } } lease .mark_reset() .unwrap_or_else(|_| fail("DMA scrub lease reset")); let (old_a, old_b) = unsafe { ( core::ptr::read_volatile(data_address as *const u8), core::ptr::read_volatile(second_data_address as *const u8), ) }; if old_a != 0x34 || old_b != 0xcb { fail("DMA scrub did not start with two old sector patterns"); } // SAFETY: reset readback detached the device; DmaLease still owns // the identity-mapped request page exclusively. let request_bytes = unsafe { core::slice::from_raw_parts_mut(request as *mut u8, memory::PAGE_SIZE as usize) }; virtio_ring::scrub_dma_request_bytes(request_bytes) .unwrap_or_else(|_| fail("DMA request page scrub")); let mut zeroed = 0usize; for offset in 0..memory::PAGE_SIZE as usize { let byte = unsafe { core::ptr::read_volatile((request + offset as u64) as *const u8) }; if byte != 0 { fail("DMA scrub left stale request byte"); } zeroed += 1; } if virtio_mmio::checked_read_span(8, 1, capacity_sectors) != Ok(Some((4096, 512))) { fail("DMA scrub follow-up sector preflight"); } let scrub_queue_size = if cfg!(feature = "turkfs-format-test") { 16 } else { 8 }; let mut scrub_admission = virtio_ring::QueueAdmission::new(scrub_queue_size) .unwrap_or_else(|_| fail("DMA scrub admission capacity")); if scrub_admission.reserve(3) != Ok(0) { fail("DMA scrub read admission"); } unsafe { for frame in [desc, avail, used] { core::ptr::write_bytes(frame as *mut u8, 0, memory::PAGE_SIZE as usize); } let table = desc as *mut virtio_ring::Descriptor; for (index, descriptor) in virtio_ring::read_descriptor_chain(header_address, data_address, status_address) .into_iter() .enumerate() { core::ptr::write_volatile(table.add(index), descriptor); } core::ptr::write_volatile( header_address as *mut virtio_ring::BlockReadHeader, virtio_ring::block_read_header(8), ); core::ptr::write_volatile(status_address as *mut u8, 0xff); core::ptr::write_volatile((avail + 4) as *mut u16, 0u16.to_le()); write32(base, 0x70, 1); write32(base, 0x70, 3); write32(base, 0x14, 0); let offered_low = read32(base, 0x10); write32(base, 0x14, 1); let offered_high = read32(base, 0x10); let (accepted_low, accepted_high) = virtio_mmio::select_modern_read_only_features(offered_low, offered_high) .unwrap_or_else(|_| fail("DMA scrub feature subset")); write32(base, 0x24, 0); write32(base, 0x20, accepted_low); write32(base, 0x24, 1); write32(base, 0x20, accepted_high); write32(base, 0x70, 0x0b); if read32(base, 0x70) != 0x0b { fail("DMA scrub FEATURES_OK"); } write32(base, 0x30, 0); if read32(base, 0x44) != 0 || read32(base, 0x34) < u32::from(scrub_queue_size) { fail("DMA scrub initial queue state"); } write32(base, 0x38, u32::from(scrub_queue_size)); write64_register_pair(base, 0x80, desc); write64_register_pair(base, 0x90, avail); write64_register_pair(base, 0xa0, used); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x44, 1); if read32(base, 0x44) != 1 { fail("DMA scrub QueueReady"); } lease .mark_ready() .unwrap_or_else(|_| fail("DMA scrub lease ready")); write32(base, 0x70, 0x0f); if read32(base, 0x70) != 0x0f { fail("DMA scrub DRIVER_OK"); } core::ptr::write_volatile((avail + 2) as *mut u16, 1u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x50, 0); } let scrub_deadline = virtio_ring::deadline_after( counter(), frequency .checked_mul(2) .unwrap_or_else(|| fail("DMA scrub timeout interval")), ) .unwrap_or_else(|_| fail("DMA scrub timeout deadline")); let scrub_used_index = loop { let index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if index != 0 { break index; } if virtio_ring::deadline_expired(counter(), scrub_deadline) { fail("DMA scrub completion timeout"); } core::hint::spin_loop(); }; unsafe { asm!("dmb oshld", options(nostack, preserves_flags)) }; let (scrub_id, scrub_len, scrub_status) = unsafe { ( u32::from_le(core::ptr::read_volatile((used + 4) as *const u32)), u32::from_le(core::ptr::read_volatile((used + 8) as *const u32)), core::ptr::read_volatile(status_address as *const u8), ) }; if scrub_used_index != 1 || scrub_id != 0 || virtio_ring::validate_read_completion_length(scrub_len, 512) != Ok(()) || virtio_ring::read_block_status(scrub_status) != Ok(()) { fail("DMA scrub follow-up completion identity/status"); } let mut scrub_checksum = 0u64; for offset in 0..512usize { let byte = unsafe { core::ptr::read_volatile((data_address + offset as u64) as *const u8) }; if byte != ((offset as u8) ^ 0x56) { fail("DMA scrub follow-up sector mismatch"); } scrub_checksum += u64::from(byte); } let mut leaked = 0usize; for offset in 0..memory::PAGE_SIZE as usize { if offset < 16 || offset == 16 || (512..1024).contains(&offset) { continue; } let byte = unsafe { core::ptr::read_volatile((request + offset as u64) as *const u8) }; if byte != 0 { leaked += 1; } } if leaked != 0 { fail("DMA scrub follow-up exposed old bytes"); } write_str("TURKOS/VIRTIO-DMA-SCRUB: OLD0=0x"); write_hex(old_a as usize); write_str(" OLD1=0x"); write_hex(old_b as usize); write_str(" ZEROED=0x"); write_hex(zeroed); write_str(" LBA=0x"); write_hex(8); write_str(" USEDIDX=0x"); write_hex(scrub_used_index as usize); write_str(" USEDID=0x"); write_hex(scrub_id as usize); write_str(" USEDLEN=0x"); write_hex(scrub_len as usize); write_str(" STATUS=0x"); write_hex(scrub_status as usize); write_str(" SUM=0x"); write_hex(scrub_checksum as usize); write_str(" LEAKBYTES=0x"); write_hex(leaked); write_str("\r\n"); write_line("TURKOS/VIRTIO-DMA-SCRUB: PASS"); #[cfg(feature = "turkfs-format-test")] { // K361: reuse the still-owned queue for a second, independent // read of the experimental superblock at LBA16. The old LBA8 // descriptor/data remains untouched until reset. if virtio_mmio::checked_read_span(turkfs::SUPERBLOCK_LBA, 1, capacity_sectors) != Ok(Some((8192, turkfs::BLOCK_BYTES as u64))) { fail("TurkFS superblock sector preflight"); } if scrub_admission.reserve(3) != Ok(3) { fail("TurkFS second read admission"); } let fs_header = request + 1024; let fs_status = request + 1040; let fs_data = request + 1536; let fs_chain = virtio_ring::read_descriptor_chain_at(3, fs_header, fs_data, fs_status) .unwrap_or_else(|_| fail("TurkFS descriptor chain")); unsafe { let table = desc as *mut virtio_ring::Descriptor; for (index, descriptor) in fs_chain.into_iter().enumerate() { core::ptr::write_volatile(table.add(3 + index), descriptor); } core::ptr::write_volatile( fs_header as *mut virtio_ring::BlockReadHeader, virtio_ring::block_read_header(turkfs::SUPERBLOCK_LBA), ); core::ptr::write_volatile(fs_status as *mut u8, 0xff); core::ptr::write_volatile((avail + 6) as *mut u16, 3u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); core::ptr::write_volatile((avail + 2) as *mut u16, 2u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x50, 0); } let fs_deadline = virtio_ring::deadline_after( counter(), frequency .checked_mul(2) .unwrap_or_else(|| fail("TurkFS timeout interval")), ) .unwrap_or_else(|_| fail("TurkFS timeout deadline")); let fs_used_index = loop { let index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if index >= 2 { break index; } if virtio_ring::deadline_expired(counter(), fs_deadline) { fail("TurkFS read timeout"); } core::hint::spin_loop(); }; unsafe { asm!("dmb oshld", options(nostack, preserves_flags)) }; let (fs_used_id, fs_used_len, fs_block_status) = unsafe { ( u32::from_le(core::ptr::read_volatile((used + 12) as *const u32)), u32::from_le(core::ptr::read_volatile((used + 16) as *const u32)), core::ptr::read_volatile(fs_status as *const u8), ) }; if fs_used_index != 2 || fs_used_id != 3 || virtio_ring::validate_read_completion_length(fs_used_len, 512) != Ok(()) || virtio_ring::read_block_status(fs_block_status) != Ok(()) { fail("TurkFS completion identity/status"); } let superblock = unsafe { &mut *core::ptr::addr_of_mut!(TURKFS_SCRATCH.superblock) }; for (index, byte) in superblock.iter_mut().enumerate() { *byte = unsafe { core::ptr::read_volatile((fs_data + index as u64) as *const u8) }; } let version = turkfs::require_format_version(superblock) .unwrap_or_else(|_| fail("TurkFS unsupported format version")); write_str("TURKOS/FS-FORMAT: LBA=0x"); write_hex(turkfs::SUPERBLOCK_LBA as usize); write_str(" VERSION=0x"); write_hex(version as usize); write_str(" BYTES=0x"); write_hex(superblock.len()); write_str(" USEDIDX=0x"); write_hex(fs_used_index as usize); write_str(" USEDID=0x"); write_hex(fs_used_id as usize); write_str(" USEDLEN=0x"); write_hex(fs_used_len as usize); write_str(" STATUS=0x"); write_hex(fs_block_status as usize); write_str("\r\n"); turkfs::require_superblock_signature(superblock) .unwrap_or_else(|_| fail("TurkFS superblock signature")); write_str("TURKOS/FS-SIGNATURE: LBA=0x"); write_hex(turkfs::SUPERBLOCK_LBA as usize); write_str(" MAGIC-BYTES=0x"); write_hex(turkfs::MAGIC.len()); write_str(" MATCH=0x"); write_hex(1); write_str("\r\n"); write_line("TURKOS/FS-SIGNATURE: PASS"); let geometry = turkfs::require_block_geometry(superblock, capacity_bytes) .unwrap_or_else(|_| fail("TurkFS block geometry")); write_str("TURKOS/FS-BLOCK: BYTES=0x"); write_hex(geometry.block_bytes as usize); write_str(" COUNT=0x"); write_hex(geometry.block_count as usize); write_str(" SPAN=0x"); write_hex(geometry.span_bytes as usize); write_str(" CAPACITY=0x"); write_hex(capacity_bytes as usize); write_str("\r\n"); write_line("TURKOS/FS-BLOCK: PASS"); write_line("TURKOS/FS-FORMAT: PASS"); // K364: the v1 superblock points to one bounded root block. // Reuse the still-live QueueNum16 ring for a third independent // read; no descriptor from the first two reads is overwritten. let (root_lba, root_count) = turkfs::root_directory_location(superblock, geometry) .unwrap_or_else(|_| fail("TurkFS root descriptor")); if virtio_mmio::checked_read_span(root_lba, 1, capacity_sectors).is_err() { fail("TurkFS root sector preflight"); } if scrub_admission.reserve(3) != Ok(6) { fail("TurkFS third read admission"); } let root_header = request + 2048; let root_status = request + 2064; let root_data = request + 2560; let root_chain = virtio_ring::read_descriptor_chain_at(6, root_header, root_data, root_status) .unwrap_or_else(|_| fail("TurkFS root descriptor chain")); unsafe { let table = desc as *mut virtio_ring::Descriptor; for (index, descriptor) in root_chain.into_iter().enumerate() { core::ptr::write_volatile(table.add(6 + index), descriptor); } core::ptr::write_volatile( root_header as *mut virtio_ring::BlockReadHeader, virtio_ring::block_read_header(root_lba), ); core::ptr::write_volatile(root_status as *mut u8, 0xff); core::ptr::write_volatile((avail + 8) as *mut u16, 6u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); core::ptr::write_volatile((avail + 2) as *mut u16, 3u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x50, 0); } let root_deadline = virtio_ring::deadline_after( counter(), frequency .checked_mul(2) .unwrap_or_else(|| fail("TurkFS root timeout interval")), ) .unwrap_or_else(|_| fail("TurkFS root timeout deadline")); let root_used_index = loop { let index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if index >= 3 { break index; } if virtio_ring::deadline_expired(counter(), root_deadline) { fail("TurkFS root read timeout"); } core::hint::spin_loop(); }; unsafe { asm!("dmb oshld", options(nostack, preserves_flags)) }; let (root_used_id, root_used_len, root_block_status) = unsafe { ( u32::from_le(core::ptr::read_volatile((used + 20) as *const u32)), u32::from_le(core::ptr::read_volatile((used + 24) as *const u32)), core::ptr::read_volatile(root_status as *const u8), ) }; if root_used_index != 3 || root_used_id != 6 || virtio_ring::validate_read_completion_length(root_used_len, 512) != Ok(()) || virtio_ring::read_block_status(root_block_status) != Ok(()) { fail("TurkFS root completion identity/status"); } let root_block = unsafe { &mut *core::ptr::addr_of_mut!(TURKFS_SCRATCH.root_block) }; for (index, byte) in root_block.iter_mut().enumerate() { *byte = unsafe { core::ptr::read_volatile((root_data + index as u64) as *const u8) }; } let root = match turkfs::parse_root_directory(root_lba, root_count, root_block) { Ok(root) => root, Err(turkfs::Error::InvalidInodeNumber) => { let available_index = unsafe { u16::from_le(core::ptr::read_volatile((avail + 2) as *const u16)) }; let used_index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if available_index != 3 || used_index != 3 { fail("TurkFS invalid inode escaped before rejection"); } write_str("TURKOS/FS-INODE-REJECT: AVAILABLE=0x"); write_hex(available_index as usize); write_str(" USED=0x"); write_hex(used_index as usize); write_str(" FOURTH-READ=0x"); write_hex(0); write_str("\r\n"); fail("TurkFS inode number"); } Err(_) => fail("TurkFS root entry"), }; let valid_root_names = turkfs::require_utf8_names(&root) .unwrap_or_else(|_| fail("TurkFS invalid UTF-8 root name")); if !root.contains(b"README") || !root.contains(b"KERNEL") || !root.contains(b"MULTI") || !root.contains(b"TAIL") { fail("TurkFS root names"); } write_str("TURKOS/FS-ROOT: LBA=0x"); write_hex(root_lba as usize); write_str(" COUNT=0x"); write_hex(root_count); write_str(" README=0x"); write_hex(1); write_str(" KERNEL=0x"); write_hex(1); write_str(" MULTI=0x"); write_hex(1); write_str(" TAIL=0x"); write_hex(1); write_str(" USEDIDX=0x"); write_hex(root_used_index as usize); write_str(" USEDID=0x"); write_hex(root_used_id as usize); write_str(" USEDLEN=0x"); write_hex(root_used_len as usize); write_str(" STATUS=0x"); write_hex(root_block_status as usize); write_str("\r\n"); write_line("TURKOS/FS-ROOT: PASS"); write_str("TURKOS/FS-INODE-LIMIT: MAX=0x"); write_hex(turkfs::MAX_ROOT_ENTRIES); write_str(" ROOT-COUNT=0x"); write_hex(root_count); write_str(" VALID=0x"); write_hex(1); write_str("\r\n"); write_line("TURKOS/FS-INODE-LIMIT: PASS"); // K365: exercise the exact 24-byte bound in a local copy of // the real root block; the actual disk path already rejects // overlong lengths before any name slice is made. let mut boundary_root = *root_block; let exact_name = *b"ABCDEFGHIJKLMNOPQRSTUVWX"; boundary_root[1] = turkfs::ROOT_NAME_BYTES as u8; boundary_root[8..32].copy_from_slice(&exact_name); let exact_listing = turkfs::parse_root_directory(root_lba, root_count, &boundary_root) .unwrap_or_else(|_| fail("TurkFS exact name boundary")); if !exact_listing.contains(&exact_name) { fail("TurkFS exact name missing"); } boundary_root[1] = 25; if turkfs::parse_root_directory(root_lba, root_count, &boundary_root) != Err(turkfs::Error::InvalidRootEntry) { fail("TurkFS overlong name accepted"); } write_str("TURKOS/FS-NAME-LIMIT: MAX=0x"); write_hex(turkfs::ROOT_NAME_BYTES); write_str(" ACTUAL-COUNT=0x"); write_hex(root_count); write_str(" LOCAL24=0x"); write_hex(1); write_str(" LOCAL25-REJECTED=0x"); write_hex(1); write_str("\r\n"); write_line("TURKOS/FS-NAME-LIMIT: PASS"); // K366: a fourth read brings in the inode table. The README // inode is zero-length and has no data LBA, so a successful // read returns zero bytes without a fifth device request. let inode_lba = turkfs::inode_table_location(superblock, geometry, root_lba) .unwrap_or_else(|_| fail("TurkFS inode table descriptor")); if virtio_mmio::checked_read_span(inode_lba, 1, capacity_sectors).is_err() { fail("TurkFS inode table preflight"); } if scrub_admission.reserve(3) != Ok(9) { fail("TurkFS fourth read admission"); } let inode_header = request + 3072; let inode_status = request + 3088; let inode_data = request + 3584; let inode_chain = virtio_ring::read_descriptor_chain_at( 9, inode_header, inode_data, inode_status, ) .unwrap_or_else(|_| fail("TurkFS inode descriptor chain")); unsafe { let table = desc as *mut virtio_ring::Descriptor; for (index, descriptor) in inode_chain.into_iter().enumerate() { core::ptr::write_volatile(table.add(9 + index), descriptor); } core::ptr::write_volatile( inode_header as *mut virtio_ring::BlockReadHeader, virtio_ring::block_read_header(inode_lba), ); core::ptr::write_volatile(inode_status as *mut u8, 0xff); core::ptr::write_volatile((avail + 10) as *mut u16, 9u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); core::ptr::write_volatile((avail + 2) as *mut u16, 4u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x50, 0); } let inode_deadline = virtio_ring::deadline_after( counter(), frequency .checked_mul(2) .unwrap_or_else(|| fail("TurkFS inode timeout interval")), ) .unwrap_or_else(|_| fail("TurkFS inode timeout deadline")); let inode_used_index = loop { let index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if index >= 4 { break index; } if virtio_ring::deadline_expired(counter(), inode_deadline) { fail("TurkFS inode read timeout"); } core::hint::spin_loop(); }; unsafe { asm!("dmb oshld", options(nostack, preserves_flags)) }; let (inode_used_id, inode_used_len, inode_block_status) = unsafe { ( u32::from_le(core::ptr::read_volatile((used + 28) as *const u32)), u32::from_le(core::ptr::read_volatile((used + 32) as *const u32)), core::ptr::read_volatile(inode_status as *const u8), ) }; if inode_used_index != 4 || inode_used_id != 9 || virtio_ring::validate_read_completion_length(inode_used_len, 512) != Ok(()) || virtio_ring::read_block_status(inode_block_status) != Ok(()) { fail("TurkFS inode completion identity/status"); } let inode_block = unsafe { &mut *core::ptr::addr_of_mut!(TURKFS_SCRATCH.inode_block) }; for (index, byte) in inode_block.iter_mut().enumerate() { *byte = unsafe { core::ptr::read_volatile((inode_data + index as u64) as *const u8) }; } let bytes_read = turkfs::read_known_empty_file(&root, inode_block, b"README") .unwrap_or_else(|_| fail("TurkFS empty file inode")); if bytes_read != 0 { fail("TurkFS empty file returned data"); } write_str("TURKOS/FS-EMPTY: INODE-LBA=0x"); write_hex(inode_lba as usize); write_str(" FILE-ID=0x"); write_hex(1); write_str(" SIZE=0x"); write_hex(0); write_str(" RETURNED=0x"); write_hex(bytes_read); write_str(" USEDIDX=0x"); write_hex(inode_used_index as usize); write_str(" USEDID=0x"); write_hex(inode_used_id as usize); write_str(" USEDLEN=0x"); write_hex(inode_used_len as usize); write_str(" STATUS=0x"); write_hex(inode_block_status as usize); write_str("\r\n"); write_line("TURKOS/FS-EMPTY: PASS"); // K367: all four earlier requests have completed. Reuse only // the first request's DMA subregion, while taking fresh // descriptor slots 12..14; no active device write can target it. let file_lba = turkfs::known_single_block_lba( &root, inode_block, b"KERNEL", geometry, inode_lba, ) .unwrap_or_else(|_| fail("TurkFS single block inode")); let multi_lba = turkfs::known_two_block_lba(&root, inode_block, b"MULTI", geometry, inode_lba) .unwrap_or_else(|_| fail("TurkFS multi-block inode")); let tail_lba = turkfs::known_partial_file_lba( &root, inode_block, b"TAIL", geometry, inode_lba, ) .unwrap_or_else(|_| fail("TurkFS partial file inode")); if turkfs::require_distinct_known_extents(file_lba, multi_lba, tail_lba).is_err() { let available_index = unsafe { u16::from_le(core::ptr::read_volatile((avail + 2) as *const u16)) }; let used_index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if available_index != 4 || used_index != 4 { fail("TurkFS overlapping extent escaped before rejection"); } write_str("TURKOS/FS-EXTENT-REJECT: AVAILABLE=0x"); write_hex(available_index as usize); write_str(" USED=0x"); write_hex(used_index as usize); write_str(" FIFTH-READ=0x"); write_hex(0); write_str("\r\n"); fail("TurkFS overlapping extent"); } write_str("TURKOS/FS-EXTENT: KERNEL=0x"); write_hex(file_lba as usize); write_str(" MULTI=0x"); write_hex(multi_lba as usize); write_str(" TAIL=0x"); write_hex(tail_lba as usize); write_str(" NONOVERLAP=0x"); write_hex(1); write_str("\r\n"); write_line("TURKOS/FS-EXTENT: PASS"); if virtio_mmio::checked_read_span(file_lba, 1, capacity_sectors).is_err() { fail("TurkFS single block preflight"); } if scrub_admission.reserve(3) != Ok(12) { fail("TurkFS fifth read admission"); } let file_header = request; let file_status = request + 16; let file_data = request + 512; let file_chain = virtio_ring::read_descriptor_chain_at(12, file_header, file_data, file_status) .unwrap_or_else(|_| fail("TurkFS file descriptor chain")); unsafe { let table = desc as *mut virtio_ring::Descriptor; for (index, descriptor) in file_chain.into_iter().enumerate() { core::ptr::write_volatile(table.add(12 + index), descriptor); } core::ptr::write_volatile( file_header as *mut virtio_ring::BlockReadHeader, virtio_ring::block_read_header(file_lba), ); core::ptr::write_volatile(file_status as *mut u8, 0xff); core::ptr::write_volatile((avail + 12) as *mut u16, 12u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); core::ptr::write_volatile((avail + 2) as *mut u16, 5u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x50, 0); } let file_deadline = virtio_ring::deadline_after( counter(), frequency .checked_mul(2) .unwrap_or_else(|| fail("TurkFS file timeout interval")), ) .unwrap_or_else(|_| fail("TurkFS file timeout deadline")); let file_used_index = loop { let index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if index >= 5 { break index; } if virtio_ring::deadline_expired(counter(), file_deadline) { fail("TurkFS file read timeout"); } core::hint::spin_loop(); }; unsafe { asm!("dmb oshld", options(nostack, preserves_flags)) }; let (file_used_id, file_used_len, file_block_status) = unsafe { ( u32::from_le(core::ptr::read_volatile((used + 36) as *const u32)), u32::from_le(core::ptr::read_volatile((used + 40) as *const u32)), core::ptr::read_volatile(file_status as *const u8), ) }; if file_used_index != 5 || file_used_id != 12 || virtio_ring::validate_read_completion_length(file_used_len, 512) != Ok(()) || virtio_ring::read_block_status(file_block_status) != Ok(()) { fail("TurkFS file completion identity/status"); } let file_block = unsafe { &mut *core::ptr::addr_of_mut!(TURKFS_SCRATCH.file_block) }; for (index, byte) in file_block.iter_mut().enumerate() { *byte = unsafe { core::ptr::read_volatile((file_data + index as u64) as *const u8) }; } let file_hash = turkfs::verify_known_single_block_data(file_block) .unwrap_or_else(|_| fail("TurkFS single block hash")); write_str("TURKOS/FS-DATA: LBA=0x"); write_hex(file_lba as usize); write_str(" FILE-ID=0x"); write_hex(2); write_str(" BYTES=0x"); write_hex(file_block.len()); write_str(" USEDIDX=0x"); write_hex(file_used_index as usize); write_str(" USEDID=0x"); write_hex(file_used_id as usize); write_str(" USEDLEN=0x"); write_hex(file_used_len as usize); write_str(" STATUS=0x"); write_hex(file_block_status as usize); write_str(" SHA256="); let file_hash_hex = turkos_kernel::program_hash::hex_lower(&file_hash); write_str( core::str::from_utf8(&file_hash_hex) .unwrap_or_else(|_| fail("TurkFS file hash encoding")), ); write_str("\r\n"); write_line("TURKOS/FS-DATA: PASS"); // K368: QueueNum16 has consumed 15 descriptor slots. All // five completions were accepted, so reset before reusing // ring/DMA pages for a two-sector contiguous file read. if virtio_mmio::checked_read_span(multi_lba, 2, capacity_sectors).is_err() { fail("TurkFS multi-block preflight"); } unsafe { write32(base, 0x70, 0); if read32(base, 0x70) != 0 || read32(base, 0x44) != 0 { fail("TurkFS multi-block reset readback"); } } lease .mark_reset() .unwrap_or_else(|_| fail("TurkFS multi-block lease reset")); scrub_admission.reset_epoch_after_device_reset(); if scrub_admission.reserve(3) != Ok(0) { fail("TurkFS multi-block admission"); } unsafe { for frame in [desc, avail, used, request] { core::ptr::write_bytes(frame as *mut u8, 0, memory::PAGE_SIZE as usize); } let header = request; let data = request + 512; let status = request + 16; let chain = virtio_ring::read_descriptor_chain_with_len(header, data, status, 1024) .unwrap_or_else(|_| fail("TurkFS multi-block descriptor chain")); let table = desc as *mut virtio_ring::Descriptor; for (index, descriptor) in chain.into_iter().enumerate() { core::ptr::write_volatile(table.add(index), descriptor); } core::ptr::write_volatile( header as *mut virtio_ring::BlockReadHeader, virtio_ring::block_read_header(multi_lba), ); core::ptr::write_volatile(status as *mut u8, 0xff); core::ptr::write_volatile((avail + 4) as *mut u16, 0u16.to_le()); write32(base, 0x70, 1); write32(base, 0x70, 3); write32(base, 0x14, 0); let offered_low = read32(base, 0x10); write32(base, 0x14, 1); let offered_high = read32(base, 0x10); let (accepted_low, accepted_high) = virtio_mmio::select_modern_read_only_features(offered_low, offered_high) .unwrap_or_else(|_| fail("TurkFS multi-block feature subset")); write32(base, 0x24, 0); write32(base, 0x20, accepted_low); write32(base, 0x24, 1); write32(base, 0x20, accepted_high); write32(base, 0x70, 0x0b); if read32(base, 0x70) != 0x0b { fail("TurkFS multi-block FEATURES_OK"); } write32(base, 0x30, 0); if read32(base, 0x44) != 0 || read32(base, 0x34) < 16 { fail("TurkFS multi-block queue state"); } write32(base, 0x38, 16); write64_register_pair(base, 0x80, desc); write64_register_pair(base, 0x90, avail); write64_register_pair(base, 0xa0, used); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x44, 1); if read32(base, 0x44) != 1 { fail("TurkFS multi-block QueueReady"); } lease .mark_ready() .unwrap_or_else(|_| fail("TurkFS multi-block lease ready")); write32(base, 0x70, 0x0f); if read32(base, 0x70) != 0x0f { fail("TurkFS multi-block DRIVER_OK"); } core::ptr::write_volatile((avail + 2) as *mut u16, 1u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x50, 0); } let multi_deadline = virtio_ring::deadline_after( counter(), frequency .checked_mul(2) .unwrap_or_else(|| fail("TurkFS multi-block timeout interval")), ) .unwrap_or_else(|_| fail("TurkFS multi-block timeout deadline")); let multi_used_index = loop { let index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if index != 0 { break index; } if virtio_ring::deadline_expired(counter(), multi_deadline) { fail("TurkFS multi-block read timeout"); } core::hint::spin_loop(); }; unsafe { asm!("dmb oshld", options(nostack, preserves_flags)) }; let (multi_used_id, multi_used_len, multi_status) = unsafe { ( u32::from_le(core::ptr::read_volatile((used + 4) as *const u32)), u32::from_le(core::ptr::read_volatile((used + 8) as *const u32)), core::ptr::read_volatile((request + 16) as *const u8), ) }; if multi_used_index != 1 || multi_used_id != 0 || virtio_ring::validate_read_completion_length(multi_used_len, 1024) != Ok(()) || virtio_ring::read_block_status(multi_status) != Ok(()) { fail("TurkFS multi-block completion identity/status"); } let multi_data = unsafe { &mut *core::ptr::addr_of_mut!(TURKFS_SCRATCH.multi_data) }; for (index, byte) in multi_data.iter_mut().enumerate() { *byte = unsafe { core::ptr::read_volatile((request + 512 + index as u64) as *const u8) }; } turkfs::verify_known_two_block_order(multi_data) .unwrap_or_else(|_| fail("TurkFS multi-block order")); write_str("TURKOS/FS-MULTI: LBA=0x"); write_hex(multi_lba as usize); write_str(" BLOCKS=0x"); write_hex(2); write_str(" BYTES=0x"); write_hex(multi_data.len()); write_str(" USEDIDX=0x"); write_hex(multi_used_index as usize); write_str(" USEDID=0x"); write_hex(multi_used_id as usize); write_str(" USEDLEN=0x"); write_hex(multi_used_len as usize); write_str(" STATUS=0x"); write_hex(multi_status as usize); write_str(" ORDER=0x"); write_hex(1); write_str("\r\n"); write_line("TURKOS/FS-MULTI: PASS"); // K369: the device must still transfer whole sectors, but // the file API returns only 513 logical bytes. Use a new // three-descriptor chain and a disjoint DMA subregion. if virtio_mmio::checked_read_span(tail_lba, 2, capacity_sectors).is_err() { fail("TurkFS partial file preflight"); } if scrub_admission.reserve(3) != Ok(3) { fail("TurkFS partial file admission"); } let tail_header = request + 2048; let tail_status = request + 2064; let tail_data = request + 2560; let tail_chain = virtio_ring::read_descriptor_chain_with_len( tail_header, tail_data, tail_status, 1024, ) .unwrap_or_else(|_| fail("TurkFS partial descriptor chain")); unsafe { let table = desc as *mut virtio_ring::Descriptor; for (index, mut descriptor) in tail_chain.into_iter().enumerate() { if index < 2 { descriptor.next_le = (4 + index as u16).to_le(); } core::ptr::write_volatile(table.add(3 + index), descriptor); } core::ptr::write_volatile( tail_header as *mut virtio_ring::BlockReadHeader, virtio_ring::block_read_header(tail_lba), ); core::ptr::write_volatile(tail_status as *mut u8, 0xff); core::ptr::write_volatile((avail + 6) as *mut u16, 3u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); core::ptr::write_volatile((avail + 2) as *mut u16, 2u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x50, 0); } let tail_deadline = virtio_ring::deadline_after( counter(), frequency .checked_mul(2) .unwrap_or_else(|| fail("TurkFS partial timeout interval")), ) .unwrap_or_else(|_| fail("TurkFS partial timeout deadline")); let tail_used_index = loop { let index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if index >= 2 { break index; } if virtio_ring::deadline_expired(counter(), tail_deadline) { fail("TurkFS partial read timeout"); } core::hint::spin_loop(); }; unsafe { asm!("dmb oshld", options(nostack, preserves_flags)) }; let (tail_used_id, tail_used_len, tail_block_status) = unsafe { ( u32::from_le(core::ptr::read_volatile((used + 12) as *const u32)), u32::from_le(core::ptr::read_volatile((used + 16) as *const u32)), core::ptr::read_volatile(tail_status as *const u8), ) }; if tail_used_index != 2 || tail_used_id != 3 || virtio_ring::validate_read_completion_length(tail_used_len, 1024) != Ok(()) || virtio_ring::read_block_status(tail_block_status) != Ok(()) { fail("TurkFS partial completion identity/status"); } // Completion and the acquire barrier above make this owned // DMA region stable until the final device reset. Borrow it // directly to avoid another 1024-byte EL1 stack copy. let tail_device_bytes = unsafe { core::slice::from_raw_parts(tail_data as *const u8, 1024) }; let tail_output = unsafe { &mut *core::ptr::addr_of_mut!(TURKFS_TAIL_OUTPUT) }; tail_output.fill(0xa5); let returned = turkfs::copy_known_partial_file_data(tail_device_bytes, tail_output) .unwrap_or_else(|_| fail("TurkFS partial file data")); if returned != 513 || tail_output[513] != 0xa5 || tail_device_bytes[513] == 0 { fail("TurkFS partial file guard"); } write_str("TURKOS/FS-TAIL: LBA=0x"); write_hex(tail_lba as usize); write_str(" SIZE=0x"); write_hex(513); write_str(" DMA=0x"); write_hex(tail_device_bytes.len()); write_str(" RETURNED=0x"); write_hex(returned); write_str(" GUARD=0x"); write_hex(tail_output[513] as usize); write_str(" IGNORED-NONZERO=0x"); write_hex(1); write_str(" USEDIDX=0x"); write_hex(tail_used_index as usize); write_str(" USEDID=0x"); write_hex(tail_used_id as usize); write_str(" USEDLEN=0x"); write_hex(tail_used_len as usize); write_str(" STATUS=0x"); write_hex(tail_block_status as usize); write_str("\r\n"); write_line("TURKOS/FS-TAIL: PASS"); // K370: seek into the validated 513-byte file and read across // the 512-byte sector boundary from the real DMA-derived data. let mut cursor = turkfs::FileCursor::new(513); cursor.seek_absolute(510); let mut seek_output = [0xa5u8; 4]; let seek_read = cursor .read(&tail_output[..513], &mut seek_output[..3]) .unwrap_or_else(|_| fail("TurkFS seek read")); if seek_read != 3 || seek_output != [0x80, 0x81, 0x42, 0xa5] || cursor.position() != 513 || cursor.seek_relative(-514) != Err(turkfs::Error::InvalidSeek) { fail("TurkFS seek result"); } write_str("TURKOS/FS-SEEK: OFFSET=0x"); write_hex(510); write_str(" READ=0x"); write_hex(seek_read); write_str(" BYTE0=0x"); write_hex(seek_output[0] as usize); write_str(" BYTE1=0x"); write_hex(seek_output[1] as usize); write_str(" BYTE2=0x"); write_hex(seek_output[2] as usize); write_str(" GUARD=0x"); write_hex(seek_output[3] as usize); write_str(" END=0x"); write_hex(cursor.position() as usize); write_str(" UNDERFLOW-REJECTED=0x"); write_hex(1); write_str("\r\n"); write_line("TURKOS/FS-SEEK: PASS"); // K371: exactly at EOF, just beyond it and at u64::MAX all // return zero and preserve the destination canary. let mut eof_output = [0xa5u8; 4]; let at_eof = cursor .read(&tail_output[..513], &mut eof_output) .unwrap_or_else(|_| fail("TurkFS EOF read")); cursor.seek_absolute(514); let beyond_eof = cursor .read(&tail_output[..513], &mut eof_output) .unwrap_or_else(|_| fail("TurkFS beyond EOF read")); cursor.seek_absolute(u64::MAX); let max_eof = cursor .read(&tail_output[..513], &mut eof_output) .unwrap_or_else(|_| fail("TurkFS maximum EOF read")); let wrap_rejected = cursor.seek_relative(1) == Err(turkfs::Error::InvalidSeek); if at_eof != 0 || beyond_eof != 0 || max_eof != 0 || eof_output != [0xa5; 4] || !wrap_rejected || cursor.position() != u64::MAX { fail("TurkFS EOF result"); } write_str("TURKOS/FS-EOF: AT=0x"); write_hex(513); write_str(" BEYOND=0x"); write_hex(514); write_str(" MAX=0x"); write_hex(u64::MAX as usize); write_str(" RETURNED=0x"); write_hex(max_eof); write_str(" GUARD=0x"); write_hex(eof_output[0] as usize); write_str(" WRAP-REJECTED=0x"); write_hex(1); write_str("\r\n"); write_line("TURKOS/FS-EOF: PASS"); // K374 uses a separate five-entry disk. The normal four-entry // fixture never takes this branch. A real LBA24 directory // read is completed before a self-referential child is tested. if root.contains(b"SUB") { let (directory_lba, directory_count) = turkfs::known_child_directory_descriptor( &root, inode_block, b"SUB", geometry, ) .unwrap_or_else(|_| fail("TurkFS child directory descriptor")); if virtio_mmio::checked_read_span(directory_lba, 1, capacity_sectors).is_err() { fail("TurkFS child directory preflight"); } if scrub_admission.reserve(3) != Ok(6) { fail("TurkFS child directory admission"); } let directory_header = request; let directory_status = request + 16; let directory_data = request + 512; let directory_chain = virtio_ring::read_descriptor_chain_at( 6, directory_header, directory_data, directory_status, ) .unwrap_or_else(|_| fail("TurkFS child directory descriptor chain")); unsafe { let table = desc as *mut virtio_ring::Descriptor; for (index, descriptor) in directory_chain.into_iter().enumerate() { core::ptr::write_volatile(table.add(6 + index), descriptor); } core::ptr::write_volatile( directory_header as *mut virtio_ring::BlockReadHeader, virtio_ring::block_read_header(directory_lba), ); core::ptr::write_volatile(directory_status as *mut u8, 0xff); core::ptr::write_volatile((avail + 8) as *mut u16, 6u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); core::ptr::write_volatile((avail + 2) as *mut u16, 3u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x50, 0); } let directory_deadline = virtio_ring::deadline_after( counter(), frequency .checked_mul(2) .unwrap_or_else(|| fail("TurkFS child directory timeout interval")), ) .unwrap_or_else(|_| fail("TurkFS child directory timeout deadline")); let directory_used_index = loop { let index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if index >= 3 { break index; } if virtio_ring::deadline_expired(counter(), directory_deadline) { fail("TurkFS child directory read timeout"); } core::hint::spin_loop(); }; unsafe { asm!("dmb oshld", options(nostack, preserves_flags)) }; let (directory_used_id, directory_used_len, directory_block_status) = unsafe { ( u32::from_le(core::ptr::read_volatile((used + 20) as *const u32)), u32::from_le(core::ptr::read_volatile((used + 24) as *const u32)), core::ptr::read_volatile(directory_status as *const u8), ) }; if directory_used_index != 3 || directory_used_id != 6 || virtio_ring::validate_read_completion_length(directory_used_len, 512) != Ok(()) || virtio_ring::read_block_status(directory_block_status) != Ok(()) { fail("TurkFS child directory completion identity/status"); } let directory_block = unsafe { core::slice::from_raw_parts( directory_data as *const u8, turkfs::BLOCK_BYTES, ) }; let child = turkfs::parse_root_directory( directory_lba, directory_count, directory_block, ) .unwrap_or_else(|_| fail("TurkFS child directory entries")); let valid_child_names = turkfs::require_utf8_names(&child) .unwrap_or_else(|_| fail("TurkFS invalid UTF-8 child name")); let kernel_inode = turkfs::resolve_known_child_entry(&root, &child, b"SUB", b"KERNEL") .unwrap_or_else(|_| fail("TurkFS child KERNEL lookup")); if kernel_inode != 2 { fail("TurkFS child KERNEL identity"); } let self_inode = match turkfs::resolve_known_child_entry(&root, &child, b"SUB", b"SELF") { Ok(inode) => inode, Err(turkfs::Error::DirectoryCycle) => { let available_index = unsafe { u16::from_le(core::ptr::read_volatile( (avail + 2) as *const u16, )) }; let used_index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if available_index != 3 || used_index != 3 { fail("TurkFS directory cycle escaped before rejection"); } write_str("TURKOS/FS-DIR-CYCLE: INODE=0x"); write_hex(5); write_str(" AVAILABLE=0x"); write_hex(available_index as usize); write_str(" USED=0x"); write_hex(used_index as usize); write_str(" NEXT-READ=0x"); write_hex(0); write_str("\r\n"); fail("TurkFS directory cycle"); } Err(_) => fail("TurkFS child SELF lookup"), }; if self_inode != 2 { fail("TurkFS child SELF identity"); } write_str("TURKOS/FS-DIR: LBA=0x"); write_hex(directory_lba as usize); write_str(" COUNT=0x"); write_hex(directory_count); write_str(" KERNEL-ID=0x"); write_hex(kernel_inode as usize); write_str(" SELF-ID=0x"); write_hex(self_inode as usize); write_str(" USEDIDX=0x"); write_hex(directory_used_index as usize); write_str(" USEDID=0x"); write_hex(directory_used_id as usize); write_str(" USEDLEN=0x"); write_hex(directory_used_len as usize); write_str(" STATUS=0x"); write_hex(directory_block_status as usize); write_str("\r\n"); write_line("TURKOS/FS-DIR: PASS"); // K375 path input uses the two real DMA-derived directory // blocks. Dot stays put; parent from SUB returns to root; // parent from root is an error, never an inode lookup. let root_dot = turkfs::resolve_known_dot_path(&root, &child, b"./KERNEL") .unwrap_or_else(|_| fail("TurkFS root dot lookup")); let child_dot = turkfs::resolve_known_dot_path(&root, &child, b"SUB/./KERNEL") .unwrap_or_else(|_| fail("TurkFS child dot lookup")); let child_parent = turkfs::resolve_known_dot_path(&root, &child, b"SUB/../KERNEL") .unwrap_or_else(|_| fail("TurkFS child parent lookup")); let root_escape_rejected = turkfs::resolve_known_dot_path(&root, &child, b"../KERNEL") == Err(turkfs::Error::PathEscapesRoot) && turkfs::resolve_known_dot_path(&root, &child, b"SUB/../../KERNEL") == Err(turkfs::Error::PathEscapesRoot); if root_dot != 2 || child_dot != 2 || child_parent != 2 || !root_escape_rejected { fail("TurkFS dot path result"); } write_str("TURKOS/FS-DOT: ROOT=0x"); write_hex(root_dot as usize); write_str(" CHILD=0x"); write_hex(child_dot as usize); write_str(" PARENT=0x"); write_hex(child_parent as usize); write_str(" ESCAPE-REJECTED=0x"); write_hex(1); write_str("\r\n"); write_line("TURKOS/FS-DOT: PASS"); // K376: an absolute path starts at root even when the // caller's cwd is SUB. A relative path starts at cwd. let abs_root = turkfs::resolve_known_scoped_path( &root, &child, turkfs::WorkingDirectory::Child, b"/KERNEL", ) .unwrap_or_else(|_| fail("TurkFS absolute root lookup")); let rel_root = turkfs::resolve_known_scoped_path( &root, &child, turkfs::WorkingDirectory::Root, b"KERNEL", ) .unwrap_or_else(|_| fail("TurkFS relative root lookup")); let rel_child = turkfs::resolve_known_scoped_path( &root, &child, turkfs::WorkingDirectory::Child, b"KERNEL", ) .unwrap_or_else(|_| fail("TurkFS relative child lookup")); let abs_child = turkfs::resolve_known_scoped_path( &root, &child, turkfs::WorkingDirectory::Child, b"/SUB/KERNEL", ) .unwrap_or_else(|_| fail("TurkFS absolute child lookup")); let abs_readme = turkfs::resolve_known_scoped_path( &root, &child, turkfs::WorkingDirectory::Child, b"/README", ) .unwrap_or_else(|_| fail("TurkFS absolute README lookup")); let relative_readme_missing = turkfs::resolve_known_scoped_path( &root, &child, turkfs::WorkingDirectory::Child, b"README", ) == Err(turkfs::Error::InvalidRootEntry); let absolute_escape_rejected = turkfs::resolve_known_scoped_path( &root, &child, turkfs::WorkingDirectory::Child, b"/../KERNEL", ) == Err(turkfs::Error::PathEscapesRoot); if abs_root != 2 || rel_root != 2 || rel_child != 2 || abs_child != 2 || abs_readme != 1 || !relative_readme_missing || !absolute_escape_rejected { fail("TurkFS absolute/relative path result"); } write_str("TURKOS/FS-PATH: ABS-ROOT=0x"); write_hex(abs_root as usize); write_str(" REL-ROOT=0x"); write_hex(rel_root as usize); write_str(" REL-CHILD=0x"); write_hex(rel_child as usize); write_str(" ABS-CHILD=0x"); write_hex(abs_child as usize); write_str(" ABS-README=0x"); write_hex(abs_readme as usize); write_str(" REL-README-MISSING=0x"); write_hex(1); write_str(" ABS-ESCAPE-REJECTED=0x"); write_hex(1); write_str("\r\n"); write_line("TURKOS/FS-PATH: PASS"); write_str("TURKOS/FS-UTF8: ROOT=0x"); write_hex(valid_root_names); write_str(" CHILD=0x"); write_hex(valid_child_names); write_str(" VALID=0x"); write_hex(1); write_str("\r\n"); write_line("TURKOS/FS-UTF8: PASS"); // K378: MULTI and TAIL were validated against real DMA // data above. Keep both opens alive while alternating // reads; each handle must own a separate FileCursor. let mut multi_open = turkfs::ReadOnlyOpenFile::open(3, multi_data, 1024) .unwrap_or_else(|_| fail("TurkFS MULTI open")); let mut tail_open = turkfs::ReadOnlyOpenFile::open(4, &tail_output[..513], 513) .unwrap_or_else(|_| fail("TurkFS TAIL open")); let mut multi_output = [0xa5u8; 3]; let mut tail_read_output = [0xa5u8; 4]; if multi_open.read(&mut multi_output[..2]) != Ok(2) || multi_output != [0x3c, 0x3d, 0xa5] || (multi_open.position(), tail_open.position()) != (2, 0) { fail("TurkFS first interleaved read"); } tail_open.seek_absolute(510); if tail_open.read(&mut tail_read_output[..3]) != Ok(3) || tail_read_output != [0x80, 0x81, 0x42, 0xa5] || (multi_open.position(), tail_open.position()) != (2, 513) { fail("TurkFS second interleaved read"); } multi_open.seek_absolute(514); if multi_open.read(&mut multi_output[..2]) != Ok(2) || multi_output != [0xc1, 0xc0, 0xa5] || (multi_open.position(), tail_open.position()) != (516, 513) { fail("TurkFS third interleaved read"); } tail_open.seek_absolute(0); if tail_open.read(&mut tail_read_output[..2]) != Ok(2) || tail_read_output[..2] != [0x7e, 0x7f] || (multi_open.position(), tail_open.position()) != (516, 2) { fail("TurkFS fourth interleaved read"); } let invalid_open_rejected = matches!( turkfs::ReadOnlyOpenFile::open(0, multi_data, 1), Err(turkfs::Error::InvalidOpenFile) ) && matches!( turkfs::ReadOnlyOpenFile::open(3, multi_data, 1025), Err(turkfs::Error::InvalidOpenFile) ); if !invalid_open_rejected || multi_open.inode() != 3 || tail_open.inode() != 4 { fail("TurkFS open identity/bounds"); } write_str("TURKOS/FS-OPEN: A-ID=0x"); write_hex(multi_open.inode() as usize); write_str(" B-ID=0x"); write_hex(tail_open.inode() as usize); write_str(" A-POS=0x"); write_hex(multi_open.position() as usize); write_str(" B-POS=0x"); write_hex(tail_open.position() as usize); write_str(" A-BYTE0=0x"); write_hex(multi_output[0] as usize); write_str(" B-BYTE0=0x"); write_hex(tail_read_output[0] as usize); write_str(" INVALID-REJECTED=0x"); write_hex(1); write_str(" INDEPENDENT=0x"); write_hex(1); write_str("\r\n"); write_line("TURKOS/FS-OPEN: PASS"); // K379: the file-facing write API must refuse both a // payload and an empty write before any virtio request. // The already-open cursors and DMA-verified data remain // unchanged; the host test also hashes its read-only disk. let nonempty_rejected = multi_open.write(&[0xde, 0xad]) == Err(turkfs::Error::ReadOnlyFilesystem); let empty_rejected = tail_open.write(&[]) == Err(turkfs::Error::ReadOnlyFilesystem); let write_available = unsafe { u16::from_le(core::ptr::read_volatile((avail + 2) as *const u16)) }; let write_used = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if !nonempty_rejected || !empty_rejected || (multi_open.position(), tail_open.position()) != (516, 2) || multi_data[514] != 0xc1 || tail_output[0] != 0x7e || (write_available, write_used) != (3, 3) { fail("TurkFS read-only file write rejection"); } write_str("TURKOS/FS-WRITE: A-POS=0x"); write_hex(multi_open.position() as usize); write_str(" B-POS=0x"); write_hex(tail_open.position() as usize); write_str(" A-BYTE=0x"); write_hex(multi_data[514] as usize); write_str(" B-BYTE=0x"); write_hex(tail_output[0] as usize); write_str(" NONEMPTY-REJECTED=0x"); write_hex(1); write_str(" EMPTY-REJECTED=0x"); write_hex(1); write_str(" AVAILABLE=0x"); write_hex(write_available as usize); write_str(" USED=0x"); write_hex(write_used as usize); write_str("\r\n"); write_line("TURKOS/FS-WRITE: PASS"); // K380: hash the exact logical bytes that came from the // verified disk reads. TAIL's unused sector suffix must // not enter the digest; README hashes zero bytes. let files: [(&str, &[u8], &[u8; 64]); 4] = [ ( "README", &[], b"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", ), ( "KERNEL", &file_block[..], b"8e6d10d6c91dba67b2876ec3c81ffd7ff76ad09ccabf8cf79cb41d879b0ed226", ), ( "MULTI", multi_data, b"3dc38e6a35d8c887a0499f3a3127df1bfc7310c1dc1b160491376db8be90b51e", ), ( "TAIL", &tail_output[..513], b"8388c98b9062541ce387d98a910f520f4f9478c2cbe5622abadd35967fb0da9a", ), ]; write_str("TURKOS/FS-HASH:"); for (name, logical_bytes, expected) in files { let digest = turkfs::file_sha256(logical_bytes); let encoded = turkos_kernel::program_hash::hex_lower(&digest); if &encoded != expected { fail("TurkFS logical file hash matrix"); } write_str(" "); write_str(name); write_str("="); write_str( core::str::from_utf8(&encoded) .unwrap_or_else(|_| fail("TurkFS hash encoding")), ); } write_str("\r\n"); write_line("TURKOS/FS-HASH: PASS"); } #[cfg(feature = "turkfs-bitmap-test")] { // The fixed four-file fixture has two completed reads in // this queue epoch. The next three descriptor slots are // idle, so the bitmap is a third real RO GET before reset. if unsafe { u16::from_le(core::ptr::read_volatile((avail + 2) as *const u16)) } != 2 || unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) } != 2 || scrub_admission.reserve(3) != Ok(6) || virtio_mmio::checked_read_span(26, 1, capacity_sectors) != Ok(Some((26 * 512, 512))) { fail("TurkFS bitmap read preflight"); } let bitmap_data = request + 2048; let chain = virtio_ring::read_descriptor_chain_at( 6, request, bitmap_data, request + 16, ) .unwrap_or_else(|_| fail("TurkFS bitmap descriptor chain")); unsafe { let table = desc as *mut virtio_ring::Descriptor; for (index, descriptor) in chain.into_iter().enumerate() { core::ptr::write_volatile(table.add(6 + index), descriptor); } core::ptr::write_volatile( request as *mut virtio_ring::BlockReadHeader, virtio_ring::block_read_header(26), ); core::ptr::write_volatile((request + 16) as *mut u8, 0xff); core::ptr::write_volatile((avail + 8) as *mut u16, 6u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); core::ptr::write_volatile((avail + 2) as *mut u16, 3u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x50, 0); } let deadline = virtio_ring::deadline_after(counter(), frequency) .unwrap_or_else(|_| fail("TurkFS bitmap deadline")); loop { let index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if index == 3 { break; } if virtio_ring::deadline_expired(counter(), deadline) { fail("TurkFS bitmap read timeout"); } core::hint::spin_loop(); } let (id, length, status) = unsafe { asm!("dmb oshld", options(nostack, preserves_flags)); ( u32::from_le(core::ptr::read_volatile((used + 20) as *const u32)), u32::from_le(core::ptr::read_volatile((used + 24) as *const u32)), core::ptr::read_volatile((request + 16) as *const u8), ) }; if (id, length, virtio_ring::read_block_status(status)) != (6, 513, Ok(())) { fail("TurkFS bitmap read completion"); } let bitmap = unsafe { core::slice::from_raw_parts(bitmap_data as *const u8, 512) }; #[cfg(not(feature = "turkfs-enospc-test"))] { match turkfs::validate_known_allocation_bitmap( bitmap, geometry.block_count, file_lba, multi_lba, tail_lba, ) { Ok(()) => {} Err(turkfs::Error::UnallocatedExtent) => { fail("TurkFS bitmap missing owner bit") } Err(turkfs::Error::OrphanAllocatedBlock) => { fail("TurkFS bitmap orphan block") } Err(turkfs::Error::OverlappingExtent) => { fail("TurkFS bitmap shared block") } Err(_) => fail("TurkFS bitmap invalid"), } write_str("TURKOS/FS-BITMAP: LBA=0x"); write_hex(26); write_str(" USEDIDX=0x"); write_hex(3); write_str(" USEDID=0x"); write_hex(id as usize); write_str(" USEDLEN=0x"); write_hex(length as usize); write_str(" OWNERS=0x"); write_hex(5); write_str(" FREE25=0x"); write_hex(1); write_str("\r\n"); write_line("TURKOS/FS-BITMAP: PASS"); } #[cfg(feature = "turkfs-enospc-test")] { // Synthetic exhaustion: sectors 0..2047 are marked // unavailable except optionally sector 27. This does // not assert that they belong to actual files. let free27 = bitmap[27 / 8] & (1 << (27 % 8)) == 0; for lba in 0..4096usize { let set = bitmap[lba / 8] & (1 << (lba % 8)) != 0; if set != (lba < 2048 && (!free27 || lba != 27)) { fail("TurkFS ENOSPC fixture bitmap"); } } let mut plan = [0u8; 512]; plan.copy_from_slice(bitmap); let first = turkfs::reserve_first_free_block(&mut plan, 27, 2048); if free27 && first != Ok(27) { fail("TurkFS ENOSPC one-free reservation"); } if !free27 && first != Err(turkfs::Error::NoSpace) { fail("TurkFS ENOSPC full reservation"); } let after_first = plan; if turkfs::reserve_first_free_block(&mut plan, 27, 2048) != Err(turkfs::Error::NoSpace) || plan != after_first { fail("TurkFS ENOSPC partial mutation"); } write_str("TURKOS/FS-ENOSPC: FREEBEFORE=0x"); write_hex(usize::from(free27)); write_str(" RESERVED=0x"); write_hex(if free27 { 27 } else { 0 }); write_str(" ENOSPC=0x1 DISK-WRITES=0x0 USEDIDX=0x3 USEDID=0x"); write_hex(id as usize); write_str(" USEDLEN=0x"); write_hex(length as usize); write_str("\r\n"); write_line("TURKOS/FS-ENOSPC: PASS"); } } } // SAFETY: reset detaches all rings before their owned frames can be reused. unsafe { write32(base, 0x70, 0); if read32(base, 0x70) != 0 || read32(base, 0x44) != 0 { fail("DMA reset readback"); } } lease .mark_reset() .unwrap_or_else(|_| fail("DMA lease reset")); lease .release(allocator) .unwrap_or_else(|_| fail("DMA lease release")); if allocator.remaining_pages() != free_before { fail("DMA frames not fully returned"); } write_line( "TURKOS/VIRTIO-DMA: ACTIVE-RELEASE-BLOCKED=0x1 RESET=0x1 RELEASED=0x4 REMAINING-RESTORED=0x1", ); write_line("TURKOS/VIRTIO-DMA: PASS"); write_line("TURKOS/VIRTIO-QUEUE: PASS"); park() } } }