//! Disposable 2048-sector disk transport derived from the measured P12 driver. //! Only control/readback IN, three fixed OUT sectors and FLUSH are reachable. use super::*; use turkos_kernel::{journal_disk::{Block, Error}, virtio_mmio, virtio_ring}; const QUEUE: u16 = 8; pub(super) struct Disk { // Transport frames belong to a verified-reset DMA lease. base: u64, capacity: u64, frames: [u64; 4], next: u16, frequency: u64, lease: virtio_ring::DmaLease, writable: bool, flush: bool, counts: [usize; 3], free_open: u64, free_leased: u64, } /// `NAME=0x` without leading zeros keeps the per-request lines short. fn hex_field(name: &str, value: usize) { const HEX: &[u8; 16] = b"0123456789abcdef"; let mut digits = [0u8; 16]; let mut start = digits.len(); let mut rest = value; loop { start -= 1; digits[start] = HEX[rest & 15]; rest >>= 4; if rest == 0 { break; } } write_str(name); write_str("=0x"); write_str(core::str::from_utf8(&digits[start..]).unwrap()); } impl Disk { pub(super) fn open( boot: &BootData, allocator: &mut memory::FrameAllocator, ) -> Result { let mut device = None; let blob = unsafe { core::slice::from_raw_parts(boot.dtb_address as *const u8, boot.dtb_size) }; let nodes = dtb::discover_virtio_mmio(blob).map_err(|_| Error::Io)?; for node in nodes.as_slice() { let b = node.base; unsafe { if read_volatile(b as *const u32) != virtio_mmio::MAGIC || read_volatile((b + 4) as *const u32) != 2 { return Err(Error::Io); } match read_volatile((b + 8) as *const u32) { 0 | 1 | 4 => {} 2 if device.replace(b).is_none() => {} _ => return Err(Error::Io), } } } let base = device.ok_or(Error::Io)?; let free_open = allocator.remaining_pages(); let lease = virtio_ring::DmaLease::allocate( allocator, boot.hardware.ram_base, boot.hardware.ram_size, ) .map_err(|_| Error::Io)?; let frames = lease.frames().map_err(|_| Error::Io)?; let mut disk = Self { base, capacity: 0, frames, next: 0, frequency: 0, lease, writable: false, flush: false, counts: [0; 3], free_open, free_leased: allocator.remaining_pages(), }; unsafe { for frame in frames { core::ptr::write_bytes(frame as *mut u8, 0, 4096); } disk.set(0x70, 0); if disk.get(0x70) != 0 { return Err(Error::Io); } disk.set(0x70, 3); disk.set(0x14, 0); let low = disk.get(0x10); disk.set(0x14, 1); let high = disk.get(0x10); if low & virtio_mmio::BLK_F_RO != 0 || low & virtio_mmio::BLK_F_FLUSH == 0 || high & virtio_mmio::MODERN_F_VERSION_1 == 0 || high & virtio_mmio::MODERN_F_IOMMU_PLATFORM != 0 { return Err(Error::Io); } let (l, h) = (virtio_mmio::BLK_F_FLUSH, virtio_mmio::MODERN_F_VERSION_1); disk.writable = l & virtio_mmio::BLK_F_RO == 0; disk.flush = l & virtio_mmio::BLK_F_FLUSH != 0; disk.set(0x24, 0); disk.set(0x20, l); disk.set(0x24, 1); disk.set(0x20, h); disk.set(0x70, 0x0b); if disk.get(0x70) != 0x0b { return Err(Error::Io); } disk.set(0x30, 0); if disk.get(0x34) < u32::from(QUEUE) || disk.get(0x44) != 0 { return Err(Error::Io); } virtio_ring::split_ring_pages( frames[0], frames[1], frames[2], u32::from(QUEUE), boot.hardware.ram_base, boot.hardware.ram_size, ) .map_err(|_| Error::Io)?; disk.set(0x38, u32::from(QUEUE)); for (at, address) in [(0x80, frames[0]), (0x90, frames[1]), (0xa0, frames[2])] { disk.set(at, address as u32); disk.set(at + 4, (address >> 32) as u32); } asm!("dmb oshst", options(nostack, preserves_flags)); disk.set(0x44, 1); if disk.get(0x44) != 1 { return Err(Error::Io); } disk.lease.mark_ready().map_err(|_| Error::Io)?; disk.set(0x70, 0x0f); if disk.get(0x70) != 0x0f { return Err(Error::Io); } disk.capacity = u64::from(disk.get(0x100)) | (u64::from(disk.get(0x104)) << 32); asm!("mrs {v}, CNTFRQ_EL0", v = out(reg) disk.frequency, options(nomem, nostack, preserves_flags)); } if disk.frequency == 0 || disk.capacity != 2048 || !disk.writable || !disk.flush { return Err(Error::Io); } Ok(disk) } unsafe fn get(&self, at: u64) -> u32 { unsafe { read_volatile((self.base + at) as *const u32) } } unsafe fn set(&self, at: u64, v: u32) { unsafe { write_volatile((self.base + at) as *mut u32, v) } } /// Submit one request (0 IN, 1 OUT, 4 FLUSH) and wait for its used entry. fn request(&mut self, operation: u32, lba: u64, buffer: &mut Block) -> Result<(), Error> { if !matches!(operation, 0 | 1 | 4) || (operation == 0 && !matches!(lba, 111 | 112 | 113 | 114 | 120 | 128 | 136)) || (operation == 1 && !matches!(lba, 112 | 113 | 114 | 120 | 128 | 136)) || self.next >= 16 || (operation != 4 && lba >= self.capacity) || (operation != 0 && !self.writable) || (operation == 4 && !self.flush) { return Err(Error::Io); } self.next = self.next.checked_add(1).ok_or(Error::Io)?; let [desc, avail, used, _] = self.frames; let lease_error = |_| Error::Io; let header = self.lease.request_subregion(0, 16).map_err(lease_error)?; let status = self.lease.request_subregion(16, 1).map_err(lease_error)?; let data = self .lease .request_subregion(512, 512) .map_err(lease_error)?; let (chain, count) = match operation { 0 => { let chain = virtio_ring::read_descriptor_chain(header, data, status); self.lease .validate_read_chain(&chain) .map_err(lease_error)?; (chain, 3) } 1 => { // The lease proves all three subregions. Validate the read // geometry, then make only the data descriptor device-readable. let mut chain = virtio_ring::read_descriptor_chain(header, data, status); self.lease.validate_read_chain(&chain).map_err(lease_error)?; chain[1].flags_le = virtio_ring::DESC_F_NEXT.to_le(); (chain, 3) } // FLUSH: device-readable header, device-writable status only. 4 => ( [ virtio_ring::Descriptor { addr_le: header.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.to_le(), len_le: 1u32.to_le(), flags_le: virtio_ring::DESC_F_WRITE.to_le(), next_le: 0, }, virtio_ring::Descriptor::default(), ], 2, ), _ => return Err(Error::Io), }; unsafe { for (index, d) in chain.into_iter().take(count).enumerate() { write_volatile((desc as *mut virtio_ring::Descriptor).add(index), d); } let h = match operation { 0 => virtio_ring::block_read_header(lba), 1 => virtio_ring::BlockReadHeader {type_le: 1u32.to_le(), reserved_le: 0, sector_le: lba.to_le()}, _ => virtio_ring::BlockReadHeader {type_le: 4u32.to_le(), reserved_le: 0, sector_le: 0}, }; write_volatile(header as *mut virtio_ring::BlockReadHeader, h); write_volatile(status as *mut u8, 0xff); if operation == 1 { core::ptr::copy_nonoverlapping(buffer.as_ptr(), data as *mut u8, 512); } let slot = u64::from((self.next - 1) % QUEUE); write_volatile((avail + 4 + slot * 2) as *mut u16, 0); asm!("dmb oshst", options(nostack, preserves_flags)); write_volatile((avail + 2) as *mut u16, self.next.to_le()); asm!("dmb oshst", options(nostack, preserves_flags)); self.set(0x50, 0); } let deadline = virtio_ring::deadline_after(counter(), self.frequency * 2).map_err(lease_error)?; let observed = loop { let n = unsafe { u16::from_le(read_volatile((used + 2) as *const u16)) }; if n != self.next - 1 || virtio_ring::deadline_expired(counter(), deadline) { break n; } core::hint::spin_loop(); }; unsafe { asm!("dmb oshld", options(nostack, preserves_flags)) }; // Everything printed below is read back from shared DMA memory. let entry = used + 4 + u64::from((self.next - 1) % QUEUE) * 8; let (id, len, s, kind, sector) = unsafe { let h = read_volatile(header as *const virtio_ring::BlockReadHeader); ( u32::from_le(read_volatile(entry as *const u32)), u32::from_le(read_volatile((entry + 4) as *const u32)), read_volatile(status as *const u8), u32::from_le(h.type_le), u64::from_le(h.sector_le), ) }; write_str("TURKOS/OS/K1680-IO: "); hex_field("SEQ", self.next as usize); write_str(match kind { 0 => " OP=IN", 1 => " OP=OUT", 4 => " OP=FLUSH", _ => " OP=UNKNOWN", }); hex_field(" SECTOR", sector as usize); hex_field(" USED", observed as usize); if observed != self.next { // The request may still be device-owned; the lease stays active. write_line(if observed == self.next - 1 { " TIMEOUT" } else { " UNEXPECTED" }); return Err(Error::Io); } hex_field(" ID", id as usize); hex_field(" LEN", len as usize); hex_field(" STATUS", s as usize); write_str("\r\n"); self.counts[match operation { 0 => 0, 1 => 1, _ => 2, }] += 1; let expected_len = if operation == 0 { 513 } else { 1 }; if id != 0 || len != expected_len || kind != operation || (operation != 4 && sector != lba) { return Err(Error::Io); } virtio_ring::read_block_status(s).map_err(lease_error)?; if operation == 0 { unsafe { core::ptr::copy_nonoverlapping(data as *const u8, buffer.as_mut_ptr(), 512) }; } Ok(()) } pub(super) fn read(&mut self, lba: u64, bytes: &mut Block) -> Result<(), Error> { self.request(0, lba, bytes) } pub(super) fn close(&mut self, allocator: &mut memory::FrameAllocator) -> Result<(), Error> { let rejected = self.lease.release(allocator) == Err(virtio_ring::Error::Active); let (status, ready) = unsafe { self.set(0x70, 0); (self.get(0x70), self.get(0x44)) }; if !rejected || status != 0 || ready != 0 { return Err(Error::Io); } self.lease.mark_reset().map_err(|_| Error::Io)?; self.lease.release(allocator).map_err(|_| Error::Io)?; if allocator.remaining_pages() != self.free_open || self.free_leased + 4 != self.free_open { return Err(Error::Io); } write_line("TURKOS/OS/K1680-DMA: ACTIVE-RELEASE=REJECTED RESET=VERIFIED FRAMES=RESTORED"); Ok(()) } } impl os_turkfs_journal_checkpoint::Device for Disk { fn write(&mut self, lba: u64, bytes: &Block) -> Result<(), ()> { let mut buffer = *bytes; self.request(1, lba, &mut buffer).map_err(|_| ()) } fn flush(&mut self) -> Result<(), ()> { self.request(4, 0, &mut [0; 512]).map_err(|_| ()) } } fn counter() -> u64 { let value: u64; unsafe { asm!("mrs {value}, CNTPCT_EL0", value = out(reg) value, options(nomem, nostack, preserves_flags)) }; value }