//! Memory a process mapped that something else may touch too: a device's //! registers, DMA memory a device reads and writes, pages shared with //! another process. Only libkos makes a `Window`, over exactly what the //! kernel said it mapped, so the rest of userspace needs no `unsafe`. //! Every access is volatile and bounds-checked; out of range panics. use core::sync::atomic::AtomicUsize; use types::address::{ByteOffset, VirtualAddress}; use types::bounded::{CapSlot, PageCount}; use types::mapping::PagePerms; const PAGE_SIZE: usize = 4096; /// The sizes a window reads and writes in one access. pub trait Scalar: Copy + private::Sealed {} impl Scalar for u8 {} impl Scalar for u16 {} impl Scalar for u32 {} impl Scalar for u64 {} mod private { pub trait Sealed {} impl Sealed for u8 {} impl Sealed for u16 {} impl Sealed for u32 {} impl Sealed for u64 {} } /// Maps `region` at `va` and returns a window over all of it. pub fn map(region: CapSlot, va: VirtualAddress, perms: PagePerms) -> crate::syscall::Result { let pages = crate::syscall::map_region(region, va, perms)?; Ok(Window::mapped(va, pages.value() as usize * PAGE_SIZE)) } /// Maps `region` read-write at `va` as plain memory, for a buffer only /// this process writes while it uses it (or one it takes turns with a /// server on, one call at a time). pub fn map_buffer(region: CapSlot, va: VirtualAddress) -> crate::syscall::Result<&'static mut [u8]> { let window = map(region, va, PagePerms::ReadWrite)?; // SAFETY: mapped read-write for as long as the capability is held, // and handed out once, at a fresh address. Ok(unsafe { core::slice::from_raw_parts_mut(va.raw() as *mut u8, window.len()) }) } /// `pages` fresh private pages, as a window: for filling pages that then /// move to another process. pub fn request(pages: PageCount) -> Option { let va = crate::syscall::request_pages(pages)?; Some(Window::mapped(va, pages.value() as usize * PAGE_SIZE)) } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Window { base: VirtualAddress, len: usize, } impl Window { /// `len` bytes at `base`, which the caller just had the kernel map /// for this process (and the kernel said how much). pub(crate) const fn mapped(base: VirtualAddress, len: usize) -> Self { Self { base, len } } pub fn len(&self) -> usize { self.len } pub fn is_empty(&self) -> bool { self.len == 0 } /// Where it is mapped in this process. pub fn address(&self) -> VirtualAddress { self.base } /// The address of `size` bytes at `offset`, aligned for them. fn at(&self, offset: ByteOffset, size: usize, align: usize) -> usize { let offset = offset.raw(); let end = offset.checked_add(size); assert!(end.is_some_and(|e| e <= self.len), "window access out of range"); let addr = self.base.raw() + offset; assert!(addr.is_multiple_of(align), "misaligned window access"); addr } pub fn read(&self, offset: ByteOffset) -> T { let addr = self.at(offset, size_of::(), align_of::()); // SAFETY: in range of memory mapped for this process, aligned for // T; T is a plain integer, valid for any bits. unsafe { core::ptr::read_volatile(addr as *const T) } } pub fn write(&self, offset: ByteOffset, value: T) { let addr = self.at(offset, size_of::(), align_of::()); // SAFETY: as in `read`; the window hands out no references to it. unsafe { core::ptr::write_volatile(addr as *mut T, value) } } /// The `len` bytes at `offset`, as a window of their own. pub fn sub(&self, offset: ByteOffset, len: usize) -> Window { let base = VirtualAddress::from_raw(self.at(offset, len, 1)); Window { base, len } } pub fn copy_from(&self, offset: ByteOffset, src: &[u8]) { self.at(offset, src.len(), 1); for (i, b) in src.iter().enumerate() { self.write(offset + i, *b); } } pub fn copy_to(&self, offset: ByteOffset, dst: &mut [u8]) { self.at(offset, dst.len(), 1); for (i, b) in dst.iter_mut().enumerate() { *b = self.read(offset + i); } } /// `count` u32s from `offset` set to `value`: one check, then a run of /// stores (drawing fills thousands at a time). pub fn fill_u32(&self, offset: ByteOffset, count: usize, value: u32) { let bytes = count.checked_mul(4).expect("window fill too large"); let addr = self.at(offset, bytes, 4) as *mut u32; for i in 0..count { // SAFETY: the whole run was checked in range and aligned. unsafe { core::ptr::write_volatile(addr.add(i), value) } } } /// `values` stored from `offset` on, in one checked run. pub fn write_u32s(&self, offset: ByteOffset, values: &[u32]) { let addr = self.at(offset, values.len() * 4, 4) as *mut u32; for (i, v) in values.iter().enumerate() { // SAFETY: the whole run was checked in range and aligned. unsafe { core::ptr::write_volatile(addr.add(i), *v) } } } /// Moves `len` bytes from `from` to `to` inside the window; the two may /// overlap. pub fn copy_within(&self, from: ByteOffset, to: ByteOffset, len: usize) { let src = self.at(from, len, 1) as *const u8; let dst = self.at(to, len, 1) as *mut u8; // SAFETY: both ranges are inside the mapped window; `copy` allows // overlap. Plain memory moves, not device registers. unsafe { core::ptr::copy(src, dst, len) } } /// `count` words from `offset` as atomics, for memory shared with /// threads or processes that only ever touch it atomically. pub fn atomics(&self, offset: ByteOffset, count: usize) -> &'static [AtomicUsize] { let bytes = count.checked_mul(size_of::()).expect("too many atomics"); let addr = self.at(offset, bytes, align_of::()); // SAFETY: mapped and aligned; AtomicUsize may be shared and is // valid for any bits. A mapping revoked later faults on access; // nothing else is mapped at a window's address while it lives. unsafe { core::slice::from_raw_parts(addr as *const AtomicUsize, count) } } }