Skip to main content

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}