Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4505f87
tests: the measured schedule — Fast is every PR, Nightly, Weekly; 17 …
Japabu Sep 27, 2026
cb43477
host tests for two guest catches: a join keeps its answer, a dump wai…
Japabu Sep 27, 2026
eb64791
schedule tests: each assertion names the reason it reds for
Japabu Sep 27, 2026
c956cb4
kernel-loom: the i8042 tally models explored one execution; they expl…
Japabu Sep 27, 2026
fae9097
kernel-loom: the tally model gates the release/acquire pair; the clai…
Japabu Sep 27, 2026
0fc0f5a
kernel-loom: the tally test states what it is, not a measurement of it
Japabu Sep 27, 2026
5e64f5a
issues: a loom model whose spawned thread opens with a load explores …
Japabu Sep 27, 2026
21005ec
issues: a loom model whose spawned thread opens with a load explores …
Japabu Sep 27, 2026
42918cf
Merge origin/main into wt/toyos-schedule
Japabu Sep 27, 2026
6f9a317
kernel/CLAUDE.md: console_line_atomicity is deleted, so it gates nothing
Japabu Sep 27, 2026
088ed7f
tests: no boot names a deleted binary, and --list refuses one that does
Japabu Sep 28, 2026
e527bee
issues: loom misses a store racing a load only when the storer loaded…
Japabu Sep 28, 2026
4919fbd
Merge origin/main into wt/toyos-schedule
Japabu Sep 28, 2026
0c03f40
tests: the review's round-2 findings on the measured schedule
Japabu Sep 28, 2026
4dec9e5
Merge origin/main into wt/toyos-schedule
Japabu Sep 28, 2026
a9dfb9c
tests: the harness's own checks live in the target that runs them
Japabu Sep 28, 2026
3b94e06
issues: the loom table loses the row of the model that went with its …
Japabu Sep 28, 2026
97f53b5
tests: run_exit_status is a host test; a settled join takes no table …
Japabu Sep 28, 2026
617e934
Merge origin/main into wt/toyos-schedule
Japabu Sep 28, 2026
4dff77b
Merge origin/main into wt/toyos-schedule
Japabu Sep 28, 2026
b1d8472
A join holds its own answer; DRIVEN_AND_SHARED exists only where it i…
Japabu Sep 28, 2026
9b2645e
DRIVEN_AND_SHARED is allowed dead again: clippy compiles toyos-build …
Japabu Sep 28, 2026
0b81fba
Merge remote-tracking branch 'origin/main' into wt/toyos-schedule
Japabu Sep 28, 2026
e30e7d7
Round 5 review fixes: the join-per-ask gap recorded on the syscall's …
Japabu Sep 28, 2026
aca8ebe
Merge origin/main into wt/toyos-schedule
Japabu Sep 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
name: nightly

# Everything that boots a guest, the host gate again to write the cache the
# merge queue restores, and portability: on main every night, and on any branch by
# `gh workflow run nightly.yml --ref <branch>`. Every job's logic is
# `cargo run -- --ci <job>` (src/ci.rs); this file says where each one runs.
# merge queue restores, and portability: on main every night, the weekly tier
# on Sundays, and on any branch by `gh workflow run nightly.yml --ref <branch>`.
# Every job's logic is `cargo run -- --ci <job>` (src/ci.rs); this file says
# where each one runs.

on:
schedule:
- cron: '0 3 * * *'
- cron: '0 3 * * 1-6'
- cron: '0 3 * * 0'
workflow_dispatch:

# Never cancelled: `build` may be an hour into a bootstrap.
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -274,3 +274,7 @@ strip = "debuginfo"
name = "toyos-build"
path = "tests/toyos.rs"
harness = false

