//! K397: a bounded three-attempt, read-only virtio-blk EIO experiment. //! The backend injects one or persistent read_aio/EIO for known LBA19. use super::*; use core::arch::asm; use turkos_kernel::{turkfs, virtio_ring}; const TARGET_LBA: u64 = 19; pub(super) fn smoke_test(base: u64, boot: &BootData, allocator: &mut memory::FrameAllocator) -> ! { let capacity = unsafe { u64::from(read32(base, 0x100)) | u64::from(read32(base, 0x104)) << 32 }; if virtio_mmio::checked_read_span(TARGET_LBA, 1, capacity) != Ok(Some((9728, 512))) { fail("retry target capacity"); } 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("retry DMA lease")); let [desc, avail, used, request] = lease.frames().unwrap_or_else(|_| fail("retry frames")); virtio_ring::split_ring_pages( desc, avail, used, 8, boot.hardware.ram_base, boot.hardware.ram_size, ) .unwrap_or_else(|_| fail("retry ring geometry")); let header = virtio_ring::dma_subregion(request, 0, 16).unwrap_or_else(|_| fail("retry header")); let status = virtio_ring::dma_subregion(request, 16, 1).unwrap_or_else(|_| fail("retry status")); let data = virtio_ring::dma_subregion(request, 512, 512).unwrap_or_else(|_| fail("retry data")); unsafe { for frame in [desc, avail, used, request] { core::ptr::write_bytes(frame as *mut u8, 0, memory::PAGE_SIZE as usize); } write32(base, 0x70, 0); if read32(base, 0x70) != 0 { fail("retry 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 (low, high) = virtio_mmio::select_modern_read_only_features(offered_low, offered_high) .unwrap_or_else(|_| fail("retry requires RO modern device")); 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("retry FEATURES_OK"); } write32(base, 0x30, 0); if read32(base, 0x34) < 8 || read32(base, 0x44) != 0 { fail("retry 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("retry QueueReady"); } lease .mark_ready() .unwrap_or_else(|_| fail("retry lease ready")); write32(base, 0x70, 0x0f); if read32(base, 0x70) != 0x0f { fail("retry DRIVER_OK"); } } let frequency: u64; unsafe { asm!("mrs {value}, CNTFRQ_EL0", value = out(reg) frequency, options(nomem, nostack, preserves_flags)); } let mut recovered = false; let mut exhausted = false; for attempt in 1..=3u16 { let chain = virtio_ring::read_descriptor_chain(header, data, status); unsafe { 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(TARGET_LBA), ); core::ptr::write_bytes(data as *mut u8, 0, 512); core::ptr::write_volatile(status as *mut u8, 0xff); core::ptr::write_volatile( (avail + 4 + u64::from(attempt - 1) * 2) as *mut u16, 0u16.to_le(), ); asm!("dmb oshst", options(nostack, preserves_flags)); core::ptr::write_volatile((avail + 2) as *mut u16, attempt.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("retry request deadline")); loop { let index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if index == attempt { break; } if virtio_ring::deadline_expired(counter(), deadline) { fail("retry request timeout"); } core::hint::spin_loop(); } let (id, length, completion_status) = unsafe { asm!("dmb oshld", options(nostack, preserves_flags)); let entry = used + 4 + u64::from(attempt - 1) * 8; ( u32::from_le(core::ptr::read_volatile(entry as *const u32)), u32::from_le(core::ptr::read_volatile((entry + 4) as *const u32)), core::ptr::read_volatile(status as *const u8), ) }; if id != 0 || length != 513 { fail("retry completion identity/length"); } let decision = virtio_ring::bounded_read_retry(attempt as u8, completion_status) .unwrap_or_else(|_| fail("retry unsupported/invalid status")); if attempt == 1 && decision != virtio_ring::ReadRetryDecision::Retry { fail("retry fixture expected initial EIO"); } write_str("TURKOS/FS-RETRY: ATTEMPT=0x"); write_hex(attempt as usize); write_str(" USEDIDX=0x"); write_hex(attempt as usize); write_str(" USEDID=0x"); write_hex(id as usize); write_str(" USEDLEN=0x"); write_hex(length as usize); write_str(" STATUS=0x"); write_hex(completion_status as usize); write_str(" DECISION="); match decision { virtio_ring::ReadRetryDecision::Retry => write_line("RETRY"), virtio_ring::ReadRetryDecision::Complete => { if cfg!(feature = "virtio-io-retry-exhaust-test") || attempt != 2 { fail("retry unexpected success attempt"); } let block = unsafe { &*(data as *const [u8; 512]) }; if virtio_ring::sector_sha256(block) != turkfs::KERNEL_BLOCK_SHA256 { fail("retry recovered data hash"); } write_line("RECOVERED"); recovered = true; break; } virtio_ring::ReadRetryDecision::Exhausted => { if !cfg!(feature = "virtio-io-retry-exhaust-test") || attempt != 3 { fail("retry exhausted unexpectedly"); } write_line("EXHAUSTED"); exhausted = true; break; } } } unsafe { write32(base, 0x70, 0); if read32(base, 0x70) != 0 || read32(base, 0x44) != 0 { fail("retry final reset"); } } lease .mark_reset() .unwrap_or_else(|_| fail("retry lease reset")); lease .release(allocator) .unwrap_or_else(|_| fail("retry lease release")); if allocator.remaining_pages() != free_before { fail("retry DMA frames leaked"); } write_str("TURKOS/FS-RETRY: RECOVERED=0x"); write_hex(usize::from(recovered)); write_str(" EXHAUSTED=0x"); write_hex(usize::from(exhausted)); write_str(" LIMIT=0x3 DMA-RESTORED=0x1\r\n"); write_line("TURKOS/FS-RETRY: PASS"); park() }