//! K401: a read-only TurkFS INIT file is loaded from four completed MMIO //! block reads, never from the embedded ELF hello fixture. use super::*; use core::arch::asm; use turkos_kernel::{turkfs, virtio_mmio, virtio_ring}; const INIT_FILE_SHA256: &[u8; 64] = b"6147537391987e50616dc3b33924a50ef138387ab54b4fc4e22d613e00d513eb"; const INIT_EXIT_FILE_SHA256: &[u8; 64] = b"6a89a44a94f07c87621d2b6fe95f4fb03f2ade763aeea7d252cbeafa6a7e0134"; static mut SECTORS: [[u8; 512]; 4] = [[0; 512]; 4]; fn fail(reason: &str) -> ! { write_str("TURKOS/INIT-ERROR: "); write_line(reason); park() } unsafe fn read32(base: u64, offset: u64) -> u32 { unsafe { core::ptr::read_volatile((base + offset) as *const u32) } } unsafe fn write32(base: u64, offset: u64, value: u32) { unsafe { core::ptr::write_volatile((base + offset) as *mut u32, value) } } unsafe fn write64(base: u64, low: u64, address: u64) { unsafe { write32(base, low, address as u32); write32(base, low + 4, (address >> 32) as u32); } } fn counter() -> u64 { let value: u64; unsafe { asm!("mrs {value}, CNTPCT_EL0", value = out(reg) value, options(nomem, nostack, preserves_flags)) }; value } fn read_sector( base: u64, lba: u64, sequence: u16, capacity: u64, frames: [u64; 4], frequency: u64, ) { if virtio_mmio::checked_read_span(lba, 1, capacity).is_err() { fail("LBA outside device"); } let [desc, avail, used, request] = frames; let header = virtio_ring::dma_subregion(request, 0, 16).unwrap_or_else(|_| fail("header range")); let status = virtio_ring::dma_subregion(request, 16, 1).unwrap_or_else(|_| fail("status range")); let data = virtio_ring::dma_subregion(request, 512, 512).unwrap_or_else(|_| fail("data range")); 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(lba), ); core::ptr::write_bytes(data as *mut u8, 0xa5, 512); core::ptr::write_volatile(status as *mut u8, 0xff); core::ptr::write_volatile( (avail + 4 + u64::from(sequence - 1) * 2) as *mut u16, 0u16.to_le(), ); asm!("dmb oshst", options(nostack, preserves_flags)); core::ptr::write_volatile((avail + 2) as *mut u16, sequence.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("request deadline")); loop { let index = unsafe { u16::from_le(core::ptr::read_volatile((used + 2) as *const u16)) }; if index == sequence { break; } if virtio_ring::deadline_expired(counter(), deadline) { fail("GET timeout"); } core::hint::spin_loop(); } let (id, length, result) = unsafe { asm!("dmb oshld", options(nostack, preserves_flags)); let entry = used + 4 + u64::from(sequence - 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 || virtio_ring::validate_read_completion_length(length, 512) != Ok(()) || virtio_ring::read_block_status(result) != Ok(()) { fail("GET identity/length/status"); } let destination = unsafe { core::ptr::addr_of_mut!(SECTORS) .cast::() .add((sequence as usize - 1) * 512) }; for offset in 0..512usize { unsafe { core::ptr::write( destination.add(offset), core::ptr::read_volatile((data + offset as u64) as *const u8), ) }; } write_str("TURKOS/INIT-GET: LBA=0x"); write_hex(lba as usize); write_str(" USEDIDX=0x"); write_hex(sequence 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(result as usize); write_str("\r\n"); } pub(super) fn smoke_test( boot: &BootData, allocator: &mut memory::FrameAllocator, tables: &mmu::Tables, layout: mmu::Layout, ) -> ! { let mut block = None; for node in boot.virtio_mmio.as_slice() { let base = node.base; 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 && block.replace(base).is_some() { fail("duplicate block device"); } if id != 0 && id != 2 { fail("unexpected device"); } } let base = block.unwrap_or_else(|| fail("block device missing")); let capacity = unsafe { u64::from(read32(base, 0x100)) | u64::from(read32(base, 0x104)) << 32 }; if capacity != 2048 { fail("INIT 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("DMA lease")); let frames = lease.frames().unwrap_or_else(|_| fail("DMA frames")); let [desc, avail, used, _request] = frames; virtio_ring::split_ring_pages( desc, avail, used, 8, boot.hardware.ram_base, boot.hardware.ram_size, ) .unwrap_or_else(|_| fail("ring geometry")); unsafe { for frame in frames { core::ptr::write_bytes(frame as *mut u8, 0, memory::PAGE_SIZE as usize); } write32(base, 0x70, 0); if read32(base, 0x70) != 0 { fail("reset"); } write32(base, 0x70, 3); write32(base, 0x14, 0); let low = read32(base, 0x10); write32(base, 0x14, 1); let high = read32(base, 0x10); let (accepted_low, accepted_high) = virtio_mmio::select_modern_read_only_features(low, high) .unwrap_or_else(|_| fail("requires RO modern device")); 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"); } write32(base, 0x30, 0); if read32(base, 0x34) < 8 || read32(base, 0x44) != 0 { fail("queue capacity"); } write32(base, 0x38, 8); write64(base, 0x80, desc); write64(base, 0x90, avail); write64(base, 0xa0, used); asm!("dmb oshst", options(nostack, preserves_flags)); write32(base, 0x44, 1); if read32(base, 0x44) != 1 { fail("QueueReady"); } lease.mark_ready().unwrap_or_else(|_| fail("lease ready")); write32(base, 0x70, 0x0f); if read32(base, 0x70) != 0x0f { fail("DRIVER_OK"); } } let frequency: u64; unsafe { asm!("mrs {value}, CNTFRQ_EL0", value = out(reg) frequency, options(nomem, nostack, preserves_flags)) }; read_sector(base, 16, 1, capacity, frames, frequency); let superblock = unsafe { core::slice::from_raw_parts(core::ptr::addr_of!(SECTORS).cast::(), 512) }; turkfs::require_format_version(superblock).unwrap_or_else(|_| fail("format version")); turkfs::require_superblock_signature(superblock) .unwrap_or_else(|_| fail("superblock signature")); let geometry = turkfs::require_block_geometry(superblock, capacity * 512) .unwrap_or_else(|_| fail("block geometry")); let (root_lba, root_count) = turkfs::root_directory_location(superblock, geometry) .unwrap_or_else(|_| fail("root descriptor")); if root_lba != 17 || root_count != 5 { fail("INIT root layout"); } read_sector(base, root_lba, 2, capacity, frames, frequency); let root_block = unsafe { core::slice::from_raw_parts(core::ptr::addr_of!(SECTORS).cast::().add(512), 512) }; let root = turkfs::parse_root_directory(root_lba, root_count, root_block) .unwrap_or_else(|_| fail("root entry")); turkfs::require_utf8_names(&root).unwrap_or_else(|_| fail("root UTF-8")); if root.find(b"INIT").map(|entry| entry.inode) != Some(5) { fail("INIT root name/inode"); } let inode_lba = turkfs::inode_table_location(superblock, geometry, root_lba) .unwrap_or_else(|_| fail("inode pointer")); if inode_lba != 18 { fail("INIT inode layout"); } read_sector(base, inode_lba, 3, capacity, frames, frequency); let inode_block = unsafe { core::slice::from_raw_parts(core::ptr::addr_of!(SECTORS).cast::().add(1024), 512) }; let file_lba = turkfs::known_single_block_lba(&root, inode_block, b"INIT", geometry, inode_lba) .unwrap_or_else(|_| fail("INIT inode")); if file_lba != 27 { fail("INIT data LBA"); } read_sector(base, file_lba, 4, capacity, frames, frequency); let file = unsafe { &*core::ptr::addr_of!(SECTORS).cast::<[u8; 512]>().add(3) }; let digest = turkfs::file_sha256(file); let hex = turkos_kernel::program_hash::hex_lower(&digest); let expected = if cfg!(feature = "turkfs-init-exit-test") { INIT_EXIT_FILE_SHA256 } else { INIT_FILE_SHA256 }; if &hex != expected { fail("INIT ELF hash"); } unsafe { write32(base, 0x70, 0); if read32(base, 0x70) != 0 || read32(base, 0x44) != 0 { fail("final reset"); } } lease.mark_reset().unwrap_or_else(|_| fail("lease reset")); lease .release(allocator) .unwrap_or_else(|_| fail("lease release")); if allocator.remaining_pages() != free_before { fail("DMA leak"); } write_str("TURKOS/INIT-FROM-DISK: ROOT=0x11 INODE=0x5 DATA=0x1b ELF-SHA256="); write_str(core::str::from_utf8(&hex).unwrap_or_else(|_| fail("hash encoding"))); write_str(" DMA-RESTORED=0x1\r\n"); write_line("TURKOS/INIT-FROM-DISK: PASS"); elf_hello_probe::smoke_test_with_file(allocator, tables, layout, file) }