<feed xmlns='http://www.w3.org/2005/Atom'>
<title>rs-dos/rsnix/src/fs.rs, 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-01T14:52:44Z</updated>
<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>Terminal-app design (ADR 0005) + disk-write controller</title>
<updated>2026-08-17T17:56:24Z</updated>
<author>
<name>salmonik</name>
<email>salmonik@tuta.io</email>
</author>
<published>2026-08-17T17:56:24Z</published>
<link rel='alternate' type='text/html' href='https://git.2137697.xyz/rs-dos/commit/?id=126d5b2e55dca93e024f5654fed6fae37a012369'/>
<id>urn:sha1:126d5b2e55dca93e024f5654fed6fae37a012369</id>
<content type='text'>
- ADR 0005: persistent home terminal (full-screen, chrome-less,
  bottom-pinned), F1 terminal windows, per-instance vt/tty/shell,
  queue-based input for all terminals, and the disk controller
- Glossary: add the Terminal term; update Task/App for the new model
- Add a re-entrant disk lock (DiskGuard over the preemption-depth
  counter) and wrap all fs/fat mutating operations per-call
</content>
</entry>
<entry>
<title>v0.5.5: crash reports go to a dedicated CRASHLOG USB stick</title>
<updated>2026-08-02T13:21:34Z</updated>
<author>
<name>salmonik</name>
<email>salmonik@tuta.io</email>
</author>
<published>2026-08-02T13:21:34Z</published>
<link rel='alternate' type='text/html' href='https://git.2137697.xyz/rs-dos/commit/?id=c7d92071e1dd11c45999457b16554b4abaa73b28'/>
<id>urn:sha1:c7d92071e1dd11c45999457b16554b4abaa73b28</id>
<content type='text'>
The fault handler now writes crash.log to the volume labeled CRASHLOG
(label-selected via fat::crash_vol), falling back to slot 0.  On real
hardware the boot USB may be unwritable, so a second stick labeled
CRASHLOG survives the write; the shell auto-prints the previous crash
report from it at next boot (read_crash_log resolves /disk/&lt;uid&gt;/crash.log
through the FAT path, since Disk nodes hold no cached children).

- fat.rs: volume_label() + crash_vol() (no allocation)
- fs.rs: write_crash_log targets crash_vol(); read_crash_log prefers the
  CRASHLOG disk node; drop now-unused first_disk()
- mkdisk.sh: --label option
- test-fault.sh: boots with a dedicated CRASHLOG stick (mkfs -F 32 -n
  CRASHLOG), verifies crash.log lands there and NOT on the ESP
</content>
</entry>
<entry>
<title>rsnix v0.5.0: graphics layer + cursor compositor</title>
<updated>2026-08-01T17:52:05Z</updated>
<author>
<name>salmonik</name>
<email>salmonik@tuta.io</email>
</author>
<published>2026-08-01T17:52:05Z</published>
<link rel='alternate' type='text/html' href='https://git.2137697.xyz/rs-dos/commit/?id=819778f292ee4e1294603b98662e083e49e23463'/>
<id>urn:sha1:819778f292ee4e1294603b98662e083e49e23463</id>
<content type='text'>
The first visible GUI surface: real rect primitives, a single
shadow→display push path, and an on-screen mouse cursor that can
never race the text surface.

gop:
- fill_rect + push_rect(x,y,w,h): every raster op rasterises into the
  RAM shadow, then push_rect copies the rect to the display (write-only,
  still never read) and composites the cursor on top. blit_glyph and
  scroll shifts now route through it.
- cursor overlay: 12x16 pointer (const-fn built from column lists) drawn
  white with a black offset outline; position in one AtomicU64 (x:high
  32, y:low 32) — unpack masks the low word (a bare 'as usize' cast is
  a no-op on 64-bit). The cursor never enters the shadow, so erasing is
  just a shadow push of the old rect.
- cursor_redraw after scroll shifts and once at spawn (the vt diff never
  re-blits idle cells).

cursor task (task 2, spawned at boot when a mouse is present):
- owns the PS/2 ring; polls every 10 ms, moves the cursor (clamped),
  tracks buttons, and forwards every event to a task's queue when
  'mouse &lt;id&gt;' armed it (the window-manager input path).
- /dev/mouse is now a position device (x/y/buttons snapshot); 'mouse'
  watches the cursor live (prints on change); 'cursor' prints one-shot
  state; 'gfx' draws a status-bar demo; 'gfx check' is an in-kernel
  pixel self-test (shadow readback, display readback, cursor overlay
  purity) — PASS/FAIL per stage.

Tests (headless QEMU + monitor injection):
- make test-gfx (new): all four compositor checks PASS.
- make test-mouse (rewritten): cursor tracks mouse_move deltas, buttons
  ride along, /dev/mouse snapshot, live watch, real-event queue routing.