[[test]]
name = "toyos-checks"
path = "tests/checks.rs"
4 changes: 2 additions & 2 deletions issues/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,5 +155,5 @@ owns "every policy about files — where they go, what they are called, how many
there are, what happens when the stick stops answering"
(`userland/logd/src/main.rs:1-10`). Gated by `esp_filesystem`,
`kernel_log_file`, `log_backing_read_error`,
`boot_volume_metadata_error`, `log_partition_layout`, `log_partition_identity`
and `wall_clock_file`, plus `toybox_cp_volume`.
`boot_volume_metadata_error`, `log_partition_layout` and
`log_partition_identity`, plus `toybox_cp_volume`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
status: open
kind: tooling
opened: 2026-09-28
---

# A settled thread join taking the table lock again is gated by nothing

`toyos_proclife::join::Join::ask` calls its `collect` only while the join is
unsettled, and `a_settled_join_does_not_take_the_table_again` holds that.
Whether `collect` is where the kernel takes `PROCESS_TABLE` is
`kernel/src/process.rs`'s `ask_join`, in no crate a host test compiles. PR
#564's round-4 review wrote it as
`let mut g = PROCESS_TABLE.lock(); join.ask(|| join::collect_zombie(g.as_mut().unwrap(), parent_pid, tid))`.
A settled join then takes the lock on every wake of its wait.

**Exit**: taking `PROCESS_TABLE` outside `ask_join`'s `collect` reds a host
test or a fast-tier test. Owner: orchestrator.

The syscall's answer-keeping is the same gap: `sys_thread_join`
(`kernel/src/syscall/proc.rs:154`) holds one `Join` across every ask, but
`a_join_asked_after_it_collected_keeps_its_answer` only replicates that shape
rather than calling the syscall, so a `Join::ask` built fresh per ask
(`join-per-ask`) stays host-green and reds only on the nightly guest
`fpu_isolation` (`thread_join failed`, `left: 18446744073709551614`).
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
status: expected-red
status: open
kind: defect
opened: 2026-08-20
---
Expand All @@ -20,6 +20,8 @@ Split out of `issues/build/parallel-tests-red-under-other-suites.md`, whose
rate table was never this test's — CI's single-guest-per-machine shards rule
out the contention shape that file is about.

**Exit condition.** The lost lines' cause is fixed, and
`console_line_atomicity` green on CI's `guest` shards, one guest per machine.
**Exit condition.** The lost lines' cause is fixed, shown against
`console_line_atomicity` as it stands at `1808fb8d` (its binary, the test
runner's `CONSOLE_JOBS` stdin and its harness arm), restored and green on CI's
`guest` shards, one guest per machine.
Owner: orchestrator.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ until the stop waits on it alone`, `stop: 4 of 7 userland thread(s) stopped
ok` and `Rebooting.`, then `shutdown: /log did not answer in 2000ms`; the uart
captured `nothing at all`. The harness's re-run alone was green in 2 s.

The same shape as
`issues/build/quiesce-wakes-on-the-last-exit-lost-its-serial-ready-beside-other-guests.md`,
on the sibling arm.

**Exit**: a cause for the empty uart on a boot that rebooted as designed, or
the marker waited for where the boot's reboot cannot race it.

Expand Down
25 changes: 25 additions & 0 deletions issues/build/the-harness-carries-three-helpers-nothing-calls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
status: open
kind: tooling
opened: 2026-09-27
---

# The harness carries three helpers nothing calls

`tests/common/mod.rs` puts `#[allow(dead_code)]` on nearly every module, so
the compiler says nothing about a harness helper whose last caller went. With
those attributes taken off, `cargo check --tests` on `wt/toyos-schedule` names
three that no test reaches, and none of the three was called by anything that
branch deleted:

- `tests/common/storage.rs`: `FileBlocks::whole`;
- `tests/common/qemu.rs`: the field `usb_images` and its method `usb_images`;
- `tests/common/qemu.rs`: `QmpDevices::set_link`.

The same pass names `tests/common/audio.rs`'s `completions` and `clients` and
`tests/common/stats.rs`'s `fisher_reject_at`, which the audio branch
(`wt/toyos-notiming`) deletes with their modules.

**Exit**: the three deleted, and the module-wide `allow(dead_code)` replaced by
nothing, so the next orphan is a warning the host gate denies. Owner:
orchestrator.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
status: open
kind: tooling
opened: 2026-09-28
---

# Which pass `drain_irqs` is told it is entered from is gated only by a nightly guest

`kernel-loom`'s `only_a_pass_entered_at_depth_zero_takes_the_request` holds
`Entered::may_serve`, the decision. The two call sites that choose its argument,
`kernel/src/sched/driver.rs`'s `pass` (`Entered::Pass` at the depth it was
entered at) and `pass_block` (`Entered::Blocking`), are in no crate a host test
compiles. PR #564's round-2 review handed `pass_block`'s drain
`Entered::Pass { depth: 0 }`: every host test and the fast tier stayed green,
and only the nightly `blocked_dump` went red.

