godspeed_sdk/lib.rs
1// SPDX-License-Identifier: Apache-2.0
2//! GodspeedOS service SDK.
3//!
4//! All userspace services link against this crate. It provides the typed wrappers around kernel
5//! syscalls so service code never issues raw syscalls.
6//!
7//! # A service, whole
8//!
9//! Everything a service is fits in one picture, and it is worth having in your head before reading
10//! any module here.
11//!
12//! ```text
13//! contracts/ping.toml what the service ASKS for, in writing
14//! ipc_send = ["pong"]
15//! ipc_receive = ["ping"]
16//! log_write = true
17//! │
18//! │ the supervisor spawns it with a spawn REQUEST (its spawn
19//! │ row, held to this contract by contract_check), and the
20//! │ KERNEL mints exactly what that request grants. Not one more.
21//! │ The kernel never reads the contract (CLAUDE.md 13.6).
22//! ▼
23//! ┌──────────────────────────────────────────────────────────┐
24//! │ ServiceContext the only door to the outside │
25//! │ │
26//! │ ctx.log(..) ctx.try_send(..) ctx.recv() │
27//! │ ctx.alloc_mem(..) ctx.spawn(..) ctx.mmio() │
28//! └──────────────────────────────────────────────────────────┘
29//! │
30//! ▼
31//! fn service_main(ctx) -> ! a loop that never returns
32//! ```
33//!
34//! Three consequences, and they are the whole model:
35//!
36//! - **What the spawn request did not grant, the service cannot do.** There is no ambient authority to
37//! fall back on: no root, no inherited handles, no global namespace. A call whose capability was
38//! never granted returns `CapNotHeld` - it does not fail late or half-succeed.
39//! - **`ServiceContext` is the only way out.** Everything crossing the boundary goes through it, so
40//! the answer to "what can this service reach?" is its spawn request plus this type, and nothing
41//! else needs reading to be sure. (The contract is where a reviewer reads that request, and the
42//! checkers hold the two together.)
43//! - **Failure is a value, never a surprise.** `EndpointDead`, `CapRevoked`, `AllocDenied` are
44//! ordinary `Result`s a service is expected to handle: a peer that died is reacquired by name and
45//! the work retried. Services are killed and restarted routinely, including by the chaos suite, so
46//! this path is the common one rather than the exception.
47//!
48//! `examples/ping` is the whole of the above in about forty lines, contract included.
49//!
50//! # Where the unsafe lives
51//!
52//! Service code contains none. §18.1 designates `syscall.rs`, `mmio.rs`, `dma.rs` and
53//! `adversarial.rs`; in practice the largest holder is `service_context.rs` (77 lines) with `ipc.rs`
54//! (8), both recorded as GRANDFATHERED FLOORS by §18.5's 2026-09-12 amendment and the three
55//! amendments after it (`audits/unsafe-audit.md` has the counts) - nearly all of them are
56//! `unsafe { raw_syscall(..) }` call sites, and they may rise only by a §18.5 amendment. Every block carries a SAFETY
57//! argument. (This said the unsafe "is confined to `syscall.rs`, `mmio.rs` and `dma.rs`", which left
58//! the largest holder unnamed.) `scripts/unsafe_check.py` fails the build on an `unsafe` in any
59//! service, which is what keeps that true rather than merely intended.
60
61// `no_std` for the real (target) build; under `cargo test` we build for the host with
62// `std` so the pure-logic modules (e.g. `hid`) can have unit tests.
63#![cfg_attr(not(test), no_std)]
64
65pub mod adversarial;
66pub mod capability;
67pub mod churn;
68pub mod dma;
69pub mod hid;
70pub mod ipc;
71pub mod memory;
72pub mod mmio;
73pub mod record;
74pub mod service_context;
75/// IPC trace emission - the client half of the trace ring `events` holds (`utilities/47_events.md`).
76pub mod trace;
77pub mod usbfn;
78pub(crate) mod syscall;
79
80pub use capability::{CapHandle, CapError};
81pub use dma::Dma;
82pub use ipc::{Message, IpcError};
83pub use mmio::{Framebuffer, Mmio};
84pub use record::{Table, Value, RecordSink, parse_predicate};
85pub use service_context::{ServiceContext, TaskStat, CapInfo, Datetime, ClockSource, ReqOutcome, DeadlineOutcome,
86 USB_DISK_BUSY, USB_DISK_ABSENT};
87
88pub type Result<T> = core::result::Result<T, Error>;
89
90#[derive(Debug)]
91pub enum Error {
92 Cap(CapError),
93 Ipc(IpcError),
94 NotFound,
95 InvalidArgument,
96 /// A spawn named a task that is already running: one name is one task (`spawn_image`).
97 AlreadyRunning,
98}
99
100impl From<CapError> for Error {
101 fn from(e: CapError) -> Self { Error::Cap(e) }
102}
103
104impl From<IpcError> for Error {
105 fn from(e: IpcError) -> Self { Error::Ipc(e) }
106}
107
108/// A panicking service must DIE, not spin.
109///
110/// This was `loop {}`, and that one line quietly disabled the whole recovery model. `panic = "abort"`
111/// means a panic reaches here; spinning in ring 3 means the task never dies, and everything downstream
112/// depends on death:
113///
114/// - the endpoint's generation is never bumped, so a peer blocked in `call` NEVER receives
115/// `ReplyDead` (8.6) - the failure-truth Commandment VIII exists to deliver simply never arrives;
116/// - the kernel never notifies the supervisor, so the service is NEVER restarted, however carefully
117/// it was added to the watched set;
118/// - nothing prints, so an operator sees a service that is "running" and answering nothing;
119/// - the liveness watchdog cannot help - the task is preempted normally, so the CORE is fine.
120///
121/// A panic in `fs`, `block-driver` or a USB driver therefore converted the entire storage chain into a
122/// permanent silent hang. That is the Rule Above The Rules inverted: the one thing nothing above the
123/// kernel may do is wedge the machine, and this made a panic do exactly that.
124///
125/// So: say what happened, then FAULT deliberately. There is no self-terminate syscall (a service
126/// cannot ask to be killed), and a ring-3 fault is the sanctioned way a task ends - the kernel kills
127/// it, reclaims its frames, bumps its endpoint generation and notifies the supervisor, which is
128/// precisely the path a page-faulting service already takes. `adversarial::fault_null_read` exists for
129/// this exact purpose and carries the SAFETY argument, so the unsafe stays in the module 18.1 permits
130/// it in rather than appearing here.
131///
132/// The log comes first and is deliberately short: `log` truncates at 256 bytes, and a panic message
133/// with a long file path can exceed that.
134#[cfg(not(test))]
135#[panic_handler]
136fn panic(info: &core::panic::PanicInfo) -> ! {
137 // `ServiceContext` is a zero-sized handle (`_private: ()`) whose methods read the kernel-written
138 // context block, so one can be made here without threading it through the panic path.
139 let ctx = crate::service_context::ServiceContext::for_panic();
140 ctx.log_fmt(format_args!("PANIC in service: {}", info));
141 ctx.log("service panicked - faulting so the kernel kills and the supervisor restarts it");
142 crate::adversarial::fault_null_read();
143 // Unreachable on a conforming kernel: the fault above kills this task. If a kernel ever failed to,
144 // spinning is still wrong but it is what is left - and the two lines above already said so.
145 loop {}
146}