refactor: unsafe only in adapter + libkos; forbid(unsafe_code) everywhere else; libkos Window (volatile, bounds-checked MMIO/DMA/shared mem), entry!, thread::spawn; MapRegion returns pages; kernel const-built (with_cores/install_platform); newtype addrs
This commit is contained in:
@@ -2,7 +2,8 @@
|
||||
//! driver's endpoint; drivers map MMIO and allocate DMA themselves.
|
||||
|
||||
use crate::syscall::{self, Received, Result};
|
||||
use types::address::VirtualAddress;
|
||||
use crate::window::Window;
|
||||
use types::address::{PhysicalAddress, VirtualAddress};
|
||||
use types::bounded::{CapSlot, PageCount};
|
||||
use types::mapping::PagePerms;
|
||||
|
||||
@@ -65,18 +66,25 @@ pub fn give_device(endpoint: CapSlot, device: Device) -> Result<()> {
|
||||
syscall::call(endpoint, [SETUP, DONE, 0, 0], None).map(|_| ())
|
||||
}
|
||||
|
||||
/// Maps a device's MMIO region read-write at `va`.
|
||||
pub fn map_mmio(region: CapSlot, va: usize) -> Result<()> {
|
||||
syscall::map_region(region, VirtualAddress::from_raw(va), PagePerms::ReadWrite)
|
||||
/// Maps a device's MMIO region read-write at `va`: its registers.
|
||||
pub fn map_mmio(region: CapSlot, va: VirtualAddress) -> Result<Window> {
|
||||
crate::window::map(region, va, PagePerms::ReadWrite)
|
||||
}
|
||||
|
||||
/// Memory a device reads and writes: where this process sees it, and the
|
||||
/// physical address the device is told.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Dma {
|
||||
pub window: Window,
|
||||
pub physical: PhysicalAddress,
|
||||
}
|
||||
|
||||
/// Fresh zeroed, physically contiguous memory for DMA, mapped read-write
|
||||
/// at `va`. Returns its physical address, for programming the device.
|
||||
pub fn dma(pages: u16, va: usize) -> Result<usize> {
|
||||
let pages = PageCount::new(pages).ok_or(syscall::Error::InvalidArgument)?;
|
||||
/// at `va`.
|
||||
pub fn dma(pages: PageCount, va: VirtualAddress) -> Result<Dma> {
|
||||
let region = syscall::create_region(pages)?;
|
||||
syscall::map_region(region, VirtualAddress::from_raw(va), PagePerms::ReadWrite)?;
|
||||
syscall::region_address(region)
|
||||
let window = crate::window::map(region, va, PagePerms::ReadWrite)?;
|
||||
Ok(Dma { window, physical: syscall::region_address(region)? })
|
||||
}
|
||||
|
||||
/// Routes `irq` to a fresh notification bound to the calling thread, so
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
use crate::syscall;
|
||||
use types::address::VirtualAddress;
|
||||
use types::bounded::CapSlot;
|
||||
use types::mapping::PagePerms;
|
||||
use types::storage::{DIR_ENTRY_SIZE, DIR_FLAG_DIRECTORY, DIR_NAME_SIZE, MAX_PATH, op, reply};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -55,7 +54,7 @@ pub struct Storage {
|
||||
impl Storage {
|
||||
/// Attaches to the server behind `ep`, mapping the buffer it shares
|
||||
/// at `va` (which must be free for `BUFFER_SIZE` bytes).
|
||||
pub fn attach(ep: CapSlot, va: usize) -> Result<Self> {
|
||||
pub fn attach(ep: CapSlot, va: VirtualAddress) -> Result<Self> {
|
||||
let reply = syscall::call(ep, [op::ATTACH, 0, 0, 0], None).map_err(|_| FsError::Failed)?;
|
||||
let [tag, size, _, _] = reply.words();
|
||||
let cap = match reply {
|
||||
@@ -65,11 +64,8 @@ impl Storage {
|
||||
let (true, Some(region)) = (tag == reply::ATTACHED, cap) else {
|
||||
return Err(FsError::Failed);
|
||||
};
|
||||
syscall::map_region(region, VirtualAddress::from_raw(va), PagePerms::ReadWrite)
|
||||
.map_err(|_| FsError::Failed)?;
|
||||
// SAFETY: the region the server made, `size` bytes, is now mapped
|
||||
// read-write at `va` and stays so while this process holds it.
|
||||
let buf = unsafe { core::slice::from_raw_parts_mut(va as *mut u8, size) };
|
||||
let mapped = crate::window::map_buffer(region, va).map_err(|_| FsError::Failed)?;
|
||||
let buf = mapped.get_mut(..size).ok_or(FsError::Failed)?;
|
||||
Ok(Self { ep, buf })
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,30 @@ pub mod spawn;
|
||||
pub mod start;
|
||||
pub mod terminal;
|
||||
pub mod syscall;
|
||||
pub mod thread;
|
||||
pub mod window;
|
||||
|
||||
pub use window::Window;
|
||||
|
||||
/// Names a program's entry point, `fn()`: what libkos's `_start` calls.
|
||||
/// Programs then need no `unsafe` (exporting a symbol is).
|
||||
#[macro_export]
|
||||
macro_rules! entry {
|
||||
($main:path) => {
|
||||
#[unsafe(export_name = "main")]
|
||||
extern "C" fn __libkos_main() {
|
||||
$main()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// The BootInfo page boot maps into init, copied out; check its magic.
|
||||
pub fn boot_info() -> types::bootinfo::BootInfo {
|
||||
// SAFETY: boot maps the page read-only at BOOT_INFO_VA before init
|
||||
// runs, and BootInfo is plain integers, valid for any bits. In any
|
||||
// other process the page is unmapped and the read faults.
|
||||
unsafe { core::ptr::read_volatile(types::bootinfo::BOOT_INFO_VA as *const types::bootinfo::BootInfo) }
|
||||
}
|
||||
|
||||
#[cfg(target_os = "none")]
|
||||
#[global_allocator]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Pages and shared regions.
|
||||
|
||||
use super::{raw, slot_of, Result};
|
||||
use types::address::VirtualAddress;
|
||||
use types::address::{PhysicalAddress, VirtualAddress};
|
||||
use types::bounded::{CapSlot, PageCount};
|
||||
use types::mapping::PagePerms;
|
||||
use types::syscall_abi::*;
|
||||
@@ -43,9 +43,10 @@ pub fn create_region(pages: PageCount) -> Result<CapSlot> {
|
||||
))
|
||||
}
|
||||
|
||||
/// Maps the region behind `region` at `va`. The mapping lasts as long as
|
||||
/// the capability: dropping or having it revoked unmaps it.
|
||||
pub fn map_region(region: CapSlot, va: VirtualAddress, perms: PagePerms) -> Result<()> {
|
||||
/// Maps the region behind `region` at `va`, returning how many pages that
|
||||
/// is. The mapping lasts as long as the capability: dropping or having it
|
||||
/// revoked unmaps it.
|
||||
pub fn map_region(region: CapSlot, va: VirtualAddress, perms: PagePerms) -> Result<PageCount> {
|
||||
let perms = match perms {
|
||||
PagePerms::ReadOnly => PERMS_READ_ONLY,
|
||||
PagePerms::ReadWrite => PERMS_READ_WRITE,
|
||||
@@ -55,11 +56,14 @@ pub fn map_region(region: CapSlot, va: VirtualAddress, perms: PagePerms) -> Resu
|
||||
SYS_MAP_REGION,
|
||||
[region.value() as usize, va.raw(), perms, 0, 0, 0],
|
||||
)
|
||||
.map(|_| ())
|
||||
.and_then(|r| {
|
||||
let pages = u16::try_from(r[1]).ok().and_then(PageCount::new);
|
||||
pages.ok_or(super::Error::InvalidArgument)
|
||||
})
|
||||
}
|
||||
|
||||
/// The physical base of the region behind `region`, for programming a
|
||||
/// device's DMA. Pool regions are physically contiguous.
|
||||
pub fn region_address(region: CapSlot) -> Result<usize> {
|
||||
raw(SYS_REGION_ADDRESS, [region.value() as usize, 0, 0, 0, 0, 0]).map(|r| r[1])
|
||||
pub fn region_address(region: CapSlot) -> Result<PhysicalAddress> {
|
||||
raw(SYS_REGION_ADDRESS, [region.value() as usize, 0, 0, 0, 0, 0]).map(|r| PhysicalAddress::from_raw(r[1]))
|
||||
}
|
||||
|
||||
39
crates/libkos/src/thread.rs
Normal file
39
crates/libkos/src/thread.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
//! Threads in this process.
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use types::address::{EntryPoint, StackPointer};
|
||||
use types::bounded::PageCount;
|
||||
|
||||
use crate::syscall;
|
||||
|
||||
const STACK_PAGES: u16 = 1;
|
||||
|
||||
/// What a new thread runs, handed over through its start argument.
|
||||
struct Start<T: ?Sized + 'static> {
|
||||
run: fn(&'static T) -> !,
|
||||
arg: &'static T,
|
||||
}
|
||||
|
||||
extern "C" fn trampoline<T: ?Sized + Sync + 'static>(start: usize) -> ! {
|
||||
// SAFETY: `spawn` passed a leaked Box<Start<T>> of exactly this T, and
|
||||
// only this thread ever takes it back.
|
||||
let start = unsafe { Box::from_raw(start as *mut Start<T>) };
|
||||
(start.run)(start.arg)
|
||||
}
|
||||
|
||||
/// Starts a thread on a fresh one-page stack running `run(arg)`; whether
|
||||
/// it started.
|
||||
pub fn spawn<T: ?Sized + Sync + 'static>(arg: &'static T, run: fn(&'static T) -> !) -> bool {
|
||||
let Some(stack) = PageCount::new(STACK_PAGES).and_then(syscall::request_pages) else {
|
||||
return false;
|
||||
};
|
||||
let top = StackPointer::from_raw(stack.raw() + STACK_PAGES as usize * 4096);
|
||||
let start = Box::into_raw(Box::new(Start { run, arg })) as usize;
|
||||
let entry = EntryPoint::from_raw(trampoline::<T> as *const () as usize);
|
||||
if syscall::spawn_thread(entry, top, start) == usize::MAX {
|
||||
// SAFETY: the thread never started, so the Box is still ours.
|
||||
drop(unsafe { Box::from_raw(start as *mut Start<T>) });
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
139
crates/libkos/src/window.rs
Normal file
139
crates/libkos/src/window.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
//! 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::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: usize, size: usize, align: usize) -> usize {
|
||||
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: usize) -> 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: usize, 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: usize, len: usize) -> Window {
|
||||
let base = VirtualAddress::from_raw(self.at(offset, len, 1));
|
||||
Window { base, len }
|
||||
}
|
||||
|
||||
pub fn copy_from(&self, offset: usize, 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: usize, 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`.
|
||||
pub fn fill_u32(&self, offset: usize, count: usize, value: u32) {
|
||||
let bytes = count.checked_mul(4).expect("window fill too large");
|
||||
self.at(offset, bytes, 4);
|
||||
for i in 0..count {
|
||||
self.write(offset + i * 4, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// `count` words from `offset` as atomics, for memory shared with
|
||||
/// threads or processes that only ever touch it atomically.
|
||||
pub fn atomics(&self, offset: usize, 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) }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user