Capability Invocation
substrate/invoke.rs is the substrate module that turns every kernel operation into a typed Rust function call.
The file is 689 lines and exposes exactly 75 pub fn entries — one per (capability type, operation) pair plus a handful of parsers for operations that return data via the IPC buffer.
Every wrapper in this file is a thin shim over the base invoke() function, which itself is just:
pub fn invoke(cap: Cap, label: u64, arg0: u64, arg1: u64, arg2: u64, arg3: u64) -> TronaResult {
syscall(SYS_INVOKE, cap, label, arg0, arg1, arg2, arg3)
}
The SYS_INVOKE syscall (#9) receives the capability in the first argument, the invoke label in the second, and up to four scalar arguments.
Results flow back through the TronaResult struct — error for the status code and value for a single-word return.
Operations that need to return more than one word write them into the per-thread IPC buffer and the caller parses them with dedicated helpers.
For the actual label numbers, see Invoke Labels. This page is about the 75 Rust wrappers that pair with those labels.
The base entry point
All 74 typed wrappers call invoke() directly, usually like this:
#[inline]
pub fn tcb_resume(tcb: Cap) -> i32 {
invoke(tcb, TCB_RESUME, 0, 0, 0, 0).error as i32
}
The #[inline] hint and the fact that invoke() and syscall() are also inlined means the compiler usually collapses the whole chain into a single syscall instruction at every call site — there is no actual function call overhead.
Wrappers that need to return a result use the TronaResult explicitly:
#[inline]
pub fn mo_get_size(mo: Cap) -> (i32, u64) {
let r = invoke(mo, MO_GET_SIZE, 0, 0, 0, 0);
(r.error as i32, r.value)
}
Wrappers that cannot fail (ioport_in8, ioport_out8, …) return bare values because the kernel guarantees the operation either succeeds or traps:
#[inline]
pub fn ioport_in8(ioport: Cap, offset: u64) -> u8 {
invoke(ioport, IOPORT_IN8, offset, 0, 0, 0).value as u8
}
Untyped (2 wrappers)
Carving kernel objects out of untyped memory.
| Wrapper | Purpose |
|---|---|
|
Retype |
|
Same as above but with an explicit destination CNode resolution depth, for callers that place new objects into a nested CNode. |
The new_type argument is one of the OBJ_* constants (OBJ_ENDPOINT, OBJ_NOTIFICATION, OBJ_TCB, OBJ_CNODE, OBJ_VSPACE, OBJ_FRAME, OBJ_IRQ_HANDLER, OBJ_IO_PORT, OBJ_SCHED_CONTEXT, OBJ_MEMORY_OBJECT).
See Invoke Labels for the complete list.
TCB (14 wrappers)
Thread Control Block operations — configure a new TCB, resume/suspend it, bind it to a notification, write its registers.
| Wrapper | Purpose |
|---|---|
|
Set entry point, stack pointer, and IPC buffer address in a single call. Used immediately after |
|
Place the TCB on a ready queue. |
|
Remove from the ready queue. Returns immediately. |
|
Like |
|
Bind a CSpace and VSpace root to a TCB. |
|
Same with explicit CSpace root depth. |
|
Install an endpoint that receives fault messages when this TCB traps. |
|
Change the TCB’s IPC buffer virtual address. |
|
Patch registers on a suspended TCB. Used by |
|
Attach a notification object to the TCB — signals on the notification wake the TCB if it is in |
|
Copy the source TCB’s FPU state into the destination. Used by |
|
Set the thread pointer (FS base on x86_64, |
|
Register a userspace dispatcher function that the kernel will jump to when a notification arrives while the TCB is in a non-blocking state. |
|
Read back the TCB’s CSpace root depth. Uses the IPC buffer for the result. |
SchedContext (2 wrappers)
Scheduling contexts encode EDF budget and period.
| Wrapper | Purpose |
|---|---|
|
Set the budget and period in microseconds. |
|
Attach this scheduling context to a TCB. |
VSpace (23 wrappers)
Page mapping, COW, MemoryObject binding, and page-table walks. This is the largest object-type group because vspace operations cover so many shapes — map/unmap/protect for single pages and ranges, demand and COW variants, device mappings, and MemoryObject mappings.
| Wrapper | Purpose |
|---|---|
|
Map a single frame into the vspace at the given vaddr. |
|
Unmap a single page. |
|
Change page protection flags on a single page. |
|
Change protections across a range of |
|
Install an intermediate page table at the given level for the target vaddr range. |
|
Walk the page table starting at |
|
Parse the header (count, root_vaddr) from the IPC buffer after a |
|
Parse an entry ( |
|
Copy the contents of a mapped page into a fresh frame. |
|
Map a physical device page at a given vaddr — used by drivers to access MMIO. |
|
Range variant. |
|
Create a COW-shared mapping of a page in another vspace. |
|
Share a page read-only across two vspaces. |
|
Install a demand-paged anonymous mapping — zero-on-fault. |
|
Range variant. |
|
Resolve a COW fault by installing a private frame at the faulting vaddr. |
|
Pre-stage a pool of frames for COW resolution so that fault handling does not have to call |
|
Configure the notification to signal when the COW pool needs replenishment. |
|
Top the pool back up. |
|
Fork a range of pages from source to destination vspace — the core of |
|
Map a MemoryObject into a vspace at |
|
Map a specific number of pages from the MemoryObject. |
|
Unmap an MO-backed range. |
CNode (12 wrappers)
Capability movement within and between CNodes.
| Wrapper | Purpose |
|---|---|
|
Copy a capability, narrowing the rights to the caller-specified subset. |
|
Explicit-depth variant for nested CSpaces. |
|
Copy with a badge attached. The badge is visible to the receiver of any message sent through the minted cap. |
|
Transfer the cap without leaving a copy behind. |
|
Move plus rebadge. |
|
Persist the caller’s reply capability (from a |
|
Delete the cap in a slot. |
|
Explicit-depth variant. |
|
Revoke every descendant of the cap in a slot — used when tearing down a resource that was shared via |
|
Explicit-depth variant. |
|
Install a guard pattern on a CNode slot for hierarchical CSpace lookup. |
|
Read back the CNode’s size, guard, etc. The info is carried in the |
IRQ and Device (5 wrappers)
IRQ control creates IRQ handler caps; IRQ handlers ack interrupts and route them to notifications.
device_untyped_create shares this numeric block because it also creates new caps.
| Wrapper | Purpose |
|---|---|
|
Acknowledge the pending interrupt so the kernel can deliver the next one. |
|
Bind the IRQ to a notification. When the kernel takes the interrupt, it signals the notification, which in turn wakes the waiting driver thread. |
|
Unbind the notification. |
|
Carve a new IRQ handler cap from the IRQ control cap. |
|
Create a device-backed untyped cap from a parent untyped — used by drivers that need a contiguous MMIO range. |
I/O Port (8 wrappers)
x86 I/O port access. On aarch64 the underlying IoPort cap is still accepted but the operations compile to no-ops, so the wrappers are architecture-neutral.
| Wrapper | Purpose |
|---|---|
|
Read a byte. |
|
Write a byte. |
|
Read a word. |
|
Write a word. |
|
Read a dword. |
|
Write a dword. |
|
Configure the base port and range for an IoPort cap. |
|
Carve a new IoPort cap from an IoPort control cap. |
MemoryObject (8 wrappers)
MemoryObject is the Fuchsia-inspired abstraction for a variable-size object backed by physical pages that the owning process can commit, decommit, clone, resize, and side-band read/write.
| Wrapper | Purpose |
|---|---|
|
Reserve and back |
|
Release pages back to untyped memory. |
|
Read the current size in pages. |
|
Make a new MemoryObject that is a COW-shared copy of the source. |
|
Grow or shrink the MemoryObject. |
|
Side-band read |
|
Side-band write |
|
Probe whether a given page index is currently committed. Used by pagers to avoid blocking on pages the MemoryObject already has resident. |
Wrappers that return through the IPC buffer
Three operations need to return more data than fits in a single TronaResult.value word.
They write their result into the per-thread IPC buffer’s reserved area, and substrate exposes small parser helpers that read it back:
| Operation | Parser(s) |
|---|---|
|
|
|
Returns |
|
Returns a |
These parsers exist because the alternative — returning a struct by value from invoke() — would require the caller to pass a destination pointer into the kernel, which the IPC buffer already is.
Doing it via the buffer keeps the invoke() / syscall() API uniform.
Wrappers that do not exist
A few operations have labels but no dedicated wrapper in invoke.rs:
-
No explicit wrappers for the arch-specific register read operations that
TCB_WRITE_REGISTERSreads back — the kernel exposes the write direction only. -
No bulk variants of
ioport_in*/ioport_out*— callers loop. -
No
SC_DESTROYorMO_DESTROY— kernel objects are revoked viacnode_revoke, not explicitly destroyed.
When a caller needs an operation for which no wrapper exists, the escape hatch is to call invoke() directly with the raw label and arguments, exactly as the wrappers themselves do.
There is no penalty for this other than losing the type-safety of the dedicated wrapper.
Related pages
-
Invoke Labels — the full catalog of label constants each wrapper passes to
invoke(). -
Syscall Wrappers — the
syscall()function thatinvoke()calls into. -
Error Codes — the error codes every wrapper can return.
-
Slot Allocator — how callers acquire the destination slots that most of these wrappers need.