/* * 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. // Untyped.retype() derives cap to a typed cap (derivation tree must be maintained) trait Untyped { // Uses T::SIZE_BITS to properly size the resulting object // in some cases size_bits must be passed as argument though... fn retype(target_cap: CapNodeRootedPath, target_cap_offset: usize, num_objects: usize) -> Result; // @todo return an array of caps? } // with GATs // trait Retyped { type Result = CapTable:: .. } // 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;///?? } // 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 -- aka HugePage // | or // +--PageDirectory (L2) // +--Page -- aka LargePage // | or // +--PageTable (L3) // +--Page -- aka Page /// Cache data management. trait PageCacheManagement { /// 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 // mod aarch64 { struct Page {} impl Page { // VirtSpace-like interface. /// Get the physical address of the underlying frame. fn get_address() -> Result { todo!() } fn map(virt_space: VirtSpace/*Cap*/, vaddr: VirtAddr, rights: CapRights, attr: VMAttributes) -> Result<()> { todo!() } /// Changes the permissions of an existing mapping. fn remap(virt_space: VirtSpace/*Cap*/, rights: CapRights, attr: VMAttributes) -> Result<()> { todo!() } fn unmap() -> Result<()> { todo!() } // MMIO space. fn map_io(iospace: IoSpace/*Cap*/, rights: CapRights, ioaddr: VirtAddr) -> Result<()> { todo!() } } 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<()> { todo!() } fn unmap() -> Result<()> { todo!() } } // 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<()> { todo!() } fn unmap() -> Result<()> { todo!() } } // L1 table struct PageUpperDirectory {} impl PageUpperDirectory { fn map(pgd: PageGlobalDirectory/*Cap*/, vaddr: VirtAddr, attr: VMAttributes) -> Result<()> { todo!() } fn unmap() -> Result<()> { todo!() } } // 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... from FrameCaps // 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; fn get_breakpoint(bp_num: u16) -> Result; 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 <> struct TCB { capability: u128, // should actually be a CapPath here - this is the argument to // Thread.read_registers(cap, ... call for example. } impl Thread for TCB { fn configure(fault_endpoint: _, cap_space_root: _, cap_space_root_data: _, virt_space_root: _, ipc_buffer_frame: _, ipc_buffer_offset: usize) -> _ { unimplemented!() } fn set_space(fault_endpoint: _, cap_space_root: _, cap_space_root_data: _, virt_space_root: _) -> _ { unimplemented!() } fn configure_single_stepping(bp_num: u16, _: _) { unimplemented!() } fn get_breakpoint(bp_num: u16) -> _ { unimplemented!() } fn set_breakpoint(bp_num: u16, bp: _) -> _ { unimplemented!() } fn unset_breakpoint(bp_num: u16) -> _ { unimplemented!() } fn suspend() -> _ { unimplemented!() } fn resume() -> _ { unimplemented!() } fn copy_registers(source: TCB, suspend_source: bool, resume_target: bool, transfer_frame_regs: bool, transfer_integer_regs: bool, arch_flags: u8) -> _ { unimplemented!() } fn read_registers(suspend_source: bool, arch_flags: u8, num_regs: u16, register_context: &mut _) -> _ { unimplemented!() } fn write_registers(resume_target: bool, arch_flags: u8, num_regs: u16, register_context: &_) -> _ { unimplemented!() } fn bind_notification(notification: _) -> _ { unimplemented!() } fn unbind_notification() -> _ { unimplemented!() } fn set_priority(authority: TCB, priority: u32) -> _ { unimplemented!() } fn set_mc_priority(authority: TCB, mcp: u32) -> _ { unimplemented!() } fn set_sched_params(authority: TCB, mcp: u32, priority: u32) -> _ { unimplemented!() } fn set_affinity(affinity: u64) -> _ { unimplemented!() } fn set_ipc_buffer(ipc_buffer_frame: _, ipc_buffer_offset: usize) -> _ { unimplemented!() } fn set_tls_base(tls_base: usize) -> _ { unimplemented!() } fn set_ept_root(eptpml: _) -> _ { unimplemented!() } } impl KernelObject for TCB { const SIZE_BITS: usize = 12; } trait Notification { fn signal(dest: Cap); fn wait(src: Cap) -> Result>; 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 Nucleus {} impl API for Nucleus { fn send(cap: _, msg_info: _) { unimplemented!() } fn recv(src: _, reply: _) -> _ { unimplemented!() } fn call(cap: _, msg_info: _) -> _ { unimplemented!() } fn reply(msg_info: _) { unimplemented!() } fn nb_send(dest: _, msg_info: _) { unimplemented!() } fn reply_recv(src: _, reply: _, msg_info: _) -> _ { unimplemented!() } fn nb_send_recv(dest: _, msg_info: _, src: _, reply: _) -> _ { unimplemented!() } fn nb_recv(src: _) -> _ { unimplemented!() } fn nb_send_wait(cap: _, msg_info: _, src: _) -> _ { unimplemented!() } fn wait(src: _) -> _ { unimplemented!() } fn r#yield() { unimplemented!() } } trait DomainSet { // ?? fn set(domain: Dom, thread: TCB); } // Virtualisation // ARM trait VCPU { fn inject_irq(virq: u16, priority: u8, group: u8, index: u8) -> Result<()>; fn read_registers(); fn write_registers(); fn set_tcb(); }