Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e14376e
AArch64 stage 4 on one CPU: the kernel's own tables, the GICv3 and th…
Japabu Sep 28, 2026
57382f7
Merge origin/main (e3a1cdc8) into wt/toyos-arm64
Japabu Sep 28, 2026
9b7ed0c
AArch64 stage 4: the GIC leaves the entry, and the guest tests judge …
Japabu Sep 28, 2026
991fc3e
Merge origin/main (89dd9d2b) into wt/toyos-arm64
Japabu Sep 29, 2026
d2e1439
AArch64 stage 4: one KernelHw, the timer floor, and SYS_DEBUG refusals
Japabu Sep 29, 2026
b4a7051
AArch64 stage 4: guest tests for the FP switch, the first entry, the …
Japabu Sep 29, 2026
27f08d4
Merge origin/main (3d902477) into wt/toyos-arm64
Japabu Sep 29, 2026
82854a5
Merge remote-tracking branch 'origin/main' into wt/toyos-arm64
Japabu Sep 29, 2026
1286115
AArch64 stage 4, round 4: one invalidation per unmap, the floor read …
Japabu Sep 29, 2026
4256e5a
Drop timer-floor's timing verdict, keep only the value relation
Japabu Sep 29, 2026
8cda9b2
timer-floor: verdict off the counter arm_within set CVAL from, not a …
Japabu Sep 29, 2026
0751537
Discard rearm's arm_ticks return: the match arm never needed it
Japabu Sep 29, 2026
b260578
Merge origin/main (7e151819) into wt/toyos-arm64
Japabu Sep 29, 2026
b0db9fb
Merge origin/main (4de5ecdb) into wt/toyos-arm64
Japabu Sep 29, 2026
299d87a
Merge remote-tracking branch 'origin/main' into wt/toyos-arm64
Japabu Sep 29, 2026
5792355
PR #589 round 7: abuse_readonly_copyout runs on the AArch64 job case
Japabu Sep 29, 2026
33c650c
Merge remote-tracking branch 'origin/main' into wt/toyos-arm64
Japabu Sep 29, 2026
6475015
PR #589 round 8: virt_job names a dying job's code; one test-build setup
Japabu Sep 29, 2026
20ded4d
PR #589 round 8: revert virt_job's early exit on a non-zero job code
Japabu Sep 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ members = [
"toyos-elide",
"toyos-fat32",
"toyos-fat32-check",
"toyos-gicv3",
"toyos-gpt",
"toyos-hda",
"toyos-i219",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pointing here:
`_mm_sfence` after writing a write-combining framebuffer. Userland has no
portable way to say "drain my stores to the scanout"; the SDK (`toyos/src`)
owes one, and it is also only changed under an ABI brief.
- Twenty guest probes in `tests/toyos-rust-tests/src/bin/`, whose subject
- Guest probes in `tests/toyos-rust-tests/src/bin/`, whose subject
is an x86 instruction (`rdgsbase`, `fxsave64`, `int1`, x87 control words)
or the raw `syscall` gate with arguments no SDK call will pass. They run in
the x86-64 suite, which is the only suite until the harness gains its arch
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
status: open
kind: defect
opened: 2026-09-29
---

# The read-only copy-out test forms `&mut` over pages nothing may write

`tests/toyos-rust-tests/src/bin/abuse_readonly_copyout.rs` names its target to
`syscall::read` and `syscall::process_stats` through `target()` and a cast
`&mut *(addr as *mut ProcessStats)`, because `toyos_abi::syscall`'s typed
wrappers take `&mut [u8]` and `&mut ProcessStats` and no wrapper takes a raw
address. The `&mut` covers a read-only anonymous map, the binary's own `.text`
and the clock page, and is never written through, so the test rests on the
compiler inventing no store through it, not on a language guarantee.
`tls_dtv_race` forms the same reference.

Owner: `toyos-abi`'s syscall wrappers, which would need a raw-address entry
for a caller that names memory it may not hold a `&mut` to; that is an ABI
change and is not this test's to make.

**Exit condition**: the test issues its calls through a wrapper that takes an
address, and `rg 'from_raw_parts_mut|&mut \*\(' tests/toyos-rust-tests/src/bin/abuse_readonly_copyout.rs`
finds nothing.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
status: open
kind: defect
opened: 2026-09-29
---

# The x86-64 toybox ships two applets that only panic

`userland/toybox/src/main.rs` puts `fp_isolation` and `first_entry` in one
`commands!` list for every architecture, so the x86-64 toybox that ships in
every image answers both names, and `userland/toybox/src/arch/x86_64.rs`'s
body for each is a `panic!` naming where x86-64's probe lives or is owed
(`test_rs_fpu_isolation`,
`issues/isolation/a-new-x86-thread-enters-ring-3-holding-kernel-register-values.md`).
A shipping binary carries two commands whose only behaviour is to die.

**Exit condition**: the x86-64 toybox answers neither name with a panic —
each is left out of its `commands!`, or runs a probe of x86-64's own.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
status: open
kind: defect
opened: 2026-09-28
---

# A new x86-64 thread enters Ring 3 holding the kernel's register values

`kernel/src/arch/x86_64/entry.rs`'s `process_start` and `thread_start` call
`sched::driver::trampoline_entry`, restore only `r12`–`r14` and the FP state,
and `iretq`: every other general register — `rax`–`rdx`, `rsi`, `rbp`,
`r8`–`r11`, `r15`, and `rdi` in a process's case — reaches the thread's first
instruction holding whatever the kernel left there, kernel stack and heap
addresses among them. A thread reads the kernel's layout off its own
registers.

**Exit condition**: a fresh thread's first instruction sees zero in every
general register but its stack pointer and its argument, and a guest test that
reads them at `_start` says so.
23 changes: 23 additions & 0 deletions issues/isolation/aarch64-el1-runs-without-pan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
status: open
kind: defect
opened: 2026-09-29
---

# AArch64's EL1 runs without PAN

`kernel/src/arch/aarch64/control_regs.rs`'s `SCTLR` leaves `SPAN` set, so an
exception taken to EL1 leaves `PSTATE.PAN` as it was, and nothing writes
`PSTATE.PAN`: EL1 can load and store through any EL0 mapping. x86-64 runs
with SMAP (`kernel/src/arch/x86_64/control_regs.rs`), so a kernel bug that
dereferences a user pointer directly faults there and not here.

The kernel reaches user memory through the direct map (`kernel/src/user_ptr.rs`)
and never through a user address, so PAN costs no
unprivileged-access instruction; what it needs is FEAT_PAN in the
declaration's check and `SPAN` clear.

**Exit condition**: `SCTLR_EL1.SPAN` is clear and `PSTATE.PAN` set at EL1 on
every CPU, `control_regs::check` refuses a CPU without FEAT_PAN, and a guest
test whose kernel reads a user address directly under `test-actuators`
takes a permission fault.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
status: open
kind: defect
opened: 2026-09-29
---

# An AArch64 crash report reads through any user leaf

`kernel/src/arch/aarch64/paging.rs`'s `read_user_word`, which the report
of an EL0 fault calls with the faulting thread's own `x29` to walk its
frames (`trap.rs`'s `user_backtrace`), reads the frame any valid user leaf
names through the direct map. The direct map holds memory and nothing
else, so a leaf naming a device's registers — a claimed function's BAR,
once the port's stage 6 maps one into a process — names an address the
direct map does not hold, and the report's read of it is an EL1 data abort:
a user fault with `x29` pointed into its own BAR ends the machine.

Two more readers take the same leaf to the direct map:

- **Every syscall's user copy.** `AddressSpace::leaf` answers a Device
leaf as it answers a Normal one, for a `Write` too where the leaf is
EL0 read-write, and `kernel/src/user_ptr.rs` copies through
`DirectMap::from_phys` of what it answered: `translate_now`, `object_run`
(`copy_in`, `copy_out`) and `View::pieces` (`UserBytes::read_at`,
`UserBytesMut::write_at` and their kin). A `read(fd, bar, n)` is an EL1
abort on an address the direct map does not hold, or, where `map_mmio`
holds it Device-nGnRE, an alignment fault on `memcpy`'s unaligned access:
a kernel panic a process chose.
- **The fault dump.** `kernel/src/process.rs`'s `dump_crash_diagnostics`,
which AArch64's `trap.rs` calls on an EL0 fault, reads the words around
the fault address and the faulting PC through `translate` and the direct
map, the same way.

Nothing maps a BAR into an AArch64 process yet — `arch::msi_message`
refuses there, so pcidev refuses every hand-over — so this is latent until
stage 6 of `issues/kernel/toyos-runs-on-arm64.md`.

**Exit condition**: `read_user_word`, `leaf` and the fault dump's
`translate` answer only a leaf of the memory type the direct map holds, and
guest tests see a process end and the kernel live when it faults with `x29`
inside a mapped BAR, faults at an address inside one, and names one as a
`read`'s buffer — the last refused with `BadAddress`.
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ vector delivered to an xAPIC id. A GICv3 message names an LPI through the ITS
(a 32-bit event id, translated to an INTID above 8191) and a redistributor,
and neither fits a `u8`.

Owned by stage 4 of `issues/kernel/toyos-runs-on-arm64.md`, which brings up
the GIC and its ITS.
Owned by stage 6 of `issues/kernel/toyos-runs-on-arm64.md`, which brings up
the GICv3 ITS for the claimed functions its SMMUv3 translates.

**Exit condition**: a PCI function's interrupt is programmed from an
arch-provided message (address and data, as `arch::msi` already provides the
Expand Down
19 changes: 19 additions & 0 deletions issues/kernel/portable-kernel-code-names-the-tsc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
status: open
kind: defect
opened: 2026-09-29
---

# Portable kernel code names the TSC

On AArch64 the CPU's counter is the generic timer's `CNTVCT_EL0`, and the
portable kernel still calls it the TSC: `kernel/src/clock.rs`'s
`tsc_deadline`, `TSC_BOOT` and `TSC_PERIOD_FS`, `kernel/src/deadline.rs`'s
`AT_TSC`, and the xHCI driver's, `hardlockup`'s and `panic_reboot`'s waits
and comments read it by that name. `clock::counter_ticks`, which the AArch64
timer reads, is renamed; the rest reads as x86-64's on both machines.
`issues/kernel/the-boot-timing-handoff-is-named-for-the-tsc.md` is the ABI's
half of the same name.

**Exit condition**: no item or comment outside `kernel/src/arch/x86_64/`
names the TSC for the counter `crate::arch::cpu::counter` reads.
20 changes: 0 additions & 20 deletions issues/kernel/the-saved-kernel-context-names-x86-registers.md

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
status: open
kind: defect
opened: 2026-09-28
---

# The x86-64 address space keeps a page map nothing fills

`kernel/src/arch/x86_64/paging.rs`'s `AddressSpace` carries
`pages: HashMap<u64, PhysPage>`, documented as the user pages it frees on
drop, and `unmap` removes from it; nothing anywhere inserts into it, so it is
always empty and the removal does nothing. Dead code the compiler cannot see,
because a field that is read is not dead to it.

**Exit condition**: the field and its removal are gone, or what it claims to
own is put in it by the path that maps the page.
30 changes: 28 additions & 2 deletions issues/kernel/toyos-runs-on-arm64.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,7 @@ before any aarch64 file exists, with x86 as its only user:

Each is its own issue, owned by the stage that removes it:

- `issues/kernel/the-saved-kernel-context-names-x86-registers.md` (stage 4)
- `issues/kernel/msi-and-pin-routing-take-an-x86-vector-and-apic-id.md` (stage 4)
- `issues/kernel/msi-and-pin-routing-take-an-x86-vector-and-apic-id.md` (stage 6)
- `issues/kernel/the-boot-timing-handoff-is-named-for-the-tsc.md` (stage 4)
- `issues/kernel/the-crash-evidence-records-x86-fault-registers.md` (stage 5)
- `issues/kernel/the-aarch64-kernel-builds-with-dead-code-allowed.md` (stage 7)
Expand Down Expand Up @@ -259,6 +258,33 @@ Each stage names its exit; "measured" means a number from a run.
one stays green in `virt_el2_drop`, because stage 3 reads no counter and
runs no FP. This stage's timer and FP tests run under that EL2 profile too,
and each of the three deletions is shown red.
**Built on one CPU, ahead of small-kernel stage 6 by the owner's word:**
the kernel's own tables (`TTBR1_EL1` holding memory and nothing else, each
user space on `TTBR0_EL1` under a 16-bit ASID from `toyos-pcid`), the
GICv3's SGIs and the virtual timer's PPI, the EL0 entry, and the context
switch carrying FP/SIMD; the `virt_` tests other than `virt_early_panic`,
`virt_early_fault` and `virt_el2_drop` judge it under the EL2 profile, emulated, because HVF
exposes no RNDR and the kernel's hash seed refuses there until stage 6's
virtio-rng. Each judges an event, never a rate: no QEMU test measures time.
Owed before the exit holds: the interrupts-off window against x86's, a
measurement only metal can make, with no instrument on either arch yet;
`issues/kernel/the-boot-timing-handoff-is-named-for-the-tsc.md`; the
instruction-cache maintenance before a mapping is executable
(`cache::make_executable`), the break-before-make ordering of a live
entry's replacement, and the TLB flush before a reclaimed ASID is issued
again, which QEMU's TCG, the only oracle this stage has, cannot fail on:
the first HVF run, once stage 6 gives HVF its RNDR, is their exit; and the
three deletions shown red. They are shown red on a machine whose
firmware leaves the registers otherwise, or by a loader that writes the
opposite values before the handoff. The ITS moves to stage 6: a claimed
function is its only consumer the small-kernel track leaves, and it needs that
stage's SMMUv3 first. Stubbed on AArch64, each owned by the small-kernel
track, which moves the driver out of the kernel:
- `arch::msi_message` refuses, so the kernel's xHCI (`virt`'s boot stick),
NVMe, HDA, virtio-sound, virtio-console and virtio-gpu drivers each
refuse their function by name.
- `drivers::gop` refuses a scanout that is not whole 2 MiB pages of its
own, which a `ramfb` scanout carved out of RAM need not be.

5. **SMP through PSCI.** `CPU_ON` from MADT GICC entries, SGIs as the IPI,
broadcast TLBI behind the machine-wide invalidation contract. **Exit**:
Expand Down
5 changes: 5 additions & 0 deletions kernel/Cargo.lock

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

1 change: 1 addition & 0 deletions kernel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ toyos-blockhold = { path = "../toyos-blockhold" }
toyos-bootmap = { path = "../toyos-bootmap" }
toyos-fat32 = { path = "../toyos-fat32" }
toyos-elf = { path = "../toyos-elf" }
toyos-gicv3 = { path = "../toyos-gicv3" }
toyos-gpt = { path = "../toyos-gpt" }
toyos-hda = { path = "../toyos-hda" }
toyos-pci = { path = "../toyos-pci" }
Expand Down
10 changes: 9 additions & 1 deletion kernel/src/actuator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ actuators! {
/// Storm the CPU spinning on `syscall` from Ring 3 with NMIs.
syscall_window_nmi = "syscall-window-nmi";

/// Take the IST index off vector 2's gate — the negative control on the row above: the CPU builds the NMI frame at whatever `rsp` holds and takes a `#DF`.
/// Take the IST index off vector 2's gate — the negative control on the row above: the CPU builds the NMI frame at whatever the stack pointer holds and takes a `#DF`.
nmi_without_ist = "nmi-without-ist";

/// Return from the NMI handler via `iretq` with a second NMI already pending.
Expand Down Expand Up @@ -346,6 +346,14 @@ actuators! {
/// Raise a vector no `idt_vectors!` row claims on this CPU once.
unclaimed_vector_selftest = "unclaimed-vector-selftest";

/// Tick the timer at a fixed period while this CPU floods itself with
/// interrupts.
irq_storm = "irq-storm";

/// Make this CPU's timer due with interrupts masked, ask it to fire within
/// a quantum, and take its interrupts with them open.
timer_floor = "timer-floor";

/// Hold a flush of `truncate-race.bin` inside its metadata window and say whether a truncate got in.
ftruncate_flush_stall = "ftruncate-flush-stall";

Expand Down
Loading
Loading