- make test-msg: task numbering shifted (cursor is task 2; msgrecv is
  task 3) — updated, still PASS.
- make test-disk regression re-passes. 0 build warnings.
</content>
</entry>
<entry>
<title>rsnix v0.4.0: GUI input foundation — PS/2 mouse + scheduler message queues</title>
<updated>2026-08-01T17:34:40Z</updated>
<author>
<name>salmonik</name>
<email>salmonik@tuta.io</email>
</author>
<published>2026-08-01T17:34:40Z</published>
<link rel='alternate' type='text/html' href='https://git.2137697.xyz/rs-dos/commit/?id=9b9758e5ee2564b04685eadf2b9ce23dd1060b86'/>
<id>urn:sha1:9b9758e5ee2564b04685eadf2b9ce23dd1060b86</id>
<content type='text'>
Mouse (IRQ12, stream mode): ByteRing generalized for both PS/2 devices,
ISR routes data-port bytes by the controller's aux bit; mouse_init with
bounded-ACK handshake (boots without a mouse); packet decoder with
bit-3 resync, screen-relative deltas, buttons.  Exposed as /dev/mouse,
the `mouse` stream command, and `make test-mouse`.

Message queues (ADR 0003's GUI-era primitive): every task gets a static
16-slot TaskMsg ring; send/recv/recv_blocking with direct wake + tick
scan safety net for the lost-wakeup race; `msgrecv`/`send` commands,
`mouse &lt;id&gt;` routes real IRQ12 events into a task's queue, jobs shows
q=.  `make test-msg` + `make test-disk` regression pass.
</content>
</entry>
<entry>
<title>rsnix: runtime device detection — rescan for hotplugged/removed disks</title>
<updated>2026-08-01T13:00:47Z</updated>
<author>
<name>salmonik</name>
<email>salmonik@tuta.io</email>
</author>
<published>2026-08-01T13:00:47Z</published>
<link rel='alternate' type='text/html' href='https://git.2137697.xyz/rs-dos/commit/?id=d562e9df2bc5e69bba7298a9439e2adca5c18faf'/>
<id>urn:sha1:d562e9df2bc5e69bba7298a9439e2adca5c18faf</id>
<content type='text'>
Phase 1: make the boot discovery pipeline re-runnable and diff it
against live hardware state.

- ahci: store ABAR/NP/port bitmap; poll_dirty() (cheap PxSSTS.DET check
  per port) and rescan() which starts newly-present ports and tombstones
  the slots of removed ones.  Slots never shift, so disk indices stay
  valid.  select() now rejects tombstoned slots.
- fat: Volume gets a mounted flag; rescan() unmounts volumes whose disk
  left and mounts every disk without a mounted volume (re-trying failed
  boot mounts).  UID dedup ignores tombstones so a re-plugged disk keeps
  its /disk/&lt;UID&gt; name.
- fs: init's mount loop factored into rescan_disks(), which lazily
  creates /disk, ensures a mount node per mounted volume and tombstones
  nodes of unmounted ones.
- shell: 'rescan' command + automatic deep rescan before each prompt
  when ahci::poll_dirty() sees a port-presence change.

Rescan events flow through serial_println so they land in klog too.
Removal (PxSSTS) is a real-hardware path: QEMU q35 rejects disk hotplug
on the ich9-ahci IDE buses, and PCIe device_del is a guest-cooperative
protocol this kernel does not implement.
</content>
</entry>
<entry>
<title>klog: kernel log ring + /dev/klog + klog shell command</title>
<updated>2026-08-01T12:09:14Z</updated>
<author>
<name>salmonik</name>
<email>salmonik@tuta.io</email>
</author>
<published>2026-08-01T12:09:14Z</published>
<link rel='alternate' type='text/html' href='https://git.2137697.xyz/rs-dos/commit/?id=c7540c31d45dac68fa1852f5d25645d569b2da9d'/>
<id>urn:sha1:c7540c31d45dac68fa1852f5d25645d569b2da9d</id>
<content type='text'>
- rsnix/src/klog.rs: RAM ring of the last 128 serial log lines (128
  chars each), fed by serial_println! so every boot/low-level message
  is captured for free.  Sequence numbers per line, wrap/drop
  accounting; the ring never allocates (const-constructible static).
- /dev/klog char device; shell 'klog' is a thin alias for cat
  /dev/klog with the same filters/redirection, so 'klog -t 5',
  'klog -g fat' and 'klog WRITING /disk/.../boot.log' all work.
  klog rejects positional args (usage) and input keywords.
- Shell user-facing output (banner, prompt, term_print/term_println,
  pipeline sink, halt/poweroff) now writes to serial directly instead
  of through serial_println!, keeping echo/cat output out of the boot
  log (a streaming cat /dev/mem would otherwise wrap the ring).
- serial_println! macro hooks klog::append_fmt via format_args! (no
  alloc at call sites); klog::dump formats the log for the device.

Verified in QEMU: 28 boot lines captured (seq 0..27), shell output
absent from the ring, klog WRITING persists a boot log (mtype-checked),
wrap verified with a temporary 140-line boot loop (128 kept / 40
dropped, seq 40..167, oldest-live correct; loop reverted); full
test-disk round trip still fsck-clean.
</content>
</entry>
<entry>
<title>Bump workspace to edition 2024</title>
<updated>2026-08-01T01:47:51Z</updated>
<author>
<name>salmonik</name>
<email>salmonik@tuta.io</email>
</author>
<published>2026-08-01T01:47:51Z</published>
<link rel='alternate' type='text/html' href='https://git.2137697.xyz/rs-dos/commit/?id=7c5603b2fed27be86982665a8b10dcd8f1c5a483'/>
<id>urn:sha1:7c5603b2fed27be86982665a8b10dcd8f1c5a483</id>
<content type='text'>
- edition = "2024" in the workspace manifest and rsdosedit
- extern blocks must be unsafe: interrupts.rs ISR stubs -&gt; unsafe extern "C"
- #[no_mangle] -&gt; #[unsafe(no_mangle)] (efi_main, isr_dispatch)
- One missed static-mut read in free_list_insert wrapped in an explicit
  unsafe block (edition 2024 removed the implicit unsafe context inside
  unsafe fns)
- rustfmt reflows for 2024: nested if-lets become let-chains, imports
  re-sorted, rsdosedit help-screen box rows wrapped

No codegen-affecting changes. Validated: cargo check/clippy/fmt clean
(host + uefi target), make test-disk PASS, editor round-trip PASS.
</content>
</entry>
<entry>
<title>rustfmt: reflow fat.rs/fs.rs formatting</title>
<updated>2026-08-01T00:50:34Z</updated>
<author>
<name>salmonik</name>
<email>salmonik@tuta.io</email>
</author>
<published>2026-08-01T00:50:34Z</published>
<link rel='alternate' type='text/html' href='https://git.2137697.xyz/rs-dos/commit/?id=c00c74f3f96f36440733e5007b465452d39212a2'/>
<id>urn:sha1:c00c74f3f96f36440733e5007b465452d39212a2</id>
<content type='text'>
</content>
</entry>
<entry>
<title>fat: mount every FAT32 volume under /disk/&lt;UID&gt;/, fix multi-entry LFN</title>
<updated>2026-08-01T00:19:07Z</updated>
<author>
<name>salmonik</name>
<email>salmonik@tuta.io</email>
</author>
<published>2026-08-01T00:19:07Z</published>
<link rel='alternate' type='text/html' href='https://git.2137697.xyz/rs-dos/commit/?id=92ed4f6668efccf6af73f45654405d333e6a318c'/>
<id>urn:sha1:92ed4f6668efccf6af73f45654405d333e6a318c</id>
<content type='text'>
- fat.rs: VOL: Option&lt;Volume&gt; → VOLS: Vec&lt;Volume&gt;; try_mount() returns
  a Volume per disk and init() mounts every valid FAT32 volume found.
  Volume UID = sanitized uppercase label + 8-hex volume serial
  (e.g. RSDOS-5BD1E55F): unique per format, stable across reboots,
  human-recognizable, machine-parseable (^[A-Z0-9_]+-[0-9A-F]{8}$);
  cloned volumes get a -N suffix.  Public ops take a vol index and
  select it (select_vol also picks the backing AHCI disk), matching
  the ahci::select pattern
- fs.rs: /disk is now a plain directory whose children are one Disk
  node per mounted volume, named by UID; DiskInfo carries vol
- LFN fix: entries are stored last-first on disk — both the writer
  (head-entry first) and reader (append in disk order) were reversed
  and cancelled out for ≤13-char names; multi-entry names (&gt;13 chars)
  read back mangled (e.g. 'from-kernel.txt' as 'xt').  Writer now
  places seq=1 closest to the SFN and seq=N|0x40 first; reader places
  chunks by sequence number and trims at the first NUL.  Verified with
  a 25-char filename round trip: kernel ls/cat correct, mtools shows
  the full LFN, fsck clean on both volumes
- test-disk.sh: test file renamed to disk-test-.txt (&gt;13 chars,
  exercises multi-entry LFN); UID discovered from the boot log; QEMU
  stdio redirected to a log file + EXIT trap so an orphaned QEMU can
  never hang a piped caller (it previously did, 400s)
</content>
</entry>
</feed>
