[wip] experiment with caps representation

This commit is contained in:
Berkus Decker 2020-11-21 00:47:53 +02:00
parent 8a5ef112be
commit 333dece260
7 changed files with 1162 additions and 5 deletions

61
Cargo.lock generated
View File

@ -33,13 +33,57 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "mashup"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e81a2ed9c9879f92f2443fec9e6326c673b0dba3190c902b0371fd1387c4289"
dependencies = [
"mashup-impl",
"proc-macro-hack",
]
[[package]]
name = "mashup-impl"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "875a96a1b7ed5bdb76b8676b98a15d8888ee15e68833bb7a42897b59a9a93047"
dependencies = [
"proc-macro-hack",
"proc-macro2 0.4.30",
]
[[package]]
name = "proc-macro-hack"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "463bf29e7f11344e58c9e01f171470ab15c925c6822ad75028cc1c0e1d1eb63b"
dependencies = [
"proc-macro-hack-impl",
]
[[package]]
name = "proc-macro-hack-impl"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38c47dcb1594802de8c02f3b899e2018c78291168a22c281be21ea0fb4796842"
[[package]]
name = "proc-macro2"
version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
dependencies = [
"unicode-xid 0.1.0",
]
[[package]]
name = "proc-macro2"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
dependencies = [
"unicode-xid",
"unicode-xid 0.2.1",
]
[[package]]
@ -54,7 +98,7 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
dependencies = [
"proc-macro2",
"proc-macro2 1.0.24",
]
[[package]]
@ -88,7 +132,7 @@ version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b"
dependencies = [
"proc-macro2",
"proc-macro2 1.0.24",
"quote",
"syn",
]
@ -99,9 +143,9 @@ version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac"
dependencies = [
"proc-macro2",
"proc-macro2 1.0.24",
"quote",
"unicode-xid",
"unicode-xid 0.2.1",
]
[[package]]
@ -110,6 +154,12 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f521a79accce68c417c9c77ce22108056b626126da1932f7e2e9b5bbffee0cea"
[[package]]
name = "unicode-xid"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
[[package]]
name = "unicode-xid"
version = "0.2.1"
@ -136,6 +186,7 @@ dependencies = [
"bitflags",
"cfg-if",
"cortex-a",
"mashup",
"qemu-exit",
"r0",
"register",

View File

@ -34,3 +34,4 @@ bit_field = "0.10.0"
bitflags = "1.2.1"
cfg-if = "1.0"
snafu = { version = "0.6", default-features = false }
mashup = "0.1.9" # @todo replace with paste 1.0

View File

@ -13,6 +13,8 @@ use {
cortex_a::{asm, regs::*},
};
//use crate::arch::caps::{CapNode, Capability};
// Stack placed before first executable instruction
const STACK_START: u64 = 0x0008_0000; // Keep in sync with linker script
@ -218,3 +220,176 @@ pub unsafe extern "C" fn _boot_cores() -> ! {
// if not core0 or not EL3/EL2/EL1, infinitely wait for events
endless_sleep()
}
/*
// caps and mem regions init
enum KernelInitError {}
fn map_kernel_window() {}
fn init_cpu() -> Result<(), KernelInitError> {
unimplemented!();
}
fn init_plat() -> Result<(), KernelInitError> {
unimplemented!();
}
fn arch_init_freemem() -> Result<(), KernelInitError> {
unimplemented!();
}
fn create_domain_cap() -> Result<(), KernelInitError> {
unimplemented!();
}
fn init_irqs() -> Result<(), KernelInitError> {
unimplemented!();
}
fn create_bootinfo_cap() -> Result<(), KernelInitError> {
unimplemented!();
}
fn create_asid_pool_for_initial_thread() -> Result<(), KernelInitError> {
unimplemented!();
}
fn create_idle_thread() -> Result<(), KernelInitError> {
unimplemented!();
}
fn clean_invalidate_l1_caches() -> Result<(), KernelInitError> {
unimplemented!();
}
fn create_initial_thread() -> Result<(), KernelInitError> {
unimplemented!();
}
fn init_core_state(_: Result<(), KernelInitError>) -> Result<(), KernelInitError> {
unimplemented!();
}
fn create_untypeds() -> Result<(), KernelInitError> {
unimplemented!();
}
fn finalise_bootinfo() -> Result<(), KernelInitError> {
unimplemented!();
}
fn invalidate_local_tlb() -> Result<(), KernelInitError> {
unimplemented!();
}
fn lock_kernel_node() -> Result<(), KernelInitError> {
unimplemented!();
}
fn schedule() {
unimplemented!();
}
fn activate_thread() {
unimplemented!();
}
#[link_section = ".text.boot"]
// #[used]
fn try_init_kernel() -> Result<(), KernelInitError> {
map_kernel_window();
init_cpu()?;
init_plat()?;
arch_init_freemem()?;
let root_capnode_cap = create_root_capnode();
create_domain_cap(root_capnode_cap);
init_irqs(root_capnode_cap);
//fill in boot info and
create_bootinfo_cap();
let it_asid_pool_cap = create_asid_pool_for_initial_thread(root_capnode_cap);
create_idle_thread();
/* Before creating the initial thread (which also switches to it)
* we clean the cache so that any page table information written
* as a result of calling create_frames_of_region will be correctly
* read by the hardware page table walker */
clean_invalidate_l1_caches();
let it = create_initial_thread(root_capnode_cap);
init_core_state(it);
create_untypeds(root_capnode_cap);
finalise_bootinfo();
clean_invalidate_l1_caches();
invalidate_local_tlb();
// grab kernel lock before returning
lock_kernel_node();
Ok(())
}
fn try_init_kernel_secondary_core() -> Result<(), KernelInitError>
{
init_cpu();
/* Enable per-CPU timer interrupts */
maskInterrupt(false, KERNEL_TIMER_IRQ);
lock_kernel_node;
ksNumCPUs++; // increase global cpu counter - this should be done differently?
init_core_state(SchedulerAction_ResumeCurrentThread);
Ok(())
}
fn init_kernel() {
try_init_kernel()?;
// or for AP:
// try_init_kernel_secondary_core();
schedule();
activate_thread();
}
const CONFIG_ROOT_CAPNODE_SIZE_BITS: usize = 12;
const wordBits: usize = 64;
fn create_root_capnode() -> Capability // Attr(BOOT_CODE)
{
// write the number of root CNode slots to global state
boot_info.max_slot_pos = 1 << CONFIG_ROOT_CAPNODE_SIZE_BITS; // 12 bits => 4096 slots
// seL4_SlotBits = 32 bytes per entry, 4096 entries =>
// create an empty root CapNode
// this goes into the kernel startup/heap memory (one of the few items that kernel DOES allocate).
let region_size = core::mem::size_of::<Capability> * boot_info.max_slot_pos; // 12 + 5 => 131072 (128Kb)
let pptr = alloc_region(region_size); // GlobalAllocator::alloc_zeroed instead?
if pptr.is_none() {
println!("Kernel init failing: could not create root capnode");
return Capability(NullCap::Type::value);
}
let Some(pptr) = pptr;
memzero(pptr, region_size); // CTE_PTR(pptr) ?
// transmute into a type? (you can use ptr.write() to just write a type into memory location)
let cap = CapNode::new_root(pptr);
// this cnode contains a cap to itself...
/* write the root CNode cap into the root CNode */
// @todo rootCapNode.write_slot(CapInitThreadCNode, cap); -- where cap and rootCapNode are synonyms!
write_slot(SLOT_PTR(pptr, seL4_CapInitThreadCNode), cap);
cap // reference to pptr is here
}
*/

