Skip to main content

godspeed_sdk/
service_context.rs

1// SPDX-License-Identifier: Apache-2.0
2//! ServiceContext - entry-point type handed to every service's `service_main`.
3//!
4//! Provides safe, named access to the capabilities the service declared in its
5//! contract. Capability names match the contract field names exactly.
6//! Requesting a cap not in the contract returns `Err(CapNotHeld)`.
7
8use crate::capability::{CapError, CapHandle};
9use crate::ipc::{IpcError, Message};
10use crate::syscall::raw_syscall;
11
12/// Why a `reacquire_cap_detail` failed. The two real cases want OPPOSITE responses, which is the
13/// entire reason this type exists rather than a `bool`.
14///
15/// `NameNotRegistered` is **transient and expected**: peers are acquired before they are spawned at
16/// boot, and again while one is mid-restart. Retry; it heals. `CapTableFull` is **permanent**: this
17/// service cannot hold another capability, so it can never reacquire anything again and will not
18/// recover until it is restarted - a failed recovery, which §26.7 says must stay exactly as visible as
19/// the original failure.
20///
21/// Collapsed into one value (as `reacquire_cap` still does, for callers that only retry) a service can
22/// only print both and commit to neither. That ambiguity cost a full debugging pass on the Pi 2: a
23/// `block-driver` that was alive the whole time produced the same line as a reader out of cap slots.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum AcquireFailure {
26    /// The name does not resolve right now - no live instance is registered under it. Transient.
27    NameNotRegistered,
28    /// The name resolved, but this service's own capability table is full. Terminal until restart.
29    CapTableFull,
30    /// The request itself was refused (name empty, too long, or not a declared peer).
31    Refused,
32}
33
34impl AcquireFailure {
35    /// Decode the kernel's negative return. Codes are fixed at the syscall boundary
36    /// (`kernel/src/syscall/dispatch.rs`); an UNRECOGNISED negative maps to `Refused` rather than
37    /// being guessed at, so a future code cannot silently masquerade as one of the two named cases.
38    fn from_code(ret: i64) -> Self {
39        match ret {
40            -20 => AcquireFailure::NameNotRegistered,
41            -21 => AcquireFailure::CapTableFull,
42            _   => AcquireFailure::Refused,
43        }
44    }
45
46    /// One clause naming the cause, for a caller assembling a log line.
47    pub fn reason(self) -> &'static str {
48        match self {
49            AcquireFailure::NameNotRegistered =>
50                "the name does not resolve - no live instance is registered under it",
51            AcquireFailure::CapTableFull =>
52                "this service's capability table is FULL - it cannot hold another cap and will not recover until restarted",
53            AcquireFailure::Refused =>
54                "the kernel refused the request (name empty, too long, or not a declared peer)",
55        }
56    }
57}
58
59/// Outcome of [`ServiceContext::request_with_reply_deadline_outcome_into`] - the buffer-writing twin
60/// of [`DeadlineOutcome`].
61///
62/// It exists because the `_into` form collapsed all four outcomes into `Option<usize>`, and the two
63/// that matter are OPPOSITES. `SendFailed` means the request never left, so nothing can arrive late
64/// and retrying after a reacquire is safe. `Timeout` means it DID leave, so a late reply is still
65/// coming and re-sending desyncs the protocol - every later reply is matched to the wrong request.
66/// A caller handed `None` cannot tell those apart, so it must either never retry (and stay broken
67/// after a peer restarts) or always retry (and desync). `fs` had the first of those.
68#[derive(Debug)]
69pub enum DeadlineOutcomeInto {
70    /// A reply arrived; the payload is in the caller's buffer, this many bytes of it.
71    Reply(usize),
72    /// The send never left: the peer's cap is stale or its name does not currently resolve. Nothing
73    /// is in flight. Reacquire by name and retry ONCE - this is the only safe retry.
74    SendFailed,
75    /// The request WAS delivered, and the peer died holding it (`ReplyDead`, 8.6), so the work may
76    /// have been done. Re-send only an operation that is safe to repeat (a block read, a rewrite of
77    /// the same sector). It was folded into `SendFailed` until 2026-10-10 (`backlog/fixed/80` S3); `gs::call`
78    /// has kept the two apart all along, as `Error::PeerDied` against `Error::Unreachable`.
79    PeerDied,
80    /// The peer's queue was full. The request did not leave either, but the peer is alive and busy.
81    QueueFull,
82    /// The request was delivered and no reply came in time. Do NOT re-send.
83    Timeout,
84}
85
86/// Outcome of an abortable request/reply ([`ServiceContext::request_with_reply_abortable`]): the reply
87/// arrived, the user pressed `q`/`Q`/ESC to abort the wait, or the deadline expired with no reply.
88/// `Timeout` also covers a request that never left (no such peer, no reply cap, a failed send).
89pub enum ReqOutcome {
90    Reply(Message),
91    Aborted,
92    Timeout,
93}
94
95/// Outcome of [`ServiceContext::request_with_reply_deadline_outcome`], which distinguishes the two
96/// ways a deadline request fails - so a caller can self-heal a *restarted* peer without penalising a
97/// *silent* one. `SendFailed`: the send never left (the peer's cap is stale or its name is currently
98/// unresolvable - it was killed and respawned, generation bumped), so reacquiring it by name and
99/// retrying will reach the fresh instance. `Timeout`: the peer received the request but did not reply
100/// within the deadline (a genuinely silent/absent host) - retrying only doubles the wait.
101/// `request_with_reply_deadline` collapses both to `None`; use this when the difference matters.
102pub enum DeadlineOutcome {
103    Reply(Message),
104    SendFailed,
105    /// The peer is ALIVE and its queue is FULL. A different failure entirely, and it was folded into
106    /// `SendFailed` until x86 showed the cost: `net-stack` answered a full queue by reacquiring a
107    /// capability that was never stale, retried once and gave up - "5 of 6 REQUESTs never left the
108    /// host", DHCP backing off for 34 seconds on a link that was up the whole time.
109    ///
110    /// Congestion is transient by definition. The answer is to pace and retry, not to go looking for
111    /// a peer that never went anywhere.
112    QueueFull,
113    Timeout,
114}
115
116/// Where a wall-clock reading came from. Reported alongside the time so a displayed timestamp says what
117/// it is standing on: a local hardware clock, a network correction, or nothing (§26.4 - a fallback chain
118/// is mechanism while its choice is visible, and magic when it is not).
119#[derive(Clone, Copy, PartialEq, Eq)]
120pub enum ClockSource {
121    /// No clock: no hardware RTC and nothing has set the time. `date` says so rather than inventing one.
122    Unset,
123    /// A local hardware real-time clock reading a plausible date.
124    Rtc,
125    /// Set from the network (SNTP) this boot.
126    Ntp,
127    /// Started from the persisted floor (`/system/services/time/floor`) - no RTC on this board and no network sync yet.
128    ///
129    /// A real reading and a distinct one: it is a LOWER BOUND carried over from the last boot, advancing
130    /// correctly since, but it cannot know how long the machine was powered off. Reporting it as `Rtc`
131    /// would claim hardware this board does not have, and as `Unset` would deny a time it is displaying.
132    Floor,
133}
134
135/// The machine's one pending power action, as the kernel holds it (InspectKernel query 28, `docs/shutdown.md`).
136/// See [`ServiceContext::power_pending`].
137#[derive(Clone, Copy, PartialEq, Eq)]
138pub struct PowerPending {
139    /// Whole seconds until it falls due, rounded up.
140    pub secs: u64,
141    /// A reboot; otherwise a power-off.
142    pub reboot: bool,
143}
144
145/// Wall-clock date/time read from the hardware RTC, fully decoded (binary,
146/// 24-hour). See [`ServiceContext::datetime`].
147#[derive(Clone, Copy, PartialEq, Eq)]
148pub struct Datetime {
149    pub year: u16,
150    pub month: u8,
151    pub day: u8,
152    pub hour: u8,
153    pub minute: u8,
154    pub second: u8,
155}
156
157impl Datetime {
158    /// Decode Unix epoch seconds into calendar fields (Howard Hinnant's `civil_from_days`) - the inverse
159    /// of [`Datetime::epoch_secs`]. Used to render a stored epoch, such as the clock floor, as a date.
160    pub fn from_epoch_secs(epoch: i64) -> Datetime {
161        let days = epoch.div_euclid(86_400);
162        let rem = epoch.rem_euclid(86_400);
163        let z = days + 719_468;
164        let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
165        let doe = z - era * 146_097;
166        let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
167        let y = yoe + era * 400;
168        let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
169        let mp = (5 * doy + 2) / 153;
170        let d = doy - (153 * mp + 2) / 5 + 1;
171        let m = if mp < 10 { mp + 3 } else { mp - 9 };
172        Datetime {
173            year:  (y + (m <= 2) as i64) as u16,
174            month: m as u8,
175            day:   d as u8,
176            hour:   (rem / 3_600) as u8,
177            minute: ((rem % 3_600) / 60) as u8,
178            second: (rem % 60) as u8,
179        }
180    }
181
182    /// Days since the epoch (1970-01-01), proleptic Gregorian and leap-year aware
183    /// (Howard Hinnant's `days_from_civil`). The basis for both `weekday` and
184    /// `epoch_secs`.
185    fn days_since_epoch(&self) -> i64 {
186        let mut y = self.year as i64;
187        let m = self.month as i64;
188        let d = self.day as i64;
189        y -= (m <= 2) as i64;
190        let era = (if y >= 0 { y } else { y - 399 }) / 400;
191        let yoe = y - era * 400;
192        let doy = (153 * (m + if m > 2 { -3 } else { 9 }) + 2) / 5 + d - 1;
193        let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
194        era * 146097 + doe - 719468
195    }
196
197    /// Day of week, 0 = Sunday .. 6 = Saturday (computed, not the RTC's
198    /// often-unreliable weekday register).
199    pub fn weekday(&self) -> u8 {
200        (self.days_since_epoch() + 4).rem_euclid(7) as u8
201    }
202
203    /// Seconds since the epoch (1970-01-01). Assumes the RTC reads UTC; if the
204    /// hardware clock is set to local time the value is offset by the timezone
205    /// (v1 has no timezone database).
206    pub fn epoch_secs(&self) -> i64 {
207        self.days_since_epoch() * 86_400
208            + self.hour as i64 * 3_600
209            + self.minute as i64 * 60
210            + self.second as i64
211    }
212}
213
214#[cfg(test)]
215mod datetime_tests {
216    use super::Datetime;
217
218    fn dt(year: u16, month: u8, day: u8, hour: u8, minute: u8, second: u8) -> Datetime {
219        Datetime { year, month, day, hour, minute, second }
220    }
221
222    fn is_leap(y: i64) -> bool { (y % 4 == 0 && y % 100 != 0) || y % 400 == 0 }
223    fn days_in_month(y: i64, m: i64) -> i64 {
224        match m {
225            1 => 31, 2 => if is_leap(y) { 29 } else { 28 }, 3 => 31, 4 => 30, 5 => 31, 6 => 30,
226            7 => 31, 8 => 31, 9 => 30, 10 => 31, 11 => 30, 12 => 31, _ => 0,
227        }
228    }
229
230    /// A deliberately naive, obviously-correct reference: count days from 1970 by iterating years +
231    /// months. It cannot share a bug with Hinnant's closed form - which is what makes the cross-check valid.
232    fn reference_epoch(y: i64, mo: i64, d: i64, h: i64, mi: i64, s: i64) -> i64 {
233        let mut days: i64 = 0;
234        for yy in 1970..y { days += if is_leap(yy) { 366 } else { 365 }; }
235        for mm in 1..mo  { days += days_in_month(y, mm); }
236        days += d - 1;
237        days * 86_400 + h * 3_600 + mi * 60 + s
238    }
239
240    #[test]
241    fn reference_matches_known_unix_anchors() {
242        // Validate the REFERENCE itself against well-known Unix epochs first, so the cross-check is trustworthy.
243        assert_eq!(reference_epoch(1970, 1, 1, 0, 0, 0), 0);
244        assert_eq!(reference_epoch(2000, 1, 1, 0, 0, 0), 946_684_800);
245        assert_eq!(reference_epoch(2038, 1, 19, 3, 14, 8), 2_147_483_648); // Y2038 (2^31)
246        assert_eq!(reference_epoch(2000, 2, 29, 0, 0, 0), 951_782_400);    // leap day
247    }
248
249    #[test]
250    fn epoch_secs_matches_known_unix_anchors() {
251        assert_eq!(dt(1970, 1, 1, 0, 0, 0).epoch_secs(), 0);
252        assert_eq!(dt(2000, 1, 1, 0, 0, 0).epoch_secs(), 946_684_800);
253        assert_eq!(dt(2038, 1, 19, 3, 14, 8).epoch_secs(), 2_147_483_648);
254    }
255
256    #[test]
257    fn epoch_secs_matches_reference_over_a_multi_century_sweep() {
258        // Cross-check Hinnant (the SDK's epoch_secs - the twin every SERVICE uses) vs the naive reference for
259        // every month of 1971..=2100: div-4 leaps, the 2100 century non-leap, the 2000 leap-400. This is the
260        // drift guard - if a future edit to the SDK's days_since_epoch diverges from the kernel's (pinned in
261        // kernel/src/clock.rs), this catches it.
262        for y in 1971..=2100i64 {
263            for mo in 1..=12i64 {
264                let last = days_in_month(y, mo);
265                for &d in &[1i64, 15, 28, last] {
266                    for &(h, mi, s) in &[(0i64, 0i64, 0i64), (23, 59, 59), (12, 30, 15)] {
267                        assert_eq!(
268                            dt(y as u16, mo as u8, d as u8, h as u8, mi as u8, s as u8).epoch_secs(),
269                            reference_epoch(y, mo, d, h, mi, s),
270                            "SDK epoch_secs mismatch at {}-{:02}-{:02} {:02}:{:02}:{:02}", y, mo, d, h, mi, s);
271                    }
272                }
273            }
274        }
275    }
276
277    #[test]
278    fn weekday_matches_known_anchors() {
279        // weekday() shares days_since_epoch with epoch_secs. 0=Sun..6=Sat. Known: 1970-01-01 Thursday (4),
280        // 2000-01-01 Saturday (6), 2026-06-06 Saturday (6) - the T630 `date` HW example.
281        assert_eq!(dt(1970, 1, 1, 0, 0, 0).weekday(), 4);
282        assert_eq!(dt(2000, 1, 1, 0, 0, 0).weekday(), 6);
283        assert_eq!(dt(2026, 6, 6, 0, 0, 0).weekday(), 6);
284    }
285
286    #[test]
287    fn from_epoch_secs_round_trips_over_the_sweep() {
288        // The INVERSE (civil_from_days) must undo epoch_secs exactly, over the same multi-century sweep -
289        // the drift guard its forward twin already has. Without this, the only thing pinning the decode
290        // used to render a stored epoch (the clock floor) would be that it looked right once.
291        for y in 1971..=2100i64 {
292            for mo in 1..=12i64 {
293                let last = days_in_month(y, mo);
294                for &d in &[1i64, 15, 28, last] {
295                    for &(h, mi, s) in &[(0i64, 0i64, 0i64), (23, 59, 59), (12, 30, 15)] {
296                        let a = dt(y as u16, mo as u8, d as u8, h as u8, mi as u8, s as u8);
297                        assert!(Datetime::from_epoch_secs(a.epoch_secs()) == a,
298                            "from_epoch_secs round-trip mismatch at {}-{:02}-{:02} {:02}:{:02}:{:02}",
299                            y, mo, d, h, mi, s);
300                    }
301                }
302            }
303        }
304        assert!(Datetime::from_epoch_secs(0) == dt(1970, 1, 1, 0, 0, 0));
305    }
306}
307
308// ---------------------------------------------------------------------------
309// ServiceContextData page layout.
310// MUST match `ServiceContextData` in `kernel/src/task/mod.rs`.
311// ---------------------------------------------------------------------------
312
313const SERVICE_CTX_ADDR:    u64   = 0x3ff000;
314/// What a spawner tells the kernel about a task it wants started from an image IT supplies
315/// (`SpawnImage`, syscall 52). Layout must match the kernel's `SpawnRequest` exactly.
316///
317/// Shaped for the end state, not for what the kernel honours today: the hardware and privilege
318/// fields are here because step D needs them, and a request that sets one is REFUSED loudly rather
319/// than silently ignored (`docs/service-ownership.md`).
320#[repr(C)]
321#[derive(Clone, Copy)]
322pub struct SpawnRequest {
323    pub version:      u32,
324    pub flags:        u32,
325    pub image_ptr:    u64,
326    pub image_len:    u64,
327    pub name_ptr:     u64,
328    pub name_len:     u32,
329    pub core:         u32,
330    pub memory_limit: u64,
331    pub privileges:   u32,
332    pub hw_flags:     u32,
333    pub mmio_base:    u64,
334    pub mmio_len:     u64,
335    pub dma_pages:    u32,
336    pub bdf:          u32,
337    pub irq_count:    u32,
338    pub irqs:         [u8; 4],
339    pub peers_ptr:      u64,
340    pub peers_len:      u32,
341    pub _pad:           u32,
342    /// Caller-provided caps to install into the child: `[label_len][label][slot_lo][slot_hi]`
343    /// repeated. The same encoding `SpawnWithCaps` uses - one wire format, not two.
344    pub installs_ptr:   u64,
345    pub installs_count: u32,
346    pub _pad2:          u32,
347    /// Mode selector. Named for probes, its first user, but general: `observe` reads it to choose
348    /// one-shot / live / foreground, and takes the LIVE LOOP at 0.
349    pub probe_mode:     u32,
350    pub _pad3:          u32,
351}
352
353/// 2 since `installs` was added, 3 since `probe_mode` was. A spawner built against a different kernel
354/// is refused loudly.
355pub const SPAWN_REQUEST_VERSION:  u32 = 3;
356pub const SPAWN_FLAG_REQ_RECV:    u32 = 1 << 0;
357pub const SPAWN_FLAG_REQ_CONSOLE: u32 = 1 << 1;
358/// `core` is a STRICT placement, not a preference.
359///
360/// The two are different rules (9.2) and the kernel has always had both: an explicitly-requested core
361/// (a restart's `--core N`) is REJECTED with `PlacementInvalid` when that core is not ready, because
362/// silently placing the service elsewhere would defeat the point of asking. A catalogue row's
363/// PREFERRED core falls back to round-robin instead, so a machine with fewer cores still boots.
364///
365/// Without this bit a moved service's table preference read as an override, and every service naming
366/// a core the machine does not have failed to spawn rather than degrading - `events` and `xhci` (both
367/// core 2) simply vanished under `-smp 2`, which 11.3 requires to keep working.
368pub const SPAWN_FLAG_CORE_STRICT: u32 = 1 << 2;
369/// Mint the child's name-wired peer caps WITH `GRANT`, so it can re-delegate them.
370///
371/// AUTHORITY, not a setting: handing out a re-delegatable capability is itself a grant (§7.4), which
372/// is why the kernel used to keep this keyed by name for the one probe that needs it (§22 Test 5A).
373/// As a request bit it is checked the same way every privilege is - the spawner may ask for it only
374/// because it could transfer such a cap itself.
375pub const SPAWN_FLAG_PEERS_GRANT: u32 = 1 << 3;
376/// Report this task's death to the supervisor's death-notification endpoint and count it as a restart. The
377/// supervisor sets it for every service it manages; the kernel keeps no list of which services those are
378/// (`docs/audio.md`, "No service names in the kernel").
379pub const SPAWN_FLAG_WATCHED:     u32 = 1 << 4;
380/// A live handover: start this beside the one task already running under its name, which keeps running until it
381/// ends itself; the new one takes the name. The supervisor's alone, since only `SpawnImage` honours it (CLAUDE.md 2.5).
382pub const SPAWN_FLAG_HANDOVER:    u32 = 1 << 5;
383
384/// Bits for `SpawnRequest::privileges`. A spawner may only request what it HOLDS ITSELF - the kernel
385/// checks, and refuses otherwise - so this passes authority on, it never mints it (3.1, 7.3).
386pub mod privbits {
387    pub const SPAWN:           u32 = 1 << 0;
388    pub const CONSOLE_PUSH:    u32 = 1 << 1;
389    pub const INTROSPECT:      u32 = 1 << 2;
390    pub const SERVICE_CONTROL: u32 = 1 << 3;
391    pub const FIRE_IRQ:        u32 = 1 << 4;
392    pub const REBOOT:          u32 = 1 << 5;
393    pub const ACQUIRE_ANY:     u32 = 1 << 6;
394    pub const RESOURCE_MINT:   u32 = 1 << 7;
395    /// ARM-only in practice; the bit is arch-neutral.
396    pub const GPIO:            u32 = 1 << 8;
397    /// SET_CLOCK with READ (raise the clock FLOOR), not WRITE (step the clock).
398    pub const SET_CLOCK_FLOOR: u32 = 1 << 9;
399    /// SET_CLOCK with WRITE (set the wall clock). Distinct from SET_CLOCK_FLOOR, the READ right.
400    pub const SET_CLOCK:       u32 = 1 << 10;
401    /// NET_DEVICE: move ethernet frames through an in-kernel network device (`NetFrame*`/`NetInfo`,
402    /// syscalls 42-44). No port has one any more - every arch answers with a stub - and no spawn
403    /// request in the tree sets this bit; the kernel still understands it.
404    pub const NET_DEVICE:      u32 = 1 << 11;
405    /// Read PCI configuration space through the legacy CF8/CFC ports (step D2). READ-ONLY and held
406    /// by ONE service - see `docs/service-ownership.md` D2 for why the write side is not on offer.
407    pub const PCI_CFG:         u32 = 1 << 12;
408    /// Set the Arm cores to the platform's minimum or maximum clock (`CpuClock`, syscall 55). Held by
409    /// ONE service, `power`, because the clock is one machine-wide setting (`docs/power.md`).
410    pub const CPU_CLOCK:       u32 = 1 << 13;
411    /// Turn the machine off, now or at a deadline the kernel holds, or halt it where it cannot cut its own
412    /// power (`PowerOff`, syscall 56). Held by the shell, for the operator (`docs/shutdown.md`).
413    pub const POWER_OFF:       u32 = 1 << 14;
414}
415
416/// Device classes a spawner can name in `SpawnRequest::hw_flags`. The kernel resolves the class to
417/// what its own bus scan found - a CLASS rather than an address, because the kernel keeps a permanent
418/// physical DMA reservation per device that a respawned driver must get back.
419pub mod hwclass {
420    /// `hw-enumerator`'s which-device question: `[BY_CLASS, class (3 bytes LE), PREFER_OWN]`, answered
421    /// `[bdf u32, class u32]`. The supervisor asks it to choose a PCI driver's device, which the kernel
422    /// then grants by that BDF; `hardware` asks it again to say which device that driver holds. One
423    /// definition, so the two cannot disagree. `PREFER_OWN`: the first device of the class that is not
424    /// a display's companion function, if there is one (the T630's HDMI audio, `docs/audio.md`).
425    pub const BY_CLASS:    u8 = 3;
426    pub const PREFER_OWN:  u8 = 1;
427    pub const NONE:        u32 = 0;
428    pub const NIC:         u32 = 2;
429    pub const XHCI:        u32 = 3;
430    pub const EHCI:        u32 = 4;
431    pub const DWC2:        u32 = 5;
432    pub const FRAMEBUFFER: u32 = 6;
433    /// Not a device: the software-raised test interrupt (§22 IR1). A class, so that the probe which
434    /// receives it names a CLASS like any driver and the kernel states the vector.
435    pub const TEST_IRQ:    u32 = 7;
436    /// An audio jack driven by PWM and fed by the SoC's DMA engine (the Pis, `docs/audio.md`). The kernel
437    /// routes the jack's pins and starts the PWM clock as part of the grant, then maps the PWM block and
438    /// the DMA engine side by side and grants a DMA arena.
439    pub const AUDIO_PWM:   u32 = 8;
440    /// A WiFi radio on an SDIO host at a fixed SoC address (the Pi 4's CYW43455 behind the Arasan, the
441    /// VisionFive 2 Lite's AIC8800 behind a DesignWare `dw_mmc`). The
442    /// kernel grants the host's window - and, where it can, the radio's power control - to the service
443    /// whose request names this kind, never to a service because of its name.
444    pub const WIFI_SDIO:   u32 = 9;
445
446    /// Bit 31: the value is a PCI CLASS CODE, not one of the named kinds above.
447    pub const PCI:         u32 = 1 << 31;
448    /// Bit 28: place this device behind the IOMMU (§6.4).
449    pub const PCI_CONFINE: u32 = 1 << 28;
450    /// Bit 29: this driver wants an INTERRUPT. The kernel allocates a vector from its MSI pool and
451    /// programs the device with it - the caller never names a vector, because routing one is
452    /// authority (`task::hw_irqs_for`).
453    pub const PCI_IRQ:     u32 = 1 << 29;
454
455    /// Describe a PCI device by what the BUS says it is: the industry-standard 24-bit
456    /// (class, subclass, prog-if) triple, plus which BAR holds its registers.
457    ///
458    /// This is what lets a driver be added with NO KERNEL CHANGE. `0x0C0330` is xHCI on every
459    /// machine ever built, so the kernel needs no name for it - it looks the code up in the table
460    /// its boot scan already filled. The BAR index is the one extra fact, because devices differ
461    /// (xHCI 0, AHCI 5), and an INDEX is not an address, so it is safe to accept from a caller.
462    pub const fn pci(class_code: u32, bar_ix: u32, confine: bool) -> u32 {
463        PCI | (if confine { PCI_CONFINE } else { 0 }) | ((bar_ix & 0x7) << 24) | (class_code & 0x00FF_FFFF)
464    }
465
466    /// BAR index meaning "the first mapped MEMORY BAR" rather than a numbered one.
467    ///
468    /// Use this when one driver covers devices that put their registers in different BARs - the
469    /// e1000 uses BAR0 where the RTL8168 puts I/O ports there and its registers in BAR2. Both are
470    /// "the first memory BAR", and neither driver should have to know which it got.
471    pub const BAR_AUTO: u32 = 7;
472
473    /// `pci`, for a driver that needs an INTERRUPT as well.
474    pub const fn pci_irq(class_code: u32, bar_ix: u32, confine: bool) -> u32 {
475        pci(class_code, bar_ix, confine) | PCI_IRQ
476    }
477}
478
479impl SpawnRequest {
480    /// A request with everything the kernel does not yet honour left at zero.
481    pub fn new(image: &[u8], name: &str) -> Self {
482        Self {
483            version: SPAWN_REQUEST_VERSION, flags: 0,
484            image_ptr: image.as_ptr() as u64, image_len: image.len() as u64,
485            name_ptr: name.as_ptr() as u64, name_len: name.len() as u32,
486            core: u32::MAX, memory_limit: 0,
487            privileges: 0, hw_flags: 0, mmio_base: 0, mmio_len: 0,
488            dma_pages: 0, bdf: 0, irq_count: 0, irqs: [0; 4],
489            peers_ptr: 0, peers_len: 0, _pad: 0,
490            installs_ptr: 0, installs_count: 0, _pad2: 0,
491            probe_mode: 0, _pad3: 0,
492        }
493    }
494
495    /// Encode `(label, cap)` pairs into `buf` and point this request at them.
496    ///
497    /// The caller must hold each cap WITH GRANT: the kernel checks it, and that check is what makes
498    /// installing them non-escalating (7.3) - a caller that can GRANT a cap could transfer it anyway.
499    pub fn set_installs(&mut self, buf: &mut [u8], pairs: &[(&str, CapHandle)]) -> bool {
500        let mut n = 0usize;
501        for (label, cap) in pairs {
502            let lb = label.as_bytes();
503            // The kernel's limit, `PEER_NAME_BYTES`: this once accepted more than the kernel did, and the kernel refused
504            // the spawn for a label it had been told was fine (`backlog/fixed/80` S5).
505            if lb.is_empty() || lb.len() > PEER_NAME_BYTES { return false; }
506            if n + 1 + lb.len() + 2 > buf.len() { return false; }
507            buf[n] = lb.len() as u8; n += 1;
508            buf[n..n + lb.len()].copy_from_slice(lb); n += lb.len();
509            buf[n] = (cap.0 & 0xFF) as u8; buf[n + 1] = ((cap.0 >> 8) & 0xFF) as u8; n += 2;
510        }
511        self.installs_ptr   = if n > 0 { buf.as_ptr() as u64 } else { 0 };
512        self.installs_count = pairs.len() as u32;
513        true
514    }
515}
516
517/// How a task ended, as the kernel says it in the death notice (`name`, NUL, then one of these). The
518/// kernel's `task::death` holds the same values; `ask` asserts its copy against these.
519pub mod death {
520    /// Ended by the `kill` syscall; the name of the task that called it follows.
521    pub const KILLED: u8 = b'K';
522    /// Ended by a user-mode CPU fault; the kind below and the address (u64 LE) follow.
523    pub const FAULT: u8 = b'F';
524    /// Ended itself with `exit`; the status it gave (u32 LE) follows.
525    pub const EXITED: u8 = b'E';
526    /// A fault the port does not classify.
527    pub const F_OTHER: u8 = 0;
528    /// A load or store its page tables do not allow; the address is the data address.
529    pub const F_MEMORY: u8 = 1;
530    /// An instruction fetch its page tables do not allow; the address is the fetch address.
531    pub const F_INSTRUCTION: u8 = 2;
532    /// An instruction the CPU will not run; the address is the instruction's.
533    pub const F_ILLEGAL: u8 = 3;
534    /// x86's general protection fault; the address is the instruction's.
535    pub const F_PROTECTION: u8 = 4;
536    /// A misaligned access the CPU refuses.
537    pub const F_ALIGNMENT: u8 = 5;
538}
539
540/// The supervisor's command channel wire format.
541///
542/// ONE definition. This lived in three services at once (`supervisor`, `control`, `shell`) - three
543/// copies of a protocol, which is the second-truth problem this project rejects everywhere else
544/// (26.4, Commandment III). A drifted copy would not fail loudly; it would send a byte the supervisor
545/// reads as a different opcode.
546///
547/// The supervisor's endpoint receives two kinds of message, told apart by the first byte:
548///
549/// ```text
550///   death notice   "pong" 0 how...               <- kernel-generated; a name, [a-z0-9-], then how it
551///                                                   ended (`death`, CLAUDE.md 6.2 amendment 2026-10-11)
552///   command        0x01 op <core u32 LE> name    <- 0x01 cannot begin a name
553///                                    name may be followed by NUL-separated PEER names
554/// ```
555///
556/// Choosing an impossible first byte means a death notice needs nothing to set it apart: it still
557/// begins with the name, as it always has (what follows the NUL is `death`'s, added 2026-10-11), so
558/// the two are unambiguous without the kernel learning anything about commands.
559pub mod supcmd {
560    /// First byte of a command. Not a legal first byte of a service name.
561    pub const MARKER:  u8 = 0x01;
562    /// Kill if alive, then spawn.
563    pub const RESTART: u8 = b'R';
564    /// Spawn a service that is not running.
565    pub const SPAWN:   u8 = b'S';
566    /// Which devices does the supervisor drive? Read only: `[MARKER, DEVICES]`, answered with
567    /// `[OK, count, entries...]` - per entry `b'H'`, the spawn row's device word (u32 LE, `hwclass`),
568    /// the service's name (length byte, bytes); or `b'U'`, a USB device it starts a driver for (vid
569    /// u16 LE, pid u16 LE, attached 0/1, the reporting host's name, the driver's name, each a length
570    /// byte and bytes). The `hardware` utility's DRIVER column (`docs/hardware-design.md`).
571    pub const DEVICES: u8 = b'D';
572    /// Why is this driver's device handled as it is? Read only: `[MARKER, WHY, name...]`, answered
573    /// with `[OK, text...]` - the reason the supervisor keeps beside the spawn rows - or `[UNKNOWN]`
574    /// for a service with none recorded. `hardware why` (`docs/hardware-design.md` 11).
575    pub const WHY:     u8 = b'W';
576    /// What has happened to the devices this supervisor drives since it started. Read only:
577    /// `[MARKER, EVENTS]`, answered with `[OK, since u32 LE, recorded u32 LE, count, entries...]` - since:
578    /// when this supervisor began its record (after its boot spawns), in seconds of uptime; recorded: every
579    /// event noted, so `recorded - count`
580    /// were overwritten; per entry the uptime second (u32 LE), an `EV_*` code, the USB vid and pid (u16
581    /// LE each, 0 for a driver event), and the driver's name (length byte, bytes). `hardware events`.
582    pub const EVENTS:  u8 = b'E';
583    /// Which services does this supervisor keep running right now? Read only: `[MARKER, MANAGED]`,
584    /// answered with `[OK, count, (len, name)*]` - the `MANAGED` services this build carries and wants
585    /// now (a USB device's driver only while its device is attached). The question `ask`'s health
586    /// answer turns on: a service on this list that is not running is down when it should be up.
587    pub const MANAGED: u8 = b'M';
588    /// How did this service last end? Read only: `[MARKER, DEATH, name...]`, answered with `[OK, since
589    /// u32 LE, deaths u32 LE, secs u32 LE, how, kind, addr u64 LE, by_len, by...]` - since: when this
590    /// supervisor began its record; deaths: the notices it has read for this name since then (0 = none,
591    /// and the rest is zero); then the newest one: its uptime second, how it ended (`death::KILLED`,
592    /// `death::FAULT`), the fault kind and address, and the name of the task that called `kill`. The
593    /// kernel says how, in the death notice; the supervisor only keeps the newest per name. `ask`.
594    pub const DEATH:   u8 = b'X';
595    /// Start an installed package: `[MARKER, INSTALL, "publisher/name version"]`. The supervisor checks the fetched
596    /// copy from the disk, grants the send peers its `grant` file names and its contract requests, and starts it
597    /// under the task name `publisher/name` (`services/supervisor/src/installed.rs`). Answered `[OK]`, or
598    /// `[FAILED, reason...]` - the reason is a sentence.
599    pub const INSTALL: u8 = b'I';
600    /// Stop an installed package and forget it: `[MARKER, UNINSTALL, "publisher/name"]`. `[OK]`, or `[UNKNOWN]` when
601    /// it was not installed.
602    pub const UNINSTALL: u8 = b'O';
603    /// Which packages has this supervisor started from the disk? Read only: `[MARKER, INSTALLED]`, answered with
604    /// `[OK, count, (len, name, running 0/1)*]`. `godspeed list installed`.
605    pub const INSTALLED: u8 = b'L';
606    /// Replace a running installed package with another version WITHOUT STOPPING IT: `[MARKER, HANDOVER, "publisher/name
607    /// version"]`. The supervisor checks the new version, starts it beside the running one, and tells the running one to
608    /// hand over (`gs::handover`). Where either version cannot - it is not running, or a contract does not declare
609    /// `handover` - it stops the old and starts the new, as INSTALL after UNINSTALL would. Answered `[OK, how]`, `how`
610    /// `b'H'` handed over live or `b'R'` restarted, or `[FAILED, reason...]`.
611    pub const HANDOVER: u8 = b'H';
612    /// A device's driver died and was restarted.
613    pub const EV_RESTARTED: u8 = 1;
614    /// A device's driver died and its restart failed.
615    pub const EV_RESTART_FAILED: u8 = 2;
616    /// A USB device was reported attached and its driver started.
617    pub const EV_ATTACHED: u8 = 3;
618    /// A USB device was reported gone and its driver stopped.
619    pub const EV_DETACHED: u8 = 4;
620    /// A device's driver was found dead by the sweep - its death notice never arrived - and restarted.
621    pub const EV_SWEPT: u8 = 5;
622    /// A USB device was reported attached and its driver could not be started.
623    pub const EV_START_FAILED: u8 = 6;
624
625    /// Reply status, one byte, so a caller can log the truth rather than assume success.
626    pub const OK:      u8 = 0;
627    pub const FAILED:  u8 = 1;
628    pub const UNKNOWN: u8 = 2;
629
630    /// Longest command payload: opcode + core + a name and its peers, each at most `NAME_MAX` bytes.
631    pub const MAX: usize = 6 + (super::MAX_SEND_PEERS + 1) * (super::NAME_MAX + 1);
632
633    /// Build `MARKER op <core> name[\0peer]*` into `buf`, returning its length.
634    pub fn encode(buf: &mut [u8; MAX], op: u8, core: u32, name: &str, peers: &[&str]) -> Option<usize> {
635        if name.is_empty() { return None; }
636        buf[0] = MARKER;
637        buf[1] = op;
638        buf[2..6].copy_from_slice(&core.to_le_bytes());
639        let mut n = 6usize;
640        let mut put = |b: &[u8], buf: &mut [u8; MAX], n: &mut usize| -> bool {
641            if *n + b.len() > MAX { return false; }
642            buf[*n..*n + b.len()].copy_from_slice(b);
643            *n += b.len();
644            true
645        };
646        if !put(name.as_bytes(), buf, &mut n) { return None; }
647        for p in peers {
648            if !put(&[0u8], buf, &mut n) || !put(p.as_bytes(), buf, &mut n) { return None; }
649        }
650        Some(n)
651    }
652}
653
654/// A USB host's report to the supervisor about a device it does not drive itself, so the supervisor can
655/// start that device's driver when it arrives and stop it when it leaves (`docs/usb-device-drivers.md`).
656///
657/// The host reports FACTS - present or not, VID:PID, how many times it has bound one - and never names a
658/// driver: which driver a device gets is the supervisor's table. Sent with `try_send` and never answered,
659/// so a host never waits on the supervisor (8.9). Each report is the host's whole state for that device,
660/// not a change, so a duplicate is harmless and a lost one is corrected by the next.
661pub mod usbdev {
662    /// The report's opcode, after `supcmd::MARKER`: `[MARKER, REPORT, present, gen(4 LE), vid(2 LE), pid(2 LE), host]`.
663    pub const REPORT: u8 = b'U';
664    /// Supervisor to host, one byte and no reply capability: send your report again. How a respawned
665    /// supervisor learns what is attached (6.2). In the host's request space beside `usbfn`'s ops (0x20 up),
666    /// clear of them and of every block and net op.
667    pub const ASK: u8 = 0x26;
668    pub const LEN: usize = 12;
669
670    /// Which host a report is from: a PC has two that bind a device for a driver of its own, `xhci` and `ehci`, and
671    /// each reports only what IT has bound - so "nothing attached" from one says nothing of the other's.
672    pub const HOST_DWC2: u8 = 0;
673    pub const HOST_XHCI: u8 = 1;
674    pub const HOST_EHCI: u8 = 2;
675    /// How many host numbers there are: a table indexed by one is this long.
676    pub const HOSTS: usize = 3;
677
678    /// The service a host number names, or `None` for a number that is not one.
679    pub fn host_name(host: u8) -> Option<&'static str> {
680        match host {
681            HOST_DWC2 => Some("dwc2"),
682            HOST_XHCI => Some("xhci"),
683            HOST_EHCI => Some("ehci"),
684            _ => None,
685        }
686    }
687
688    /// One report. `gen` counts every bind on that host, so the same dongle bound again - a replug, or the
689    /// host re-enumerating it - reads differently from the same dongle still bound.
690    #[derive(Clone, Copy, PartialEq, Eq, Debug)]
691    pub struct Report {
692        pub present: bool,
693        pub gen: u32,
694        pub vid: u16,
695        pub pid: u16,
696        /// The host it is from (`HOST_*`).
697        pub host: u8,
698    }
699
700    pub fn encode(r: &Report) -> [u8; LEN] {
701        let mut b = [0u8; LEN];
702        b[0] = super::supcmd::MARKER;
703        b[1] = REPORT;
704        b[2] = r.present as u8;
705        b[3..7].copy_from_slice(&r.gen.to_le_bytes());
706        b[7..9].copy_from_slice(&r.vid.to_le_bytes());
707        b[9..11].copy_from_slice(&r.pid.to_le_bytes());
708        b[11] = r.host;
709        b
710    }
711
712    /// `None` for anything that is not exactly a report.
713    pub fn decode(p: &[u8]) -> Option<Report> {
714        if p.len() != LEN || p[0] != super::supcmd::MARKER || p[1] != REPORT || p[2] > 1 || host_name(p[11]).is_none() {
715            return None;
716        }
717        Some(Report {
718            present: p[2] == 1,
719            gen: u32::from_le_bytes([p[3], p[4], p[5], p[6]]),
720            vid: u16::from_le_bytes([p[7], p[8]]),
721            pid: u16::from_le_bytes([p[9], p[10]]),
722            host: p[11],
723        })
724    }
725}
726
727
728// (Three probe-parameter flags that once rode in the upper bits of `Spawn`'s `arg0` sat here. Nothing
729// set them after the kernel's probe path went, and `Spawn` itself was retired 2026-10-10; deleted the
730// same day, `backlog/fixed/80` K14 and K20.)
731
732/// Upper bound on a spawn name payload (`name` + NUL-separated peers). Matches the kernel's limit.
733pub const SPAWN_PAYLOAD_MAX: usize = 128;
734
735/// The most one boot-record read copies (InspectKernel query 27). Equal to the kernel's
736/// `log::BOOT_READ_CHUNK`; a reader's buffer is exactly this size, so the kernel never writes past it.
737pub const BOOT_READ_CHUNK: usize = 512;
738
739/// MUST match `kernel::task::SERVICE_CTX_MAGIC`. It names the page's LAYOUT, and changes when the layout does.
740const SERVICE_CTX_MAGIC:   u32   = 0xD0_5D_EA_D6;
741/// MUST match `kernel::task::MAX_SEND_PEERS` - this indexes the kernel-written context page.
742const MAX_SEND_PEERS:      usize = 6;
743/// MUST match `kernel::task::PEER_NAME_BYTES`: a send-peer's name in the page the kernel writes.
744const PEER_NAME_BYTES:     usize = NAME_MAX;
745/// The longest name the kernel holds - a task's, a send-peer's, a directory entry's - and so the longest a
746/// program can run under and be reached by. 128 since 2026-10-18: an installed package runs under its
747/// `publisher/name`, which may be that long. It was 24 for a peer and for acquiring by name, and 32 for a
748/// task, and a 26-byte package name was then unreachable by its own clients. Growing it changed the context
749/// page's layout, so its magic changed with it, and a package built before that is refused at start
750/// (`LAYOUT_RELEASE`).
751pub const NAME_MAX:        usize = 128;
752/// The first GodspeedOS release whose program layout this one runs: the context page and `TaskStat`, which
753/// changed when names grew to 128 bytes. A package built for an earlier release would read them wrongly, so
754/// the supervisor refuses to start it and says its publisher must rebuild it.
755pub const LAYOUT_RELEASE:  &str  = "0.29.0";
756
757#[repr(C)]
758struct SendPeerEntry {
759    slot:     u32,
760    name_len: u32,
761    name:     [u8; PEER_NAME_BYTES],
762}
763
764/// Layout of the kernel-written page at SERVICE_CTX_ADDR.
765#[repr(C)]
766struct ServiceContextData {
767    magic:              u32,
768    log_write_slot:     u32,
769    recv_slot:          u32,
770    spawn_slot:         u32,
771    send_peer_count:    u32,
772    core_id:            u32,
773    probe_mode:         u32,
774    console_read_slot:  u32, // u32::MAX = not present
775    xhci_mmio_va:       u64, // 0 = not mapped; else VA of the mapped xHCI BAR
776    xhci_mmio_len:      u64, // length of the mapped MMIO register window in bytes (SEC-4)
777    xhci_dma_va:        u64, // 0 = none; else VA of the driver's DMA arena
778    xhci_dma_phys:      u64, // physical base of the DMA arena
779    xhci_dma_len:       u64, // length of the DMA arena in bytes
780    console_push_slot:  u32, // u32::MAX = none; else CONSOLE_PUSH cap slot
781    self_grant_slot:    u32, // u32::MAX = none; else SEND|GRANT cap to own endpoint (H11)
782    // --- Framebuffer grant (the `console` service only) ---
783    // The kernel maps the display's framebuffer into this service's address space Normal NON-cacheable
784    // + USER, as a driver's MMIO BAR is mapped, and describes it here. Deliberately PIXEL geometry only:
785    // no rows, no columns, no cell size. Character geometry belongs to the terminal, and the terminal is
786    // the service (`docs/console-service.md` §9.7).
787    fb_va:              u64, // 0 = no framebuffer grant; else VA of the mapped framebuffer
788    fb_len:             u64, // length of the mapping in bytes (pitch * height)
789    fb_pitch:           u32, // bytes per scanline
790    fb_width:           u32, // visible width in pixels
791    fb_height:          u32, // visible height in pixels
792    fb_bpp:             u32, // bytes per pixel
793    fb_shifts:          u32, // r_shift | g_shift << 8 | b_shift << 16
794    send_peers:         [SendPeerEntry; MAX_SEND_PEERS],
795    /// A SECOND endpoint, for REPLIES only. `u32::MAX` = none.
796    ///
797    /// A service that serves clients on the endpoint it also awaits replies on cannot drain that
798    /// endpoint while it is blocked for a reply. Sixteen client requests arrive, the queue is full,
799    /// and the reply it is waiting for is DROPPED by a peer that (correctly) uses `try_send` rather
800    /// than deadlocking. The wait then runs to its full deadline - 30 s per block operation on x86,
801    /// which is what made `write append` take 73 seconds.
802    ///
803    /// Correlation tags cannot reach this: a tag identifies a reply that ARRIVED, and this one never
804    /// did. `docs/net-tags-design.md` rejected a second endpoint for lacking a `CreateEndpoint`
805    /// syscall - true, and not needed: the first endpoint is minted at spawn and so is this one.
806    reply_recv_slot:    u32,
807    /// SEND|GRANT cap to `reply_recv_slot`'s endpoint, for handing out as a reply cap. `u32::MAX` = none.
808    reply_grant_slot:   u32,
809    /// The interrupt VECTOR(s) this service was granted, so a driver can learn which one it got.
810    ///
811    /// The kernel used to hand every driver a vector it could also hardcode: `XHCI_MSI_VECTOR` was a
812    /// kernel constant, so both sides knew 0x28 statically and a driver comparing against its own copy
813    /// was correct. The MSI pool (step D1b) makes the vector ALLOCATED AT SPAWN, and that quietly
814    /// invalidated the hardcoded copy: the kernel began routing 0x30 while `services/xhci` still tested
815    /// for 0x28, so it classified every real interrupt as an unknown message and reported `0 MSI, 7799
816    /// msg` on hardware. Nothing broke - the interrupt still WOKE the driver - but the one instrument
817    /// that says whether interrupts work had started reporting the opposite.
818    ///
819    /// The design gap was this field's absence, not the stale constant. A driver already learns its
820    /// MMIO window and DMA arena from the kernel (`ctx.mmio()`, `ctx.dma_region()`) precisely because
821    /// it may not name them; its vector is the same kind of fact and now travels the same way. A
822    /// caller still cannot ASK for a particular vector - that is authority (§12.3) - it can only be
823    /// told which one it was given.
824    irq_count:          u32,      // 0 = no interrupt granted
825    irqs:               [u8; 4],  // the granted vectors; only the first `irq_count` are meaningful
826}
827
828// The kernel writes this struct and the SDK reads it, from two crates, with no shared definition -
829// they are kept in step BY HAND. There was no check on that, and adding a field to one and not the
830// other silently misaligns every field after it: a service would read its neighbour's slot numbers.
831//
832// Pinned by SIZE in both crates. It does not prove field ORDER, but it catches the mistake that
833// actually happens - an append on one side only - and it fails at compile time in the crate that
834// drifted rather than at boot in a service that reads garbage.
835const SERVICE_CONTEXT_DATA_SIZE: usize = 952;   // 320 + irq_count(4) + irqs[4] (D1b); then each of the 6 send-peer names 24 -> 128 bytes (2026-10-18), +624
836const _: () = assert!(
837    core::mem::size_of::<ServiceContextData>() == SERVICE_CONTEXT_DATA_SIZE,
838    "ServiceContextData changed size: update BOTH kernel/src/task/mod.rs and      sdk/rust/src/service_context.rs, then update SERVICE_CONTEXT_DATA_SIZE in both"
839);
840
841
842// ---------------------------------------------------------------------------
843// Dynamic send-cap cache - updated by `reacquire_cap` after EndpointDead.
844// Safe: each service is a single-threaded process with its own BSS.
845// ---------------------------------------------------------------------------
846
847/// How many reacquired peers a service can hold at once.
848///
849/// **It must cover every peer a service names, because a peer that does not fit used to be dropped in
850/// silence.** It was 8. The shell is wired at spawn with `fs` alone and reaches every other peer through
851/// this cache, and it names more than eight - net-stack, wifi-driver, events, time, console, block-driver,
852/// power, supervisor, nic-driver, recorder and more. After a chaos storm the respawned shell's `selfcheck`
853/// filled the cache before reaching the network, so `reacquire_by_name("net-stack")` returned `true` while
854/// storing nothing: every `ping` then found no send slot, reported `net-stack not responding` at once, and
855/// leaked the capability it had just acquired - and killing net-stack could not help (Pi 4, 2026-10-04).
856///
857/// 32 is above the supervisor's whole managed roster. A full cache now EVICTS (see `reacquire_cap_detail`)
858/// and says so, rather than refusing the newcomer quietly.
859const CACHE_SIZE: usize = 32;
860
861/// The next entry to evict when the cache is full - round robin, so no one peer is always the victim.
862// SAFETY: single-threaded service process; no concurrent access.
863static mut CACHE_NEXT_EVICT: usize = 0;
864
865struct CacheEntry {
866    slot:     u32,
867    name_len: u8,
868    name:     [u8; NAME_MAX],
869}
870
871impl CacheEntry {
872    /// ALL ZEROS, and that is load-bearing: an empty entry is one with no name (`name_len == 0`), so the
873    /// whole table is zero and lives in `.bss`, which costs a program's image nothing. It used to mark empty
874    /// with `slot: u32::MAX`, which put the table in `.data` - stored in every program's file - and when names
875    /// grew to 128 bytes that was 4 KiB more in each of the forty-odd services the Pi 2's kernel embeds, and
876    /// pushed that kernel past the context page it must end below.
877    const fn empty() -> Self {
878        CacheEntry { slot: 0, name_len: 0, name: [0u8; NAME_MAX] }
879    }
880}
881
882// SAFETY: single-threaded service process; no concurrent access.
883static mut SEND_CAP_CACHE: [CacheEntry; CACHE_SIZE] =
884    [const { CacheEntry::empty() }; CACHE_SIZE];
885
886// ---------------------------------------------------------------------------
887// TaskStat - returned by ServiceContext::task_stat.
888// ---------------------------------------------------------------------------
889
890/// Snapshot of kernel task state for a single scheduler slot.
891#[derive(Clone, Copy)]
892pub struct TaskStat {
893    /// True if the slot holds a live task.
894    pub valid:       bool,
895    /// Task state: 0=Ready, 1=Running, 2=BlockedOnRecv, 3=BlockedOnSend, 4=Dead.
896    pub state:       u8,
897    /// Core the task is pinned to.
898    pub core:        u8,
899    /// Bytes dynamically allocated so far.
900    pub mem_used:    u64,
901    /// Maximum bytes the task may allocate.
902    pub mem_limit:   u64,
903    /// Byte length of the name stored in `name`.
904    pub name_len:    usize,
905    /// Task name bytes (zero-padded to `NAME_MAX` bytes).
906    pub name:        [u8; NAME_MAX],
907    /// Number of times this service has been restarted (0 on a fresh boot / first spawn, +1 per
908    /// respawn). Saturating u64 - effectively unbounded.
909    pub restart_count: u64,
910    /// Current inbound IPC queue depth (0-16).
911    pub queue_depth: u8,
912    /// Timer ticks spent as the running task on its core (monotonic since boot).
913    pub run_ticks:   u64,
914    /// Seconds since this service last (re)started - resets on restart. Per-service uptime.
915    pub uptime_secs: u64,
916}
917
918impl TaskStat {
919    /// Return the task name as a `&str`.
920    pub fn name_str(&self) -> &str {
921        let len = self.name_len.min(NAME_MAX);
922        core::str::from_utf8(&self.name[..len]).unwrap_or("?")
923    }
924
925    /// Return a short human-readable state label.
926    pub fn state_str(&self) -> &'static str {
927        match self.state {
928            0 => "Ready",
929            1 => "Running",
930            2 => "BlockRecv",
931            3 => "BlockSend",
932            4 => "Dead",
933            _ => "?",
934        }
935    }
936}
937
938/// One held capability, as reported by [`ServiceContext::task_caps`].
939#[derive(Clone, Copy, Default)]
940pub struct CapInfo {
941    /// Resource the cap targets. Stable kernel resources: 1=log_write, 2=spawn,
942    /// 3=console_read, 4=console_push, 5=introspect, 6..=18 the other kernel
943    /// authorities (`kernel/src/capability/mod.rs`, e.g. 6=service_control,
944    /// 8=reboot, 15=image_spawn); larger ids are IPC endpoints, and [4096, 6144)
945    /// is the delegated-resource band (§7.10).
946    pub resource_id: u64,
947    /// Rights bitfield: READ=1, WRITE=2, SEND=4, RECV=8, GRANT=16, REVOKE=32.
948    pub rights: u8,
949}
950
951/// THE KERNEL'S FIXED AUTHORITIES, by resource id: (id, the name a contract uses, what it lets a task do).
952///
953/// One table for everything that names them - the shell's `caps` and `ask "who can reboot?"` - so what a
954/// service is observed to HOLD reads the same everywhere, and in the words its contract DECLARES
955/// (`pci_cfg = true`). The ids are `kernel/src/capability/mod.rs`'s `*_RESOURCE`; anything above 18 is an
956/// IPC endpoint. A resource added there and not here would be shown as an endpoint, which names the
957/// wrong kind of thing (26.9), so the two move together.
958pub const AUTHORITIES: &[(u64, &str, &str)] = &[
959    (1, "log_write", "write to the kernel log and serial"),
960    (2, "spawn", "start services"),
961    (3, "console_read", "read the keyboard"),
962    (4, "console_push", "type into the console as if at the keyboard"),
963    (5, "introspect", "see every task, its capabilities and the kernel's counters"),
964    (6, "service_control", "kill and restart services"),
965    (7, "resource_mint", "mint capabilities for resources it owns, as fs does for files"),
966    (8, "reboot", "reboot the machine"),
967    (9, "acquire_any", "reach any service by name, not only its declared peers"),
968    (10, "net_device", "move raw ethernet frames on an in-kernel network device (none exists; granted to nothing now)"),
969    (11, "gpio_device", "drive the GPIO pins"),
970    (12, "usb_disk", "read and write any sector of an in-kernel USB disk (none exists; granted to nothing now)"),
971    (13, "set_clock", "set the wall clock (granted to nothing now)"),
972    (14, "fire_irq", "inject a test interrupt"),
973    (15, "image_spawn", "start a program from an image it supplies"),
974    (16, "pci_cfg", "read and write PCI configuration space"),
975    (17, "device_power", "cut and restore the power of the device it was granted"),
976    (18, "cpu_clock", "set the Arm clock"),
977];
978
979// ---------------------------------------------------------------------------
980// AllocError - returned by ServiceContext::alloc_mem.
981// ---------------------------------------------------------------------------
982
983/// Error from the AllocMem syscall (6).
984#[derive(Debug, Clone, Copy, PartialEq, Eq)]
985pub enum AllocError {
986    /// Allocation would exceed the task's memory limit (§10.3).
987    Denied,
988    /// Physical memory exhausted or other kernel-side failure.
989    Failed,
990}
991
992// ---------------------------------------------------------------------------
993// ServiceContext.
994// ---------------------------------------------------------------------------
995
996// (2026-10-09) HISTORY, kept as `//` so rustdoc does not hang it on `ServiceContext`. The wait
997// helpers it describes BLOCK again now - each waits in `await_slice`
998// (`RecvTimeout`), or in the kernel's `Call` / `CallDeadline` - so its first sentence is no longer true.
999//
1000// These wait helpers POLL (`try_recv` + `yield_cpu`); they do not block. That is deliberate, and it is
1001// a REVERSAL - they were made to block earlier on this branch, and the change was wrong twice over.
1002//
1003// Why it was tried: a blocked task lets the core reach the scheduler's idle path, which saves power and
1004// (on ARM) lets idle-hook work run while a command waits. Both are real benefits.
1005//
1006// Why it is reverted:
1007// 1. **It broke x86 networking.** net-stack and nic-driver both sit on core 1, and every exchange
1008//    between them goes through `request_with_reply_deadline`. With blocking waits, net-stack degraded
1009//    to "no NIC MAC yet (driver absent/not ready)" and DHCP/ARP never completed; restoring the poll
1010//    made DHCP, ARP and a sustained 14 ms ping work immediately. Proven by an A/B on real hardware -
1011//    identical build, this one difference.
1012// 2. **It bought nothing that survived.** It was introduced to make USB hot-plug get noticed during a
1013//    `ping`. That symptom turned out to be the chaos harness pacing itself with a fixed yield count
1014//    (3000 yields x a full quantum = 30 s on the Pi), not these helpers. The hot-plug fixes that
1015//    actually worked were the hub change-latch and the catch-up sweep, neither of which needs this.
1016//
1017// It also exposed a genuine kernel bug on the way through - the BSP halting onto a consumed one-shot
1018// TSC deadline - which is FIXED and stays fixed independently of this revert.
1019//
1020// The lesson worth keeping: same-core request/reply through a blocking waiter is not exercised by the
1021// test suite (identity and fs-restart are all cross-core), so a change here cannot be validated by
1022// those suites. If blocking is attempted again, it needs a same-core request/reply test first.
1023
1024/// Passed by the kernel to `service_main`. Non-Copy; one per service instance.
1025pub struct ServiceContext {
1026    _private: (),
1027}
1028
1029/// The USB-disk syscalls' "device NAKed, re-ask" status.
1030///
1031/// The kernel claims no USB disk on any port any more (every arch's `usb_disk_sectors()` is 0, and the
1032/// USB stacks are services), so the four `usb_disk_*` syscalls have no caller in the tree; kept as the
1033/// record of their ABI.
1034///
1035/// Outside the capability-error range (-2..-7) ON PURPOSE, and this must stay in step with the kernel's
1036/// `USB_DISK_BUSY` (`kernel/src/syscall/dispatch.rs`). It was originally `-2`, which is `CapNotHeld`, so
1037/// a driver missing its `USB_DISK_RESOURCE` capability was indistinguishable from a busy device and got retried
1038/// thousands of times before being reported as a device that "stayed busy" - a cap failure wearing an
1039/// I/O failure's name.
1040pub const USB_DISK_BUSY: i64 = -20;
1041
1042/// No USB disk is attached - the opposite instruction to [`USB_DISK_BUSY`], and it must stay in step with
1043/// the kernel's `USB_DISK_ABSENT`. BUSY means "come back, I am working"; ABSENT means "there is nothing
1044/// here, and asking again cannot change that - only a hot-plug can". A caller that retries on this waits
1045/// out its entire budget against an empty socket and then reports the device as busy, which is a false
1046/// statement about hardware sitting on the operator's desk.
1047pub const USB_DISK_ABSENT: i64 = -21;
1048
1049/// Can this LBA survive the syscall ABI intact?
1050///
1051/// The ARM ABI gives each syscall argument exactly ONE 32-bit register (`arch/arm/CLAUDE.md`, hazard
1052/// A-U1), so a wider value is silently narrowed on the way in. That matters here more than anywhere:
1053/// the kernel guards `lba >= MSC_SECTORS` and then builds a READ(10)/WRITE(10) CDB, but it guards the
1054/// value it RECEIVED. An LBA of 0x1_0000_0000 arrives as 0, passes the range check, and overwrites the
1055/// GSFS superblock while the device, `block-driver` and `fs` all report success - a range check
1056/// defeated one level above itself, and silent corruption of exactly the block the check exists to
1057/// protect. On x86 the same request is rejected loudly, so the two ports would disagree about what is
1058/// a valid write.
1059///
1060/// Rejecting it in the wrapper is the fix the ABI hazard prescribes (clamp before the syscall, or split
1061/// into a register pair). Nothing legitimate is refused: capacity comes from SCSI READ CAPACITY(10),
1062/// whose last-LBA field is 32 bits wide, so an LBA above `u32::MAX` is a caller bug - and it is
1063/// reported as a failure rather than aliased onto a real block (Invariant 12, failures are loud).
1064#[inline]
1065fn lba_fits_syscall_abi(lba: u64) -> bool { lba <= u32::MAX as u64 }
1066
1067impl ServiceContext {
1068    /// A handle for the PANIC HANDLER, which has no `ctx` to borrow.
1069    ///
1070    /// This type is a zero-sized token - every method reads the kernel-written context block rather
1071    /// than any field - so making one costs nothing and grants nothing that was not already granted.
1072    /// It is `pub(crate)` because the panic path is the only caller that legitimately lacks a context:
1073    /// a service is handed one and must thread it, and an escape hatch for anyone else would let a
1074    /// caller pretend to authority it was never given.
1075    pub(crate) fn for_panic() -> Self {
1076        ServiceContext { _private: () }
1077    }
1078
1079    #[inline]
1080    fn ctx() -> &'static ServiceContextData {
1081        // SAFETY: kernel maps a valid ServiceContextData at SERVICE_CTX_ADDR
1082        // before SYSRETQ into the service; page is read-only and lifetime-stable.
1083        unsafe { &*(SERVICE_CTX_ADDR as *const ServiceContextData) }
1084    }
1085
1086    /// Look up a named capability from this service's cap table.
1087    pub fn capability(&self, name: &str) -> Result<CapHandle, CapError> {
1088        let data = Self::ctx();
1089        if data.magic != SERVICE_CTX_MAGIC { return Err(CapError::CapNotHeld); }
1090        match name {
1091            "log_write" if data.log_write_slot != u32::MAX =>
1092                Ok(CapHandle(data.log_write_slot)),
1093            "spawn" if data.spawn_slot != u32::MAX =>
1094                Ok(CapHandle(data.spawn_slot)),
1095            "recv" if data.recv_slot != u32::MAX =>
1096                Ok(CapHandle(data.recv_slot)),
1097            _ => Err(CapError::CapNotHeld),
1098        }
1099    }
1100
1101    /// Publish `msgs.received = 0` the first time this service WAITS for a message.
1102    ///
1103    /// The third case of the same bug the comment inside `count_recv` records twice. Publishing on the first
1104    /// message covers a service that is quiet; republishing on a sink change covers a sink that
1105    /// restarted. Neither covers a SERVICE that restarted and has not been spoken to since - and that
1106    /// is the ordinary state of `block-driver` after a chaos storm, because nothing sends to it until
1107    /// somebody does disk I/O. Observed on the Wyse: 1917 log lines between
1108    /// `supervisor: block-driver restarted` and a metrics query that found no row for it, with zero
1109    /// messages received in between. The selfcheck caught it as
1110    /// `where owner contains block-driver | assert contains msgs.received`.
1111    ///
1112    /// A row that is absent reads as dead, which is the exact question this metric exists to answer -
1113    /// so an idle live service must have one.
1114    ///
1115    /// THE ZERO IS EARNED, which is the only kind worth printing: the service is running, it has
1116    /// entered its receive loop, and it has genuinely received nothing. That is a fact about the
1117    /// system, not a placeholder standing in for one. Emitted once per instance, from the wait rather
1118    /// than from startup, because reaching the wait is what proves the service got as far as serving.
1119    #[inline]
1120    fn note_listening(&self) {
1121        static ANNOUNCED: core::sync::atomic::AtomicBool =
1122            core::sync::atomic::AtomicBool::new(false);
1123        if !ANNOUNCED.swap(true, core::sync::atomic::Ordering::Relaxed) {
1124            self.metric("msgs.received", 0);
1125        }
1126    }
1127
1128    /// Count one RECEIVED message, and publish the running total every 64.
1129    ///
1130    /// In the SDK rather than in each service, for the same reason trace emission is: it is the same
1131    /// counter in every one of them, and ten hand-placed copies is ten chances to place it wrong. Every
1132    /// receive path funnels here, and only a message that actually ARRIVED is counted - a polling
1133    /// driver calling `try_recv` in a tight loop counts nothing until something comes.
1134    ///
1135    /// WHY THIS ONE IS WORTH HAVING EVERYWHERE. Paired with the `age_s` column, it answers the
1136    /// question an operator actually has when something is wrong: not "how busy is this service" but
1137    /// "is it doing ANYTHING". A `block-driver` whose count last moved forty seconds ago is a
1138    /// different fault from one that is still receiving and failing, and nothing else in the system
1139    /// distinguishes them.
1140    ///
1141    /// Costs a service with no `events` cap one relaxed add and a not-taken branch; `events` itself
1142    /// holds no send cap to itself, so its own publish resolves to `u32::MAX` and returns - the same
1143    /// cut that stops it tracing its own sends. No recursion is possible.
1144    #[inline]
1145    fn count_recv(&self) {
1146        static SERVED: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
1147        const PUBLISH_EVERY: u64 = 64;
1148        let n = SERVED.fetch_add(1, core::sync::atomic::Ordering::Relaxed) + 1;
1149        // THE FIRST MESSAGE PUBLISHES TOO, not just every 64th. Publishing only on the interval meant a
1150        // service that had received fewer than 64 messages had NO ROW AT ALL - and a service with no row
1151        // is indistinguishable from one that is dead, which is precisely the question this metric exists
1152        // to answer. Observed: `block-driver`, `time`, `xhci`, `ehci` and `dwc2` all absent from a
1153        // healthy machine. One extra send per service for the life of the machine.
1154        // ...OR THE SINK JUST CAME BACK. A restarted sink has never heard of this service, and
1155        // waiting out the interval leaves it looking dead for up to 63 more messages - on a service
1156        // that only receives when someone else does I/O, that is however long the machine stays
1157        // quiet. Republishing on the first message after a reacquire is the metric half of "reacquire
1158        // and re-establish" (14.3). Observed: `block-driver` had no row for the whole window after a
1159        // chaos storm restarted the sink, purely because its next 64th message had not arrived.
1160        if n == 1 || n % PUBLISH_EVERY == 0 || crate::trace::take_sink_changed() {
1161            self.metric("msgs.received", n);
1162        }
1163    }
1164
1165    /// Block until a message arrives on this service's primary recv endpoint.
1166    ///
1167    /// **Every failure here now PANICS rather than spinning.** All three exits used to be `loop {}`:
1168    /// a silent, logless, non-yielding tight spin that pegged the core. That is the worst possible
1169    /// response to `EndpointDead`, which is not corruption but an ordinary runtime truth (§8.6) - the
1170    /// service was told its endpoint died and answered by burning a core forever, telling nobody.
1171    ///
1172    /// Panicking is now the RIGHT answer because the panic handler was fixed in the same pass: it
1173    /// logs and faults, so the kernel kills the task, bumps its endpoint generation, wakes any peer
1174    /// blocked in `call` with `ReplyDead`, and the supervisor restarts it. Loud, and recovered.
1175    ///
1176    /// A service that wants to HANDLE the error instead of dying should call `recv_result`.
1177    pub fn recv(&self) -> Message {
1178        self.note_listening();
1179        let data = Self::ctx();
1180        if data.magic != SERVICE_CTX_MAGIC {
1181            panic!("recv: corrupt ServiceContext (bad magic) - the kernel handed us an unusable context");
1182        }
1183        let slot = data.recv_slot;
1184        if slot == u32::MAX {
1185            panic!("recv: no receive endpoint - this service has no ipc_receive in its contract");
1186        }
1187        match crate::ipc::recv(CapHandle(slot)) {
1188            Ok(msg) => { self.count_recv(); msg }
1189            Err(e)  => panic!("recv failed: {:?} - endpoint is gone; dying so the supervisor restarts us", e),
1190        }
1191    }
1192
1193    /// Non-blocking receive on this service's primary recv endpoint: `Some(msg)` if a
1194    /// message was waiting, `None` if the queue is empty. A busy-polling driver uses this
1195    /// to drain interrupt events (§12) each loop iteration without blocking.
1196    ///
1197    /// A receive ERROR on this service's OWN endpoint (it is dead, or the cap was refused) is not an
1198    /// empty queue: the service dies loudly, exactly as [`Self::recv`] does, so the supervisor
1199    /// restarts it. It read as `None` until 2026-10-10, and a loop polling a dead endpoint spun on it
1200    /// forever (`backlog/fixed/80` S5).
1201    pub fn try_recv(&self) -> Option<Message> {
1202        self.note_listening();
1203        let data = Self::ctx();
1204        if data.magic != SERVICE_CTX_MAGIC { return None; }
1205        let slot = data.recv_slot;
1206        if slot == u32::MAX { return None; }
1207        let m = match crate::ipc::try_recv(CapHandle(slot)) {
1208            Ok(m) => m,
1209            Err(e) => panic!("try_recv failed: {:?} - endpoint is gone; dying so the supervisor restarts us", e),
1210        };
1211        if m.is_some() { self.count_recv(); }
1212        m
1213    }
1214
1215    /// Block on this service's recv endpoint until a message arrives or `timeout_cycles`
1216    /// (cycle-counter ticks, the unit of `read_tsc` - convert with `duration_cycles`) elapse:
1217    /// `Some(msg)` = message, `None` = timed out. A receive ERROR on this service's own endpoint dies
1218    /// loudly, as [`Self::recv`] and [`Self::try_recv`] do (`backlog/fixed/80` S5).
1219    /// `timeout_cycles == 0` blocks forever. The kernel turns the count into whole scheduler ticks
1220    /// (at least one), so the bound is never finer than one quantum. A driver uses this to idle on
1221    /// its hardware interrupt while still waking on a timer for auto-repeat (§12 timed-wait).
1222    ///
1223    /// It returns a 4 KiB `Message` by value. It once carried `#[inline(always)]` to save that frame;
1224    /// 8f32a5e9 removed it, because inlining a body that holds a 4 KiB local into a caller that holds
1225    /// one puts both in the same frame.
1226    pub fn recv_timeout(&self, timeout_cycles: u64) -> Option<Message> {
1227        self.note_listening();
1228        let data = Self::ctx();
1229        if data.magic != SERVICE_CTX_MAGIC { return None; }
1230        let slot = data.recv_slot;
1231        if slot == u32::MAX { return None; }
1232        let m = match crate::ipc::recv_timeout(CapHandle(slot), timeout_cycles) {
1233            Ok(m) => m,
1234            Err(e) => panic!("recv_timeout failed: {:?} - endpoint is gone; dying so the supervisor restarts us", e),
1235        };
1236        if m.is_some() { self.count_recv(); }
1237        m
1238    }
1239
1240    /// Re-open the kernel's IOAPIC gate for a level-triggered IRQ `vector` after this driver
1241    /// has cleared its device's interrupt source (§12). The kernel masks a level INTx while the
1242    /// driver handles it (so it can't storm); call this to let it fire again. Only the driver
1243    /// registered for `vector` (via its `hw_interrupt` route) may unmask it. No-op for MSI.
1244    pub fn irq_unmask(&self, vector: u8) {
1245        // SAFETY: syscall(36) = IrqUnmask; gated kernel-side by the route registration.
1246        let _ = unsafe { raw_syscall(36, vector as u64, 0, 0) };
1247    }
1248
1249    /// Block this task for roughly `cycles` TSC cycles, then return (syscall 37). A real sleep:
1250    /// the core can halt while parked, so a poll/wait loop does not busy-`yield` (which pegs the
1251    /// core at ~100% and makes every task on it read as fully busy in `observe`). Like `yield`,
1252    /// needs no capability. A sleep under one quantum (10 ms) uses the arch's sub-tick timer where it
1253    /// has one (`hires_arm`), with a tick backstop; otherwise granularity is one scheduler quantum.
1254    /// Use for UI repaint pacing and "wait for child" loops - not for precise timing. On a 32-bit
1255    /// target `cycles` is clamped to what one register carries, so a long sleep is shortened to the
1256    /// longest the ABI can express rather than wrapping to a short one (`backlog/fixed/80` S5).
1257    pub fn sleep(&self, cycles: u64) {
1258        let cycles = cycles.min(usize::MAX as u64);
1259        // SAFETY: syscall(37) = Sleep; sleeping your own task is unprivileged (like yield).
1260        let _ = unsafe { raw_syscall(37, cycles, 0, 0) };
1261    }
1262
1263    // `net_frame_tx`, `net_frame_rx` and `net_info` (syscalls 42-44, NET_DEVICE) were here until
1264    // 2026-10-11: no port has an in-kernel network device, nothing is granted NET_DEVICE, and nothing
1265    // called them (`backlog/21` step 2). The syscalls themselves are the last step, a kernel change.
1266
1267    /// Block until a message arrives; returns the error instead of looping silently.
1268    pub fn recv_result(&self) -> Result<Message, crate::ipc::IpcError> {
1269        let data = Self::ctx();
1270        if data.magic != SERVICE_CTX_MAGIC { return Err(crate::ipc::IpcError::EndpointDead); }
1271        let slot = data.recv_slot;
1272        if slot == u32::MAX { return Err(crate::ipc::IpcError::EndpointDead); }
1273        crate::ipc::recv(CapHandle(slot))
1274    }
1275
1276    /// Send to a named peer declared in `ipc_send`. Blocking.
1277    pub fn send(&self, peer: &str, msg: &Message) -> Result<(), IpcError> {
1278        let slot = self.find_send_slot(peer).ok_or(IpcError::CapError(CapError::CapNotHeld))?;
1279        crate::ipc::send(CapHandle(slot), msg)
1280    }
1281
1282    /// Non-blocking send; returns `QueueFull` immediately if the queue is full.
1283    pub fn try_send(&self, peer: &str, msg: &Message) -> Result<(), IpcError> {
1284        let slot = self.find_send_slot(peer).ok_or(IpcError::CapError(CapError::CapNotHeld))?;
1285        crate::ipc::try_send(CapHandle(slot), msg)
1286    }
1287
1288    /// Acquire a fresh SEND cap to `peer` via the kernel name directory.
1289    ///
1290    /// Called after `try_send` returns `EndpointDead` (§14.2). Updates the
1291    /// per-service dynamic cap cache so subsequent `try_send` calls use the
1292    /// new slot without going to the kernel again. Every failure collapses to
1293    /// `CapNotHeld`; `reacquire_cap_detail` says which.
1294    pub fn reacquire_cap(&self, peer: &str) -> Result<CapHandle, CapError> {
1295        self.reacquire_cap_detail(peer).map_err(|_| CapError::CapNotHeld)
1296    }
1297
1298    /// `reacquire_cap`, but saying WHICH failure - see [`AcquireFailure`].
1299    ///
1300    /// Use this wherever the answer changes what the caller does or prints. The plain
1301    /// `reacquire_cap`/`reacquire_by_name` collapse every failure to one value, which is fine for a
1302    /// caller that only retries, and wrong for one that reports: a message naming two possible causes
1303    /// and committing to neither is a message that sends the reader to the wrong service (§26.7).
1304    pub fn reacquire_cap_detail(&self, peer: &str) -> Result<CapHandle, AcquireFailure> {
1305        let bytes = peer.as_bytes();
1306        let len   = bytes.len();
1307        if len == 0 || len > NAME_MAX { return Err(AcquireFailure::Refused); }
1308
1309        // SAFETY: syscall(10) = AcquireSendCap; peer bytes are in user space.
1310        let ret = unsafe {
1311            raw_syscall(10, bytes.as_ptr() as u64, len as u64, 0)
1312        };
1313        if ret < 0 { return Err(AcquireFailure::from_code(ret)); }
1314        let new_slot = ret as u32;
1315
1316        // Update the dynamic cache. Reuse this peer's EXISTING entry if it has one (and reclaim its
1317        // now-stale cap), otherwise take a free slot. Reclaiming the old cap is essential: without it
1318        // every restart-reacquire orphans the previous cap, and a storm (e.g. `chaos max-carnage`)
1319        // fills the cap table until `derive_cap`/`acquire_send_cap` start returning None - which shows
1320        // up as "storage unavailable" (fs) and "never registered" (pipe filters). Searching the peer's
1321        // entry first also prevents creating a duplicate entry when a free slot precedes it.
1322        // SAFETY: single-threaded service; no concurrent cache writes. addr_of_mut! avoids a direct
1323        // &mut to the static (static_mut_refs lint).
1324        //
1325        // NEVER DROP THE NEW CAP. If the peer has no entry and no entry is free, an existing one is
1326        // evicted - its cap reclaimed, its peer reacquired on next use - and the eviction is logged. This
1327        // used to fall through doing nothing and still return Ok: the caller believed the peer was
1328        // reacquired, `find_send_slot` could not find it, and the cap just acquired leaked (CACHE_SIZE).
1329        let mut stale: Option<u32> = None;
1330        let mut evicted: Option<([u8; NAME_MAX], usize)> = None;
1331        let mut placed = false;
1332        unsafe {
1333            let cache = &mut *core::ptr::addr_of_mut!(SEND_CAP_CACHE);
1334            for entry in cache.iter_mut() {
1335                if entry.name_len as usize == len && &entry.name[..len] == bytes {
1336                    if entry.slot != new_slot { stale = Some(entry.slot); }
1337                    entry.slot = new_slot;
1338                    placed = true;
1339                    break;
1340                }
1341            }
1342            if !placed {
1343                let free = cache.iter().position(|e| e.name_len == 0);
1344                let i = match free {
1345                    Some(i) => i,
1346                    None => {
1347                        let next = &mut *core::ptr::addr_of_mut!(CACHE_NEXT_EVICT);
1348                        let i = *next % CACHE_SIZE;
1349                        *next = next.wrapping_add(1);
1350                        stale = Some(cache[i].slot);
1351                        evicted = Some((cache[i].name, cache[i].name_len as usize));
1352                        i
1353                    }
1354                };
1355                let entry = &mut cache[i];
1356                entry.slot     = new_slot;
1357                entry.name_len = len as u8;
1358                entry.name     = [0u8; NAME_MAX];
1359                entry.name[..len].copy_from_slice(bytes);
1360            }
1361        }
1362        if let Some(old) = stale { self.remove_cap(CapHandle(old)); }
1363        if let Some((name, nlen)) = evicted {
1364            let gone = core::str::from_utf8(&name[..nlen.min(NAME_MAX)]).unwrap_or("?");
1365            self.log_fmt(format_args!(
1366                "sdk: the send-cap cache is full ({} peers) - dropped the cap to '{}' to hold '{}'; '{}' is reacquired when next used",
1367                CACHE_SIZE, gone, peer, gone));
1368        }
1369
1370        Ok(CapHandle(new_slot))
1371    }
1372
1373    /// Derive a duplicate of a capability this service holds **with the GRANT right**
1374    /// into a fresh slot (syscall 29 = `DeriveCap`). The copy carries the same
1375    /// resource, generation, and (non-widened) rights.
1376    ///
1377    /// Used to hand out many copies of a held endpoint cap: derive a copy per
1378    /// recipient and grant that copy away (via `send_with_cap_by_handle`) while
1379    /// keeping the original. Returns `None` if the cap lacks GRANT, is stale, or the
1380    /// cap table is full.
1381    pub fn derive_cap(&self, held: CapHandle) -> Option<CapHandle> {
1382        // SAFETY: syscall(29) = DeriveCap; `held.0` is a slot index into this task's
1383        // own cap table. The kernel validates GRANT + generation before duplicating.
1384        let ret = unsafe { raw_syscall(29, held.0 as u64, 0, 0) };
1385        if ret < 0 { None } else { Some(CapHandle(ret as u32)) }
1386    }
1387
1388    /// Return the probe mode written by the kernel at spawn (0 for all production services).
1389    pub fn probe_mode(&self) -> u32 { Self::ctx().probe_mode }
1390
1391    /// The REPLY mailbox: `(recv, grant)` for the endpoint that exists only to receive replies, or
1392    /// `None` on a task that has none (then the caller uses the shared endpoint, as before).
1393    ///
1394    /// Awaiting a reply on the endpoint you also SERVE means you cannot drain client traffic while
1395    /// you wait; the queue fills and your own reply is dropped. Replies come here instead, where
1396    /// nothing else is ever sent.
1397    fn reply_mailbox(&self) -> Option<(crate::capability::CapHandle, crate::capability::CapHandle)> {
1398        let d = Self::ctx();
1399        if d.reply_recv_slot == u32::MAX || d.reply_grant_slot == u32::MAX { return None; }
1400        Some((crate::capability::CapHandle(d.reply_recv_slot),
1401              crate::capability::CapHandle(d.reply_grant_slot)))
1402    }
1403
1404    /// Discard replies left over from requests this service already gave up on. Returns how many.
1405    ///
1406    /// **A DETECTED DESYNC HAS TO BE REPAIRABLE, and this is the repair.** `fs` tags every block
1407    /// request and checks the tag on the reply, so a stale reply is always DETECTED - and detection
1408    /// was all there was. The mismatched reply was discarded and the request failed, which leaves this
1409    /// service's own reply still queued for the NEXT request to find. Every subsequent request then
1410    /// receives its predecessor's reply, discards it, fails, and queues another: permanently one reply
1411    /// out of phase, alternating forever, with the volume mounted and every operation failing.
1412    ///
1413    /// Observed 133 times in one `osdev test peer-storm` run, still alternating after the storm had
1414    /// stopped. The code that discarded expected the queue to drain on its own - "the caller retries
1415    /// at its own level, by which point the queue has drained" - and it never does, because each
1416    /// retry consumes exactly one stale reply and contributes exactly one more.
1417    ///
1418    /// WHY DRAINING IS SAFE HERE, which is the whole reason this is in the SDK rather than hand-rolled
1419    /// at a call site: it drains the dedicated REPLY MAILBOX, an endpoint that carries nothing but
1420    /// replies to calls this service made. A caller that is synchronous - one outstanding request at a
1421    /// time, which is what `request_with_reply*` enforces - can have nothing legitimate waiting there
1422    /// at the moment it is about to issue a new request. Anything present is a reply it stopped
1423    /// waiting for.
1424    ///
1425    /// **It refuses to drain the SHARED endpoint**, and that refusal is load-bearing. A service
1426    /// without a reply mailbox awaits replies on the endpoint its CLIENTS send to, so draining there
1427    /// would silently eat client requests - the precise hazard the `CallDeadline` amendment (§8.2)
1428    /// exists to describe. No mailbox, no drain, and the caller keeps the behaviour it had.
1429    ///
1430    /// SO THIS REPAIR IS CONDITIONAL, and the condition is not theoretical. A reply mailbox is an
1431    /// OPTIONAL endpoint (`routing::try_register_optional`): it is refused when the routing table is
1432    /// near full, and a bare-metal T630 refuses three times during `selfcheck`. A service refused one
1433    /// falls back to its shared endpoint and silently loses this repair - which is correct, because
1434    /// the alternative is eating client traffic, but it means "the desync is fixed" holds only where
1435    /// the mailbox was granted.
1436    ///
1437    /// Measured rather than assumed: an arm32 Pi 2 run logged ZERO refusals, so `fs` had its mailbox
1438    /// and the repair was live on that port. `spawn[ipc]: '<name>' gets no reply mailbox` in a log
1439    /// names each service in the other case (it read `routing: reply endpoint refused`, unnamed, until
1440    /// `backlog/74`).
1441    pub fn drain_stale_replies(&self) -> usize {
1442        let Some((recv, _)) = self.reply_mailbox() else { return 0 };
1443        let mut n = 0usize;
1444        // Bounded by the endpoint's own depth (§8.5): a queue that cannot hold more than 16 cannot
1445        // yield more than 16, so this terminates without needing to trust the peer.
1446        for _ in 0..16 {
1447            match crate::ipc::try_recv(recv) {
1448                Ok(Some(_)) => { self.release_discarded_cap(); n += 1 }
1449                _ => break,
1450            }
1451        }
1452        n
1453    }
1454
1455    /// [`Self::drain_stale_replies`] at the top of a request, and SAID when it found anything - the one
1456    /// thing every request path that waits on the reply mailbox must do before it issues.
1457    ///
1458    /// It was done by ONE of the three such paths. `request_with_reply_deadline_into` drained (it is
1459    /// where `backlog/fixed/37` was fixed); `request_with_reply_call_err`, which `gs::call` and so every `gs::fs`
1460    /// call goes through, and `request_with_reply_deadline_outcome_into` did not. So the exact failure
1461    /// [`Self::drain_stale_replies`] describes was still live for every service on the standard library:
1462    /// in the jobs suite the shell's history save timed out while a background `drives check` held `fs`
1463    /// for five seconds, its reply arrived late, and from then on every shell request to `fs` received
1464    /// its predecessor's reply and rejected it as malformed - `churn verify` failing as "storage
1465    /// unavailable" and every later history save lost (2026-10-11). One helper now, so a path cannot
1466    /// have the drain without the log or the log without the drain.
1467    fn drain_before_request(&self, peer: &str) {
1468        let dropped = self.drain_stale_replies();
1469        if dropped > 0 {
1470            // LOUD, because it means a previous request was abandoned and this one would have
1471            // inherited its reply. Silent self-repair is how the fs desync stayed invisible for so
1472            // long (§26.7 - a recovery that hides what it recovered from is a silent fallback).
1473            self.log_fmt(format_args!(
1474                "sdk: discarded {} abandoned repl(y/ies) before a request to `{}` - an earlier request timed out",
1475                dropped, peer));
1476        }
1477    }
1478
1479    /// Give back a capability a DISCARDED message may have carried. A received `Message` does not
1480    /// record its caps; the kernel queues them for `take_pending_cap`. Every caller claims a cap
1481    /// straight after its own receive, so what is pending while a drain runs came with the message it
1482    /// is discarding - and was leaked one table slot at a time until 2026-10-10 (`backlog/fixed/80` S5).
1483    fn release_discarded_cap(&self) {
1484        if let Some(c) = self.take_pending_cap() {
1485            self.remove_cap(c);
1486        }
1487    }
1488
1489    /// Wait, bounded by `max_ms`, for up to `owed` replies to arrive in the reply MAILBOX, and discard
1490    /// them. Returns how many were discarded, or `None` when this service has no mailbox - in which case
1491    /// owed-reply accounting cannot work (replies land on the shared endpoint) and the caller must fall
1492    /// back. For a synchronous caller that gave up on `owed` requests whose answers are still in flight:
1493    /// `drain_stale_replies` clears what has ARRIVED; this also waits for what has not.
1494    pub fn drain_owed_replies(&self, owed: usize, max_ms: u64) -> Option<usize> {
1495        let (recv, _) = self.reply_mailbox()?;
1496        let t0 = self.read_tsc();
1497        let limit = self.duration_cycles(max_ms);
1498        let mut n = 0usize;
1499        while n < owed {
1500            match crate::ipc::try_recv(recv) {
1501                Ok(Some(_)) => { self.release_discarded_cap(); n += 1 }
1502                _ => {
1503                    if self.read_tsc().wrapping_sub(t0) >= limit {
1504                        break;
1505                    }
1506                    self.sleep_ms(10);
1507                }
1508            }
1509        }
1510        Some(n)
1511    }
1512
1513    /// Return the recv cap handle for direct-handle use (e.g. wrong-right test probing).
1514    pub fn recv_handle(&self) -> Option<crate::capability::CapHandle> {
1515        let slot = Self::ctx().recv_slot;
1516        if slot == u32::MAX { None } else { Some(crate::capability::CapHandle(slot)) }
1517    }
1518
1519    // (A stray doc block for `request_with_reply` sat here, attached by rustdoc to `trace_as` below. It
1520    // is a plain comment now; the function's own doc is at `request_with_reply`.)
1521    //
1522    // `request_with_reply` embeds a per-request reply cap - a `SEND|GRANT` copy of this service's own
1523    // endpoint cap - so the server can reply via `take_pending_cap()` + `send_by_handle()` (§8). Its
1524    // wait is a synchronous kernel CALL (syscall 41): if `peer` dies after receiving the request but
1525    // before replying, the kernel wakes the caller with `ReplyDead` (§8.6) instead of hanging it. It
1526    // returns `None` for that and for a failed send alike, which is exactly the distinction a caller
1527    // needs: `ReplyDead` means the request was DELIVERED and may have been acted on, so re-sending is
1528    // safe only for an operation that is safe to repeat. A service uses `gs::call::request_within`,
1529    // which keeps the two apart (`Error::PeerDied`, never re-sent, against `Error::Unreachable`).
1530    #[inline]
1531    /// Declare this service's own name for the trace ring, once, at startup.
1532    ///
1533    /// A service cannot ask what it is called - identity is not ambient - so a traced service says.
1534    /// Costs nothing in trust: the whole event is already the emitter's testimony (see `crate::trace`).
1535    /// A service that never calls this still traces; its events read `?` in the caller column, which
1536    /// is the honest answer rather than a guess.
1537    pub fn trace_as(&self, name: &str) {
1538        // A TRUNCATED SERVICE NAME IS REPORTED, not swallowed. `PEER_LEN` is 12 - sized for
1539        // `block-driver` - and `hw-enumerator` is 13, so it appears everywhere as `hw-enumerato`.
1540        // Readable, but it is still a name silently becoming a different name, and two services
1541        // sharing a 12-byte prefix would merge into ONE metric row with their counters interleaving
1542        // (the exact failure an unnamed service caused before every service declared itself).
1543        //
1544        // Widening `PEER_LEN` was the other option and was rejected on cost: the trace DUMP is bounded
1545        // by one 4 KiB message, so a 16-byte field drops it from 110 events per screen to 95. Trading
1546        // real scrollback in the tool for four characters of a name is the wrong way round. Say it
1547        // once instead, so the fix - a shorter name - is the developer's to make.
1548        if name.len() > crate::trace::PEER_LEN {
1549            self.log_fmt(format_args!(
1550                "sdk: service name '{}' exceeds {} bytes and is TRUNCATED in traces and metrics - shorten it, or two services could share one row",
1551                name, crate::trace::PEER_LEN));
1552        }
1553        crate::trace::set_caller(name);
1554    }
1555
1556    /// Declare that requests to `peer` carry their opcode at byte `at` (default 0).
1557    ///
1558    /// For protocols that prepend a correlation tag - `shell -> fs` and `fs -> block-driver` both do -
1559    /// byte 0 is a request id, so a trace that recorded it was showing noise in a column labelled
1560    /// "op". The SDK is generic and cannot know; the service that owns the protocol can. See
1561    /// `crate::trace::set_op_offset`.
1562    pub fn trace_op_at(&self, peer: &str, at: u8) { crate::trace::set_op_offset(peer, at); }
1563
1564    /// Emit one trace event, or do nothing at all.
1565    ///
1566    /// The whole cost when this service is not tracing is the `trace::should_resolve` and
1567    /// `trace::sink_slot` loads and a not-taken branch. When it IS tracing, the send is `try_send` and its result is DISCARDED on purpose: an
1568    /// observer must never be able to slow, block, or fail the thing it observes. A full events queue
1569    /// loses one event, which the ring counts and `events status` reports - a visible loss, not a
1570    /// silent one (invariant 12).
1571    ///
1572    /// Recursion is cut at the source: `events` itself holds no `events` send cap, so the sink cannot trace
1573    /// its own sends, and no event can beget another.
1574    fn trace_emit(&self, peer: &str, op: u8, kind: u8) {
1575        // Arm lazily, ONCE. A service is handed a context with no init hook to run in, so there is
1576        // nowhere else to resolve from. A service whose contract does not grant `ipc_send =
1577        // ["events"]` records `u32::MAX` here and never looks again: one relaxed load per call for the
1578        // rest of its life, which is the cost the untraced majority pays.
1579        if crate::trace::should_resolve() {
1580            let mut slot = self.find_send_slot(crate::trace::SINK_NAME).unwrap_or(u32::MAX);
1581            if slot == u32::MAX {
1582                // NO CAP WIRED AT SPAWN - ASK THE KERNEL FOR ONE. `find_send_slot` only inspects caps
1583                // this service ALREADY HOLDS, so a service spawned during the milliseconds `events`
1584                // was restarting has nothing to find, resolves to MAX, and returns early on every
1585                // emission for the rest of its life. The reacquire that would fix it sits behind a
1586                // send FAILURE - and a send that never happens never fails, so it was unreachable.
1587                //
1588                // Measured on the T630: after a storm that restarted the sink ~48 times, `fs` and
1589                // `block-driver` published NOTHING for the remaining 9.4 s of the run, while the
1590                // sink's own LOCALLY-written rows were all present. That split - local rows yes, every
1591                // IPC-delivered row no - is the signature of a publisher whose message never left.
1592                //
1593                // `AcquireSendCap` is authorised by the DECLARATION, which the kernel keeps now even
1594                // when the peer was down at spawn. This is the call that spends it (14.3).
1595                if self.reacquire_by_name(crate::trace::SINK_NAME) {
1596                    slot = self.find_send_slot(crate::trace::SINK_NAME).unwrap_or(u32::MAX);
1597                }
1598            }
1599            crate::trace::set_sink_slot(slot);
1600            // AND SAY IT, IF IT TOOK MORE THAN ONE TRY. This report was written once already and
1601            // silently lost: the edit that added it failed to apply and only its trace.rs half landed,
1602            // so `take_late_resolve` sat with no caller and the hardware run reported "0 late
1603            // resolves" - a zero that measured nothing. A missing instrument reads exactly like a
1604            // healthy system, which is the worst way for one to fail.
1605            if slot != u32::MAX {
1606                if let Some(missed) = crate::trace::take_late_resolve() {
1607                    if missed > 1 {
1608                        self.log_fmt(format_args!(
1609                            "sdk: events sink resolved LATE - {} emission(s) went nowhere first",
1610                            missed - 1));
1611                    }
1612                }
1613            }
1614        }
1615        let slot = crate::trace::sink_slot();
1616        if slot == u32::MAX {
1617            return;
1618        }
1619        // Do NOT record a call to the SINK ITSELF. The `trace` utility reads the ring by asking
1620        // `events` for it, so tracing that call would fill the ring with the reader's own questions -
1621        // each dump adding two more events, pushing out the traffic the reader came to see. An
1622        // instrument that mostly measures itself is not measuring anything. This hides nothing: every
1623        // OTHER peer is still recorded, and `events status` still counts every event accepted.
1624        if peer == crate::trace::SINK_NAME {
1625            return;
1626        }
1627        // NO CLOCK READ HERE. `epoch_secs_monotonic` is a full CMOS RTC read: `wait_update_clear`
1628        // spins until the update-in-progress flag clears (up to ~1 ms), then seven port-I/O reads,
1629        // repeated until two agree. Two of those per request/reply turned `dir`, `move`, `find` and tab
1630        // completion into TIMEOUTS, and cost the shell so much time inside the kernel that it stopped
1631        // draining the console and lost Enter keystrokes. Measured, not guessed: `osdev test files`
1632        // was 222/0 before, 213/9 with the clock read, 222/0 again without it.
1633        //
1634        // The SINK stamps the event instead. `events` has to wake to receive it either way, so the
1635        // cost lands on the service whose job this is rather than on every service that talks to
1636        // anyone - and the caller already used `try_send` and did not wait. An observer must not be
1637        // able to slow the thing it observes, and a millisecond of port I/O per IPC is exactly that.
1638        let ev = crate::trace::encode(peer, op, kind);
1639        if self.try_send_by_handle(CapHandle(slot), &crate::ipc::Message::from_bytes(&ev)).is_err() {
1640            // THE SINK RESTARTED - REACQUIRE IT. The slot is resolved once and cached, so a `events`
1641            // respawn left every emitter holding a stale generation FOREVER: tracing silently stopped,
1642            // `events ipc` could not reach the ring, and each emission logged a kernel gen-mismatch.
1643            // Seen on hardware after a chaos storm restarted `events` forty times - cap generation 985
1644            // against a live record of 1025.
1645            //
1646            // This is the ordinary reacquire-by-name recovery every client owes a restartable peer
1647            // (14.3); the trace path just never did it. Only on FAILURE, so the healthy path is
1648            // unchanged, and if the sink is genuinely down the next emission tries again.
1649            //
1650            // AND RETRY ON THE FRESH SLOT. Reacquiring but dropping the event that prompted it is a
1651            // recovery that discards its own result (26.7) - the peer is reachable again and the
1652            // sample is thrown away anyway. 14.3 says reacquire AND retry; this used to do half.
1653            if self.reacquire_by_name(crate::trace::SINK_NAME) {
1654                if let Some(fresh) = self.find_send_slot(crate::trace::SINK_NAME) {
1655                    crate::trace::set_sink_slot(fresh);
1656                    let _ = self.try_send_by_handle(
1657                        CapHandle(fresh),
1658                        &crate::ipc::Message::from_bytes(&ev),
1659                    );
1660                }
1661            }
1662        }
1663    }
1664
1665    /// Publish a metric sample to `events`. Fire-and-forget, and never able to slow this service down.
1666    ///
1667    /// The value is a SET, not an increment: this service owns the counter (it is this service's own
1668    /// state, §3.8) and publishes what it currently reads; `events` remembers the last sample. So a
1669    /// service that dies leaves its final value behind rather than taking it along, which is the one
1670    /// useful thing an observer can still learn from a service that is gone.
1671    ///
1672    /// Shaped exactly like `trace_emit`, and the shape is the point:
1673    /// - `try_send`, result DISCARDED. An observer must never slow, block or fail the thing it
1674    ///   observes. A full sink loses one sample, which the sink counts and reports (invariant 12).
1675    /// - No clock read here. The sink stamps it; a CMOS RTC read on every publish once cost the shell
1676    ///   its keystrokes.
1677    /// - Reacquire on failure only, so the healthy path is unchanged and a restarted sink does not
1678    ///   leave every emitter holding a stale generation forever (14.3).
1679    ///
1680    /// SELF-OBSERVATION NEEDS NO CHECK HERE, and that is deliberate. `events` holds no send cap to
1681    /// `events`, so this resolves to `u32::MAX` inside its own address space and returns - the same cut
1682    /// that stops the sink tracing its own sends. A service reporting on itself must never take an IPC
1683    /// hop, because the hop is itself a reportable event and the ring would fill with itself. `events`
1684    /// counts its own work locally instead (`docs/observability.md` 9).
1685    pub fn metric(&self, name: &str, value: u64) {
1686        // FUNCTION-LOCAL, so the latch is owned by the only code that reads it (§3.8, invariant 9).
1687        // A one-shot: it never resets, because the point is to say it once for the life of the service.
1688        static TRUNCATED_SAID: core::sync::atomic::AtomicBool =
1689            core::sync::atomic::AtomicBool::new(false);
1690        if crate::trace::should_resolve() {
1691            let mut slot = self.find_send_slot(crate::trace::SINK_NAME).unwrap_or(u32::MAX);
1692            if slot == u32::MAX {
1693                // NO CAP WIRED AT SPAWN - ASK THE KERNEL FOR ONE. `find_send_slot` only inspects caps
1694                // this service ALREADY HOLDS, so a service spawned during the milliseconds `events`
1695                // was restarting has nothing to find, resolves to MAX, and returns early on every
1696                // emission for the rest of its life. The reacquire that would fix it sits behind a
1697                // send FAILURE - and a send that never happens never fails, so it was unreachable.
1698                //
1699                // Measured on the T630: after a storm that restarted the sink ~48 times, `fs` and
1700                // `block-driver` published NOTHING for the remaining 9.4 s of the run, while the
1701                // sink's own LOCALLY-written rows were all present. That split - local rows yes, every
1702                // IPC-delivered row no - is the signature of a publisher whose message never left.
1703                //
1704                // `AcquireSendCap` is authorised by the DECLARATION, which the kernel keeps now even
1705                // when the peer was down at spawn. This is the call that spends it (14.3).
1706                if self.reacquire_by_name(crate::trace::SINK_NAME) {
1707                    slot = self.find_send_slot(crate::trace::SINK_NAME).unwrap_or(u32::MAX);
1708                }
1709            }
1710            crate::trace::set_sink_slot(slot);
1711            // AND SAY IT, IF IT TOOK MORE THAN ONE TRY. This report was written once already and
1712            // silently lost: the edit that added it failed to apply and only its trace.rs half landed,
1713            // so `take_late_resolve` sat with no caller and the hardware run reported "0 late
1714            // resolves" - a zero that measured nothing. A missing instrument reads exactly like a
1715            // healthy system, which is the worst way for one to fail.
1716            if slot != u32::MAX {
1717                if let Some(missed) = crate::trace::take_late_resolve() {
1718                    if missed > 1 {
1719                        self.log_fmt(format_args!(
1720                            "sdk: events sink resolved LATE - {} emission(s) went nowhere first",
1721                            missed - 1));
1722                    }
1723                }
1724            }
1725        }
1726        let slot = crate::trace::sink_slot();
1727        if slot == u32::MAX {
1728            return;
1729        }
1730        // A TRUNCATED NAME IS A DIFFERENT NAME. Said once per service, not per sample: repeating it
1731        // would flood the channel the warning has to be read on, and one line names the offender well
1732        // enough to fix it. Silence here would mean two metrics quietly merging into one row under a
1733        // shared prefix, with the numbers interleaving and nothing to say why (invariant 12).
1734        if name.len() > crate::trace::MET_NAME_LEN
1735            && !TRUNCATED_SAID.swap(true, core::sync::atomic::Ordering::Relaxed)
1736        {
1737            self.log_fmt(format_args!(
1738                "sdk: metric name '{}' is longer than {} bytes and was TRUNCATED - shorten it, or two metrics will share one row",
1739                name, crate::trace::MET_NAME_LEN));
1740        }
1741        let m = crate::trace::encode_metric(name, value);
1742        if self
1743            .try_send_by_handle(CapHandle(slot), &crate::ipc::Message::from_bytes(&m))
1744            .is_err()
1745        {
1746            // AND RETRY, for the reason given on the trace path above. It matters MORE here: a
1747            // publisher samples on an INTERVAL (`fs` every 32 requests), so a dropped sample is not
1748            // retried for another whole interval - and after a sink restart that meant TWO intervals
1749            // with no row for that service at all. A service with no row is indistinguishable from a
1750            // dead one, which is the single question this metric exists to answer.
1751            // THE FAILING BRANCH IS NAMED, NOT COLLAPSED TO A BOOL. This used to be
1752            // `if reacquire_by_name(..)`, so a reacquire that FAILED did nothing and said nothing -
1753            // the sample was dropped and the service simply stopped publishing, with no symptom but a
1754            // row that never appeared. The three causes are not alike and the difference is the whole
1755            // diagnosis: NameNotRegistered is transient (the sink is mid-restart, try again next
1756            // interval), while CapTableFull is TERMINAL until this service restarts - it will never
1757            // publish again, and that deserves to be said out loud once rather than discovered as an
1758            // absence. Invariant 12, and 26.7's "a recovery that itself fails is still a failure".
1759            // ONE LATCH PER SEVERITY, and the wording matches the severity. A single latch was
1760            // wrong twice over: it said "metrics stop here" for the TRANSIENT case (the sink is
1761            // simply mid-restart - the next interval reconnects, and a kill-storm produces this by
1762            // the dozen), and having latched on that it would then stay silent about a genuinely
1763            // TERMINAL cause arriving later. Observed on the T630: 17 of these during a 100-round
1764            // storm, every one of them transient and every one of them overstating the damage.
1765            static ACQ_TRANSIENT_SAID: core::sync::atomic::AtomicBool =
1766                core::sync::atomic::AtomicBool::new(false);
1767            static ACQ_TERMINAL_SAID: core::sync::atomic::AtomicBool =
1768                core::sync::atomic::AtomicBool::new(false);
1769            let acq = self.reacquire_cap_detail(crate::trace::SINK_NAME);
1770            if let Err(why) = acq {
1771                let transient = matches!(why, AcquireFailure::NameNotRegistered);
1772                let said = if transient { &ACQ_TRANSIENT_SAID } else { &ACQ_TERMINAL_SAID };
1773                if !said.swap(true, core::sync::atomic::Ordering::Relaxed) {
1774                    if transient {
1775                        self.log("sdk: events sink not registered right now - samples dropped until it is back");
1776                    } else {
1777                        self.log_fmt(format_args!(
1778                            "sdk: cannot reacquire the events sink ({}) - metrics from this service stop here",
1779                            why.reason()));
1780                    }
1781                }
1782            }
1783            if acq.is_ok() {
1784                if let Some(fresh) = self.find_send_slot(crate::trace::SINK_NAME) {
1785                    crate::trace::set_sink_slot(fresh);
1786                    // A RETRY THAT STILL FAILS IS ITS OWN OUTCOME (26.7): the reacquire worked, so
1787                    // this is not "the sink is down" - it is a fresh cap that still would not take the
1788                    // sample. Said once per service; silence here would look identical to success.
1789                    static RETRY_FAILED_SAID: core::sync::atomic::AtomicBool =
1790                        core::sync::atomic::AtomicBool::new(false);
1791                    if self
1792                        .try_send_by_handle(CapHandle(fresh), &crate::ipc::Message::from_bytes(&m))
1793                        .is_err()
1794                        && !RETRY_FAILED_SAID.swap(true, core::sync::atomic::Ordering::Relaxed)
1795                    {
1796                        self.log("sdk: events sink reacquired but the retried sample still failed");
1797                    }
1798                }
1799            }
1800        }
1801    }
1802
1803    // ---------------------------------------------------------------------------
1804    // The request/reply family: each wraps its `_inner` implementation with one
1805    // trace event on the way in and one on the way out.
1806    // ---------------------------------------------------------------------------
1807    //
1808    // THESE ARE WRAPPERS AND NOT EDITS TO EACH BODY, deliberately. There are a dozen of these now (it
1809    // was eight), most an independent implementation with several early returns inside a wait loop;
1810    // instrumenting the bodies means finding every exit in each function and getting all of them right, forever. A
1811    // wrapper cannot miss one - the value returned IS the outcome.
1812    //
1813    // Instrumenting them ALL is also deliberate. The first cut traced only `request_with_reply`, and
1814    // on hardware that produced an EMPTY ring while the shell was busily talking to `fs` - because the
1815    // shell uses `_abortable` and `_deadline`, not the plain one. Partial instrumentation is worse
1816    // than none: `events ipc` then shows SOME traffic and silently omits the rest, with nothing on
1817    // screen saying which. That is a silent gap in the instrument, which is the exact failure this
1818    // ring exists to prevent (26.4, invariant 12).
1819    //
1820    // `op` is byte 0 of OUR OWN message. A service is entitled to interpret its own protocol; the
1821    // kernel is not, and that asymmetry is the whole reason this lives here rather than at the
1822    // routing point (4.4, 26.10).
1823
1824    // `#[inline]` on every wrapper below is LOAD-BEARING, not decoration. `Message` is 4 KiB by
1825    // value, so an un-inlined wrapper adds a whole extra payload move to each request - and the shell
1826    // paths that do many round trips (tab completion listing a directory, `move`, `find` descending)
1827    // went from passing to timing out on it. Measured, not assumed: `osdev test files` was 222/0
1828    // before the wrappers, 213/9 after, and 213/9 again with tracing DISABLED - so the cost was the
1829    // restructuring, never the emission.
1830    #[inline]
1831    fn trace_out(&self, peer: &str, op: u8, kind: u8) { self.trace_emit(peer, op, kind); }
1832
1833    /// The op byte of an outgoing request. NO EVENT IS EMITTED HERE.
1834    ///
1835    /// One event per exchange, recording how it ENDED, not two recording that it started and how it
1836    /// ended. Two doubled the traffic through the sink's single 16-deep endpoint, and the sink drains
1837    /// on its own core's schedule - so the flood crowded out the READER's own request and `trace
1838    /// status` reported a service that was alive and busy as "unavailable". Every exchange still
1839    /// produces exactly one event carrying its fate (REPLY / TIMEOUT / PEER_LOST / QUEUE_FULL /
1840    /// ABORTED), so nothing is lost but the duplicate.
1841    ///
1842    /// A request still IN FLIGHT is therefore not in the ring - and that is the one question the ring
1843    /// was never the right instrument for: `events blocked` reads it from the kernel, live, which is
1844    /// what a hang needs (`utilities/46_trace.md` mechanism A).
1845    #[inline]
1846    fn trace_in(&self, peer: &str, msg: &crate::ipc::Message) -> u8 {
1847        // The opcode's byte is the PEER'S protocol's business, not a fixed 0 - see `trace_op_at`.
1848        let at = crate::trace::op_offset(peer);
1849        msg.payload_bytes().get(at).copied().unwrap_or(0)
1850    }
1851
1852    /// Synchronous request/reply on the caller's own endpoint (kernel `Call`, syscall 41). Blocks until
1853    /// the reply arrives, or until the kernel wakes it with `ReplyDead` because the replier died (8.6).
1854    /// It has NO deadline: a replier that stays alive and never answers holds the caller forever.
1855    #[inline]
1856    pub fn request_with_reply(&self, peer: &str, msg: &crate::ipc::Message)
1857        -> Option<crate::ipc::Message>
1858    {
1859        let op = self.trace_in(peer, msg);
1860        let out = self.request_with_reply_inner(peer, msg);
1861        // No deadline on this variant, so `None` is always a lost peer, never a timeout.
1862        self.trace_out(peer, op, if out.is_some() { crate::trace::KIND_REPLY }
1863                                 else { crate::trace::KIND_PEER_LOST });
1864        out
1865    }
1866
1867    /// `request_with_reply_call`, saying WHY it failed instead of collapsing everything to `None`.
1868    #[inline]
1869    pub fn request_with_reply_call_err(&self, peer: &str, msg: &crate::ipc::Message, max_secs: i64)
1870        -> Result<Option<crate::ipc::Message>, crate::ipc::IpcError>
1871    {
1872        let op = self.trace_in(peer, msg);
1873        let out = self.request_with_reply_call_err_inner(peer, msg, max_secs);
1874        // A full queue is a busy peer, not a lost one - the ring said PEER_LOST for both until
1875        // 2026-10-10, so `gs::trace` showed `gs::call`'s `Busy` as a lost peer (`backlog/fixed/80` S2).
1876        self.trace_out(peer, op, match &out {
1877            Ok(Some(_)) => crate::trace::KIND_REPLY,
1878            Ok(None)    => crate::trace::KIND_TIMEOUT,
1879            Err(crate::ipc::IpcError::QueueFull) => crate::trace::KIND_QUEUE_FULL,
1880            Err(_)      => crate::trace::KIND_PEER_LOST,
1881        });
1882        out
1883    }
1884
1885    /// Bounded request/reply that decodes straight into the caller buffer.
1886    ///
1887    /// This variant collapses "the deadline passed" and "the send never left" into one `None`, so the
1888    /// ring records both as TIMEOUT. Where that difference matters the caller should be using
1889    /// `_call_err` or `_deadline_outcome`, which keep it - and so does the ring.
1890    #[inline]
1891    pub fn request_with_reply_deadline_into(
1892        &self, peer: &str, req: &[u8], buf: &mut [u8], max_secs: i64,
1893    ) -> Option<usize> {
1894        let op = req.get(crate::trace::op_offset(peer)).copied().unwrap_or(0);
1895        let out = self.request_with_reply_deadline_into_inner(peer, req, buf, max_secs);
1896        self.trace_out(peer, op, if out.is_some() { crate::trace::KIND_REPLY }
1897                                 else { crate::trace::KIND_TIMEOUT });
1898        out
1899    }
1900
1901    /// Bounded request/reply. `None` is "no answer within the deadline" (see `_into` on the collapse).
1902    #[inline]
1903    pub fn request_with_reply_deadline(
1904        &self, peer: &str, msg: &crate::ipc::Message, max_secs: i64,
1905    ) -> Option<crate::ipc::Message> {
1906        let op = self.trace_in(peer, msg);
1907        let out = self.request_with_reply_deadline_inner(peer, msg, max_secs);
1908        self.trace_out(peer, op, if out.is_some() { crate::trace::KIND_REPLY }
1909                                 else { crate::trace::KIND_TIMEOUT });
1910        out
1911    }
1912
1913    /// `request_with_reply_deadline` with a millisecond budget.
1914    #[inline]
1915    pub fn request_with_reply_ms(
1916        &self, peer: &str, msg: &crate::ipc::Message, max_ms: u64,
1917    ) -> Option<crate::ipc::Message> {
1918        let op = self.trace_in(peer, msg);
1919        let out = self.request_with_reply_ms_inner(peer, msg, max_ms);
1920        self.trace_out(peer, op, if out.is_some() { crate::trace::KIND_REPLY }
1921                                 else { crate::trace::KIND_TIMEOUT });
1922        out
1923    }
1924
1925    /// Bounded request/reply that distinguishes every way it can fail.
1926    #[inline]
1927    pub fn request_with_reply_deadline_outcome(
1928        &self, peer: &str, msg: &crate::ipc::Message, max_secs: i64,
1929    ) -> DeadlineOutcome {
1930        let op = self.trace_in(peer, msg);
1931        let out = self.request_with_reply_deadline_outcome_inner(peer, msg, max_secs);
1932        self.trace_out(peer, op, match &out {
1933            DeadlineOutcome::Reply(_)   => crate::trace::KIND_REPLY,
1934            DeadlineOutcome::SendFailed => crate::trace::KIND_PEER_LOST,
1935            DeadlineOutcome::QueueFull  => crate::trace::KIND_QUEUE_FULL,
1936            DeadlineOutcome::Timeout    => crate::trace::KIND_TIMEOUT,
1937        });
1938        out
1939    }
1940
1941    /// Bounded request/reply that SIFTS what arrives instead of believing the first thing that lands.
1942    ///
1943    /// Every other variant on this page returns whatever message turns up next, which is correct for a
1944    /// caller whose endpoint carries nothing but its own replies and WRONG for a caller that also
1945    /// SERVES on it. `net-stack` is the second kind: it receives client requests and `nic-driver`
1946    /// replies in one mailbox, so a client that spoke while it was waiting for the driver was consumed
1947    /// as the driver's answer, misparsed, and never served - the failure `docs/net-tags-design.md`
1948    /// opens with, and the reason that service still cannot do any background work.
1949    ///
1950    /// `mine` is asked about each message as it arrives, BEFORE anything else is done with it, and
1951    /// answers one question: is this the reply I sent for? Say `true` and the wait ends with it. Say
1952    /// `false` and the wait continues - the message is the caller's to deal with, and the caller is
1953    /// the only thing that can, because only it knows whether that means stashing a client request or
1954    /// discarding a reply that arrived too late. This is deliberately not an enum of dispositions: the
1955    /// SDK would then be deciding what to keep, which is policy, and the thing being kept usually has
1956    /// a reply capability that must be reclaimed or answered rather than merely stored (26.10, 8.5).
1957    ///
1958    /// The kernel's per-task pending cap and receive badge are both readable from inside `mine`
1959    /// (`take_pending_cap`, `last_recv_badge`), which is the point of calling it at the moment of
1960    /// arrival rather than afterwards: they describe THE MESSAGE JUST RECEIVED and are overwritten by
1961    /// the next one. A discriminator built on the pending cap is what `net-stack` uses, and it is free
1962    /// - a client request carries a reply cap, a driver reply does not.
1963    ///
1964    /// Everything else matches [`Self::request_with_reply_deadline_outcome`] exactly, including the
1965    /// reply-cap reclaim on a failed send (a delivered request's cap is the peer's - backlog/67) and the warning attached to `Timeout`: the request was
1966    /// SENT, so a late reply is still coming and re-sending desyncs the protocol.
1967    #[inline]
1968    pub fn request_with_reply_deadline_sifted(
1969        &self, peer: &str, msg: &crate::ipc::Message, max_secs: i64,
1970        mine: impl FnMut(&crate::ipc::Message) -> bool,
1971    ) -> DeadlineOutcome {
1972        let op = self.trace_in(peer, msg);
1973        let out = self.request_with_reply_deadline_sifted_inner(peer, msg, max_secs, mine);
1974        self.trace_out(peer, op, match &out {
1975            DeadlineOutcome::Reply(_)   => crate::trace::KIND_REPLY,
1976            DeadlineOutcome::SendFailed => crate::trace::KIND_PEER_LOST,
1977            DeadlineOutcome::QueueFull  => crate::trace::KIND_QUEUE_FULL,
1978            DeadlineOutcome::Timeout    => crate::trace::KIND_TIMEOUT,
1979        });
1980        out
1981    }
1982
1983    /// [`Self::request_with_reply_deadline_sifted`], bounded in MILLISECONDS.
1984    ///
1985    /// The same reason the unsifted `_ms` form exists: a sub-second window cannot be built out of a
1986    /// whole-second bound, and the drain inside `net-stack`'s ~900 ms ping window is exactly such a
1987    /// window. Bounded by the CYCLE counter for the same reason too - it is the only clock here with
1988    /// sub-second resolution, and it is the one the caller's own budget is measured in.
1989    #[inline]
1990    pub fn request_with_reply_ms_sifted(
1991        &self, peer: &str, msg: &crate::ipc::Message, max_ms: u64,
1992        mut mine: impl FnMut(&crate::ipc::Message) -> bool,
1993    ) -> Option<crate::ipc::Message> {
1994        let op = self.trace_in(peer, msg);
1995        let target = CapHandle(self.find_send_slot(peer)?);
1996        let self_grant = self.self_grant_handle()?;
1997        let reply_cap = self.derive_cap(self_grant)?;
1998        if self.offer_request(target, reply_cap, msg).is_err() {
1999            self.remove_cap(reply_cap);
2000            self.trace_out(peer, op, crate::trace::KIND_PEER_LOST);
2001            return None;
2002        }
2003        let t0 = self.read_tsc();
2004        let budget = self.duration_cycles(max_ms);
2005        loop {
2006            if let Some(r) = self.await_slice(Self::AWAIT_SLICE_MS.min(max_ms.max(1))) {
2007                if mine(&r) {
2008                    self.trace_out(peer, op, crate::trace::KIND_REPLY);
2009                    return Some(r);
2010                }
2011            }
2012            // wrapping_sub, so a counter that wraps mid-wait reads as a small elapsed rather than as
2013            // an enormous one that expires the deadline instantly.
2014            if self.read_tsc().wrapping_sub(t0) >= budget {
2015                // NOT RECLAIMED: the send succeeded, so the kernel moved the reply cap to the peer and emptied
2016                // this slot - and the first cap this task RECEIVED since went into it. Removing "the reply
2017                // cap" here destroyed that one: a client's reply cap stashed mid-wait, so its answer went
2018                // into a dead cap (backlog/67). The peer removes the cap after answering.
2019                self.trace_out(peer, op, crate::trace::KIND_TIMEOUT);
2020                return None;
2021            }
2022        }
2023    }
2024
2025    /// Bounded request/reply the user can abandon with `q`.
2026    #[inline]
2027    pub fn request_with_reply_abortable(
2028        &self, peer: &str, msg: &crate::ipc::Message, max_secs: i64,
2029    ) -> ReqOutcome {
2030        let op = self.trace_in(peer, msg);
2031        let out = self.request_with_reply_abortable_inner(peer, msg, max_secs);
2032        self.trace_out(peer, op, match &out {
2033            ReqOutcome::Reply(_) => crate::trace::KIND_REPLY,
2034            ReqOutcome::Aborted  => crate::trace::KIND_ABORTED,
2035            ReqOutcome::Timeout  => crate::trace::KIND_TIMEOUT,
2036        });
2037        out
2038    }
2039
2040    /// `request_with_reply_abortable`, plus a callback when the wait starts to linger.
2041    #[inline]
2042    pub fn request_with_reply_qhint(
2043        &self, peer: &str, msg: &crate::ipc::Message, hint_after_secs: i64, max_secs: i64,
2044        on_linger: impl FnOnce(),
2045    ) -> ReqOutcome {
2046        self.request_with_reply_keyhint(peer, msg, hint_after_secs, max_secs, Self::QUIT_KEYS, on_linger)
2047    }
2048
2049    /// [`Self::request_with_reply_qhint`] that HANDS BACK every key it reads that is not a quit key.
2050    ///
2051    /// A wait that watches the console for `q` has to read what is typed, and a key read is a key
2052    /// taken: the plain form discards the rest, so whatever the operator typed ahead while it waited
2053    /// was lost (`backlog/fixed/80` H16). This passes each one to `keep`, in order, for the caller to put
2054    /// where its own input reader will find it first.
2055    #[inline]
2056    pub fn request_with_reply_qhint_keeping(
2057        &self, peer: &str, msg: &crate::ipc::Message, hint_after_secs: i64, max_secs: i64,
2058        on_linger: impl FnOnce(), keep: &dyn Fn(u8),
2059    ) -> ReqOutcome {
2060        let op = self.trace_in(peer, msg);
2061        let out = self.request_with_reply_qhint_inner(
2062            peer, msg, hint_after_secs, max_secs, Self::QUIT_KEYS, on_linger, |_| true, Some(keep));
2063        self.trace_out(peer, op, match &out {
2064            ReqOutcome::Reply(_) => crate::trace::KIND_REPLY,
2065            ReqOutcome::Aborted  => crate::trace::KIND_ABORTED,
2066            ReqOutcome::Timeout  => crate::trace::KIND_TIMEOUT,
2067        });
2068        out
2069    }
2070
2071    /// The keys [`Self::request_with_reply_qhint`] leaves on: `q`, `Q` and Escape.
2072    pub const QUIT_KEYS: &'static [u8] = &[b'q', b'Q', 0x1b];
2073
2074    /// [`Self::request_with_reply_qhint`] with the LEAVE KEYS chosen by the caller. The convention
2075    /// (`utilities/0_conventions.md`) gives `q` to a task that STOPS when the operator leaves and `b` to
2076    /// one that keeps running; a request whose peer finishes the work whether or not this caller waits -
2077    /// `wifi radio off hard`, where the driver cuts the chip's power either way - is the second kind, and
2078    /// offering `q` for it would promise a stop that cannot happen. `Aborted` means one of `leave_keys`
2079    /// was pressed; what that means for the task is the caller's to say.
2080    pub fn request_with_reply_keyhint(
2081        &self, peer: &str, msg: &crate::ipc::Message, hint_after_secs: i64, max_secs: i64,
2082        leave_keys: &[u8], on_linger: impl FnOnce(),
2083    ) -> ReqOutcome {
2084        let op = self.trace_in(peer, msg);
2085        let out = self.request_with_reply_qhint_inner(peer, msg, hint_after_secs, max_secs, leave_keys, on_linger, |_| true, None);
2086        self.trace_out(peer, op, match &out {
2087            ReqOutcome::Reply(_) => crate::trace::KIND_REPLY,
2088            ReqOutcome::Aborted  => crate::trace::KIND_ABORTED,
2089            ReqOutcome::Timeout  => crate::trace::KIND_TIMEOUT,
2090        });
2091        out
2092    }
2093
2094    /// [`Self::request_with_reply_keyhint`] that SIFTS what arrives, as
2095    /// [`Self::request_with_reply_deadline_sifted`] does for the unabortable wait. `mine` is asked about
2096    /// each message as it lands; `true` ends the wait with it, `false` hands it to the caller - who must
2097    /// release any cap it carries - and the wait goes on, its deadline unchanged by how many arrive.
2098    /// Messages ALREADY queued when it is called are drained first without being asked (the stale-reply
2099    /// drain every abortable wait opens with), so a caller that counts late answers drains before calling.
2100    ///
2101    /// For a peer whose replies the caller can recognise (a correlation tag) on an endpoint that also
2102    /// takes other traffic: the unsifted form returns the first message whatever it is, so a late
2103    /// answer to a request this caller already gave up on is read as the answer to this one.
2104    pub fn request_with_reply_keyhint_sifted(
2105        &self, peer: &str, msg: &crate::ipc::Message, hint_after_secs: i64, max_secs: i64,
2106        leave_keys: &[u8], on_linger: impl FnOnce(),
2107        mine: impl FnMut(&crate::ipc::Message) -> bool,
2108    ) -> ReqOutcome {
2109        let op = self.trace_in(peer, msg);
2110        let out = self.request_with_reply_qhint_inner(peer, msg, hint_after_secs, max_secs, leave_keys, on_linger, mine, None);
2111        self.trace_out(peer, op, match &out {
2112            ReqOutcome::Reply(_) => crate::trace::KIND_REPLY,
2113            ReqOutcome::Aborted  => crate::trace::KIND_ABORTED,
2114            ReqOutcome::Timeout  => crate::trace::KIND_TIMEOUT,
2115        });
2116        out
2117    }
2118
2119    /// [`Self::request_with_reply_keyhint_sifted`] that HANDS BACK every key it reads that is not a leave
2120    /// key, in order, as [`Self::request_with_reply_qhint_keeping`] does for the unsifted request. For a
2121    /// caller whose waits must say `[q] quit` and must not lose what the operator types meanwhile.
2122    pub fn request_with_reply_keyhint_sifted_keeping(
2123        &self, peer: &str, msg: &crate::ipc::Message, hint_after_secs: i64, max_secs: i64,
2124        leave_keys: &[u8], on_linger: impl FnOnce(),
2125        mine: impl FnMut(&crate::ipc::Message) -> bool, keep: &dyn Fn(u8),
2126    ) -> ReqOutcome {
2127        let op = self.trace_in(peer, msg);
2128        let out = self.request_with_reply_qhint_inner(peer, msg, hint_after_secs, max_secs, leave_keys, on_linger, mine, Some(keep));
2129        self.trace_out(peer, op, match &out {
2130            ReqOutcome::Reply(_) => crate::trace::KIND_REPLY,
2131            ReqOutcome::Aborted  => crate::trace::KIND_ABORTED,
2132            ReqOutcome::Timeout  => crate::trace::KIND_TIMEOUT,
2133        });
2134        out
2135    }
2136
2137
2138
2139    fn request_with_reply_inner(
2140        &self,
2141        peer: &str,
2142        msg:  &crate::ipc::Message,
2143    ) -> Option<crate::ipc::Message> {
2144        let target = CapHandle(self.find_send_slot(peer)?);
2145        let self_grant = self.self_grant_handle()?;
2146        let reply_cap = self.derive_cap(self_grant)?;
2147        let recv = self.recv_handle()?;
2148        match crate::ipc::call(target, reply_cap, recv, msg) {
2149            Ok(reply) => Some(reply),
2150            Err(_) => {
2151                // Send failed (dead endpoint) or the peer died before replying (ReplyDead): the
2152                // embedded reply cap may not have been transferred, so reclaim it. Safe when the kernel
2153                // DID move it out: a Call receives nothing but its own reply, so no other cap can have
2154                // been inserted into the emptied slot, and removing an empty slot does nothing. (The
2155                // send-then-recv paths cannot say that, and do not reclaim - backlog/67.) Without this, a
2156                // storm of failed calls would leak reply caps until the table fills and every request
2157                // returns None.
2158                self.remove_cap(reply_cap);
2159                None
2160            }
2161        }
2162    }
2163
2164    #[inline(never)]
2165    /// A mark of how deep the stack currently is, in bytes below the first mark taken.
2166    ///
2167    /// THE INSTRUMENT THAT WAS MISSING. Five attempts were made to fit correlation tags into `fs`
2168    /// without ever measuring the frame they had to fit in; each removed one 4 KiB item, the overflow
2169    /// returned at a different pc, and QEMU - which has no disk, so `fs` never mounts and never makes
2170    /// the deep calls - reported every build clean. A change to stack usage that cannot be measured
2171    /// can only be guessed at, and five guesses is the evidence.
2172    ///
2173    /// Safe Rust and no kernel involvement: the address of a local IS the stack pointer, near enough.
2174    /// Relative to a mark taken at service start, so it needs no knowledge of where the stack lives.
2175    pub fn stack_mark(&self) -> usize {
2176        let probe = 0u8;
2177        &probe as *const u8 as usize
2178    }
2179
2180    /// Bytes of stack used at this point, without needing a reference mark.
2181    ///
2182    /// A mark taken at service start only works if the report is taken at the DEEPEST point, and the
2183    /// first attempt took it after `mount` had returned - so it measured a popped frame and printed
2184    /// zero. Absolute is harder to misuse: it can be called from anywhere and still means the same
2185    /// thing.
2186    ///
2187    /// The stack is one region, 256 KiB, and top-aligned, so rounding the current pointer UP to the
2188    /// next 256 KiB boundary lands on its top and the difference is what has been used. That derives
2189    /// the top from the alignment the kernel already guarantees rather than restating the address here,
2190    /// which would be a second copy of a layout decision (Commandment III).
2191    pub fn stack_used(&self) -> usize {
2192        const REGION: usize = 256 * 1024;
2193        let sp = self.stack_mark();
2194        let top = (sp + (REGION - 1)) & !(REGION - 1);
2195        top - sp
2196    }
2197
2198    /// A bounded request/reply through the KERNEL's `CallDeadline`, returning the reply as a message.
2199    ///
2200    /// The same primitive `request_with_reply_deadline_into` already uses, in the shape the other
2201    /// request helpers have, so a caller can move to it without restructuring its buffers.
2202    ///
2203    /// Use this rather than `request_with_reply_deadline` / `_deadline_outcome` when the caller SERVES
2204    /// clients on the same endpoint it awaits replies on. Those helpers wait with a plain timed `recv`,
2205    /// which takes whatever is next - so under client load the peer's reply can be crowded out of a 16-deep queue and the
2206    /// caller reports a timeout that blames the peer for something it did correctly. `net-stack` hit
2207    /// exactly that: the wire showed 7 DHCP REQUESTs sent and 7 ACKs returned while the log said
2208    /// "6 of 6 REQUESTs never left the host - the driver refused them".
2209    ///
2210    /// `CallDeadline` does not have that failure: `call_dequeue` matches the reply BY ITS SENDER and
2211    /// leaves every other message queued, which is the entire reason §8.2 added it - hand-rolling the
2212    /// bounded wait out of send + recv is what lost messages. No new kernel surface: syscall 50 and
2213    /// its semantics are existing, ratified law. `max_secs <= 0` is a one-second
2214    /// bound, never none: the kernel reads a 0 as NO deadline, and a bounded call turning into an
2215    /// unbounded one by a unit slip is the hang it exists to prevent (`backlog/fixed/80` S5).
2216    pub fn request_with_reply_call(&self, peer: &str, msg: &crate::ipc::Message, max_secs: i64)
2217        -> Option<crate::ipc::Message>
2218    {
2219        self.request_with_reply_call_err(peer, msg, max_secs).ok().flatten()
2220    }
2221
2222    /// `request_with_reply_call`, but saying WHY it failed instead of collapsing everything to None.
2223    ///
2224    /// Exists because "no answer" is not a diagnosis. A caller that turns every failure into one word
2225    /// then reports it as the PEER's fault - `net-stack` blamed the driver for refusing frames the wire
2226    /// showed it had sent - and there is no way to tell a send that never left from a reply that never
2227    /// came. `Ok(None)` is the deadline passing; `Err(e)` is the send failing, and `e` says how.
2228    fn request_with_reply_call_err_inner(&self, peer: &str, msg: &crate::ipc::Message, max_secs: i64)
2229        -> Result<Option<crate::ipc::Message>, crate::ipc::IpcError>
2230    {
2231        let target = match self.find_send_slot(peer) {
2232            Some(s) => CapHandle(s),
2233            None    => return Err(crate::ipc::IpcError::CapError(crate::capability::CapError::CapNotHeld)),
2234        };
2235        // Abandoned replies out first, or this request takes one of them (`drain_before_request`).
2236        self.drain_before_request(peer);
2237        let (recv, grant) = match self.reply_mailbox() {
2238            Some((r, g)) => (r, g),
2239            None => match (self.recv_handle(), self.self_grant_handle()) {
2240                (Some(r), Some(g)) => (r, g),
2241                _ => return Err(crate::ipc::IpcError::CapError(crate::capability::CapError::CapNotHeld)),
2242            },
2243        };
2244        let reply_cap = match self.derive_cap(grant) {
2245            Some(c) => c,
2246            None    => return Err(crate::ipc::IpcError::CapError(crate::capability::CapError::CapNotHeld)),
2247        };
2248        let secs = deadline_secs(max_secs);
2249        let mut buf = [0u8; crate::ipc::MAX_PAYLOAD];
2250        let n = msg.payload_bytes().len().min(buf.len());
2251        buf[..n].copy_from_slice(&msg.payload_bytes()[..n]);
2252        match crate::ipc::call_deadline_into(target, reply_cap, recv, &msg.payload_bytes()[..n],
2253                                             &mut buf, secs) {
2254            Ok(Some(len)) => Ok(Some(crate::ipc::Message::from_bytes(&buf[..len]))),
2255            // Delivered, deadline passed: the reply cap is the peer's now (backlog/67).
2256            Ok(None)      => Ok(None),
2257            Err(e)        => { self.remove_cap(reply_cap); Err(e) }
2258        }
2259    }
2260
2261    /// Bounded request/reply into a caller buffer, reporting WHICH failure happened.
2262    ///
2263    /// Same work as [`Self::request_with_reply_deadline_into`], same stack shape - the caller supplies
2264    /// the buffer so no reply-sized frame appears here - but it does not throw the reason away. See
2265    /// [`DeadlineOutcomeInto`] for why the distinction is not a nicety: the two common failures want
2266    /// opposite responses, and a caller given `None` has to guess wrong in one direction or the other.
2267    pub fn request_with_reply_deadline_outcome_into(
2268        &self, peer: &str, req: &[u8], buf: &mut [u8], max_secs: i64,
2269    ) -> DeadlineOutcomeInto {
2270        let op = req.get(crate::trace::op_offset(peer)).copied().unwrap_or(0);
2271        let out = self.request_with_reply_deadline_outcome_into_inner(peer, req, buf, max_secs);
2272        self.trace_out(peer, op, match &out {
2273            DeadlineOutcomeInto::Reply(_)   => crate::trace::KIND_REPLY,
2274            DeadlineOutcomeInto::SendFailed => crate::trace::KIND_PEER_LOST,
2275            DeadlineOutcomeInto::PeerDied   => crate::trace::KIND_PEER_LOST,
2276            DeadlineOutcomeInto::QueueFull  => crate::trace::KIND_QUEUE_FULL,
2277            DeadlineOutcomeInto::Timeout    => crate::trace::KIND_TIMEOUT,
2278        });
2279        out
2280    }
2281
2282    fn request_with_reply_deadline_outcome_into_inner(
2283        &self, peer: &str, req: &[u8], buf: &mut [u8], max_secs: i64,
2284    ) -> DeadlineOutcomeInto {
2285        // Each of these is a DISTINCT reason the request never left, and every one of them used to be
2286        // an anonymous `None`: no such peer wired or resolvable, no endpoint of our own to be replied
2287        // to, and - the one that made this permanent rather than transient - NO FREE CAP SLOT to derive
2288        // a reply cap into. A service whose table has filled reports "peer did not answer", which sends
2289        // the reader looking at the peer.
2290        let target = match self.find_send_slot(peer) {
2291            Some(s) => CapHandle(s),
2292            None => return DeadlineOutcomeInto::SendFailed,
2293        };
2294        // Abandoned replies out first, or this request takes one of them (`drain_before_request`).
2295        self.drain_before_request(peer);
2296        let (recv, grant) = match self.reply_mailbox() {
2297            Some((r, g)) => (r, g),
2298            None => match (self.recv_handle(), self.self_grant_handle()) {
2299                (Some(r), Some(g)) => (r, g),
2300                _ => return DeadlineOutcomeInto::SendFailed,
2301            },
2302        };
2303        let reply_cap = match self.derive_cap(grant) {
2304            Some(c) => c,
2305            None => return DeadlineOutcomeInto::SendFailed,
2306        };
2307        let secs = deadline_secs(max_secs);
2308        let out = crate::ipc::call_deadline_into(target, reply_cap, recv, req, buf, secs);
2309        // The kernel consumes the reply cap on a delivered call - and `Ok(None)` is a delivered call
2310        // whose deadline passed, so it is the peer's then too (backlog/67). On a failed send it is ours
2311        // to reclaim, or the slot leaks one per failed request (§8.5, the three checks).
2312        match out {
2313            Ok(Some(n)) => DeadlineOutcomeInto::Reply(n),
2314            Ok(None) => DeadlineOutcomeInto::Timeout,
2315            Err(crate::ipc::IpcError::QueueFull) => {
2316                self.remove_cap(reply_cap);
2317                DeadlineOutcomeInto::QueueFull
2318            }
2319            // Delivered: the reply cap went with the request, as on `Ok(None)` - nothing to reclaim.
2320            Err(crate::ipc::IpcError::ReplyDead) => DeadlineOutcomeInto::PeerDied,
2321            Err(_) => { self.remove_cap(reply_cap); DeadlineOutcomeInto::SendFailed }
2322        }
2323    }
2324
2325    /// Send `req` to `peer` and receive the reply into `buf`, bounded by `max_secs`; returns the
2326    /// reply's length. `max_secs <= 0` is a one-second bound, never an unbounded wait (see
2327    /// `request_with_reply_call`).
2328    ///
2329    /// The by-value path costs 4 KiB for the request `Message`, 4 KiB for the reply `Message`, and
2330    /// 4 KiB again at every wrapper that forwards it. This costs none of that: the caller owns both
2331    /// buffers, and only a length is returned. Drains abandoned replies from the reply mailbox first
2332    /// (`drain_stale_replies`), and reclaims the reply cap only when the send failed (§8.5, backlog/67).
2333    ///
2334    /// **This used to be send + `recv_timeout_into`, and that was wrong in a way that cost days.**
2335    /// `recv_timeout_into` takes whatever is next on the endpoint, and a service that SERVES clients on
2336    /// the same endpoint it awaits replies on would receive an unrelated client request, fail to match
2337    /// it, and drop it - losing that request outright while its own reply arrived later as an orphan
2338    /// that desynced the next exchange. `fs` does exactly that, which is why its clients hung and its
2339    /// block protocol "lost step" on both boards.
2340    ///
2341    /// It now uses `CallDeadline`, which dequeues the REPLY specifically (the kernel's `call_dequeue`
2342    /// takes only a message stamped as sent by the target's owner) and leaves everything else queued.
2343    /// The correct primitive, bounded.
2344    fn request_with_reply_deadline_into_inner(
2345        &self, peer: &str, req: &[u8], buf: &mut [u8], max_secs: i64,
2346    ) -> Option<usize> {
2347        let target = CapHandle(self.find_send_slot(peer)?);
2348        // DRAIN ABANDONED REPLIES BEFORE ISSUING, FOR EVERY CALLER.
2349        //
2350        // `drain_stale_replies` documents this failure precisely and had exactly one caller - `fs` -
2351        // so every OTHER service was one timeout away from being permanently broken. The shell was:
2352        // a single scroll request that timed out left its reply queued, and from that moment every
2353        // request waited out its full deadline and failed, for the rest of the boot. Observed on a
2354        // Dell Wyse as `scrollback: the console did not answer` once a second, indefinitely, with the
2355        // console sitting idle at `BlockRecv, queue 0` the whole time (`backlog/fixed/37`).
2356        //
2357        // **It is safe here for the reason that function already gives**, and that reason is exactly
2358        // why this belongs in the SDK rather than at each call site: `request_with_reply*` enforces
2359        // ONE OUTSTANDING REQUEST AT A TIME, so at the instant a new request is issued nothing
2360        // legitimate can be waiting in the reply mailbox. Anything present is a reply this service
2361        // stopped waiting for. Leaving it to each caller to remember means a caller that forgets is
2362        // not slightly worse - it is permanently broken after its first timeout, silently.
2363        //
2364        // Cheap: a non-blocking `try_recv` that finds nothing in the healthy case, bounded by the
2365        // endpoint depth.
2366        self.drain_before_request(peer);
2367        // Reply mailbox when the task has one, shared endpoint when it does not. The reply cap and the
2368        // endpoint waited on must name the SAME endpoint, or the kernel's reply-matched dequeue waits
2369        // for something that will never be delivered there.
2370        let (recv, grant) = match self.reply_mailbox() {
2371            Some((r, g)) => (r, g),
2372            None => (self.recv_handle()?, self.self_grant_handle()?),
2373        };
2374        let reply_cap = self.derive_cap(grant)?;
2375        let secs = deadline_secs(max_secs);
2376        let out = crate::ipc::call_deadline_into(target, reply_cap, recv, req, buf, secs);
2377        // The kernel consumes the reply cap on a delivered call - and `Ok(None)` is a delivered call
2378        // whose deadline passed, so it is the peer's then too (backlog/67). On a failed send it is ours
2379        // to reclaim, or the slot leaks one per failed request (§8.5, the three checks).
2380        match out {
2381            Ok(Some(n)) => Some(n),
2382            Ok(None) => None,
2383            Err(_)   => { self.remove_cap(reply_cap); None }
2384        }
2385    }
2386
2387    /// How long one blocking wait slice lasts.
2388    ///
2389    /// 20 ms: two scheduler quanta, so a waiter never holds its core, and short enough that a caller
2390    /// checking for a keypress between slices still feels instant to a human. Nothing depends on the
2391    /// value for correctness - a message arriving mid-slice wakes the task immediately; the slice only
2392    /// bounds how often a caller gets to look at anything OTHER than its endpoint.
2393    const AWAIT_SLICE_MS: u64 = 20;
2394    /// The slice a noticing wait blocks for before its notice is due, when it is not reading the
2395    /// console and has only the reply and the clock to watch.
2396    const PRE_NOTICE_SLICE_MS: u64 = 250;
2397
2398    /// Wait for a message on our own endpoint, BLOCKING, for at most one slice.
2399    ///
2400    /// The whole point of this helper, and the reason it exists rather than a `try_recv` loop: a task
2401    /// that spins while waiting is not idle, it is COMPETING with whatever it is waiting for. On this
2402    /// system that is not an abstract cost - `net-stack` waiting for a DHCP reply spun through the SDK
2403    /// and through its own `drain_scan`, two nested loops, and saturated `nic-driver` and the USB
2404    /// driver that had to fetch the very reply it was waiting for. The waiting starved the answering.
2405    ///
2406    /// Pinning the services to different cores hid it and fixed nothing: on a single-core machine the
2407    /// spinner simply consumes the whole system. A blocked task consumes nothing and is woken the
2408    /// instant a message lands (`RecvTimeout`, §12), so this is correct at any core count - which is
2409    /// the only kind of correct worth having.
2410    ///
2411    /// Sliced rather than one long block so callers that must also watch something else - a keypress,
2412    /// an abort flag - get the chance, without any of them going back to spinning.
2413    ///
2414    /// (2026-10-09) The `#[inline(always)]` the next paragraphs describe is NOT on this function any
2415    /// more: 8f32a5e9 removed it, because inlining a body that holds a 4 KiB local into a caller that
2416    /// already holds one puts both in the same frame. The cost they describe is real either way.
2417    ///
2418    /// `#[inline(always)]`, and that attribute is load-bearing rather than an optimisation hint. A
2419    /// `Message` is 4096 bytes BY VALUE, so a wrapper that returns one is not free: it is a whole
2420    /// extra 4 KiB stack frame on every request in every service. Introducing this helper cost `fs`
2421    /// exactly that, and `fs` was already the service closest to its 256 KiB limit - it began taking a
2422    /// data abort at `mount`, with SP below the bottom of its stack region.
2423    ///
2424    /// It only showed on hardware. QEMU's raspi2b has no disk, so `fs` never mounts and never makes
2425    /// the deep block calls, and thirty-second QEMU boots kept coming back clean while the board
2426    /// restart-looped. A one-line wrapper around a large return value has to be inlined or it must not
2427    /// exist.
2428    fn await_slice(&self, ms: u64) -> Option<crate::ipc::Message> {
2429        self.recv_timeout(self.duration_cycles(ms))
2430    }
2431
2432    /// Offer a request to `target`, riding out a FULL peer queue instead of calling it a failure.
2433    ///
2434    /// A full queue and an unreachable peer are different things, and reporting them the same way is
2435    /// what desynced the shell against `fs` on x86. The caller's answer to "send failed" is to
2436    /// reacquire the peer by name and RE-SEND with a fresh tag - correct when a restart made the cap
2437    /// stale, and actively harmful when the queue was merely full: the original request is still in
2438    /// flight, so its reply arrives as an orphan and every request afterwards collects the PREVIOUS
2439    /// one's reply. The log showed exactly that, permanently one behind:
2440    ///
2441    ///     shell: discarded an fs reply for tag 68 while awaiting 69
2442    ///     shell: discarded an fs reply for tag 72 while awaiting 73
2443    ///
2444    /// Congestion clears as soon as the peer is scheduled, so the fix is to wait a moment and offer
2445    /// the SAME request again - never a second one. Bounded, and a queue still full after that is
2446    /// reported honestly to the caller.
2447    fn offer_request(
2448        &self, target: CapHandle, reply_cap: CapHandle, msg: &crate::ipc::Message,
2449    ) -> Result<(), crate::ipc::IpcError> {
2450        const BUSY_MS: u64 = 2;
2451        const BUSY_TRIES: u32 = 8;
2452        let mut last = crate::ipc::IpcError::QueueFull;
2453        for _ in 0..BUSY_TRIES {
2454            match self.send_with_cap_by_handle(target, reply_cap, msg) {
2455                Ok(()) => return Ok(()),
2456                Err(crate::ipc::IpcError::QueueFull) => {
2457                    last = crate::ipc::IpcError::QueueFull;
2458                    self.sleep(self.duration_cycles(BUSY_MS));
2459                }
2460                Err(e) => return Err(e),
2461            }
2462        }
2463        Err(last)
2464    }
2465
2466    /// Like `request_with_reply`, but the wait for the reply is **bounded** by `max_secs` of
2467    /// **wall-clock** time (`epoch_secs_monotonic`, InspectKernel query 17). Returns `None` on timeout,
2468    /// and on a send that never left - so a peer that receives the request and then stays silent cannot
2469    /// hold the caller forever (`request_with_reply`, which has no deadline, would). Use it when the
2470    /// peer may be unstable - e.g. writing a report to `fs` right after a chaos storm hammered `fs` +
2471    /// its `block-driver`.
2472    ///
2473    /// Uses the RTC (not a TSC-cycle deadline) deliberately: a cycle bound is not portable - under
2474    /// QEMU's TCG the guest TSC races ahead and expires the deadline before the reply arrives, while
2475    /// the RTC is real wall-clock on both TCG and hardware. Waits in blocking slices (`await_slice`),
2476    /// so it does not spin, and its resolution is one second.
2477    /// **Costs one message-sized stack frame, not two.**
2478    ///
2479    /// This used to be a thin wrapper over `request_with_reply_deadline_outcome`, and that made it
2480    /// unusable on a tight stack: `DeadlineOutcome` CARRIES a `Message`, so the enum is a second
2481    /// 4 KiB temporary on top of the `Option<Message>` returned. `fs` already sits near its 256 KiB
2482    /// user stack, and switching its block RPC to this wrapper produced an instant data abort at
2483    /// startup and an 826-deep restart loop on real hardware - the machine unusable, from a change
2484    /// whose whole purpose was to make a hang impossible.
2485    ///
2486    /// So the wait is written out here instead of borrowed. It is the same loop, minus the enum: a
2487    /// caller that does not need to distinguish "the send never left" from "the peer was silent"
2488    /// should not pay a message-sized frame for the distinction. Callers that DO need it still have
2489    /// `..._outcome`, and should keep an eye on their own stack.
2490    ///
2491    /// **Shares the flaw `request_with_reply_deadline_into` was fixed for, and is NOT fixed here.**
2492    /// It sends with a reply cap and then receives generically, so a caller that also SERVES on its
2493    /// endpoint can consume an unrelated message and drop it. `_into` moved to `CallDeadline`, which
2494    /// dequeues the reply specifically; this one has not, because nothing has yet been shown to be
2495    /// harmed by it and a blind sweep of four helpers is how a fix becomes a regression.
2496    ///
2497    /// Note that `_abortable` and `_qhint` CANNOT simply follow: they interleave on purpose, to
2498    /// notice a `q` keypress while waiting. Making them dequeue only the reply would delete that. If
2499    /// they need this too, the answer is a bounded stash, not a substitution.
2500    fn request_with_reply_deadline_inner(
2501        &self,
2502        peer: &str,
2503        msg:  &crate::ipc::Message,
2504        max_secs: i64,
2505    ) -> Option<crate::ipc::Message> {
2506        let target = CapHandle(self.find_send_slot(peer)?);
2507        let self_grant = self.self_grant_handle()?;
2508        let reply_cap = self.derive_cap(self_grant)?;
2509        let recv = self.recv_handle()?;
2510        if self.offer_request(target, reply_cap, msg).is_err() {
2511            // The send never left, so the embedded cap was not transferred - reclaim it or a storm of
2512            // failures leaks the table (§8.5).
2513            self.remove_cap(reply_cap);
2514            return None;
2515        }
2516        let t0 = self.epoch_secs_monotonic();
2517        loop {
2518            // BLOCK, do not spin. See `await_slice`.
2519            if let Some(r) = self.await_slice(Self::AWAIT_SLICE_MS) {
2520                return Some(r);
2521            }
2522            let now = self.epoch_secs_monotonic();
2523            // Guard the clock going BACKWARDS as well as forwards: on hardware whose counter is
2524            // unreliable, `now - t0` can read huge and expire the deadline on the first pass.
2525            if now >= t0 && now - t0 >= max_secs {
2526                // Abandoned: the reply may still arrive later and sit in our queue, which is the
2527                // hazard `..._outcome` documents. The caller reports the failure.
2528                // NOT RECLAIMED: the send succeeded, so the kernel moved the reply cap to the peer and emptied
2529                // this slot - and the first cap this task RECEIVED since went into it. Removing "the reply
2530                // cap" here destroyed that one: a client's reply cap stashed mid-wait, so its answer went
2531                // into a dead cap (backlog/67). The peer removes the cap after answering.
2532                let _ = recv;
2533                return None;
2534            }
2535        }
2536    }
2537
2538    /// `request_with_reply_deadline`, bounded in MILLISECONDS instead of whole seconds.
2539    ///
2540    /// Exists because a sub-second window cannot be built out of a whole-second bound, and one was
2541    /// being attempted. `net-stack`'s ping window is ~900 ms and cycle-based, but the drain inside it
2542    /// was bounded by `LINK_SECS = 1` on `epoch_secs_monotonic`, whose resolution IS one second - so a
2543    /// single drain could legitimately outlast the entire window. Measured on a Pi 4: the window
2544    /// reported `closed after 1017663 us [budget 900000 us]` with `0 drains`, meaning it completed
2545    /// exactly ONE call and that call overran the whole budget. Ping sends once a second, so a window
2546    /// that runs 1.018 s misses roughly every third reply - which read as "the network is flaky" and
2547    /// was arithmetic.
2548    ///
2549    /// Bounded by the CYCLE counter, deliberately, because that is the only clock here with sub-second
2550    /// resolution and it is the same clock the caller's own window uses - a bound and the budget it
2551    /// must fit inside should not be measured by different clocks. `duration_cycles` already floors an
2552    /// uncalibrated counter to one quantum, so a board with no calibration degrades to "return almost
2553    /// at once" rather than to "wait forever".
2554    ///
2555    /// No new kernel surface: this is the same userspace poll the seconds variants use, with a
2556    /// different clock read in the deadline test.
2557    fn request_with_reply_ms_inner(
2558        &self,
2559        peer: &str,
2560        msg:  &crate::ipc::Message,
2561        max_ms: u64,
2562    ) -> Option<crate::ipc::Message> {
2563        let target = CapHandle(self.find_send_slot(peer)?);
2564        let self_grant = self.self_grant_handle()?;
2565        let reply_cap = self.derive_cap(self_grant)?;
2566        let recv = self.recv_handle()?;
2567        if self.offer_request(target, reply_cap, msg).is_err() {
2568            self.remove_cap(reply_cap);
2569            return None;
2570        }
2571        let t0 = self.read_tsc();
2572        let budget = self.duration_cycles(max_ms);
2573        loop {
2574            if let Some(r) = self.await_slice(Self::AWAIT_SLICE_MS.min(max_ms.max(1))) {
2575                return Some(r);
2576            }
2577            // wrapping_sub, so a counter that wraps mid-wait reads as a small elapsed rather than as
2578            // an enormous one that expires the deadline instantly.
2579            if self.read_tsc().wrapping_sub(t0) >= budget {
2580                // NOT RECLAIMED: the send succeeded, so the kernel moved the reply cap to the peer and emptied
2581                // this slot - and the first cap this task RECEIVED since went into it. Removing "the reply
2582                // cap" here destroyed that one: a client's reply cap stashed mid-wait, so its answer went
2583                // into a dead cap (backlog/67). The peer removes the cap after answering.
2584                let _ = recv;
2585                return None;
2586            }
2587        }
2588    }
2589
2590    /// Like [`Self::request_with_reply_deadline`] but returns the DISTINCTION between a send that never
2591    /// left (stale/unresolvable peer cap) and a peer that received the request but stayed silent past
2592    /// the deadline (see [`DeadlineOutcome`]). Same bounded, RTC-deadline wait; use this when a caller
2593    /// must reacquire+retry a *restarted* peer (`SendFailed`) but must NOT double the wait on a merely
2594    /// *silent* one (`Timeout`).
2595    fn request_with_reply_deadline_outcome_inner(
2596        &self,
2597        peer: &str,
2598        msg:  &crate::ipc::Message,
2599        max_secs: i64,
2600    ) -> DeadlineOutcome {
2601        let target = match self.find_send_slot(peer) { Some(s) => CapHandle(s), None => return DeadlineOutcome::SendFailed };
2602        let self_grant = match self.self_grant_handle() { Some(g) => g, None => return DeadlineOutcome::SendFailed };
2603        let reply_cap = match self.derive_cap(self_grant) { Some(c) => c, None => return DeadlineOutcome::SendFailed };
2604        if let Err(e) = self.send_with_cap_by_handle(target, reply_cap, msg) {
2605            self.remove_cap(reply_cap);   // send failed: reclaim the untransferred reply cap (no leak)
2606            return match e {
2607                crate::ipc::IpcError::QueueFull => DeadlineOutcome::QueueFull,
2608                _ => DeadlineOutcome::SendFailed,
2609            };
2610        }
2611        // Deglitched monotonic clock, not the raw RTC: a single CMOS misread (the "4383d" glitch on the
2612        // T630) would otherwise make `now - t0` read huge and expire the deadline instantly.
2613        let t0 = self.epoch_secs_monotonic();
2614        loop {
2615            // Block, do not spin - and now it actually does. This comment said so while the line
2616            // beneath it polled, which is how the claim survived so long unexamined.
2617            if let Some(r) = self.await_slice(Self::AWAIT_SLICE_MS) { return DeadlineOutcome::Reply(r); }
2618            if self.epoch_secs_monotonic() - t0 >= max_secs {
2619                // NOT RECLAIMED: the send succeeded, so the kernel moved the reply cap to the peer and emptied
2620                // this slot - and the first cap this task RECEIVED since went into it. Removing "the reply
2621                // cap" here destroyed that one: a client's reply cap stashed mid-wait, so its answer went
2622                // into a dead cap (backlog/67). The peer removes the cap after answering.
2623                // CALLER BEWARE: the request was already SENT, so the peer will reply into our endpoint
2624                // whether we are listening or not. Leaving the reply cap alone does not remove that message
2625                // from the queue - the NEXT `try_recv` on this endpoint may return the ABANDONED reply
2626                // instead of the answer it expects. That has bitten for real: a timed-out fs read left a
2627                // 1-byte `[FS_NOTFOUND]` behind, and the next command consumed it and reported a healthy
2628                // mounted disk as absent. `request_with_reply_abortable` avoids this with a DRAIN at its
2629                // own top; this variant cannot drain blindly, because a service that also SERVES on this
2630                // endpoint (net-stack) would discard live client requests. So a caller that can time out
2631                // must deal with the late reply itself - see the shell's `drain_stale_fs_replies`, which
2632                // discards leftovers BEFORE sending rather than chasing one afterwards (a race that
2633                // cannot be won: if the late reply has not arrived yet, the NEXT command eats it).
2634                return DeadlineOutcome::Timeout;
2635            }
2636            self.yield_cpu();
2637        }
2638    }
2639
2640    /// The sifting wait. A deliberate near-copy of `request_with_reply_deadline_outcome_inner`
2641    /// rather than a shared body that one of them passes a closure into.
2642    ///
2643    /// Folding the two together would put a generic parameter on the hot path of every service that
2644    /// makes a bounded request, for the benefit of the one that sifts. The SDK has been bitten by
2645    /// exactly that kind of tidying before: a one-line wrapper returning a 4 KiB `Message` by value
2646    /// cost `fs` a whole extra stack frame per request and took it over its stack limit on hardware
2647    /// while every QEMU run stayed green (see `await_slice`). The duplication is four lines of loop,
2648    /// it is visible, and both copies are right here.
2649    fn request_with_reply_deadline_sifted_inner(
2650        &self,
2651        peer: &str,
2652        msg:  &crate::ipc::Message,
2653        max_secs: i64,
2654        mut mine: impl FnMut(&crate::ipc::Message) -> bool,
2655    ) -> DeadlineOutcome {
2656        let target = match self.find_send_slot(peer) { Some(s) => CapHandle(s), None => return DeadlineOutcome::SendFailed };
2657        let self_grant = match self.self_grant_handle() { Some(g) => g, None => return DeadlineOutcome::SendFailed };
2658        let reply_cap = match self.derive_cap(self_grant) { Some(c) => c, None => return DeadlineOutcome::SendFailed };
2659        if let Err(e) = self.send_with_cap_by_handle(target, reply_cap, msg) {
2660            self.remove_cap(reply_cap);   // send failed: reclaim the untransferred reply cap (no leak)
2661            return match e {
2662                crate::ipc::IpcError::QueueFull => DeadlineOutcome::QueueFull,
2663                _ => DeadlineOutcome::SendFailed,
2664            };
2665        }
2666        let t0 = self.epoch_secs_monotonic();
2667        loop {
2668            if let Some(r) = self.await_slice(Self::AWAIT_SLICE_MS) {
2669                // Asked at the moment of arrival, so `take_pending_cap` and `last_recv_badge` inside
2670                // it still describe THIS message. A `false` hands ownership to the caller and the
2671                // wait goes on; the deadline below is unchanged by how many arrive, so a flood of
2672                // other traffic cannot extend it.
2673                if mine(&r) { return DeadlineOutcome::Reply(r); }
2674            }
2675            if self.epoch_secs_monotonic() - t0 >= max_secs {
2676                // NOT RECLAIMED: the send succeeded, so the kernel moved the reply cap to the peer and emptied
2677                // this slot - and the first cap this task RECEIVED since went into it. Removing "the reply
2678                // cap" here destroyed that one: a client's reply cap stashed mid-wait, so its answer went
2679                // into a dead cap (backlog/67). The peer removes the cap after answering.
2680                // Same caveat as the unsifted twin: the request WAS sent, so the peer answers whether
2681                // or not anyone is still listening, and that answer will arrive later. A caller that
2682                // times out must expect to meet it - here, `mine` will simply be asked about it and
2683                // can say no.
2684                return DeadlineOutcome::Timeout;
2685            }
2686            self.yield_cpu();
2687        }
2688    }
2689
2690    /// Like [`Self::request_with_reply_deadline`] but ABORTABLE: while waiting it also drains the console
2691    /// and, on q/Q/ESC, returns [`ReqOutcome::Aborted`] IMMEDIATELY - it does NOT wait for the in-flight
2692    /// reply (that wait felt like "it pauses instead of quitting"). The request is already sent, so the
2693    /// peer replies into our endpoint whether we listen or not; that late reply is cleared by the DRAIN at
2694    /// the top of the *next* abortable request, so it never pollutes a later command (the `net scan` ->
2695    /// `net` "0.0.0.0 / 00:00 MAC" bug that drain closes). Sends the request ONCE (no re-trigger). Use for
2696    /// any interactive command that blocks on a peer (the "q to quit" rule, `utilities/0_conventions.md`).
2697    /// A service with no console foreground never sees input, so this degrades to the plain deadline wait.
2698    fn request_with_reply_abortable_inner(
2699        &self,
2700        peer: &str,
2701        msg:  &crate::ipc::Message,
2702        max_secs: i64,
2703    ) -> ReqOutcome {
2704        // Drain any stale reply a prior INSTANT-abort left in our endpoint (the peer replied after we
2705        // stopped listening), so this request cannot read it as its own. Safe for the shell - a client
2706        // whose endpoint only holds replies; between commands it is otherwise empty.
2707        while self.try_recv().is_some() {}
2708        let target = match self.find_send_slot(peer) { Some(s) => CapHandle(s), None => return ReqOutcome::Timeout };
2709        let self_grant = match self.self_grant_handle() { Some(g) => g, None => return ReqOutcome::Timeout };
2710        let reply_cap = match self.derive_cap(self_grant) { Some(c) => c, None => return ReqOutcome::Timeout };
2711        if self.offer_request(target, reply_cap, msg).is_err() {
2712            self.remove_cap(reply_cap);
2713            return ReqOutcome::Timeout;
2714        }
2715        let t0 = self.epoch_secs_monotonic();
2716        loop {
2717            // BLOCK for the reply, with a short timeout - do not spin. `try_recv` + `yield_cpu` kept this
2718            // task permanently RUNNABLE, so a core running a waiting command never reached the scheduler's
2719            // idle path at all. On ARM that path is where USB hot-plug is watched, so plugging or
2720            // unplugging anything during a `ping` was noticed only once the ping ended: `events` queued
2721            // up and all arrived at the prompt. It also pegged the core at 100% for a task that is, in
2722            // truth, doing nothing (the same busy-wait the `observe` and muted loops were already fixed
2723            // for - see the shell's MUTED_POLL_MS). Blocking parks the task, the core halts, idle work runs.
2724            if let Some(r) = self.await_slice(Self::AWAIT_SLICE_MS) {
2725                // DO NOT remove the reply cap on a REPLY. The send already removed it.
2726                //
2727                // §8.5: a cap embedded in a message "is transferred and REMOVED from sender's table".
2728                // The instant the request went out this slot stopped being ours, and by the time the
2729                // reply lands the kernel has REUSED it - `CapTable::insert` hands out the first EMPTY
2730                // slot, and this one is empty. When the reply carries an embedded cap (a file cap from
2731                // `fs`), that cap lands in exactly this slot, so removing "the reply cap" DELETES IT.
2732                //
2733                // Measured on the Pi 4: `fcap` printed `file=12 reply=12` - the file handle and the
2734                // next derived cap were the same slot, which is only possible if the slot was empty.
2735                // Every file-cap read/write then failed before reaching `fs`, and two negative
2736                // sub-checks "passed" vacuously because no invoke could get that far.
2737                //
2738                // A remove-by-stale-index can bite ANY request whose reply carries a cap, not just
2739                // fcap - and the abort and timeout paths below are no different: the send DID deliver
2740                // there too, so the slot is not ours either, and they leave it alone (backlog/67).
2741                return ReqOutcome::Reply(r);
2742            }
2743            while let Some(b) = self.try_console_read() {
2744                // q/Q/ESC aborts IMMEDIATELY - never wait for the in-flight reply (that wait was the
2745                // "it pauses instead of quitting" complaint). The peer's late reply lands in our endpoint
2746                // and the drain atop the NEXT abortable request clears it, so it pollutes nothing.
2747                // "Immediately" still holds: the abort does not wait on the peer. What the block adds is
2748                // up to one poll interval before the keypress is LOOKED at - tens of milliseconds, under
2749                // the threshold at which a person can tell, and the same trade the observe loop makes.
2750                if b == b'q' || b == b'Q' || b == 0x1b { return ReqOutcome::Aborted; }
2751            }
2752            if self.epoch_secs_monotonic() - t0 >= max_secs {
2753                return ReqOutcome::Timeout;
2754            }
2755        }
2756    }
2757
2758    /// Like [`Self::request_with_reply_abortable`], but if no reply has arrived after
2759    /// `hint_after_secs` it invokes `on_linger` ONCE (e.g. to print a "[q] quit" hint) and keeps
2760    /// waiting/aborting. A snappy reply never fires the hint, so a fast request stays silent and only
2761    /// a genuinely lingering wait tells the user they can bail. Abort semantics are identical to
2762    /// `request_with_reply_abortable` (q/Q/ESC -> `Aborted` immediately; the request is sent once and
2763    /// a late reply is drained atop the next abortable/qhint request). The hint text lives in the
2764    /// caller's closure, so this stays mechanism - the SDK provides the *timing*, the caller the UX.
2765    fn request_with_reply_qhint_inner(
2766        &self,
2767        peer: &str,
2768        msg:  &crate::ipc::Message,
2769        hint_after_secs: i64,
2770        max_secs: i64,
2771        leave_keys: &[u8],
2772        on_linger: impl FnOnce(),
2773        mut mine: impl FnMut(&crate::ipc::Message) -> bool,
2774        keep: Option<&dyn Fn(u8)>,
2775    ) -> ReqOutcome {
2776        // Drain any stale reply a prior INSTANT-abort left in our endpoint (see the abortable variant).
2777        while self.try_recv().is_some() {}
2778        let target = match self.find_send_slot(peer) { Some(s) => CapHandle(s), None => return ReqOutcome::Timeout };
2779        let self_grant = match self.self_grant_handle() { Some(g) => g, None => return ReqOutcome::Timeout };
2780        let reply_cap = match self.derive_cap(self_grant) { Some(c) => c, None => return ReqOutcome::Timeout };
2781        if self.offer_request(target, reply_cap, msg).is_err() {
2782            self.remove_cap(reply_cap);
2783            return ReqOutcome::Timeout;
2784        }
2785        let t0 = self.epoch_secs_monotonic();
2786        let mut on_linger = Some(on_linger);   // FnOnce, fired at most once when the wait lingers
2787        loop {
2788            // Block, do not spin - see `request_with_reply_abortable`. This is the variant `net`/`ping`
2789            // actually use (the "[q] quit" hint), so it is the one that kept core 0 permanently
2790            // busy during a continuous ping and starved the idle-path USB hot-plug watch.
2791            //
2792            // THE CONSOLE IS NOT TOUCHED UNTIL `[q] quit` HAS BEEN SHOWN. Before the notice the
2793            // operator has been offered nothing to press, so a key typed then is typing ahead, not an
2794            // answer: reading it would either lose it or - a `q` meant for the NEXT command, a pager
2795            // say - end this wait. And most requests are answered before the notice is due, so they
2796            // now block in the kernel for the whole wait instead of polling the console every slice
2797            // (`backlog/fixed/80` H16: both of those made the shell keep its noticing waits out of scripts
2798            // and pipelines).
2799            let noticed = on_linger.is_none();
2800            let slice = if noticed { Self::AWAIT_SLICE_MS } else { Self::PRE_NOTICE_SLICE_MS };
2801            if let Some(r) = self.await_slice(slice) {
2802                // DO NOT remove the reply cap on a REPLY. The send already removed it.
2803                //
2804                // §8.5: a cap embedded in a message "is transferred and REMOVED from sender's table".
2805                // The instant the request went out this slot stopped being ours, and by the time the
2806                // reply lands the kernel has REUSED it - `CapTable::insert` hands out the first EMPTY
2807                // slot, and this one is empty. When the reply carries an embedded cap (a file cap from
2808                // `fs`), that cap lands in exactly this slot, so removing "the reply cap" DELETES IT.
2809                //
2810                // Measured on the Pi 4: `fcap` printed `file=12 reply=12` - the file handle and the
2811                // next derived cap were the same slot, which is only possible if the slot was empty.
2812                // Every file-cap read/write then failed before reaching `fs`, and two negative
2813                // sub-checks "passed" vacuously because no invoke could get that far.
2814                //
2815                // A remove-by-stale-index can bite ANY request whose reply carries a cap, not just
2816                // fcap - and the abort and timeout paths below are no different: the send DID deliver
2817                // there too, so the slot is not ours either, and they leave it alone (backlog/67).
2818                //
2819                // Asked at the moment of arrival, so a cap the message carries is still pending for
2820                // `mine` to take; `false` hands the message to the caller and the wait goes on.
2821                if mine(&r) {
2822                    return ReqOutcome::Reply(r);
2823                }
2824            }
2825            if noticed {
2826                while let Some(b) = self.try_console_read() {
2827                    if leave_keys.contains(&b) { return ReqOutcome::Aborted; }
2828                    // Not a leave key: the operator's typing, handed back when the caller can keep it.
2829                    if let Some(k) = keep { k(b); }
2830                }
2831            }
2832            let elapsed = self.epoch_secs_monotonic() - t0;
2833            if elapsed >= hint_after_secs {
2834                if let Some(f) = on_linger.take() { f(); }
2835            }
2836            if elapsed >= max_secs {
2837                return ReqOutcome::Timeout;
2838            }
2839        }
2840    }
2841
2842    /// Wait for the next message on our own recv endpoint, ABORTABLE (q/Q/ESC) and bounded by an RTC
2843    /// deadline, WITHOUT sending anything. The failure-aware twin of [`Self::recv`]: where `recv`
2844    /// blocks forever (and panics on error), this returns [`ReqOutcome::Timeout`] if no message
2845    /// arrives within `max_secs` and [`ReqOutcome::Aborted`] the instant the user presses q. A receive
2846    /// ERROR is not reported: it reads as no message, so the wait runs to its deadline. Use it to
2847    /// await a reply we already sent by some path OTHER than a named-peer request - a badged
2848    /// `resource_invoke` (a file/socket capability), or draining a pipe filter's stream - where
2849    /// [`Self::request_with_reply_abortable`] (which does its own send) does not fit. This is the wait
2850    /// half of the abortable request, factored out: a peer that received our invocation but died before
2851    /// replying, or a filter that wedges mid-stream, can no longer hang us (Commandment VIII - wait on
2852    /// truth *including failure*). The caller owns any reply cap it derived and reclaims it only when the send failed (backlog/67), not on every
2853    /// outcome. A service with no console foreground never sees input, so this degrades to a plain
2854    /// deadline wait. Does NOT drain a stale reply first - a caller that can be re-entered after an
2855    /// abort should `while self.try_recv().is_some() {}` before it sends (as the request variants do).
2856    pub fn recv_abortable_deadline(&self, max_secs: i64) -> ReqOutcome {
2857        self.recv_abortable_deadline_keeping(max_secs, &|_| {})
2858    }
2859
2860    /// [`Self::recv_abortable_deadline`] that HANDS BACK every key it reads that is not a quit key, in
2861    /// order, as [`Self::request_with_reply_qhint_keeping`] does for a request. The plain form reads the
2862    /// console to see a `q` and discards everything else, so what the operator typed while a pipe stage
2863    /// ran was lost: `ask`'s answer takes seconds, and the next command typed during it vanished in QEMU
2864    /// (the receive-side half of `backlog/fixed/80` H16).
2865    pub fn recv_abortable_deadline_keeping(&self, max_secs: i64, keep: &dyn Fn(u8)) -> ReqOutcome {
2866        self.recv_abortable_deadline_noticing(max_secs, i64::MAX, &|| {}, keep)
2867    }
2868
2869    /// [`Self::recv_abortable_deadline_keeping`] that calls `on_linger` ONCE when no message has come
2870    /// after `hint_after_secs` - the receive-side twin of [`Self::request_with_reply_qhint`]. The SDK
2871    /// supplies the timing and the caller the words, as there: a task with no prompt has nothing to
2872    /// print, so this never writes to the console itself.
2873    pub fn recv_abortable_deadline_noticing(
2874        &self, max_secs: i64, hint_after_secs: i64, on_linger: &dyn Fn(), keep: &dyn Fn(u8),
2875    ) -> ReqOutcome {
2876        let t0 = self.epoch_secs_monotonic();
2877        let mut lingered = false;
2878        loop {
2879            // Block, do not spin - see `await_slice`.
2880            if let Some(r) = self.await_slice(Self::AWAIT_SLICE_MS) { return ReqOutcome::Reply(r); }
2881            while let Some(b) = self.try_console_read() {
2882                if b == b'q' || b == b'Q' || b == 0x1b { return ReqOutcome::Aborted; }
2883                keep(b);
2884            }
2885            let elapsed = self.epoch_secs_monotonic() - t0;
2886            if !lingered && elapsed >= hint_after_secs {
2887                lingered = true;
2888                on_linger();
2889            }
2890            if elapsed >= max_secs {
2891                return ReqOutcome::Timeout;
2892            }
2893        }
2894    }
2895
2896    /// Reacquire a fresh SEND cap to `peer` and point the named-peer cache at it, so subsequent
2897    /// `try_send(peer)` / `send(peer)` use the new cap. Returns `false` if `peer` cannot currently
2898    /// be resolved (e.g. it has not finished respawning) - the caller should retry on a later tick.
2899    ///
2900    /// A thin shim over `reacquire_cap` (syscall 10): name resolution is the **kernel name
2901    /// directory**, not a service. The directory is populated synchronously at each service's spawn,
2902    /// so there is no round-trip and no bootstrap chicken-and-egg (the directory lives in the kernel,
2903    /// always reachable). `reacquire_cap` also updates the send-cap cache.
2904    #[must_use = "the peer is NOT reacquired if this is false"]
2905    pub fn reacquire_by_name(&self, peer: &str) -> bool {
2906        self.reacquire_cap(peer).is_ok()
2907    }
2908
2909    /// Handle to this service's `SEND|GRANT` cap to its **own** endpoint, minted at
2910    /// spawn. A service hands a copy of its endpoint to a peer by deriving one
2911    /// (`derive_cap`) and granting it across - keeping this original so it can derive
2912    /// again later. `None` if the service has no endpoint.
2913    pub fn self_grant_handle(&self) -> Option<crate::capability::CapHandle> {
2914        let slot = Self::ctx().self_grant_slot;
2915        if slot == u32::MAX { None } else { Some(crate::capability::CapHandle(slot)) }
2916    }
2917
2918    /// The send cap for a named peer, or `None` if this service has no such peer.
2919    ///
2920    /// The public form of what `request_with_reply` resolves internally. Exposed for the case that
2921    /// helper cannot serve: a request whose reply must NOT be waited for. A single-threaded service that
2922    /// owns an in-memory answer (the `time` service and its wall clock) has to be able to send a
2923    /// best-effort request to a slow peer without becoming unable to answer its own clients meanwhile -
2924    /// so it pairs this with `derive_cap` + `send_with_cap_by_handle` and picks the reply up later, out
2925    /// of its ordinary receive loop, matched by the protocol's correlation tag.
2926    pub fn send_peer_handle(&self, peer: &str) -> Option<crate::capability::CapHandle> {
2927        self.find_send_slot(peer).map(crate::capability::CapHandle)
2928    }
2929
2930    /// Return the cap handle for the Nth send-peer entry (0-indexed).
2931    ///
2932    /// Used by property-test probes (P9) to access multiple cap slots wired to
2933    /// the same endpoint, verifying all are invalidated on endpoint death (§7.5).
2934    pub fn send_peer_at(&self, idx: usize) -> Option<crate::capability::CapHandle> {
2935        let data  = Self::ctx();
2936        let count = (data.send_peer_count as usize).min(MAX_SEND_PEERS);
2937        if idx >= count { return None; }
2938        let slot = data.send_peers[idx].slot;
2939        if slot == u32::MAX { None } else { Some(crate::capability::CapHandle(slot)) }
2940    }
2941
2942    /// Send to a specific cap handle directly, bypassing peer-name lookup.
2943    ///
2944    /// Used by the probe service to test kernel cap enforcement (§22 Tests 3B, 9B).
2945    pub fn try_send_by_handle(
2946        &self,
2947        handle: crate::capability::CapHandle,
2948        msg:    &crate::ipc::Message,
2949    ) -> Result<(), crate::ipc::IpcError> {
2950        crate::ipc::try_send(handle, msg)
2951    }
2952
2953    /// Send a message to a named peer WITH an embedded capability grant.
2954    ///
2955    /// The send-peer cap (which must carry the `GRANT` right) is transferred to
2956    /// the receiver. On success the calling service loses that cap (§7.6).
2957    /// Returns `CapNotGrantable` if the cap lacks `GRANT` - the cap is kept.
2958    pub fn send_with_cap(&self, peer: &str, msg: &crate::ipc::Message) -> Result<(), crate::ipc::IpcError> {
2959        let slot = self.find_send_slot(peer)
2960            .ok_or(crate::ipc::IpcError::CapError(crate::capability::CapError::CapNotHeld))?;
2961        // syscall 11 = SendWithCap
2962        // arg0 = (grant_slot << 16) | endpoint_slot - same slot holds both SEND and GRANT.
2963        let packed  = ((slot as u64) << 16) | (slot as u64);
2964        let payload = msg.payload_bytes();
2965        // SAFETY: syscall(11) = SendWithCap; packed and payload are from user space.
2966        let ret = unsafe {
2967            raw_syscall(11, packed, payload.as_ptr() as u64, payload.len() as u64)
2968        };
2969        if ret == 0 { Ok(()) } else { Err(crate::ipc::i64_to_ipc_error(ret)) }
2970    }
2971
2972    /// Take the next pending received capability, if any.
2973    ///
2974    /// After `recv()` delivers a message containing an embedded cap, the kernel
2975    /// installs the cap into this task's table and queues the slot index. Call
2976    /// this once per embedded cap to retrieve each one.
2977    pub fn take_pending_cap(&self) -> Option<CapHandle> {
2978        // SAFETY: syscall(12) = TakePendingCap; no args.
2979        let ret = unsafe { raw_syscall(12, 0, 0, 0) };
2980        if ret >= 0 { Some(CapHandle(ret as u32)) } else { None }
2981    }
2982
2983    /// Acquire a fresh SEND|GRANT cap to `peer` via the kernel name directory.
2984    ///
2985    /// Used by property-test probes that need to transfer capabilities (P3).
2986    /// Returns `None` if the service is not registered or the cap table is full.
2987    pub fn acquire_send_grant_cap(&self, peer: &str) -> Option<CapHandle> {
2988        let bytes = peer.as_bytes();
2989        let len   = bytes.len();
2990        if len == 0 || len > NAME_MAX { return None; }
2991        // SAFETY: syscall(10) = AcquireSendCap; arg2=1 requests SEND|GRANT.
2992        let ret = unsafe { raw_syscall(10, bytes.as_ptr() as u64, len as u64, 1) };
2993        if ret < 0 { None } else { Some(CapHandle(ret as u32)) }
2994    }
2995
2996    /// Acquire a fresh SEND cap to `peer` via the kernel name directory.
2997    ///
2998    /// Returns the new cap handle, or `None` if the name is not registered.
2999    pub fn acquire_send_cap(&self, peer: &str) -> Option<CapHandle> {
3000        let bytes = peer.as_bytes();
3001        let len   = bytes.len();
3002        if len == 0 || len > NAME_MAX { return None; }
3003        // SAFETY: syscall(10) = AcquireSendCap; arg2=0 = SEND only.
3004        let ret = unsafe { raw_syscall(10, bytes.as_ptr() as u64, len as u64, 0) };
3005        if ret < 0 { None } else { Some(CapHandle(ret as u32)) }
3006    }
3007
3008    /// Query the current generation of the named endpoint.
3009    ///
3010    /// Returns the generation counter as a u64, or 0 if the name is not
3011    /// registered OR the caller lacks INTROSPECT (query 2 is gated). Used by
3012    /// property tests P2 and P8 (§7.5, §14.2).
3013    pub fn inspect_endpoint_generation(&self, name: &str) -> u64 {
3014        let bytes = name.as_bytes();
3015        let len   = bytes.len();
3016        if len == 0 || len > NAME_MAX { return 0; }
3017        // SAFETY: syscall(13) = InspectKernel; query_id=2 = endpoint generation by name.
3018        let ret = unsafe {
3019            raw_syscall(13, 2, bytes.as_ptr() as u64, len as u64)
3020        };
3021        if ret < 0 { 0 } else { ret as u64 }
3022    }
3023
3024    /// One byte from the COM2 operator channel, or `None` when the port is empty (kernel query 21).
3025    ///
3026    /// The kernel owns the UART - hardware, and §11.4 already sanctions it owning a serial console -
3027    /// and hands bytes out; the `control` service owns what they MEAN. Transport in the kernel,
3028    /// interpretation in a service (C1-6).
3029    pub fn com2_byte(&self) -> Option<u8> {
3030        // SAFETY: syscall(13) = InspectKernel; query 21 = pop one COM2 byte, -1 when empty.
3031        let v = unsafe { raw_syscall(13, 21, 0, 0) };
3032        if v < 0 { None } else { Some(v as u8) }
3033    }
3034
3035    /// The board's own MAC address (kernel query 23), or `None` where the board cannot say.
3036    ///
3037    /// A NIC driver runs in userspace and cannot reach the board identity: on the Pi this address lives
3038    /// in the VideoCore mailbox, nowhere near the controller's register window. Without it a driver has
3039    /// to invent an address, and an invented address that is hardcoded is the same on every board -
3040    /// which stops being harmless the moment two of them share a network.
3041    pub fn board_mac(&self) -> Option<[u8; 6]> {
3042        // SAFETY: syscall(13) = InspectKernel; query 23 = the board MAC packed little-endian, -1 if none.
3043        let v = unsafe { raw_syscall(13, 23, 0, 0) };
3044        if v < 0 { return None; }
3045        let v = v as u64;
3046        Some([v as u8, (v >> 8) as u8, (v >> 16) as u8,
3047              (v >> 24) as u8, (v >> 32) as u8, (v >> 40) as u8])
3048    }
3049
3050    /// Inject a test interrupt. Requires the FIRE_IRQ capability, held only by `control`; a non-holder
3051    /// gets `false` rather than a silent no-op.
3052    pub fn fire_irq(&self, irq: u8) -> bool {
3053        // SAFETY: syscall(51) = FireIrq; the kernel validates FIRE_IRQ by holdings.
3054        unsafe { raw_syscall(51, irq as u64, 0, 0) == 0 }
3055    }
3056
3057    /// Return the bytes dynamically allocated by this task so far.
3058    ///
3059    /// Wraps InspectKernel query 0. Used by property test P4 (§10.3).
3060    pub fn inspect_kernel_alloc_bytes(&self) -> u64 {
3061        // SAFETY: syscall(13) = InspectKernel; query_id=0 = task alloc bytes.
3062        let ret = unsafe { raw_syscall(13, 0, 0, 0) };
3063        if ret < 0 { 0 } else { ret as u64 }
3064    }
3065
3066    /// Return the count of live endpoints in the kernel routing table.
3067    ///
3068    /// Wraps InspectKernel query 1 (INTROSPECT-gated; 0 when refused). Used by property test P5 (§8.3).
3069    pub fn inspect_kernel_endpoint_count(&self) -> Option<u32> {
3070        // SAFETY: syscall(13) = InspectKernel; query_id=1 = live endpoint count.
3071        let ret = unsafe { raw_syscall(13, 1, 0, 0) };
3072        // A refusal (no INTROSPECT) or an error is `None`, never a value: it read as 0 until
3073        // 2026-10-10, a believable count of nothing (`backlog/fixed/80` K18).
3074        if ret < 0 { None } else { Some(ret as u32) }
3075    }
3076
3077    /// Capacity of the in-kernel USB mass-storage device in 512-byte sectors, 0 if none is attached.
3078    /// Requires the `USB_DISK_RESOURCE` capability. Syscall 46. The kernel drives no USB disk on any
3079    /// port now (see [`USB_DISK_BUSY`]), so this is always 0 and the block calls below always fail.
3080    pub fn usb_disk_sectors(&self) -> u64 {
3081        // SAFETY: syscall(46) = UsbDiskInfo; no arguments, gated by the USB_DISK capability.
3082        let ret = unsafe { raw_syscall(46, 0, 0, 0) };
3083        if ret < 0 { 0 } else { ret as u64 }
3084    }
3085
3086    /// Read the 512-byte block at `lba` from the USB mass-storage device into `dst`. Returns false if
3087    /// there is no device, the LBA is past the end, or the transfer failed. Requires `USB_DISK_RESOURCE`.
3088    /// Syscall 47.
3089    #[must_use = "the destination buffer is NOT valid data if this is false"]
3090    pub fn usb_disk_read(&self, lba: u64, dst: &mut [u8; 512]) -> bool {
3091        // SAFETY: syscall(47) = UsbDiskRead; the kernel writes exactly 512 bytes to `dst` on success,
3092        // through its checked user-pointer path.
3093        if !lba_fits_syscall_abi(lba) { return false; }
3094        let ret = unsafe { raw_syscall(47, lba, dst.as_mut_ptr() as u64, 0) };
3095        ret == 0
3096    }
3097
3098    /// Write `src` as the 512-byte block at `lba` on the USB mass-storage device. Requires `USB_DISK_RESOURCE`.
3099    /// Syscall 48.
3100    #[must_use = "the block did NOT reach the medium if this is false"]
3101    pub fn usb_disk_write(&self, lba: u64, src: &[u8; 512]) -> bool {
3102        // SAFETY: syscall(48) = UsbDiskWrite; the kernel reads exactly 512 bytes from `src` through its
3103        // checked user-pointer path.
3104        if !lba_fits_syscall_abi(lba) { return false; }
3105        let ret = unsafe { raw_syscall(48, lba, src.as_ptr() as u64, 0) };
3106        ret == 0
3107    }
3108
3109    /// Read a block, returning the raw status so BUSY is distinguishable from FAILED: `0` ok, the
3110    /// device is busy (`USB_DISK_BUSY` - re-ask, nothing is wrong), anything else a real error. The `bool` variants
3111    /// above collapse those two, which is exactly the conflation that made a busy stick look broken.
3112    pub fn usb_disk_read_status(&self, lba: u64, dst: &mut [u8; 512]) -> i64 {
3113        // SAFETY: syscall(47) = UsbDiskRead; the kernel writes exactly 512 bytes through its checked
3114        // user-pointer path.
3115        if !lba_fits_syscall_abi(lba) { return -1; }
3116        unsafe { raw_syscall(47, lba, dst.as_mut_ptr() as u64, 0) }
3117    }
3118
3119    /// Write a block, returning the raw status (see `usb_disk_read_status`).
3120    pub fn usb_disk_write_status(&self, lba: u64, src: &[u8; 512]) -> i64 {
3121        // SAFETY: syscall(48) = UsbDiskWrite; the kernel reads exactly 512 bytes from `src`.
3122        if !lba_fits_syscall_abi(lba) { return -1; }
3123        unsafe { raw_syscall(48, lba, src.as_ptr() as u64, 0) }
3124    }
3125
3126    /// Make previously written blocks durable on the USB mass-storage device (SCSI SYNCHRONIZE CACHE).
3127    /// Requires `USB_DISK_RESOURCE` WRITE. Syscall 49.
3128    ///
3129    /// A write is only ACKNOWLEDGED when `usb_disk_write` returns - the device may still be holding the
3130    /// bytes in a volatile buffer. Anything that promises durability (a format, a journal commit) has to
3131    /// ask for it, and check the answer: `false` means the data is NOT known to be on the medium.
3132    #[must_use = "prior writes are NOT durable if this is false"]
3133    pub fn usb_disk_flush(&self) -> bool {
3134        // SAFETY: syscall(49) = UsbDiskFlush; takes no arguments and touches no user memory.
3135        let ret = unsafe { raw_syscall(49, 0, 0, 0) };
3136        ret == 0
3137    }
3138
3139    /// The SD/EMMC controller's base clock in Hz (0 if the platform does not report one), via
3140    /// InspectKernel query 20. The block driver needs it to compute its clock divider: on the BCM283x
3141    /// the controller's own capability register reports the base clock wrongly, and the driver holds
3142    /// only its controller's registers, so it cannot ask the platform firmware itself. A driver that
3143    /// gets 0 should report that rather than guess - a wrong divider runs the card's identification
3144    /// clock at the wrong speed, which fails on hardware and not under emulation. Ungated - task-neutral
3145    /// hardware info, like the console geometry and the RTC.
3146    pub fn emmc_base_clock_hz(&self) -> u32 {
3147        // SAFETY: syscall(13) = InspectKernel; query_id=20 = EMMC base clock (Hz).
3148        let ret = unsafe { raw_syscall(13, 20, 0, 0) };
3149        if ret < 0 { 0 } else { ret as u32 }
3150    }
3151
3152    /// The discovered NIC's PCI identity as `vendor | device<<16` (0 if no NIC), via InspectKernel
3153    /// query 14. A NIC driver reads it to know which chip it is driving (e.g. Intel e1000 =
3154    /// 0x100E_8086 vs Realtek RTL8168 = 0x8168_10EC). Ungated - task-neutral hardware info.
3155    pub fn nic_vendor_device(&self) -> u32 {
3156        // SAFETY: syscall(13) = InspectKernel; query_id=14 = NIC vendor|device.
3157        let ret = unsafe { raw_syscall(13, 14, 0, 0) };
3158        if ret < 0 { 0 } else { ret as u32 }
3159    }
3160
3161    /// Whether the PCI scan found an xHCI USB host controller (InspectKernel query 18, ungated
3162    /// task-neutral hardware fact). The supervisor reads it to skip spawning the `xhci` driver on a
3163    /// machine that has no xHCI controller (an idle driver would busy-hold a core). Falls back to
3164    /// `true` if the query is unavailable, preserving the always-spawn behaviour.
3165    pub fn xhci_present(&self) -> bool {
3166        // SAFETY: syscall(13) = InspectKernel; query_id=18 = USB controller presence bitmask.
3167        let ret = unsafe { raw_syscall(13, 18, 0, 0) };
3168        if ret < 0 { return true; } // query unavailable - spawn as before
3169        (ret & 0b01) != 0
3170    }
3171
3172    /// Whether the PCI scan found an EHCI (USB 2.0) host controller (InspectKernel query 18, ungated).
3173    /// The supervisor reads it to skip spawning the `ehci` driver on a machine with no EHCI at all
3174    /// (e.g. the Wyse 5070), so an idle driver does not busy-hold a core. Falls back to `true` if the
3175    /// query is unavailable, preserving the always-spawn behaviour.
3176    pub fn ehci_present(&self) -> bool {
3177        // SAFETY: syscall(13) = InspectKernel; query_id=18 = USB controller presence bitmask.
3178        let ret = unsafe { raw_syscall(13, 18, 0, 0) };
3179        if ret < 0 { return true; } // query unavailable - spawn as before
3180        (ret & 0b10) != 0
3181    }
3182
3183    /// Whether the PCI scan found a NIC this build can actually drive (present AND an e1000 or RTL8168),
3184    /// via InspectKernel query 18 (ungated). The supervisor reads it to skip spawning `nic-driver` (and
3185    /// its dependent `net-stack`) on a machine with no usable NIC, so they do not busy-hold cores.
3186    /// Falls back to `true` if the query is unavailable, preserving the always-spawn behaviour.
3187    pub fn nic_present(&self) -> bool {
3188        // SAFETY: syscall(13) = InspectKernel; query_id=18 = hardware-driver presence bitmask.
3189        let ret = unsafe { raw_syscall(13, 18, 0, 0) };
3190        if ret < 0 { return true; } // query unavailable - spawn as before
3191        (ret & 0b100) != 0
3192    }
3193
3194    /// The NIC's register-space MMIO base (the BAR the PCI scan chose), 0 if none. InspectKernel query
3195    /// 15, ungated. A diagnostic - a driver reads it to confirm which BAR it was handed.
3196    pub fn nic_mmio_base(&self) -> u64 {
3197        // SAFETY: syscall(13) = InspectKernel; query_id=15 = NIC MMIO base.
3198        let ret = unsafe { raw_syscall(13, 15, 0, 0) };
3199        if ret < 0 { 0 } else { ret as u64 }
3200    }
3201
3202    /// Return the number of free physical frames.
3203    ///
3204    /// Wraps InspectKernel query 4. Queries 4-8 are INTROSPECT-gated, and these wrappers read a
3205    /// refusal as 0 (or, for the core count, 1) - a value the caller cannot tell from a real one.
3206    pub fn inspect_kernel_free_frames(&self) -> Option<u64> {
3207        // SAFETY: syscall(13) = InspectKernel; query_id=4 = free frame count.
3208        let ret = unsafe { raw_syscall(13, 4, 0, 0) };
3209        // A refusal (no INTROSPECT) or an error is `None`, never a value: it read as 0 until
3210        // 2026-10-10, a believable count of nothing (`backlog/fixed/80` K18).
3211        if ret < 0 { None } else { Some(ret as u64) }
3212    }
3213
3214    /// Return the total usable physical frames at boot time.
3215    ///
3216    /// Wraps InspectKernel query 5.
3217    pub fn inspect_kernel_total_frames(&self) -> Option<u64> {
3218        // SAFETY: syscall(13) = InspectKernel; query_id=5 = total frame count.
3219        let ret = unsafe { raw_syscall(13, 5, 0, 0) };
3220        // A refusal (no INTROSPECT) or an error is `None`, never a value: it read as 0 until
3221        // 2026-10-10, a believable count of nothing (`backlog/fixed/80` K18).
3222        if ret < 0 { None } else { Some(ret as u64) }
3223    }
3224
3225    /// The endpoint task `slot` OWNS, or 0 if it owns none (InspectKernel query 24, INTROSPECT).
3226    ///
3227    /// With [`Self::task_awaits_endpoint`] this is the whole of the blocked-chain walk: map the
3228    /// endpoint a stuck task awaits back to the task that owns it, then ask what THAT one awaits.
3229    /// See `utilities/47_events.md`.
3230    pub fn task_own_endpoint(&self, slot: u32) -> Option<u64> {
3231        // SAFETY: syscall(13) = InspectKernel; query_id=24 = the task's own endpoint.
3232        let ret = unsafe { raw_syscall(13, 24, slot as u64, 0) };
3233        // A refusal (no INTROSPECT) or an error is `None`, never a value: it read as 0 until
3234        // 2026-10-10, a believable count of nothing (`backlog/fixed/80` K18).
3235        if ret < 0 { None } else { Some(ret as u64) }
3236    }
3237
3238    /// The endpoint task `slot` is blocked-in-CALL awaiting a reply from, or 0 if no call is in
3239    /// flight (InspectKernel query 25, INTROSPECT).
3240    ///
3241    /// A best-effort snapshot, on the same contract as [`Self::task_stat`]: the kernel reads it
3242    /// without taking the routing lock, so an observer cannot stall the thing it observes.
3243    pub fn task_awaits_endpoint(&self, slot: u32) -> Option<u64> {
3244        // SAFETY: syscall(13) = InspectKernel; query_id=25 = the endpoint awaited in a CALL.
3245        let ret = unsafe { raw_syscall(13, 25, slot as u64, 0) };
3246        // A refusal (no INTROSPECT) or an error is `None`, never a value: it read as 0 until
3247        // 2026-10-10, a believable count of nothing (`backlog/fixed/80` K18).
3248        if ret < 0 { None } else { Some(ret as u64) }
3249    }
3250
3251    /// The kernel's BOOT RECORD size: `(held, capacity)` in bytes, or `None` without INTROSPECT
3252    /// (InspectKernel query 27). The record is a fixed copy of the first bytes ever logged, which
3253    /// never wraps; `held == capacity` means it filled, and later lines are only in the kernel's ring
3254    /// (until it wraps) and on serial.
3255    pub fn boot_record_size(&self) -> Option<(usize, usize)> {
3256        let held = self.boot_record_query(0, 0);
3257        let cap = self.boot_record_query(1, 0);
3258        if held < 0 || cap < 0 { None } else { Some((held as usize, cap as usize)) }
3259    }
3260
3261    /// Copy the boot record from `offset` into `buf`: `Some(n)` bytes, `Some(0)` at the end, `None`
3262    /// refused (InspectKernel query 27, INTROSPECT). The buffer is the kernel's chunk size exactly, so
3263    /// no read can ask it to write past the end of one.
3264    pub fn boot_record_read(&self, offset: usize, buf: &mut [u8; BOOT_READ_CHUNK]) -> Option<usize> {
3265        let r = self.boot_record_query(offset as u64, buf.as_mut_ptr() as u64);
3266        if r < 0 { None } else { Some(r as usize) }
3267    }
3268
3269    fn boot_record_query(&self, a1: u64, a2: u64) -> i64 {
3270        // SAFETY: syscall(13) = InspectKernel; query_id=27 = the boot record. `a2` is 0 or a buffer of
3271        // BOOT_READ_CHUNK bytes the caller holds mutably; the kernel validates the range before writing.
3272        unsafe { raw_syscall(13, 27, a1, a2) }
3273    }
3274
3275    /// The wall-clock datetime captured by the kernel at **boot** (InspectKernel query 12, ungated).
3276    /// Same packed layout as `datetime`. Pairs with `datetime` to compute uptime as a wall-clock
3277    /// delta - portable across timer modes (a tick counter's rate is not: periodic-mode QEMU ticks
3278    /// at ~10 Hz, TSC-deadline HW at 100 Hz). Returns the epoch (all-zero fields) if not captured.
3279    pub fn boot_datetime(&self) -> Datetime {
3280        // SAFETY: syscall(13) = InspectKernel; query_id=12 = packed boot datetime.
3281        let p = unsafe { raw_syscall(13, 12, 0, 0) } as u64;
3282        Self::unpack_datetime(p)
3283    }
3284
3285    /// System uptime in **seconds** = now − boot, both from the hardware RTC. Never negative
3286    /// (saturates at 0). The `uptime` shell command renders this. Wall-clock based, so it is
3287    /// correct regardless of the APIC timer mode (unlike a raw tick counter).
3288    pub fn uptime_secs(&self) -> i64 {
3289        // "now" is the DEGLITCHED MONOTONIC clock (query 17), not the raw RTC datetime (which is frozen
3290        // at 0 on a board with no RTC, e.g. the Pi 2 - the reason the old `datetime - boot` read 0 there).
3291        let now = self.epoch_secs_monotonic();
3292        let boot_epoch = self.boot_datetime().epoch_secs();
3293        // A board with no RTC reports a zero/garbage boot datetime whose epoch is far before 2001 (a
3294        // packed all-zeros datetime unpacks to ~year 0 = ~-6.2e10 s - subtracting THAT would ADD ~1970
3295        // years). There the monotonic clock is already seconds-since-boot (its CNTPCT baseline, ARM), so
3296        // it IS the uptime. With a real RTC, uptime is the elapsed wall-clock since boot.
3297        if boot_epoch < 1_000_000_000 { now.max(0) } else { (now - boot_epoch).max(0) }
3298    }
3299
3300    /// Timer ticks the given core spent running a user task (not idle) since boot.
3301    ///
3302    /// Wraps InspectKernel query 6 (arg1 = core index).
3303    pub fn inspect_core_active_ticks(&self, core: u32) -> Option<u64> {
3304        // SAFETY: syscall(13) = InspectKernel; query_id=6, arg1=core.
3305        let ret = unsafe { raw_syscall(13, 6, core as u64, 0) };
3306        // A refusal (no INTROSPECT) or an error is `None`, never a value: it read as 0 until
3307        // 2026-10-10, a believable count of nothing (`backlog/fixed/80` K18).
3308        if ret < 0 { None } else { Some(ret as u64) }
3309    }
3310
3311    /// Total timer ticks seen on the given core since boot.
3312    ///
3313    /// Wraps InspectKernel query 7 (arg1 = core index).
3314    pub fn inspect_core_total_ticks(&self, core: u32) -> Option<u64> {
3315        // SAFETY: syscall(13) = InspectKernel; query_id=7, arg1=core.
3316        let ret = unsafe { raw_syscall(13, 7, core as u64, 0) };
3317        // A refusal (no INTROSPECT) or an error is `None`, never a value: it read as 0 until
3318        // 2026-10-10, a believable count of nothing (`backlog/fixed/80` K18).
3319        if ret < 0 { None } else { Some(ret as u64) }
3320    }
3321
3322    /// Number of CPU cores ready since boot.
3323    ///
3324    /// Wraps InspectKernel query 8.
3325    pub fn inspect_core_count(&self) -> u32 {
3326        // SAFETY: syscall(13) = InspectKernel; query_id=8.
3327        let ret = unsafe { raw_syscall(13, 8, 0, 0) };
3328        // A refusal or an error is NOT "one core": it said 1 until 2026-10-10, so a caller without
3329        // INTROSPECT reported a one-core machine (`backlog/fixed/80` H20). 0 is "the kernel did not say", and
3330        // a caller must not present it as a count.
3331        if ret <= 0 { 0 } else { ret as u32 }
3332    }
3333
3334    /// Terminal geometry as `(rows, cols)` text cells, or `(0, 0)` if it cannot be determined.
3335    ///
3336    /// **Asked of the `console` service, not the kernel.** It used to be `InspectKernel` query 9, which
3337    /// is now deleted: rows and columns are derived from the safe-area inset, the cell size and the
3338    /// font-scale rule, all of which live in the terminal - so the terminal is the only party that can
3339    /// answer, and asking anyone else would be a second source of truth (Commandment III).
3340    ///
3341    /// `(0, 0)` means **unknown**, and callers already treat it that way explicitly rather than
3342    /// substituting a size behind the user's back (the pager prints unpaged; `edit` falls back to 24x80
3343    /// and says so). It is returned when there is no console service, when it is mid-restart, or when it
3344    /// holds no framebuffer - all cases where a guessed geometry would be a silent fallback.
3345    ///
3346    /// Costs one IPC round trip, so callers that lay out a screen should ask once and keep the answer
3347    /// for that screen rather than per line.
3348    pub fn console_dims(&self) -> (u16, u16) {
3349        let mut buf = [0u8; 8];
3350        // Opcode 1 = REQ_DIMS (`services/console/src/main.rs`). The reply is rows then cols, u16 LE.
3351        // ACQUIRE THE PEER BY NAME IF WE WERE NEVER WIRED TO IT.
3352        //
3353        // The shell's contract declares `console` as a send peer, but the supervisor cannot wire it:
3354        // the shell is spawned BEFORE the console service exists, so there is nothing to hand it. The
3355        // kernel name directory is exactly the answer to that (§14.3 - reacquire by name), and
3356        // `time_rpc` in the shell already does this. This did not, so `find_send_slot` missed, the
3357        // request returned None, and the caller read (0, 0).
3358        //
3359        // Zero rows is not a small error here: `cmd_help` takes it to mean "no terminal" and prints
3360        // its whole table instead of paging it. A feature disappeared because a lookup missed, which
3361        // is the quiet kind of failure §26.7 is about - nothing was reported, it simply stopped
3362        // behaving.
3363        let mut n = self.request_with_reply_deadline_into("console", &[1u8], &mut buf, 2);
3364        if n.is_none() && self.reacquire_by_name("console") {
3365            n = self.request_with_reply_deadline_into("console", &[1u8], &mut buf, 2);
3366        }
3367        match n {
3368            Some(k) if k >= 4 => (
3369                u16::from_le_bytes([buf[0], buf[1]]),
3370                u16::from_le_bytes([buf[2], buf[3]]),
3371            ),
3372            _ => (0, 0),
3373        }
3374    }
3375
3376    // RETAINED REASONING FROM THE DELETED `console_scroll`, and it is `//` rather than `///` on
3377    // purpose. A `///` run attaches to the item BELOW it, and the item below this one is
3378    // `console_history` - so while these lines were doc comments, rustdoc documented
3379    // `console_history` as "Move the console's scrolled-back view" and handed the reader a
3380    // deleted method's contract as if it were that method's. The words are kept because two of
3381    // the lessons in them were expensive and are still true of anything that asks the console a
3382    // question: a failure must not wear a valid value, and a KEYSTROKE may not wait seconds.
3383    // The last sentence below stops mid-clause; its tail went with the code, and it is left
3384    // truncated rather than completed by guess.
3385    //
3386    // Move the console's scrolled-back view, returning `(lines back, most it can go back)`.
3387    //
3388    // `action` is one of the `SCROLL_*` values in `services/console/src/term.rs`: 0 live, 1 line
3389    // up, 2 line down, 3 page up, 4 page down, 5 oldest kept.
3390    //
3391    // The framebuffer console has no scrollback of its own - reaching the bottom of the screen used
3392    // to mean the top was gone permanently - and this is how a holder of the keyboard asks to look
3393    // back. The console does the arithmetic, because only it knows how many lines it is holding and
3394    // how tall the screen is; a second copy of either number here would be a second thing to drift.
3395    //
3396    // **THE SCROLL IS A REQUEST, NOT AN ESCAPE SEQUENCE.** Console output is untrusted content: a
3397    // file being `read` can hold any bytes, so a scroll expressed in the byte stream would let a
3398    // file scroll the view of the terminal showing it. A request carries a reply cap and output
3399    // does not, so the two channels are separated by construction.
3400    //
3401    // **`None` MEANS THE CONSOLE DID NOT ANSWER, and that is not the same as `(0, 0)`.**
3402    //
3403    // It returned `(0, 0)` for both "the view is at live" and "I could not reach the console",
3404    // and the caller could not tell them apart. On a Dell Wyse, 2026-09-18, that turned a timed-out
3405    // request into a plausible success: the shell believed it had scrolled back to live, left the
3406    // scrollback view, and returned to the prompt **while the screen was still showing history** -
3407    // the two disagreeing about what was on the display, with nothing said. A failure wearing a
3408    // valid value is the exact shape invariant 12 and 26.7 exist to forbid, and it is worth the
3409    // `Option` to make it unrepresentable.
3410    //
3411    // **ONE SECOND, AND NO RETRY - because this is a KEYSTROKE.** It used to be the same two
3412    // seconds plus a reacquire-and-retry that `console_dims` uses, which is right for a
3413    // once-per-command lookup and wrong here: it let a single press of an arrow key block the
3414    // shell for four seconds, which the operator experiences as the machine locking up. Nothing
3415    // above the kernel may hang on a peer (Commandment V); a scroll that cannot be served quickly
3416    // must FAIL quickly and say so.
3417    //
3418    // Dropping the retry costs little. It exists so a client survives the peer RESTARTING, and a
3419    // console that has just restarted is rebuilding the screen anyway; the next `console_dims`
3420        // `console_scroll` IS GONE, and it is the mechanism `backlog/fixed/37` was about.
3421    //
3422    // It asked the console to move its own view, and the console could not answer until it had
3423    // repainted the framebuffer - `paint_view` + `present`, synchronously, inside the request. On a
3424    // 3840x2160 panel that is the most expensive thing it does, and the caller gave it one second
3425    // from the same core. That is why a scroll took "over two seconds to answer": nothing was lost
3426    // or stuck, the work simply did not fit the deadline.
3427    //
3428    // `console_history` below replaced it by moving the WORK rather than tuning the number: the
3429    // console hands over bytes, and the caller paints its own screen through ordinary output, which
3430    // is a send and carries no deadline at all. Deleting this leaves no way to reintroduce the
3431    // shape by accident.
3432
3433    /// Read a page of the console's scrollback AS DATA, starting at line `from` (0 = oldest KEPT).
3434    ///
3435    /// Returns `(lines, total retained, any line has aged out, bytes written)`. The payload after
3436    /// the 4-byte header is `lines` records of `[len, bytes...]`; `bytes` is the whole reply so a
3437    /// caller can bound its own walk.
3438    ///
3439    /// **The replacement for the deleted `console_scroll`, which could not be made cheap.**
3440    /// Moving the terminal's own view forces a full repaint before the console can reply - on a
3441    /// 3840x2160 panel the most expensive thing it does, paid inside the caller's blocking request,
3442    /// from the same core (`backlog/fixed/37`). This asks for BYTES: the console copies out of its ring
3443    /// and replies, and the caller paints its own screen through ordinary output, which is a send
3444    /// rather than a call and carries no deadline at all.
3445    ///
3446    /// The history is BOUNDED (32 KiB / 512 lines). `aged` is how a view says "this is the oldest
3447    /// line kept" rather than implying it is the start of the session (§26.7).
3448    pub fn console_history(&self, from: u16, buf: &mut [u8]) -> Option<(usize, u16, bool, usize)> {
3449        // Opcode 3 = REQ_HISTORY (`services/console/src/main.rs`).
3450        let req = [3u8, from as u8, (from >> 8) as u8];
3451        let k = self.request_with_reply_deadline_into("console", &req, buf, 1)?;
3452        if k < 4 { return None; }
3453        Some((buf[0] as usize, u16::from_le_bytes([buf[1], buf[2]]), buf[3] != 0, k))
3454    }
3455
3456    /// Whether the input driver has reported setup complete (syscall 13, query 10).
3457    /// The deterministic end-of-boot signal: the shell watches it to auto-clear the
3458    /// boot screen the moment the keyboard subsystem is up. Ambient.
3459    pub fn input_ready(&self) -> bool {
3460        // SAFETY: syscall(13) = InspectKernel; query_id=10 = input-ready flag.
3461        unsafe { raw_syscall(13, 10, 0, 0) > 0 }
3462    }
3463
3464    /// Report that input-subsystem setup is complete (syscall 27). Called by the
3465    /// USB keyboard driver (xHCI) in every terminal path once it has finished - the
3466    /// end-of-boot signal. Requires the CONSOLE_PUSH cap (the input driver only).
3467    pub fn signal_input_ready(&self) {
3468        let slot = Self::ctx().console_push_slot;
3469        if slot == u32::MAX { return; }
3470        // SAFETY: syscall(27) = SignalInputReady; slot is the kernel-written cap index.
3471        let _ = unsafe { raw_syscall(27, slot as u64, 0, 0) };
3472    }
3473
3474    /// Read the hardware TSC (Time Stamp Counter) via the kernel.
3475    ///
3476    /// Returns the arch's free-running counter (InspectKernel query 3): RDTSC on x86, CNTPCT on
3477    /// ARM; its rate is `tsc_ticks_per_10ms`. Useful for measuring kernel operation latencies
3478    /// in benchmark probes (§22 Perf B1-B10). Not comparable across hosts.
3479    pub fn read_tsc(&self) -> u64 {
3480        // SAFETY: syscall(13) = InspectKernel; query_id=3 = read TSC.
3481        let ret = unsafe { raw_syscall(13, 3, 0, 0) };
3482        if ret < 0 { 0 } else { ret as u64 }
3483    }
3484
3485    /// Counter ticks per 10 ms, from the kernel's boot-time calibration (CPUID on x86, the measured
3486    /// timer rate / 100 on ARM) (InspectKernel query 16).
3487    /// Convert a TSC delta to milliseconds with `delta_cycles * 10 / tsc_ticks_per_10ms()`. Returns 0
3488    /// if the TSC was not calibrated (callers should then skip the millisecond conversion). `ping` uses
3489    /// it to report round-trip time.
3490    pub fn tsc_ticks_per_10ms(&self) -> u64 {
3491        // SAFETY: syscall(13) = InspectKernel; query_id=16 = TSC ticks per 10 ms quantum.
3492        let ret = unsafe { raw_syscall(13, 16, 0, 0) };
3493        if ret < 0 { 0 } else { ret as u64 }
3494    }
3495
3496    /// Sleep for approximately `ms` milliseconds - the PORTABLE way to pace a loop.
3497    ///
3498    /// Prefer this over `sleep(cycles)` for anything expressing a DURATION. A "cycle" is not a portable
3499    /// unit: on x86 it is a ~2 GHz CPU cycle, on the Pi 2 it is a tick of a ~1 MHz timer, so the same
3500    /// literal means two wildly different lengths of time. Services were written with x86 in mind -
3501    /// `60_000_000` meaning "~30 ms at 2 GHz" - and on ARM that same constant asks for ~60 SECONDS once
3502    /// the quantum is calibrated. Three loops (the shell's muted poll and observe q-poll, and `observe`'s
3503    /// repaint pace) were exactly that, and each would have become a minute-long stall.
3504    ///
3505    /// Converts through the kernel's own calibration, so it is right on any machine. Floors at one
3506    /// scheduler quantum: the underlying `sleep(0)` returns immediately, which would turn a paced loop
3507    /// into a busy spin - the opposite of what every caller wants.
3508    pub fn sleep_ms(&self, ms: u64) {
3509        self.sleep(self.duration_cycles(ms));
3510    }
3511
3512    /// Cycles for approximately `ms` milliseconds, for the two syscalls that take a cycle count
3513    /// (`sleep`, `recv_timeout`). See [`Self::sleep_ms`] for why a raw cycle literal is not portable.
3514    ///
3515    /// Never returns 0. That matters beyond pacing: `recv_timeout(0)` means **block forever**, so a
3516    /// zero here would convert a bounded wait into an unbounded one - a hang produced by a unit
3517    /// conversion. An uncalibrated machine (query 16 reads 0) also lands on the floor, which is exactly
3518    /// the one-quantum behaviour those platforms had before any of this existed.
3519    pub fn duration_cycles(&self, ms: u64) -> u64 {
3520        let per_10ms = self.tsc_ticks_per_10ms();
3521        if per_10ms == 0 { return 1; }              // uncalibrated: floor to one quantum
3522        let c = per_10ms.saturating_mul(ms) / 10;
3523        if c == 0 { 1 } else { c }
3524    }
3525
3526    /// Read the hardware real-time clock (wall-clock date/time) via the kernel.
3527    ///
3528    /// Ambient - the time of day is task-neutral hardware info, like the TSC.
3529    /// Wraps InspectKernel query 11; the kernel returns the fields packed into a
3530    /// `u64` (see `kernel/src/arch/x86_64/rtc.rs`), which this unpacks.
3531    pub fn datetime(&self) -> Datetime {
3532        // SAFETY: syscall(13) = InspectKernel; query_id=11 = packed RTC datetime.
3533        let p = unsafe { raw_syscall(13, 11, 0, 0) } as u64;
3534        Self::unpack_datetime(p)
3535    }
3536
3537    /// Deglitched monotonic "now" in epoch seconds (kernel query 17). Unlike `datetime().epoch_secs()`
3538    /// (the raw RTC, query 11 - a CMOS misread on an in-range year slips through and reads years off), this
3539    /// drops backward / huge-forward glitches. Use it for time-DELTA deadlines and pacing, NOT for display.
3540    pub fn epoch_secs_monotonic(&self) -> i64 {
3541        // SAFETY: syscall(13) = InspectKernel; query 17 = deglitched monotonic epoch seconds.
3542        unsafe { raw_syscall(13, 17, 0, 0) }
3543    }
3544
3545    // `set_wall_clock`, `set_clock_floor` and `clock_synced_secs_ago` were REMOVED with the kernel's
3546    // wall clock (clock slice 3). They called syscall 50 and query 21, which no longer mean what they
3547    // meant: 50 is deleted, and 21 was REUSED for `com2_byte`, so the stale reader did not merely fail -
3548    // it popped a byte off the operator channel. Ask the `time` service; it owns the clock now.
3549    // (2026-10-09) 50 has since been REUSED too: it is `CallDeadline`. Neither number is free.
3550
3551
3552
3553
3554    /// A hardware-random u32 from the SoC RNG (the BCM2835 RNG on the Pi 2), or None if this build exposes
3555    /// no hardware RNG. Ungated (entropy confers no authority). The `random` shell utility consumes it.
3556    pub fn hw_random(&self) -> Option<u32> {
3557        // SAFETY: syscall(13) = InspectKernel; query 19 = a hardware-random u32 (-1 if unavailable).
3558        let r = unsafe { raw_syscall(13, 19, 0, 0) };
3559        if r < 0 { None } else { Some(r as u32) }
3560    }
3561
3562    /// Drive a SoC GPIO pin (the shell `gpio` command; the Pi 2's BCM2835). `op`: 0 input / 1 output /
3563    /// 2 high / 3 low / 4 read. Returns the level (0/1) for a read, 0 on success, -1 on a bad pin /
3564    /// unsupported arch. Gated by the GPIO_DEVICE cap (only the shell holds it).
3565    pub fn gpio(&self, op: u32, pin: u32) -> i64 {
3566        // SAFETY: syscall(45) = Gpio; the kernel validates the GPIO_DEVICE cap and bounds op/pin.
3567        unsafe { raw_syscall(45, op as u64, pin as u64, 0) }
3568    }
3569
3570    /// Decode the packed RTC `u64` (the layout shared by query 11 / 12) into a `Datetime`.
3571    fn unpack_datetime(p: u64) -> Datetime {
3572        Datetime {
3573            second: (p & 0x3F) as u8,
3574            minute: ((p >> 6) & 0x3F) as u8,
3575            hour: ((p >> 12) & 0x1F) as u8,
3576            day: ((p >> 17) & 0x1F) as u8,
3577            month: ((p >> 22) & 0x0F) as u8,
3578            year: ((p >> 26) & 0xFFF) as u16,
3579        }
3580    }
3581
3582    /// Query the kernel task stat for scheduler slot `slot` (syscall 16).
3583    ///
3584    /// Returns a best-effort snapshot. If `slot` is out of range or the task
3585    /// is dead, `valid` will be false - and also when the caller lacks the
3586    /// INTROSPECT cap the kernel requires, which this cannot tell apart.
3587    pub fn task_stat(&self, slot: u32) -> TaskStat {
3588        // The kernel's `STAT_SIZE`: 176 bytes since names grew to 128 (`handle_task_stat`).
3589        let mut buf = [0u8; 176];
3590        // SAFETY: syscall(16) = TaskStat; buf is a local array on the user stack.
3591        let ret = unsafe {
3592            raw_syscall(16, slot as u64, buf.as_mut_ptr() as u64, 176)
3593        };
3594        if ret != 0 {
3595            return TaskStat {
3596                valid: false, state: 0, core: 0,
3597                mem_used: 0, mem_limit: 0, name_len: 0, name: [0u8; NAME_MAX],
3598                restart_count: 0, queue_depth: 0, run_ticks: 0, uptime_secs: 0,
3599            };
3600        }
3601        let valid       = buf[0] != 0;
3602        let state       = buf[1];
3603        let core        = buf[2];
3604        let name_len    = u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]) as usize;
3605        let mem_used    = u64::from_le_bytes([buf[8],  buf[9],  buf[10], buf[11],
3606                                              buf[12], buf[13], buf[14], buf[15]]);
3607        let mem_limit   = u64::from_le_bytes([buf[16], buf[17], buf[18], buf[19],
3608                                              buf[20], buf[21], buf[22], buf[23]]);
3609        let mut name = [0u8; NAME_MAX];
3610        let copy_len = name_len.min(NAME_MAX);
3611        name[..copy_len].copy_from_slice(&buf[24..24 + copy_len]);
3612        let restart_count = u64::from_le_bytes([buf[152], buf[153], buf[154], buf[155],
3613                                                buf[156], buf[157], buf[158], buf[159]]);
3614        let queue_depth = buf[3];
3615        let run_ticks   = u64::from_le_bytes([buf[160], buf[161], buf[162], buf[163],
3616                                              buf[164], buf[165], buf[166], buf[167]]);
3617        let uptime_secs = u64::from_le_bytes([buf[168], buf[169], buf[170], buf[171],
3618                                              buf[172], buf[173], buf[174], buf[175]]);
3619        TaskStat { valid, state, core, mem_used, mem_limit, name_len: copy_len, name,
3620                   restart_count, queue_depth, run_ticks, uptime_secs }
3621    }
3622
3623    /// List the capabilities held by the task in `slot`, into `out`. Returns the
3624    /// number of entries written (capped at `out.len()` and 64). Requires the
3625    /// INTROSPECT cap; a refusal returns 0, the same as a task holding nothing.
3626    /// Best-effort snapshot - see [`task_stat`](Self::task_stat).
3627    pub fn task_caps(&self, slot: u32, out: &mut [CapInfo]) -> usize {
3628        const ENTRY: usize = 16;
3629        const MAX: usize = 64;
3630        let want = out.len().min(MAX);
3631        if want == 0 { return 0; }
3632        let mut buf = [0u8; ENTRY * MAX];
3633        // SAFETY: syscall(28) = TaskCaps; buf is a local array on the user stack.
3634        let ret = unsafe {
3635            raw_syscall(28, slot as u64, buf.as_mut_ptr() as u64, (want * ENTRY) as u64)
3636        };
3637        if ret <= 0 { return 0; }
3638        let count = (ret as usize).min(want);
3639        for i in 0..count {
3640            let o = i * ENTRY;
3641            out[i].resource_id = u64::from_le_bytes([
3642                buf[o], buf[o + 1], buf[o + 2], buf[o + 3],
3643                buf[o + 4], buf[o + 5], buf[o + 6], buf[o + 7],
3644            ]);
3645            out[i].rights = buf[o + 8];
3646        }
3647        count
3648    }
3649
3650    /// Send a message via an explicit cap handle (blocking).
3651    ///
3652    /// Used by benchmark probes that dynamically acquire send caps rather than
3653    /// using named peer slots, avoiding repeated name-lookup overhead.
3654    pub fn send_by_handle(&self, handle: CapHandle, msg: &Message) -> Result<(), IpcError> {
3655        crate::ipc::send(handle, msg)
3656    }
3657
3658    /// Query the rights bitfield of the cap at `handle`.
3659    ///
3660    /// Returns the rights byte as a u64, or `None` if the slot is empty.
3661    /// Used by property test P3 to verify rights do not widen on transfer (§7.3).
3662    pub fn query_cap_rights(&self, handle: CapHandle) -> Option<u64> {
3663        // SAFETY: syscall(14) = QueryCapRights; slot is a cap table index.
3664        let ret = unsafe { raw_syscall(14, handle.0 as u64, 0, 0) };
3665        if ret < 0 { None } else { Some(ret as u64) }
3666    }
3667
3668    /// Remove the cap at `handle` from this task's cap table.
3669    ///
3670    /// Idempotent: removing an already-empty slot is a no-op.
3671    pub fn remove_cap(&self, handle: CapHandle) {
3672        // SAFETY: syscall(15) = RemoveCap; slot is a cap table index.
3673        unsafe { raw_syscall(15, handle.0 as u64, 0, 0); }
3674    }
3675
3676    /// Send a message to `endpoint` with cap `grant` embedded.
3677    ///
3678    /// Unlike `send_with_cap` (which looks up a peer by name), this takes
3679    /// explicit handles - used by P3 where the endpoint and grant slot are
3680    /// the same (self-referential cap transfer).  On success the grant cap is
3681    /// removed from the caller's table (§7.6).
3682    pub fn send_with_cap_by_handle(
3683        &self,
3684        endpoint: CapHandle,
3685        grant:    CapHandle,
3686        msg:      &crate::ipc::Message,
3687    ) -> Result<(), crate::ipc::IpcError> {
3688        let packed  = ((grant.0 as u64) << 16) | (endpoint.0 as u64);
3689        let payload = msg.payload_bytes();
3690        // SAFETY: syscall(11) = SendWithCap; packed and payload are from user space.
3691        let ret = unsafe {
3692            raw_syscall(11, packed, payload.as_ptr() as u64, payload.len() as u64)
3693        };
3694        if ret == 0 { Ok(()) } else { Err(crate::ipc::i64_to_ipc_error(ret)) }
3695    }
3696
3697    /// Mint a **delegated resource** (§7.10, P2 file-as-capability): the kernel allocates a
3698    /// fresh resource owned by this service and a cap for it carrying `rights` (use the
3699    /// `RIGHT_*` bits), returning `(resource_id, cap)`. Requires the `RESOURCE_MINT` authority
3700    /// (held by `fs`). The `resource_id` is what the kernel badges into a later
3701    /// `resource_invoke`, so this service knows which resource a client is acting on (e.g. `fs`
3702    /// maps it → file). `None` if the authority is missing, the band is full, or the cap table
3703    /// is full. Syscall 30 = `ResourceMint`.
3704    pub fn resource_mint(&self, rights: u8) -> Option<(u64, CapHandle)> {
3705        let mut id: u64 = 0;
3706        // SAFETY: syscall(30) = ResourceMint; arg1 points at our own `id` for the kernel to
3707        // fill via write_user_bytes (validated kernel-side). Return is the cap slot or <0.
3708        let ret = unsafe { raw_syscall(30, rights as u64, &mut id as *mut u64 as u64, 0) };
3709        if ret < 0 { None } else { Some((id, CapHandle(ret as u32))) }
3710    }
3711
3712    /// Use a delegated resource cap (§7.10) - the "send" of file-as-capability. The kernel
3713    /// validates the cap holds `right` (a read needs `RIGHT_READ`, a write needs `RIGHT_WRITE`;
3714    /// a cap lacking it fails `CapInsufficientRights` - the non-escalation check), then routes
3715    /// `msg` to the owning service badged with the resource id + the validated right, embedding
3716    /// `reply` (a SEND|GRANT cap) so the owner can reply. `Ok(())` on delivery. Syscall 31.
3717    pub fn resource_invoke(&self, file: CapHandle, right: u8, reply: CapHandle, msg: &Message)
3718        -> Result<(), IpcError> {
3719        // Packed into ONE 32-BIT word: file[0..12] | reply[12..24] | right[24..32].
3720        //
3721        // A syscall argument is a single register, and on a 32-bit target (arm32's r1/r2/r3) that
3722        // register is 32 bits - so anything placed above bit 31 is silently truncated on the way in.
3723        // The original layout put `right` at bit 32, which arrived as 0 on ARM: the kernel then had
3724        // no right to validate, `fs` received a badge of 0, and every real operation failed `op <=
3725        // right` while a read-only cap invoked declaring WRITE sailed past the kernel check that was
3726        // supposed to stop it. Three symptoms, one truncated field.
3727        //
3728        // 12 bits per slot is 4095 against a MAX_CAPS_PER_TASK of 64 - room to grow by 60x - and
3729        // `right` is a u8, so the whole thing lands in 24 bits with 8 to spare. This is the A-U1
3730        // rule from arch/arm/CLAUDE.md: on a 32-bit ABI, a syscall argument that does not fit in one
3731        // register must be narrowed at the wrapper, never assumed to survive.
3732        // REJECT a handle that does not fit its field instead of letting it corrupt the next one.
3733        //
3734        // Each slot gets 12 bits. A handle above 4095 does not simply get truncated - its high bits
3735        // land in the NEXT field. The `fcap` forged-handle check passes 60000 (0xEA60) and the kernel
3736        // logged `file_slot=2656 reply_slot=14`: the 0xE spilled out of `file` and rewrote `reply`, so
3737        // a bad file handle silently redirected the REPLY to whatever cap happened to sit in slot 14.
3738        // The invocation was rejected for the right reason by luck, not by design.
3739        //
3740        // A slot above 4095 cannot be valid anyway (MAX_CAPS_PER_TASK is 64), so this rejects with the
3741        // same error the kernel gives for a slot it does not hold - the caller sees no difference,
3742        // and no neighbouring field is ever silently rewritten.
3743        const SLOT_MAX: u32 = 0xFFF;
3744        if file.0 > SLOT_MAX || reply.0 > SLOT_MAX {
3745            return Err(crate::ipc::i64_to_ipc_error(-2)); // -2 = capability not held
3746        }
3747        let packed = ((right as u64) << 24) | ((reply.0 as u64) << 12) | (file.0 as u64);
3748
3749        let payload = msg.payload_bytes();
3750        // SAFETY: syscall(31) = ResourceInvoke; packed + payload are user values the kernel
3751        // validates (cap slots, rights, generation, and the message bounds) before acting.
3752        let ret = unsafe {
3753            raw_syscall(31, packed, payload.as_ptr() as u64, payload.len() as u64)
3754        };
3755        if ret == 0 { Ok(()) } else { Err(crate::ipc::i64_to_ipc_error(ret)) }
3756    }
3757
3758    /// Read (and clear) the delegated-resource badge of the message just `recv`'d (§7.10). A
3759    /// service that owns delegated resources (e.g. `fs`) calls this right after `recv`: `Some((
3760    /// resource_id, right))` means the message was a **kernel-validated** invocation of a real
3761    /// cap on `resource_id` with `right` already checked (the owner enforces op ≤ `right`); `None`
3762    /// means an ordinary message (no badge - handle it on the name-addressed path). The badge
3763    /// cannot be forged over a plain `send`, so its presence is trustworthy. Syscall 33.
3764    pub fn last_recv_badge(&self) -> Option<(u64, u8)> {
3765        // SAFETY: syscall(33) = LastRecvBadge; reads+clears this task's stored badge.
3766        let packed = unsafe { raw_syscall(33, 0, 0, 0) } as u64;
3767        if packed == 0 {
3768            None
3769        } else {
3770            Some((packed & 0xFFFF_FFFF, ((packed >> 32) & 0xFF) as u8))
3771        }
3772    }
3773
3774    /// Revoke a delegated resource this service owns (§7.10): bumps its generation so every
3775    /// outstanding cap to it goes stale (clients see `CapRevoked`/`EndpointDead` on next use).
3776    /// Owner-gated by the kernel (ownership is the check). `true` on success. Syscall 32.
3777    #[must_use = "the capability is STILL VALID if this is false"]
3778    pub fn resource_revoke(&self, resource_id: u64) -> bool {
3779        // SAFETY: syscall(32) = ResourceRevoke; the kernel checks this task owns the resource.
3780        unsafe { raw_syscall(32, resource_id, 0, 0) == 0 }
3781    }
3782
3783    /// Inject one byte into the console input ring (syscall 20). Only effective
3784    /// for an input-driver service holding a CONSOLE_PUSH cap (the USB keyboard
3785    /// driver, §12); the byte reaches the shell exactly like a serial keystroke.
3786    /// No-op for services without the cap.
3787    pub fn console_push(&self, byte: u8) {
3788        let slot = Self::ctx().console_push_slot;
3789        if slot == u32::MAX {
3790            return;
3791        }
3792        // SAFETY: syscall(20) = ConsolePush; slot is the kernel-written cap index.
3793        let _ = unsafe { raw_syscall(20, slot as u64, byte as u64, 0) };
3794    }
3795
3796    /// Block until one byte is available on COM1 console input (syscall 17).
3797    ///
3798    /// Returns the byte value. Only usable by a service spawned with a CONSOLE_READ cap
3799    /// (`SPAWN_FLAG_REQ_CONSOLE` in its spawn request: the shell, `chaos`, `observe-live`). A kernel error
3800    /// is returned as byte 0, and a missing cap or corrupt context parks the caller in a busy loop.
3801    pub fn console_read(&self) -> u8 {
3802        let data = Self::ctx();
3803        // A wrong magic means the kernel handed us a corrupt ServiceContext - `self.log()` reads the
3804        // same corrupt ctx, so it cannot be trusted to report; parking is the service-level analog of
3805        // the kernel's halt-on-corrupt-state (§6.2). The slot guard below CAN speak (audit L8).
3806        if data.magic != SERVICE_CTX_MAGIC { loop {} }
3807        let slot = data.console_read_slot;
3808        if slot == u32::MAX {
3809            // No CONSOLE_READ cap: a caller that isn't the shell reached a shell-only syscall. Say so
3810            // LOUDLY (inv12) rather than wedge silently, then park - the caller's contract is wrong.
3811            self.log("sdk: console_read called without a CONSOLE_READ cap - parking (contract error)");
3812            loop {}
3813        }
3814        // SAFETY: syscall(17) = ConsoleRead; slot is kernel-written cap index.
3815        let ret = unsafe { raw_syscall(17, slot as u64, 0, 0) };
3816        if ret >= 0 { ret as u8 } else { 0 }
3817    }
3818
3819    /// Non-blocking console read (syscall 24). Returns `Some(byte)` if a keystroke
3820    /// is waiting, `None` if the ring is empty. A foreground full-screen app polls
3821    /// this for `q`-to-quit between repaints instead of blocking in `console_read`.
3822    /// Requires the CONSOLE_READ cap (`SPAWN_FLAG_REQ_CONSOLE` in the spawn request).
3823    pub fn try_console_read(&self) -> Option<u8> {
3824        let data = Self::ctx();
3825        if data.magic != SERVICE_CTX_MAGIC { return None; }
3826        let slot = data.console_read_slot;
3827        if slot == u32::MAX { return None; }
3828        // SAFETY: syscall(24) = TryConsoleRead; slot is kernel-written cap index.
3829        // Returns 0..=255 (byte), 256 (empty), or negative (cap error).
3830        let ret = unsafe { raw_syscall(24, slot as u64, 0, 0) };
3831        if (0..=255).contains(&ret) { Some(ret as u8) } else { None }
3832    }
3833
3834    /// Enable (`true`) or disable (`false`) console keystroke echo (syscall 25).
3835    /// A foreground full-screen app disables echo while it owns the screen - so
3836    /// its raw key polls do not smear its frame - and re-enables it on exit.
3837    /// Requires the CONSOLE_READ cap.
3838    pub fn console_echo(&self, on: bool) {
3839        let data = Self::ctx();
3840        if data.magic != SERVICE_CTX_MAGIC { return; }
3841        let slot = data.console_read_slot;
3842        if slot == u32::MAX { return; }
3843        // SAFETY: syscall(25) = ConsoleEcho; slot is kernel-written cap index.
3844        let _ = unsafe { raw_syscall(25, slot as u64, on as u64, 0) };
3845    }
3846
3847    /// End boot-log mirroring to the framebuffer and clear the TV (syscall 26).
3848    /// The shell calls this once, on the first keystroke, so the user sees the
3849    /// boot sequence on the display and then gets a clean interactive console.
3850    /// Requires the CONSOLE_READ cap.
3851    pub fn console_boot_complete(&self) {
3852        let data = Self::ctx();
3853        if data.magic != SERVICE_CTX_MAGIC { return; }
3854        let slot = data.console_read_slot;
3855        if slot == u32::MAX { return; }
3856        // SAFETY: syscall(26) = ConsoleBootComplete; slot is kernel-written cap index.
3857        let _ = unsafe { raw_syscall(26, slot as u64, 0, 0) };
3858    }
3859
3860    /// Whether THIS task currently owns (or shares, when unclaimed) console input - i.e. its console
3861    /// reads return bytes. False when another task holds the foreground (syscall 40, e.g. `chaos`): a
3862    /// backgrounded task should then stay quiet (not draw, not read) and redraw its prompt only when
3863    /// this returns true again. InspectKernel query 13 (ungated, caller-specific).
3864    pub fn is_console_foreground(&self) -> bool {
3865        // SAFETY: syscall(13) = InspectKernel; query 13 = is-foreground for the caller.
3866        unsafe { raw_syscall(13, 13, 0, 0) != 0 }
3867    }
3868
3869    /// Claim exclusive console input (syscall 40, op = 1): after this, only THIS task's
3870    /// `try_console_read` returns bytes; every other task reads empty. The `chaos` service
3871    /// claims it for the duration of a run so a resurrected shell cannot swallow its
3872    /// `q`-to-quit. Pair with `release_console_foreground` on exit, after ensuring a live
3873    /// shell exists to hand the keyboard back to. Requires the CONSOLE_READ cap.
3874    pub fn claim_console_foreground(&self) {
3875        let data = Self::ctx();
3876        if data.magic != SERVICE_CTX_MAGIC { return; }
3877        let slot = data.console_read_slot;
3878        if slot == u32::MAX { return; }
3879        // SAFETY: syscall(40) = ConsoleForeground; op 1 = claim; slot is kernel-written cap index.
3880        let _ = unsafe { raw_syscall(40, slot as u64, 1, 0) };
3881    }
3882
3883    /// Release exclusive console input (syscall 40, op = 0) back to the unclaimed state, so
3884    /// any CONSOLE_READ holder (the shell) reads normally again. Idempotent.
3885    pub fn release_console_foreground(&self) {
3886        let data = Self::ctx();
3887        if data.magic != SERVICE_CTX_MAGIC { return; }
3888        let slot = data.console_read_slot;
3889        if slot == u32::MAX { return; }
3890        // SAFETY: syscall(40) = ConsoleForeground; op 0 = release; slot is kernel-written cap index.
3891        let _ = unsafe { raw_syscall(40, slot as u64, 0, 0) };
3892    }
3893
3894    /// Return the core this service was spawned on.
3895    pub fn core_id(&self) -> u32 { Self::ctx().core_id }
3896
3897    /// Safe MMIO handle for this service's xHCI controller, if one was granted
3898    /// (§12). The kernel mapped the controller's BAR into this driver's address
3899    /// space; the returned [`crate::Mmio`] reads/writes the uncached device
3900    /// registers directly. `None` for non-driver services.
3901    pub fn xhci_mmio(&self) -> Option<crate::mmio::Mmio> {
3902        let va = Self::ctx().xhci_mmio_va;
3903        if va == 0 {
3904            None
3905        } else {
3906            Some(crate::mmio::Mmio::new(va as *mut u8, Self::ctx().xhci_mmio_len as usize))
3907        }
3908    }
3909
3910    /// Safe MMIO handle for this service's EHCI controller, if one was granted
3911    /// (§12). Reads the same kernel-mapped controller-BAR window as
3912    /// [`xhci_mmio`](Self::xhci_mmio) - a driver service holds exactly one
3913    /// controller, so the field is shared and unambiguous. `None` for non-drivers.
3914    pub fn ehci_mmio(&self) -> Option<crate::mmio::Mmio> {
3915        let va = Self::ctx().xhci_mmio_va;
3916        if va == 0 {
3917            None
3918        } else {
3919            Some(crate::mmio::Mmio::new(va as *mut u8, Self::ctx().xhci_mmio_len as usize))
3920        }
3921    }
3922
3923    /// The framebuffer the kernel granted this service, or `None` if it holds no grant.
3924    ///
3925    /// Held only by `console`, which renders the terminal into it (`docs/console-service.md` §9). The
3926    /// grant carries PIXEL geometry only - character rows and columns are the terminal's own business,
3927    /// and the kernel deliberately does not compute or publish them (Commandment III).
3928    pub fn framebuffer(&self) -> Option<crate::mmio::Framebuffer> {
3929        let d = Self::ctx();
3930        if d.fb_va == 0 || d.fb_len == 0 {
3931            return None;
3932        }
3933        let sh = d.fb_shifts;
3934        Some(crate::mmio::Framebuffer::new(
3935            d.fb_va as *mut u8,
3936            d.fb_len as usize,
3937            d.fb_pitch as usize,
3938            d.fb_bpp as usize,
3939            d.fb_width as usize,
3940            d.fb_height as usize,
3941            sh & 0xFF,
3942            (sh >> 8) & 0xFF,
3943            (sh >> 16) & 0xFF,
3944        ))
3945    }
3946
3947    /// Safe MMIO handle to this service's device register window, if one was
3948    /// granted (§12) - the neutrally-named accessor for non-USB drivers (e.g. the
3949    /// AHCI `block-driver`, which maps its HBA ABAR here). Same kernel-mapped
3950    /// window as [`xhci_mmio`](Self::xhci_mmio). `None` for non-driver services.
3951    pub fn mmio(&self) -> Option<crate::mmio::Mmio> {
3952        let va = Self::ctx().xhci_mmio_va;
3953        if va == 0 {
3954            None
3955        } else {
3956            Some(crate::mmio::Mmio::new(va as *mut u8, Self::ctx().xhci_mmio_len as usize))
3957        }
3958    }
3959
3960    /// Safe handle to this service's DMA arena, if one was granted (§12). The
3961    /// kernel mapped a physically-contiguous region into this driver; the
3962    /// returned [`crate::Dma`] gives the CPU view (read/write) and the physical
3963    /// base to program into the controller. `None` for non-driver services.
3964    pub fn dma_region(&self) -> Option<crate::dma::Dma> {
3965        let d = Self::ctx();
3966        if d.xhci_dma_va == 0 {
3967            None
3968        } else {
3969            Some(crate::dma::Dma::new(
3970                d.xhci_dma_va as *mut u8,
3971                d.xhci_dma_phys,
3972                d.xhci_dma_len as usize,
3973            ))
3974        }
3975    }
3976
3977
3978    /// The interrupt vector the kernel granted this service, if any.
3979    ///
3980    /// A driver must NOT hardcode this. It used to be safe to: every vector was a kernel constant, so
3981    /// both sides knew the number statically. The MSI pool allocates one at spawn instead, and a
3982    /// driver holding a stale copy does not fail loudly - it keeps working (the interrupt still wakes
3983    /// it) while reporting every interrupt as an unknown message. That is exactly what `services/xhci`
3984    /// did on the T630: `0 MSI, 7799 msg`.
3985    ///
3986    /// Use it to recognise an interrupt notification: the kernel's wake payload is the vector number.
3987    ///
3988    /// ```ignore
3989    /// let irq = ctx.irq_vector();
3990    /// let is_irq = irq.is_some_and(|v| msg.payload_bytes() == [v]);
3991    /// ```
3992    ///
3993    /// `None` means no interrupt was granted - a non-driver service, or a driver whose device offered
3994    /// neither MSI nor MSI-X. A driver that needs interrupts should say so rather than poll silently.
3995    pub fn irq_vector(&self) -> Option<u8> {
3996        let d = Self::ctx();
3997        if d.irq_count == 0 { None } else { Some(d.irqs[0]) }
3998    }
3999
4000    /// Every interrupt vector granted to this service (at most four).
4001    ///
4002    /// [`Self::irq_vector`] is the common case; this is for a driver routed more than one line.
4003    pub fn irq_vectors(&self) -> &'static [u8] {
4004        let d = Self::ctx();
4005        let n = core::cmp::min(d.irq_count as usize, d.irqs.len());
4006        &d.irqs[..n]
4007    }
4008
4009    /// Read one 32-bit PCI configuration register. Requires the `PCI_CFG` privilege.
4010    ///
4011    /// `sel` is a bus/device/function selector in the platform's own encoding, which is the CALLER'S
4012    /// knowledge - the kernel performs the access and enforces which registers may be reached, but
4013    /// never interprets what the selector names. `offset` is the register within that function.
4014    ///
4015    /// ONE call, not a select-then-read pair, because the underlying hardware register pair is
4016    /// stateful: split across two calls, two callers would each read whichever register the other
4017    /// selected, and the KERNEL uses the same pair on its spawn and kill paths. Atomic here, that
4018    /// window does not exist - and this interface cannot write anything at all.
4019    ///
4020    /// `None` if the capability is missing, or the access is one this machine will not admit (a bus
4021    /// its bridge does not forward, say - reading past it is an abort on some hardware rather than a
4022    /// harmless all-ones). Both are reported by the kernel too (invariant 12), so a caller need not
4023    /// guess which it was. Note the distinction from `Some(0xFFFF_FFFF)`, which is a SUCCESSFUL read
4024    /// of an absent device - the bus floats high, and that is data, not an error.
4025    pub fn pci_cfg_read(&self, sel: u32, offset: u16) -> Option<u32> {
4026        // SAFETY: a plain syscall; the kernel validates the capability and the access before any I/O.
4027        let r = unsafe { crate::syscall::raw_syscall(53, sel as u64, offset as u64, 0) };
4028        if r < 0 { None } else { Some(r as u32) }
4029    }
4030
4031    /// Cut (`false`) or restore (`true`) the power of the device this service was granted a fixed
4032    /// peripheral window to (`DevicePower`, syscall 54). Needs `DEVICE_POWER`, which the kernel mints
4033    /// with the window wherever the machine can power the device; everywhere else, and for every
4034    /// service without such a window, this returns `false` and the kernel says why on the serial log.
4035    /// How long to hold the device off and how long to wait after restoring it are the caller's to know
4036    /// - they are properties of the device, not of the kernel (`docs/wifi.md` 47).
4037    pub fn device_power(&self, on: bool) -> bool {
4038        // SAFETY: a plain syscall; the kernel validates the capability before touching any pin.
4039        let r = unsafe { crate::syscall::raw_syscall(54, if on { 1 } else { 0 }, 0, 0) };
4040        r == 0
4041    }
4042
4043    /// Set the Arm cores to the platform's maximum (`true`) or minimum (`false`) clock, and return the rate
4044    /// they read back in Hz (`CpuClock`, syscall 55). Needs `CPU_CLOCK`, which only the `power` service is
4045    /// spawned with; everyone else asks `power` for a lease over IPC. `None` is a refusal or a machine with
4046    /// no control over its clock, and the kernel says which on the serial log.
4047    pub fn cpu_clock(&self, max: bool) -> Option<u32> {
4048        // SAFETY: a plain syscall; the kernel validates the capability before asking the firmware.
4049        let r = unsafe { crate::syscall::raw_syscall(55, if max { 1 } else { 0 }, 0, 0) };
4050        if r > 0 { Some(r as u32) } else { None }
4051    }
4052
4053    /// Turn the machine off NOW (`PowerOff`, syscall 56, op 0). Needs `POWER_OFF`, which the shell holds for the
4054    /// operator. With it, this does not return: the machine is off, or - where the hardware cannot cut its own
4055    /// power - every core is halted and the kernel's last line says it is safe to switch off. It returns only
4056    /// when refused, with the kernel's code; the kernel says why on the serial log.
4057    pub fn power_off(&self) -> i64 {
4058        self.power_off_call(0, 0)
4059    }
4060
4061    /// Set the machine's one pending power-off `secs` from now (1 ..= 24 h), held by the KERNEL so it survives
4062    /// every service (`docs/shutdown.md` 6). `0`: this is now the pending one. `1`: one due sooner was kept
4063    /// (earliest wins). `2`: a reboot is pending, and is kept. `-1`: out of range. `-2`: refused (no `POWER_OFF`).
4064    pub fn power_off_in(&self, secs: u64) -> i64 {
4065        self.power_off_call(1, secs)
4066    }
4067
4068    /// Cancel the pending power-off. `0`: there was one. `1`: none is pending. `2`: the pending one is a reboot,
4069    /// and is kept. `-2`: refused.
4070    pub fn power_off_cancel(&self) -> i64 {
4071        self.power_off_call(2, 0)
4072    }
4073
4074    /// Set the machine to reboot `secs` from now (1 ..= 24 h), on the same one kernel deadline as a power-off
4075    /// (`PowerOff` op 3). Needs `REBOOT`. The answers are `power_off_in`'s, with `2` meaning a power-off is
4076    /// pending. A reboot NOW is `reboot`.
4077    pub fn reboot_in(&self, secs: u64) -> i64 {
4078        self.power_off_call(3, secs)
4079    }
4080
4081    /// Cancel the pending reboot (`PowerOff` op 4). The answers are `power_off_cancel`'s, with `2` meaning the
4082    /// pending one is a power-off.
4083    pub fn reboot_cancel(&self) -> i64 {
4084        self.power_off_call(4, 0)
4085    }
4086
4087    fn power_off_call(&self, op: u64, secs: u64) -> i64 {
4088        // SAFETY: a plain syscall with two integers; the kernel validates the capability before acting.
4089        unsafe { crate::syscall::raw_syscall(56, op, secs, 0) }
4090    }
4091
4092    /// The machine's pending power action (InspectKernel query 28): whole seconds until it, rounded up, and
4093    /// whether it is a reboot - or `None` when none is pending. Readable by every task, deliberately: a pending
4094    /// shutdown or reboot must never be hidden.
4095    pub fn power_pending(&self) -> Option<PowerPending> {
4096        // SAFETY: syscall(13) = InspectKernel; query 28 takes no pointer and is ungated.
4097        let r = unsafe { raw_syscall(13, 28, 0, 0) };
4098        if r >= 0 { Some(PowerPending { secs: r as u64 / 2, reboot: r & 1 == 1 }) } else { None }
4099    }
4100
4101    /// Whole seconds until the pending power-off, or `None` when none is pending - a pending reboot is not one.
4102    pub fn power_off_pending(&self) -> Option<u64> {
4103        self.power_pending().filter(|p| !p.reboot).map(|p| p.secs)
4104    }
4105
4106    /// Whole seconds until the pending reboot, or `None` when none is pending - a pending power-off is not one.
4107    pub fn reboot_pending(&self) -> Option<u64> {
4108        self.power_pending().filter(|p| p.reboot).map(|p| p.secs)
4109    }
4110
4111    /// Allocate `size` bytes of read/write memory within this task's budget.
4112    ///
4113    /// Returns the virtual address of the mapping on success, or `AllocError`
4114    /// if the allocation would exceed the contract memory limit (AllocDenied)
4115    /// or physical memory is exhausted.
4116    pub fn alloc_mem(&self, size: usize) -> Result<u64, AllocError> {
4117        // SAFETY: syscall(6) = AllocMem; no user pointers passed.
4118        let ret = unsafe { raw_syscall(6, size as u64, 0, 0) };
4119        if ret >= 0 {
4120            Ok(ret as u64)
4121        } else if ret == -11 {
4122            Err(AllocError::Denied)
4123        } else {
4124            Err(AllocError::Failed)
4125        }
4126    }
4127
4128    // `abort()` (the kernel `Abort`/9 syscall) was removed: it let any task panic the kernel, an
4129    // ungated §3.1 hole, and its only caller (`init`) is gone (Phase 5). A service that hits a fatal
4130    // error now simply dies (and is restarted by the supervisor) rather than aborting the kernel.
4131
4132    /// Trigger a hardware reset via the kernel reboot syscall (18). Does not return.
4133    ///
4134    /// The kernel prints `reboot: hardware reset` to serial before resetting, so the operator sees
4135    /// confirmation before the line goes silent. Requires REBOOT (held by the shell); a refusal is
4136    /// logged and the caller then yields forever.
4137    pub fn reboot(&self) -> ! {
4138        // SAFETY: syscall(18) = Reboot; no arguments.
4139        let rc = unsafe { raw_syscall(18, 0, 0, 0) };
4140        // A REFUSED reboot must not look like a successful one.
4141        //
4142        // The kernel gates this on the REBOOT capability (§3.1) and returns `CapNotHeld` to anyone
4143        // without it. This used to fall into a bare `loop {}`, so a caller that lacked the cap simply
4144        // stopped - no message, no reset, a frozen prompt. "Reboot does nothing" is exactly how that
4145        // reads from the outside, and it is indistinguishable from a reset that failed in hardware.
4146        //
4147        // Say which it was. The kernel's own reset path is bounded and reports if the SoC does not come
4148        // down; this covers the other half, where the syscall never got that far. Invariant 12: failures
4149        // are loud, never silent.
4150        self.log_fmt(format_args!(
4151            "reboot REFUSED by the kernel (rc {}) - this service does not hold the REBOOT capability", rc));
4152        loop {
4153            self.yield_cpu();
4154        }
4155    }
4156
4157    /// Attempt a reboot but RETURN the syscall result instead of assuming it never comes back.
4158    ///
4159    /// A successful reset does not return; a denial returns a negative error code (CapNotHeld = -2
4160    /// when the caller lacks the REBOOT capability, §3.1). For tests/probes that must *observe* the
4161    /// denial without resetting the machine - ordinary rebooters use `reboot()`.
4162    pub fn try_reboot(&self) -> i64 {
4163        // SAFETY: syscall(18) = Reboot; no arguments. On success it never returns; on denial it
4164        // returns the error code, which we hand back to the caller.
4165        unsafe { raw_syscall(18, 0, 0, 0) }
4166    }
4167
4168    /// End this program, with `status`: 0 for success, anything else for a failure it has already said.
4169    ///
4170    /// The kernel ends the caller and no one else - it takes no capability - and tells the supervisor how
4171    /// it ended (`death::EXITED` and the status), which is how a program that FINISHED is told apart from one
4172    /// that crashed. A service the supervisor keeps running is restarted all the same: exiting is for a
4173    /// program that does one job (CLAUDE.md 14.5 amendment).
4174    pub fn exit(&self, status: u32) -> ! {
4175        // SAFETY: syscall(57) = Exit; one integer, no pointer. It does not return.
4176        unsafe { raw_syscall(57, status as u64, 0, 0); }
4177        // Unreachable unless the kernel refused, which it never does for the caller itself: park rather than
4178        // return into code that believes it has ended.
4179        self.park()
4180    }
4181
4182    /// Advisory yield (§9.3).
4183    pub fn yield_cpu(&self) {
4184        // SAFETY: syscall(4) = Yield; always valid from ring-3.
4185        unsafe { raw_syscall(4, 0, 0, 0); }
4186    }
4187
4188    /// Park this task forever: block with no waker. For idle services that have
4189    /// no further work - far better than `loop { yield_cpu() }`,
4190    /// which keeps the core busy and prevents it from halting (so it never runs
4191    /// cool). Nothing wakes a parked task in v1; the loop re-parks defensively.
4192    pub fn park(&self) -> ! {
4193        loop {
4194            // SAFETY: syscall(21) = Park; blocks this task indefinitely.
4195            unsafe { raw_syscall(21, 0, 0, 0); }
4196        }
4197    }
4198
4199    /// Offer a queryable COPY of one log line to `events`. Never the log itself.
4200    ///
4201    /// Called only AFTER `log`'s syscall has already written the kernel ring and serial, so the
4202    /// authoritative record exists before this runs and does not depend on it in any way. A service
4203    /// with no `events` cap does nothing here; `events` itself holds no cap to itself, so its own
4204    /// logging cannot recurse into the sink.
4205    ///
4206    /// NO REACQUIRE ON FAILURE, deliberately, unlike `metric` and `trace_emit`. The cap slot is shared
4207    /// between all three, and those two already refresh it after a sink restart - `count_recv`
4208    /// publishes every 64 messages, so the slot heals on its own. Reacquiring from inside the LOGGING
4209    /// path is the one place it could bite: a reacquire that itself wanted to report something would
4210    /// re-enter here. Dropping a copy costs a line of queryable scrollback and nothing else, because
4211    /// serial already has it.
4212    #[inline]
4213    fn log_copy(&self, text: &[u8]) {
4214        let slot = crate::trace::sink_slot();
4215        if slot == u32::MAX {
4216            return;
4217        }
4218        let mut buf = [0u8; 1 + crate::trace::PEER_LEN + crate::trace::LOG_TEXT_MAX];
4219        let n = crate::trace::encode_log(text, &mut buf);
4220        let _ = self.try_send_by_handle(CapHandle(slot), &crate::ipc::Message::from_bytes(&buf[..n]));
4221    }
4222
4223    /// Log a string via the kernel ring buffer and serial (syscall 5, requires the log_write cap).
4224    /// Over 256 bytes it is cut on a character boundary and followed by ` [TRUNCATED]`. Without the
4225    /// cap it does nothing, and a kernel refusal is not reported.
4226    pub fn log(&self, msg: &str) {
4227        let data = Self::ctx();
4228        if data.magic != SERVICE_CTX_MAGIC { return; }
4229        let slot = data.log_write_slot;
4230        if slot == u32::MAX { return; }
4231
4232        let bytes = msg.as_bytes();
4233        let len   = bytes.len();
4234        if len == 0 { return; }
4235
4236        // TOO LONG IS TRUNCATED, NEVER DROPPED.
4237        //
4238        // This used to be `if len == 0 || len > 256 { return; }` - a message over the limit was
4239        // discarded whole, with no truncation, no marker and no error. The reporting channel itself
4240        // failed silently, which makes every carefully-worded warning in the system conditional on
4241        // its own length.
4242        //
4243        // It was not hypothetical. The two longest literal log lines in the tree are both in `fs`,
4244        // and both are messages the constitution leans on: the durability-not-attested warning (280
4245        // bytes), which CLAUDE.md 6.1's backend-conditional TCB claim describes as "`fs` says so once
4246        // per mount", and the journal-recovery refusal (356 bytes), whose own comment argues it must
4247        // stay as visible as the fault it reports. Neither has ever been printed on this board, and
4248        // the durability one is latched before the call, so it never would be.
4249        //
4250        // Truncating loses the tail of a sentence. Dropping loses the fact. The marker says which
4251        // happened, so a reader is never left believing they saw the whole message.
4252        const LOG_MAX: usize = 256;
4253        if len > LOG_MAX {
4254            const MARK: &str = " [TRUNCATED]";
4255            let keep = LOG_MAX - MARK.len();
4256            // Cut on a CHARACTER boundary: `as_bytes` is UTF-8 and slicing mid-codepoint would hand
4257            // the kernel an invalid string. Walk back at most 3 bytes to the start of a codepoint.
4258            let mut end = keep;
4259            while end > 0 && (bytes[end] & 0xC0) == 0x80 {
4260                end -= 1;
4261            }
4262            // SAFETY: syscall(5) = Log; both slices are valid, in-bounds and within user space. Sent
4263            // as two calls rather than staged through a buffer, because a fixed staging buffer here
4264            // is exactly the kind of hidden limit this change exists to remove.
4265            unsafe {
4266                raw_syscall(5, slot as u64, bytes.as_ptr() as u64, end as u64);
4267                raw_syscall(5, slot as u64, MARK.as_ptr() as u64, MARK.len() as u64);
4268            }
4269            self.log_copy(&bytes[..end]);
4270            return;
4271        }
4272
4273        // SAFETY: syscall(5) = Log; bytes is a valid slice within user space.
4274        unsafe {
4275            raw_syscall(5, slot as u64, bytes.as_ptr() as u64, len as u64);
4276        }
4277        self.log_copy(bytes);
4278    }
4279
4280    /// Write a string to serial WITHOUT a trailing newline (syscall 22, requires the
4281    /// log_write cap). For inline output such as the shell prompt, where `log`'s newline
4282    /// would push the user's typed echo to the next line. A string longer than the kernel's 256-byte
4283    /// limit is written in pieces, each ending on a character boundary - it was dropped whole, with
4284    /// no marker, until 2026-10-10 (`backlog/fixed/80` S5).
4285    pub fn print(&self, msg: &str) {
4286        let data = Self::ctx();
4287        if data.magic != SERVICE_CTX_MAGIC { return; }
4288        let slot = data.log_write_slot;
4289        if slot == u32::MAX { return; }
4290        for piece in Pieces::new(msg, CONSOLE_WRITE_MAX) {
4291            // SAFETY: syscall(22) = Print; `piece` is a valid slice within user space, at most
4292            // CONSOLE_WRITE_MAX bytes.
4293            unsafe {
4294                raw_syscall(22, slot as u64, piece.as_ptr() as u64, piece.len() as u64);
4295            }
4296        }
4297    }
4298
4299    /// Log a formatted message, rendered into a 256-byte stack buffer. A message longer than that is
4300    /// cut on a character boundary and ends `...`, so the cut is visible (`backlog/fixed/80` S5: it was cut
4301    /// with no marker, and a cut through a character logged `(fmt error)` instead of the message).
4302    pub fn log_fmt(&self, args: core::fmt::Arguments) {
4303        let mut buf = [0u8; FMT_MAX];
4304        let s = render(&mut buf, args);
4305        if !s.is_empty() {
4306            self.log(s);
4307        }
4308    }
4309
4310    /// Write a string to the **interactive console** (serial + framebuffer),
4311    /// WITHOUT a trailing newline (syscall 23, requires log_write cap in Stage 1).
4312    /// This is the user-facing path: the shell prompt, command results, and
4313    /// `observe` frames. Unlike `log`/`print` (now serial-only), this also reaches
4314    /// the framebuffer/TV - the interactive surface (see docs/console-service.md).
4315    pub fn console_write(&self, msg: &str) {
4316        let data = Self::ctx();
4317        if data.magic != SERVICE_CTX_MAGIC { return; }
4318        let slot = data.log_write_slot;
4319        if slot == u32::MAX { return; }
4320
4321        // Longer than the kernel takes in one call: written in pieces on character boundaries, never
4322        // dropped (`backlog/fixed/80` S5).
4323        for piece in Pieces::new(msg, CONSOLE_WRITE_MAX) {
4324            // SAFETY: syscall(23) = ConsoleWrite; `piece` is a valid slice within user space, at
4325            // most CONSOLE_WRITE_MAX bytes.
4326            unsafe {
4327                raw_syscall(23, slot as u64, piece.as_ptr() as u64, piece.len() as u64);
4328            }
4329        }
4330    }
4331
4332    /// Write a string to the interactive console followed by a newline.
4333    pub fn console_writeln(&self, msg: &str) {
4334        self.console_write(msg);
4335        self.console_write("\n");
4336    }
4337
4338    /// Write a formatted message to the interactive console, with **no** trailing
4339    /// newline (e.g. a pager status line the cursor should park on).
4340    pub fn console_write_fmt(&self, args: core::fmt::Arguments) {
4341        let mut buf = [0u8; FMT_MAX];
4342        self.console_write(render(&mut buf, args));
4343    }
4344
4345    /// Write a formatted message to the interactive console, followed by a newline.
4346    pub fn console_writeln_fmt(&self, args: core::fmt::Arguments) {
4347        let mut buf = [0u8; FMT_MAX];
4348        self.console_write(render(&mut buf, args));
4349        self.console_write("\n");
4350    }
4351
4352    /// Write one console line. When `clear_eol` is true the line ends with
4353    /// `ESC[K` (erase to end of line) before the newline - so a full-screen app
4354    /// repainting in place (cursor homed each frame) overwrites a previous,
4355    /// longer line without leaving stale characters, and without a full-screen
4356    /// clear (no flicker). When false, behaves exactly like `console_writeln`.
4357    pub fn console_line(&self, clear_eol: bool, msg: &str) {
4358        self.console_write(msg);
4359        self.console_write(if clear_eol { "\x1b[K\n" } else { "\n" });
4360    }
4361
4362    /// Formatted variant of [`Self::console_line`].
4363    pub fn console_line_fmt(&self, clear_eol: bool, args: core::fmt::Arguments) {
4364        let mut buf = [0u8; FMT_MAX];
4365        self.console_write(render(&mut buf, args));
4366        self.console_write(if clear_eol { "\x1b[K\n" } else { "\n" });
4367    }
4368
4369    /// Spawn a service by name on the kernel-selected core.
4370    pub fn spawn(&self, name: &str) -> Result<(), crate::Error> {
4371        self.spawn_on(name, 0xFFFF)
4372    }
4373
4374    /// Spawn a service by name on `core` (0xFFFF = kernel round-robin).
4375    ///
4376    /// **Asks the SUPERVISOR when this service can reach it**, and the kernel otherwise.
4377    ///
4378    /// Step C moves service images out of the kernel (`docs/service-ownership.md`), and a service the
4379    /// kernel does not hold cannot be spawned by name through the kernel. Seven separate callers did
4380    /// exactly that - control's RESTART, the supervisor's own respawn, the shell's `spawn`, the
4381    /// shell's PIPES, `spawncap`, `spawnwired`, and `chaos` - and each was found by a regression
4382    /// rather than by reading. Routing HERE fixes all of them at once, including the ones not yet
4383    /// found.
4384    ///
4385    /// With no supervisor peer there is nothing to ask: the kernel's by-name spawn (syscall 7) was
4386    /// retired, because its catalogue holds `supervisor` alone and it could only refuse
4387    /// (`backlog/fixed/80` K14). So that case is refused here, plainly, rather than sent to a syscall that
4388    /// would refuse it less clearly.
4389    pub fn spawn_on(&self, name: &str, core: u32) -> Result<(), crate::Error> {
4390        if self.find_send_slot("supervisor").is_some() {
4391            // The supervisor hands back a cap to the new service's endpoint. This path does not keep
4392            // it, so it is released here - `map(|_| ())` dropped the handle and leaked one table slot per
4393            // spawn (`backlog/fixed/80`, found during H20).
4394            return self.spawn_via_supervisor(name, core, &[]).map(|cap| {
4395                if let Some(c) = cap { self.remove_cap(c); }
4396            });
4397        }
4398        let _ = (name, core);
4399        Err(crate::Error::NotFound)
4400    }
4401
4402    /// Ask the supervisor to spawn `name`, returning the `SEND|GRANT` cap it hands back (`None` if
4403    /// the service has no recv endpoint, or the cap could not be taken).
4404    ///
4405    /// The supervisor is RESTARTABLE (6.2), so a cached cap to it goes stale on every respawn. This
4406    /// reacquires by name and retries ONCE when the send NEVER LEFT - a stale cap - and never when it
4407    /// was delivered: not on `Ok(None)`, where the deadline passed, and not on `ReplyDead`, where the
4408    /// supervisor died holding it. Either may have started the service, and retrying would start it
4409    /// twice. (`ReplyDead` was retried until 2026-10-10, `backlog/fixed/80` S1.) A full queue is not retried
4410    /// either: the supervisor is alive and busy, and the caller decides whether to ask again.
4411    pub fn spawn_via_supervisor(&self, name: &str, core: u32, peers: &[&str])
4412        -> Result<Option<CapHandle>, crate::Error>
4413    {
4414        let mut buf = [0u8; supcmd::MAX];
4415        let n = supcmd::encode(&mut buf, supcmd::SPAWN, core, name, peers)
4416            .ok_or(crate::Error::InvalidArgument)?;
4417        let msg = crate::ipc::Message::from_bytes(&buf[..n]);
4418        for attempt in 0..2 {
4419            match self.request_with_reply_call_err("supervisor", &msg, 2) {
4420                Ok(Some(reply)) => {
4421                    let ok = reply.payload_bytes().first() == Some(&supcmd::OK);
4422                    let cap = self.take_pending_cap();
4423                    if ok { return Ok(cap); }
4424                    // Answered "no". Reclaim any cap that rode along, or it leaks a table slot.
4425                    if let Some(c) = cap { self.remove_cap(c); }
4426                    return Err(crate::Error::InvalidArgument);
4427                }
4428                Ok(None) => return Err(crate::Error::InvalidArgument),
4429                Err(crate::ipc::IpcError::ReplyDead) | Err(crate::ipc::IpcError::QueueFull) =>
4430                    return Err(crate::Error::InvalidArgument),
4431                Err(_) if attempt == 0 => { let _ = self.reacquire_by_name("supervisor"); }
4432                Err(_) => return Err(crate::Error::InvalidArgument),
4433            }
4434        }
4435        Err(crate::Error::InvalidArgument)
4436    }
4437
4438    /// Spawn a task from an image THIS service supplies (`SpawnImage`, syscall 52).
4439    ///
4440    /// The kernel loads what it is handed instead of looking a name up in its own catalogue - the
4441    /// step that ends that catalogue (`docs/service-ownership.md`). Requires the SPAWN capability,
4442    /// exactly as `spawn` does: supplying the image is not authority to start it - AND the caller
4443    /// must hold `IMAGE_SPAWN`, which only the supervisor has and cannot delegate (CLAUDE.md 14.1).
4444    /// A name already running comes back as `AlreadyRunning`; every other refusal as `InvalidArgument`,
4445    /// and the kernel's serial log says which it was.
4446    ///
4447    /// `peers` are NUL-joined into a caller-owned buffer; each name still resolves through the
4448    /// kernel name directory, so this grants nothing a contract's `send_peers` would not.
4449    /// Returns a `SEND|GRANT` cap to the new service's recv endpoint, or `None` if it has none -
4450    /// the spawner needs it to record `name -> cap` and re-wire dependents after a restart.
4451    pub fn spawn_image(&self, req: &mut SpawnRequest, peers_buf: &mut [u8], peers: &[&str])
4452        -> Result<Option<CapHandle>, crate::Error>
4453    {
4454        let data = Self::ctx();
4455        if data.magic != SERVICE_CTX_MAGIC { return Err(crate::Error::InvalidArgument); }
4456        let slot = data.spawn_slot;
4457        if slot == u32::MAX { return Err(crate::Error::Cap(CapError::CapNotHeld)); }
4458
4459        let mut n = 0usize;
4460        for (i, p) in peers.iter().enumerate() {
4461            if i > 0 {
4462                if n >= peers_buf.len() { return Err(crate::Error::InvalidArgument); }
4463                peers_buf[n] = 0; n += 1;
4464            }
4465            let b = p.as_bytes();
4466            if n + b.len() > peers_buf.len() { return Err(crate::Error::InvalidArgument); }
4467            peers_buf[n..n + b.len()].copy_from_slice(b);
4468            n += b.len();
4469        }
4470        req.peers_ptr = if n > 0 { peers_buf.as_ptr() as u64 } else { 0 };
4471        req.peers_len = n as u32;
4472
4473        // SAFETY: syscall(52) = SpawnImage; `req` is a live, fully initialised SpawnRequest whose
4474        // layout matches the kernel's, and the kernel copies it once before reading any field.
4475        let ret = unsafe {
4476            raw_syscall(52, req as *const SpawnRequest as u64,
4477                        core::mem::size_of::<SpawnRequest>() as u64, slot as u64)
4478        };
4479        // slot + 1, so 0 means "spawned, no recv endpoint" without colliding with slot 0.
4480        // -22 is the kernel's SPAWN_ALREADY_RUNNING.
4481        if ret == -22 { Err(crate::Error::AlreadyRunning) }
4482        else if ret < 0 { Err(crate::Error::InvalidArgument) }
4483        else if ret == 0 { Ok(None) }
4484        else { Ok(Some(CapHandle(ret as u32 - 1))) }
4485    }
4486
4487
4488    /// Kill a named service. Requires SERVICE_CONTROL (the kernel refuses with `CapNotHeld`
4489    /// otherwise); every failure is returned as `InvalidArgument`.
4490    pub fn kill(&self, name: &str) -> Result<(), crate::Error> {
4491        let bytes = name.as_bytes();
4492        // SAFETY: syscall(8) = Kill; bytes is a valid slice within user space.
4493        let ret = unsafe {
4494            raw_syscall(8, bytes.as_ptr() as u64, bytes.len() as u64, 0)
4495        };
4496        // The kernel says which failure it was, and so does this (`backlog/fixed/80` S4): -2 is the
4497        // `SERVICE_CONTROL` refusal, anything else is no running service of that name (or a name
4498        // the kernel could not read). Every one used to come back as `InvalidArgument`.
4499        match ret {
4500            0 => Ok(()),
4501            -2 => Err(crate::Error::Cap(CapError::CapNotHeld)),
4502            _ => Err(crate::Error::NotFound),
4503        }
4504    }
4505
4506    /// Kill then respawn a service with optional core override (§14.4).
4507    ///
4508    /// A service that is not running is simply started - that is `NotFound` from the kill, and the
4509    /// one kill failure that is passed over. A refusal (no `SERVICE_CONTROL`) is returned, and
4510    /// nothing is spawned: until 2026-10-10 the kill's result was discarded, so a caller without the
4511    /// authority to kill went on to spawn a second copy (`backlog/fixed/80` S4, CLAUDE.md 13.6).
4512    pub fn restart(&self, name: &str, core_override: Option<u32>) -> Result<(), crate::Error> {
4513        match self.kill(name) {
4514            Ok(()) | Err(crate::Error::NotFound) => {}
4515            Err(e) => return Err(e),
4516        }
4517        let core = core_override.unwrap_or(0xFFFF);
4518        self.spawn_on(name, core)
4519    }
4520
4521    // ---------------------------------------------------------------------------
4522    // Private helpers.
4523    // ---------------------------------------------------------------------------
4524
4525    /// Find the cap slot for a named send peer.
4526    ///
4527    /// Search order: dynamic cache (post-restart reacquisitions), then the
4528    /// kernel-written ServiceContextData send_peers array.
4529    fn find_send_slot(&self, peer: &str) -> Option<u32> {
4530        let bytes = peer.as_bytes();
4531        let len   = bytes.len();
4532
4533        // 1. Dynamic cache (updated after EndpointDead + reacquire).
4534        // SAFETY: single-threaded service process.
4535        unsafe {
4536            for entry in SEND_CAP_CACHE.iter() {
4537                if entry.name_len != 0
4538                    && entry.name_len as usize == len
4539                    && &entry.name[..len] == bytes
4540                {
4541                    return Some(entry.slot);
4542                }
4543            }
4544        }
4545
4546        // 2. ServiceContextData send_peers (wired at spawn).
4547        let data  = Self::ctx();
4548        let count = (data.send_peer_count as usize).min(MAX_SEND_PEERS);
4549        for i in 0..count {
4550            let entry = &data.send_peers[i];
4551            if entry.slot == u32::MAX { continue; }
4552            let nlen = entry.name_len as usize;
4553            if nlen == len && &entry.name[..len] == bytes {
4554                return Some(entry.slot);
4555            }
4556        }
4557
4558        None
4559    }
4560}
4561
4562// ---------------------------------------------------------------------------
4563// Stack-based fmt::Write helper for log_fmt.
4564// ---------------------------------------------------------------------------
4565
4566struct StackWriter<'a> {
4567    buf: &'a mut [u8],
4568    pos: &'a mut usize,
4569    /// Set when something did not fit, so the result can say it was cut.
4570    cut: bool,
4571}
4572
4573impl<'a> core::fmt::Write for StackWriter<'a> {
4574    fn write_str(&mut self, s: &str) -> core::fmt::Result {
4575        let space = self.buf.len().saturating_sub(*self.pos);
4576        // Never split a character: what is kept is always valid UTF-8.
4577        let mut n = s.len().min(space);
4578        while n > 0 && !s.is_char_boundary(n) {
4579            n -= 1;
4580        }
4581        if n < s.len() {
4582            self.cut = true;
4583        }
4584        self.buf[*self.pos .. *self.pos + n].copy_from_slice(&s.as_bytes()[..n]);
4585        *self.pos += n;
4586        Ok(())
4587    }
4588}
4589
4590/// The most one formatted line renders to, on the stack.
4591const FMT_MAX: usize = 256;
4592/// The most the kernel takes in one `Print` or `ConsoleWrite` call.
4593const CONSOLE_WRITE_MAX: usize = 256;
4594
4595/// Render `args` into `buf`. A rendering that did not fit ends `...` in place of its last bytes, on a
4596/// character boundary, so a reader can see it was cut.
4597fn render<'b>(buf: &'b mut [u8; FMT_MAX], args: core::fmt::Arguments) -> &'b str {
4598    let mut pos = 0usize;
4599    let cut = {
4600        let mut w = StackWriter { buf: &mut buf[..], pos: &mut pos, cut: false };
4601        let _ = core::fmt::write(&mut w, args);
4602        w.cut
4603    };
4604    if cut {
4605        let mut end = FMT_MAX - 3;
4606        while end > 0 && core::str::from_utf8(&buf[..end]).is_err() {
4607            end -= 1;
4608        }
4609        buf[end..end + 3].copy_from_slice(b"...");
4610        pos = end + 3;
4611    }
4612    core::str::from_utf8(&buf[..pos]).unwrap_or("")
4613}
4614
4615/// A string in pieces of at most `max` bytes, each ending on a character boundary.
4616struct Pieces<'s> {
4617    rest: &'s str,
4618    max: usize,
4619}
4620
4621impl<'s> Pieces<'s> {
4622    fn new(s: &'s str, max: usize) -> Self {
4623        Pieces { rest: s, max }
4624    }
4625}
4626
4627impl<'s> Iterator for Pieces<'s> {
4628    type Item = &'s [u8];
4629    fn next(&mut self) -> Option<&'s [u8]> {
4630        if self.rest.is_empty() {
4631            return None;
4632        }
4633        let mut n = self.rest.len().min(self.max);
4634        while n > 0 && !self.rest.is_char_boundary(n) {
4635            n -= 1;
4636        }
4637        if n == 0 {
4638            return None;
4639        }
4640        let (head, tail) = self.rest.split_at(n);
4641        self.rest = tail;
4642        Some(head.as_bytes())
4643    }
4644}
4645
4646/// The seconds a `CallDeadline` is given for a caller's `max_secs`. Never 0, which the kernel reads
4647/// as NO deadline: a non-positive bound is the shortest real one, not the absence of one.
4648fn deadline_secs(max_secs: i64) -> u64 {
4649    if max_secs <= 0 { 1 } else { max_secs as u64 }
4650}
4651
4652// ---------------------------------------------------------------------------
4653// Placeholder types retained for compatibility.
4654// ---------------------------------------------------------------------------
4655
4656pub struct ServiceDescriptor;
4657impl ServiceDescriptor {
4658    pub fn name(&self) -> &str { todo!() }
4659}
4660
4661pub struct BootManifest;
4662impl BootManifest {
4663    pub fn services(&self) -> &[ServiceDescriptor] { todo!() }
4664}