From 615dac5f5cd36f883dd67b3fe38209289858075e Mon Sep 17 00:00:00 2001 From: Paul Pan Date: Mon, 22 Apr 2024 21:02:07 +0800 Subject: [PATCH] feat: add initial cnode and untyped support --- api/src/cap.rs | 25 +++++- api/src/{failure.rs => error.rs} | 27 ++++--- api/src/lib.rs | 3 +- kernel/src/objects/cap.rs | 3 +- kernel/src/objects/cnode.rs | 130 +++++++++++++++++++++++++++++++ kernel/src/objects/mod.rs | 16 ++-- kernel/src/objects/untyped.rs | 129 ++++++++++++++++++++++++++++++ 7 files changed, 309 insertions(+), 24 deletions(-) rename api/src/{failure.rs => error.rs} (51%) create mode 100644 kernel/src/objects/cnode.rs create mode 100644 kernel/src/objects/untyped.rs diff --git a/api/src/cap.rs b/api/src/cap.rs index 30e2053..7f100eb 100644 --- a/api/src/cap.rs +++ b/api/src/cap.rs @@ -1,4 +1,4 @@ -#[derive(Clone, Copy, Eq, PartialEq, FromPrimitive, ToPrimitive)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, FromPrimitive, ToPrimitive)] pub enum ObjectType { Null = 0, CNode = 1, @@ -18,3 +18,26 @@ impl Default for ObjectType { Self::Null } } + +impl ObjectType { + pub const fn size(&self, user_obj_bits: usize) -> usize { + 1 << self.bits(user_obj_bits) + } + + pub const fn bits(&self, user_obj_bits: usize) -> usize { + #![rustfmt::skip] + match self { + ObjectType::Null => 0, // TODO: fill it! + ObjectType::CNode => 0, // TODO: fill it! + ObjectType::TCB => 0, // TODO: fill it! + ObjectType::SchedCtx => 0, // TODO: fill it! + ObjectType::Endpoint => 0, // TODO: fill it! + ObjectType::Reply => 0, // TODO: fill it! + ObjectType::Notification => 0, // TODO: fill it! + ObjectType::Frame => 0, // TODO: fill it! + ObjectType::PageTable => 0, // TODO: fill it! + ObjectType::Interrupt => 0, // TODO: fill it! + ObjectType::Untyped => user_obj_bits, + } + } +} diff --git a/api/src/failure.rs b/api/src/error.rs similarity index 51% rename from api/src/failure.rs rename to api/src/error.rs index 8cbaa2a..c07c261 100644 --- a/api/src/failure.rs +++ b/api/src/error.rs @@ -1,12 +1,5 @@ -use crate::cap::ObjectType; - -pub enum Fault { - Ok, - CapFault(CapFault), -} - +#[derive(Clone, Copy, Debug)] pub enum CapFault { - // vallina InvalidRoot, MissingCapability { bits_left: usize, @@ -16,13 +9,19 @@ pub enum CapFault { bits_left: usize, }, GuardMismatch { - guard_found: usize, bits_left: usize, + guard_found: usize, guard_size: usize, }, - // additional - TypeMismatch { - expected: ObjectType, - found: ObjectType, - }, } + +#[derive(Clone, Copy, Debug, Eq, PartialEq, FromPrimitive, ToPrimitive)] +pub enum SysError { + Ok, + CapTypeMismatch, // InvalidCapability + SlotNotEmpty, // DeleteFirst + InvalidArgument, + RangeError, +} + +pub type SysResult = Result; diff --git a/api/src/lib.rs b/api/src/lib.rs index 68319d3..5f72744 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -1,7 +1,8 @@ #![no_std] +#![feature(custom_inner_attributes)] #[macro_use] extern crate num_derive; pub mod cap; -pub mod failure; +pub mod error; diff --git a/kernel/src/objects/cap.rs b/kernel/src/objects/cap.rs index 365c106..1b7db6c 100644 --- a/kernel/src/objects/cap.rs +++ b/kernel/src/objects/cap.rs @@ -2,8 +2,9 @@ use api::cap::ObjectType; use vspace::addr::PhysAddr; /// RawCap is the specific implementation of capability which stores in CNode -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Default)] pub struct RawCap { + // TODO: this could be an enum, figure out a way to do this /// args: in vanilla seL4 implementation, a cap use two 64-bit words to store all information, /// but we'll waste some space rather than using bitfield to simplify the implementation pub args: [usize; 2], diff --git a/kernel/src/objects/cnode.rs b/kernel/src/objects/cnode.rs new file mode 100644 index 0000000..b083f6b --- /dev/null +++ b/kernel/src/objects/cnode.rs @@ -0,0 +1,130 @@ +use core::cell::Cell; + +use super::{cap::RawCap, Cap, KernelObject}; +use crate::arch::layout::mmap_phys_to_virt; +use api::{cap::ObjectType, error::CapFault}; +use utils::MASK; +use vspace::addr::{AddressOps, PhysAddr}; + +/// CNodeObject is a array of Capabilities (`RawCap`) +/// The size of the array is stored in CNodeCap +pub type CNodeObject = [Cell]; + +impl KernelObject for CNodeObject { + const OBJ_TYPE: ObjectType = ObjectType::CNode; +} + +/* + * in vanilla seL4, CNodeCap layout: + * > args[0]: | cap_tag | guard_size | radix | cnode_ptr | + * > | [63:59] | [58:53] | [52:47] | [46:0] | + * > args[1]: guard + * + * in our implementation, CNodeCap layout: + * > args[0]: [guard_size, radix] + * > args[1]: guard + * > ptr: cnode_ptr + * > cap_type: cap_tag + */ + +pub type CNodeCap<'a> = Cap<'a, CNodeObject>; + +impl<'a> CNodeCap<'a> { + const GUARD_SIZE_BITS: usize = 6; + const GUARD_SIZE_MASK: usize = MASK!(Self::GUARD_SIZE_BITS); + const GUARD_SIZE_OFFSET: usize = Self::RADIX_BITS + Self::RADIX_OFFSET; + const RADIX_BITS: usize = 6; + const RADIX_MASK: usize = MASK!(Self::RADIX_BITS); + const RADIX_OFFSET: usize = 0; + + pub fn mint(radix: usize, guard_size: usize, guard: usize, ptr: PhysAddr) -> RawCap { + let arg0 = ((radix & Self::RADIX_MASK) << Self::RADIX_OFFSET) + | ((guard_size & Self::GUARD_SIZE_MASK) << Self::GUARD_SIZE_OFFSET); + let arg1 = guard; + + let cap = RawCap::new(arg0, arg1, ptr, ObjectType::CNode); + CNodeCap::try_from(&Cell::new(cap)) + .unwrap() + .as_object() + .iter() + .for_each(|cell| { + cell.set(RawCap::default()); + }); + + cap + } + + fn radix(&self) -> usize { + (self.cap.get().args[0] >> Self::RADIX_OFFSET) & Self::RADIX_MASK + } + + fn guard_size(&self) -> usize { + (self.cap.get().args[0] >> Self::GUARD_SIZE_OFFSET) & Self::GUARD_SIZE_MASK + } + + fn guard(&self) -> usize { + self.cap.get().args[1] + } + + /// CNodeObject length + fn length(&self) -> usize { + // Return the limited size of the CNode, Rust will help us to check the boundary :) + // > the kernel then uses the next most-significant `radix` bits of the + // > capability address as an `index` into the CNode to which the CNode capability refers + 1 << self.radix() + } + + fn as_object(&self) -> &CNodeObject { + unsafe { + let virt = mmap_phys_to_virt(self.cap.get().ptr); + core::slice::from_raw_parts(virt.as_const_ptr(), self.length()) + } + } + + fn resolve_address_bits(&self, cap_ptr: usize, n_bits: usize) -> Result<&Cell, CapFault> { + let mut bits_remaining = n_bits; + let mut slot = self.cap; + + while let Ok(cnode) = CNodeCap::try_from(slot) { + let radix_bits = cnode.radix(); + let guard_bits = cnode.guard_size(); + let level_bits = radix_bits + guard_bits; + let cap_guard = cnode.guard(); + + assert_ne!(level_bits, 0); + + if level_bits > bits_remaining { + return Err(CapFault::DepthMismatch { + bits_found: level_bits, + bits_left: bits_remaining, + }); + } + + let guard = || { + return (cap_ptr >> (bits_remaining - guard_bits)) & MASK!(guard_bits); + }; + + if guard_bits > bits_remaining || guard() != cap_guard { + return Err(CapFault::GuardMismatch { + bits_left: bits_remaining, + guard_found: cap_guard, + guard_size: guard_bits, + }); + } + + let offset = (cap_ptr >> (bits_remaining - level_bits)) & MASK!(radix_bits); + slot = unsafe { &*(&cnode.as_object()[offset] as *const _) }; + + if bits_remaining == level_bits { + return Ok(slot); + } + + bits_remaining -= level_bits; + } + + if CNodeCap::try_from(slot).is_err() { + return Err(CapFault::InvalidRoot); + } + Ok(slot) + } +} diff --git a/kernel/src/objects/mod.rs b/kernel/src/objects/mod.rs index 1b85765..f66ba6c 100644 --- a/kernel/src/objects/mod.rs +++ b/kernel/src/objects/mod.rs @@ -14,12 +14,17 @@ */ -use api::{cap::ObjectType, failure::CapFault}; +use api::{ + cap::ObjectType, + error::{SysError, SysResult}, +}; use cap::RawCap; use core::{cell::Cell, marker::PhantomData}; pub mod cap; +pub mod cnode; pub mod null; +pub mod untyped; /// Cap is the high-level wrapper of RawCap, it's a typed reference to RawCap (which is untyped in Rust) /// For the typed objects, we should bound it with an empty traits `KernelObject` @@ -30,14 +35,11 @@ pub struct Cap<'a, T: KernelObject + ?Sized> { } impl<'a, T: KernelObject + ?Sized> TryFrom<&'a Cell> for Cap<'a, T> { - type Error = CapFault; + type Error = SysError; - fn try_from(new: &'a Cell) -> Result { + fn try_from(new: &'a Cell) -> SysResult { if new.get().cap_type != T::OBJ_TYPE { - Err(CapFault::TypeMismatch { - expected: T::OBJ_TYPE, - found: new.get().cap_type, - }) + Err(SysError::CapTypeMismatch) } else { Ok(Self { cap: new, diff --git a/kernel/src/objects/untyped.rs b/kernel/src/objects/untyped.rs new file mode 100644 index 0000000..1286c31 --- /dev/null +++ b/kernel/src/objects/untyped.rs @@ -0,0 +1,129 @@ +use super::cap::RawCap; +use super::cnode::{CNodeCap, CNodeObject}; +use super::null::NullCap; +use super::{Cap, KernelObject}; +use api::cap::ObjectType; +use api::error::{SysError, SysResult}; +use utils::then::Then; +use utils::MASK; +use vspace::addr::{align_up, PhysAddr}; + +/// UntypedObject is used as raw memory (associated with PhysAddr) +/// It can be further retyped to other objects (TCB, CNode ...) +pub struct UntypedObject {} + +impl KernelObject for UntypedObject { + const OBJ_TYPE: ObjectType = ObjectType::Untyped; +} + +/* + * in vanilla seL4, UntypedCap layout: + * > args[0]: | cap_tag | unused | cap_ptr | + * > | [63:59] | unused | [38:0] | + * > args[1]: | free_index | unused | is_device | block_bits | + * > | [63:25] | unused | [6:6] | [5:0] | + * + * in our implementation, UntypedCap layout: + * > NOTE: we are not storing bits, but bytes. free_offset is in bytes. + * > args[0]: free_offset + * > args[1]: [is_device, block_bits] + * > ptr: cap_ptr + * > cap_type: cap_tag + */ + +pub type UntypedCap<'a> = Cap<'a, UntypedObject>; +impl UntypedCap<'_> { + pub fn mint(free_offset: usize, block_bits: usize, is_device: bool, ptr: PhysAddr) -> RawCap { + RawCap::new( + free_offset, + ((is_device as usize) << 6) | (block_bits & MASK!(6)), + ptr, + ObjectType::Untyped, + ) + } + + fn free_offset(&self) -> usize { + self.cap.get().args[0] + } + + fn set_free_offset(&self, free_offset: usize) { + self.cap.get().args[0] = free_offset; + } + + fn is_device(&self) -> bool { + (self.cap.get().args[1] >> 6) & 1 == 1 + } + + fn block_bits(&self) -> usize { + self.cap.get().args[1] & MASK!(6) + } + + fn block_size(&self) -> usize { + 1 << self.block_bits() + } + + pub fn retype(&self, obj_type: ObjectType, user_obj_bits: usize, slots: &CNodeObject) -> SysResult<()> { + /* + * vallina seL4: `decode_untiped_invocation` + * - _service CPTR to an untyped object. + * - type The seL4 object type that we are retyping to. + * - size_bits Used to determine the size of variable-sized objects. + * - root CPTR to the CNode at the root of the destination CSpace. + * - node_index CPTR to the destination CNode. Resolved relative to the root parameter. + * - node_depth Number of bits of node_index to translate when addressing the destination CNode. + * - node_offset Number of slots into the node at which capabilities start being placed. + * - num_objects Number of capabilities to create. + * + * for our `retype` + * - _service is just self + * - type is passed by obj_type + * - size_bits passed by user_obj_bits + * - root + node_index + node_depth are used by `resolve_address_bits` + * - cnode[node_offset +: num_objects] being cut-off from previous, passing as slots + */ + + // Make sure all slots are empty + slots + .iter() + .any(|cap: &core::cell::Cell| NullCap::try_from(cap).is_err()) + .then_ok((), SysError::CapTypeMismatch)?; + + // Start allocating from free_offset + // Notice: in vallina seL4, it will check whether there are child nodes, + // if not, will ignore free_index and start from 0. + // We won't do this check, so we need to make sure free_offset is valid anytime. + let obj_size = obj_type.size(user_obj_bits); + let start_offset = align_up(self.free_offset(), obj_size); + + // Check whether the size is valid + let objs_size = obj_size * slots.len(); + if start_offset + objs_size > self.block_size() { + return Err(SysError::RangeError); + } + + // Check whether we are handling device memory + if self.is_device() && obj_type != ObjectType::Frame && obj_type != ObjectType::Untyped { + // TODO: we might split frame into tera, giga, mega, kilo... + return Err(SysError::InvalidArgument); + } + + // Create new capabilities in slot + for (i, slot) in slots.iter().enumerate() { + let addr = self.cap.get().ptr + start_offset + i * obj_size; + let new_cap = match obj_type { + ObjectType::Untyped => UntypedCap::mint(0, obj_type.bits(user_obj_bits), self.is_device(), addr), + ObjectType::CNode => CNodeCap::mint(user_obj_bits, 0, 0, addr), + // TODO: other object types + _ => return Err(SysError::InvalidArgument), + }; + + slot.set(new_cap); + // TODO: insert into linked list + } + + // Update free_offset + self.set_free_offset(start_offset + objs_size); + + Ok(()) + } +}