162 lines
6.3 KiB
Rust
162 lines
6.3 KiB
Rust
//! 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<Window> {
|
|
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<Window> {
|
|
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<T: Scalar>(&self, offset: ByteOffset) -> T {
|
|
let addr = self.at(offset, size_of::<T>(), align_of::<T>());
|
|
// 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<T: Scalar>(&self, offset: ByteOffset, value: T) {
|
|
let addr = self.at(offset, size_of::<T>(), align_of::<T>());
|
|
// 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::<AtomicUsize>()).expect("too many atomics");
|
|
let addr = self.at(offset, bytes, align_of::<AtomicUsize>());
|
|
// 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) }
|
|
}
|
|
}
|