View File

@ -0,0 +1,528 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
*/
// DerivationTree nodes record the tree of inheritance for caps:
// See the picture on derivation from seL4 manual for how this works: each cap contains a ref to
// DerivationTree node, which records the previous cap and the following cap(s).
// ☐ Rust implementation of capabilities - ?
// ☐ Need to implement in kernel entries storage and lookup
// ☐ cte = cap table entry (a cap_t plus mdb_node_t)
// ☐ mdb = ? (mdb_node_new)
// ☐ sameObjectAs()
// cap_get_capType();//generated
// lookupCapAndSlot();
// cap_domain_cap_new() etc //generated
// create_mapped_it_frame_cap(); //vspace.c
// pptr_of_cap(); -- extracts cap.pptr from cnode_cap
// deriveCap();
use core::{convert::TryFrom, fmt};
use mashup::*;
use register::LocalRegisterCopy;
//==================
// Caps definitions
//==================
register_bitfields! [u128,
NullCap [
Type OFFSET(64) NUMBITS(5) [
value = 0
]
],
UntypedCap [
FreeIndex OFFSET(0) NUMBITS(48) [],
IsDevice OFFSET(57) NUMBITS(1) [],
BlockSize OFFSET(58) NUMBITS(6) [],
Type OFFSET(64) NUMBITS(5) [
value = 2
],
Ptr OFFSET(80) NUMBITS(48) []
],
EndpointCap [
Badge OFFSET(0) NUMBITS(64) [],
Type OFFSET(64) NUMBITS(5) [
value = 4
],
CanGrantReply OFFSET(69) NUMBITS(1) [],
CanGrant OFFSET(70) NUMBITS(1) [],
CanReceive OFFSET(71) NUMBITS(1) [],
CanSend OFFSET(72) NUMBITS(1) [],
Ptr OFFSET(80) NUMBITS(48) []
],
NotificationCap [ // @todo replace with Event
Badge OFFSET(0) NUMBITS(64) [],
Type OFFSET(64) NUMBITS(5) [
value = 6
],
CanReceive OFFSET(69) NUMBITS(1) [],
CanSend OFFSET(70) NUMBITS(1) [],
Ptr OFFSET(80) NUMBITS(48) []
],
ReplyCap [
TCBPtr OFFSET(0) NUMBITS(64) [],
Type OFFSET(64) NUMBITS(5) [
value = 8
],
ReplyCanGrant OFFSET(126) NUMBITS(1) [],
ReplyMaster OFFSET(127) NUMBITS(1) []
],
CapNodeCap [
Guard OFFSET(0) NUMBITS(64) [],
Type OFFSET(64) NUMBITS(5) [
value = 10
],
GuardSize OFFSET(69) NUMBITS(6) [],
Radix OFFSET(75) NUMBITS(6) [],
Ptr OFFSET(81) NUMBITS(47) []
],
ThreadCap [
Type OFFSET(64) NUMBITS(5) [
value = 12
],
TCBPtr OFFSET(80) NUMBITS(48) []
],
IrqControlCap [
Type OFFSET(64) NUMBITS(5) [
value = 14
]
],
IrqHandlerCap [
Irq OFFSET(52) NUMBITS(12) [],
Type OFFSET(64) NUMBITS(5) [
value = 16
]
],
ZombieCap [
ZombieID OFFSET(0) NUMBITS(64) [],
Type OFFSET(64) NUMBITS(5) [
value = 18
],
ZombieType OFFSET(121) NUMBITS(7) []
],
DomainCap [
Type OFFSET(64) NUMBITS(5) [
value = 20
]
],
// https://ts.data61.csiro.au/publications/csiro_full_text/Lyons_MAH_18.pdf
// Resume objects, modelled after KeyKOS [Bomberger et al.1992], are a new object type
// that generalise the “reply capabilities” of baseline seL4. These were capabilities
// to virtual objects created by the kernel on-the-fly in seL4s RPC-style call() operation,
// which sends a message to an endpoint and blocks on a reply. The receiver of the message
// (i.e. the server) receives the reply capability in a magic “reply slot” in its
// capability space. The server replies by invoking that capability. Resume objects
// remove the magic by explicitly representing the reply channel (and the SC-donation chain).
// They also provide more efficient support for stateful servers that handle concurrent client
// sessions.
// The introduction of Resume objects requires some changes to the IPC system-call API.
// The client-style call() operation is unchanged, but server-side equivalent, ReplyRecv
// (previously ReplyWait) replies to a previous request and then blocks on the next one.
// It now must provide an explicit Resume capability; on the send phase, that capability
// identifies the client and returns the SC if appropriate, on the receive phase it is
// populated with new values. The new API makes stateful server implementation more efficient.
// In baseline seL4, the server would have to use at least two extra system calls to save the
// reply cap and later move it back into its magic slot, removing the magic also removes
// the need for the extra system calls.
ResumeCap [
Type OFFSET(64) NUMBITS(5) [
value = 22
]
]
];
// mod aarch64 {
// ARM-specific caps
register_bitfields! [u128,
FrameCap [
MappedASID OFFSET(0) NUMBITS(16) [],
BasePtr OFFSET(16) NUMBITS(48) [], // PhysAddr
Type OFFSET(64) NUMBITS(5) [
value = 1
],
Size OFFSET(69) NUMBITS(2) [],
VMRights OFFSET(71) NUMBITS(2) [],
IsDevice OFFSET(73) NUMBITS(1) [],
MappedAddress OFFSET(80) NUMBITS(48) [] // VirtAddr
],
PageTableCap [
MappedASID OFFSET(0) NUMBITS(16) [],
BasePtr OFFSET(16) NUMBITS(48) [], // PhysAddr
Type OFFSET(64) NUMBITS(5) [
value = 3
],
IsMapped OFFSET(79) NUMBITS(1) [],
MappedAddress OFFSET(80) NUMBITS(28) [] // VirtAddr
],
PageDirectoryCap [
MappedASID OFFSET(0) NUMBITS(16) [],
BasePtr OFFSET(16) NUMBITS(48) [], // PhysAddr
Type OFFSET(64) NUMBITS(5) [
value = 5
],
IsMapped OFFSET(79) NUMBITS(1) [],
MappedAddress OFFSET(80) NUMBITS(19) [] // VirtAddr
],
PageUpperDirectoryCap [
MappedASID OFFSET(0) NUMBITS(16) [],
BasePtr OFFSET(16) NUMBITS(48) [], // PhysAddr
Type OFFSET(64) NUMBITS(5) [
value = 7
],
IsMapped OFFSET(79) NUMBITS(1) [],
MappedAddress OFFSET(80) NUMBITS(10) [] // VirtAddr
],
PageGlobalDirectoryCap [
MappedASID OFFSET(0) NUMBITS(16) [],
BasePtr OFFSET(16) NUMBITS(48) [], // PhysAddr
Type OFFSET(64) NUMBITS(5) [
value = 9
],
IsMapped OFFSET(79) NUMBITS(1) []
],
AsidControlCap [
Type OFFSET(64) NUMBITS(5) [
value = 11
]
],
AsidPoolCap [
Type OFFSET(64) NUMBITS(5) [
value = 13
],
ASIDBase OFFSET(69) NUMBITS(16) [],
ASIDPool OFFSET(91) NUMBITS(37) []
]
// For HYP mode:
// VCpuCap [
// Type OFFSET(64) NUMBITS(5) [], // 15
// VCPUPtr OFFSET(80) NUMBITS(48) [],
// ],
];
//================
// Kernel objects
//================
//-- Mapping database (MDB) node: size = 16 bytes
//block mdb_node {
//padding 16
//field_high mdbNext 46
//field mdbRevocable 1
//field mdbFirstBadged 1
//
//field mdbPrev 64
//}
register_bitfields! [u128,
CapDerivationNode [
// Next CTE node address -- per cteInsert this is address of the entire CTE slot
Next OFFSET(16) NUMBITS(46) [], // 4-bytes-aligned, size of canonical phys address is 48 bits
Revocable OFFSET(62) NUMBITS(1) [
Disable = 0,
Enable = 1
],
FirstBadged OFFSET(63) NUMBITS(1) [
Disable = 0,
Enable = 1
],
// Prev CTE node address -- per cteInsert this is address of the entire CTE slot
Prev OFFSET(64) NUMBITS(64) []
]
];
/// Opaque capability object, manipulated by the kernel.
pub trait Capability {
///
/// Is this capability arch-specific?
///
fn is_arch(&self) -> bool;
///
/// Retrieve this capability as scalar value.
///
fn as_u128(&self) -> u128;
}
macro_rules! capdefs {
($($name:ident),*) => {
mashup! {
$(
m[$name "Capability"] = $name Capability;
m[$name "Cap"] = $name Cap;
)*
}
m! {
$(
pub struct $name "Capability"(LocalRegisterCopy<u128, $name "Cap"::Register>);
impl Capability for $name "Capability" {
#[inline]
fn as_u128(&self) -> u128 {
self.0.into()
}
#[inline]
fn is_arch(&self) -> bool {
($name "Cap"::Type::Value::value as u8) % 2 != 0
}
}
impl TryFrom<u128> for $name "Capability" {
type Error = CapError;
fn try_from(v: u128) -> Result<$name "Capability", Self::Error> {
let reg = LocalRegisterCopy::<_, $name "Cap"::Register>::new(v);
if reg.read($name "Cap"::Type) == u128::from($name "Cap"::Type::value) {
Ok($name "Capability"(LocalRegisterCopy::new(v)))
} else {
Err(Self::Error::InvalidCapabilityType)
}
}
}
impl From<$name "Capability"> for u128 {
#[inline]
fn from(v: $name "Capability") -> u128 {
v.as_u128()
}
}
)*
}
}
}
capdefs! {
Null, Untyped, Endpoint,
Notification, Reply, CapNode,
Thread, IrqControl, IrqHandler,
Zombie, Domain, Resume,
Frame, PageTable, PageDirectory,
PageUpperDirectory, PageGlobalDirectory,
AsidControl, AsidPool
}
// * Capability slots: 16 bytes of memory per slot (exactly one capability). --?
// CapNode describes `a given number of capability slots` with `a given guard`
// of `a given guard size` bits.
// @todo const generic on number of capabilities contained in the node? currently only contains a Cap
// capnode_cap has a pptr, guard_size, guard and radix
// this is enough to address a cap in the capnode contents
// by having a root capnode cap we can traverse the whole tree.
impl CapNodeCapability {
// create new root node
pub fn new_root(pptr: u64) -> CapNodeCapability {
const CONFIG_ROOT_CAPNODE_SIZE_BITS: u32 = 12;
const WORD_BITS: u32 = 64;
CapNodeCapability::new(
pptr,
CONFIG_ROOT_CAPNODE_SIZE_BITS,
WORD_BITS - CONFIG_ROOT_CAPNODE_SIZE_BITS,
0,
)
}
// pub const fn from_capability(cap: dyn Capability) -> CapNodeCapability {
// let reg = LocalRegisterCopy::<_, CapNodeCap::Register>::new(cap.as_u128());
// //assert_eq!(
// // reg.read(CapNodeCap::Type),
// // u128::from(CapNodeCap::Type::value)
// //);
// CapNodeCapability(reg)
// }
// @internal
pub fn write_slot(&mut self, slot: usize, cap: &dyn Capability) {
let ptr = self.0.read(CapNodeCap::Ptr);
let size =
(1usize << self.0.read(CapNodeCap::Radix)) * core::mem::size_of::<CapTableEntry>();
let slice = unsafe { core::slice::from_raw_parts_mut(ptr as *mut CapTableEntry, size) };
slice[slot].capability = cap.as_u128();
slice[slot].derivation_node = DerivationTreeNode::empty()
.set_revocable(true)
.set_first_badged(true);
}
}
impl NullCapability {
pub fn new() -> NullCapability {
NullCapability(LocalRegisterCopy::new(u128::from(NullCap::Type::value)))
}
}
impl CapNodeCapability {
pub fn new(pptr: u64, radix: u32, guard_size: u32, guard: u64) -> CapNodeCapability {
CapNodeCapability(LocalRegisterCopy::new(u128::from(
CapNodeCap::Type::value
+ CapNodeCap::Radix.val(radix.into())
+ CapNodeCap::GuardSize.val(guard_size.into())
+ CapNodeCap::Guard.val(guard.into())
+ CapNodeCap::Ptr.val(pptr.into()),
)))
}
}
// Wrapper for CapDerivationNode
#[derive(Clone)]
pub struct DerivationTreeNode(LocalRegisterCopy<u128, CapDerivationNode::Register>);
pub enum DerivationTreeError {
InvalidPrev,
}
impl DerivationTreeNode {
fn empty() -> Self {
Self(LocalRegisterCopy::new(0))
}
// SAFETY: it is UB to get prev reference from a null Prev pointer.
pub unsafe fn get_prev(&self) -> CapTableEntry {
let ptr = self.0.read(CapDerivationNode::Prev) as *const CapTableEntry;
(*ptr).clone()
}
pub fn try_get_prev(&self) -> Result<CapTableEntry, DerivationTreeError> {
if self.0.read(CapDerivationNode::Prev) == 0 {
Err(DerivationTreeError::InvalidPrev)
} else {
Ok(unsafe { self.get_prev() })
}
}
fn set_first_badged(mut self, enable: bool) -> Self {
self.0.modify(if enable {
CapDerivationNode::FirstBadged::Enable
} else {
CapDerivationNode::FirstBadged::Disable
});
self
}
fn set_revocable(mut self, enable: bool) -> Self {
self.0.modify(if enable {
CapDerivationNode::Revocable::Enable
} else {
CapDerivationNode::Revocable::Disable
});
self
}
}
// -- cte_t from seL4
// structures.h:140
// /* Capability table entry (CTE) */
// struct cte {
// cap_t cap; // two words
// mdb_node_t cteMDBNode; // two words
// }; // -- four words: u256, 32 bytes.
// typedef struct cte cte_t;
#[derive(Clone)]
pub struct CapTableEntry {
capability: u128,
derivation_node: DerivationTreeNode,
}
impl fmt::Debug for CapTableEntry {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:x}", self.capability); // @todo
Ok(())
}
}
impl CapTableEntry {
// Temporary for testing:
fn empty() -> CapTableEntry {
CapTableEntry {
capability: 0,
derivation_node: DerivationTreeNode::empty(),
}
}
}
// The different libraries seem to target different use-cases, though. For example, `snafu` with its
// strongly typed errors and contexts seems to be a good fit for libraries. On the other hand,
// anyhow with its focus on the type-erased Error and on creating string errors and contexts seems
// to be more useful for applications. After all, errors produced by libraries need to be understood
// by other code, errors produced by executables need to be understood by humans.
// -- https://lukaskalbertodt.github.io/2019/11/14/thoughts-on-error-handling-in-rust.html
// @see also https://blog.yoshuawuyts.com/error-handling-survey/
// @see also https://www.reddit.com/r/rust/comments/dfs1zk/2019_q4_error_patterns_snafu_vs_errderive_anyhow/
#[derive(Debug)]
pub enum CapError {
CannotCreate,
InvalidCapabilityType,
}
// @note src and dest are swapped here, compared to seL4 api
/*
struct CapNodePath {
index: u32,
depth: u32,
}
struct CapNodeRootedPath {
root: CapNode,
path: CapNodePath,
}
// @todo just use CapNodeCap
//struct CapNodeConfig {
// guard: u32,
// guard_size: u32,
//}
impl CapNode {
fn mint(
src: CapNodeRootedPath,
dest: CapNodePath,
rights: CapRights,
badge: Badge,
) -> Result<(), CapError> {
unimplemented!();
}
fn copy(src: CapNodeRootedPath, dest: CapNodePath, rights: CapRights) -> Result<(), CapError> {
unimplemented!();
}
fn r#move(src: CapNodeRootedPath, dest: CapNodePath) -> Result<(), CapError> {
unimplemented!();
}
fn mutate(src: CapNodeRootedPath, dest: CapNodePath, badge: Badge) -> Result<(), CapError> {
unimplemented!();
}
fn rotate(
src: CapNodeRootedPath,
dest: CapNodePath,
dest_badge: Badge,
pivot: CapNodeRootedPath,
pivot_badge: Badge,
) -> Result<(), CapError> {
unimplemented!();
}
fn delete(path: CapNodePath) -> Result<(), CapError> {
unimplemented!();
}
fn revoke(path: CapNodePath) -> Result<(), CapError> {
unimplemented!();
}
fn save_caller(r#where: CapNodePath) -> Result<(), CapError> {
unimplemented!();
}
fn cancel_badged_sends(path: CapNodePath) -> Result<(), CapError> {
unimplemented!();
}
}*/
//struct CapSpace {} -- capspace is collection of capnodes in a single address space?
//impl CapNode for CapSpace {}
#[cfg(test)]
mod tests {
use super::*;
#[test_case]
fn first_capability_derivation_has_no_prev_link() {}
}

