Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
13ed7b4
usbd drives every xHCI controller; the kernel drives no USB
Japabu Oct 21, 2026
bab43d8
issues: the crypto table's hang is QEMU's console drop
Japabu Oct 21, 2026
706396d
tests: the boot's log lands on the USB stick it booted from
Japabu Oct 21, 2026
9bfc7f5
issues: a disk plugged in after settling is routed from its offer alone
Japabu Oct 21, 2026
d4459fc
usbd: a failed CLEAR_FEATURE(HALT) takes back the transfer it moved past
Japabu Oct 21, 2026
f4c34d8
tests: a USB key reaches the console through usbd
Japabu Oct 21, 2026
9bdc96a
The machine's stop hands every USB device back: usbd resets its ports…
Japabu Oct 21, 2026
e185b68
tests: stick_survives_a_reset_under_load, and the stop asked for unde…
Japabu Oct 21, 2026
ecbb0ea
tests: the tone and mask_windows rows under a stick write
Japabu Oct 21, 2026
f40887c
issues: the records #971 made false, made true
Japabu Oct 21, 2026
aa0d01b
tests: the stick load judge reads its last line from the end
Japabu Oct 21, 2026
45b2475
usbd: the grant is sized by grant_bytes alone
Japabu Oct 21, 2026
ee38361
Merge wt/toyos-usbd (origin/main and #962's round 2) into wt/toyos-us…
Japabu Oct 21, 2026
9b7fa89
tests: no metal row stages a machine that cannot remap
Japabu Oct 21, 2026
29bf29d
usbd settles only once every port's attach window has passed
Japabu Oct 21, 2026
5e4bafb
usbd ends by name; a driver that ends before settling holds no listing
Japabu Oct 21, 2026
cc789d1
pcidev: a claim's function masters nothing until its first grant, fro…
Japabu Oct 21, 2026
2c33314
Revert "pcidev: a claim's function masters nothing until its first gr…
Japabu Oct 21, 2026
e729c3e
usbd: a controller firmware will not release is refused by name and l…
Japabu Oct 21, 2026
fa0f434
tests: the stick loads write the stick, and a deadline-ended boot is …
Japabu Oct 21, 2026
d36af38
tests: stick_load writes at a rate its boot's log can keep whole
Japabu Oct 21, 2026
56bd104
toyos-metal: a deadline-ended boot's log volume may hold what a stop …
Japabu Oct 21, 2026
82ce232
Merge origin/main (#962 and #974) into wt/toyos-usbd-2
Japabu Oct 21, 2026
8471eca
usbd: states its address limit, starts its mastering, and takes a con…
Japabu Oct 21, 2026
8a93782
Merge origin/main (#973) into wt/toyos-usbd-2
Japabu Oct 21, 2026
b07a9b1
usbd: a controller firmware keeps is refused again, per §4.22.1
Japabu Oct 21, 2026
9f9cad5
tests: the stop judge reads each line through its head
Japabu Oct 21, 2026
4ee2dca
Merge origin/main (#975, #976) into usbd's PR
Japabu Oct 21, 2026
0e0c37e
usbd: a TD is put whole before the Link, its first TRB last, its TD S…
Japabu Oct 21, 2026
5461e94
tests: the AMD testcases boot's rows judge their own faults
Japabu Oct 21, 2026
421c2b3
usbd: an early Link marks what it skips; a TD is at most half a ring;…
Japabu Oct 21, 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
29 changes: 14 additions & 15 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 2 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ members = [
"kernel/phys",
"kernel/sim",
"kernel/smmu",
"kernel/usbhid",
"lib/acpi",
"lib/acpiserver-api",
"lib/bcachefs",
Expand Down Expand Up @@ -73,13 +72,12 @@ members = [
"lib/tmpdir",
"lib/trace",
"lib/tsc",
"lib/usbhid",
"lib/update",
"lib/userbound",
"lib/vdagent",
"lib/virtio",
"lib/wallclock",
"lib/xhci",
"lib/xhci/sim",
"loader",
"sdk/abi",
"sdk/filepicker",
Expand Down Expand Up @@ -140,6 +138,7 @@ members = [
"system/update",
"system/update/copies",
"system/usbd",
"system/usbd/xhci",
"system/vdagent",
"system/vdagent/bridge",
"tests/guest/kernelprobe",
Expand Down Expand Up @@ -296,9 +295,6 @@ toyos-userbound = { path = "lib/userbound" }
# The scheduler's quantum and the windows' hold, so the harness judges a boot
# against the constants the kernel schedules by.
kernel = { path = "kernel" }
# The Bulk-Only phases, so the harness judging a wedge reads the word
# `toyos_xhci::bot::Phase` declares instead of spelling it a second time.
toyos-xhci = { path = "lib/xhci" }
# The ACPI server's count line, so the judge reads the words the server writes
# and the job that waits for them matches.
acpiserver-api = { path = "lib/acpiserver-api" }
Expand Down
16 changes: 0 additions & 16 deletions build/bootlog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,18 +67,6 @@ pub const SEAL_PC: &str = " pc=";
/// that names the wrong instruction.
pub const WEDGE_SPIN: &str = "kernel::deadline::this_cpu+";

/// What the `usb-reset-under-load` arm says once it is streaming, and the three
/// ways it says it is not, in `kernel/src/usb_gate.rs`.
///
/// **The witness that the reset landed on a busy bus.** A boot whose page
/// carries the first line and none of the other three is one whose reset found
/// a controller still moving bytes; any of the other three is the idle case
/// under this arm's name.
pub const USB_LOAD_RUNNING: &str = "usb-load: sweeping disk 0";
pub const USB_LOAD_REFUSED: &str = "usb-load: refused";
pub const USB_LOAD_STOPPED: &str = "usb-load: the disk stopped answering";
pub const USB_LOAD_SWEPT: &str = "usb-load: the sweep reached the end of the disk";

/// What one CPU's own NMI writes into the black box when that CPU has taken no
/// interrupt for its bound, in `kernel/src/hardlockup/mod.rs`.
///
Expand Down Expand Up @@ -703,10 +691,6 @@ mod tests {
"kernel/src/deadline.rs",
format!("fn {}() -> !", WEDGE_SPIN.trim_end_matches('+').rsplit("::").next().expect("a path")),
),
("kernel/src/usb_gate.rs", format!("LOAD_RUNNING: &str = \"{USB_LOAD_RUNNING}\"")),
("kernel/src/usb_gate.rs", format!("LOAD_REFUSED: &str = \"{USB_LOAD_REFUSED}\"")),
("kernel/src/usb_gate.rs", format!("LOAD_STOPPED: &str = \"{USB_LOAD_STOPPED}\"")),
("kernel/src/usb_gate.rs", format!("LOAD_SWEPT: &str = \"{USB_LOAD_SWEPT}\"")),
("kernel/src/hardlockup/mod.rs", format!("LOCKED_UP: &str = \"{LOCKED_UP}\"")),
("kernel/src/hardlockup/mod.rs", format!("\"{LOCKUP_PC}{{}}\", At(pc)")),
("kernel/src/sync.rs", "pub fn lock(&self) -> LockGuard<'_, T>".to_string()),
Expand Down
53 changes: 30 additions & 23 deletions build/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -615,22 +615,28 @@ const SUPERVISOR_PROGRAM: &str = "supervisor";
/// row receives it.
const SUPERVISOR_SERVED: &[&str] = &[toyos_swap::PORT, "power", toyos_manifest::grants::PORT];

/// The ports that one row alone may receive, each with that row: the swap
/// port, whose holder replaces any service's binary, the grants port, whose
/// holder writes which folder of the home a package sees, the prompt port,
/// whose holder draws above every window and has every key while it does, the
/// input port, whose holder types into the focused window, the clipboard
/// port, whose holder is sent every copy made in the session, and the display
/// port, whose holder sets the mode and the scale every window is drawn at.
const HELD_ALONE: &[(&str, &str)] = &[
(toyos_swap::PORT, toyos_swap::HOLDER),
(toyos_manifest::grants::PORT, toyos_manifest::grants::HOLDER),
(toyos_manifest::consent::PROMPT, toyos_manifest::consent::SERVER),
(toyos_input::wire::PORT, toyos_input::wire::HOLDER),
(toyos_manifest::clipboard::PORT, toyos_manifest::clipboard::HOLDER),
(toyos_manifest::display::PORT, toyos_manifest::display::HOLDER),
/// The ports that named rows alone may receive, each with those rows: the
/// swap port, whose holder replaces any service's binary, the grants port,
/// whose holder writes which folder of the home a package sees, the prompt
/// port, whose holder draws above every window and has every key while it
/// does, the input port, whose holders type into the focused window, the
/// clipboard port, whose holder is sent every copy made in the session, and
/// the display port, whose holder sets the mode and the scale every window is
/// drawn at.
const HELD_ALONE: &[(&str, &[&str])] = &[
(toyos_swap::PORT, &[toyos_swap::HOLDER]),
(toyos_manifest::grants::PORT, &[toyos_manifest::grants::HOLDER]),
(toyos_manifest::consent::PROMPT, &[toyos_manifest::consent::SERVER]),
(toyos_input::wire::PORT, toyos_input::wire::HOLDERS),
(toyos_manifest::clipboard::PORT, &[toyos_manifest::clipboard::HOLDER]),
(toyos_manifest::display::PORT, &[toyos_manifest::display::HOLDER]),
];

/// Rows, as a refusal names them.
fn named(rows: &[&str]) -> String {
rows.iter().map(|r| format!("`{r}`")).collect::<Vec<_>>().join(" and ")
}

/// Who may hold the authorities that change what the machine runs or what a
/// package sees: each of [`HELD_ALONE`]'s ports, its holder and nothing else
/// — no other `[programs]` row and never `[apps]` — the idle slot,
Expand All @@ -645,9 +651,9 @@ const HELD_ALONE: &[(&str, &str)] = &[
fn held_by_their_holders_alone(config: &SystemConfig) -> Result<(), String> {
use toyos_manifest::consent;
for (name, program) in &config.programs {
for (port, holder) in HELD_ALONE {
if name != holder && program.receives.iter().any(|r| r == port) {
return Err(format!("`{name}` receives `{port}`, which only `{holder}` may hold"));
for (port, holders) in HELD_ALONE {
if !holders.contains(&name.as_str()) && program.receives.iter().any(|r| r == port) {
return Err(format!("`{name}` receives `{port}`, which only {} may hold", named(holders)));
}
}
if program.receives.iter().any(|r| r == consent::PORT) {
Expand All @@ -665,9 +671,9 @@ fn held_by_their_holders_alone(config: &SystemConfig) -> Result<(), String> {
return Err(format!("`{name}` asks for `slots` and is in `[boot] start`, and the slots are granted to a launch alone"));
}
}
for (port, holder) in HELD_ALONE {
for (port, holders) in HELD_ALONE {
if config.apps.receives.iter().any(|r| r == port) {
return Err(format!("`[apps] receives` names `{port}`, which only `{holder}` may hold"));
return Err(format!("`[apps] receives` names `{port}`, which only {} may hold", named(holders)));
}
}
if config.apps.receives.iter().any(|r| r == consent::PORT) {
Expand Down Expand Up @@ -3225,14 +3231,15 @@ mod tests {
}
}

/// The input port reaches ps2server alone, and never `[apps]`: a
/// connector types into the focused window.
/// The input port reaches ps2server and usbd alone, and never `[apps]`:
/// a connector types into the focused window.
#[test]
fn only_ps2server_holds_the_input_port() {
fn only_the_keyboard_drivers_hold_the_input_port() {
let gate = |text: &str| held_by_their_holders_alone(&toml::from_str(text).unwrap());
assert_eq!(gate("[programs.ps2server]\nreceives = [\"input\"]\n[programs.compositor]\nserves = [\"input\"]\n"), Ok(()));
assert_eq!(gate("[programs.usbd]\nreceives = [\"input\", \"disk\"]\n[programs.compositor]\nserves = [\"input\"]\n"), Ok(()));
for (text, said) in [
("[programs.paint]\nreceives = [\"input\"]\n", "`paint` receives `input`, which only `ps2server` may hold"),
("[programs.paint]\nreceives = [\"input\"]\n", "`paint` receives `input`, which only `ps2server` and `usbd` may hold"),
("[apps]\nreceives = [\"input\"]\n", "`[apps] receives` names `input`"),
] {
match gate(text) {
Expand Down
3 changes: 0 additions & 3 deletions build/ci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -497,9 +497,6 @@ pub(crate) const CONTROLS: &[Control] = &[
red(KERNEL_LOOM, "lock-acquire-off", Some("ticket_lock"), &[
Fails("try_lock_observes_the_previous_owners_writes"),
]),
red(KERNEL_LOOM, "owed-fence-off", Some("owed_lock"), &[
Fails("a_turned_away_cpu_holds_the_lock_or_is_answered"),
]),
red(KERNEL_LOOM, "seqlock-writer-fence-off", Some("panic_console_publish"), &[
Fails("a_snapshot_is_one_publication_whole"),
]),
Expand Down
8 changes: 1 addition & 7 deletions build/clippy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,7 @@ fn control_features() -> Vec<String> {
/// set never sees; the third is the one `--kernel-param` builds, `boot-actuators`
/// without `test-actuators`, whose dead code neither of the others can see.
/// `undocumented_unsafe_blocks` is adopted per area as each area's
/// justifications land. `toyos-xhci` has a shape of its own because the
/// workspace run builds it only with `toyos-xhci-sim`'s `flaws`, never as the
/// kernel does. `toyos-pci-claim` has one because `$GUESTS` excludes it and
/// justifications land. `toyos-pci-claim` has one because `$GUESTS` excludes it and
/// only netstack's host tests build its `host-grant`. `kernel-loom` has one
/// because `victim-retires-mid-probe`'s test arm excludes `no-preempt-guard`,
/// which `$CONTROLS` turns on beside it. `toyos-crypto` has three because the
Expand Down Expand Up @@ -150,10 +148,6 @@ const SHAPES: &[Shape] = &[
before: &["-p", "bootloader", "--target", Arch::Aarch64.loader()],
after: &["$ADOPTED", "-W", "clippy::undocumented_unsafe_blocks", "-D", "warnings"],
},
Shape {
before: &["-p", "toyos-xhci", "--all-targets"],
after: &["$ADOPTED", "-D", "warnings"],
},
Shape {
before: &["-p", CRYPTO, "--all-targets", "--features", CRYPTO_DISPATCHED],
after: &["$ADOPTED", "-D", "warnings"],
Expand Down
8 changes: 4 additions & 4 deletions build/hostws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ mod tests {
#[test]
fn the_gate_refuses_a_crate_that_joined_neither_list() {
let members: BTreeSet<String> =
[".", "lib/elf", "lib/xhci", "lib/xhci/sim"].iter().map(|s| s.to_string()).collect();
[".", "lib/elf", "system/usbd", "system/usbd/xhci"].iter().map(|s| s.to_string()).collect();

let all_declared: BTreeSet<String> = members.clone();
assert!(unclaimed(&members, &all_declared).is_empty());
Expand All @@ -324,8 +324,8 @@ mod tests {

// And a nested one.
let with_a_nested_newcomer: BTreeSet<String> =
members.union(&["lib/xhci/newthing".to_string()].into()).cloned().collect();
assert_eq!(unclaimed(&members, &with_a_nested_newcomer), ["lib/xhci/newthing"]);
members.union(&["system/usbd/newthing".to_string()].into()).cloned().collect();
assert_eq!(unclaimed(&members, &with_a_nested_newcomer), ["system/usbd/newthing"]);
}

/// The walk has teeth only if it can find anything: it must reach the real
Expand All @@ -338,7 +338,7 @@ mod tests {
assert!(found.contains("."), "the walk did not find the root package");
assert!(found.contains("lib/elf"), "the walk did not find lib/elf: {found:?}");
assert!(
found.contains("lib/xhci/sim"),
found.contains("system/usbd/xhci"),
"the walk did not descend past the first level, so a nested member could \
go missing without this gate noticing: {found:?}"
);
Expand Down
23 changes: 23 additions & 0 deletions build/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,29 @@ pub fn read_file_on(file: &mut std::fs::File, guid: [u8; 16], name: &str) -> Res
Ok(bytes)
}

/// Every file in the root of the image's log volume (its one Microsoft basic
/// data partition), by name, read with the driver the kernel mounts it with.
pub fn log_volume_files(file: &mut std::fs::File) -> Result<Vec<(String, Vec<u8>)>, String> {
let part = only_partition(&mut FileSectors(file), toyos_gpt::Guid::MICROSOFT_BASIC)?;
let (start, len) = (part.first_lba() * u64::from(LBA), part.lba_count().get() * u64::from(LBA));
let len = usize::try_from(len).map_err(|_| format!("a {len}-byte volume"))?;
let mut volume = vec![0u8; len];
file.seek(SeekFrom::Start(start))
.and_then(|_| file.read_exact(&mut volume))
.map_err(|e| format!("reading the log volume at byte {start}: {e}"))?;
let mut fs = Fat32::mount(VolumeIo(&mut volume)).map_err(|e| format!("the log volume does not mount: {e}"))?;
let entries = fs.read_dir("/", usize::MAX).map_err(|e| format!("listing the log volume: {e}"))?;
let mut files = Vec::new();
for entry in entries.into_iter().filter(|e| !e.is_dir) {
let path = format!("/{}", entry.name);
let mut found = fs.open(&path).map_err(|e| format!("opening {path}: {e}"))?;
let mut bytes = vec![0u8; usize::try_from(found.len()).unwrap_or(usize::MAX)];
fs.read(&mut found, 0, &mut bytes).map_err(|e| format!("reading {path}: {e}"))?;
files.push((entry.name, bytes));
}
Ok(files)
}

/// Why a scan's `out[0]` and `matched` count did not pick out exactly one
/// partition, once the table itself was readable.
pub enum OnePartitionError {
Expand Down
6 changes: 1 addition & 5 deletions build/sourcegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ const NMI_SILENT: &[&str] =
const BUS_MASTER_SITES: &[(&str, usize)] = &[
("kernel/src/drivers/pci.rs", 1),
("kernel/src/drivers/virtio.rs", 1),
("kernel/src/drivers/xhci/wait/boot.rs", 1),
("kernel/src/arch/x86_64/amdvi/mod.rs", 1),
];

Expand Down Expand Up @@ -307,10 +306,7 @@ const HOSTNAME: &str = "a hostname carrying a personal name";
/// Every value of a refused shape the tree keeps, by file, each identifying
/// nobody. A value a shape passes says the same without a row here: a locally
/// administered MAC, an RFC 5737 or RFC 3849 address.
const IDENTIFIES_NOBODY: &[(&str, &str)] = &[
// Made up, in the kernel's own record of a disk that came back.
("tests/checks/usb.rs", "FEDCBA98765432FEDCBA"),
];
const IDENTIFIES_NOBODY: &[(&str, &str)] = &[];

/// Every maximal run of `line`'s bytes that `of` takes, and where it starts.
/// `of` takes ASCII alone, so a run begins and ends on a character.
Expand Down
9 changes: 8 additions & 1 deletion images/console.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ provides = ["surface"]
serves = ["input"]
receives = ["log"]
starts = ["shell"]
devices = ["framebuffer", "keyboard", "mouse"]
devices = ["framebuffer"]

[programs.shell]
receives = ["surface"]
Expand Down Expand Up @@ -107,6 +107,13 @@ serves = ["block", "disk"]
drives = ["pci:01:08:02/1b36:0010"]
receives = ["disk"]

# The USB driver: one process per xHCI controller, its sticks offered to the
# block service as disks it serves, its keyboards and pointers reported to the
# owner of input.
[programs.usbd]
drives = ["pci:0c:03:30"]
receives = ["disk", "input"]

# The file servers: DATA, the log and the boot volume, one process
# each, serving every program the directories of its role. Started again when
# one ends, on the same ports.
Expand Down
Loading