Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
64 changes: 64 additions & 0 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ members = [
"userland/terminal",
"userland/test-runner",
"userland/toybox",
"userland/toyfetch",
"userland/toyos-font",
"userland/toyos-pci-claim",
"userland/toyos-window",
Expand Down Expand Up @@ -261,6 +262,7 @@ loom = { git = "https://github.com/ToyOSOrg/loom", branch = "toyos" }
toyos-abi = { path = "toyos-abi" }
toyos = { path = "toyos" }
toyos-window = { path = "userland/toyos-window" }
toyos-osrelease = { path = "toyos-osrelease" }
getrandom = { git = "https://github.com/ToyOSOrg/getrandom", branch = "toyos-0.2-sdk-0.12", package = "getrandom" }
getrandom_03 = { git = "https://github.com/ToyOSOrg/getrandom", branch = "toyos-0.3-sdk-0.12", package = "getrandom" }
getrandom_04 = { git = "https://github.com/ToyOSOrg/getrandom", branch = "toyos-0.4", package = "getrandom" }
Expand All @@ -274,6 +276,7 @@ cpal = { git = "https://github.com/ToyOSOrg/cpal", branch = "toyos-0.18.0" }
softbuffer = { git = "https://github.com/ToyOSOrg/softbuffer", branch = "toyos-0.4.8" }
winit = { git = "https://github.com/ToyOSOrg/winit", branch = "toyos-0.30.13" }
ring = { git = "https://github.com/ToyOSOrg/ring", branch = "toyos-0.17.14" }
sysinfo = { git = "https://github.com/ToyOSOrg/sysinfo", branch = "toyos-0.39.6" }

# The one profile every guest binary is built with.
# Optimised, because an unoptimised guest mismeasures everything under TCG;
Expand Down
25 changes: 25 additions & 0 deletions issues/a-process-on-aarch64-cannot-learn-its-cpu-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
status: open
kind: track
opened: 2026-10-10
---

# A process on AArch64 cannot learn its CPU model

On x86-64 a process reads its CPU's vendor and brand string with CPUID,
which the kernel does not fault, and ToyOSOrg/sysinfo's ToyOS backend
answers `Cpu::brand` and `Cpu::vendor_id` from it. On AArch64 the model is
`MIDR_EL1`, which traps from EL0, and an unserved EL0 sync exception ends the
process (`kernel/src/arch/aarch64/trap.rs`); the kernel publishes it nowhere.
So the backend answers empty strings there, and `toyfetch` says "model not
reported".

Built: nothing. The answer is ambient, as the `SYS_SYSINFO` header is: a
CPU's identity authorizes nothing.

## Exit condition

A process on AArch64 reads each CPU's `MIDR_EL1` (implementer, part,
variant, revision) without a capability, the fork's AArch64 arm answers
`vendor_id` and `brand` from it, and `toyfetch` on the `virt` machine prints
a model.
11 changes: 6 additions & 5 deletions issues/the-tree-says-who-uses-each-thing.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,9 @@ apps/ CLAUDE.md (sdk/ and registries only; Linux under Wayland, macOS, Windo
calc editor files paint snake doom/ (doomgeneric/ DOOM1.WAD soundfont licences);
toyfetch when built
sdk/ CLAUDE.md (identity, sysroot, publication; std links abi and toyos)
abi/ toyos/ std/ keymap/ font/ window/ filepicker/ libc/ (arch/)
abi/ toyos/ std/ keymap/ font/ window/ filepicker/ osrelease/ libc/ (arch/)
lib/ acpi bcachefs blackbox blockhold blockring bootmap elf elide fat32 gpt hda i219 inspect
logstream manifest osrelease quiesce rootimage swap symbols tco tmpdir tsc untrusted
logstream manifest quiesce rootimage swap symbols tco tmpdir tsc untrusted
update userbound wallclock xhci
share/ mirrors /system/share: fonts/ (+ OFL) icons/ (+ MIT)
images/ system.toml console.toml diag.toml
Expand All @@ -156,8 +156,9 @@ Where this differs from the tree the owner adopted:
ruling `files` stops using it, which leaves the compositor its one user
(rule step 4).
- `apps/` names toyfetch, by the Apps ruling.
- `lib/osrelease` is `toyos-osrelease`, which #722 added after the design;
the build, `libc` and the supervisor use it (rule step 5).
- `sdk/osrelease` is `toyos-osrelease`, which #722 added after the design;
the sysinfo fork, outside this repository, uses it from crates.io (rule
step 3).
- `lib/tsc` is `toyos-tsc`, which #721 added after the design; the kernel
and the loader use it (rule step 5).
- `sdk/std/` is std's ToyOS backend, which left the `rust` fork after the
Expand Down Expand Up @@ -186,7 +187,7 @@ tree's top level: `.cargo`, `.claude`, `.github`, `.gitignore`,
3. **One workspace.** Landed in #746.
4. **std off repository paths:** `issues/std-names-the-sdk-crates-by-path.md`.
**Exit:** that file's.
5. **`sdk/`:** abi, toyos, keymap, font, window, filepicker and libc (with
5. **`sdk/`:** abi, toyos, keymap, font, window, filepicker, osrelease and libc (with
`tests/libc-arch` as `sdk/libc/arch/`); `src/sysroot.rs`,
`src/sdkversion.rs` and `src/release.rs` read the directory instead of a
list. A branch that bumps the `rust` gitlink merges the fork change with
Expand Down
13 changes: 6 additions & 7 deletions issues/toyos-is-a-normal-target.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,18 @@ opened: 2026-09-03
A clean clone of any third-party program that targets ToyOS cannot resolve its
dependencies on Linux or macOS, because cargo resolves every platform's
dependencies and the ToyOS crates the forks name — `toyos-abi`, `toyos`,
`toyos-window` — are on no registry. A git dependency on the monorepo is not the
answer: it clones the `rust` submodule. So the five SDK crates go on crates.io
and stay there. The ABI they carry is unstable by owner ruling: a built program
`toyos-window`, `toyos-osrelease` — are on no registry. A git dependency on the
monorepo is not the answer: it clones the `rust` submodule. So the SDK crates,
`src/sdkversion.rs` `PUBLISHED`, go on crates.io and stay there. The ABI they carry is unstable by owner ruling: a built program
that breaks, breaks.

Stages, in order:

1. **Done.** `toyos-abi`, `toyos-keymap`, `toyos-font`, `toyos` and
`toyos-window` carry a description and a repository, and are published by
`.github/workflows/publish.yml`.
2. **The owner's.** `CARGO_REGISTRY_TOKEN` as a repository secret, then the
first publish. Until it is there the publish job fails by name on every
landing, which is the intended noise.
`.github/workflows/publish.yml` through crates.io trusted publishing.
2. **Done.** `toyos-osrelease`, which the sysinfo fork names by version, is
on crates.io, and names `publish.yml` its trusted publisher.
3. **The forks.** softbuffer names
`toyos-window` and sits on the v0.4.8 release, and raw-window-handle sits on
v0.6.2, so nothing the window path goes through is based on a master any
Expand Down
4 changes: 3 additions & 1 deletion src/sdkversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub const PUBLISHED: &[Crate] = &[
Crate { name: "toyos-font", dir: "userland/toyos-font" },
Crate { name: "toyos", dir: "toyos" },
Crate { name: "toyos-window", dir: "userland/toyos-window" },
Crate { name: "toyos-osrelease", dir: "toyos-osrelease" },
];

/// One crate at this tree: the version crates.io has or is owed, whether it is
Expand Down Expand Up @@ -268,7 +269,8 @@ mod tests {
std::fs::write(tmp.join("fork/src/lib.rs"), "").unwrap();
let fork = format!(
"[package]\nname = \"fork\"\nversion = \"0.0.0\"\nedition = \"2021\"\n\n[dependencies]\n\
toyos-abi = \">=0.12, <1\"\ntoyos = \">=0.13, <1\"\ntoyos-window = \">=0.15, <1\"\n\n\
toyos-abi = \">=0.12, <1\"\ntoyos = \">=0.13, <1\"\ntoyos-window = \">=0.15, <1\"\n\
toyos-osrelease = \">=0.1, <1\"\n\n\
[patch.crates-io]\n{patch}"
);
std::fs::write(tmp.join("fork/Cargo.toml"), fork).unwrap();
Expand Down
10 changes: 7 additions & 3 deletions system.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ devices = ["pci:1af4:1041"]
[programs.sshserver]
service = true
receives = ["netstack"]
starts = ["toybox", "shell", "terminal", "doom", "snake", "calc", "editor", "paint", "files", "host", "inspect", "pkg", "proctest", "sshserver", "swap", "update", "/apps"]
starts = ["toybox", "shell", "terminal", "doom", "snake", "calc", "editor", "paint", "files", "host", "inspect", "pkg", "proctest", "sshserver", "swap", "toyfetch", "update", "/apps"]
login = true

# `swap` is the authority to replace a running service's binary; the build
Expand Down Expand Up @@ -143,7 +143,7 @@ starts = ["shell"]

[programs.shell]
receives = ["surface"]
starts = ["toybox", "shell", "terminal", "doom", "snake", "calc", "editor", "paint", "files", "host", "inspect", "pkg", "proctest", "sshserver", "swap", "update", "/apps"]
starts = ["toybox", "shell", "terminal", "doom", "snake", "calc", "editor", "paint", "files", "host", "inspect", "pkg", "proctest", "sshserver", "swap", "toyfetch", "update", "/apps"]

[programs.doom]
receives = ["compositor", "soundserver"]
Expand All @@ -156,6 +156,10 @@ receives = ["compositor", "filepicker"]
[programs.calc]
receives = ["compositor"]

# toyfetch reads only what every process may: the machine's ambient header,
# CPUID and `/system/etc/os-release`.
[programs.toyfetch]

[programs.editor]
receives = ["compositor", "filepicker"]

Expand All @@ -178,7 +182,7 @@ receives = ["compositor", "filepicker"]
[programs.toybox]
receives = ["compositor", "soundserver", "surface", "power"]
syscap = ["roster"]
starts = ["toybox", "shell", "terminal", "doom", "snake", "calc", "editor", "paint", "files", "host", "inspect", "pkg", "proctest", "sshserver", "swap", "update", "/apps"]
starts = ["toybox", "shell", "terminal", "doom", "snake", "calc", "editor", "paint", "files", "host", "inspect", "pkg", "proctest", "sshserver", "swap", "toyfetch", "update", "/apps"]

# Symlinks created on ROOT at build time.
# Keys are ROOT-relative; targets are the guest paths they resolve to.
Expand Down
3 changes: 3 additions & 0 deletions tests/testcases/system.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ receives = ["soundserver"]
# `disk_backtrace` names its child's crash record with it.
[programs.symbolize]

# `toyfetch_reports` runs it, holding nothing, as the image's row does.
[programs.toyfetch]

# One xHCI controller driven from userland: `usbd_spare` claims the controller
# the kernel was told to leave alone (`xhci-leave=`, `pci:1b36:000d` on the
# guest and the Type-C controller on the T14), and starts and ends this itself,
Expand Down
64 changes: 64 additions & 0 deletions tests/toyos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,11 @@ const MACHINE_TESTS: &[&str] = &[
// kernel's driver reading a report a device delivered over xHCI has no host
// build, and the T14 binds no USB keyboard.
"usb_keyboard_rollover",
// What `toyfetch` reads through the `sysinfo` fork's ToyOS backend: the
// backend calls `SYS_SYSINFO` and reads ROOT, which no host has, and the
// CPU count is held to the `-smp` this boot gave QEMU, a count the
// kernel did not make.
"toyfetch_reports",
];

/// **The metal profile**: which registrations run on the ThinkPad T14, what
Expand Down Expand Up @@ -3711,6 +3716,7 @@ fn run_machine_test(name: &str, test_config: &Path) -> Result<(), String> {
"update_writes_the_idle_slot_through_the_block_service" => update_writes_the_idle_slot_through_the_block_service(),
"usbd_drives_the_spare" => usbd_drives_the_spare(test_config),
"usb_keyboard_rollover" => usb_keyboard_rollover(test_config),
"toyfetch_reports" => toyfetch_reports(test_config),
other => Err(format!("unknown machine test {other}")),
}
}
Expand Down Expand Up @@ -3767,6 +3773,64 @@ fn job_said(qemu: &mut QemuInstance, command: &str) -> Result<String, String> {
Ok(result.stdout)
}

/// `toyfetch` names the build the image's os-release does, the CPUs this boot
/// gave QEMU, and the memory total `free` reads off the same ambient header.
fn toyfetch_reports(test_config: &Path) -> Result<(), String> {
const CPUS: u32 = 3;
let mut qemu = QemuInstance::boot_with_options(test_config, &[], &[], BootOptions { smp: CPUS, ..Default::default() });
let (_, bytes) = toyos_build::image::root_file_on(qemu.boot_image(), toyos_osrelease::PATH)?;
let release = toyos_osrelease::parse(&bytes).map_err(|why| format!("the image's {}: {why:?}", toyos_osrelease::PATH))?;
// `free`'s second line is the total, used and free mebibytes.
let free = job_said(&mut qemu, "free")?;
let total: u64 = free
.lines()
.find_map(|line| line.trim().strip_suffix('M')?.split_whitespace().next()?.strip_suffix('M')?.parse().ok())
.ok_or_else(|| format!("`free` said no total:\n{free}"))?;

// The console carries an escape as the four characters `\x1b`.
let said = job_said(&mut qemu, "toyfetch")?.replace('\x1b', "\\x1b");
// The text a terminal shows: every SGR sequence taken out.
let mut shown = String::new();
let mut rest = said.as_str();
while let Some(at) = rest.find("\\x1b[") {
shown.push_str(&rest[..at]);
let end = rest[at..].find('m').ok_or_else(|| format!("an SGR sequence that never ends:\n{said}"))?;
rest = &rest[at + end + 1..];
}
shown.push_str(rest);
let field = |label: &str| -> Result<String, String> {
let at = format!("{label}: ");
let mut values = shown.lines().filter_map(|line| Some(line[line.find(&at)? + at.len()..].to_string()));
match (values.next(), values.next()) {
(Some(value), None) => Ok(value),
_ => Err(format!("toyfetch said no single {label} line:\n{shown}")),
}
};

let wanted = [
("OS", release.pretty_name().to_string()),
("Kernel", format!("{} {}", toyos_osrelease::NAME, release.uname_release())),
("Arch", release.arch.machine().to_string()),
];
for (label, value) in wanted {
if field(label)? != value {
return Err(format!("toyfetch's {label} is not {value:?}:\n{shown}"));
}
}
// x86-64 reads its model off CPUID; QEMU names one.
let cpu = field("CPU")?;
if !cpu.ends_with(&format!(" ({CPUS})")) || cpu.starts_with("model not reported") {
return Err(format!("toyfetch's CPU is {cpu:?}, on a boot QEMU gave {CPUS} CPUs:\n{shown}"));
}
let memory = field("Memory")?;
if !memory.ends_with(&format!(" / {total} MiB")) {
return Err(format!("toyfetch's Memory is {memory:?}, and `free` says {total}M in all:\n{shown}"));
}
field("Uptime")?;
eprintln!(" [toyfetch] OS {}, CPU {cpu}, Memory {memory}", field("OS")?);
Ok(())
}

/// A machine with one NVMe disk and no USB controller boots the image off
/// that disk, and what one boot wrote to `/home` and said into `/log` the next
/// boot reads back: a file under a name of this run's, line for line, and a
Expand Down
5 changes: 3 additions & 2 deletions toyos-osrelease/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[package]
name = "toyos-osrelease"
description = "The one definition of /system/etc/os-release: which build an image is, written by the build and read by the supervisor and libc's uname (no_std, no alloc)."
version = "0.1.0"
description = "The one definition of /system/etc/os-release: which build an image is, written by the build and read by the supervisor, libc's uname and the sysinfo fork (no_std, no alloc)."
version = "0.1.0" # never bumped; crates.io's is assigned on publish (src/sdkversion.rs)
edition = "2021"
license = "MIT OR Apache-2.0"
repository = "https://github.com/ToyOSOrg/ToyOS"
Loading
Loading