<feed xmlns='http://www.w3.org/2005/Atom'>
<title>rs-dos/rsnix/src, branch qemu</title>
<subtitle>Simple, kernel-only, 64bit OS, early stage.
</subtitle>
<id>https://git.2137697.xyz/rs-dos/atom?h=qemu</id>
<link rel='self' href='https://git.2137697.xyz/rs-dos/atom?h=qemu'/>
<link rel='alternate' type='text/html' href='https://git.2137697.xyz/rs-dos/'/>
<updated>2026-11-01T16:09:59Z</updated>
<entry>
<title>ehci: bound all init waits; stop auto-rescanning; wider boot watchdog</title>
<updated>2026-11-01T16:09:59Z</updated>
<author>
<name>salmonik</name>
<email>salmonik@tuta.io</email>
</author>
<published>2026-11-01T16:09:59Z</published>
<link rel='alternate' type='text/html' href='https://git.2137697.xyz/rs-dos/commit/?id=d99906b48698c1bd722a1fa5f2270b76317ec661'/>
<id>urn:sha1:d99906b48698c1bd722a1fa5f2270b76317ec661</id>
<content type='text'>
Hardware run 3 showed the same PED=0 SETUP halt and then never reached the
prompt.  Two boot-time hazards explain the death:

- reset_port polled PED with a 2,000,000-iteration MMIO loop (seconds on real
  silicon) and the reset retry ran it twice per port, so the port scan alone
  exceeded the boot watchdog window (task 0 in one frame for 7.5 s panics).
- while no HID was found the cursor task re-ran the whole EHCI scan, port
  resets included, every second -- a rate-matching hub never settles.

Changes:
- every EHCI wait is wall-clock bounded: halt/reset/run 500 ms, PED 200 ms
- control-transfer timeout 5 s -&gt; 500 ms
- per-controller 3 s port-scan budget; no automatic re-scan (explicit
  `usb rescan` only)
- 300 ms power-up settle before the reset walk (RMH bring-up)
- boot-phase watchdog 750 -&gt; 2000 ticks (20 s), room for hub bring-up
- xHCI gets the same persistent bottom-row port/device summary
</content>
</entry>
<entry>
<title>ehci: diagnostics + hardening for the port-not-enabled case</title>
<updated>2026-11-01T15:55:14Z</updated>
<author>
<name>salmonik</name>
<email>salmonik@tuta.io</email>
</author>
<published>2026-11-01T15:55:14Z</published>
<link rel='alternate' type='text/html' href='https://git.2137697.xyz/rs-dos/commit/?id=e53161d6b9191533caeea0ef6148f247f4e22441'/>
<id>urn:sha1:e53161d6b9191533caeea0ef6148f247f4e22441</id>
<content type='text'>
Second OptiPlex QR: the Host System Error is gone (the 64-bit QH/qTD layout
fix worked), but the SETUP qTD now halts with TBYTES=0, C_ERR=3 and no
XactErr/Babble/DBE -- a split transaction with no valid response.  PORTSC
shows CCS=1, PP=1 but PED=0, i.e. the port never enabled, so the split has no
TT to address.  On Intel PCH a healthy USB2 port shows the rate-matching hub
as high-speed (PED=1), so the next step is to see every port's state.

- settle 100 ms after powering ports and clear stale change bits before the
  reset walk (RMHs come up slower than direct devices)
- retry the port reset once after settling
- log a clear line when a connected port remains disabled
- retry a failed full-speed device as low-speed (the PORTSC speed field is
  only valid once the port is enabled)
- persistent bottom-row summary: controller count, caps64, USB legacy handoff
  result, every connected port's PORTSC and the HID device count.  A hardware
  run without serial now reports the whole picture instead of one QR.
</content>
</entry>
<entry>
<title>ehci: reserve the 64-bit QH/qTD layout (fixes hardware HSE)</title>
<updated>2026-11-01T15:48:40Z</updated>
<author>
<name>salmonik</name>
<email>salmonik@tuta.io</email>
</author>
<published>2026-11-01T15:48:40Z</published>
<link rel='alternate' type='text/html' href='https://git.2137697.xyz/rs-dos/commit/?id=12549dabf96bd6eb08c6834134fedf9edc7452b5'/>
<id>urn:sha1:12549dabf96bd6eb08c6834134fedf9edc7452b5</id>
<content type='text'>
First OptiPlex run: the controller halted with HSE (USBSTS bit4) 20 ms into
the first control transfer, with otherwise valid QH/qTD fields (control QH,
DTC, MPS 8, split hub 0/port 1, active qTD).