View File

@ -8,6 +8,8 @@
use cortex_a::asm;
mod boot;
pub mod caps;
pub use self::caps::*;
#[cfg(feature = "jtag")]
pub mod jtag;
pub mod memory;

View File

@ -0,0 +1,330 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
*/
// The basic services Vesper provides are as follows:
//
// * _Threads_ are an abstraction of CPU execution that supports running software;
// * _Address spaces_ are virtual memory spaces that each contain an application.
// Applications are limited to accessing memory in their address space;
// * _Inter-process communication (IPC)_ via endpoints allows threads to communicate using
// message passing;
// * _Events_ provide a non-blocking signalling mechanism similar to counting semaphores;
// * _Device primitives_ allow device drivers to be implemented as unprivileged applications.
// The kernel exports hardware device interrupts via IPC messages; and
// * _Capability spaces_ store capabilities (i.e., access rights) to kernel services along with
// their book-keeping information.
//================
// Kernel objects
//================
register_bitfields! {u128,
Endpoint [
QueueHead OFFSET(0) NUMBITS(64) [],
QueueTail OFFSET(80) NUMBITS(46) [],
State OFFSET(126) NUMBITS(2) [
Idle = 00b,
Send = 01b,
Recv = 10b,
],
],
}
// @todo replace with Event
register_bitfields! {u256,
Notification [
BoundTCB OFFSET(16) NUMBITS(48) [],
MsgId OFFSET(64) NUMBITS(64) [],
QueueHead OFFSET(144) NUMBITS(48) [],
QueueTail OFFSET(192) NUMBITS(48) [],
State OFFSET(254) NUMBITS(2) [
Idle = 00b,
Waiting = 01b,
Active = 10b,
],
]
}
// TCB (Thread)
// +--VirtSpace
// +--CapSpace
enum MemoryKind {
General,
Device,
}
// The source of all available memory, device or general.
// Boot code reserves kernel memory and initial mapping allocations (4 pages probably - on rpi3? should be platform-dependent).
// The rest is converted to untypeds with appropriate kind and given away to start thread.
trait Untyped {
// Uses T::SIZE_BITS to properly size the resulting object
// (`where T: KernelObject`)
// in some cases size_bits must be passed as argument though...
fn retype<T>(target_cap: CapNodeRootedPath, target_cap_offset: usize, num_objects: usize) -> Result<()>; // @todo return an array of caps?
}
// MMU
// ActivePageTable (--> impl VirtSpace for ActivePageTable etc...)
// * translate(VirtAddr)->PhysAddr
// * translate_page(Page)->PhysAddr
// * map_to(Page, PhysFrame, Flags, FrameAllocator)->()
// * map(Page, Flags, FrameAllocator)->()
// * identity_map(PhysFrame, Flags, FrameAllocator)->()
// * unmap(Page, FrameAllocator)->()
trait VirtSpace {
fn map(virt_space: VirtSpace/*Cap*/, vaddr: VirtAddr, rights: CapRights, attr: VMAttributes) -> Result<()>; /// ??
fn unmap() -> Result<()>; /// ??
fn remap(virt_space: VirtSpace/*Cap*/, rights: CapRights, attr: VMAttributes) -> Result<()>; /// ??
fn get_address() -> Result<PhysAddr>;///??
}
// ARM AArch64 processors have a four-level page-table structure, where the
// VirtSpace is realised as a PageGlobalDirectory. All paging structures are
// indexed by 9 bits of the virtual address.
// AArch64 page hierarchy:
//
// PageGlobalDirectory (L0) -- aka VirtSpace
// +--PageUpperDirectory (L1)
// +--Page<Size1GiB> -- aka HugePage
// | or
// +--PageDirectory (L2)
// +--Page<Size2MiB> -- aka LargePage
// | or
// +--PageTable (L3)
// +--Page<Size4KiB> -- aka Page
trait PageCacheManagement {
// Cache data management.
/// Cleans the data cache out to RAM.
/// The start and end are relative to the page being serviced.
fn clean_data(start_offset: usize, end_offset: usize) -> Result<()>;
/// Clean and invalidates the cache range within the given page.
/// The range will be flushed out to RAM. The start and end are relative
/// to the page being serviced.
fn clean_invalidate_data(start_offset: usize, end_offset: usize) -> Result<()>;
/// Invalidates the cache range within the given page.
/// The start and end are relative to the page being serviced and should
/// be aligned to a cache line boundary where possible. An additional
/// clean is performed on the outer cache lines if the start and end are
/// not aligned, to clean out the bytes between the requested and
/// the cache line boundary.
fn invalidate_data(start_offset: usize, end_offset: usize) -> Result<()>;
/// Cleans data lines to point of unification, invalidates
/// corresponding instruction lines to point of unification, then
/// invalidates branch predictors.
/// The start and end are relative to the page being serviced.
fn unify_instruction_cache(start_offset: usize, end_offset: usize) -> Result<()>;
}
// ARM
struct Page {}
impl Page {
// VirtSpace-like interface.
/// Get the physical address of the underlying frame.
fn get_address() -> Result<PhysAddr>;
fn map(virt_space: VirtSpace/*Cap*/, vaddr: VirtAddr, rights: CapRights, attr: VMAttributes) -> Result<()>;
/// Changes the permissions of an existing mapping.
fn remap(virt_space: VirtSpace/*Cap*/, rights: CapRights, attr: VMAttributes) -> Result<()>;
fn unmap() -> Result<()>;
// MMIO space.
fn map_io(iospace: IoSpace/*Cap*/, rights: CapRights, ioaddr: VirtAddr) -> Result<()>;
}
impl PageCacheManagement for Page {
fn clean_data(start_offset: usize, end_offset: usize) -> _ {
todo!()
}
fn clean_invalidate_data(start_offset: usize, end_offset: usize) -> _ {
todo!()
}
fn invalidate_data(start_offset: usize, end_offset: usize) -> _ {
todo!()
}
fn unify_instruction_cache(start_offset: usize, end_offset: usize) -> _ {
todo!()
}
}
// ARM
// L3 tables
struct PageTable {}
impl PageTable {
fn map(virt_space: VirtSpace/*Cap*/, vaddr: VirtAddr, attr: VMAttributes) -> Result<()>;
fn unmap() -> Result<()>;
}
// AArch64 - probably just impl some Mapping trait for these "structs"?
// L2 table
struct PageDirectory {}
impl PageDirectory {
fn map(pud: PageUpperDirectory/*Cap*/, vaddr: VirtAddr, attr: VMAttributes) -> Result<()>;
fn unmap() -> Result<()>;
}
// L1 table
struct PageUpperDirectory {}
impl PageUpperDirectory {
fn map(pgd: PageGlobalDirectory/*Cap*/, vaddr: VirtAddr, attr: VMAttributes) -> Result<()>;
fn unmap() -> Result<()>;
}
// L0 table
struct PageGlobalDirectory {
// @todo should also impl VirtSpace to be able to map shit?
// or the Page's impl will do this?
}
impl PageCacheManagement for PageGlobalDirectory {
fn clean_data(start_offset: usize, end_offset: usize) -> _ {
todo!()
}
fn clean_invalidate_data(start_offset: usize, end_offset: usize) -> _ {
todo!()
}
fn invalidate_data(start_offset: usize, end_offset: usize) -> _ { todo!() }
fn unify_instruction_cache(start_offset: usize, end_offset: usize) -> _ {
todo!()
}
}
// implemented for x86 and arm
trait ASIDPool {
fn assign(virt_space: VirtSpace/*Cap*/) -> Result<()>;
}
// implemented for x86 and arm
trait ASIDControl {
fn make_pool(untyped: Untyped, target_cap_space_cap: CapNodeRootedPath) -> Result<()>;
}
// Allocation details
// 1. should be possible to map non-SAS style
// 2. should be easy to map SAS style
// 3. should not allocate any memory dynamically
// ^ problem with the above API is FrameAllocator
// ^ clients should supply their own memory for frames...
// https://github.com/seL4/seL4_libs/tree/master/libsel4allocman
// Allocation overview
// Allocation is complex due to the circular dependencies that exist on allocating resources. These dependencies are loosely described as
// Capability slots: Allocated from untypeds, book kept in memory.
// Untypeds / other objects (including frame objects): Allocated from other untypeds, into capability slots, book kept in memory.
// memory: Requires frame object.
// Other seL4-like kernel objects and their interfaces:
trait Thread {
// Effectively, SetSpace followed by SetIPCBuffer.
fn configure(fault_endpoint: CapNode, cap_space_root: CapNode, cap_space_root_data: CapNodeConfig, virt_space_root: CapNode, virt_space_root_data: ??, ipc_buffer_frame: CapNode, ipc_buffer_offset: usize) -> Result<()>;
fn set_space(fault_endpoint: CapNode, cap_space_root: CapNode, cap_space_root_data: CapNodeConfig, virt_space_root: CapNode, virt_space_root_data: ??) -> Result<()>;
fn configure_single_stepping(bp_num: u16, num_insns): Result<SingleStepping>;
fn get_breakpoint(bp_num: u16) -> Result<BreakpointInfo>;
fn set_breakpoint(bp_num: u16, bp: BreakpointInfo) -> Result<()>;
fn unset_breakpoint(bp_num: u16) -> Result<()>;
fn suspend() -> Result<()>;
fn resume() -> Result<()>;
fn copy_registers(source: TCB/*Cap*/, suspend_source: bool, resume_target: bool, transfer_frame_regs: bool, transfer_integer_regs: bool, arch_flags: u8) -> Result<()>;
fn read_registers(suspend_source: bool, arch_flags: u8, num_regs: u16, register_context: &mut ArchRegisterContext) -> Result<()>;
fn write_registers(resume_target: bool, arch_flags: u8, num_regs: u16, register_context: &ArchRegisterContext) -> Result<()>;
fn bind_notification(notification: CapNode) -> Result<()>;
fn unbind_notification() -> Result<()>;
fn set_priority(authority: TCB/*Cap*/, priority: u32) -> Result<()>;
fn set_mc_priority(authority: TCB/*Cap*/, mcp: u32) -> Result<()>;
fn set_sched_params(authority: TCB/*Cap*/, mcp: u32, priority: u32) -> Result<()>;
fn set_affinity(affinity: u64) -> Result<()>;
fn set_ipc_buffer(ipc_buffer_frame: CapNode, ipc_buffer_offset: usize) -> Result<()>;
// Arch-specific
fn set_tls_base(tls_base: usize) -> Result<()>;
// virtualized - x86-specific
fn set_ept_root(eptpml: X86::EPTPML4) -> Result<()>;
}
// @todo <<SchedContext>>
struct TCB {}
impl Thread for TCB {}
impl KernelObject for TCB {
const SIZE_BITS: usize = 12;
}
trait Notification {
fn signal(dest: Cap);
fn wait(src: Cap) -> Result<Option<&Badge>>;
fn poll(cap: Cap) -> Result<(MessageInfo, Option<&Badge>)>;
}
trait IRQHandler {
fn set_notification(notification: CapNode) -> Result<()>;
fn ack() -> Result<()>;
fn clear() -> Result<()>;
}
trait IRQControl {
fn get(irq: u32, dest: CapNodeRootedPath) -> Result<()>;
// ARM?
fn get_trigger();
fn get_trigger_core();
}
// Syscalls (kernel API)
trait API {
fn send(cap: Cap, msg_info: MessageInfo);
// Wait for message, when it is received,
// return object Badge and block caller on `reply`.
fn recv(src: Cap, reply: Cap) -> Result<(MessageInfo, Option<&Badge>)>;
fn call(cap: Cap, msg_info: MessageInfo) -> Result<(MessageInfo, Option<&Badge>)>;
fn reply(msg_info: MessageInfo);
fn nb_send(dest: Cap, msg_info: MessageInfo);
// As Recv but invoke `reply` first.
fn reply_recv(src: Cap, reply: Cap, msg_info: MessageInfo) -> Result<(MessageInfo, Option<&Badge>)>;
// As ReplyRecv but invoke `dest` not `reply`.
fn nb_send_recv(dest: Cap, msg_info: MessageInfo, src: Cap, reply: Cap) -> Result<(MessageInfo, Options<&Badge>)>;
fn nb_recv(src: Cap) -> Result<(MessageInfo, Option<&Badge>)>;
// As NBSendRecv, with no reply. Donation is not possible.
fn nb_send_wait(cap: Cap, msg_info: MessageInfo, src: Cap) -> Result<(MessageInfo, Option<&Badge>)>;
// As per Recv, but donation not possible.
fn wait(src: Cap) -> Result<(MessageInfo, Option<&Badge>)>;
fn r#yield();
// Plus some debugging calls...
}
struct Kernel {} // Nucleus, actually...
impl API for Kernel {}
trait DomainSet {
// ??
fn set(domain, thread: TCB);
}
// Virtualisation
// ARM
trait VCPU {
fn inject_i_r_q(virq: u16, priority: u8, group: u8, index: u8) -> Result<()>;
fn read_registers();
fn write_registers();
fn set_tcb();
}