**Exit**: a mutation of either call site's `Entered` reds a host test or a
fast-tier test. Owner: orchestrator.
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,7 @@ issues/boot-media/partition-claim-gives-up-reds-beside-other-guests-and-is-green
names as racing whichever fsync the boot reaches first. Not shown: whether
that race is this test's cause.

**Exit**: the cause shown on a red run's log, and the test green on a
nightly.
**Exit**: the cause shown on the log of a red run of `home_budget_refusal_retried`
as it stands at `1808fb8d` (its binary `test_rs_home_fsync_budget` and its
`storage` body), restored and run on CI's nightly `guest` shards; and that
test green on a nightly after the fix. Owner: orchestrator.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
status: expected-red
status: open
kind: defect
opened: 2026-08-08
---
Expand All @@ -16,5 +16,4 @@ probe found, only this one still reds.
Split out of `issues/hardware/eleven-names-red-on-ci.md`, which covers eleven
names and has no exit condition for this one in particular.

**Exit condition.** The cause of the empty first controller is fixed, and
`usb_disk_index_stable` green on CI's `guest` shards. Owner: orchestrator.
**Exit condition.** The cause of the empty first controller is fixed. Owner: orchestrator.
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ opened: 2026-09-25

# A `quiesce_writers` writer's first write-and-fsync pass outlasts the job's 5 s spin-up

`quiesce_dump_holds_the_stopped` and `quiesce_stops_the_machine` both boot
`quiesce_writers`. It asks for the reset only once each of its six writers has
It asks for the reset only once each of its six writers has
finished one pass: a create, 64 KiB of writes and an fsync. If a writer is
still in its first pass after 5 s, the job prints `quiesce_writers: <n> of 6
writers reached their loop in 5s` and exits 1 without asking, so no stop
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
status: expected-red
status: open
kind: defect
opened: 2026-09-03
---
Expand All @@ -15,5 +15,6 @@ twelve 2 MiB images entered a cache whose test budget refuses at the second.

Owed: a mechanism. Nobody has one.

**Exit condition.** The cause of the missing refusal is fixed, and
`so_cache_refusals` green on CI's KVM `guest` shards. Owner: orchestrator.
**Exit condition.** The cause of the missing refusal is fixed, shown against
`so_cache_refusals` and the `so-cache-tiny` budget it arms, as both stand at
`1808fb8d`, restored and green on CI's KVM `guest` shards. Owner: orchestrator.
43 changes: 14 additions & 29 deletions kernel-loom/tests/dump_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
//! ```
#![cfg(feature = "loom")]

use kernel_loom::dump_request::{DumpRequest, Left};
use kernel_loom::dump_request::{DumpRequest, Entered, Left};
use loom::cell::UnsafeCell;
use loom::sync::Arc;

Expand All @@ -39,16 +39,10 @@ impl Machine {
if !self.request.take() {
return false;
}
self.report_until_nothing_pending();
true
}

/// `sched::dump::report_until_nothing_pending`, once the caller took the request.
fn report_until_nothing_pending(&self) {
loop {
self.report();
if !self.request.end_report() {
return;
return true;
}
}
}
Expand Down Expand Up @@ -111,27 +105,6 @@ fn one_request_is_taken_once() {
});
}