The PCH EHCI advertises HCCPARAMS bit0 (64-bit addressing capability), which
extends every QH with bufptr_hi[5] (17 dwords / 68 B) and every qTD the same
way (13 dwords / 52 B).  Packing them 32-bit-style (48/32 B) made the
controller read the next qTD as the overlay's high buffer pointer and DMA to
a bogus 64-bit address -&gt; Host System Error.

Space every structure for the 64-bit variant (32-bit controllers ignore the
tail): HID qTD at QH+0x80; control qTDs at QH+0x100/0x140/0x180 with the
setup packet at +0x1C0.  The failure QR now reports the capability and the
DMA bases so the next hardware iteration is self-describing.
</content>
</entry>
<entry>
<title>usb: real-hardware rework — hubs, both EHCI controllers, keyboard, xHCI hardening</title>
<updated>2026-11-01T15:33:05Z</updated>
<author>
<name>salmonik</name>
<email>salmonik@tuta.io</email>
</author>
<published>2026-11-01T15:33:05Z</published>
<link rel='alternate' type='text/html' href='https://git.2137697.xyz/rs-dos/commit/?id=50b4d2f0ab05901a847d8e1318152308265e579b'/>
<id>urn:sha1:50b4d2f0ab05901a847d8e1318152308265e579b</id>
<content type='text'>
EHCI (OptiPlex 7010 / Q77 and similar Intel PCH):
- enumerate *both* EHCI controllers (00:1a.0 + 00:1d.0), not just the first
- hub support: read the hub class descriptor, power/reset each port via hub
  class requests, read the child speed, then drive the child with split
  transactions carrying the hub's address and port (TT).  On 7-series PCH the
  root port presents the integrated Rate Matching Hub (8087:0024, class 9)
  and the mouse/keyboard sit behind it; without this the driver stopped at
  "no HID mouse/keyboard endpoint in config".
- HID mouse *and* keyboard, boot protocol + idle, per-device periodic QH
  chained into the frame list, per-device report buffers and counters
- periodic re-arm follows the standard shape (qTD re-activated, Next-qTD
  re-linked); the HC owns the status/toggle overlay
- DMA from the page allocator inside [16 MiB, 4 GiB): EHCI programs 32-bit
  addresses and the page bitmap spans all RAM
- 2 ms recovery after SET_ADDRESS

xHCI:
- scratchpad buffers (HCSPARAMS2) and MaxSlotsEn &gt; 1, with per-slot EP0 and
  interrupt rings plus per-slot device contexts
- DMA from the page allocator instead of the heap allocator
- boot protocol + idle, mouse + keyboard, first-report diagnostics
- transfer events are routed to every device before matching a waiter, so a
  second device's enumeration cannot swallow an armed device's report

shared:
- rsnix/src/hid.rs: HID usage -&gt; scancode-set-1 tables, one scancode FIFO
  feeding ps2::feed_scancode, per-device boot-keyboard report differ
- WM input pump polls PS/2 -&gt; UHCI -&gt; EHCI -&gt; xHCI
- memory::alloc_pages_range(count, min, max) for bounded DMA allocations

QEMU coverage: make test-usb (EHCI + xHCI, mouse + keyboard).  QEMU's EHCI
split path delivers only the first report for FS devices, so repeated-report
delivery and the RMH path remain hardware-test items.
</content>
</entry>
<entry>
<title>usb: re-enable EHCI, share HID keyboard decoding, fix boot protocol</title>
<updated>2026-11-01T15:05:09Z</updated>
<author>
<name>salmonik</name>
<email>salmonik@tuta.io</email>
</author>
<published>2026-11-01T15:05:09Z</published>
<link rel='alternate' type='text/html' href='https://git.2137697.xyz/rs-dos/commit/?id=561354b69c5ae5e73b036b382988fda02bc9fb01'/>
<id>urn:sha1:561354b69c5ae5e73b036b382988fda02bc9fb01</id>
<content type='text'>
- ehci: DEBUG_MATRIX was left true in be868a4, so every boot replaced
  enumeration with the one-shot diagnostic transfer matrix and returned
  false -- the EHCI driver never brought a device up.  Default it off;
  flip it back on only when bisecting a controller fault.
- new rsnix/src/hid.rs: boot-keyboard HID usage -&gt; scancode-set-1
  tables, the shared scancode FIFO, and a per-device report differ.
  UHCI now uses it instead of its own copy, so EHCI/xHCI keyboards can
  feed the same PS/2 decoder (`ps2::feed_scancode`).