View File

@ -19,6 +19,7 @@
#![feature(custom_test_frameworks)]
#![test_runner(crate::tests::test_runner)]
#![reexport_test_harness_main = "test_main"]
#![recursion_limit = "4096"] // for mashup! macro
#![deny(missing_docs)]
#![deny(warnings)]
@ -161,6 +162,11 @@ pub fn kmain() -> ! {
#[cfg(test)]
test_main();
/*
try_init_kernel().expect("Failed to init kernel");*/
// schedule();
// activate_thread();
command_prompt();
reboot()
@ -231,6 +237,70 @@ fn reboot() -> ! {
}
}
#[derive(Debug)]
enum KernelInitError {
CapabilityCreationFailed,
}
#[link_section = ".text.boot"]
fn try_init_kernel() -> Result<(), KernelInitError> {
let root_capnode_cap = create_root_capnode();
if root_capnode_cap.is_err() {
return Err(KernelInitError::CapabilityCreationFailed);
}
Ok(())
}
const CONFIG_ROOT_CAPNODE_SIZE_BITS: usize = 12;
const WORD_BITS: usize = 64;
enum BootInfoCaps {
InitThreadCapNode = 1,
}
impl From<BootInfoCaps> for usize {
fn from(val: BootInfoCaps) -> usize {
val as usize
}
}
#[link_section = ".text.boot"]
fn create_root_capnode() -> Result<CapNodeCapability, caps::CapError> {
// write the number of root CNode slots to global state
BOOT_INFO.lock(|bi| bi.max_slot_pos = 1 << CONFIG_ROOT_CAPNODE_SIZE_BITS); // 12 bits => 4096 slots
// seL4_SlotBits = 32 bytes per entry, 4096 entries =>
// create an empty root CapNode
// this goes into the kernel startup/heap memory (one of the few items that kernel DOES allocate).
let region_size = core::mem::size_of::<CapTableEntry>() * BOOT_INFO.lock(|bi| bi.max_slot_pos); // 12 + 5 => 131072 (128Kb)
let pptr = BOOT_INFO.lock(|bi| bi.alloc_region(region_size)); // GlobalAllocator::alloc_zeroed instead?
let pptr = match pptr {
Ok(pptr) => pptr,
Err(_) => {
println!("Kernel init failed: could not create root capnode");
return Err(caps::CapError::CannotCreate);
}
};
// @todo lifetime of slice -- slice here only to zero out memory
let slice =
unsafe { core::slice::from_raw_parts_mut(pptr.as_u64() as *mut u8, region_size as usize) };
for byte in slice.iter_mut() {
*byte = 0;
} // @todo wtf
// transmute into a type? (you can use ptr.write() to just write a type into memory location)
let cap = CapNodeCapability::new_root(pptr.as_u64());
use core::convert::TryFrom;
let mut cap_node = CapNodeCapability::try_from(cap.as_u128()).unwrap();
// this cnode contains a cap to itself...
/* write the root CNode cap into the root CNode */
cap_node.write_slot(usize::from(BootInfoCaps::InitThreadCapNode), &cap);
Ok(cap_node) // reference to pptr is here
}
fn check_display_init() {
display_graphics()
.map_err(|e| {