Conversation
…xact ports
The first stage of taking the i8042 out of the kernel (owner ruling: drivers
are userland, and a dead kernel takes no input, with no emergency way). The
whole move does not fit one reviewable change: the driver is 1,496 lines, a
dozen guest tests measure its internals through kernel actuators, and the
harness paces every typed line on its drain trace. This stage lands what the
server will stand on and deletes nothing half way; ps2d and the driver's
deletion are stage 7.2 of the small-kernel track.
The panic console takes no input. The pager cycles on its own and is never
steered, the reboot bound has no retire and fires whoever is at the machine,
and poll_byte, the one read of port 0x60 outside the driver's ISR, is gone
from both architectures. screen_pager_keys is deleted with the keys it
pressed; panic_key_holds becomes panic_ignores_keys, the same guest with a key
pressed inside the bound that must now reset anyway. screen_paged_scrollback
stays: it presses nothing and judges the automatic cycle, which survives. The
panic-path issue about reading the key off an unconfigured i8042 closes with
the key.
The isa claim, DeviceType::Isa ("isa:0060,0064:1,12"), names an ISA function
by exactly its ports and lines, and the kernel grants only a whole row of its
architecture's GRANTABLE table: on x86-64 the i8042, on AArch64 nothing. The
first read of the claim binds the ports to the reading process and answers
the set back; each CPU's TSS now carries an I/O permission bitmap for ports
below 0x100 and its refusing end byte (Intel SDM Vol. 1 19.5.2), and every
context switch opens a row's ports for the process holding it and closes them
for every other. IOPL stays 0. The ports stay with that process until its
teardown, after every thread has left, so no revocation ever races a running
thread; a handle moved on after the bind answers PermissionDenied. The lines
are routed once per boot to vector 0x2c, unmasked while a claim exists, and
counted into the same record a claimed PCI function's are. A claim on a
function the kernel drives is refused by name, which is every machine whose
i8042 the kernel armed. A Ring 3 #GP at an in or out now names the port and
the direction in the kill record. No syscall was added.
Tests: toyos-abi's parser and wire round trip; isa_grant in three roles on
three machines (refused where the kernel drives; granted, and refused one port
over and to every other process, on a machine with no i8042; a real
controller the kernel gave up on driven by the claim to a keystroke's record
and byte). The holder can reset the machine through 0x64's 0xFE, which the
bitmap cannot filter: filed as a question for the owner.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The kernel-drives refusal is one boot for one syscall word, so it runs with the nightly tier; the binding test, the security boundary itself, stays in the fast one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu
marked this pull request as ready for review
September 28, 2026 22:35
Conflicts, each hunk accounted for: - tests/toyos.rs: main rewrote screen_pager_keys (PageUp one page back per key) and deleted panic_key_holds; this branch deletes screen_pager_keys with the pager keys and turns panic_key_holds into panic_ignores_keys. Kept the branch's side of all four hunks: the pager takes no key, so main's rewrite has no subject left. - tests/common/power.rs: main moved PANIC_REBOOTING to tests/common/qemu.rs and reworded two doc comments. The branch's local constant goes; qemu.rs's takes the kernel's new line, "panic: the bound is spent". The doc on panic_reboots keeps the branch's wording (the bound fires whoever is at the machine); PANIC_FAST_SECS takes main's. - issues/build/timing-verdicts-ruled-off-qemu-have-no-metal-arm.md: the sentence recording panic_key_holds' property as having no metal arm is deleted, since the panel no longer holds for a key at all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
isa_ports_are_the_binders_alone was red: every refused `in` was killed as a bare #GP and none named its port. The decode was reached and its bytes were wrong. `safe_read_u64` walked a user address to the PDE and read it as a 2 MiB leaf always, but an image whose first 2 MiB holds text beside rodata and data (`isa_grant`: r--, r-x and rw- LOADs under 0x9f000) is mapped by `map_window` as a page table of 4 KiB leaves. The PDE then names that table, and the report read the table's 2 MiB-aligned neighbourhood instead of the `ec` at the faulting rip. The stack, one uniform window, read correctly, which is why the backtrace never showed it. The walk now goes one level down when the PDE's PS bit is clear, as `paging::debug_page_walk` does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The first stage of taking the i8042 (PS/2) out of the kernel, on the owner's ruling that drivers are userland and a dead kernel takes no input, with no emergency way. Stage 7.1 of
issues/kernel/the-kernel-is-small-interrupts-post-and-threads-wait.md; 7.2, the userland server and the driver's deletion, is recorded there.Why a stage and not the whole move
The whole move is not one reviewable change. The driver is 1,496 lines (
i8042/mod.rs,tally.rs, its IDT entry), a dozen guest tests measure its internals through nine kernel actuators, and the harness paces every line it types at a windowed shell on the kernel'si8042: drain bytes=trace (shell_type_once, everyi8042-traceboot), so moving the driver moves that pacing and puts the server in every boot config that types. This stage lands what the server stands on and deletes nothing half way: there is no second path to the controller, and the kernel refuses a claim on a controller it drives.The panic console takes no input
page_foreverwaitsPAGE_HOLDand paints the next page,hold_the_panelspins on the bound or halts.Bound::retireis gone: the reboot bound fires whoever is at the machine. The arm line is nowpanic: rebooting in N s, timed by …and the raw last linepanic: the bound is spent: returning this machine to firmware.keyboard_controller::poll_byte, the one read of port 0x60 outside the driver's ISR, is deleted on both architectures, and the kernel's panic path no longer usestoyos-ps2.screen_pager_keysis deleted with the keys it pressed.panic_key_holds(and its release-only arm) becomespanic_ignores_keys: the same panicked guest withapressed inside the bound, which must reset anyway.screen_paged_scrollbackstays: it presses nothing and judges the automatic cycle, which survives. Thepanic_armed()/PANIC_REBOOTINGstrings intests/common/power.rsandscreen_panic_muted's arm line follow the kernel's.issues/panic-path/a-storage-phase-panic-reads-its-key-off-an-unconfigured-i8042.mdis closed: the key it was about is gone. A stale clause ina-fatal-event-stands-down-both-bounds…and the README's PageUp/PageDown sentence are deleted.The
isaclaimDeviceType::Isa = 9, spelledisa:0060,0064:1,12in adevicesentry, one spelling per set (four lowercase hex digits per port, decimal lines, both ascending). The selector carries the ports in 16-bit lanes and the lines as a mask;IsaIdintoyos-abiis the one parser,SysCap::claim_isathe SDK's mint, and init mints it like any other entry. No syscall was added.arch::pio::GRANTABLEis the whole of what can be claimed: on x86-64 one row, the i8042's0x60, 0x64and lines1, 12; on AArch64 none. A subset, a missing line or a neighbouring port isNotFound; a row the kernel drives (i8042::drives, itsACTIVE) isPermissionDenied, which is every machine whose i8042 the kernel armed, so in this stage the claim is live only where the kernel's probe gave up or found nothing.pio::switch_to, fromKernelHw::switch). The ports stay bound until the process's teardown, which runs after every thread has left and none returns to Ring 3, so no revocation ever has to reach a thread running on another CPU. A handle moved on after the bind answersPermissionDenied, and the row is claimable again once both the claim and the process are gone. IOPL stays 0.0x100and the all-ones byte the processor reads past the last (Intel SDM Vol. 1 §19.5.2). The descriptor's limit ends at that byte, so every port from0x100up is refused by the limit with no bit at all. Compile-time asserts pin the bitmap's offset and the limit.0x2c(an interrupt-remapping entry is never given back), unmasked while a claim exists and masked when it goes, and counted intopcidev's ownInterruptrecord (nowpub(crate)), posted through theUserDevIRQ-ring slot, and read the way a PCI claim's are.in/outnow addsin of 1 byte(s) from port 0x0061, which this process holds no grant forto the kill record; the decode is aconst fnwith compile-time cases. The report reads the faulting bytes throughsafe_read_u64, the crash path's hand walk of the user page tables, which now goes down to a 4 KiB leaf when the PDE is not one: an image whose first 2 MiB mixes text, rodata and data is mapped bymap_windowas a table of 4 KiB leaves, and the walk used to read that table's PDE as a 2 MiB page. That is whyisa_ports_are_the_binders_alonewas red ate72cf7f4: all four refusedins were killed as a bare #GP, and none named its port (isa_grant's LOADs arer--,r-xandrw-below0x9f000, and the bytes at its faulting rips are66 ba NN 00 ec:mov dx, NN; in al, dx). The stack is one uniform window, which is why the backtrace was right all along.0xFEto0x64), which a bitmap cannot filter — filed for the owner asissues/isolation/the-i8042s-holder-holds-the-machines-reset-line.md.kernel/src: 64,918 lines onorigin/main(3d902477), 65,313 on this head (find kernel/src -name '*.rs' -print0 | xargs -0 cat | wc -l): the mechanism lands before the driver it replaces is deleted.Gates on the head
All on
7c13193f, each its own exit code, with the tree clean after:cargo run -- --ci host: EXIT=0 (54 steps)cargo run -- --build-only: EXIT=0Merged with
origin/main(3d902477)Main rewrote
screen_pager_keysand deletedpanic_key_holds(a QEMU test measures no time); this branch deletes the first with the pager keys and turns the second intopanic_ignores_keys, which waits on the reset, not on a clock. The branch's side is kept in all fourtests/toyos.rshunks. Main had movedPANIC_REBOOTINGtotests/common/qemu.rs; it now holds the kernel's new raw line,panic: the bound is spent, andpower.rs's copy is gone.issues/build/timing-verdicts-ruled-off-qemu-have-no-metal-arm.mdloses the sentence about the panel holding while a key is held, since the panel now holds for no key.Nothing on the panic path reads input: no
poll_byte, noKeyDecoder, no page keys indrivers/panic_console,panic.rsorpanic_reboot.rs. The one movement left ispage_forever's automatic page cycle, which no key steers.Tests, controls and the oracle
New guest tests:
isa_ports_are_the_binders_alone(Fast;MetalNoUsb, no i8042: the claim, its refusals, and one child per access that must die — holding the claim unbound, one port past the grant, holding nothing, holding a claim its parent bound — each named by the kernel),isa_claim_refused_where_the_kernel_drives(Nightly),isa_lines_reach_their_holder(Nightly;i8042-budget-expiredmakes the kernel give the real controller up, and the test drives it through the claim to a keystroke's record and its byte,0x1e).Negative controls, each a checked patch in the job scratchpad. m1–m4 and m6 are in
/private/tmp/claude-502/-Users-jan-Dev-jan-toyos/2280e09e-428b-4b81-bc00-1ede594b7247/scratchpad/ps2-r2/, regenerated on7c13193f, then applied, built (cargo run -- --build-onlyEXIT=0 each, the kernel recompiled each time) and reverted to a clean tree. m5 is still the earlier…/scratchpad/ps2/m5-keys-retire-the-bound-again.patch:git apply --checkpasses on this head (EXIT=0), but it was built only on36518879. The orchestrator runs the guest verdicts:m1-every-process-gets-the-ports.patchisa_ports_are_the_binders_alone(unbound survived its access)m2-a-port-grants-its-neighbour.patchisa_ports_are_the_binders_alone(bound survivedat0x61)m3-a-subset-is-a-row.patchisa_ports_are_the_binders_alone("0060:1"minted)m4-no-bitmap-in-the-limit.patchisa_ports_are_the_binders_alone(the bound child dies before0x61),isa_lines_reach_their_holderm5-keys-retire-the-bound-again.patchpanic_ignores_keysm6-the-report-reads-a-split-window-as-a-2mib-leaf.patch7c13193freverted: the crash path's walk reads every PDE as a 2 MiB leaf againisa_ports_are_the_binders_alone(no port named; measured red ate72cf7f4, which is this arm)Independent oracles: the Intel SDM Vol. 1 §19.5.2 and AMD APM Vol. 2 §12.2.4 for the bitmap, its end byte and the limit; QEMU's TCG
check_iois a second implementation of that check the local guests run on, and CI's KVM shards run it on silicon. For the controller, the IBM PC AT Technical Reference's 8042 commands (0x20,0x60,0xAE), status bits and set-10x1e, which QEMU'spckbd/ps2model implements independently.Unsure
e72cf7f4the orchestrator measuredisa_ports_are_the_binders_alonered for the reason fixed above, with every access refused where it should be.isa_claim_refused_where_the_kernel_drives,isa_lines_reach_their_holder,panic_ignores_keys,panic_reboots,screen_paged_scrollback,screen_panic_muted,screen_late_panicandklogd_fault_haltswere green there, before the merge.port_access_atreads the 8 bytes after the faulting rip's aligned word as well. Aninthat ends right before an unmapped page is decoded as no port access, and the kill record then carries only the bare #GP.isa_lines_reach_their_holderrests on QEMU leaving the keyboard scanning after the kernel's aborted probe, which is QEMU's reset state and not something the probe promises.Guest runs to queue
One per run:
cargo test --test toyos-build -- isa_ports_are_the_binders_alonecargo test --test toyos-build -- --nightly isa_claim_refused_where_the_kernel_drivescargo test --test toyos-build -- --nightly isa_lines_reach_their_holdercargo test --test toyos-build -- --weekly panic_ignores_keyscargo test --test toyos-build -- --nightly panic_rebootscargo test --test toyos-build -- --weekly screen_paged_scrollbackcargo test --test toyos-build -- --weekly screen_panic_mutedcargo test --test toyos-build -- screen_late_paniccargo test --test toyos-build -- --nightly klogd_fault_haltscargo test --test toyos-build(the Fast tier, this head merged with main)🤖 Generated with Claude Code