- uhci: SET_PROTOCOL now asks for the boot protocol (wValue 0) instead
  of report protocol (wValue 1).  In report protocol the report layout
  is device-defined, but every driver parses boot reports.
</content>
</entry>
<entry>
<title>fs: /bin alias for the system volume</title>
<updated>2026-11-01T14:52:44Z</updated>
<author>
<name>salmonik</name>
<email>salmonik@tuta.io</email>
</author>
<published>2026-11-01T14:52:44Z</published>
<link rel='alternate' type='text/html' href='https://git.2137697.xyz/rs-dos/commit/?id=316a2a239446fe927e35b029b651cbeb08ff3f48'/>
<id>urn:sha1:316a2a239446fe927e35b029b651cbeb08ff3f48</id>
<content type='text'>
ADR 0006 deferred root-path unification because it "needs VFS
bind/alias machinery".  Implement the /bin half: paths whose first
component is `bin` are rewritten to the first mounted volume whose
root has a \bin directory, so `ls /bin/cli`, `cat /bin/cli/x.efi` and
a binary's own fs_resolve("/bin/cli/x.efi") work without knowing the
volume UID.

The alias is transparent — it creates no node, so `ls /` keeps its old
listing and /bin simply does not exist when no system volume is
mounted — and allocates nothing for non-matching paths.  exec::resolve
keeps its per-file volume scan (stricter for multiple volumes).

Update ADR 0006 to record the decision and what remains deferred
(\etc and friends at the root).
</content>
</entry>
<entry>
<title>streams: per-invocation channel, pumping mode, filter pipeline wiring</title>
<updated>2026-08-27T14:58:31Z</updated>
<author>
<name>salmonik</name>
<email>salmonik@tuta.io</email>
</author>
<published>2026-08-27T14:58:31Z</published>
<link rel='alternate' type='text/html' href='https://git.2137697.xyz/rs-dos/commit/?id=300c0acbd7ab3fae8596ff839c39d9a17c70c68d'/>
<id>urn:sha1:300c0acbd7ab3fae8596ff839c39d9a17c70c68d</id>
<content type='text'>
exec: LineChannel bounded ring (cap=16) with abort/eof/marker fields.
Shell 'pumping mode' assembles canonical lines from its queue into the
channel while the command runs; ^C marks the channel aborted.

Shell-side grammar: USE/TIL pre-drain into the channel before spawn;
&lt;cmd&gt; &lt;path&gt; TIL &lt;marker&gt; sugar drains paths until the marker line.
Interactive TIL: pump_key checks each submitted line against the marker
and closes the channel when it matches.

Exports callbacks run in the command task but touch shell-owned state:
this is safe by construction — the shell blocks in exec_external until
the command completes.  put_line feeds Pipeline::line directly;
get_line pops from the channel (yield-poll when empty).

load: drop NX from image mapping (single-privilege kernel, NX bit 63
reserved when EFER.NXE is set by OVMF); compute image span from section
RVAs not file size.

bins/count: gate binary — reads lines via get_line, reports count via
put_line.  Verified: count USE file → 6; count &lt;path&gt; TIL END → 2;
interactive count TIL DONE → 3; -h filter across task boundary;
^C abort.

v1 stream exports: put_line → Pipeline, get_line ← channel, log_line
→ klog append, exit → abort_current.

Accounting: print_one_line → pipe.line() returns false (head kill /
pager ^C) → the binary should wrap up promptly; accepted: binary does
not check the return value in v1 (phase 3 simplification).
</content>
</entry>
<entry>
<title>exec: PE32+ loader, versioned ABI, first external binaries (v0.6.0)</title>
<updated>2026-08-21T19:17:44Z</updated>
<author>
<name>salmonik</name>
<email>salmonik@tuta.io</email>
</author>
<published>2026-08-21T19:17:44Z</published>
<link rel='alternate' type='text/html' href='https://git.2137697.xyz/rs-dos/commit/?id=71b6445b048456bf198d8b622570db95f4665cb9'/>
<id>urn:sha1:71b6445b048456bf198d8b622570db95f4665cb9</id>
<content type='text'>
rsapi: the kernel&lt;-&gt;binary ABI crate (no_std, no deps).  Versioned
Exports table (magic + version header), EntryPoint = extern "C" on
x86_64-unknown-uefi (Win64: RCX/RDX/R8), Line/Arg/StreamResult types.
Binaries link only this crate.

