From 7f29c345811c793843c55911ce172c39c498d2b2 Mon Sep 17 00:00:00 2001 From: japabu Date: Wed, 21 Oct 2026 20:51:42 +0200 Subject: [PATCH 1/2] issues: audit batch 3 against a8ea09d20 Each open defect record of batch 3 was checked against origin/main at db0cf636d16d265ac400ab5f32658ed81f5cd632, and every record whose cited code changed up to a8ea09d209c514fdcffcdbe58f3c1826e6aa40e9 (#978's descriptor table, #981) was read again there. Seven of the batch were already deleted by 13ed7b4f4, and tearing-is-what-gop-cannot-give-back carries uncommitted owner edits, so it is untouched. Deleted as fixed, each exit met: - the-ack-delay-abi-doc-names-one-cpu: debug_action::TLB_ACK_DELAY_ARM's doc names each other CPU in turn, the smallest wait answered and the two-second window, as kernel/src/arch/x86_64/tlb.rs does. - the-running-slots-volume-is-claimable: 13ed7b4f4 deleted the kernel's USB storage path; the kernel serves no partition claim (Claimed holds a class, a PCI function, an ISA row or the ACPI row) and hold_source is gone. What remains, the block service not refusing the running ROOT, is issues/the-block-service-does-not-refuse-the-running-root.md, rejected. - the-sysroots-core-and-alloc-carry-the-compiler-profiles-debug-assertions: 5f732156a set debug-assertions-std = false on its own line in std_config and re-ran the link-time no-panic proof against the built sysroot. - thread-exits-completion-post-is-the-second-one: fa1e3254d deleted publish_released, so process::thread_exit's post is the only one. - the-socket2-forks-ownedfd-holds-a-table-key-not-a-handle: #978 gave the process one descriptor table, and socket2 at 2aa6b00, which the lock now pins, installs each socket as a descriptor in it and releases the socket when the last descriptor goes; std's OwnedFd closes through toyos::fd::close. The number OwnedFd holds is the socket's descriptor. Deleted as superseded: - t14-keyboard-will-not-report-its-scancode-set: ps2server (#899) replaced the kernel driver; its probe resets the keyboard with translation on, asks no scancode set and has no firmware-bit fallback, and names undecoded runs. The T14 passed ps2server_arms at a42489c7c. - the-memory-map-buffer-is-the-unreported-pointer: the framebuffer lies in the boot map's scanout regions now, and the margin is MAP_MARGIN, 64. The memory map's report and the T14 reading it needs are folded into the-boot-map-reaches-4-gib-and-firmware-decides-what-lands-in-it, whose exit already asked for the report. - the-supervisors-claim-of-a-pci-function-the-t14-lacks-holds-preemption- off-for-3-8-ms: #926 removed the measured claim path, as the record itself said. The preemption-off windows it read are syscall-preemption-is-incidental's, whose 131 us mask_windows bound on the T14 covers the inventory read that replaced the claim. - watch-window-spins-out-a-hold-its-poster-is-queued-behind: dc62efca4 deleted the hold with its actuator. The record's one inbound reference was in a-wake-to-a-busy-cpu-waits-for-its-next-pass-and-no-idle-cpu-can- take-it, which audit batch 1 (#982) deleted, so no reference remains. blocking-read-window-reds-beside-other-guests keeps the pointer that git revert dc62efca4 restores the hold. The hold's own defect, a 50 ms spin with the task that would post queued behind it on the same CPU, is recorded only here. Renamed, the old slug refuted, no inbound reference: - the-panel-seeds-the-loaders-own-file-so-the-gcd-map-is-logged-nowhere -> the-gcd-maps-held-mmio-descriptors-are-logged-nowhere: the console seeds from logkeeper's stream, not /log's files. - the-kernel-depends-on-toyos-fat32-and-uses-none-of-it -> three-comments-say-the-kernel-uses-toyos-fat32: 13ed7b4f4 removed the dependency; three comments remain. - two-std-maps-still-answer-other-for-a-dead-peer -> std-net-maps-every-other-syscall-error-to-other: TcpStream moved to toyos::net::stream; one map remains, on UdpSocket and the listener. Corrected and kept, each residual defect stated: suite-rows (a guest binary deleted), t14-desktop-froze-at-64s (the drop line's wording), t14-lost-every-integrated-input (ps2server's counters and decoder test), the-t14-mouse-may-be-another-defect (usbd's HID path, no T14 reading), t14-rows (Bench::blocks), test-runner-holds-logread (metalcase alone), the EC record (mediated access, WR_EC for AML), the compiler workspace's toyos-abi lock, the compositor's unknown message, the deadline header's call sites, the pipe lock (user_ptr pins runs; names and lines), the boot map's report_reach list, the VFS's function name, the black box's foreign-record test, the power-off seal (kernel xHCI gone), the copy-out test, the reset word, both shell records, the shared-object cache's exit, the SSH key exchange (toyos-crypto has ML-KEM-768 and its NIST answers), the roster readers, toyos-dns, toybox's row (power is a connector), the click fold, the I219 hang, the lendable region, x2APIC's declaration, the volatile composite site and the file-size ledger. The SO_BROADCAST record's pin is 2aa6b00, where the setter is still a no-op. Inbound references fixed in the-t14-touchpad-is-i2c-hid-and-unbuilt, a-shared-boot-stopped-answering-and-no-capture-says-why and the-diary-computes-no-lateness-and-records-no-slow-system-call. Co-Authored-By: Claude Opus 5.5 --- ...opped-answering-and-no-capture-says-why.md | 4 +- ...maps-every-other-syscall-error-to-other.md | 37 +++++++ ...are-measurements-or-make-several-claims.md | 1 - issues/t14-desktop-froze-at-64s.md | 2 +- ...yboard-will-not-report-its-scancode-set.md | 82 -------------- issues/t14-lost-every-integrated-input.md | 52 +++++---- ...ows-hold-toyos-to-linuxs-choice-or-copy.md | 9 +- ...holds-logread-where-no-log-builtin-runs.md | 27 ++--- issues/the-ack-delay-abi-doc-names-one-cpu.md | 31 ------ ...ives-the-embedded-controller-unfiltered.md | 21 ++-- ...b-and-firmware-decides-what-lands-in-it.md | 17 ++- ...kspace-resolves-the-published-toyos-abi.md | 8 +- ...itor-ignores-a-message-it-does-not-know.md | 8 +- ...nterrupt-and-only-the-timer-entry-polls.md | 42 ++++---- ...ateness-and-records-no-slow-system-call.md | 4 +- ...eld-mmio-descriptors-are-logged-nowhere.md | 30 ++++++ .../the-global-pipe-lock-spans-a-user-copy.md | 102 ++++++++++++++---- ...ends-on-toyos-fat32-and-uses-none-of-it.md | 23 ---- ...nel-resolves-a-path-by-rules-of-its-own.md | 2 +- ...l-on-a-pass-that-hands-the-machine-back.md | 10 +- ...ry-map-buffer-is-the-unreported-pointer.md | 34 ------ ...n-file-so-the-gcd-map-is-logged-nowhere.md | 47 -------- ...-give-back-line-is-in-no-black-box-tail.md | 5 +- ...-forms-mut-over-pages-nothing-may-write.md | 1 - issues/the-reset-word-is-spelled-twice.md | 4 +- .../the-running-slots-volume-is-claimable.md | 31 ------ ...ommand-whose-redirect-it-could-not-open.md | 6 +- ...ll-says-not-found-for-every-spawn-error.md | 8 +- ...es-refusals-are-narrower-than-its-reach.md | 6 +- ...-ownedfd-holds-a-table-key-not-a-handle.md | 58 ---------- ...-forks-so-broadcast-setter-does-nothing.md | 2 +- ...4-lacks-holds-preemption-off-for-3-8-ms.md | 58 ---------- ...-the-compiler-profiles-debug-assertions.md | 64 ----------- ...g-after-rebooting-with-its-i219-faulted.md | 2 +- issues/the-t14-mouse-may-be-another-defect.md | 58 +++++----- ...the-t14-touchpad-is-i2c-hid-and-unbuilt.md | 5 +- ...exits-completion-post-is-the-second-one.md | 64 ----------- ...omments-say-the-kernel-uses-toyos-fat32.md | 23 ++++ issues/toybox-is-one-row-for-every-applet.md | 86 +++++++-------- ...-only-the-roster-header-not-its-entries.md | 36 +++---- ...-decodes-what-a-published-crate-decodes.md | 2 +- ...sh-offers-only-a-classical-key-exchange.md | 11 +- ...-compositor-reads-at-once-are-one-click.md | 4 +- ...maps-still-answer-other-for-a-dead-peer.md | 58 ---------- ...volatile-composites-on-mmio-dma-structs.md | 42 +++----- ...-out-a-hold-its-poster-is-queued-behind.md | 36 ------- issues/what-is-owed-on-file-size.md | 16 +-- ...-region-may-be-lent-is-a-runtime-option.md | 4 +- ...abled-without-reading-firmwares-opt-out.md | 8 +- 49 files changed, 411 insertions(+), 880 deletions(-) create mode 100644 issues/std-net-maps-every-other-syscall-error-to-other.md delete mode 100644 issues/t14-keyboard-will-not-report-its-scancode-set.md delete mode 100644 issues/the-ack-delay-abi-doc-names-one-cpu.md create mode 100644 issues/the-gcd-maps-held-mmio-descriptors-are-logged-nowhere.md delete mode 100644 issues/the-kernel-depends-on-toyos-fat32-and-uses-none-of-it.md delete mode 100644 issues/the-memory-map-buffer-is-the-unreported-pointer.md delete mode 100644 issues/the-panel-seeds-the-loaders-own-file-so-the-gcd-map-is-logged-nowhere.md delete mode 100644 issues/the-running-slots-volume-is-claimable.md delete mode 100644 issues/the-socket2-forks-ownedfd-holds-a-table-key-not-a-handle.md delete mode 100644 issues/the-supervisors-claim-of-a-pci-function-the-t14-lacks-holds-preemption-off-for-3-8-ms.md delete mode 100644 issues/the-sysroots-core-and-alloc-carry-the-compiler-profiles-debug-assertions.md delete mode 100644 issues/thread-exits-completion-post-is-the-second-one.md create mode 100644 issues/three-comments-say-the-kernel-uses-toyos-fat32.md delete mode 100644 issues/two-std-maps-still-answer-other-for-a-dead-peer.md delete mode 100644 issues/watch-window-spins-out-a-hold-its-poster-is-queued-behind.md diff --git a/issues/a-shared-boot-stopped-answering-and-no-capture-says-why.md b/issues/a-shared-boot-stopped-answering-and-no-capture-says-why.md index d31764eb55f..3beb8d70966 100644 --- a/issues/a-shared-boot-stopped-answering-and-no-capture-says-why.md +++ b/issues/a-shared-boot-stopped-answering-and-no-capture-says-why.md @@ -46,8 +46,8 @@ shared boot waiting for its next command is parked at almost every sample. `SYS_THREAD_JOIN` whose wake was lost: it parks on the target thread's own watch at `Deadline::never()` (`kernel/src/syscall/proc.rs:171`), and nothing else would leave a whole boot idle straight after a sibling thread's clean exit. -`issues/thread-exits-completion-post-is-the-second-one.md` owns that -path's two posts. +That wake is the one post `process::thread_exit` makes on the thread's own +watch before its exit pass. **Exit condition.** A capture taken from a boot that has actually stopped, which names the first waiter and the subject it waits on — that boot's trace diary diff --git a/issues/std-net-maps-every-other-syscall-error-to-other.md b/issues/std-net-maps-every-other-syscall-error-to-other.md new file mode 100644 index 00000000000..012fc2a8615 --- /dev/null +++ b/issues/std-net-maps-every-other-syscall-error-to-other.md @@ -0,0 +1,37 @@ +--- +status: open +kind: defect +opened: 2026-09-01 +--- + +# std's net backend still maps every other `SyscallError` to `ErrorKind::Other` + +Making a broken pipe reach a Rust caller as `BrokenPipe` put one exhaustive +`SyscallError -> ErrorKind` map in `sdk/std/sys/pal/mod.rs` and pointed +`sys/pipe`, `sys/stdio` and `sys/fs` at it. `sdk/std/sys/net/connection.rs` +keeps a map of its own with a `_ => Other` arm: + +```rust +fn syscall_err(e: SyscallError) -> io::Error { + match e { + SyscallError::WouldBlock => io::ErrorKind::WouldBlock.into(), + _ => io::Error::new(io::ErrorKind::Other, "syscall error"), + } +} +``` + +A TCP stream no longer goes through it: `TcpStream` reads and writes through +`toyos::net::stream`, whose refusals `stream_refused` maps by name. What still +does is `UdpSocket`'s `recv_from` and `send_to`, which read and write the +socket's pipes to netstack, and the listener's notification reads. So a `UdpSocket::send_to` after netstack has gone +answers `ErrorKind::Other` where the pipe path answers `BrokenPipe`, by reading +the code, and a new `SyscallError` variant is absorbed there silently rather +than failing to compile until somebody decides its kind. + +## What closing it takes + +Pointing `syscall_err`'s callers at `sys::to_io_error` and deleting the local +map, as the three modules already moved were; and a test that a datagram +socket's write to a departed netstack answers `BrokenPipe`, against libc's +`sendto` on the same shape. `netstack_gone_mid_bind` is the boot that already +stages a departed netstack. diff --git a/issues/suite-rows-that-are-measurements-or-make-several-claims.md b/issues/suite-rows-that-are-measurements-or-make-several-claims.md index 80fb18b9ad7..44c3ae1be53 100644 --- a/issues/suite-rows-that-are-measurements-or-make-several-claims.md +++ b/issues/suite-rows-that-are-measurements-or-make-several-claims.md @@ -32,7 +32,6 @@ Guest binaries in `tests/guest/rust-tests/src/bin/` that make several claims: - `std_process.rs`: eight std process behaviours. - `std_sync.rs`: mutex, condvar, park and rwlock. - `sched_stress.rs`: io_uring acceptor isolation, the post-wake vruntime lag, and a connect storm. -- `toybox_file_tools.rs`: `cp`, `mv` and `hexdump`. - `std_fs.rs`: listing, a self-read, NotFound, and file types. Owner: the orchestrator, which dispatches the reshape. diff --git a/issues/t14-desktop-froze-at-64s.md b/issues/t14-desktop-froze-at-64s.md index 81b6070721d..7aa5a11c559 100644 --- a/issues/t14-desktop-froze-at-64s.md +++ b/issues/t14-desktop-froze-at-64s.md @@ -57,7 +57,7 @@ ruled it out. **What the next boot should capture.** Nothing new, which is the point: the compositor now names every client it drops and why. A recurrence with no -`compositor: dropping pid` line and no telemetry is a mechanism none of the +`compositor: dropping client` line and no telemetry is a mechanism none of the four closed ones covers, and that is itself the finding. **Nothing replaces the 2 s report as a heartbeat.** It left the log (#891), diff --git a/issues/t14-keyboard-will-not-report-its-scancode-set.md b/issues/t14-keyboard-will-not-report-its-scancode-set.md deleted file mode 100644 index f97644e7a09..00000000000 --- a/issues/t14-keyboard-will-not-report-its-scancode-set.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -status: open -kind: defect -opened: 2026-08-02 ---- - -# The T14's keyboard will not report its scancode set, and one byte reached no event - -The boot after the FADT gate came out reached the keyboard and stopped one step -from the end: - -``` -i8042: ok selftest=0x55 cfg=0x77->0x64 port1=ok port2=ok -i8042: kbd cmd 0x02 answered Some(238), not ack -i8042: kbd refused scancode set 2 ... disabled -``` - -238 is `0xEE`, ECHO's own reply, returned for the **argument** of `0xF0 0x02` -after the command byte was acked and after `0xF5` had been acked — so the EC -answers commands and does not implement this one. The driver now reads the set -rather than writing it, and where the read is refused it decides the wire format -from the translate bit firmware left in the config byte (`0x77` on this -machine), which is exactly what Linux's `i8042.c`/`atkbd` do and all they do on -a portable device. `i8042_kbd_echo` gates it. - -**The boot after that one worked**, and it is the first time any of this has run -on the metal it was written for: - -``` -i8042: kbd set2+xlat (assumed, the set query was refused) scanning on, GSI 1 -> vec 0x24 apic 0 on -i8042: aux rate=100 res=8/mm, GSI 12 -> vec 0x24 apic 0 -i8042: armed at 1460ms, idle at 3394ms, 0 interrupts ... the pin has never asserted (kbd GSI 1, aux GSI 12) -i8042: the pin asserts ... 1 interrupts, 1 bytes, 0 keys, 0 motion, first seen at 11375ms -``` - -The driver attaches; the **aux port initialises fully** — `rate=100 res=8/mm` -means the TrackPoint answered its whole reset/id/rate/resolution sequence, which -no previous boot reached because every keyboard-side refusal returns before that -block; and a physical keypress at 11375 ms raised a real interrupt on GSI 1, so -the routing, the RTE programming, the vector and the unmask are all correct on -Tiger Lake silicon. `Boot: peripherals ready` went from 6 ms to 398 ms in the -same boot: that is the aux reset stage now actually running against a device -that takes real time, not a regression. - -What is open: - -- **One byte reached the kernel and produced no event, and the counters could - not say which byte.** That is the open item, and half of it is the - instrument. Enumerated against the real tables (`kernel/ps2/src/key.rs`), - **84 of the 256 single byte values decode to nothing** under set 1: both - prefixes (`0xE0`, `0xE1`), the two `Lost` codes, and every unmapped slot — - `0x54`, `0x55`, `0x59`–`0x80` and their break forms. `handle_key` drops a - break for a usage nothing held, which adds `0xAA` (left Shift's break under - translation, and a keyboard announcing a reset). So `1 bytes, 0 keys` covers - an extended key where **nothing is wrong**, a late `0xFA`, another `0xEE`, a - device reset, and a wire carrying raw set 2 — where Enter is `0x5A`, - Backspace `0x66`, Escape `0x76` and 23 such codes land on unmapped slots. - Only the byte separates them. The driver now records the bytes that produced - no event and names them in the health line, and says it a second time if a - later byte does decode; `i8042_undecoded_bytes` gates both. The next diag - boot answers this in one line without a reflash. -- **The wire format is still the `assumed` one, so a raw-set-2 wire is among - the suspects.** It is not the likeliest — a mismatch would usually produce - *wrong* events rather than none, since most set-2 codes do land on a mapped - set-1 slot — but 23 of them do not, and the byte value is what settles it. -- **The fallback's evidence is firmware's intent, not a read-back.** `before & - CFG_TRANSLATE` says firmware enabled a set2→set1 translator; that it did so - for a device emitting set 2 is inference, tight but inference. The success - line says `(assumed, the set query was refused)` rather than `(readback - 0x41)` precisely so the panel does not claim otherwise. A machine where the - inference is wrong types nonsense, which is the outcome the read-back exists - to prevent — there is no third instrument for it on this wire. -- **`0xF2` is not that instrument.** A translating controller answers the MF2 id - `AB 83` as `AB 41` (`translate_table[0x83] == 0x41`, QEMU `hw/input/ps2.c`; - QEMU's own keyboard hardcodes the same pair), which would prove the translator - is live on the data path and not merely enabled in a bit. It is not sent, - because Linux's `atkbd_skip_getid` withholds `0xF2` from every translated - portable device — "on many modern laptops ATKBD_CMD_GETID may cause problems" - — and the T14 is one. Sending a command Linux avoids on this exact machine - class to shore up an inference is the wrong trade. - -`i8042_undecoded_bytes` is deleted; `issues/parallel-tests-red-under-other-suites.md` records the commit that restores it. diff --git a/issues/t14-lost-every-integrated-input.md b/issues/t14-lost-every-integrated-input.md index 3490312200c..88a5bd2ad43 100644 --- a/issues/t14-lost-every-integrated-input.md +++ b/issues/t14-lost-every-integrated-input.md @@ -24,17 +24,22 @@ packets — `0x08` is a resting head and `0x06` is a `dx` of +6. **The pointer w framing perfectly.** The arithmetic is forced and no reader would do it. Closed, therefore: the decoder did not desync, and no fix for a desync was -needed. What was wrong is the instrument, and it is now fixed (`647c3c0`, -`toyos-ps2`) — `MouseOutcome` could not distinguish a byte held inside a packet -from a byte thrown away at a boundary, so two of every three bytes of a healthy -pointer stream were reported as suspects. `i8042_mouse` now runs three thousand bytes of -clean packets and requires the driver to name none of them; reverting the split -reds it with the T14's own line shape. +needed. What was wrong is the instrument, and it is fixed (`647c3c0`) — +`MouseOutcome` could not distinguish a byte held inside a packet from a byte +thrown away at a boundary, so two of every three bytes of a healthy pointer +stream were reported as suspects. `toyos-ps2`'s +`a_healthy_stream_discards_nothing_and_leaves_no_byte_unaccounted` +(`system/ps2server/ps2/tests/decode.rs`) holds that with the T14's two packets. + +The kernel driver that logged this session is gone: `/system/bin/ps2server` +drives the i8042 from userland (#899) and resets both devices itself +(`system/ps2server/src/probe.rs`). Nothing since has read a T14 session long +enough to see whether the loss recurs under it. **What remains open is the actual question, and the log cannot answer it.** The tally counts in the ISR before any decoding, so 6 interrupts is hardware truth -— but it is truth *as of 6.609 s*, which is when the driver stopped speaking. -`HEALTH_DONE` was terminal. For the remaining 54 s the log cannot separate: +— but it is truth *as of 6.609 s*, which is when the driver stopped speaking: +that driver's health line was said once. For the remaining 54 s the log cannot separate: - **the pin stopped asserting** — a wedged controller, a lost edge, an EC that stopped scanning, an RTE that got masked; from @@ -48,16 +53,17 @@ keyboard produced no byte at all in 58 s** — not "stopped at 6.6 s", never. Th same machine's earlier boots drove a shell off that keyboard (`c7efcd30d^:specs/reference/metal-hardware-inventory.md`), so it is not a routing fault. -The cadence fix is what makes the next session decisive rather than a guess: -after the verdict the counters repeat, at most once per 10 s and **only when the -pin has asserted since the last line**. That gating is the point — past the -first repeat, no line means no interrupt, so silence becomes evidence instead of -absence of evidence. +ps2server's counters line is what makes the next session decisive rather +than a guess: it repeats at most once per 10 s and **only when an interrupt has +come since the last line** (`Stream::counters`, `system/ps2server/src/stream.rs`). +Past the first line, no line means no interrupt, so silence becomes evidence +instead of absence of evidence. -**What the next boot should capture.** A repeat line dated after 6.6 s, or none. -If bytes are arriving, `undecoded`/`discarded` name the fault in this driver. If -no line appears at all, the pin is not asserting and the next suspect is the -controller or the EC — and nothing in `toyos-ps2` can be responsible. +**What the next session should capture.** A `ps2server:` counters line dated +after the inputs go dead, or none. If bytes are arriving, `undecoded` and +`discarded` name the fault in the decoder. If no line appears at all, the pin is +not asserting and the next suspect is the controller or the EC — and nothing in +`toyos-ps2` can be responsible. Two things deliberately not concluded: @@ -67,15 +73,7 @@ Two things deliberately not concluded: `0xF2` with id `0x00` — a plain 3-byte mouse — so the driver's 3-byte frame is what the wire carries, and the 4-byte IntelliMouse mismatch usually blamed for a PS/2 desync is not available here. -- **The USB mouse plugged in at 30.4 s produced no motion either**, which is the - xHCI HID completion-requeue item in this section and not this one. - -The endowment table stays indistinguishable here even after the presence gate -landed (#342): on this machine the i8042 is present and both claims still mint -whether the devices answer or not — the gate keys `declare_source()` on -`aux_line.is_some()` / the GSI existing, while `unmasked`/`aux_unmasked`, both -computed one line above (i8042/mod.rs:1320,1323), go unread. Gating the two -declarations on those is the one-line fix direction the #342 review named; it -needs this machine to prove. +- **The USB mouse plugged in at 30.4 s produced no motion either**, which is + `issues/the-t14-mouse-may-be-another-defect.md` and not this one. `i8042_mouse` is deleted; `issues/i8042-mouse-ends-four-packets-short-with-a-clean-exit.md` records the commit that restores it. diff --git a/issues/t14-rows-hold-toyos-to-linuxs-choice-or-copy.md b/issues/t14-rows-hold-toyos-to-linuxs-choice-or-copy.md index b6cb542d86e..034ac13a849 100644 --- a/issues/t14-rows-hold-toyos-to-linuxs-choice-or-copy.md +++ b/issues/t14-rows-hold-toyos-to-linuxs-choice-or-copy.md @@ -11,11 +11,12 @@ setting must be, and its copy of a fact a primary source states is never an orac rows in `tests/toyos.rs` used one. `counters_on_metal`'s, the request Linux's governor chose, went in #915; the other remains: -- `acpi_tables_on_metal` holds the number of definition blocks the server loaded to - `T14_DEFINITION_BLOCKS`, the 14 Linux reports loading. The firmware's XSDT and FADT, which the - kernel already reads, are the primary source of that count. +- `acpi_tables_on_metal` holds the number of definition blocks the server loaded to a + constant per bench, `Bench::blocks` in `tests/toyos.rs`: the T14's 14, the count Linux + reported loading, and the AMD bench's 17. The firmware's XSDT and FADT, which the kernel + already reads, are the primary source of that count. Owner: the orchestrator, which dispatches the reshape. **Exit condition**: `acpi_tables_on_metal` holds the count to the definition blocks -the same boot's firmware tables list, and `T14_DEFINITION_BLOCKS` is deleted. +the same boot's firmware tables list, and `Bench::blocks` is deleted. diff --git a/issues/test-runner-holds-logread-where-no-log-builtin-runs.md b/issues/test-runner-holds-logread-where-no-log-builtin-runs.md index 6efe8df1685..b45e39c11d5 100644 --- a/issues/test-runner-holds-logread-where-no-log-builtin-runs.md +++ b/issues/test-runner-holds-logread-where-no-log-builtin-runs.md @@ -6,20 +6,21 @@ opened: 2026-09-28 # test-runner holds `logread` where no log builtin runs -`logread` in a `[programs.test-runner]` row is authority for the builtins that -read the log inside test-runner (`log-gate`, `log-storm`, `log-close`, -`tests/guest/test-runner/src/main.rs`'s `BUILTINS`). Every boot that runs one is a -`tests/images/testcases.toml` boot (`tests/common/logread.rs` boots the machine tests' config; -`tests/toyos.rs`'s one job list naming `log-close` is `tests/images/testcases.toml`). Seven -other manifests grant it anyway: `tests/partclaimcase`, `tests/blockdcase`, -`tests/doommusiccase`, `tests/logrotatecase`, `tests/images/metalcase.toml`, `tests/images/netcase.toml` -and `tests/sshdcase`. +test-runner has no builtin that reads the log any more: `log-gate`, +`log-storm` and `log-close` are gone from `tests/guest/test-runner`. Its +`logread` is used only by the children it hands a `SysCap` duplicate to, and +it hands one only where its row also grants `dup` (`run_one`, +`tests/guest/test-runner/src/main.rs`). -`partclaimcase` and `blockdcase` also grant `dup`, so there every child -test-runner spawns receives a `SysCap` duplicate carrying `LOG` as well. +- `tests/images/testcases.toml`'s row grants `dup` beside `logread`, and its + children `disk_backtrace` and `inbox_log_post` read the log through the + duplicate. Its comment still gives "the log gate" as the reason. +- `tests/images/metalcase.toml`'s row grants `logread` and no `dup`, so no + child receives it and test-runner reads no log itself: nothing on that boot + uses it. -**Evidence:** `rg -n '"log-gate"|"log-storm"|"log-close"' tests userland`, and -`rg -n logread -g system.toml tests`. +**Evidence:** `rg -n logread tests/images` and `rg -n 'log_read|cursor_words' +tests/guest`. **Exit:** every `logread` in a test manifest is one a program on that boot -uses, or the row says which use it is for. +uses, and the row's comment says which use it is for. diff --git a/issues/the-ack-delay-abi-doc-names-one-cpu.md b/issues/the-ack-delay-abi-doc-names-one-cpu.md deleted file mode 100644 index 378310c8b8c..00000000000 --- a/issues/the-ack-delay-abi-doc-names-one-cpu.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -status: open -kind: defect -opened: 2026-09-14 ---- - -# `TLB_ACK_DELAY_ARM`'s ABI doc still names one CPU - -`sdk/abi/src/syscall.rs`'s `debug_action::TLB_ACK_DELAY_ARM` reads - -> Make the last CPU a shootdown waits for answer `arg` nanoseconds late, and -> take it away again. - -The kernel arm no longer picks a CPU by arithmetic. `kernel/src/arch/x86_64/tlb.rs`'s -`debug_arm_ack_delay` holds each other CPU's acknowledgement back for `arg` -nanoseconds in turn, takes one shootdown against each, and returns the smallest -wait any of them cost the initiator; the arming is then left standing against -every other CPU until a disarm or the end of a fresh `ARM_WINDOW_NANOS` -(`kernel/src/arch/x86_64/tlb.rs:230`, two seconds), whichever comes first. So the one -sentence userland reads to learn what action 12 does describes a selection the -kernel does not make, omits the answer it returns, and says nothing about how -long what it leaves behind lasts. - -`sdk/abi/src` is one of `toolchain::SYSROOT_SOURCES`, so the correction is a -single-commit branch of its own. - -**Exit condition.** The doc names what the kernel does: each other CPU in turn, -the smallest of those waits returned, and the arming left standing against every -other CPU until a disarm or the end of the two-second window, whichever comes -first. A doc that names the arming without its window states a duration the -kernel does not provide. diff --git a/issues/the-acpi-servers-holder-drives-the-embedded-controller-unfiltered.md b/issues/the-acpi-servers-holder-drives-the-embedded-controller-unfiltered.md index 3433d11867e..166b1f449c0 100644 --- a/issues/the-acpi-servers-holder-drives-the-embedded-controller-unfiltered.md +++ b/issues/the-acpi-servers-holder-drives-the-embedded-controller-unfiltered.md @@ -6,15 +6,18 @@ opened: 2026-10-04 # The ACPI server's holder drives the embedded controller unfiltered -The `acpi` claim (`kernel/src/arch/x86_64/acpi_mode.rs`) opens the embedded -controller's command and data ports to `/system/bin/acpiserver` through the -TSS I/O permission bitmap, which grants a port or refuses it and cannot see -the value written. Stage 1 sends the controller nothing but queries, but the -same two ports take `WR_EC` (ACPI 6.5 §12.3), which writes any byte of the -controller's space: on a laptop that is where fan control, charge thresholds -and the controller's own thermal limits live. So a bug in that one program can -reach whatever the machine's controller lets its host write, which the kernel -never sees. +The holder of the `acpi` claim, `/system/bin/acpiserver`, reaches the +embedded controller's command and data ports through the kernel's mediated +access, one byte each (`access`, `kernel/src/arch/x86_64/acpi_mode.rs`; +`system/acpiserver/src/host.rs`). No row names those ports, and the fence +(`toyos_userbound::firmware`) refuses a port only for whose it is, never for +the value written. The server sends queries and, for the AML's accesses to +the controller's space, `RD_EC` and `WR_EC` (`system/acpiserver/src/ec.rs`). +`WR_EC` (ACPI 6.5 §12.3) writes any byte of the controller's space: on a +laptop that is where fan control, charge thresholds and the controller's own +thermal limits live. So a bug in that one program, or in the AML it runs, can +reach whatever the machine's controller lets its host write, and the kernel +makes the write without judging it. The command port also takes any byte, and §12.3 defines five commands (0x80–0x84); every other is the controller's maker's. Whether one of them puts diff --git a/issues/the-boot-map-reaches-4-gib-and-firmware-decides-what-lands-in-it.md b/issues/the-boot-map-reaches-4-gib-and-firmware-decides-what-lands-in-it.md index 7ecdd8f4ed4..a8e9e8098d8 100644 --- a/issues/the-boot-map-reaches-4-gib-and-firmware-decides-what-lands-in-it.md +++ b/issues/the-boot-map-reaches-4-gib-and-firmware-decides-what-lands-in-it.md @@ -11,10 +11,19 @@ scanout, and everything the kernel touches before `mm::init` has to be inside it. The loader places none of it there: the kernel image (`alloc_kernel_memory`), the memory map it hands over, the boot parameter and ROOT's image all come from the pool allocator or `AnyPages`, wherever firmware -puts them. `report_reach` then refuses the boot by name for the three it -checks — and does not check the memory map at all, which the kernel reads +puts them. `report_reach` (`loader/src/main.rs`) then refuses the boot by name +for the kernel image, the parameter buffer, the microcode patch and the kernel +arguments — and does not check the memory map at all, which the kernel reads through the direct map before `mm::init` too. +The memory map's buffer is sized and allocated after `loaderlog::close()`, +because a console write, a FAT write and a handle drop can each add a +descriptor, and `MAP_MARGIN` is fixed. Reporting the buffer means allocating +it before its report line and the close, so those writes fall between the +sizing and `ExitBootServices`. A `metal-sim` boot read the map's size before +those writes and after the close and found zero added descriptors; the T14, +whose FAT write goes to a real stick, has not been read. + On q35 OVMF allocates below 4 GiB, so the x86 suite has never met it. On QEMU `virt` RAM starts at 1 GiB and AAVMF allocates from its top: with `-m 4G` the kernel image lands at 0x13b400000 and the loader refuses the boot @@ -27,8 +36,8 @@ the same defect. **Exit condition**: the loader allocates what the kernel reads before `mm::init` with `AllocateType::MaxAddress` inside the map (or the plan grows to reach all of firmware's write-back memory), the memory map is among what -`report_reach` checks, and `Profile::Virt` boots with the 4 GiB every other -profile has. +`report_reach` checks, its sizing margin read on the T14, and `Profile::Virt` +boots with the 4 GiB every other profile has. **The AArch64 typing refuses a part-typed page.** `Typing::of` (`lib/bootmap/src/lib.rs`) refuses the boot (`Refusal::Mixed`) for any 2 MiB diff --git a/issues/the-compiler-workspace-resolves-the-published-toyos-abi.md b/issues/the-compiler-workspace-resolves-the-published-toyos-abi.md index d2bb3682010..a0a381797d0 100644 --- a/issues/the-compiler-workspace-resolves-the-published-toyos-abi.md +++ b/issues/the-compiler-workspace-resolves-the-published-toyos-abi.md @@ -10,10 +10,10 @@ Every bootstrap run is handed a `[patch]` of `toyos-abi` to the tree's requirement accepts. The four forks `rust/Cargo.lock` pins that name `toyos-abi` — getrandom `toyos-0.2` at 46592414 and `toyos-0.3` at d3045447, libloading `toyos` at fa0abe77, stacker `toyos` at c25842ac — each -require `"0.1"`, and the tree's crate is 0.16.0. So every bootstrap run -re-locks `rust/Cargo.lock` onto `toyos-abi 0.1.0` from crates.io and records -`[[patch.unused]] toyos-abi 0.16.0`; the committed lock still names the path -package from when the tree's crate was 0.1.0. On a macOS or Linux host those +require `"0.1"`, and the tree's crate is 0.16.0. So the committed lock (the +`rust` fork at `11e31a5277`) names `toyos-abi 0.1.0` from crates.io, and a +bootstrap run handed the `[patch]` records `[[patch.unused]] toyos-abi +0.16.0`. On a macOS or Linux host those dependencies are `cfg(target_os = "toyos")` and never compiled; the ToyOS-hosted rustc (`x86_64-unknown-toyos`) compiles them, so its next build links getrandom, libloading and stacker against the published 0.1.0 ABI — diff --git a/issues/the-compositor-ignores-a-message-it-does-not-know.md b/issues/the-compositor-ignores-a-message-it-does-not-know.md index 14736ad821c..01cdb5fd485 100644 --- a/issues/the-compositor-ignores-a-message-it-does-not-know.md +++ b/issues/the-compositor-ignores-a-message-it-does-not-know.md @@ -12,12 +12,6 @@ dropped without a word, on a window's connection and on a fresh one alike. The one retired type is refused by name; every other unknown type is accepted and silently discarded. -`compositor_stall`'s streaming case depends on it: its window sends -`UNKNOWN_MSG` on every pass as load with nothing to draw -(`tests/guest/rust-tests/src/bin/compositor_stall.rs`). A refusal there would -drop the window after its first frame and leave the case passing with no load. - Owner: `Session::dispatch` in `system/compositor/src/session.rs`. -**Exit**: an unknown type drops its client with `DropReason::OutOfProtocol`, -and the stall's stream is a type the compositor serves without drawing. +**Exit**: an unknown type drops its client with `DropReason::OutOfProtocol`. diff --git a/issues/the-deadline-header-promises-any-interrupt-and-only-the-timer-entry-polls.md b/issues/the-deadline-header-promises-any-interrupt-and-only-the-timer-entry-polls.md index 56c4725212b..3e1e7143eff 100644 --- a/issues/the-deadline-header-promises-any-interrupt-and-only-the-timer-entry-polls.md +++ b/issues/the-deadline-header-promises-any-interrupt-and-only-the-timer-entry-polls.md @@ -6,27 +6,22 @@ opened: 2026-09-16 # `deadline.rs`'s header promises the bound to any interrupt, and only the timer entry polls it -`kernel/src/deadline.rs:11-13`: "So this one is armed off the parameter line -and polled from the timer interrupt entry, on every CPU, in both rings. Its -whole requirement is that some CPU still takes an interrupt". -`kernel/src/hardlockup/mod.rs:4-5` repeats it: "`crate::deadline` is the bound -on a *machine*, and its whole requirement is that some CPU still takes an -interrupt: it is polled from the timer entry." +`kernel/src/deadline.rs`'s header: "So this one is armed off the parameter +line and polled from the timer interrupt entry, on every CPU, in both rings. +Its whole requirement is that some CPU still takes an interrupt". +`kernel/src/hardlockup/mod.rs`'s header repeats it: "`crate::deadline` is the +bound on a *machine*, and its whole requirement is that some CPU still takes +an interrupt: it is polled from the timer entry." -`poll` says the narrower thing (`deadline.rs:185-186`): "Whether this -machine's bound has passed; the timer interrupt entry's, in both rings, and -nothing else's." Its call sites are exactly two, both in -`kernel/src/arch/x86_64/idt/timer.rs`: the Ring 0 naked entry's `call {deadline}` -(`:71`, `deadline = sym crate::deadline::poll` at `:79`, placed there because -"a CPU spinning on a ticket still takes this interrupt", `:61-63`) and -`timer_handler`, which opens at `:92`: its first statement is -`crate::irq_census::irq_took!(Timer);` at `:93`, and `crate::deadline::poll()` -at `:96` is the second. `rg -n 'deadline::poll' kernel/src` finds no third. The same module already states the true requirement once, in -`armed`'s doc (`deadline.rs:86-87`): "Both bounds rest on some CPU taking a -timer interrupt". +The calls say the narrower thing. `poll` and `poll_in_kernel` are reached from +the timer entries alone: x86-64's Ring 0 naked entry and `timer_handler` +(`kernel/src/arch/x86_64/idt/timer.rs`), and AArch64's timer arm +(`kernel/src/arch/aarch64/trap.rs`); `rg -n 'deadline::poll' kernel/src` finds +no other. The same module already states the true requirement once, in +`armed`'s doc: "Both bounds rest on some CPU taking a timer interrupt". -So a CPU taking xHCI interrupts, IPIs, or the hard-lockup detector's own NMI -sample every second (`hardlockup/mod.rs:9-14`) — each an interrupt the +So a CPU taking device interrupts, IPIs, or the hard-lockup detector's own NMI +sample every second (`hardlockup/mod.rs`'s header) — each an interrupt the header's sentence counts — never reaches `poll`. The header claims coverage of a machine on which some CPU takes *an* interrupt; the code covers a machine on which some CPU takes a *timer* interrupt. A T14 boot on @@ -37,8 +32,7 @@ timer ticks, and a bound that fired 132859 ms late. Whether that boot's 133 s were a machine with no timer interrupt on any CPU is unmeasured; that such a machine is outside what the code does and inside what the header says is not. -**Exit condition**: the header and the call sites say the same thing — -either `:11-13` and `hardlockup/mod.rs:4-5` say "timer interrupt" and the -machine that takes interrupts but no timer tick is listed under "What it does -not cover" (`:17-32`), or `poll` is reached from every interrupt entry and -`:185-186` says so. +**Exit condition**: the headers and the call sites say the same thing — +either both headers say "timer interrupt" and the machine that takes +interrupts but no timer tick is listed under `deadline.rs`'s "What it does not +cover", or `poll` is reached from every interrupt entry and its doc says so. diff --git a/issues/the-diary-computes-no-lateness-and-records-no-slow-system-call.md b/issues/the-diary-computes-no-lateness-and-records-no-slow-system-call.md index 233bb0ef68a..1cc2444db8e 100644 --- a/issues/the-diary-computes-no-lateness-and-records-no-slow-system-call.md +++ b/issues/the-diary-computes-no-lateness-and-records-no-slow-system-call.md @@ -52,9 +52,7 @@ anything more is built on it. kernel reads back from the trace with its number and its program. A window's record names its opener by address, which `issues/the-cpu-that-spawns-a-toybox-applet-reads-1-4-ms-of-preemption-off-on-the-t14.md` - and - `issues/the-supervisors-claim-of-a-pci-function-the-t14-lacks-holds-preemption-off-for-3-8-ms.md` - need. + needs. **Ruled** (owner, 2026-10-04), on when these steps start, **"Both in parallel"**: "Start the trace diary's first steps now, beside latency step 1 diff --git a/issues/the-gcd-maps-held-mmio-descriptors-are-logged-nowhere.md b/issues/the-gcd-maps-held-mmio-descriptors-are-logged-nowhere.md new file mode 100644 index 00000000000..80e162e366b --- /dev/null +++ b/issues/the-gcd-maps-held-mmio-descriptors-are-logged-nowhere.md @@ -0,0 +1,30 @@ +--- +status: open +kind: defect +opened: 2026-09-14 +--- + +# The GCD map's held MMIO descriptors are logged nowhere + +`loader/src/gcd.rs`'s `free_mmio` prints one line per free MMIO range it hands +the kernel and a count of the map's descriptors, and no line for any other +descriptor. So the map's held MMIO descriptors — firmware's *current* +allocations, the reading that lets that file's module header be checked against +a real machine — reach no console and no file. Eighteen of the ThinkPad T14's 75 +descriptors are MMIO and five are logged; eleven of q35's 34 are MMIO and two +are logged. + +What kept them out is gone. The loader's whole map, moved into `loader.log` on +`bar-placement` at `ffd3f9c2`, once cost the panel its kernel log, because +`/system/bin/console` seeded the screen from every `*.log` file in `/log`. It +now seeds from the boot `logkeeper` hands it over the log port +(`seed`, `system/console/src/main.rs`), so by reading, a line the loader writes +to its own file alone (`loaderlog::line`) costs no panel row; no boot has +checked that. + +What pays for it is `toyos-metal`'s readback of `loader.log` +(`tests/metal/src/lib.rs`): a machine whose aperture is in neither channel +cannot have that premise checked against it afterwards. + +**Exit condition.** The loader writes one line per GCD descriptor to its file +alone, and a T14 readback of `loader.log` carries the whole map. diff --git a/issues/the-global-pipe-lock-spans-a-user-copy.md b/issues/the-global-pipe-lock-spans-a-user-copy.md index 228ae2cb30a..9896a4e8660 100644 --- a/issues/the-global-pipe-lock-spans-a-user-copy.md +++ b/issues/the-global-pipe-lock-spans-a-user-copy.md @@ -6,51 +6,107 @@ opened: 2026-08-28 # `pipe::try_read`/`write` hold the one global `PIPES` spinlock across a 2 MiB user copy, and a pipe's first write adds a bitmap scan and a 2 MiB zeroing under it -Every pipe and every IPC `Connection` in the machine lives behind one static: `static PIPES: Lock>>` (`kernel/src/pipe.rs:174`). `with_pipes_mut` takes it and runs the whole closure under it (`pipe.rs:181-184`), and `sync::Lock::lock` calls `crate::preempt::disable()` before it spins (`kernel/src/sync.rs:56`), so the holder is non-preemptible and every other CPU that wants a pipe spins with preemption off too. +Every pipe and every IPC `Connection` in the machine lives behind one static, +`PIPES: Lock>>` (`kernel/src/pipe.rs`). +`with_pipes_mut` and `with_pipes` run their whole closure under it, and +`sync::Lock::lock` disables preemption before it spins (`kernel/src/sync.rs`), +so the holder is non-preemptible and every other CPU that wants a pipe spins +with preemption off too. ## The mechanism The bulk copy is inside that closure, not outside it. -* `pipe::try_read` (`pipe.rs:210`) calls `ring.read(len, |off, src| buf.write_at(off, src))` at `pipe.rs:220`, inside `with_pipes_mut`. -* `pipe::write` (`pipe.rs:244`) calls `backing.ring.write(buf.len(), |off, dst| buf.read_at(off, dst))` at `pipe.rs:254`, same acquisition. -* `Ring::read`/`Ring::write` (`sdk/abi/src/ring.rs:141`, `:178`) hand the closure one or two contiguous runs; the closure is `UserBytesMut::write_at` / `UserBytes::read_at` (`kernel/src/user_ptr.rs:206`, `:164`), a `copy_nonoverlapping` of the whole run. - -The size is bounded only by the ring: `PIPE_SIZE = PAGE_2M` (`pipe.rs:104`), `PAGE_2M = 2 * 1024 * 1024` (`lib/userbound/src/span.rs:27`), and `capacity = total_size - size_of::()` (`ring.rs:60`) with `RingHeader` `#[repr(C, align(64))]` holding one `AtomicU32` (`ring.rs:24-27`) — so **2,097,088 bytes** is the largest single copy under the lock. Nothing above caps it: `SYS_READ`/`SYS_WRITE` pass the userland length straight through (`kernel/src/syscall/dispatch.rs:110-116`), `object::ops::try_read` hands the full window to `pipe::try_read` (`kernel/src/object/ops.rs:339-340`), and the only other bound, `user_ptr::window` (`user_ptr.rs:269`), requires physical contiguity — which a demand-paged 2 MiB frame satisfies exactly. - -A pipe's **first** write is worse, because the page is allocated lazily under the same lock. `write` calls `pipe.back()` (`pipe.rs:250`), which calls `pmm::alloc_page()` (`pipe.rs:148`). That takes `BITMAP` nested inside `PIPES` (`kernel/src/mm/pmm.rs:221`), linearly scans up to the whole physical bitmap for a free frame (`pmm.rs:224-241`), and then `write_bytes(..., 0, PAGE_2M)` — a 2 MiB zeroing (`pmm.rs:233-237`) — before `Ring::new` and the user copy that follows it. +* `pipe::try_read_within`, which `try_read` calls, runs + `ring.read(len, |off, src| buf.write_run(off, &src))` inside + `with_pipes_mut`. +* `pipe::write` runs `backing.ring.write(n, |off, dst| buf.read_run(off, dst))` + in the same acquisition. +* `Ring::read`/`Ring::write` (`sdk/abi/src/ring.rs`) hand the closure one or two + contiguous runs, and the closure copies each whole. + +The size is bounded only by the ring: `PIPE_SIZE` is `PAGE_2M`, and the +capacity is that less the 64-byte `RingHeader`, so **2,097,088 bytes** is the +largest single copy under the lock. Nothing above caps it: `SYS_READ` and +`SYS_WRITE` pass the caller's length through (`kernel/src/syscall/dispatch.rs`), +`object::ops::try_read` hands the whole buffer to `pipe::try_read`, and +`user_ptr`'s bulk buffer is pinned run by run before the lock, so a buffer that +spans several physical runs is copied whole as well. + +A pipe's **first** write is worse, because the page is allocated lazily under the +same lock. `write` calls `Pipe::back`, which calls `pmm::alloc_page`: a scan of +the physical bitmap under `BITMAP`, nested inside `PIPES`, for a free frame, +then a 2 MiB zeroing (`kernel/src/mm/pmm.rs`), before `Ring::new` and the user +copy that follows it. `map_page` allocates the same way. ## What queues behind it -Everything pipe-shaped in the kernel goes through the same static: `create` (`pipe.rs:191`), `map_page` (`pipe.rs:206`), `has_data`/`has_space` (`pipe.rs:261`, `:267`) — which the inbox readiness predicate calls per registered source (`kernel/src/inbox/mod.rs:767-768`) and the blocking read/write wrappers call per attempt (`kernel/src/syscall/io.rs:76`, `:162`) — every `PipeReader`/`PipeWriter` clone and close (`pipe.rs:74-80`, `close_read`), and `add_inbox_watcher`/`remove_inbox_watcher` (`pipe.rs:338`, `:348`). A compositor client, netd, logd and the shell all contend the same word. +Everything pipe-shaped in the kernel goes through the same static: `create`, +`map_page`, `has_data` and `has_space` — which the blocking read and write +wrappers call per attempt (`kernel/src/syscall/io.rs`) — every +`PipeReader`/`PipeWriter` clone and close, and the read and write watches. A +compositor client, netstack, logkeeper and the shell all contend the same word. ## Impact, stated at its real size -This is **not** the device-I/O class. The copy is memory-speed and takes no device round trip, so it does not approach `sync::Lock`'s 500,000,000-spin `DEADLOCK` panic (`sync.rs:72-75`) the way the kernel's block device lock did before the block layer left the kernel — and that class is not what this file is about. - -What it is: a global lock held with preemption disabled for a stretch that scales with a userland-chosen length up to 2 MiB, on the path that carries every IPC message in the system. The scheduler's own bar for an uninterruptible stretch is `MAX_PASS_NS = 200_000` ns (`kernel/pure/sched/cpu.rs:1235`); a 2 MiB copy plus, on first write, a 2 MiB zeroing and a bitmap scan is on the wrong side of that at any plausible memory bandwidth. **The hold time is not measured and that is the first thing owed.** - -Two things make it worth closing rather than noting. The convoy is machine-wide, not per-pipe: one process reading 2 MiB out of a full pipe stalls an unrelated process's `SYS_CONNECT` and an unrelated inbox's readiness poll. And it is directly userland-driven — the length is the caller's, so a program that wants the stall only has to ask for it. +This is **not** the device-I/O class. The copy is memory-speed and takes no +device round trip, so it does not approach `sync::Lock`'s 500,000,000-spin +`DEADLOCK` panic the way the kernel's block device lock did before the block +layer left the kernel. + +What it is: a global lock held with preemption disabled for a stretch that +scales with a userland-chosen length up to 2 MiB, on the path that carries every +IPC message in the system. The scheduler's own bar for an uninterruptible +stretch is `MAX_PASS_NS = 200_000` ns (`kernel/pure/sched/cpu.rs`); a 2 MiB copy +plus, on first write, a 2 MiB zeroing and a bitmap scan is on the wrong side of +that at any plausible memory bandwidth. **The hold time is not measured and that +is the first thing owed.** + +Two things make it worth closing rather than noting. The convoy is machine-wide, +not per-pipe: one process reading 2 MiB out of a full pipe stalls an unrelated +process's `SYS_CONNECT` and an unrelated reader's readiness check. And it is +directly userland-driven — the length is the caller's, so a program that wants +the stall only has to ask for it. ## Precondition / repro 1. Create a pipe or connection; write until the ring is full (2,097,088 bytes). -2. From the reader, `SYS_READ` with a 2 MiB-aligned buffer inside one demand-paged 2 MiB region and `len` = 2 MiB, so `user_ptr::window` accepts it as one contiguous window. -3. From another CPU, time an unrelated `pipe::has_data` or `pipe::create`; it waits for the whole copy. +2. From the reader, `SYS_READ` with a 2 MiB buffer. +3. From another CPU, time an unrelated `pipe::has_data` or `pipe::create`; it + waits for the whole copy. -For the allocation half, the repro is simpler: `SYS_CONNECT` then a single first `SYS_WRITE` of one byte — `pipe.rs:250` -> `pipe.rs:148` -> `pmm.rs:233-237` runs the scan and the 2 MiB zeroing under `PIPES` before one byte moves. +For the allocation half: `SYS_CONNECT` then a single first `SYS_WRITE` of one +byte runs the scan and the 2 MiB zeroing under `PIPES` before one byte moves. ## Fix direction -`PIPES` is doing two jobs and only one of them needs the global. It is the table lock, and it is also the serialization `Ring` relies on — `ring.rs:41-43` says so plainly: "Every access is serialized by the caller's lock (`kernel::pipe`'s `PIPES`), which is why the cursors are plain integers rather than atomics." So the copy cannot simply be lifted out; the ring's data-race argument has to move with it. +`PIPES` is doing two jobs and only one of them needs the global. It is the table +lock, and it is also the serialization `Ring` relies on — `ring.rs`'s doc says +"Every access is serialized by the caller's lock (`kernel::pipe`'s `PIPES`)", +which is why the cursors are plain integers rather than atomics. So the copy +cannot simply be lifted out; the ring's data-race argument has to move with it. -The shape that keeps that argument true is a per-pipe lock under the table lock: `PIPES` guards the `IdMap` and the refcounts, a `Lock` (or a claimed-range token) inside `Pipe` guards `Backing`, and the table lock is dropped once the per-pipe guard is in hand. `try_read`/`write` then hold only the pipe's own lock across `Ring::read`/`Ring::write`, and `has_data`/`has_space`/`create`/`clone`/`close` stop queueing behind a stranger's copy. `Ring`'s header sentence changes with it — the serializing lock becomes the pipe's, not the table's. +The shape that keeps that argument true is a per-pipe lock under the table lock: +`PIPES` guards the `IdMap` and the refcounts, a `Lock` (or a claimed-range token) +inside `Pipe` guards `Backing`, and the table lock is dropped once the per-pipe +guard is in hand. `try_read`/`write` then hold only the pipe's own lock across +`Ring::read`/`Ring::write`, and `has_data`/`has_space`/`create`/`clone`/`close` +stop queueing behind a stranger's copy. `Ring`'s doc sentence changes with it. -The lazy allocation should move out from under both: allocate the ring page before taking the table lock (or, on the miss, drop and retry the way `file_cache::read_page` already does at `kernel/src/file_cache.rs:217-231`), so neither the bitmap scan nor the 2 MiB zeroing runs with a global held. +The lazy allocation should move out from under both: allocate the ring page +before taking the table lock (or, on the miss, drop and retry the way +`file_cache::read_page` does), so neither the bitmap scan nor the 2 MiB zeroing +runs with a global held. -Sequencing note: this touches `sdk/abi/src/ring.rs`'s doc contract — the sentence naming `PIPES` as the serializing lock — so it lands as one commit over `sdk/abi/src` and `kernel/src` together. The two closures at `pipe.rs:220` and `pipe.rs:254` now hand `Ring` a `toyos_abi::ring::Src`/`Dst` rather than a slice; a per-pipe lock changes who serializes them, not their shape. +Sequencing note: this touches `sdk/abi/src/ring.rs`'s doc contract, so it lands +as one commit over `sdk/abi/src` and `kernel/src` together. ## Two checks -* **Negative control:** revert the whole change onto the base and re-run the contention measurement; the per-CPU wait on an unrelated pipe operation must return to the pre-change value. -* **Independent oracle:** `kernel-loom`'s existing lock models for the new two-level acquisition (table then pipe), which is a different epistemic source from the kernel's own test suite; plus the `audio_tone` worst-wake instrument, which is a recorded real-failure oracle rather than a re-derivation. +* **Negative control:** revert the whole change onto the base and re-run the + contention measurement; the per-CPU wait on an unrelated pipe operation must + return to the pre-change value. +* **Independent oracle:** `kernel-loom`'s lock models for the new two-level + acquisition (table then pipe), which is a different epistemic source from the + kernel's own test suite; plus `audio_tone`'s worst-wake reading, a recorded + real-failure oracle rather than a re-derivation. diff --git a/issues/the-kernel-depends-on-toyos-fat32-and-uses-none-of-it.md b/issues/the-kernel-depends-on-toyos-fat32-and-uses-none-of-it.md deleted file mode 100644 index 8f236dab041..00000000000 --- a/issues/the-kernel-depends-on-toyos-fat32-and-uses-none-of-it.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -status: open -kind: defect -opened: 2026-10-12 ---- - -# The kernel depends on toyos-fat32 and uses none of it - -`kernel/Cargo.toml` lists `toyos-fat32` among the binary's dependencies -(`[target.'cfg(target_os = "none")'.dependencies]`), and no kernel source -names it: `git grep -n fat32 -- kernel` finds that manifest line alone, and -the kernel crate includes no file from outside `kernel/`. Its last user, the -kernel's FAT adapter, went in #536 and the edge stayed, so every kernel build -compiles a FAT32 driver it never calls. Three comments still say the kernel -uses it: `lib/fat32/Cargo.toml`'s header (the kernel depends on it by path), -`build/fatformat.rs`'s module doc ("the kernel's driver") and `populate`'s -doc in `build/image.rs` ("the driver the kernel appends `kernel.log` with"). - -**Exit**: `git grep -n fat32 -- kernel` finds nothing, the root `Cargo.lock` -lists no `toyos-fat32` among `kernel`'s dependencies, none of the three -comments names the kernel, and `cargo run -- --build-only` exits 0. - -Owner: the orchestrator, which dispatches it. diff --git a/issues/the-kernel-resolves-a-path-by-rules-of-its-own.md b/issues/the-kernel-resolves-a-path-by-rules-of-its-own.md index 64afd59cdc5..6af35d3d637 100644 --- a/issues/the-kernel-resolves-a-path-by-rules-of-its-own.md +++ b/issues/the-kernel-resolves-a-path-by-rules-of-its-own.md @@ -12,7 +12,7 @@ A path on the kernel's own mounts, ROOT and `/tmp`, is resolved by - `..` is read off the text before any name is looked up (`normalize`), so `/tmp/d/dir/../f`, where `dir` links to `/tmp/x`, names `/tmp/d/f` and not `/tmp/f`. -- Only the last name's link is followed (`resolve_for_open_depth`): a path +- Only the last name's link is followed (`resolve_for_open`): a path through a link to a directory, `/tmp/d/dir/f`, names nothing. - A relative link is read against the mount it is on, the first name of its path, and not against its own directory. diff --git a/issues/the-loader-says-it-boots-its-kernel-on-a-pass-that-hands-the-machine-back.md b/issues/the-loader-says-it-boots-its-kernel-on-a-pass-that-hands-the-machine-back.md index b24785ed32d..24edf03452c 100644 --- a/issues/the-loader-says-it-boots-its-kernel-on-a-pass-that-hands-the-machine-back.md +++ b/issues/the-loader-says-it-boots-its-kernel-on-a-pass-that-hands-the-machine-back.md @@ -12,8 +12,11 @@ before `attempt::read`. When the foreign record was the only word of this image's last boot, the same pass then prints `Boot attempts: the previous boot of this image never reported; the machine is handed back` and boots nothing, so `loader.log` -carries two lines that contradict each other. The QEMU test -`blackbox_foreign_record` asserts the first phrase on exactly that pass. +carries two lines that contradict each other. The metal row +`blackbox_foreign_record` (`tests/toyos.rs`) reads that pass for the record's +opening (`bootlog::FOREIGN_DONE`), and the host check of that judge in +`tests/checks.rs` feeds it a fixture carrying the whole line, the first phrase +among it. Seen on the T14 run of `main` at `7e151819`, the `foreignrecord` readback's `loader.log`, @@ -23,4 +26,5 @@ last boot`, then the hand-back line. ## Exit condition A pass that clears a foreign record says what it then does, in one line that -is true of that pass, and `blackbox_foreign_record` asserts that line. +is true of that pass, and `blackbox_foreign_record`'s judge asserts that +line, its host check's fixture carrying it. diff --git a/issues/the-memory-map-buffer-is-the-unreported-pointer.md b/issues/the-memory-map-buffer-is-the-unreported-pointer.md deleted file mode 100644 index 7de9f2e4f90..00000000000 --- a/issues/the-memory-map-buffer-is-the-unreported-pointer.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -status: open -kind: defect -opened: 2026-09-06 ---- - -# The one pointer the loader cannot say whether the kernel can reach - -`kernel_main` dereferences three firmware pool pointers before `mm::init` gives -it page tables of its own, while the bootloader's 4 GiB boot map is the whole of -what is mapped: the framebuffer (`panic_console::arm`), the boot parameter -(`core::str::from_utf8` on `cmdline_addr`), and the memory map, which -`framebuffer_is_reclaimed_ram` walks. The loader reports the first two before -`ExitBootServices`. A Tiger Lake framebuffer is not below 4 GiB, and nothing -constrains a UEFI pool allocation to stay there either, so a T14 whose memory -map lands high faults in `arm` with nothing on the panel and nothing in -`loader.log` — while both printed lines read "inside". - -The third is unreported because its buffer is sized from `memory_map_size()`, -and `loaderlog::close()` runs before that sizing: a console write, a FAT write -and a handle drop each add a descriptor, and the `+ 8` margin is fixed. -Reporting the buffer means allocating it first, which puts the three console -writes and the FAT close between the sizing and the exit. - -**Measured, and the margin looks ample.** A `metal-sim` boot asked -`memory_map_size()` immediately before those three writes and again immediately -after `loaderlog::close()`: `MEASURE: entries before 109 after 109 (entry_size -48)`. They add **zero** descriptors against a margin of eight. That is QEMU, -whose FAT write goes to a file the host owns; the T14's goes to a real USB -stick, and no boot has asked the same question there. - -Closed by the same reading on the T14, after which the sizing and the -allocation move above the three lines and the memory map's `report_reach` -(`loader/src/main.rs`) joins the ones already there. diff --git a/issues/the-panel-seeds-the-loaders-own-file-so-the-gcd-map-is-logged-nowhere.md b/issues/the-panel-seeds-the-loaders-own-file-so-the-gcd-map-is-logged-nowhere.md deleted file mode 100644 index cb4e0c0b80b..00000000000 --- a/issues/the-panel-seeds-the-loaders-own-file-so-the-gcd-map-is-logged-nowhere.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -status: open -kind: defect -opened: 2026-09-14 ---- - -# The panel seeds the loader's own file, so the GCD map is logged nowhere - -`/system/bin/console` seeds the screen from every `*.log` file in `/log`, newest -two, oldest first (`system/console/src/main.rs:277-286`). `/log` is the root of -the log partition and the bootloader writes `loader.log` there -(`userland/logd/src/store.rs:8-9`, `loader/src/loaderlog.rs:51`), so -`loader.log` passes the extension filter. A line the loader writes to its file costs a row of the -kernel's log on the panel exactly as a line it prints to the firmware console -does — the file is not a second channel. - -Measured on `bar-placement` at `ffd3f9c2` with the loader's whole GCD map moved -off the console and into `loader.log` alone, by `loaderlog::line` and no -`println!`: - - cargo test screen_console_shell EXIT=1 - FAIL screen_console_shell: no `i8042:` line above the prompt, and the - console seeded 22093 bytes of kernel log - -and all 34 of the q35 map's lines are in the decoded panel, above a prompt with -no kernel line left over it. The same head with those lines written to neither -channel is `cargo test screen_console_shell` **EXIT=0**, `PASS (2s)`, `30 kernel -log rows above a prompt`. - -What that costs today: `loader/src/gcd.rs` prints one line per free MMIO -range it hands the kernel and no line for any other descriptor, so the map's -held MMIO descriptors — firmware's *current* allocations, the reading that lets -that file's module header be checked against a real machine — reach no console -and no file. Eighteen of the ThinkPad T14's 75 descriptors are MMIO and five -are logged; eleven of q35's 34 are MMIO and two are logged. - -The fix is `/system/bin/console`'s, at `system/console/src/main.rs:277-286`, -and nobody is holding it. What pays for it is `toyos-metal`'s readback of -`loader.log` (`tests/metal/src/lib.rs`): a machine whose aperture is in neither -channel cannot have that premise checked against it afterwards. - -**Exit condition.** `/system/bin/console`'s seed takes only the files -`/system/bin/logd` wrote — `build/bootlog.rs:175-187` already splits a `/log` -listing that way for the host-side reader, so the rule exists and the guest does -not apply it. With that in place `loader.log` costs no panel row, the loader -writes one line per descriptor to its file alone, and `cargo test -screen_console_shell` stays green with the whole map in the readback. diff --git a/issues/the-power-offs-give-back-line-is-in-no-black-box-tail.md b/issues/the-power-offs-give-back-line-is-in-no-black-box-tail.md index 45bb12a9c6f..dfb953e19f7 100644 --- a/issues/the-power-offs-give-back-line-is-in-no-black-box-tail.md +++ b/issues/the-power-offs-give-back-line-is-in-no-black-box-tail.md @@ -29,9 +29,8 @@ tail is read after a power-off only where the write did not take: `S5 did not take`, a panic and then a reset. Not moved in #767, which put the settle above the console's drain: the seal -is `quiesce`'s, shared by the reboot, which settles nothing; it stands above -`xhci::seal_shut`, which must be the last thing `quiesce` does, and the -settle needs the `Stopping` and runs below it. Sealing after the settle, or +is `quiesce`'s, shared by the reboot, which settles nothing, and the settle +needs the `Stopping` `quiesce` returns and runs below it. Sealing after the settle, or settling above the last word, is a second change of the stop's order. Owner: the `acpi` claim's author (#749). diff --git a/issues/the-readonly-copyout-test-forms-mut-over-pages-nothing-may-write.md b/issues/the-readonly-copyout-test-forms-mut-over-pages-nothing-may-write.md index 13f3fae7b5b..5f423407ef9 100644 --- a/issues/the-readonly-copyout-test-forms-mut-over-pages-nothing-may-write.md +++ b/issues/the-readonly-copyout-test-forms-mut-over-pages-nothing-may-write.md @@ -13,7 +13,6 @@ wrappers take `&mut [u8]` and `&mut ProcessStats` and no wrapper takes a raw address. The `&mut` covers a read-only anonymous map, the binary's own `.text` and the clock page, and is never written through, so the test rests on the compiler inventing no store through it, not on a language guarantee. -`tls_dtv_race` forms the same reference. Owner: `toyos-abi`'s syscall wrappers, which would need a raw-address entry for a caller that names memory it may not hold a `&mut` to; that is an ABI diff --git a/issues/the-reset-word-is-spelled-twice.md b/issues/the-reset-word-is-spelled-twice.md index 9ba6f878b65..0379c5edccf 100644 --- a/issues/the-reset-word-is-spelled-twice.md +++ b/issues/the-reset-word-is-spelled-twice.md @@ -6,8 +6,8 @@ opened: 2026-09-07 # The word a metal verdict turns on is spelled twice, and neither speller reads the other -`kernel/src/syscall/machine.rs:86` writes `quiesce("Rebooting.")` and -`build/bootlog.rs:15` declares `REBOOTING: &str = "Rebooting."` as the last line a +`kernel/src/syscall/machine.rs` writes `quiesce("Rebooting.")` and +`build/bootlog.rs` declares `REBOOTING: &str = "Rebooting."` as the last line a passing boot must leave. Two literals, no shared declaration: reword the kernel's and every metal run refuses with `the log's last line is … and not "Rebooting."` while the kernel is behaving exactly as intended. diff --git a/issues/the-running-slots-volume-is-claimable.md b/issues/the-running-slots-volume-is-claimable.md deleted file mode 100644 index 2eaff6b3e9d..00000000000 --- a/issues/the-running-slots-volume-is-claimable.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -status: open -kind: defect -opened: 2026-09-26 ---- - -# The running slot's FAT volume is claimable, where its ROOT is not - -The kernel holds the partition its ROOT image came from for the machine's life -(`kernel/src/rootfs.rs`, `hold_source`), so no claim can write the ROOT a boot -runs. It holds nothing of the same slot's FAT volume — the kernel, its boot -parameter and the signed header the loader read — so on a disk the kernel -drives, any process whose `SysCap` carries `device` can claim that partition -by its unique GUID and write it. The test estate's `test-runner` holds -`device`, and so would any row a config gives it to. - -`/system/bin/update` does not: the loader names the booted volume in the -inventory (`Role::Slot`), and `slots::grant` refuses an idle slot naming it, -whatever the table the updater wrote says. - -A write there is not a silent compromise: the loader holds every byte to the -signature at the next boot and refuses the slot by name. It is a denial — -the running slot is refused next boot and the machine falls back to an older -image, or to nothing on a one-slot image. - -**Exit**: the kernel's USB storage path is deleted with its xHCI driver -(`issues/the-kernel-is-small-interrupts-post-and-threads-wait.md`, step 10). -The kernel then serves no partition claim, `hold_source` goes with it, and -the running slot's volume and ROOT are reached only through the block -service's grants (`issues/the-block-service-does-not-refuse-the-running-root.md`). -No kernel hold of the volume is added in the meantime. diff --git a/issues/the-shell-runs-a-command-whose-redirect-it-could-not-open.md b/issues/the-shell-runs-a-command-whose-redirect-it-could-not-open.md index af256769fb9..d356dcdf7bc 100644 --- a/issues/the-shell-runs-a-command-whose-redirect-it-could-not-open.md +++ b/issues/the-shell-runs-a-command-whose-redirect-it-could-not-open.md @@ -13,15 +13,13 @@ of the file. `echo secret > /somewhere/refused` therefore writes `secret` to the terminal, or into whatever the shell's output is, and the line's status is the command's. The `2>` arms do the same without the line. -Read from the source, not measured. `tests/guest/rust-tests/src/bin/fs_share.rs` -launches a shell whose redirect's connection is refused and judges only that -the file was never made, so it does not hold this behaviour in place. +Read from the source, not measured, and no test holds it. Owner: the shell. ## Exit condition A command whose redirect could not be opened is not run, the shell says the -error the open answered, and the line's status is a failure; a guest probe +error the open answered, and the line's status is a failure; a test redirects into a path that does not open and reads back neither the command's output nor a zero status. diff --git a/issues/the-shell-says-not-found-for-every-spawn-error.md b/issues/the-shell-says-not-found-for-every-spawn-error.md index 84cbb634a38..7a260535824 100644 --- a/issues/the-shell-says-not-found-for-every-spawn-error.md +++ b/issues/the-shell-says-not-found-for-every-spawn-error.md @@ -15,10 +15,8 @@ that is not there. Evidence: the T14's `proctreecase` boot at 1e9cb8c77. The kernel recorded `spawn: refused under pid 79 at depth 65, more than 64 below init`, and the shell at that depth printed `/system/bin/test_rs_process_tree: not found`. -`process_tree`'s chain (`tests/guest/rust-tests/src/bin/process_tree.rs`) -accepts that line as its stop, and the row's judge reads the kernel's record -for the depth. +`process_tree` has since spawned its links directly and reads their own +refusal, so it no longer goes through the shell. Exit: the shell prints the error the spawn answered, as its `detach` does, and -says `not found` for `ErrorKind::NotFound` alone; `process_tree`'s chain no -longer accepts `: not found` as its stop. Owner: the shell. +says `not found` for `ErrorKind::NotFound` alone. Owner: the shell. diff --git a/issues/the-so-caches-refusals-are-narrower-than-its-reach.md b/issues/the-so-caches-refusals-are-narrower-than-its-reach.md index 10d3d444fd6..cb111115ab9 100644 --- a/issues/the-so-caches-refusals-are-narrower-than-its-reach.md +++ b/issues/the-so-caches-refusals-are-narrower-than-its-reach.md @@ -21,8 +21,8 @@ rewrite of a library on `/tmp` carries the identity it had. *Exit condition:* an identity that does not rest on a clock — a content hash, a per-file generation the mount bumps on every write, or a `FileId` plus a write -counter — with a test that rewrites a library at the same size on the -`rtc-dead` machine and loads the second image. +counter — with a test that rewrites a library at the same size with every +write stamped 0 and loads the second image. ## The budget is a machine-wide, boot-permanent denial @@ -40,7 +40,7 @@ track gives a mapped image a rule that lets one be taken back. ## The key is the caller's spelling, so an alias walks past the refusal -`Vfs::resolve_absolute` (`kernel/src/vfs.rs:237`) normalises `.` and `..` and +`Vfs::resolve_absolute` (`kernel/src/vfs.rs`) normalises `.` and `..` and resolves no symlink, so one file under two spellings is two entries and two physical images. The base had the same key, so this is not a regression — but the refusal is now **per spelling**, which is worse than the key alone: a diff --git a/issues/the-socket2-forks-ownedfd-holds-a-table-key-not-a-handle.md b/issues/the-socket2-forks-ownedfd-holds-a-table-key-not-a-handle.md deleted file mode 100644 index 162ebf7dca8..00000000000 --- a/issues/the-socket2-forks-ownedfd-holds-a-table-key-not-a-handle.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -status: open -kind: defect -opened: 2026-10-12 ---- - -# The socket2 fork's `From for OwnedFd` holds a table key, not a handle - -Read from the fork at the commit the lock names and the `toyos` branch's tip, -`1d90250`, and not run. socket2's ToyOS backend (`src/sys/toyos.rs`) names a -socket by a key into a table of its own, counted up from 1000 (`NEXT_FD`, -`sockets`, `alloc_fd`, lines 320-329), and keeps the socket's kernel handle -inside the entry (`SocketState`, 331-364). Its `From for -OwnedFd` (1237-1241) hands `OwnedFd::from_raw_fd` that key, taken by -`socket_into_raw` (435-439), which forgets the socket. So: - -- The `OwnedFd` holds a number the kernel never gave the process. `RawHandle` - (`sdk/abi/src/handle.rs`) reads a key K as slot K mod 4096 at generation - K / 4096: the first socket's key, 1000, as slot 1000 at generation 0. -- What the socket held is never let go: its entry stays in the table, so its - `Drop` (408-421), which sends `tcp_close` and closes its connection's or - listener's handle, never runs while the process lives. -- The `OwnedFd`'s drop closes the key as a handle (`OwnedFd`'s `Drop`, - `rust/library/std/src/os/fd/owned.rs:241-244` at the gitlink's `c1b8813`). - The kernel looks it up in the process's table (`sys_close`, - `kernel/src/syscall/handles.rs`; `HandleTable::remove`, - `kernel/src/object/handle.rs`). A slot past the table, empty, or at another - generation is `BadHandle` or `Stale`, and `process::handle_fault` ends the - process with a handle fault. A process holding a live handle at that slot and - generation loses it silently, whatever object it names, and the next use of - that handle by its holder is `Stale` and ends the process there. - -The other fd impls cross the same two numbers. `AsFd`, `AsRawFd` (1225-1235) -and `IntoRawFd` (1243-1247) hand out the key as a descriptor. `From` -and `FromRawFd` (1249-1259) take a kernel handle as a key: it names no entry, -so every call on the socket that looks the number up is refused, "invalid -socket fd" or, from `poll_connect` (538-545), `NotConnected`, and its drop -closes nothing and leaks the handle; or, where the handle's number is a live -key, it names another socket's entry, which its drop then closes. An unconnected socket -(`SocketState::Unconnected`) holds no kernel object at all, so it has no handle -to give. - -Nothing calls the conversion yet. On ToyOS socket2 is built only for the tokio -fork (`902fb97`). There `TcpSocket::connect` and `listen` drop their socket2 -socket instead of converting it (`tokio/src/net/tcp/socket.rs:838-843` and -`913-923`), and its ToyOS `IntoRawFd for TcpSocket` (1081-1086) passes the key -to whoever calls it. Nothing in the tree, `system/sshserver` included, or in -the russh fork (`389804d`) calls one. - -**Exit**: on ToyOS, `From for OwnedFd` and each other impl at -1225-1259 either names a kernel handle the process holds for that socket, or -does not compile, and the lock names the fork commit that does it. A root -`[patch.crates-io]` that no longer names `ToyOSOrg/socket2` (stage 4 of -`issues/the-ssh-server-is-toyos-own-and-russh-is-gone.md`) also meets this, -because upstream socket2 has no ToyOS backend. - -**Owner**: whoever holds `issues/toyos-has-its-own-network-stack.md`; the -change is a commit on the socket2 fork's `toyos` branch. diff --git a/issues/the-socket2-forks-so-broadcast-setter-does-nothing.md b/issues/the-socket2-forks-so-broadcast-setter-does-nothing.md index 579c5a70e3c..f50b9b26491 100644 --- a/issues/the-socket2-forks-so-broadcast-setter-does-nothing.md +++ b/issues/the-socket2-forks-so-broadcast-setter-does-nothing.md @@ -6,7 +6,7 @@ opened: 2026-10-08 # The socket2 fork's SO_BROADCAST setter does nothing -Read from the fork at the commit the lock names (`1d90250`), not run. On +Read from the fork at the commit the root `Cargo.lock` names (`2aa6b00`), not run. On ToyOS, socket2's `setsockopt` (`src/sys/toyos.rs`) matches `(SOL_SOCKET, SO_BROADCAST)` to an empty arm and answers `Ok`, and its `getsockopt` answers 0 for it: a setter that says yes to a permission it hands diff --git a/issues/the-supervisors-claim-of-a-pci-function-the-t14-lacks-holds-preemption-off-for-3-8-ms.md b/issues/the-supervisors-claim-of-a-pci-function-the-t14-lacks-holds-preemption-off-for-3-8-ms.md deleted file mode 100644 index 5b4153c9de8..00000000000 --- a/issues/the-supervisors-claim-of-a-pci-function-the-t14-lacks-holds-preemption-off-for-3-8-ms.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -status: open -kind: defect -opened: 2026-10-02 ---- - -# init's claim of a PCI function the T14 lacks holds preemption off for 3.8 ms - -On LENOVO 20W0003AMZ, BIOS N34ET71W (1.71), one boot of `main` at `c59e09ed6` -with a throwaway instrument that names a window's opener and samples its CPU -every 2 ms (#681, comment 5968784527) read init's `SYS_DEVICE_CLAIM` for -blockd's `pci:1b36:0010`, which this machine does not have, holding cpu0's -interrupts off for 3,817,265 ns at 1.162 s, from the syscall's entry to its -return, with `MSR_SMI_COUNT` unmoved. Its one sample, 1.7 µs before it closed, -is in `PciDevice::is_id` under `pcidev::claim`. By reading, the claim then read the -vendor ID of each of the 24 functions the kernel enumerated -(`kernel/src/pcidev/mod.rs`); nothing has said what it spent 3.8 ms on. - -**That path no longer exists in that form** (#926, not re-measured): the -supervisor matches a `devices` entry against the inventory and asks the kernel -for no function the machine lacks, and `pcidev::claim` takes an arrival and -reads no function's vendor ID. The figures here stay as measured. What -replaced the claim at boot is one `SYS_DEVICE_INVENTORY` read per start of a -row with a PCI matcher, and nobody has measured it; the exit below reads a -claim no boot now makes, so a T14 reading of that read is what decides -whether this closes. - -A syscall's body now runs with interrupts open and preemption off -(`issues/syscall-preemption-is-incidental.md`). #716's interleaved -`mask_windows` boots (comment 5979107466; readbacks -`irqon/metal/{base,head}/mask_windows/runN` and `irqon/metal/head-full`) each -made this claim (`supervisor: diskserver: no pci:1b36:0010 on this machine`) -before the first job's report, which spans it and the partition claims below. -cpu0's line there: - -| arm | `irqs_off_ns` | `preempt_off_ns` | -|---|---|---| -| base, main at `d47b383cf` | 6592629, 6565352, 6491279, 6614740, 6509061 | 6592463, 6565169, 6491091, 6614595, 6508897 | -| head, images at `f89e73128` | 3061, 10315, 14214, 15492, 3510; 15409 | 6550562, 6517919, 6632892, 6549746, 6619444; 5706681 | - -On the head no CPU but the one `hold_once` held reads more than 44537 ns of -interrupts off in that report, which bounds every window inside the claim -from above; no reading separates the claim's own windows from the rest. - -Before the first job's exit cpu0 also carried init's partition claims, 6.0 -and 6.6 ms in that boot, which left the kernel with its xHCI, and the -`mask-windows` kernel (`kernel/src/windows.rs`) prints each CPU's longest -window and no opener, so its cpu0 line there reads this claim only once those -have left the kernel. - -**Owner**: `issues/the-kernel-is-small-interrupts-post-and-threads-wait.md`, -whose stage 5 exits on no interrupts-off window longer than a register access, -and whose step 10 takes the partition claims off cpu0. - -**Exit**: a T14 boot reads the longest interrupts-off window inside that -claim no longer than one register access, with `MSR_SMI_COUNT` unmoved across -that window. A figure for that access comes from the claim's own reads timed -on the T14, and nobody has timed them yet. diff --git a/issues/the-sysroots-core-and-alloc-carry-the-compiler-profiles-debug-assertions.md b/issues/the-sysroots-core-and-alloc-carry-the-compiler-profiles-debug-assertions.md deleted file mode 100644 index 456a7af965a..00000000000 --- a/issues/the-sysroots-core-and-alloc-carry-the-compiler-profiles-debug-assertions.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -status: open -kind: defect -opened: 2026-10-04 ---- - -# The sysroot's `core` and `alloc` carry the debug assertions of a profile written for compiler contributors - -`std_config` (`build/sysroot.rs`) writes `profile = "compiler"` into the -bootstrap configuration every ToyOS library is built under: the kernel's and -the loader's `core` and `alloc` (`Libraries::Freestanding`) as well as every -program's `std`. In the `rust/` fork at `a0d44493`, that profile -(`src/bootstrap/defaults/bootstrap.compiler.toml`) sets -`rust.debug-assertions = true`; bootstrap's `std_debug_assertions` falls back -to it when `rust.debug-assertions-std` is unset -(`src/bootstrap/src/core/config/config.rs`), which `std_config` leaves it, and -passes it to the library crates' cargo profile -(`src/bootstrap/src/core/builder/cargo.rs`). Upstream's distributed libraries -are built without. The libraries' assertions were never chosen: `std_config`'s -doc comment takes the profile so the libraries are built as the compiler was. -The guest profile keeps debug assertions on in ToyOS's own code "because -fail-fast beats speed here" (the root `Cargo.toml`); whether the libraries should too is the decision nobody -made. - -So every kernel and program ships `alloc`'s and `core`'s `debug_assert!`s, -`RawVecInner::finish_grow`'s alignment check among them -(`library/alloc/src/raw_vec/mod.rs`), and the precondition checks `ub_checks` -gates in the code compiled into those crates. The same profile's -`frame-pointers = true` reaches them as `-Cforce-frame-pointers=true`. - -**Measured.** A link-time no-panic proof: a `no_std` binary for -`x86_64-unknown-none` whose panic handler calls a symbol nobody defines, -linking a fallible parser. The binary with no parser links in every arm. Each -of a `Vec::push`, a `handle_alloc_error`, an over-wide shift and an -out-of-range index fails to link in every arm without `-Zub-checks=no`; with -it, only the `handle_alloc_error` control ran, against stable's and nightly's -libraries, and failed, and the ToyOS sysroot's rows ran no failing control. -The parser, built with the -binary's own debug assertions off, links against stable 1.98.1's and nightly's -upstream libraries and fails against a ToyOS sysroot, at `opt-level = 2` and -under fat LTO alike. In the parser's arm that writes into a vector's spare -capacity and sets its length in `unsafe`, lld's `--why-live` names `core::panicking::panic` and `panic_fmt`, -live through `RawVecInner::finish_grow` alone; in its default arm -`alloc::raw_vec::handle_error` and `handle_alloc_error` are live beside them, -and against upstream's libraries nothing is. Under the guest -profile with `-Zub-checks=no` it links against stable's and fails against -ToyOS's. The matrix, the `--why-live` logs and the spike's source are a -comment on the pull request that added this file. So stage 2 of -`issues/a-panic-is-never-an-accident.md` can prove a parser panic-free -against an upstream library on the host, and against none the kernel links. - -**Not measured**: the `ub_checks` share of what the proof found, which no log -attributes; what the libraries' assertions, checks and frame pointers cost a -kernel or a program in time or in image bytes, on QEMU or the T14. - -Owner: the orchestrator. Stage 2 of -`issues/a-panic-is-never-an-accident.md` exits on a host step against -upstream libraries, so it does not wait on this file. - -**Exit**: `std_config` sets `rust.debug-assertions-std` by a line of its own, -so the compiler profile decides nothing about the libraries' assertions; and -either it is `false` and the proof's parser, built with its own debug -assertions off, links against the sysroot that configuration builds, or a -ruling keeps it `true` and this file becomes `rejected`, citing the ruling. diff --git a/issues/the-t14-hung-after-rebooting-with-its-i219-faulted.md b/issues/the-t14-hung-after-rebooting-with-its-i219-faulted.md index 365e3d63330..442a3408152 100644 --- a/issues/the-t14-hung-after-rebooting-with-its-i219-faulted.md +++ b/issues/the-t14-hung-after-rebooting-with-its-i219-faulted.md @@ -34,4 +34,4 @@ bus mastering with receive still enabled. It stayed that way to the reset. The swap's fix (the kernel resets a released function, netd stops the part before it starts it mastering) removes that state; the next swap run says whether the hang goes with it. If it does not, the reboot path is owed a quiesce of every -function a process holds, as it already does for its own xHCI controllers. +function a process holds. diff --git a/issues/the-t14-mouse-may-be-another-defect.md b/issues/the-t14-mouse-may-be-another-defect.md index 092589b5812..aaffc483cd0 100644 --- a/issues/the-t14-mouse-may-be-another-defect.md +++ b/issues/the-t14-mouse-may-be-another-defect.md @@ -4,41 +4,33 @@ kind: defect opened: 2026-08-03 --- -# The T14's mouse may not have been this defect at all, and the next boot is what says +# A USB mouse on the T14 moved nothing for 28 s, and no boot since has read one -Fixed in passing and **unverifiable in this suite by construction**, so it is -recorded rather than claimed. The HID endpoint context's dword 4 was a flat `8` -copied from EP0's, where a control endpoint has no Max ESIT Payload and 8 is a -setup stage's Average TRB Length. Every periodic endpoint this driver configured -therefore declared that it moves **zero bytes per service interval** — the term -xHCI 1.2 §6.2.3.8 defines and §4.14.2 makes the periodic scheduler's input. -Linux's `xhci_endpoint_init` writes `max_packet` into both halves for a low- or -full-speed interrupt endpoint; the driver now does the same. QEMU has no -bandwidth scheduler and never reads the field, so no test here can tell the two -values apart. +On the T14 compositor session of 2026-08-03 a USB mouse plugged in at 30.4 s +produced no motion for the rest of the boot +(`issues/t14-lost-every-integrated-input.md` records the same session's +integrated inputs). The kernel's xHCI driver that ran it is gone: `usbd` +drives every xHCI controller (`13ed7b4f4`), with its own HID path +(`system/usbd/xhci/src/hid.rs`), so nothing that driver did is evidence about +this one. -That leaves two candidates for the 28 silent seconds, and they are -**distinguishable on the next metal boot**, which is why closing the first did -not close this: +What that session left open, and where `usbd` stands on each: -1. the endpoint's first transfer completed with an error — the new line names - the device, the endpoint and the code, and the recovery runs; -2. the endpoint was never scheduled at all — **no line, because no completion - event ever arrives**, and the mouse is still silent. +- **Max ESIT Payload.** The old driver declared zero bytes per service + interval for every periodic endpoint (xHCI 1.2 §6.2.3.8, §4.14.2). `usbd` + writes the endpoint's payload into dword 4 for a periodic endpoint + (`system/usbd/xhci/src/configure.rs`). QEMU has no bandwidth scheduler and + never reads the field, so no host or QEMU test tells the values apart. +- **A first transfer that completes with an error, or an endpoint never + scheduled at all.** That session's log could not tell them apart; under + `usbd` the first names the completion code, and the second leaves no + completion. +- **`SET_IDLE`.** The old driver never sent it; `usbd` sends `SET_IDLE(0)` to a + keyboard and says a refusal (`hid.rs`). -Ruled out already: SET_PROTOCOL is sent to every boot-interface HID and the T14 -log carries no failure line for it, so EP0 was not left halted (see the open -item on that in this section). The interval encoding is legal — -`bInterval=10` frames at low speed gives `log2(10 × 8) = 6`, inside Table 6-12's -3..10. `SET_IDLE` (HID 1.11 §7.2.4) is the one class request the enumeration -path does **not** send, where Linux's `usbhid_parse` sends it unconditionally -and ignores the result; its absence leaves the device on its default idle rate, -which is chattier and not silent, so it is not a candidate for this — but a -device that expects it is a real class of hardware and nothing here has one. +No T14 boot has had a USB mouse read under `usbd`: its T14 rows serve the boot +stick and drive the Type-C controller, and none binds a pointer. -## Promoted 2026-08-25 - -The fix (max_packet in both endpoint-context dword-4 halves) is in the tree -and only the next metal boot can tell the two remaining candidates apart. -Owed to whoever runs the next T14 session with the mouse plugged in: read -whether a completion-error line names the endpoint, or stays silent. +**Exit**: a T14 boot with a USB mouse plugged in whose `usbd` lines show the +pointer's reports reaching the owner of input, or the completion code or +silence that says which of the two cases above it is. diff --git a/issues/the-t14-touchpad-is-i2c-hid-and-unbuilt.md b/issues/the-t14-touchpad-is-i2c-hid-and-unbuilt.md index 41a3db4c0c7..1bca8806d43 100644 --- a/issues/the-t14-touchpad-is-i2c-hid-and-unbuilt.md +++ b/issues/the-t14-touchpad-is-i2c-hid-and-unbuilt.md @@ -30,7 +30,6 @@ Machine facts it will need, none of which QEMU can supply: this machine. - Metal boot is about **1151 ms against QEMU's 196 ms**, roughly 5.9×. -Three T14 defects are open beside this one: +Two T14 defects are open beside this one: `issues/t14-lost-every-integrated-input.md`, -`issues/pulling-the-boot-stick-freezes-the-t14.md`, -`issues/t14-keyboard-will-not-report-its-scancode-set.md`. +`issues/pulling-the-boot-stick-freezes-the-t14.md`. diff --git a/issues/thread-exits-completion-post-is-the-second-one.md b/issues/thread-exits-completion-post-is-the-second-one.md deleted file mode 100644 index f9444a61d54..00000000000 --- a/issues/thread-exits-completion-post-is-the-second-one.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -status: open -kind: defect -opened: 2026-08-24 ---- - -# A thread's exit posts `Gone` on its own watch twice, and only the second post is load-bearing - -`process::thread_exit` posts a completion before it leaves: - -```rust -if let Some(handle) = crate::sched::driver::current_handle() { - crate::completion::post( - crate::completion::Subject::of(handle.watch()), - crate::completion::Outcome::Gone(crate::completion::Reason::Closed), - ); -} -scheduler::exit_current(code); -``` - -`exit_current` reaches `driver::pass(Dispose::Exit)`, and the pass after that -one drops the task's payload through `Hw::release` -(`kernel/src/arch/x86_64/hw.rs`), which ends with `TaskHandle::publish_released` — -and that posts `Gone(Closed)` **on the same watch**, which its own comment -states in those words: *"the retirer is armed on this thread's own watch — the -same subject a joiner uses, and the reason the release no longer needs a queue -of its own"* (`kernel/src/sched/payload.rs`). - -So a `SYS_THREAD_JOIN` on a sibling is released twice, by two posts to one -subject, and the second one happens whether or not the first is written. The -difference between them is one scheduler pass of latency and nothing else: the -zombie mark that the joiner's predicate reads is already in the table before -either post, because `release_thread` writes it before returning. - -**How this was found, and why it is filed rather than acted on.** The host -model of the lifecycle (`kernel/pure/proclife`) was built with a negative control -that reverted the post's subject to the process's main thread — the kernel this -tree had before `1bfe4e5b`, when the wake was by name into a shared parking lot. -The control had no teeth: with `publish_released` modelled faithfully, the -joiner is released in every interleaving anyway. That is a fact about today's -kernel rather than about the model, and it is worth recording because the -opposite is what a reader of `thread_exit` would assume. - -**What is owed.** One of two sentences, and the evidence for it: - -- the early post buys promptness that a join is entitled to — in which case say - so at the site, with what a pass of latency costs a joiner; or -- it buys nothing, and it goes, leaving `publish_released` as the one place a - thread's death reaches its waiters. - -Do not answer it by deleting the post and running the suite green: `std_threading` -and every other join in the tree would pass either way, which is the whole -content of this entry. - -**2026-08-25, promoted to `defect`.** Both posts are still there on this tree: -`process::thread_exit` posts `Gone(Closed)` through `handle.watch()` -(`kernel/src/process.rs`, now under a `debug_assert_eq!` that the subject is -`Watch::Thread(pid, tid)`), and `TaskHandle::publish_released` posts the same -outcome on the same watch at `kernel/src/sched/payload.rs:222`. Two posts to one -subject where the second is load-bearing regardless is either an undocumented -promptness guarantee or dead code on the exit path — "one of two sentences and -the evidence for it" is owed work, not an observation. Owed by the lifecycle -work in `kernel::proclife`, which is the only machine that can enumerate the -interleaving either answer rests on. diff --git a/issues/three-comments-say-the-kernel-uses-toyos-fat32.md b/issues/three-comments-say-the-kernel-uses-toyos-fat32.md new file mode 100644 index 00000000000..0ef4e4eb80d --- /dev/null +++ b/issues/three-comments-say-the-kernel-uses-toyos-fat32.md @@ -0,0 +1,23 @@ +--- +status: open +kind: defect +opened: 2026-10-12 +--- + +# Three comments say the kernel uses toyos-fat32, and it no longer depends on it + +The kernel's dependency on `toyos-fat32` went with its USB storage path +(`13ed7b4f4`): `git grep -n fat32 -- kernel` finds nothing, and the root +`Cargo.lock` lists no `toyos-fat32` among `kernel`'s dependencies. Its users +are `system/fileserver`, `system/update` and `tests/metal`. Three comments +still say the kernel uses it: + +- `lib/fat32/Cargo.toml`'s header: "the kernel depends on it by path"; +- `build/fatformat.rs`'s module doc: "`toyos-fat32`, which is the kernel's + driver"; +- `populate`'s doc in `build/image.rs`: "the driver the kernel appends + `kernel.log` with". + +**Exit**: none of the three comments names the kernel as the crate's user. + +Owner: the orchestrator, which dispatches it. diff --git a/issues/toybox-is-one-row-for-every-applet.md b/issues/toybox-is-one-row-for-every-applet.md index 376920a55f6..cb26ffffea7 100644 --- a/issues/toybox-is-one-row-for-every-applet.md +++ b/issues/toybox-is-one-row-for-every-applet.md @@ -6,61 +6,55 @@ opened: 2026-08-20 # `[programs.toybox]` is one authority row for every applet in the binary -Noticed while giving `/system/bin/shutdown` the `power` right -(`sdk/abi/src/handle.rs`'s `Rights::POWER`). - A manifest row's granularity is the **binary**, and `/system/bin/toybox` is many -programs behind that many symlinks. `/system/bin/init`'s `declared` resolves -`/system/bin/shutdown` through `std::fs::read_link` to `/system/bin/toybox` and endows the -`toybox` row (`userland/init/src/main.rs`), so every applet in the image is -spawned holding the union of what any of them needs. - -This is not new — `system.toml`'s `toybox` row already hands `ls` a connector to -the compositor and one to soundd, because `screen` and `tone` need them. What is -new is the size of the largest member of that union: `power` ends the machine, -so `/system/bin/echo` in the shipped image now holds a capability that cuts the power, -where before it held two connectors it does not use. +programs behind that many symlinks. `/system/bin/supervisor`'s `declared` +resolves `/system/bin/shutdown` through `std::fs::read_link` to +`/system/bin/toybox` and endows the `toybox` row +(`system/supervisor/src/main.rs`), so every applet in the image is spawned +holding the union of what any of them needs. + +In the shipped image (`images/system.toml`) that row receives the +`compositor`, `soundserver`, `surface`, `power` and `display` connectors and +holds `syscap = ["roster"]`. `power` is the connector `shutdown` and `reboot` +ask the supervisor to stop the machine through (`system/toybox/src/shutdown.rs`, +`reboot.rs`), so `/system/bin/echo` in the shipped image can end the machine, +and `roster` is `ps`'s. ## What it is not It is not the closed isolation defect that `SYS_SHUTDOWN` demanded no -capability at all. Before that change -*every process in the machine* could halt it with an argument-less syscall; -now the set is one program's row plus `/system/bin/init`. The remaining exposure is a -program a person deliberately runs from a shell, not a daemon endowed one -connector. +capability at all. Before that change *every process in the machine* could +halt it with an argument-less syscall; now the set is the rows that receive +`power`. The remaining exposure is a program a person deliberately runs from a +shell, not a daemon endowed one connector. ## What would fix it A `[programs]` key whose row is chosen by the **invoked path** rather than by the binary the symlink resolves to. `declared` already tries the caller's own path first (`row(path)` before `read_link`), so a row keyed `shutdown` with -`path = "/system/bin/shutdown"` would win — but nothing builds such a row today: a -`[programs]` key names a crate the build compiles, and `bin/shutdown` is a +`path = "/system/bin/shutdown"` would win — but nothing builds such a row today: +a `[programs]` key names a crate the build compiles, and `bin/shutdown` is a `[symlinks]` entry with no crate behind it. Making the build able to declare an -authority row over an existing binary is the work, and it is worth doing only -if the union keeps growing. - -## Its size is measured now, and it is a number - -`endowment_denied`'s `every_applet_holds_only_what_its_policy_names` reads the -links off `/system/bin` and the rows off `/system/etc/system.manifest`, neither through -`declared`, and holds each applet against a per-applet policy table. On the -image a guest test boots it answers `15 links behind /system/bin/toybox, 14 declared -over-grants` — every applet but `tone` endowed a connector to soundd because it -needs one. `DECLARED_OVER_GRANTS` is that list, so the over-grant cannot grow -without a red naming what grew, and it shrinks when the row is split. - -The shipped image is not that image and is still unmeasured: a guest boots -`tests/images/testcases.toml`, whose `toybox` row carries no `syscap` at all, so -nothing yet sees `/system/bin/echo` holding `Rights::POWER`. -`issues/one-manifest-row-grants-every-applet-and-one-is-compared.md` -carries what closing that costs. - -**Promoted to `defect` 2026-08-25** (finding-lifecycle ruling). `/system/bin/echo` in -the shipped image holds `Rights::POWER` because `/system/bin/shutdown` is behind the -same binary — an authority over-grant that is true of the image as built, not a -hypothetical. Owed by whoever makes the build able to declare a `[programs]` -row over an existing binary keyed by the invoked path; `declared` already tries -`row(path)` before `read_link`, so the resolution half exists and the build half -does not. +authority row over an existing binary is the work. + +## Its size is measured on the test image only + +`endowment_denied`'s `every_applet_holds_only_what_its_policy_names` +(`tests/guest/rust-tests/src/bin/endowment_denied.rs`) reads the links off +`/system/bin` and the rows off `/system/etc/system.manifest`, neither through +`declared`, and holds each applet against `APPLET_NEEDS`. On the image a guest +test boots, `tests/images/testcases.toml`, whose `toybox` row receives +`soundserver` alone, every applet but `tone` holds that connector without +needing it; `DECLARED_OVER_GRANTS` is that list, so the over-grant cannot grow +there without a red naming what grew. + +The shipped image is not that image and is still unmeasured: nothing yet sees +`/system/bin/echo` holding `power`. +`issues/one-manifest-row-grants-every-applet-and-one-is-compared.md` carries +what closing that costs. + +**Exit**: each applet behind `/system/bin/toybox` is endowed only what its own +row grants, through a `[programs]` row the build declares over the existing +binary keyed by the invoked path, and the shipped image's applets are held to +their policy as the test image's are. diff --git a/issues/toyos-abi-decodes-only-the-roster-header-not-its-entries.md b/issues/toyos-abi-decodes-only-the-roster-header-not-its-entries.md index f845a98b1d0..51aad1ad029 100644 --- a/issues/toyos-abi-decodes-only-the-roster-header-not-its-entries.md +++ b/issues/toyos-abi-decodes-only-the-roster-header-not-its-entries.md @@ -6,27 +6,27 @@ opened: 2026-09-28 # `toyos-abi` decodes only the roster header, not its entries -`SysinfoHeader::decode` (`sdk/abi/src/syscall.rs:85`) is "the one spelling of +`SysinfoHeader::decode` (`sdk/abi/src/syscall.rs`) is "the one spelling of the header's offsets, so a reader takes a field rather than an index" — but -that one spelling stops at the header. `SYSINFO_ENTRY_SIZE` -(`sdk/abi/src/syscall.rs:103`) documents the entry's byte layout in prose -only, and every reader of an entry hand-spells its offsets instead of calling -a decoder: +that one spelling stops at the header. `SYSINFO_ENTRY_SIZE` documents the +entry's byte layout in prose only, and every reader of an entry hand-spells +its offsets instead of calling a decoder: -- `tests/guest/rust-tests/src/bin/kill_ends_every_wait.rs:140-141` — - `entry[9] == 0`, `entry[8]`. -- `tests/guest/rust-tests/src/bin/process_lifecycle.rs:258` — - `buf[pos + 9] != 0, buf[pos + 8]`. -- `tests/guest/rust-tests/src/bin/abuse_thread_name.rs:62` — `entry[9]`. -- `system/toybox/src/ps.rs:64-65` — `buf[pos + 8]`, `buf[pos + 9] != 0`. +- `system/toybox/src/roster.rs` — `buf[pos + 8]`, `buf[pos + 9] != 0`. +- `tests/guest/rust-tests/src/roster.rs` — `buf[pos + 9] != 0, buf[pos + 8]`. +- `tests/guest/rust-tests/src/bin/kill_ends_every_wait.rs` — `entry[9] == 0`, + `entry[8]`. +- `tests/guest/rust-tests/src/bin/process_tree.rs` — `entry[9] == 0 && + entry[8] != ZOMBIE`. +- `tests/guest/rust-tests/src/bin/abuse_thread_name.rs` — `entry[9]`. -Four readers, each free to walk off by a field the way the header's own doc -comment warns against. `kernel/src/syscall/machine.rs:309-310` is the one -writer (`entry[8] = state; entry[9] = is_thread;`), so the four are already -one silent renumbering away from reading the wrong column. +Five readers, each free to walk off by a field the way the header's own doc +comment warns against. `kernel/src/syscall/machine.rs` is the one writer +(`entry[8] = state; entry[9] = is_thread;`), so the five are already one +silent renumbering away from reading the wrong column. -**Exit**: a `SysinfoEntry::decode` (or equivalent) in `toyos-abi`, the four -readers above converted to call it, and no roster-entry offset left -hand-spelled outside that one function. +**Exit**: a `SysinfoEntry::decode` (or equivalent) in `toyos-abi`, the readers +above converted to call it, and no roster-entry offset left hand-spelled +outside that one function. Owner: `sdk/abi/src/syscall.rs`, whoever next touches the roster ABI. diff --git a/issues/toyos-dns-decodes-what-a-published-crate-decodes.md b/issues/toyos-dns-decodes-what-a-published-crate-decodes.md index 98f90fd5258..de8faca2f80 100644 --- a/issues/toyos-dns-decodes-what-a-published-crate-decodes.md +++ b/issues/toyos-dns-decodes-what-a-published-crate-decodes.md @@ -6,7 +6,7 @@ opened: 2026-09-26 # toyos-dns decodes DNS wire format that a published crate already decodes -Resolving names is the system's job and belongs in netd; decoding DNS +Resolving names is the system's job and belongs in netstack; decoding DNS messages is not ToyOS's job. `toyos-dns` carries its own reader for names, compression pointers and records (#511), where `hickory-proto` is the widely used published decoder. The own reader was kept for size and a diff --git a/issues/toyos-ssh-offers-only-a-classical-key-exchange.md b/issues/toyos-ssh-offers-only-a-classical-key-exchange.md index c589b3c3650..8fbb17da786 100644 --- a/issues/toyos-ssh-offers-only-a-classical-key-exchange.md +++ b/issues/toyos-ssh-offers-only-a-classical-key-exchange.md @@ -12,17 +12,18 @@ with OpenSSH 9.9 and later: the pinned russh (`389804d`, `russh/src/negotiation.rs:103`) offers it first. The switch would therefore trade a post-quantum hybrid exchange for a classical one. Every session recorded against `toyos-ssh` with OpenSSH_10.3p1 logs `** WARNING: connection -is not using a post-quantum key exchange algorithm.` (#817). `ring` 0.17.14 has -no ML-KEM. +is not using a post-quantum key exchange algorithm.` (#817). -The owner ruled "Write our own ML-KEM (Recommended)". The switch of -`system/sshserver` waits for it, so no shipped server downgrades +The owner ruled "Write our own ML-KEM (Recommended)". `toyos-crypto` now has +it: ML-KEM-768 (`lib/crypto/src/mlkem.rs`), held to NIST's known answers by +`nist_mlkem768_kats` (`lib/crypto/tests/wycheproof.rs`), and the SSH +combiner `hybrid::for_ssh`. `toyos-ssh` (`build/ssh`) offers none of it. The +switch of `system/sshserver` waits for it, so no shipped server downgrades (`issues/the-ssh-server-is-toyos-own-and-russh-is-gone.md`, stages 2 and 3). **Owner**: `toyos-ssh`. **Exit**: - `toyos-ssh` offers `mlkem768x25519-sha256` first. -- NIST's ML-KEM-768 encapsulation known answers pass. - An OpenSSH recording that negotiated it replays byte for byte, and its client's log has no post-quantum warning. diff --git a/issues/two-clicks-the-compositor-reads-at-once-are-one-click.md b/issues/two-clicks-the-compositor-reads-at-once-are-one-click.md index aa4b96e61d7..60be4b76bb6 100644 --- a/issues/two-clicks-the-compositor-reads-at-once-are-one-click.md +++ b/issues/two-clicks-the-compositor-reads-at-once-are-one-click.md @@ -17,8 +17,8 @@ click land on a closed launcher. `consent_prompt` (`tests/toyos.rs`) met it on a loaded host (load average 57 when its batch of runs ended): it clicked `+` and then the launcher row with nothing between them, and no launch was made, which these two clicks -folded into one on the row of a launcher not yet open accounts for. The test -now waits for the launcher on the panel before its second click. +folded into one on the row of a launcher not yet open accounts for. That +test went with the integration tests `ff1a11dbd` cut. ## Owner diff --git a/issues/two-std-maps-still-answer-other-for-a-dead-peer.md b/issues/two-std-maps-still-answer-other-for-a-dead-peer.md deleted file mode 100644 index 2cb5ebc916d..00000000000 --- a/issues/two-std-maps-still-answer-other-for-a-dead-peer.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -status: open -kind: defect -opened: 2026-09-01 ---- - -# A `TcpStream` write to a departed peer is still `ErrorKind::Other` - -Making a broken pipe reach a Rust caller as `BrokenPipe` put one exhaustive -`SyscallError -> ErrorKind` map in `sdk/std/sys/pal/mod.rs` -and pointed `sys/pipe`, `sys/stdio` and `sys/fs` at it. Two maps beside it -were not moved and still carry a `_ => Other` arm; the line numbers are those -of `rust` fork commit `719118153253`, which held these files: - -```rust -// sdk/std/sys/net/connection.rs:46 -fn syscall_err(e: SyscallError) -> io::Error { - match e { - SyscallError::WouldBlock => io::ErrorKind::WouldBlock.into(), - _ => io::Error::new(io::ErrorKind::Other, "syscall error"), - } -} -``` - -```rust -// sdk/std/sys/process.rs:272 -let kind = match e { - toyos_abi::syscall::SyscallError::NotFound => io::ErrorKind::NotFound, - _ => io::ErrorKind::Other, -}; -``` - -The first is on a write path. `TcpStream::write` -(`sdk/std/sys/net/connection.rs:199`) ends in -`syscall::write(self.raw_handle(), buf).map_err(syscall_err)` at `:216`, and a -netd socket's tx end is a pipe — so a Rust program writing to a socket whose -peer has gone gets `ErrorKind::Other` — the same wrong word the pipe path was -just cured of, one layer down. `SyscallError::Gone` is the kernel's word for it -(`kernel/src/object/ops.rs:381`). - -**And the claim that closed that one is only true of the three modules it -moved.** Its commit message and pull-request body said a new `SyscallError` -variant "now fails to compile until somebody decides its kind"; these two arms -would absorb one silently. The correction is recorded in this branch's history -and in the pull request, not left to be rediscovered here. - -## What closing it takes - -Pointing both at `sys::to_io_error` and deleting the local maps, as the three -already moved were. `sdk/std/sys/process.rs`'s is the spawn refusal and wants a -look at whether `NotFound` is doing work the shared map does not; the shared -map answers `NotFound` for the same variant, so it is likely a plain deletion. - -The instrument is the differential the pipe arm already uses, one layer down: a -guest arm whose `TcpStream::write` to a peer that has exited must answer -`BrokenPipe`, against libc's `send` on the same shape. `netd_gone_mid_bind` -is the boot that already stages a departed netd, and its module header records -what today's `Other` costs `/system/bin/sshd`. diff --git a/issues/volatile-composites-on-mmio-dma-structs.md b/issues/volatile-composites-on-mmio-dma-structs.md index 1d14eca2f44..6c1247d1006 100644 --- a/issues/volatile-composites-on-mmio-dma-structs.md +++ b/issues/volatile-composites-on-mmio-dma-structs.md @@ -4,15 +4,16 @@ kind: defect opened: 2026-08-20 --- -# `write_volatile`/`read_volatile` over a whole DMA/MMIO struct, ten times +# `write_volatile`/`read_volatile` over a whole DMA/MMIO struct -`clippy::volatile_composites` (nursery) measured **10**, kernel-only, all in -driver code that hands a composite type straight to `write_volatile` or -`read_volatile` instead of touching each primitive field on its own. The NVMe -driver's queue entries, virtio-gpu's EDID answer and the xHCI's `ErstEntry` -and `Trb` left the kernel with their drivers; what is left: - -- `drivers/virtio.rs:414,487` — the virtio descriptor. +`clippy::volatile_composites` (nursery) measured ten sites in the kernel that +hand a composite type straight to `write_volatile` or `read_volatile` instead +of touching each primitive field on its own. The NVMe driver's queue entries, +virtio-gpu's EDID answer and the xHCI's `ErstEntry` and `Trb` left the kernel +with their drivers. What is left is the virtio descriptor: `Virtqueue`'s +submission writes a whole `VirtqDesc` (`kernel/src/drivers/virtio.rs`) through +`Dma::write`, whose generic `write_volatile` (`kernel/src/mm/dma.rs`) takes any +`T: Copy`. The lint's own docs put the reason plainly: volatile ops are well-defined for a primitive because the compiler must emit exactly the load/store the source @@ -20,23 +21,10 @@ says, in order — the property MMIO needs. That guarantee is **not** extended to composite types; how rustc lowers a whole-struct `write_volatile` (one store, several, in what order) is implementation-defined and can change between compiler versions. The fix the lint wants is per-field volatile -access via pointer arithmetic. - -**Not adopted this pass.** This is not mechanical: at least the xHCI `Trb` -sites carry a real protocol constraint the lint can't see — hardware must not -observe the TRB as valid until its Control field (Cycle bit) is written, so -splitting a `write_volatile(ptr, trb)` into per-field stores has to preserve -that ordering by hand, on a path this driver depends on for every command and -transfer. Getting that wrong silently (works in QEMU, wrong on some real -controller's stricter timing, or vice versa) is worse than the current -implementation-defined-but-tested state. Virtio's descriptor likely has its -own per-field ordering rules from its spec, not derived here. +access. -Fixing this is real driver work: read each device's spec for what field -ordering its rings actually require, decide per site whether one -`write_volatile` is provably fine (e.g. the struct is small enough that the -target's ISA guarantees a single-instruction store, which is again not -something the *language* promises) or needs splitting, and add the -regression coverage that would have caught the difference. Filed rather than -fixed — `issues/clippy-stage-two-is-lints-one-at-a-time.md` records the measurement -and the reason it wasn't attempted here. +Fixing it is driver work: read the virtio specification for what field +ordering the descriptor table requires before the available ring publishes +it, decide whether one `write_volatile` is provably fine or needs splitting, +and add the coverage that would catch the difference. +`issues/clippy-stage-two-is-lints-one-at-a-time.md` records the measurement. diff --git a/issues/watch-window-spins-out-a-hold-its-poster-is-queued-behind.md b/issues/watch-window-spins-out-a-hold-its-poster-is-queued-behind.md deleted file mode 100644 index e0a10988147..00000000000 --- a/issues/watch-window-spins-out-a-hold-its-poster-is-queued-behind.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -status: open -kind: defect -opened: 2026-09-28 ---- - -# `watch-window` spins out a hold whose poster is queued behind it on the same CPU - -`kernel/src/watch.rs`'s `window::hold` spins in Ring 0 until the waiter's -notified bit is set or its 50 ms `WINDOW` lapses, and nothing in the loop is a -preemption point: a Ring 0 timer fire only sets `need_resched`, and an ordinary -wake to a CPU that is not asleep rings no IPI (`Urgency::Normal`) and is -drained at that CPU's next pass. So when the task that would post is queued on -the holding CPU, or its wake waits in that CPU's mailbox, the post cannot land -and the hold always lapses. - -`blocking_read_stress` is a strict ping-pong, so this is self-sustaining once -its two processes share a CPU: each half-trip's reader holds 50 ms with its -writer queued behind it, then parks, and the writer runs. The idle CPU's steal -probe cannot break it, because `answer_steal_requests` hands over nothing at -`fair_len() <= 1` and at most one of the pair is runnable at any pass. The -recorded reds fit it: `only 27 of 500 round trips completed inside 3s` is -about 111 ms a round trip, two lapsed holds. In that run (#536 at -`06c6195f`) the echo child made 28 reads and spent `cpu=1471ms`, about -50 ms each; the parent spent `cpu=1665ms`. - -Neither that the pair shared a CPU nor how it came to was observed; the guest -prints no placement for it. This is read from the code and one run's numbers, -not reproduced. - -**Exit**: a hold gives its CPU up while that CPU owes a pass or has work queued -(`CpuHandle::doorbell().kick_pending()`, `CpuHandle::load()`), so a post from a -task on the same CPU lands in it; and `blocking_read_window` shown green at a -rate on a guest whose canary pair shares a CPU. - -`watch-window` and `blocking_read_window` are deleted; `issues/blocking-read-window-reds-beside-other-guests.md` records the commit that restores them. diff --git a/issues/what-is-owed-on-file-size.md b/issues/what-is-owed-on-file-size.md index 34b4b88e74a..0a94f99ccea 100644 --- a/issues/what-is-owed-on-file-size.md +++ b/issues/what-is-owed-on-file-size.md @@ -17,11 +17,12 @@ what survives it is a different question. - `compositor/main.rs` → `toyos-desktop/` (pure, since moved to `system/compositor/desktop/`), `763712b`; the effects shell is `system/compositor/` over five files with a 71-line `main.rs`, `72705d9`. -- `xhci/mod.rs` → the port machine is `lib/xhci/`, with a host simulator, - `2e81ae8`. +- `xhci/mod.rs` → the port machine went to a crate with a host simulator, + `2e81ae8`; the whole driver has since left the kernel for `usbd` + (`13ed7b4f4`). - `soundd/main.rs` → `toyos-mixer/` (pure, `no_std`; since moved to `system/soundserver/mixer/`), 2026-08-20; the effects - shell is `userland/soundd/` over eight files with a 261-line `main.rs`, from a + shell was `userland/soundd/` (now `system/soundserver/`) over eight files with a 261-line `main.rs`, from a 2,366-line one. What the note asked for is what it got: **the mixing is sample-exact and proven so.** `system/soundserver/mixer/fixtures/mix-corpus.txt` was written by `soundd/src/main.rs` before a line of it moved, and @@ -49,10 +50,11 @@ what survives it is a different question. - `process.rs` — the lifecycle state machine is `kernel/pure/proclife/` (pure, `forbid(unsafe_code)`, every interleaving of a scripted pair of paths enumerated; #142's mechanism class now has a host reproduction). Three - subjects are still decided inside the file and reachable only by a booted - guest: the handle/endowment build (`build_child_handles` + - `Endowments::encode`, a crafted-input corpus's natural subject — the natural - second chunk), `handle_page_fault`'s window arithmetic (~290 pure lines + subjects are still decided in the kernel and reachable only by a booted + guest: the handle/endowment build (`build_child_handles`, now in + `kernel/src/loader/start.rs`, + `Endowments::encode`, a crafted-input + corpus's natural subject — the natural second chunk), + `handle_page_fault`'s window arithmetic (~290 pure lines inside a function that also does device I/O), and the accounting (`stats_from`, `retire_threads`' merge, `PageFaultTrace`). #142 and #156 remain the standing evidence. diff --git a/issues/whether-a-region-may-be-lent-is-a-runtime-option.md b/issues/whether-a-region-may-be-lent-is-a-runtime-option.md index 19b7f408efe..db4590ece67 100644 --- a/issues/whether-a-region-may-be-lent-is-a-runtime-option.md +++ b/issues/whether-a-region-may-be-lent-is-a-runtime-option.md @@ -15,8 +15,8 @@ error. The one test written for it, `blockd_lends_within_its_bound`, refused virtio-sound's pool, which left the kernel with its driver; the test is out of the guest suite and staged as a T14 row in `issues/the-guest-suite-runs-only-what-no-cheaper-tier-reaches.md`'s stage J, -where the kernel driver's pool it can refuse is HDA's. And the field's name -says nothing about lending. +which named HDA's pool for it to refuse, and HDA has left the kernel too +(#950). And the field's name says nothing about lending. **Exit condition.** A region's kind is a type — owned pages, a kernel driver's pool, an aperture — and `dma_map` takes only the owned-pages kind, so a region diff --git a/issues/x2apic-is-enabled-without-reading-firmwares-opt-out.md b/issues/x2apic-is-enabled-without-reading-firmwares-opt-out.md index 711348e329c..6fab1a061b5 100644 --- a/issues/x2apic-is-enabled-without-reading-firmwares-opt-out.md +++ b/issues/x2apic-is-enabled-without-reading-firmwares-opt-out.md @@ -8,10 +8,10 @@ opened: 2026-10-04 VT-d Rev. 4.1 §8.1, DMAR Flags bit 1 `X2APIC_OPT_OUT`: firmware asks system software "to opt out of enabling Extended xAPIC (X2APIC) mode", and software -checks it "as part of detecting X2APIC mode support". This kernel enables -x2APIC unconditionally in `arch::apic::init` (`enable_x2apic`), reached from -`arch::boot::interrupts`, before the DMAR is first read in `iommu::init` -(`kernel/src/main.rs`). The flag is logged (`x2apic_opt_out=` on the `iommu: +checks it "as part of detecting X2APIC mode support". This kernel declares +x2APIC on every CPU wherever `CPUID.01H:ECX[21]` offers it +(`kernel/src/arch/x86_64/control_regs.rs`), and nothing else decides the mode; +the DMAR is first read later, in `iommu::init` (`kernel/src/main.rs`). The flag is logged (`x2apic_opt_out=` on the `iommu: DMAR` line) and decides nothing. The owner's ruling on extended interrupt mode From 7629e1910762ea239e2484ff1260103c785eb942 Mon Sep 17 00:00:00 2001 From: japabu Date: Wed, 21 Oct 2026 21:31:48 +0200 Subject: [PATCH 2/2] issues: audit batch 3, review round 1 - the-t14-mouse-may-be-another-defect: usbd names a HID endpoint's completion code only when the endpoint will not restart, so a failure a restart clears is as silent as an endpoint that never completes; the text says so, and the exit asks for a boot that tells the two apart. - the-t14-touchpad-is-i2c-hid-and-unbuilt: no count of the T14 defects beside it, and its keyboard bullet says what ps2server does: it asks for no scancode set and resets the keyboard with translation on. - the-compiler-workspace-resolves-the-published-toyos-abi: the lock is cited at the current rust pin, a56b4057, which names the same four fork commits and toyos-abi 0.1.0 from crates.io. Co-Authored-By: Claude Opus 5.5 --- ...er-workspace-resolves-the-published-toyos-abi.md | 2 +- issues/the-t14-mouse-may-be-another-defect.md | 13 ++++++++----- issues/the-t14-touchpad-is-i2c-hid-and-unbuilt.md | 10 +++++----- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/issues/the-compiler-workspace-resolves-the-published-toyos-abi.md b/issues/the-compiler-workspace-resolves-the-published-toyos-abi.md index a0a381797d0..07409c04b5a 100644 --- a/issues/the-compiler-workspace-resolves-the-published-toyos-abi.md +++ b/issues/the-compiler-workspace-resolves-the-published-toyos-abi.md @@ -11,7 +11,7 @@ requirement accepts. The four forks `rust/Cargo.lock` pins that name `toyos-abi` — getrandom `toyos-0.2` at 46592414 and `toyos-0.3` at d3045447, libloading `toyos` at fa0abe77, stacker `toyos` at c25842ac — each require `"0.1"`, and the tree's crate is 0.16.0. So the committed lock (the -`rust` fork at `11e31a5277`) names `toyos-abi 0.1.0` from crates.io, and a +`rust` fork at `a56b4057`) names `toyos-abi 0.1.0` from crates.io, and a bootstrap run handed the `[patch]` records `[[patch.unused]] toyos-abi 0.16.0`. On a macOS or Linux host those dependencies are `cfg(target_os = "toyos")` and never compiled; the diff --git a/issues/the-t14-mouse-may-be-another-defect.md b/issues/the-t14-mouse-may-be-another-defect.md index aaffc483cd0..23d9723fc4c 100644 --- a/issues/the-t14-mouse-may-be-another-defect.md +++ b/issues/the-t14-mouse-may-be-another-defect.md @@ -22,9 +22,11 @@ What that session left open, and where `usbd` stands on each: (`system/usbd/xhci/src/configure.rs`). QEMU has no bandwidth scheduler and never reads the field, so no host or QEMU test tells the values apart. - **A first transfer that completes with an error, or an endpoint never - scheduled at all.** That session's log could not tell them apart; under - `usbd` the first names the completion code, and the second leaves no - completion. + scheduled at all.** That session's log could not tell them apart, and + `usbd`'s does not either. A transfer that ends with an error code is + restarted, and `usbd` names the code only when the endpoint will not run + again (`system/usbd/xhci/src/hid.rs`). An error the restart clears says + nothing, and neither does an endpoint that never completes. - **`SET_IDLE`.** The old driver never sent it; `usbd` sends `SET_IDLE(0)` to a keyboard and says a refusal (`hid.rs`). @@ -32,5 +34,6 @@ No T14 boot has had a USB mouse read under `usbd`: its T14 rows serve the boot stick and drive the Type-C controller, and none binds a pointer. **Exit**: a T14 boot with a USB mouse plugged in whose `usbd` lines show the -pointer's reports reaching the owner of input, or the completion code or -silence that says which of the two cases above it is. +pointer's reports reaching the owner of input. Or, where none arrive, the +boot's `usbd` says whether the endpoint completed transfers with an error +code, naming the code, or completed none at all. diff --git a/issues/the-t14-touchpad-is-i2c-hid-and-unbuilt.md b/issues/the-t14-touchpad-is-i2c-hid-and-unbuilt.md index 1bca8806d43..b79c97e0f37 100644 --- a/issues/the-t14-touchpad-is-i2c-hid-and-unbuilt.md +++ b/issues/the-t14-touchpad-is-i2c-hid-and-unbuilt.md @@ -25,11 +25,11 @@ Machine facts it will need, none of which QEMU can supply: bit must not gate the i8042 probe; a floating-bus `0xff` from port 0x64 is the real absence test. - The keyboard will not report its scancode set: the argument byte of the query - answers `0xEE`, which is the echo command's own reply. The driver reads the set - and never writes it, falling back to the firmware's translate bit — `0x77` on - this machine. + answers `0xEE`, which is the echo command's own reply. `/system/bin/ps2server` + asks for no set: it resets the keyboard with translation on, which leaves + set 2 translated (`system/ps2server/src/probe.rs`). - Metal boot is about **1151 ms against QEMU's 196 ms**, roughly 5.9×. -Two T14 defects are open beside this one: -`issues/t14-lost-every-integrated-input.md`, +T14 defects open beside this one include +`issues/t14-lost-every-integrated-input.md` and `issues/pulling-the-boot-stick-freezes-the-t14.md`.