diff --git a/Cargo.lock b/Cargo.lock index 996b42da242..284d9e3eac5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5765,7 +5765,6 @@ dependencies = [ "toyos-fat32", "toyos-fat32-check", "toyos-gpt", - "toyos-i219", "toyos-keymap", "toyos-logstream", "toyos-manifest", diff --git a/Cargo.toml b/Cargo.toml index bd9228ec2c6..119a669c1c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -222,11 +222,6 @@ gix = { version = "0.88", default-features = false, features = ["status", "sha1" # `USER_TOP`, so the harness judges a held `rsp` against the bound the kernel # checks every user pointer against and not against a second copy of it. toyos-userbound = { path = "toyos-userbound" } -# The I219 driver's own words and its PHY probe's exit-code table, so the -# harness judging netstack's `exit:` record off the T14 and netstack writing it read one -# declaration. Pure and `no_std`, so it compiles for the host as it does for -# the guest. -toyos-i219 = { path = "toyos-i219" } # The scheduler's quantum and the windows' hold, so the harness judges a boot # against the constants the kernel schedules by. kernel = { path = "kernel" } diff --git a/issues/a-cable-refusal-hides-the-stick-only-row-of-the-talking-boot.md b/issues/a-cable-refusal-hides-the-stick-only-row-of-the-talking-boot.md new file mode 100644 index 00000000000..36ef9a402c6 --- /dev/null +++ b/issues/a-cable-refusal-hides-the-stick-only-row-of-the-talking-boot.md @@ -0,0 +1,50 @@ +--- +status: open +kind: tooling +opened: 2026-10-08 +--- + +# A cable refusal hides the stick-only row of the talking boot + +`toyos-metal` refuses a talking boot whose host heard nothing over the cable, +and the harness takes a refused boot's readback as the refusal +(`read_readback`, `tests/common/metal.rs`): every row riding the boot is red +with the refusal's text, whatever it reads. `lantalkcase` carries three rows. +`lan_talk` reads the conversation and `lan_dhcp_lease` holds the lease to the +address the host reached, so both need the cable. `lan_message_delivery` reads +only the kernel's records on the stick, and is red with them. + +## Measured + +The T14, `lantalkcase`, with the bench's wired path down +(`issues/lan-talk-is-unread-on-the-t14-since-the-claims-binding-landed.md`), at +`main`'s `6f87cdb9c` and at `c84f5fafe` the same day. Both `verdict.txt`: + +``` +refused +the boot did not say over its own cable what a talking boot owes: + the stream never opened +``` + +At `c84f5fafe` the judge printed the same line for all three rows: + +``` +FAIL lan_message_delivery: toyos-metal refused lantalkcase: the boot did not say over its own cable what a talking boot owes: +``` + +and the stick it did not read carries what that row asks for: + +``` +pcidev: PCI 00:1f.6 [8086:15fc] handed over on slot 0, vector 0x28 +pcidev: slot 0 took its first message on vector 0x28 +``` + +## Owner + +The harness's author. + +## Exit condition + +On a `lantalkcase` readback refused only for what the host heard, +`lan_message_delivery` is judged on the stick's records, shown by a +`toyos-checks` fixture of such a readback. diff --git a/issues/a-t14-boot-that-outlogs-its-retention-loses-its-middle-and-the-rows-whose-lines-sat-there.md b/issues/a-t14-boot-that-outlogs-its-retention-loses-its-middle-and-the-rows-whose-lines-sat-there.md new file mode 100644 index 00000000000..286396216e3 --- /dev/null +++ b/issues/a-t14-boot-that-outlogs-its-retention-loses-its-middle-and-the-rows-whose-lines-sat-there.md @@ -0,0 +1,55 @@ +--- +status: open +kind: tooling +opened: 2026-10-08 +--- + +# A T14 boot that outlogs its retention loses its middle, and the rows whose lines sat there + +`logkeeper` keeps sixteen files of a megabyte each (`MAX_LOG_FILES`, +`userland/logkeeper/src/store.rs`) and deletes the writing boot's own middle +past that, by design: one boot's flood costs its own middle and nobody's +start. A metal judge reads what came back, so a line a flooding boot wrote in +its middle is a line the judge reports missing, and nothing in the harness +says the readback has a hole. + +`testcases` is such a boot: `test_rs_counters_metal` dumps every CPU's +counters and the boot writes about forty files. + +## Measured + +The T14 at `9ded9075c`, where `acpi_server_events` rode `testcases` with +`test_rs_acpi_hold` its last job. On the log's clock the readback's last +record before the hole is at 34.875 s and its first after it at 47.220 s. The +boot went from part `_0026` to `_0041`; what came back is its first part and +its fifteen newest, and fifteen deletion lines survive in them, the earlier +ones having gone with the parts that held them. The first that survives, at +47.238 s: + +``` +logkeeper: /log holds more than 16 logs, so /log/2026-10-08-140646_0012.log was deleted +``` + +`acpiserver` armed at 12.851 s and writes its count thirty seconds on, at +about 42.9 s, inside the hole. The row red: +`the server logged 1 first sighting(s) and None for counts`. The server was +serving throughout: cpu0's census reads `userdev=48` at the boot's end, as it +does on the quiet `testcases-hold` boot of `779330742` the same day, whose +count line is there at 42.579 s (`28 SCIs; embedded controller queries taken: +0x4f x14`). `testcases`' readback of 2026-10-07 has the same hole: no record +in seconds 36 to 47. + +So `acpi_server_events` and `acpi_tables_loaded` keep `testcases-hold`, a boot +whose log is whole. + +## Owner + +The harness's author, with the work that makes the T14's boots fewer and +fuller: a boot that carries every shipping-kernel member logs more than +`testcases` does. + +## Exit condition + +No boot of the metal profile writes past `logkeeper`'s retention, or a +readback whose own boot deleted a part of its log reds by name before any row +is judged on it; and `acpi_server_events` passes on `testcases` on the T14. diff --git a/issues/lan-hold-holds-a-boot-open-for-a-flat-twenty-seconds.md b/issues/lan-hold-holds-a-boot-open-for-a-flat-twenty-seconds.md new file mode 100644 index 00000000000..55b1153b1be --- /dev/null +++ b/issues/lan-hold-holds-a-boot-open-for-a-flat-twenty-seconds.md @@ -0,0 +1,19 @@ +--- +status: open +kind: tooling +opened: 2026-10-03 +--- + +# `lan_hold` holds a boot open for a flat twenty seconds + +`tests/toyos-rust-tests/src/bin/lan_hold.rs` sleeps `toyos_tco::LEASE_BOUND_MS` +and exits. The T14 boot `testcases-deaf` runs it as its one job, so the boot +does not end before the `dump-deaf-cpu` actuator has armed and dumped. It is a +fixed delay standing in for an event the job does not wait on, which root +`CLAUDE.md` forbids in a test. + +## Exit condition + +The boot's one job holds no sleep: it waits on the event it is held open for, +bounded by a timeout that panics by name, and `dump_nmi_probe` passes on a T14 +run of that head. diff --git a/issues/lan-hold-holds-two-boots-open-for-a-flat-twenty-seconds.md b/issues/lan-hold-holds-two-boots-open-for-a-flat-twenty-seconds.md deleted file mode 100644 index ba34e6b9664..00000000000 --- a/issues/lan-hold-holds-two-boots-open-for-a-flat-twenty-seconds.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -status: open -kind: tooling -opened: 2026-10-03 ---- - -# `lan_hold` holds two boots open for a flat twenty seconds - -`tests/toyos-rust-tests/src/bin/lan_hold.rs` sleeps `toyos_tco::LEASE_BOUND_MS` -and exits. Two T14 boots run it as their one job: `lanleasecase`, so the boot -does not end before netd's `--exit-with-lease` has, and `testcases-deaf`, so -it does not end before the `dump-deaf-cpu` actuator has armed and dumped. Each -is a fixed delay standing in for an event the job does not wait on, which root -`CLAUDE.md` forbids in a test. - -On the T14 run of #638's head netd exited at 19.212 s of the `lanleasecase` -boot and init stopped the machine at 21.202 s. - -## Exit condition - -Neither boot's one job holds a sleep: each waits on the event it is held open -for, bounded by a timeout that panics by name, and `lan_lease_report` and -`dump_nmi_probe` pass on a T14 run of that head. diff --git a/issues/netstacks-lease-probe-answers-a-question-its-lines-already-answer.md b/issues/netstacks-lease-probe-answers-a-question-its-lines-already-answer.md deleted file mode 100644 index ae672c12040..00000000000 --- a/issues/netstacks-lease-probe-answers-a-question-its-lines-already-answer.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -status: open -kind: tooling -opened: 2026-09-16 ---- - -# netd's lease probe answers a question its own lines already answer - -netd's `--exit-with-lease` brings the card up and serves exactly as the -shipping boot does for a bounded window, leaves `/log/lease.txt` on the log -volume one durable line at a time (the bring-up's own words, every link change, -the lease, what the driver and the MAC counted each way), and ends with -`toyos_i219::lease::Verdict`'s code. It existed because every line netd printed -about the same things was a console write, and on the T14 a console write -reached no file. Every program's lines reach `/log` through its log ring now, -and the talking boot's `lan_dhcp_lease` judge reads the lease off netd's own -lines. What still arms the probe is `tests/lanleasecase`, the `lan_lease_report` -metal row: a T14 flash of its own, because netd ends inside it. - -## Owner - -The I219 bring-up's author, and after it the network track. - -## What would close it - -The probe deleted: `tests/lanleasecase/system.toml`, its row and its gate in -`src/build.rs`, the `lan_lease_report` metal row in `tests/toyos.rs` with -`LANLEASECASE`, `lan::leased_on_metal`, `Readback::log_volume_file` and -`tests/common/volumes.rs`, netd's `--exit-with-lease` and -`userland/netd/src/report.rs`, `toyos-i219/src/lease.rs`'s report lines, and -`toyos_i219::phy::Outcome`, whose tests then assert the refusal itself. - -An earlier form of this arm, `--exit-with-phy-outcome` on `d409139f^:tests/lanphycase`, -ended right after the bring-up with the PHY's outcome; its codes are still -`Verdict::NotLeased`'s, so a code read off one of its boots means what -`toyos_i219::phy::Outcome` says. diff --git a/issues/the-guest-suite-runs-only-what-no-cheaper-tier-reaches.md b/issues/the-guest-suite-runs-only-what-no-cheaper-tier-reaches.md index 78b862c2bfe..473cf52c2ab 100644 --- a/issues/the-guest-suite-runs-only-what-no-cheaper-tier-reaches.md +++ b/issues/the-guest-suite-runs-only-what-no-cheaper-tier-reaches.md @@ -225,7 +225,7 @@ Host: `toyos-net-tcp`, `toyos-dns`, `toyos-mdns`, `toyos-swap`, `toyos-inspect`, program are refused a swap. - host `sshd_key_auth`: sshd refuses a key not authorized. - host `lan_lease_report`: a link that goes down and comes back after a lease neither gives the - lease up nor starts the client over. The T14 cannot flap its cable, and its row reads only a + lease up nor starts the client over. The T14 cannot flap its cable, and `lan_dhcp_lease` reads only a lease from the bench's router. Exit: netd's link-up decision (its main loop and `dhcp::restart`) lifted into a function a netd `#[test]` drives with a lease held, red when the `!dhcp.leased()` guard goes. diff --git a/issues/the-t14-answers-only-through-a-usb-stick.md b/issues/the-t14-answers-only-through-a-usb-stick.md index 34ce1d21b36..66ff0670266 100644 --- a/issues/the-t14-answers-only-through-a-usb-stick.md +++ b/issues/the-t14-answers-only-through-a-usb-stick.md @@ -9,9 +9,9 @@ opened: 2026-09-07 Every boot of the bench is flashed to a stick under Ubuntu and judged by what is read off it after a reboot into Ubuntu. The laptop is on a cable on the same LAN as the development Mac and its NIC is the onboard Intel I219 at `00:1f.6`, -`8086:15fc`, which netd claims on the two LAN boots (`tests/lanleasecase`, -`tests/lantalkcase`); on the second the Mac reads the boot's log, runs a -command and hands the machine back over the cable. The track is to make that +`8086:15fc`, which netd claims on the LAN boot (`tests/lantalkcase`), where +the Mac reads the boot's log, runs a command and hands the machine back over +the cable. The track is to make that cable the answer path. What is left is the harness running userland tests over ssh through a russh diff --git a/issues/the-t14-stopped-answering-ssh-between-two-lan-boots.md b/issues/the-t14-stopped-answering-ssh-between-two-lan-boots.md index 2e9ce33926f..2ad1d5333bd 100644 --- a/issues/the-t14-stopped-answering-ssh-between-two-lan-boots.md +++ b/issues/the-t14-stopped-answering-ssh-between-two-lan-boots.md @@ -33,10 +33,10 @@ toyos-metal: the swap did not put the new binary in service: ## Exit condition -`lan_lease_report` and `lan_swap` are owned by this file until it closes. -Three consecutive T14 metal runs each reach both judges, `lanleasecase` and -`lanswapcase`, without an `ssh` timeout before either flash; then this file is -deleted. A red on a judge in any of the three is filed as its own issue file -naming that test and its cause before this one closes. +`lan_swap` is owned by this file until it closes. Three consecutive T14 metal +runs each reach `lanswapcase`'s judge without an `ssh` timeout before its +flash; then this file is deleted. A red on that judge in any of the three is +filed as its own issue file naming the test and its cause before this one +closes. -`lan_swap` and its boot `lanswapcase` are deleted; `issues/a-connect-between-two-accepts-is-reset.md` records the commit that restores them, and this exit's `lanswapcase` half waits on that restore. +`lan_swap` and its boot `lanswapcase` are deleted; `issues/a-connect-between-two-accepts-is-reset.md` records the commit that restores them, and this exit waits on that restore. diff --git a/issues/where-everything-lives.md b/issues/where-everything-lives.md index e784cc39861..39e9b3edbde 100644 --- a/issues/where-everything-lives.md +++ b/issues/where-everything-lives.md @@ -112,11 +112,3 @@ until that lands nothing verifies it. `sys-locale` answers on ToyOS. - rustls-native-certs reads a CA bundle under `/system/etc`, once something needs native roots. **Exit**: the first native-roots consumer runs. - -## Not moved - -`/log/lease.txt` stays on `/log`. It is not netd's lease: it is the -`--exit-with-lease` bench report the metal loop reads off the stick's FAT log -volume, as it reads `/log/metal-*.bin`, and the DATA volume is not readable -there. It goes with -`issues/netstacks-lease-probe-answers-a-question-its-lines-already-answer.md`. diff --git a/src/build.rs b/src/build.rs index 8c8c90eba7e..9b8b73c365b 100644 --- a/src/build.rs +++ b/src/build.rs @@ -3097,7 +3097,6 @@ mod tests { "console/system.toml", "tests/acpicase/system.toml", "tests/jobcase/system.toml", - "tests/lanleasecase/system.toml", "tests/lantalkcase/system.toml", "tests/latencycase/system.toml", "tests/logstallcase/system.toml", @@ -3285,128 +3284,6 @@ mod tests { assert!(one_claimant_per_device(&bad).is_err()); } - /// netstack's actuator that only its Intel driver answers, spelled here and - /// held to netstack's own declaration by - /// [`netstack_declares_the_flag_this_gate_spells`]. - const EXIT_WITH_LEASE: &str = "--exit-with-lease"; - - /// netstack's main module, which is where both halves of this gate's spelling - /// live: nothing links the two crates, so the build system reads the source. - fn netstack_source() -> (std::path::PathBuf, String) { - let at = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("userland/netstack/src/main.rs"); - let text = std::fs::read_to_string(&at).expect("netstack's main module"); - (at, text) - } - - /// Nothing links the two crates: netstack is a userland binary and this is the - /// build system, so the flags both ends spell are held to netstack's own - /// declarations by reading its source. - #[test] - fn netstack_declares_the_flag_this_gate_spells() { - let (at, source) = netstack_source(); - assert!( - crate::bootlog::declares(&source, &format!("\"{EXIT_WITH_LEASE}\"")), - "{} declares no constant equal to \"{EXIT_WITH_LEASE}\"", - at.display() - ); - } - - /// The four hex digits after `key` on this line. - fn hex_after(line: &str, key: &str) -> Option { - let at = line.find(key)? + key.len(); - let digits: String = line[at..].chars().take_while(|c| c.is_ascii_hexdigit()).collect(); - (digits.len() == 4).then_some(digits) - } - - /// The device entries netstack opens with the driver that has §10.2.4.4's `ICS`, - /// read out of netstack's own `CARDS` rather than guessed from a vendor id: each - /// row spells an id and the constructor that takes it on one line. - /// - /// **The scan reaches that one spelling and no other**, so every - /// `Card::intel` row it saw has to have yielded an id — a table written - /// another way reds here instead of narrowing this gate to nothing. - fn netstack_intel_cards(source: &str) -> Vec { - let mut cards = Vec::new(); - let mut rows = 0; - for line in source.lines() { - if !line.contains("Card::intel") { - continue; - } - rows += 1; - if let (Some(vendor), Some(device)) = - (hex_after(line, "vendor: 0x"), hex_after(line, "device: 0x")) - { - cards.push(format!("pci:{vendor}:{device}")); - } - } - assert_eq!( - cards.len(), - rows, - "netstack names `Card::intel` on {rows} line(s) and an id was read off {}; its `CARDS` \ - table is spelled in a way this gate does not reach", - cards.len() - ); - cards - } - - /// netstack's Intel-only actuators — `--exit-with-lease` reports the Intel - /// driver's bring-up beside the lease — and virtio's driver has none, so a - /// boot config that arms one on a card netstack opens with any other driver is - /// a boot that panics instead of answering the question it was built for. - fn an_armed_intel_actuator_claims_a_card_the_driver_opens( - cfg: &SystemConfig, - cards: &[String], - ) -> Result<(), String> { - for (name, prog) in &cfg.programs { - if !prog.args.iter().any(|arg| arg == EXIT_WITH_LEASE) { - continue; - } - if !prog.devices.iter().any(|d| cards.contains(d)) { - return Err(format!( - "`{name}` is armed with `{EXIT_WITH_LEASE}` and claims {:?}, none of which \ - is one of the {cards:?} netstack opens with that driver", - prog.devices - )); - } - } - Ok(()) - } - - #[test] - fn every_armed_intel_actuator_claims_a_card_the_driver_opens() { - let (_, source) = netstack_source(); - let cards = netstack_intel_cards(&source); - assert!(!cards.is_empty(), "netstack's `CARDS` names no card its Intel driver opens"); - let mut armed = 0; - for cfg in ALL_CONFIGS { - let config = load(cfg); - armed += config - .programs - .values() - .filter(|p| p.args.iter().any(|arg| arg == EXIT_WITH_LEASE)) - .count(); - an_armed_intel_actuator_claims_a_card_the_driver_opens(&config, &cards) - .unwrap_or_else(|e| panic!("{cfg}: {e}")); - } - // A walk that reached no armed program passes on having found nothing, - // which is the one way this gate can rot while every config still loads. - assert!(armed > 0, "no shipped boot config arms `{EXIT_WITH_LEASE}` at all"); - let armed_on = |device: &str, args: &str| { - let cfg: SystemConfig = toml::from_str(&format!( - "[programs.netstack]\ndevices = [\"{device}\"]\nargs = [{args}]\n" - )) - .unwrap(); - an_armed_intel_actuator_claims_a_card_the_driver_opens(&cfg, &cards) - }; - // The card netstack drives with the other driver, and an Intel function it - // drives with none: a vendor id is not what gives a part an `ICS` or a - // PHY behind `MDIC`. - let flag = format!("\"{EXIT_WITH_LEASE}\""); - assert!(armed_on("pci:1af4:1041", &flag).is_err()); - assert!(armed_on("pci:8086:1502", &flag).is_err()); - assert!(armed_on(&cards[0], &flag).is_ok()); - } - /// A device name the ABI does not know renders fine and leaves the supervisor with a /// `devices` entry it cannot mint — a dead machine for a typo, where this is /// a red in milliseconds. Same for a `syscap` right. diff --git a/tests/common/lan.rs b/tests/common/lan.rs index d8b9264403b..cbccc80e392 100644 --- a/tests/common/lan.rs +++ b/tests/common/lan.rs @@ -3,31 +3,12 @@ //! //! Every line read here is a line of a boot's log — the kernel's records, and //! netstack's own lines, which reach the stick under netstack's name through its log -//! ring — or the one file netstack leaves beside them, the lease probe's -//! report. The judge reads netstack's lines by that name and no other program's. +//! ring. The judge reads netstack's lines by that name and no other program's. use toyos_build::lan::{lease_in, link_up_ms, LINK_UP, MAC, READY}; -use toyos_i219::lease::{self, Event, Verdict}; use super::metal; -/// The boot with netstack's `--exit-with-lease` armed: netstack brings the card up and -/// serves, leaves [`LEASE_FILE`] on the log volume one durable line at a time, -/// and ends with the lease's verdict as its exit code, which the kernel's -/// `exit:` record carries off a machine whose console reaches nobody. -pub const LEASE_CONFIG: &str = "tests/lanleasecase"; -pub const LEASE_BOOT: &str = "lanleasecase"; - -/// The file that report is left in, at the root of the log volume — netstack's -/// `report::PATH` under `/log`. -pub const LEASE_FILE: &str = "lease.txt"; - -/// netstack, as the kernel's `exit:` record names it. -const NETSTACK: &str = "netstack"; - -/// The one job on that boot: it holds the machine up. -pub const JOBS: &[&str] = &["test_rs_lan_hold"]; - /// The boot the host talks to over its own cable: the log it serves, sshserver, and /// `reboot` as the way the machine is handed back. pub const TALK_CONFIG: &str = "tests/lantalkcase"; @@ -140,49 +121,6 @@ pub fn on_metal(back: &metal::Readback) -> Result<(), String> { Err(format!("{} finding(s):\n {}", bad.len(), bad.join("\n "))) } -/// The lease probe's judge: netstack's exit code, decoded through the table the -/// driver crate owns, and the report it left on the log volume — the lease, -/// the router, and what the driver and the MAC counted each way. A code that is -/// no lease is a finding by its name, which the shipping boot's silence cannot -/// give. -pub fn leased_on_metal(back: &metal::Readback) -> Result<(), String> { - let code = back.exit_code(NETSTACK)?; - let text = back - .log_volume_file(LEASE_FILE)? - .ok_or_else(|| format!("{LEASE_BOOT}'s log volume carries no {LEASE_FILE}"))?; - for line in text.lines() { - eprintln!(" [lan] {LEASE_FILE}: {line}"); - } - let summary = lease::summary(&text).map_err(|why| format!("{LEASE_FILE}: {why}"))?; - if summary.exit != Some(code) { - return Err(format!( - "netstack exited {code} and its report ends in {:?}: the two records of one exit disagree", - summary.exit - )); - } - match Verdict::from_exit_code(code) { - Some(Verdict::Leased) => {} - Some(other) => return Err(format!("netstack exited {code} on {LEASE_BOOT}: {other}")), - None => { - return Err(format!("netstack exited {code} on {LEASE_BOOT}, which is no verdict the probe encodes")) - } - } - let Some((ms, Event::Leased { address, prefix, server, router })) = summary.lease else { - return Err(format!("netstack exited leased and {LEASE_FILE} records no lease")); - }; - let counts = summary.counts.ok_or_else(|| format!("{LEASE_FILE} carries no counts"))?; - eprintln!( - " [lan] leased {address}/{prefix} from {server}, router {router:?}, {ms} ms after netstack \ - started; the driver counted {} sent and {} received, the MAC {} sent, {} received of \ - {} seen", - counts.sent, counts.received, counts.wire.sent, counts.wire.received, counts.wire.seen - ); - if counts.sent == 0 || counts.received == 0 { - return Err(format!("a lease with {counts:?} is no exchange this card carried")); - } - Ok(()) -} - /// The T14 talked to over its own cable, judged from the Mac's side: the log /// the machine served, asked for by its name while it booted, is the stick's /// own log from its first line in its own order, the address the name answered diff --git a/tests/common/metal.rs b/tests/common/metal.rs index c4862013199..a09a4e3b108 100644 --- a/tests/common/metal.rs +++ b/tests/common/metal.rs @@ -297,24 +297,6 @@ impl Readback { Ok((heard, stream.split_inclusive('\n').map(str::to_string).collect())) } - /// One file off the log volume that is neither the loader's nor `logkeeper`'s, - /// read out of the partition's own bytes; `None` where the volume has no - /// such file. - /// - /// **The loop copies two kinds of file off the mount and this is neither**, - /// so it comes out of `metal::READBACK_VOLUME` — which the loop keeps on - /// every boot that came back, because the outside judge runs on every one. - pub fn log_volume_file(&self, name: &str) -> Result, String> { - let at = self.home.join(toyos_build::metal::READBACK_VOLUME); - let volume = std::fs::read(&at).map_err(|e| format!("{}: {e}", at.display()))?; - let found = super::volumes::read_files(&volume, &[name])?.pop().flatten(); - found - .map(|bytes| { - String::from_utf8(bytes).map_err(|e| format!("{}'s {name}: {e}", self.label)) - }) - .transpose() - } - /// Every `logkeeper` file this boot wrote, as one text, less every program's /// line ([`bootlog::kernel_records`]): no program's line is read as the kernel's. /// It ends where the supervisor had `logkeeper` make it whole, so what the kernel writes diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 9d19545e7ea..fb920195959 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -21,4 +21,3 @@ pub mod screen; pub mod serial; pub mod ssh; pub mod usb; -pub mod volumes; diff --git a/tests/common/volumes.rs b/tests/common/volumes.rs deleted file mode 100644 index 6e6dd5de689..00000000000 --- a/tests/common/volumes.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::io::{Cursor, Read}; - -use fatfs::FsOptions; - -/// Read several files out of a FAT volume in one mount. `None` is a file that -/// is not there, which is an assertion in its own right here. -/// -/// `fatfs` wants a writable, seekable device even to read, so the volume is -/// copied — once per call, which is why the callers ask for everything they -/// need at once rather than a file at a time. -pub fn read_files(volume: &[u8], paths: &[&str]) -> Result>>, String> { - let fs = fatfs::FileSystem::new(Cursor::new(volume.to_vec()), FsOptions::new()) - .map_err(|e| format!("the volume does not mount on the host: {e}"))?; - let root = fs.root_dir(); - let mut out = Vec::with_capacity(paths.len()); - for path in paths { - match root.open_file(path) { - Ok(mut file) => { - let mut bytes = Vec::new(); - file.read_to_end(&mut bytes).map_err(|e| format!("reading {path}: {e}"))?; - out.push(Some(bytes)); - } - Err(_) => out.push(None), - } - } - Ok(out) -} diff --git a/tests/lanleasecase/system.toml b/tests/lanleasecase/system.toml deleted file mode 100644 index 8b23ade2615..00000000000 --- a/tests/lanleasecase/system.toml +++ /dev/null @@ -1,39 +0,0 @@ -# netstack in front of the ThinkPad T14's own NIC with its lease probe armed: netstack -# brings the card up and serves, leaves what happened on the log volume one -# durable line at a time, and ends with the lease's verdict as its exit code — -# which the kernel's `exit:` record carries off a machine whose console reaches -# nobody. Nothing here reaches the internal NVMe: no `[disks]` row, and the -# only device any program claims is the PCI function named below. - -[boot] -start = ["logkeeper", "diskserver", "fileserver", "netstack", "test-runner"] - -[programs.logkeeper] -service = true -syscap = ["logread"] - -[programs.netstack] -service = true -serves = ["netstack"] -devices = ["pci:8086:15fc"] -args = ["--exit-with-lease"] - -[programs.test-runner] -syscap = ["logread"] - -# The block service: the NVMe controller this machine's DATA is on, driven -# from userland through its claim. Started again when it ends; the claim goes -# back with the process and is minted again for the next. -[programs.diskserver] -service = true -restart = true -serves = ["block"] -devices = ["pci:1b36:0010"] - -# The file servers: DATA, the log and the running slot's volume, one process -# each, serving every program the directories of its role. Started again when -# one ends, on the same ports. -[programs.fileserver] -restart = true -roles = ["data", "log", "boot"] -receives = ["block"] diff --git a/tests/metal/lenovo-20w0003amz.toml b/tests/metal/lenovo-20w0003amz.toml index 929ff2ad3bf..e4f78ee5d69 100644 --- a/tests/metal/lenovo-20w0003amz.toml +++ b/tests/metal/lenovo-20w0003amz.toml @@ -22,12 +22,6 @@ bios = "N34ET71W (1.71 )" "boot.isa-withheld.complete_ms" = 780 "boot.isa-withheld.panel_max_us" = 3888 "boot.isa-withheld.panel_us" = 19251 -"boot.jobcase.complete_ms" = 1164 -"boot.jobcase.panel_max_us" = 3873 -"boot.jobcase.panel_us" = 21418 -"boot.lanleasecase.complete_ms" = 1165 -"boot.lanleasecase.panel_max_us" = 3841 -"boot.lanleasecase.panel_us" = 22280 "boot.lantalkcase.complete_ms" = 1152 "boot.lantalkcase.panel_max_us" = 3919 "boot.lantalkcase.panel_us" = 20906 @@ -46,9 +40,6 @@ bios = "N34ET71W (1.71 )" "boot.proctreecase.complete_ms" = 1153 "boot.proctreecase.panel_max_us" = 3773 "boot.proctreecase.panel_us" = 21279 -"boot.selftests.complete_ms" = 1172 -"boot.selftests.panel_max_us" = 3950 -"boot.selftests.panel_us" = 22805 "boot.shared-2.complete_ms" = 1165 "boot.shared-2.panel_max_us" = 3721 "boot.shared-2.panel_us" = 21855 diff --git a/tests/toyos-rust-tests/src/bin/lan_hold.rs b/tests/toyos-rust-tests/src/bin/lan_hold.rs index abac209162e..f998e4aedba 100644 --- a/tests/toyos-rust-tests/src/bin/lan_hold.rs +++ b/tests/toyos-rust-tests/src/bin/lan_hold.rs @@ -1,12 +1,9 @@ -//! Hold the boot open, and exit. It asserts nothing; the host judges the -//! records netstack wrote inside this window, and `dump_nmi_probe`'s metal row the -//! dump its actuator stages inside it. +//! Hold the boot open, and exit. It asserts nothing; `dump_nmi_probe`'s metal +//! row judges the dump its actuator stages inside this window. use std::thread::sleep; use std::time::Duration; -/// **netstack's own lease bound**, so this boot cannot hand the machine back before -/// netstack has settled whether it has an address. const HOLD: Duration = Duration::from_millis(toyos_tco::LEASE_BOUND_MS); fn main() { diff --git a/tests/toyos.rs b/tests/toyos.rs index 7b95c2bddaa..6b3934f0f6d 100644 --- a/tests/toyos.rs +++ b/tests/toyos.rs @@ -62,8 +62,7 @@ const ACTUATOR_TESTS: &[&str] = &[ "ring0_timer_in_syscall", ]; -/// What [`ACTUATOR_TESTS`] boots: the one kernel that carries `SYS_DEBUG`, with -/// no actuator armed in it. +/// What [`ACTUATOR_TESTS`] boots: the one kernel that carries `SYS_DEBUG`. const ACTUATOR_KERNEL: &[&str] = toyos_build::build::TEST_KERNEL; // Rust helper binaries that are spawned by tests, not tests themselves. @@ -135,7 +134,7 @@ const RUST_SKIP: &[&str] = &[ // It needs a NIC in front of netstack and a host server behind it: // `netstack_socket_churn` runs it on `tests/netcase`. "netstack_socket_churn", - // It asserts nothing at all: it holds a `tests/lanleasecase` boot open for + // It asserts nothing at all: it holds `dump_nmi_probe`'s boot open for // twenty seconds. On a shared boot it would be twenty seconds of nothing. "lan_hold", // The same for `tests/lantalkcase`, held until the runner's bound is near @@ -323,12 +322,6 @@ const METAL: &[(&str, metal::Metal)] = &[ "lan_message_delivery", metal::Metal { arms: LANTALKCASE, judge: |b| lan::delivered_on_metal(b[0]) }, ), - ( - // The first byte: a lease from the bench's own router, read off the - // stick. - "lan_lease_report", - metal::Metal { arms: LANLEASECASE, judge: |b| lan::leased_on_metal(b[0]) }, - ), ( "lan_talk", metal::Metal { arms: LANTALKCASE, judge: |b| lan::talked_on_metal(b[0]) }, @@ -647,26 +640,7 @@ const METAL: &[(&str, metal::Metal)] = &[ }, }, ), - // ---- one image: tests/jobcase ---- - ( - // Two boots the suite already flashes: the device boot writes and - // fsyncs megabytes before its reset, and `jobcase` is the same reset - // with nothing moved across the bus. Neither costs the machine a - // minute it was not already spending. - "usb_reset_hands_devices_back", - metal::Metal { - arms: USB_RESET_BOOTS, - judge: power::usb_reset_on_metal, - }, - ), - ( - // Already precisely this boot: `args = ["reboot"]`. On the T14 the - // chain is what every metal boot does — the loader points `BootNext` at - // itself before each handoff, so the pass that reads the page appends - // its report to the same `loader.log` the driver hands back. - "blackbox_done_chain", - metal::Metal { arms: JOBCASE, judge: |b| power::done_chain(&b[0].after_the_reset()?) }, - ), + // ---- tests/jobcase, each image armed to end its boot its own way ---- ( // Its own boot, and it must not share one: it is the only arm in this // profile that deliberately leaves the machine unable to end its own @@ -731,20 +705,6 @@ const METAL: &[(&str, metal::Metal)] = &[ }, }, ), - ( - // The machine came back to Ubuntu's ssh server, which is what tells a reset from the - // S5 power-off the QEMU stop reason exists to catch — the driver - // established it before this judge ran. What is left is the kernel's own - // decode, and `0xcf9 <- 0x0f` is q35's register rather than this one's. - "machine_reboot", - metal::Metal { - arms: JOBCASE, - judge: |b| { - power::reset_register_decoded(&b[0].kernel())?; - bootlog::handed_back(b[0].after_the_reset()?.text()).map_err(|why| why.to_string()) - }, - }, - ), // ---- one image: tests/proctreecase ---- ( // A parent's end takes its children down. The guest carries every @@ -776,15 +736,50 @@ const METAL: &[(&str, metal::Metal)] = &[ "fs_share", metal::Metal { arms: PROCTREECASE, judge: |b| b[0].job_passed("test_rs_fs_share") }, ), - // ---- one image: tests/metalcase ---- + // ---- one image: tests/metalcase, which runs no job ---- + // + // The rows after the first read what any boot that hands the machine back + // writes, so they ride this one. ( "metal_sim_scanout_wc", metal::Metal { arms: METALCASE, judge: |b| scanout_wc(b[0].kernel().text()) }, ), - // ---- one image ---- + ( + // Two boots the suite already flashes: the device boot writes and + // fsyncs megabytes before its reset, and `metalcase` is the same reset + // with no job behind it. + "usb_reset_hands_devices_back", + metal::Metal { + arms: USB_RESET_BOOTS, + judge: power::usb_reset_on_metal, + }, + ), + ( + // On the T14 the chain is what every metal boot does — the loader + // points `BootNext` at itself before each handoff, so the pass that + // reads the page appends its report to the same `loader.log` the + // driver hands back. + "blackbox_done_chain", + metal::Metal { arms: METALCASE, judge: |b| power::done_chain(&b[0].after_the_reset()?) }, + ), + ( + // The machine came back to Ubuntu's ssh server, which is what tells a reset from the + // S5 power-off the QEMU stop reason exists to catch — the driver + // established it before this judge ran. What is left is the kernel's own + // decode, and `0xcf9 <- 0x0f` is q35's register rather than this one's. + "machine_reboot", + metal::Metal { + arms: METALCASE, + judge: |b| { + power::reset_register_decoded(&b[0].kernel())?; + bootlog::handed_back(b[0].after_the_reset()?.text()).map_err(|why| why.to_string()) + }, + }, + ), + // ---- the `SYS_DEBUG` members' boot, armed ---- // The cheapest cluster there is: every one of these arms a check that runs - // at init, logs its verdict and does nothing else, so they cost one flash - // between them. **Nothing had to be promoted into `kernel/src/params.rs`** + // at init, logs its verdict and does nothing else, so they cost no flash + // of their own. **Nothing had to be promoted into `kernel/src/params.rs`** // — the metal profile flashes test images (the track's ruling), and the // pre-flash gate is what says the machine survives each one. ( @@ -957,8 +952,6 @@ const BOUNDS: &[metal::Arm] = &[metal::once( &["test_rs_abuse_mmap_regions", "test_rs_abuse_thread_table", "test_rs_abuse_dlopen_ledger"], )]; -const JOBCASE: &[metal::Arm] = &[metal::once("jobcase", "tests/jobcase", &[], &[])]; - /// The shipping kernel with the windows' instrument and nothing else, so what /// it reads is that kernel under the herd. Three exits, a report each: /// `idle_span`'s is the boot's first, where the kernel holds; `pwd`'s reads the @@ -986,7 +979,7 @@ const LOGSTALLCASE: &[metal::Arm] = &[metal::once("logstallcase", "tests/logstallcase", &[], &["test_rs_soundserver_log_stall"])]; /// The two boots the reset ruling is judged on: the device boot for a reset -/// with megabytes behind it, and `jobcase` for one with nothing. +/// with megabytes behind it, and `metalcase` for one with no job behind it. const USB_RESET_BOOTS: &[metal::Arm] = &[ metal::once( devices::BOOT, @@ -994,7 +987,7 @@ const USB_RESET_BOOTS: &[metal::Arm] = &[ &[], devices::JOBS, ), - metal::once("jobcase", "tests/jobcase", &[], &[]), + metal::once("metalcase", "tests/metalcase", &[], &[]), ]; const METALCASE: &[metal::Arm] = &[metal::once("metalcase", "tests/metalcase", &[], &[])]; @@ -1016,52 +1009,46 @@ const PROCTREECASE: &[metal::Arm] = &[metal::once( ], )]; -/// netstack in front of the T14's I219 with its lease probe armed: netstack's exit code -/// is the lease's verdict, read out of the kernel's own `exit:` record, and its -/// report is on the log volume. It names the I219, so the loop refuses a cable -/// that is out before it flashes, as [`LANTALKCASE`] does. -const LANLEASECASE: &[metal::Arm] = &[metal::Arm { - nic: Some(lan::NIC), - ..metal::once(lan::LEASE_BOOT, lan::LEASE_CONFIG, &[], lan::JOBS) -}]; - /// The cable's boot, netstack in front of the T14's I219, which the host talks to /// over that cable: the loop reads the log it serves under its name, pings it, -/// runs a command on it and tells it to reboot. +/// runs a command on it and tells it to reboot. It names the I219, so the loop +/// refuses a cable that is out before it flashes. const LANTALKCASE: &[metal::Arm] = &[metal::Arm { talk: true, nic: Some(lan::NIC), ..metal::once(lan::TALK_BOOT, lan::TALK_CONFIG, &[], lan::TALK_JOBS) }]; -/// One boot for every in-kernel self-test that logs its verdict at init and -/// does nothing else. +/// Every in-kernel self-test that logs its verdict at init and does nothing +/// else. /// -/// They cost the machine one flash between them because none of them changes +/// They cost the machine no flash of their own because none of them changes /// what the machine *is*: each stages inputs the hardware cannot produce — a /// crafted capability list, a malformed descriptor, a vector nothing claims — -/// runs a check over them and prints a count. -const SELFTESTS: &[metal::Arm] = &[metal::once( - "selftests", - "tests/testcases", - &[ - "pci-cap-selftest", - "revoked-backing-selftest", - "leak-rollback-selftest", - "lapic-spurious-selftest", - "unclaimed-vector-selftest", - "xhci-xecp-selftest", - "xhci-descriptor-selftest", - "sysret-ss-probe", - "test-input-merge", - "sched-operation-nesting", - "iommu-firmware-left", - ], - &[], -)]; +/// runs a check over them and prints a count. So they arm the boot +/// [`ACTUATOR_TESTS`] ride, which is the same kernel. +const SELFTEST_PARAMS: &[&str] = &[ + "pci-cap-selftest", + "revoked-backing-selftest", + "leak-rollback-selftest", + "lapic-spurious-selftest", + "unclaimed-vector-selftest", + "xhci-xecp-selftest", + "xhci-descriptor-selftest", + "sysret-ss-probe", + "test-input-merge", + "sched-operation-nesting", + "iommu-firmware-left", +]; + +const SELFTESTS: &[metal::Arm] = &[metal::Arm { + features: ACTUATOR_KERNEL, + ..metal::once("shared-debug", "tests/testcases", SELFTEST_PARAMS, &[]) +}]; /// The boots every discovered Rust binary rides on the T14: the shipping -/// kernel's, and [`ACTUATOR_TESTS`] on the kernel that carries `SYS_DEBUG`. +/// kernel's, and [`ACTUATOR_TESTS`] on the kernel that carries `SYS_DEBUG`, +/// armed with [`SELFTEST_PARAMS`]. fn shared_metal() -> Vec { let (debug, shipping): (Vec, Vec) = discover_rust_tests() .into_iter() @@ -1085,8 +1072,8 @@ fn shared_metal() -> Vec { metal::SharedBoot { boot: "shared-debug".to_string(), config: "tests/testcases", - params: &[], - features: toyos_build::build::TEST_KERNEL, + params: SELFTEST_PARAMS, + features: ACTUATOR_KERNEL, members: const { std::num::NonZeroUsize::new(18).expect("a chunk holds a member") }, jobs: debug.iter().map(|n| format!("test_rs_{n}")).collect(), files: Vec::new(), @@ -4954,7 +4941,7 @@ fn the_metal_gate_refuses_what_it_names() -> Result<(), String> { fn judge(_: &[&metal::Readback]) -> Result<(), String> { Ok(()) } - const RUNS: metal::Metal = metal::Metal { arms: JOBCASE, judge }; + const RUNS: metal::Metal = metal::Metal { arms: METALCASE, judge }; const NONE: metal::Metal = metal::Metal { arms: &[], judge }; const NO_CONFIG: metal::Metal = metal::Metal { arms: &[metal::once("nowhere", "tests/no-such-config", &[], &[])], diff --git a/toyos-i219/src/lease.rs b/toyos-i219/src/lease.rs deleted file mode 100644 index e677653b07e..00000000000 --- a/toyos-i219/src/lease.rs +++ /dev/null @@ -1,262 +0,0 @@ -//! What netstack's lease probe answers, in the two places it can answer on a -//! machine whose console reaches nobody: its exit code, and a file of durable -//! lines on the log volume. One table both ends read. -//! -//! **The lease is the claim.** A DHCP lease is a DISCOVER this machine put on -//! the wire, an OFFER a server it does not control sent back, a REQUEST and an -//! ACK — frames out and frames in, answered by somebody else. The counts beside -//! it say how many of each the driver and the MAC's own statistics saw, so a -//! boot that got no lease still says which half of the exchange went missing. - -use core::net::Ipv4Addr; - -use crate::phy::Outcome; -use crate::{Counters, Link, Speed, Wire}; - -/// The code a probe that holds a leased address when its window ends exits -/// with. Clear of [`Outcome`]'s block and its retired 82, and of 101, which a -/// panicking netstack ends with. -pub const LEASED: i32 = 83; - -const _: () = { - let mut at = 0; - while at < Outcome::ALL.len() { - assert!(Outcome::ALL[at] as i32 != LEASED); - at += 1; - } -}; - -/// What one lease probe boot answers. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum Verdict { - /// A leased address was held when the probe's window ended. - Leased, - /// None was, and this is what the bring-up and the link said: a PHY that - /// came up with a link and still no lease is a network that did not - /// answer, a PHY that never came up is the bring-up's refusal. - NotLeased(Outcome), -} - -impl Verdict { - pub fn exit_code(self) -> i32 { - match self { - Self::Leased => LEASED, - Self::NotLeased(outcome) => outcome.exit_code(), - } - } - - /// The verdict a code names, or `None` for one no probe exits with. - pub fn from_exit_code(code: i32) -> Option { - if code == LEASED { - return Some(Self::Leased); - } - Outcome::from_exit_code(code).map(Self::NotLeased) - } -} - -impl core::fmt::Display for Verdict { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::Leased => f.write_str("an address was leased: frames went out and came back"), - Self::NotLeased(outcome) => write!(f, "no address was leased, and {outcome}"), - } - } -} - -/// What the driver and the MAC counted, as one line spells them. -#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] -pub struct Counts { - /// Transmit descriptors the part wrote back. - pub sent: u32, - /// Receive descriptors the part filled and the driver handed up. - pub received: u32, - /// The MAC's own statistics. - pub wire: Wire, -} - -impl Counts { - pub fn of(counters: Counters, wire: Wire) -> Self { - Self { sent: counters.sent, received: counters.received, wire } - } -} - -/// One thing the probe says, in the order it happened. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum Event<'a> { - /// The bring-up's own words about itself, one sentence per line. - BroughtUp(&'a str), - /// The link, whenever it changed. - Link(Link), - /// The lease, the moment it landed. - Leased { address: Ipv4Addr, prefix: u8, server: Ipv4Addr, router: Option }, - /// The address given up, the moment the client gave it up. - Lost, - Counts(Counts), - /// The last line: the code the process ends with. - Exit { code: i32 }, -} - -/// One line of the file: milliseconds since the process started, and what -/// happened then. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct Line<'a> { - pub ms: u64, - pub event: Event<'a>, -} - -impl core::fmt::Display for Line<'_> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{} ", self.ms)?; - match self.event { - Event::BroughtUp(words) => write!(f, "brought-up {words}"), - Event::Link(Link::Down) => f.write_str("link down"), - Event::Link(Link::Up { speed, full_duplex }) => write!( - f, - "link up {} {}", - speed.mbps(), - if full_duplex { "full" } else { "half" } - ), - Event::Leased { address, prefix, server, router } => { - write!(f, "leased {address}/{prefix} from {server} router ")?; - match router { - Some(router) => write!(f, "{router}"), - None => f.write_str("none"), - } - } - Event::Lost => f.write_str("lost"), - Event::Counts(Counts { sent, received, wire }) => write!( - f, - "counts sent {sent} received {received} wire-sent {} wire-received {} \ - wire-seen {} missed {} crc-errors {}", - wire.sent, wire.received, wire.seen, wire.missed, wire.crc_errors - ), - Event::Exit { code } => write!(f, "exit {code}"), - } - } -} - -impl<'a> Line<'a> { - pub fn parse(text: &'a str) -> Option { - let (ms, rest) = text.split_once(' ')?; - let ms = ms.parse().ok()?; - let (word, rest) = rest.split_once(' ').unwrap_or((rest, "")); - let event = match word { - "brought-up" if !rest.is_empty() => Event::BroughtUp(rest), - "link" => Event::Link(parse_link(rest)?), - "leased" => parse_lease(rest)?, - "lost" if rest.is_empty() => Event::Lost, - "counts" => Event::Counts(parse_counts(rest)?), - "exit" => Event::Exit { code: rest.parse().ok()? }, - _ => return None, - }; - Some(Self { ms, event }) - } -} - -fn parse_link(text: &str) -> Option { - let mut words = text.split(' '); - let link = match (words.next()?, words.next(), words.next()) { - ("down", None, None) => Link::Down, - ("up", Some(mbps), Some(duplex)) => Link::Up { - speed: match mbps { - "10" => Speed::Mbps10, - "100" => Speed::Mbps100, - "1000" => Speed::Mbps1000, - _ => return None, - }, - full_duplex: match duplex { - "full" => true, - "half" => false, - _ => return None, - }, - }, - _ => return None, - }; - words.next().is_none().then_some(link) -} - -fn parse_lease(text: &str) -> Option> { - let mut words = text.split(' '); - let (address, prefix) = words.next()?.split_once('/')?; - let (from, server, keyword, router) = - (words.next()?, words.next()?, words.next()?, words.next()?); - if from != "from" || keyword != "router" || words.next().is_some() { - return None; - } - Some(Event::Leased { - address: address.parse().ok()?, - prefix: prefix.parse().ok()?, - server: server.parse().ok()?, - router: match router { - "none" => None, - router => Some(router.parse().ok()?), - }, - }) -} - -fn parse_counts(text: &str) -> Option { - let mut words = text.split(' '); - let mut field = |name: &str| -> Option { - (words.next()? == name).then_some(())?; - words.next()?.parse().ok() - }; - let counts = Counts { - sent: u32::try_from(field("sent")?).ok()?, - received: u32::try_from(field("received")?).ok()?, - wire: Wire { - sent: field("wire-sent")?, - received: field("wire-received")?, - seen: field("wire-seen")?, - missed: field("missed")?, - crc_errors: field("crc-errors")?, - }, - }; - words.next().is_none().then_some(counts) -} - -/// What a whole file says: the first lease it recorded, whether a lease was -/// still held after its last line, the last counts, and the code it ended with. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct Summary { - pub lease: Option<(u64, Event<'static>)>, - /// Whether the last of the file's `leased` and `lost` lines is a lease. - pub held: bool, - pub counts: Option, - pub exit: Option, -} - -/// Why a file is not a probe's report. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct Unreadable<'a> { - pub line: &'a str, -} - -impl core::fmt::Display for Unreadable<'_> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{:?} ends in a newline and is no line of a lease probe's report", self.line) - } -} - -/// Read a whole file. **A last line with no newline after it is the write the -/// machine ended in**, and is left out rather than refused. -pub fn summary(text: &str) -> Result> { - let whole = text.rfind('\n').map_or("", |end| &text[..=end]); - let mut summary = Summary { lease: None, held: false, counts: None, exit: None }; - for line in whole.lines() { - let parsed = Line::parse(line).ok_or(Unreadable { line })?; - match parsed.event { - Event::Leased { address, prefix, server, router } => { - summary.held = true; - if summary.lease.is_none() { - summary.lease = - Some((parsed.ms, Event::Leased { address, prefix, server, router })); - } - } - Event::Lost => summary.held = false, - Event::Counts(counts) => summary.counts = Some(counts), - Event::Exit { code } => summary.exit = Some(code), - _ => {} - } - } - Ok(summary) -} diff --git a/toyos-i219/src/lib.rs b/toyos-i219/src/lib.rs index 72b2f8479a9..4c818cc3d9f 100644 --- a/toyos-i219/src/lib.rs +++ b/toyos-i219/src/lib.rs @@ -15,8 +15,6 @@ //! within `MDIC`'s reach before the reset and what shape the reset then takes — //! the MAC and the PHY together — on properties of the part no Intel document //! publishes, and `pch` is what the PCH's MAC is given before its rings. -//! [`lease`] is the one table a lease probe's answer crosses a machine with no -//! console through. //! //! # The boundary //! @@ -73,7 +71,6 @@ #[cfg(test)] extern crate std; -pub mod lease; mod pch; pub mod phy; pub mod power; diff --git a/toyos-i219/src/phy.rs b/toyos-i219/src/phy.rs index 3dae44305cc..c8b7b9b5450 100644 --- a/toyos-i219/src/phy.rs +++ b/toyos-i219/src/phy.rs @@ -21,7 +21,7 @@ use core::cell::Cell; use crate::power::{Reading, Settled}; use crate::regs::{self, extcnf, mdic}; use crate::wake::{self, Action, Answer, Moment, Woke}; -use crate::{Clock, Link, Registers, Speed}; +use crate::{Clock, Registers}; /// How long [`Owned::transact`] waits for §10.2.2.7's `Ready` before the part /// is refused by name. @@ -435,204 +435,6 @@ impl core::fmt::Display for Phy { } } -/// What one probe boot answers, with every field dropped: the bring-up's -/// outcome and, where it brought the PHY up, the link that came after it — -/// which is what one exit code carries off a machine whose console reaches -/// nobody. -/// -/// **One table, read at both ends.** netstack exits with [`Outcome::exit_code`] -/// when its caller asks for the outcome that way, the kernel records the code -/// in its `exit:` record, and the harness reads the record back through -/// [`Outcome::from_exit_code`]. The block starts at 64: clear of 0, a clean -/// exit, of the codes at 128 and above that a process ends with when it did -/// not choose its own end, and of 101, which the Rust runtime ends a panicking -/// netstack with and which would therefore read back as an outcome the PHY never -/// gave. -/// -/// **A PHY that came up is seven codes and not one.** The question the probe -/// is flashed for is whether the cable came up, and the only channel it has is -/// this number — so the link's absence and each speed and duplex §10.2.2.2's -/// `STATUS` can resolve to has a code of its own. -/// -/// **[`PhyRefusal::SoftwareFlagStood`] is four codes and not one**, for the -/// same reason: a flag that is another software agent's is the one refusal -/// whose cause is another agent, and which of §4.5.2's other two stood beside -/// it is what a machine with no console cannot otherwise say. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -#[repr(i32)] -pub enum Outcome { - BroughtUpNoLink = 64, - LinkAt10Half = 65, - LinkAt10Full = 66, - LinkAt100Half = 67, - LinkAt100Full = 68, - LinkAt1000Half = 69, - LinkAt1000Full = 70, - Unrouted = 71, - SoftwareFlagStood = 72, - SoftwareFlagStoodBesideHardware = 73, - SoftwareFlagStoodBesideManageability = 74, - SoftwareFlagStoodBesideBoth = 75, - GrantNeverCame = 76, - MdiUnready = 77, - /// **Out of order because a code is never reused and never renumbered**: - /// every code above is already on a boot log somewhere. 82 is retired: it - /// was a refusal this driver no longer makes. - MdiWaiting = 81, - MdiError = 78, - Identity = 79, - NotThisRegisterMap = 80, -} - -impl core::fmt::Display for Outcome { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self.link() { - Some(Link::Up { speed, full_duplex }) => write!( - f, - "the PHY was brought up and the link came at {} Mb/s {}", - speed.mbps(), - if full_duplex { "full duplex" } else { "half duplex" } - ), - Some(Link::Down) => { - write!(f, "the PHY was brought up and no link came inside the driver's bound") - } - None => write!(f, "the bring-up refused the PHY: {}", self.refusal_word()), - } - } -} - -impl Outcome { - /// The clause each refusal stands for, in a word — the other half of - /// [`Display`], and never reached for an outcome that brought the PHY up. - /// - /// [`Display`]: core::fmt::Display - fn refusal_word(self) -> &'static str { - match self { - Self::BroughtUpNoLink - | Self::LinkAt10Half - | Self::LinkAt10Full - | Self::LinkAt100Half - | Self::LinkAt100Full - | Self::LinkAt1000Half - | Self::LinkAt1000Full => "it did not", - Self::Unrouted => "a register it reaches answers ones", - Self::SoftwareFlagStood => "§4.5.2's software flag was another agent's", - Self::SoftwareFlagStoodBesideHardware => { - "§4.5.2's software flag was another agent's, the part's own hardware beside it" - } - Self::SoftwareFlagStoodBesideManageability => { - "§4.5.2's software flag was another agent's, the manageability agent beside it" - } - Self::SoftwareFlagStoodBesideBoth => { - "§4.5.2's software flag was another agent's, the part's own hardware and the \ - manageability agent beside it" - } - Self::GrantNeverCame => "§4.5.2's request was never granted", - Self::MdiWaiting => "§8.2.3's Wait bit never went, so no transaction was issued", - Self::MdiUnready => "an MDI transaction never reported §10.2.2.7's Ready", - Self::MdiError => "the part reported §10.2.2.7's Error on a transaction", - Self::Identity => "§9.5.2.3's identifier is not Intel's at either PHY address", - Self::NotThisRegisterMap => "this part's PHY is not the one §9 describes", - } - } - - /// Every outcome, in exit-code order. - pub const ALL: [Self; 18] = [ - Self::BroughtUpNoLink, - Self::LinkAt10Half, - Self::LinkAt10Full, - Self::LinkAt100Half, - Self::LinkAt100Full, - Self::LinkAt1000Half, - Self::LinkAt1000Full, - Self::Unrouted, - Self::SoftwareFlagStood, - Self::SoftwareFlagStoodBesideHardware, - Self::SoftwareFlagStoodBesideManageability, - Self::SoftwareFlagStoodBesideBoth, - Self::GrantNeverCame, - Self::MdiUnready, - Self::MdiError, - Self::Identity, - Self::NotThisRegisterMap, - Self::MdiWaiting, - ]; - - /// The outcome of a bring-up, and of the link that followed it. - /// - /// **`link` is read only where the PHY came up**: what a part whose PHY - /// this driver never configured reports about its link is the agent before - /// it talking, and the refusal is the whole of what that boot has to say. - pub fn of(phy: Result, link: Link) -> Self { - match phy { - Ok(_) => match link { - Link::Down => Self::BroughtUpNoLink, - Link::Up { speed: Speed::Mbps10, full_duplex: false } => Self::LinkAt10Half, - Link::Up { speed: Speed::Mbps10, full_duplex: true } => Self::LinkAt10Full, - Link::Up { speed: Speed::Mbps100, full_duplex: false } => Self::LinkAt100Half, - Link::Up { speed: Speed::Mbps100, full_duplex: true } => Self::LinkAt100Full, - Link::Up { speed: Speed::Mbps1000, full_duplex: false } => Self::LinkAt1000Half, - Link::Up { speed: Speed::Mbps1000, full_duplex: true } => Self::LinkAt1000Full, - }, - Err(PhyRefusal::Unrouted { .. }) => Self::Unrouted, - Err(PhyRefusal::SoftwareFlagStood { beside, .. }) => match beside { - Others::Nobody => Self::SoftwareFlagStood, - Others::Hardware => Self::SoftwareFlagStoodBesideHardware, - Others::Manageability => Self::SoftwareFlagStoodBesideManageability, - Others::HardwareAndManageability => Self::SoftwareFlagStoodBesideBoth, - }, - Err(PhyRefusal::GrantNeverCame { .. }) => Self::GrantNeverCame, - Err(PhyRefusal::MdiWaiting { .. }) => Self::MdiWaiting, - Err(PhyRefusal::MdiUnready { .. }) => Self::MdiUnready, - Err(PhyRefusal::MdiError { .. }) => Self::MdiError, - Err(PhyRefusal::Identity { .. }) => Self::Identity, - Err(PhyRefusal::NotThisRegisterMap) => Self::NotThisRegisterMap, - } - } - - /// The link this outcome carries, or `None` where the PHY was not brought - /// up at all — on which the link is the agent before this driver's and - /// never this bring-up's reading. - pub fn link(self) -> Option { - let up = |speed, full_duplex| Some(Link::Up { speed, full_duplex }); - match self { - Self::BroughtUpNoLink => Some(Link::Down), - Self::LinkAt10Half => up(Speed::Mbps10, false), - Self::LinkAt10Full => up(Speed::Mbps10, true), - Self::LinkAt100Half => up(Speed::Mbps100, false), - Self::LinkAt100Full => up(Speed::Mbps100, true), - Self::LinkAt1000Half => up(Speed::Mbps1000, false), - Self::LinkAt1000Full => up(Speed::Mbps1000, true), - Self::Unrouted - | Self::SoftwareFlagStood - | Self::SoftwareFlagStoodBesideHardware - | Self::SoftwareFlagStoodBesideManageability - | Self::SoftwareFlagStoodBesideBoth - | Self::GrantNeverCame - | Self::MdiWaiting - | Self::MdiUnready - | Self::MdiError - | Self::Identity - | Self::NotThisRegisterMap => None, - } - } - - /// Whether the PHY was brought up, whatever the link then did. - pub fn came_up(self) -> bool { - self.link().is_some() - } - - pub fn exit_code(self) -> i32 { - self as i32 - } - - /// The outcome an exit code names, or `None` for a code outside - /// [`Self::ALL`] — a process that ended for some other reason. - pub fn from_exit_code(code: i32) -> Option { - Self::ALL.into_iter().find(|outcome| outcome.exit_code() == code) - } -} - /// Every access this driver makes to §4.5.2's arbitration, paced. /// /// **The pace is the whole of what this adds.** The register is one word two diff --git a/toyos-i219/src/tests.rs b/toyos-i219/src/tests.rs index 5abbfe7af3d..654d8a99aea 100644 --- a/toyos-i219/src/tests.rs +++ b/toyos-i219/src/tests.rs @@ -10,7 +10,7 @@ use std::vec::Vec; use std::{format, vec}; use crate::phy as toyos_phy; -use crate::phy::{Others, Phy, PhyRefusal}; +use crate::phy::{Others, PhyRefusal}; use crate::regs::{self, cause, ctrl, extcnf, ivar, rctl, rx_desc, tctl, tx_desc}; use crate::stub::{Nic, Permits, Unanswered, NVM_MAC}; use crate::*; @@ -1547,68 +1547,6 @@ fn a_register_nothing_decodes_is_refused_and_never_written() { ); } -/// Every answer the probe can give has one exit code, and every code reads back -/// as its answer: the table netstack exits through and the harness decodes with is -/// one declaration, so the two ends cannot disagree about a number. -#[test] -fn every_probe_outcome_has_one_exit_code_that_reads_back() { - use toyos_phy::Outcome; - let oem = toyos_phy::Oem { found: 0, wrote: 0, after: 0 }; - let up = Ok(Phy { addr: toyos_phy::SPECIFIC, id: 0x0154_00a1, port_general: 0, oem }); - let link = |speed, full_duplex| Link::Up { speed, full_duplex }; - let stood = |beside| Err(PhyRefusal::SoftwareFlagStood { beside, after_nanos: 1 }); - let down = Link::Down; - let outcomes: [(Result, Link, Outcome); 18] = [ - (up, down, Outcome::BroughtUpNoLink), - (up, link(Speed::Mbps10, false), Outcome::LinkAt10Half), - (up, link(Speed::Mbps10, true), Outcome::LinkAt10Full), - (up, link(Speed::Mbps100, false), Outcome::LinkAt100Half), - (up, link(Speed::Mbps100, true), Outcome::LinkAt100Full), - (up, link(Speed::Mbps1000, false), Outcome::LinkAt1000Half), - (up, link(Speed::Mbps1000, true), Outcome::LinkAt1000Full), - (Err(PhyRefusal::Unrouted { reg: regs::EXTCNF_CTRL }), down, Outcome::Unrouted), - (stood(Others::Nobody), down, Outcome::SoftwareFlagStood), - (stood(Others::Hardware), down, Outcome::SoftwareFlagStoodBesideHardware), - (stood(Others::Manageability), down, Outcome::SoftwareFlagStoodBesideManageability), - (stood(Others::HardwareAndManageability), down, Outcome::SoftwareFlagStoodBesideBoth), - ( - Err(PhyRefusal::GrantNeverCame { held_by: 0x80, after_nanos: 1 }), - down, - Outcome::GrantNeverCame, - ), - (Err(PhyRefusal::MdiUnready { phy: 1, reg: 31, after_nanos: 1 }), down, Outcome::MdiUnready), - (Err(PhyRefusal::MdiError { phy: 2, reg: 2 }), down, Outcome::MdiError), - (Err(PhyRefusal::Identity { specific: 0, general: 0 }), down, Outcome::Identity), - (Err(PhyRefusal::NotThisRegisterMap), down, Outcome::NotThisRegisterMap), - ( - Err(PhyRefusal::MdiWaiting { phy: 1, reg: 2, after_nanos: 3 }), - down, - Outcome::MdiWaiting, - ), - ]; - // The block's base is pinned, not read off the table it is judging. - assert_eq!(Outcome::ALL[0].exit_code(), 64, "the block no longer starts at 64"); - let mut codes = Vec::new(); - for (phy, link, outcome) in outcomes { - assert_eq!(Outcome::of(phy, link), outcome, "{phy:?} {link:?}"); - let code = outcome.exit_code(); - assert!((64..128).contains(&code), "{outcome:?} exits {code}"); - assert_ne!(code, 101, "{outcome:?} exits the code a panicking netstack ends with"); - assert_eq!(Outcome::from_exit_code(code), Some(outcome)); - assert!(!codes.contains(&code), "{outcome:?} shares {code} with another outcome"); - codes.push(code); - } - assert_eq!(codes.len(), Outcome::ALL.len()); - assert_eq!(Outcome::from_exit_code(0), None); - assert_eq!(Outcome::from_exit_code(Outcome::ALL.len() as i32 + 64), None); - - // A refusal carries no link, whatever the part's `STATUS` was saying: the - // agent before this driver may have left one up. - for (phy, _, outcome) in outcomes.into_iter().filter(|(phy, ..)| phy.is_err()) { - assert_eq!(Outcome::of(phy, link(Speed::Mbps1000, true)), outcome, "{phy:?}"); - } -} - /// §4.5.2 arbitrates three bits of `EXTCNF_CTRL` and no more, so who stands /// beside another agent's software flag is decided by the other two and by /// nothing else in the word. @@ -1639,20 +1577,16 @@ fn every_reading_of_the_other_two_ownership_bits_names_who_stands_beside_the_fla } } -/// **The one question a probe boot on a machine with no console can answer.** /// Another software agent's flag is the refusal whose cause is another agent, /// so each reading of the two bits that can stand beside it is driven on the -/// part and the exit code it produces is asserted: one code each, all four -/// distinct. +/// part, and the refusal names who stood there. #[test] -fn each_agent_standing_beside_the_flag_has_its_own_exit_code() { - use toyos_phy::Outcome; - let mut codes = Vec::new(); +fn the_refusal_names_each_agent_standing_beside_the_flag() { for (seed, hardware, manageability, wanted) in [ - (70, false, false, Outcome::SoftwareFlagStood), - (71, true, false, Outcome::SoftwareFlagStoodBesideHardware), - (72, false, true, Outcome::SoftwareFlagStoodBesideManageability), - (73, true, true, Outcome::SoftwareFlagStoodBesideBoth), + (70, false, false, Others::Nobody), + (71, true, false, Others::Hardware), + (72, false, true, Others::Manageability), + (73, true, true, Others::HardwareAndManageability), ] { let nic = Nic::i219(seed); nic.mdio_flag_held_by_another_agent(); @@ -1666,12 +1600,12 @@ fn each_agent_standing_beside_the_flag_has_its_own_exit_code() { let phy = driver.brought_up().phy; assert!( - matches!(phy, Err(PhyRefusal::SoftwareFlagStood { .. })), + matches!(phy, Err(PhyRefusal::SoftwareFlagStood { beside, .. }) if beside == wanted), "{}", - nic.because(&format!("the bring-up answered {phy:?} on a flag another agent holds")) + nic.because(&format!("the bring-up answered {phy:?} with {wanted:?} beside the flag")) ); - // The part really did answer the bits this row is about, so the code - // below is about that reading and not about a model that lost one. + // The part really did answer the bits this row is about, so the + // refusal is about that reading and not about a model that lost one. assert_eq!( nic.peek(regs::EXTCNF_CTRL) & extcnf::OWNERSHIP, extcnf::MDIO_SW_OWNERSHIP @@ -1680,25 +1614,10 @@ fn each_agent_standing_beside_the_flag_has_its_own_exit_code() { "{}", nic.because("the part did not stand the agents this row names") ); - let outcome = Outcome::of(phy, Link::Down); - assert_eq!( - outcome, - wanted, - "{}", - nic.because("the exit code does not name which agent stood beside the flag") - ); - let code = outcome.exit_code(); - assert!( - !codes.contains(&code), - "{}", - nic.because(&format!("{outcome:?} shares exit code {code} with another reading")) - ); - codes.push(code); } - assert_eq!(codes.len(), 4); } -/// §4.5.2's arbitration moves while a driver waits on it, and what a probe +/// §4.5.2's arbitration moves while a driver waits on it, and what the log /// carries off a machine is the interface at the moment the wait ended — so the /// refusal names the *last* reading and not the one it started on. #[test] @@ -2122,7 +2041,6 @@ fn an_interconnect_in_transition_is_waited_out_and_never_written_over() { nic.because(&format!("it refused with {why:?}")) ); assert!(!nic.written().contains(®s::MDIC), "{}", nic.because("a command was written")); - assert_eq!(phy::Outcome::of(Err(why), Link::Down), phy::Outcome::MdiWaiting); // The interface is not kept: §4.5.2's release runs on this path too. assert_eq!(nic.peek(regs::EXTCNF_CTRL) & extcnf::MDIO_SW_OWNERSHIP, 0); } @@ -2501,7 +2419,7 @@ fn what_the_wake_and_the_full_reset_write() { assert_eq!(wake::phy_smbus_released(0x0013), 0x0012); } -// --- the PCH's own bits, host wake-up, the counts, and the lease probe --- +// --- the PCH's own bits, host wake-up and the counts --- /// The PCH's MAC is given `pch`'s three registers before its rings, every /// other bit of each carried, and the 82574 — whose datasheet this driver is @@ -2669,99 +2587,6 @@ fn the_driver_and_the_macs_statistics_count_the_same_frames() { assert_eq!(driver.wire().seen, 3); } -/// Every verdict the lease probe can exit with reads back to itself, and none -/// of them is a code a panicking netstack or an ordinary exit ends with. -#[test] -fn every_lease_verdict_has_one_exit_code_that_reads_back() { - use crate::lease::{Verdict, LEASED}; - use toyos_phy::Outcome; - assert_eq!(Verdict::Leased.exit_code(), LEASED); - let mut codes = vec![LEASED]; - for outcome in Outcome::ALL { - let verdict = Verdict::NotLeased(outcome); - assert_eq!(Verdict::from_exit_code(verdict.exit_code()), Some(verdict)); - codes.push(verdict.exit_code()); - } - assert_eq!(Verdict::from_exit_code(LEASED), Some(Verdict::Leased)); - codes.sort_unstable(); - codes.dedup(); - assert_eq!(codes.len(), Outcome::ALL.len() + 1, "two verdicts share a code"); - for foreign in [0, 1, 101, 139] { - assert_eq!(Verdict::from_exit_code(foreign), None, "{foreign}"); - } -} - -/// The report's lines spell and read back, and a file of them sums up to the -/// lease, the last counts and the exit — with a torn last line left out. -#[test] -fn a_lease_report_reads_back_as_it_was_written() { - use crate::lease::{self, Counts, Event, Line}; - use core::net::Ipv4Addr; - let lease = Event::Leased { - address: Ipv4Addr::new(192, 168, 1, 46), - prefix: 24, - server: Ipv4Addr::new(192, 168, 1, 1), - router: Some(Ipv4Addr::new(192, 168, 1, 1)), - }; - let counts = Counts { - sent: 4, - received: 9, - wire: Wire { sent: 4, received: 9, seen: 12, missed: 1, crc_errors: 0 }, - }; - let events = [ - Event::BroughtUp("the MAC and the PHY were reset together"), - Event::Link(Link::Up { speed: Speed::Mbps10, full_duplex: true }), - Event::Link(Link::Down), - lease, - Event::Leased { - address: Ipv4Addr::new(10, 0, 2, 15), - prefix: 24, - server: Ipv4Addr::new(10, 0, 2, 2), - router: None, - }, - Event::Counts(counts), - Event::Exit { code: lease::LEASED }, - ]; - let mut file = std::string::String::new(); - for (ms, event) in events.iter().enumerate() { - let line = Line { ms: ms as u64 * 100, event: *event }; - let text = line.to_string(); - assert_eq!(Line::parse(&text), Some(line), "{text}"); - file.push_str(&text); - file.push('\n'); - } - assert_eq!( - Line { ms: 300, event: lease }.to_string(), - "300 leased 192.168.1.46/24 from 192.168.1.1 router 192.168.1.1" - ); - let summary = lease::summary(&format!("{file}700 exit 6")).expect("a whole report"); - assert_eq!(summary.lease, Some((300, lease)), "the first lease is the one recorded"); - assert_eq!(summary.counts, Some(counts)); - assert_eq!(summary.exit, Some(lease::LEASED), "the torn line is not the exit"); - assert!(summary.held, "the file's last word on the lease is a lease"); - - // A lease that was lost after it landed is not held at the end, and one - // that came back after the loss is. - let lost = Line { ms: 800, event: Event::Lost }; - assert_eq!(lost.to_string(), "800 lost"); - assert_eq!(Line::parse("800 lost"), Some(lost)); - let dropped = lease::summary(&format!("{file}{lost}\n")).expect("a whole report"); - assert!(!dropped.held, "a lease lost inside the window was read as held"); - assert_eq!(dropped.lease, summary.lease, "the first lease is still the one recorded"); - let back = Line { ms: 900, event: lease }; - assert!(lease::summary(&format!("{file}{lost}\n{back}\n")).expect("a whole report").held); - - for bad in [ - "x link up\n", - "5 link up 10\n", - "5 leased 192.0.2.4 from 192.0.2.5 router none\n", - "5 exit\n", - "5 lost now\n", - ] { - assert!(lease::summary(bad).is_err(), "{bad:?}"); - } -} - /// Each part's table, and the one bit asking for a group sets in it. The /// 82574's is §10.2.5.21 with `RCTL.MO` = `00b`, bits 47:36 into 128 dwords; /// the PCH MAC's is 612523 §9.5.9.2.23, bits 47:38 into 32. The differential diff --git a/userland/netstack/src/dhcp.rs b/userland/netstack/src/dhcp.rs index 2f3273976b7..a5025042e28 100644 --- a/userland/netstack/src/dhcp.rs +++ b/userland/netstack/src/dhcp.rs @@ -80,15 +80,6 @@ impl Change { dhcpv4::Event::Deconfigured => Some(Self::Lost), } } - - /// The address, the router and the server that leased them, where this - /// change is a lease. - pub fn lease(&self) -> Option<(Ipv4Cidr, Option, Ipv4Address)> { - match self { - Self::Leased { address, router, server, .. } => Some((*address, *router, *server)), - Self::Lost => None, - } - } } /// The lease the interface holds: everything the server decided. diff --git a/userland/netstack/src/i219.rs b/userland/netstack/src/i219.rs index 7083811b94a..72968ae012a 100644 --- a/userland/netstack/src/i219.rs +++ b/userland/netstack/src/i219.rs @@ -212,7 +212,7 @@ fn granted(dev: &PciDev) -> Result<(Bar, Grant, Window), Opening> { /// What a bring-up says about itself: a sentence each for what it asked the /// I219's PHY before its reset and what that reset did, and one for the rest. -pub fn brought_up_words(brought_up: toyos_i219::BringUp) -> Vec { +fn brought_up_words(brought_up: toyos_i219::BringUp) -> Vec { let mut words = Vec::new(); match brought_up.woke { Some(Ok(woke)) => words.push(format!("before the reset the PHY was asked: {woke}")), @@ -282,22 +282,17 @@ impl Nic { self.driver.borrow().accept_multicast(group); } - /// What the bring-up found. - pub fn brought_up(&self) -> toyos_i219::BringUp { - self.driver.borrow().brought_up() - } - pub fn link(&self) -> toyos_i219::Link { self.driver.borrow().link() } /// What the driver and the MAC have counted so far, the transmit ring /// reclaimed first so a frame that has left is counted as sent. - pub fn counts(&self) -> toyos_i219::lease::Counts { + pub fn counts(&self) -> (toyos_i219::Counters, toyos_i219::Wire) { let mut driver = self.driver.borrow_mut(); driver.reclaim(); let wire = driver.wire(); - toyos_i219::lease::Counts::of(driver.counters(), wire) + (driver.counters(), wire) } /// Take the interrupt, acknowledge its causes and refresh the link — and diff --git a/userland/netstack/src/main.rs b/userland/netstack/src/main.rs index fd74c9de52b..0bd5fcc9fc9 100644 --- a/userland/netstack/src/main.rs +++ b/userland/netstack/src/main.rs @@ -11,7 +11,6 @@ mod dhcp; mod i219; mod listen; mod mdns; -mod report; mod resolve; mod virtio_net; @@ -33,37 +32,9 @@ const CARDS: [(PciId, fn(toyos::PciDev) -> Card); 3] = [ (PciId { vendor: 0x1af4, device: 0x1041 }, Card::virtio), ]; -/// The probe under which this process brings the card up and serves exactly as -/// it always does for [`LEASE_WINDOW`], leaves what happened on the log volume -/// one durable line at a time (`report::PATH`), and then ends with -/// `toyos_i219::lease::Verdict`'s code: whether a leased address is held when -/// the window ends, and where none is, what the bring-up and the link said. -/// -/// **A lease is a frame out and a frame in, answered by a server this machine -/// does not control**; the lines beside it say what the driver and the MAC -/// counted each way, and so which half went missing on a boot that got none. -/// The argument comes from the `[programs.netstack] args` row of a boot config, -/// and nothing a shipped machine runs carries it. -const EXIT_WITH_LEASE: &str = "--exit-with-lease"; - -/// How long [`EXIT_WITH_LEASE`] serves before it ends, counted from this -/// process's start. -/// -/// **It ends inside the job that holds its boot open**: `test_rs_lan_hold` -/// sleeps `toyos_tco::LEASE_BOUND_MS` from a start after this process's, so -/// the exit record and the report's last line land before the runner reboots, -/// with two seconds to spare. Every moment of it after the lease is a moment -/// the machine answers the host's ping at the leased address. -const LEASE_WINDOW: Duration = Duration::from_millis(toyos_tco::LEASE_BOUND_MS - 2_000); - -fn armed(actuator: &str) -> bool { - std::env::args().any(|arg| arg == actuator) -} - use toyos::endow; use toyos::Pipe; use toyos_abi::syscall::PciId; -use toyos_i219::lease::{Event, Verdict}; use toyos_i219::Part; use toyos_inspect::Snapshot; use virtio_net::VirtioNet; @@ -126,18 +97,6 @@ impl Card { } } - /// The Intel driver, for [`EXIT_WITH_LEASE`]: the bring-up it reports - /// beside the lease is that driver's. - fn intel_driver(&self) -> &i219::Nic { - match self { - Self::Virtio(_) => Self::undrivable(format_args!( - "{EXIT_WITH_LEASE} reports the Intel driver's bring-up beside the lease, which \ - this card has not" - )), - Self::Intel(nic) => nic, - } - } - /// **The record has to be taken, not merely noticed.** A claim reads ready /// while it holds an undrained interrupt, so a pass that saw the token and /// left it would find the same one on the next `wait` and every one after @@ -203,14 +162,14 @@ impl Card { snap.put("link.duplex", if full_duplex { "full" } else { "half" }); } } - let counts = nic.counts(); - snap.put("descriptors.sent", counts.sent); - snap.put("descriptors.received", counts.received); - snap.put("wire.sent", counts.wire.sent); - snap.put("wire.received", counts.wire.received); - snap.put("wire.seen", counts.wire.seen); - snap.put("errors.missed", counts.wire.missed); - snap.put("errors.crc", counts.wire.crc_errors); + let (counters, wire) = nic.counts(); + snap.put("descriptors.sent", counters.sent); + snap.put("descriptors.received", counters.received); + snap.put("wire.sent", wire.sent); + snap.put("wire.received", wire.received); + snap.put("wire.seen", wire.seen); + snap.put("errors.missed", wire.missed); + snap.put("errors.crc", wire.crc_errors); } fn tx(&self, len: usize, fill: impl FnOnce(&mut [u8]) -> R) -> R { @@ -1684,28 +1643,7 @@ const _: () = assert!( "a snapshot is one frame" ); -/// [`EXIT_WITH_LEASE`]'s last two lines, and the exit they announce. `held` is -/// whether a leased address is held now, at the end of the window — a lease -/// that landed and was then lost inside it is not one. -/// -/// **The card is taken and dropped before the exit**, which runs no destructor: -/// dropping the driver is what lets the function go. -fn end_the_lease_probe(report: &report::Report, card: Card, held: bool) -> ! { - let nic = card.intel_driver(); - report.say(Event::Counts(nic.counts())); - let verdict = if held { - Verdict::Leased - } else { - Verdict::NotLeased(toyos_i219::phy::Outcome::of(nic.brought_up().phy, nic.link())) - }; - drop(card); - let code = verdict.exit_code(); - report.say(Event::Exit { code }); - std::process::exit(code) -} - fn main() { - let started = Instant::now(); // **The order this used to have was load-bearing and is now moot.** The // device was claimed before the name was published, because a client that // connected while netstack was still in `DmaNic::open` reached a listener owned @@ -1725,15 +1663,6 @@ fn main() { let acceptor = endow::acceptor("netstack") .expect("the manifest declares this program serves `netstack`"); let nic = open(claim); - let report = armed(EXIT_WITH_LEASE).then(|| { - let report = report::Report::open(started); - let intel = nic.intel_driver(); - for words in i219::brought_up_words(intel.brought_up()) { - report.say(Event::BroughtUp(&words)); - } - report.say(Event::Link(intel.link())); - report - }); // The link as the card came up with it, which the first change a pass // reports is measured against. Virtio reports no link changes at all. let mut link_up = match &nic { @@ -1795,9 +1724,6 @@ fn main() { // Before `iface.poll`, because it is what makes the interrupt taken and // what gives a driver with a per-pass receive budget that budget back. if let Some(link) = device.nic.begin_pass() { - if let Some(report) = &report { - report.say(Event::Link(link)); - } // Down to up only, and only with no lease held: a speed change is // no new network, and a bound lease is kept across a flap rather // than given up — `dhcp::restart`'s own header. Before the poll @@ -1816,21 +1742,6 @@ fn main() { // answered before it was applied would be answered on a machine that is // on no network. let change = dhcp::Change::of(socket_set.get_mut::(dhcp_handle)); - let held = dhcp.leased(); - if let (Some(report), Some(change)) = (&report, change.as_ref()) { - match change.lease() { - Some((address, router, server)) => report.say(Event::Leased { - address: address.address(), - prefix: address.prefix_len(), - server, - router, - }), - // Only a lease that was held is lost: the client reports the - // same on its way to a first one. - None if held => report.say(Event::Lost), - None => {} - } - } if dhcp.pass(change, &mut iface, &mut daemon.resolver) { say!( "netstack: ready, at most {max_piped} piped connections \ @@ -1910,18 +1821,6 @@ fn main() { Some(left) => timeout.min(left.as_nanos() as u64), None => timeout, }; - // The probe's window is a wake of its own: an idle machine would - // otherwise sleep through the moment it owes its answer. - let timeout = match &report { - Some(report) => { - let left = LEASE_WINDOW.saturating_sub(started.elapsed()); - if left.is_zero() { - end_the_lease_probe(report, device.nic, dhcp.leased()); - } - timeout.min(left.as_nanos() as u64) - } - None => timeout, - }; // A client that connects and then says nothing wakes nothing, so the // deadline that removes it has to be a wake in its own right: without // this netstack can sit in `wait` forever with `pending` full of clients diff --git a/userland/netstack/src/report.rs b/userland/netstack/src/report.rs deleted file mode 100644 index b8ef763ee86..00000000000 --- a/userland/netstack/src/report.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! `crate::EXIT_WITH_LEASE`'s report: `toyos_i219::lease` lines appended to a -//! file on the log volume, each flushed to the device before the process goes -//! on. -//! -//! **`sync_all` is the whole claim**: a line this returned from is on the -//! stick whatever the machine does next. A -//! line that cannot be made durable ends the process: a report with a hole in -//! it says the wrong thing. - -use std::cell::RefCell; -use std::fs::{File, OpenOptions}; -use std::io::Write; -use std::time::Instant; - -use toyos_i219::lease::{Event, Line}; - -/// Beside `/system/bin/logkeeper`'s files and not one of them: -/// `toyos_wallclock::classify` is what logkeeper may delete and what the metal -/// loop reads as the boot's log, and it does not recognise this name. -pub const PATH: &str = "/log/lease.txt"; - -pub struct Report { - file: RefCell, - /// When this process started, which every line's milliseconds count from. - began: Instant, -} - -impl Report { - /// Appending, so a second boot off one flash adds to the first's report - /// instead of replacing the only record of how it went. - pub fn open(began: Instant) -> Self { - let file = OpenOptions::new() - .append(true) - .create(true) - .open(PATH) - .unwrap_or_else(|why| panic!("netstack: {PATH} would not open: {why}")); - Self { file: RefCell::new(file), began } - } - - pub fn say(&self, event: Event<'_>) { - let line = Line { ms: self.began.elapsed().as_millis() as u64, event }; - let text = format!("{line}\n"); - let mut file = self.file.borrow_mut(); - file.write_all(text.as_bytes()) - .and_then(|()| file.sync_all()) - .unwrap_or_else(|why| panic!("netstack: `{line}` is not on {PATH}: {why}")); - } -}