/// The stop asks while a sibling's pass may serve: filed and taken in one exchange, the request is never
/// pending for the sibling to take, and the one report is the asker's.
#[test]
fn the_asker_owns_the_report_it_asks_for() {
loom::model(|| {
let machine = Machine::new();

let sibling = {
let machine = machine.clone();
loom::thread::spawn(move || machine.serve())
};
assert!(machine.request.file_and_take(), "no report ran, and the asker did not take its own request");
machine.report_until_nothing_pending();
let sibling_took = sibling.join().unwrap();

assert!(!sibling_took, "a sibling's pass took the request the asker filed");
assert!(!machine.request.pending(), "the request outlived its report");
assert_eq!(machine.reports(), 1, "one request was reported other than once");
});
}

/// Two passes that may not serve and one that may: the request is announced at
/// most once, and by nobody once it has been taken unannounced.
#[test]
Expand Down Expand Up @@ -186,3 +159,15 @@ fn a_request_left_during_a_report_is_still_taken_by_its_end() {
assert_eq!(machine.reports(), 2);
});
}

/// Only a pass entered at depth zero takes the request. A syscall's pass — `yield_now`, `exit_current` —
/// is entered above zero and a blocking pass is inside a wait ticket, and `dump::request` asserts it runs
/// with neither under it: a report served from one of them is a kernel panic. Not an interleaving question,
/// so no model.
#[test]
fn only_a_pass_entered_at_depth_zero_takes_the_request() {
assert!(Entered::Pass { depth: 0 }.may_serve());
for entered in [Entered::Blocking, Entered::Pass { depth: 1 }, Entered::Pass { depth: 2 }] {
assert!(!entered.may_serve(), "{entered} took the request, and its report runs above a bare pass");
}
}
2 changes: 1 addition & 1 deletion kernel/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ The module header at the site owns its subsystem — read it before changing a m
- **A block-layer `BudgetExpired` is not-durable-yet and never a loss** — it is retried on a fresh budget above every lock; a flush that discards its pages on one splits a FAT mirror.
- **A decision the process table makes lives in `toyos-proclife`, never in `process.rs`** — its defects are interleavings and that crate is the only machine that can enumerate one.
- **A task holds at most one watch registration** — a standing registration across a loop must not call anything that registers again. A double registration panics only at attempt ≥ 2, so the contention depth is the coverage.
- **A console is per holder, minted at spawn** — the object *is* the line buffer; `console_line_atomicity` is the gate.
- **A console is per holder, minted at spawn** — the object *is* the line buffer.
- **`ops::close` cancels a poll only for a source its object really ends** — `Watch::cancel_polls` answers every ring's poll on that watch; `ops::close_ends_polls` is where a new object kind answers.
- **A page shared with userland is never reached through a Rust reference** — the words a protocol shares are `&AtomicU32` one at a time, everything else is a volatile copy of the whole value, and a page is laid out *before* it is mapped (`SharedMemObject::phys_before_mapping`). The kernel, `toyos-abi` and the SDK each hold one end of this rule.
- **A device a process drives reaches only the memory its grants map** — the domain is the gate, not the descriptor; a device address outside a grant is a `DMA FAULT` record and never a crash, and `userdev_dma_fault` is the registration.
Expand Down
18 changes: 0 additions & 18 deletions kernel/src/actuator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,6 @@ actuators! {
/// Hold the thread named `toyos_quiesce::LAST_THREAD` inside `SYS_NANOSLEEP`, and the shutdown until it is held there, until the stop waits on it alone: its park is then the stop's last transition.
quiesce_last_park = "quiesce-last-park";

/// The same inside `SYS_THREAD_EXIT`: its exit is then the stop's last transition.
quiesce_last_exit = "quiesce-last-exit";

/// Serve a blocked-task dump from the shutdown once its first stage has stopped the machine: the report Ctrl+Alt+D gives on a shutdown stuck in its stop.
quiesce_dump = "quiesce-dump";

/// Refuse the second directory-entry write of the file `writeback_durability` stages for the retry gate — the first is that file's own seed being made durable — as a budget expiry, so a flush fails at its metadata write with its pages already written and settled.
fat_flush_meta_refuse = "fat-flush-meta-refuse";

Expand Down Expand Up @@ -248,9 +242,6 @@ actuators! {
/// `usb_reset_records_the_phase_it_cut`.
usb_reset_under_load = "usb-reset-under-load";

/// Put the shared-object cache's byte budget within reach of the libraries a guest can build, so the shipped refusal runs at all.
so_cache_tiny = "so-cache-tiny";

/// Run the first attempt of each run `object::ops::until_answered` retries —
/// a file's `SYS_FSYNC`, a claimed partition's read, write or flush — under an
/// operation that is already over, once per file and per partition and kind.
Expand Down Expand Up @@ -326,12 +317,6 @@ actuators! {
/// Give PORTSC's PED bit the RW1CS meaning xHCI 1.2 §5.4.8 gives it.
xhci_portsc_rw1c = "xhci-portsc-rw1c";

/// Take a bound HID device's first completion away and hand back a stall.
xhci_hid_break_first = "xhci-hid-break-first";

/// The same at its fourth completion.
xhci_hid_break_late = "xhci-hid-break-late";

/// Run `parse_config` over nine crafted configuration descriptors at init.
xhci_descriptor_selftest = "xhci-descriptor-selftest";

Expand Down Expand Up @@ -462,9 +447,6 @@ actuators! {
/// Let a handle close cancel every poll on the keyboard's watch in the machine.
keyboard_close_cancels_every_console = "keyboard-close-cancels-every-console";

/// Panic inside `klogd` on its first instruction.
klogd_panic = "klogd-panic";

/// Read address zero inside `klogd` on its first instruction.
klogd_fault = "klogd-fault";

Expand Down
1 change: 0 additions & 1 deletion kernel/src/drivers/xhci/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -784,7 +784,6 @@ fn bind_hid(
prev_report: [0; 8],
broke_with: None,
failures: 0,
completions: 0,
};

dev.requeue(&ctrl.db_base);
Expand Down
31 changes: 0 additions & 31 deletions kernel/src/drivers/xhci/hid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,6 @@ pub struct HidDevice {
pub broke_with: Option<u32>,
/// Consecutive failures; a delivered report clears it — see [`super::MAX_HID_FAILURES`].
pub failures: u8,
/// Completions this endpoint has produced.
/// Counted unconditionally so the `xhci-hid-break-*` actuators aren't a second code path.
#[cfg_attr(not(feature = "boot-actuators"), allow(dead_code))]
pub completions: u32,
}

impl HidDevice {
Expand Down Expand Up @@ -106,30 +102,3 @@ impl HidDevice {
db_base.write_u32(self.slot_id as u64 * 4, self.int_ep_dci as u32);
}
}

/// Takes one completion away from the device that earned it and hands the driver a stall in its place.
// QEMU's usb-hid has no path to USB_RET_STALL for an interrupt IN token, so nothing on the host side can stage this.
// Replaces the completion code and the delivered report, not the TRB/ring/transfer-event/output-context chain, so a dispatched "success" can only be real.
#[cfg(feature = "boot-actuators")]
impl HidDevice {
// The first completion is a never-delivered endpoint; the fourth is one that was working and stopped — different driver states, not degrees of one.
fn break_at() -> Option<u32> {
if crate::actuator::xhci_hid_break_first() {
Some(1)
} else if crate::actuator::xhci_hid_break_late() {
Some(4)
} else {
None
}
}

pub fn stage_break(&mut self, code: u32) -> u32 {
self.completions += 1;
if Self::break_at() != Some(self.completions) {
return code;
}
// Zeroing leaves the slot as a stalled endpoint would have; runs before requeue, so nothing else touches the buffer.
self.report.subview(0, self.report_size as usize).zero();
super::CC_STALL
}
}
Loading
Loading