//! K393: disposable TurkFS copy-on-write experiment, not a production writer. //! A known inode block is read and hash-checked before changing its KERNEL //! data pointer. New file bytes are written and flushed before inode metadata. use super::*; use core::arch::asm; use turkos_kernel::virtio_ring; const INODE_LBA: u64 = 18; const OLD_DATA_LBA: u64 = 19; const NEW_DATA_LBA: u64 = 25; const INODE2_DATA_POINTER_OFFSET: u64 = 32 + 16; const KNOWN_INODE_SHA256: [u8; 32] = [ 0xc1, 0xf6, 0x16, 0x98, 0xd4, 0x40, 0xf6, 0xe6, 0x6d, 0x57, 0x4b, 0xda, 0x56, 0xfd, 0x55, 0xad, 0x9f, 0x07, 0x5c, 0x5c, 0xbb, 0x2f, 0x52, 0x94, 0x5b, 0xec, 0x42, 0x98, 0xab, 0x3f, 0xab, 0x02, ]; fn request( base: u64, desc: u64, avail: u64, used: u64, header_addr: u64, status_addr: u64, data_addr: u64, operation: u32, sector: u64, next_index: u16, frequency: u64, ) { let (chain, descriptors) = match operation { 0 => ( virtio_ring::read_descriptor_chain(header_addr, data_addr, status_addr), 3, ), 1 => ( virtio_ring::write_descriptor_chain(header_addr, data_addr, status_addr), 3, ), 4 => ( [ virtio_ring::Descriptor { addr_le: header_addr.to_le(), len_le: 16u32.to_le(), flags_le: virtio_ring::DESC_F_NEXT.to_le(), next_le: 1u16.to_le(), }, virtio_ring::Descriptor { addr_le: status_addr.to_le(), len_le: 1u32.to_le(), flags_le: virtio_ring::DESC_F_WRITE.to_le(), next_le: 0, }, virtio_ring::Descriptor::default(), ], 2, ), _ => fail("unsupported metadata operation"), }; // SAFETY: all addresses are disjoint regions of the four leased identity // RAM frames. The previous request has completed before any descriptor is // reused; the device sees the available index only after the store barrier. unsafe { for (index, descriptor) in chain.into_iter().take(descriptors).enumerate() { core::ptr::write_volatile( (desc as *mut virtio_ring::Descriptor).add(index), descriptor, ); } let header = match operation { 0 => virtio_ring::block_read_header(sector), 1 => virtio_ring::block_write_header(sector), _ => virtio_ring::block_flush_header(), }; core::ptr::write_volatile(header_addr as *mut virtio_ring::BlockReadHeader, header); core::ptr::write_volatile(status_addr as *mut u8, 0xff); let ring_offset = 4 + u64::from((next_index - 1) % 8) * 2; core::ptr::write_volatile((avail + ring_offset) as *mut u16, 0u16.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); core::ptr::write_volatile((avail + 2) as *mut u16, next_index.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("metadata request deadline")); let observed = loop { let index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if index >= next_index { break index; } if virtio_ring::deadline_expired(counter(), deadline) { fail("metadata request timeout"); } core::hint::spin_loop(); }; let (id, length, status) = unsafe { asm!("dmb oshld", options(nostack, preserves_flags)); let entry = used + 4 + u64::from(next_index - 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_addr as *const u8), ) }; if observed != next_index || id != 0 || length != if operation == 0 { 513 } else { 1 } || virtio_ring::read_block_status(status) != Ok(()) { fail("metadata request completion/status"); } } 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 capacity <= NEW_DATA_LBA { fail("metadata fixture 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("metadata DMA lease")); let [desc, avail, used, request_page] = lease.frames().unwrap_or_else(|_| fail("metadata frames")); virtio_ring::split_ring_pages( desc, avail, used, 8, boot.hardware.ram_base, boot.hardware.ram_size, ) .unwrap_or_else(|_| fail("metadata ring geometry")); let header = virtio_ring::dma_subregion(request_page, 0, 16).unwrap_or_else(|_| fail("metadata header")); let status = virtio_ring::dma_subregion(request_page, 16, 1).unwrap_or_else(|_| fail("metadata status")); let new_data = virtio_ring::dma_subregion(request_page, 512, 512) .unwrap_or_else(|_| fail("metadata data")); let inode_data = virtio_ring::dma_subregion(request_page, 1536, 512) .unwrap_or_else(|_| fail("metadata inode")); unsafe { for frame in [desc, avail, used, request_page] { core::ptr::write_bytes(frame as *mut u8, 0, memory::PAGE_SIZE as usize); } write32(base, 0x70, 0); if read32(base, 0x70) != 0 { fail("metadata 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_experimental_flush_features(offered_low, offered_high) .unwrap_or_else(|_| fail("metadata requires writable FLUSH 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("metadata FEATURES_OK"); } write32(base, 0x30, 0); if read32(base, 0x34) < 8 || read32(base, 0x44) != 0 { fail("metadata 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("metadata QueueReady"); } lease .mark_ready() .unwrap_or_else(|_| fail("metadata lease ready")); write32(base, 0x70, 0x0f); if read32(base, 0x70) != 0x0f { fail("metadata DRIVER_OK"); } } let frequency: u64; unsafe { asm!("mrs {value}, CNTFRQ_EL0", value = out(reg) frequency, options(nomem, nostack, preserves_flags)); } request( base, desc, avail, used, header, status, inode_data, 0, INODE_LBA, 1, frequency, ); let inode_block = unsafe { &*(inode_data as *const [u8; 512]) }; if virtio_ring::sector_sha256(inode_block) != KNOWN_INODE_SHA256 { fail("metadata inode fixture hash"); } let old_pointer = unsafe { u64::from_le(core::ptr::read_volatile( (inode_data + INODE2_DATA_POINTER_OFFSET) as *const u64, )) }; if old_pointer != OLD_DATA_LBA { fail("metadata old inode pointer"); } unsafe { for index in 0..512usize { core::ptr::write_volatile((new_data + index as u64) as *mut u8, index as u8 ^ 0x5a); } } request( base, desc, avail, used, header, status, new_data, 1, NEW_DATA_LBA, 2, frequency, ); request( base, desc, avail, used, header, status, new_data, 4, 0, 3, frequency, ); write_str( "TURKOS/META-PRECOMMIT: INODE-READIDX=0x1 DATA-WRITEIDX=0x2 DATA-FLUSHIDX=0x3 OLD-DATA-LBA=0x", ); write_hex(OLD_DATA_LBA as usize); write_str(" NEW-DATA-LBA=0x"); write_hex(NEW_DATA_LBA as usize); write_str(" INODE-AVAILIDX=0x3\r\n"); if cfg!(feature = "virtio-metadata-precommit-test") { write_line("TURKOS/META-PRECOMMIT: PAUSE"); park() } unsafe { core::ptr::write_volatile( (inode_data + INODE2_DATA_POINTER_OFFSET) as *mut u64, NEW_DATA_LBA.to_le(), ); } request( base, desc, avail, used, header, status, inode_data, 1, INODE_LBA, 4, frequency, ); request( base, desc, avail, used, header, status, inode_data, 4, 0, 5, frequency, ); unsafe { write32(base, 0x70, 0); if read32(base, 0x70) != 0 || read32(base, 0x44) != 0 { fail("metadata final reset"); } } lease .mark_reset() .unwrap_or_else(|_| fail("metadata lease reset")); lease .release(allocator) .unwrap_or_else(|_| fail("metadata lease release")); if allocator.remaining_pages() != free_before { fail("metadata DMA frames leaked"); } write_str( "TURKOS/META-COMMIT: READIDX=0x1 DATA-IDX=0x2 DATA-FLUSHIDX=0x3 INODE-IDX=0x4 INODE-FLUSHIDX=0x5 OLD=0x", ); write_hex(OLD_DATA_LBA as usize); write_str(" NEW=0x"); write_hex(NEW_DATA_LBA as usize); write_str(" DMA-RESTORED=0x1\r\n"); write_line("TURKOS/META-COMMIT: PASS"); park() }