feat: add initial cnode and untyped support

This commit is contained in:
Paul Pan 2024-04-22 21:02:07 +08:00
parent 59514440e0
commit 615dac5f5c
7 changed files with 309 additions and 24 deletions

View File

@ -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,
}
}
}

View File

@ -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<T> = Result<T, SysError>;

View File

@ -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;

View File

@ -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],

130
kernel/src/objects/cnode.rs Normal file
View File

@ -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<RawCap>];
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<RawCap>, 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)
}
}

View File

@ -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<RawCap>> for Cap<'a, T> {
type Error = CapFault;
type Error = SysError;
fn try_from(new: &'a Cell<RawCap>) -> Result<Self, Self::Error> {
fn try_from(new: &'a Cell<RawCap>) -> SysResult<Self> {
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,

View File

@ -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<RawCap>| 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(())
}
}