rsnix::load: PE32+ parser — DOS/COFF/optional headers, section copy,
BSS zero-fill, .reloc base relocations (DIR64) via rva_to_file mapping
(the reloc directory is an RVA, not a file offset).  Images are
allocated at LOAD_BASE=192MiB inside the page allocator's conventional
span and identity-mapped user+writable+nx.

rsnix::exec: resolution name -&gt; /disk/&lt;UID&gt;/bin/&lt;kind&gt;/&lt;name&gt;.efi per
mounted volume; spawn reserves a sched slot, stores a per-task ExecCtx
(exports + argv block + image) before launch_at installs the frame —
no adopt race.  Trampoline calls entry(&amp;Exports, argc, argv), posts
TaskMsg::User(exit_code) to the shell.  abort_current notifies the
shell with u64::MAX when an exec'd binary dies, so a faulting command
cannot leave the shell blocked forever.

sched: reserve_slot/launch_at split out of spawn; task_exit made pub.

shell: exec_external runs before non-frozen built-ins (ADR 0006
resolution rule); frozen core (help/clear/echo/halt/poweroff/exit/
rescan) is exempt.  Exports v1 wires put_line to the terminal and
get_line to EOF; stream injection lands in Phase 3.  ^C while waiting
kills the command task.

bins/{hello,boom}: gate binaries.  hello prints through the exports
table; boom faults on 0x400000000000 to prove containment.  The image
entry point IS the ABI entry (efi_main carries the Exports signature).

Version 0.6.0: exec exists end-to-end.

Gates verified in QEMU: 'hello' loads from \\bin\\cli\\hello.efi, calls
put_line across the ABI, exits 0, shell unblocks; 'boom' faults, task
dies gracefully with full report, shell survives, jobs shows the corpse;
built-in fallback works when the file is absent; test-disk PASS.
</content>
</entry>
<entry>
<title>input: total keyboard routing + per-terminal discipline (ADR 0006 ph1)</title>
<updated>2026-08-21T18:13:59Z</updated>
<author>
<name>salmonik</name>
<email>salmonik@tuta.io</email>
</author>
<published>2026-08-21T18:13:59Z</published>
<link rel='alternate' type='text/html' href='https://git.2137697.xyz/rs-dos/commit/?id=2f4cf666c0c7bd224932fa48973e7334360c1de3'/>
<id>urn:sha1:2f4cf666c0c7bd224932fa48973e7334360c1de3</id>
<content type='text'>
wm: FOCUSED is now a plain window id — window 0 (home terminal) is an
ordinary focusable window in the z-order.  The CONSOLE sentinel
(usize::MAX) and the focused()==0 special cases are gone; focus_console()
is an alias for focus(0).  Desktop clicks focus window 0.  The WM input
pump routes every key unconditionally: IRQ ring -&gt; pump -&gt; focused
window's owner; nothing polls the scancode rings directly any more.

tty: read_line() reads from the calling task's message queue (the old
global-ring read_line and the duplicate read_line_from_queue are merged);
raw mode (read_byte/try_read_byte) consumes the same queue;
interrupt_pressed drains the queue instead of the ring.

shell: task 0 owns home terminal window 0 and blocks on its queue like a
windowed shell; cat/hexdump stdin paths pass the home terminal handle.

sched: watchdog comments updated (shell parks on recv_blocking).

Verified in QEMU: F1 spawns terminals, F2 cycles focus home-&gt;term1-&gt;
term2, keystrokes land only in the focused window, ^C cancels a line,
edit runs raw over the queue and exits to a working prompt; test-disk
regression PASS.
</content>
</entry>
<entry>
<title>wm: fix usize underflow in move_window at screen edges</title>
<updated>2026-08-21T02:09:57Z</updated>
<author>
<name>salmonik</name>
<email>salmonik@tuta.io</email>
</author>
<published>2026-08-21T02:09:57Z</published>
<link rel='alternate' type='text/html' href='https://git.2137697.xyz/rs-dos/commit/?id=235906cc36d9195b7b36d67f94f40cacccf030fd'/>
<id>urn:sha1:235906cc36d9195b7b36d67f94f40cacccf030fd</id>
<content type='text'>
ox.saturating_sub(pad) / oy.saturating_sub(pad) — plain subtraction
on usize wraps to usize::MAX when the window sits at x=0 or y=0,
causing composite_rect to draw at the far end of the framebuffer
instead of near the window.  Also broke drag-to-move when stuck
against the top or left edge.
</content>
</entry>
</feed>
