Phase 3 Plan E: FS write path + console fd + shell + utilities#17
Merged
Conversation
…de stub) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Enable IRQ_UART_RX in PLIC, call console.init() + file.init(), allocate one Console File entry with ref_count=3, and install it as ofile[0..2] on init_p so /bin/init inherits stdin/stdout/stderr. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…atch) Replace sysWrite's hard-coded UART path with file.write dispatch: - sysWrite now routes through file.ofile[fd] → file.write → console.write - sysRead already used this pattern; now both are symmetric - Return type changed from u32 to i32 for proper error propagation - Dispatch arm 64 updated with @bitcast to handle signed return - Dropped unused uart import from syscall.zig Also install console fds 0/1/2 in FORK_DEMO arm so init inherits stdin/stdout/stderr (was only in FS_DEMO after Task 9). This ensures sysWrite through file.write works consistently in both boot modes. All e2e tests pass: e2e-fs and e2e-fork maintain original behavior (write to UART via file.write → console.write). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nk==0) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds src/kernel/user/ls.zig (~73 LoC): lists directory entries via DirEntry reads or prints file path+size for regular files. Wires the kernel-ls build step in build.zig after kernel-cat. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- populateFromDir: skip entries whose name starts with '.' (.gitkeep, .DS_Store) - directory branch: comment confirms Dir inode is created even for empty subdirs (logic was already correct) - CLI: add --init <path> flag; when given, installs that binary as /bin/init after the --bin walk Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Plan 3.D's mkfs hard-coded /etc as the only subdirectory under root and silently dropped any other entries. The shell-fs/ staging tree adds /tmp as an empty directory carrier, so mkfs now walks every top-level entry, creating an inode for each subdirectory regardless of whether it has children. /bin is still wired separately via --bin and skipped here. Closes the gap left by the earlier mkfs commit (e822dfd) which addressed dot-file skipping + empty-dir handling but kept the /etc-only walker. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without this, init's restart loop would keep re-spawning sh after each clean exit, including the shell's `exit` builtin path. The e2e harness needs init to halt cleanly so the kernel can write the final "ticks observed" trailer and stop the emulator. Restart-on-nonzero remains, so a crash still gets the diagnostic + restart. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan 3.E's e2e-shell needs `--input` bytes to flow into the UART RX
FIFO while the shell is sleeping in console.read. The 3.D scheduler's
"open SIE briefly" busy-poll never invokes cpu.idleSpin (which is the
only drain site for the rx_pump), so without changes the shell never
sees its keystrokes.
Adding WFI to the idle window fixes that — but on its own creates a
fixed-point loop: cpu.step's prologue raises the pending S-trap before
WFI is fetched, sret returns to WFI, and the same trap fires again on
the next step. Three coordinated changes break the loop and make the
delivery cadence right:
- sched.zig: WFI inserted between the SIE csrs/csrc pair so the
emulator actually pauses and runs cpu.idleSpin.
- trap.zig: s_kernel_trap_dispatch advances sepc by 4 before sret.
That's safe because the only call site is the scheduler's WFI
window — sepc points at a 4-byte WFI (or, if the trap fired on
the boundary just before WFI, the 4-byte csrs that precedes it;
either way, advancing skips ahead to the trailing csrc which
closes the window). Without the advance, sret re-executes WFI
and the scheduler can never re-scan ptable.
- cpu.zig idleSpin: check_interrupt now fires BEFORE draining the
rx_pump, so disk-I/O sleeps (block IRQ pending) take their trap
immediately and don't leak --input bytes into the FIFO mid-boot.
Drain only happens when the guest is truly idle — i.e., the
shell prompt is waiting in read(). And drain is now one byte
per iteration (uart.RxPump.drainOne) so cooked-mode console
echo interleaves with the shell's per-line prompts instead of
bulk-emitting every echo before the second prompt prints.
Regressions checked: e2e-fs / e2e-fork / e2e-snake / e2e-plic-block all
still pass. e2e-multiproc-stub remains broken on Task-33 baseline (a
separate pre-existing issue with the Phase 2 kmain branch not setting
up the console file table).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan 3.E milestone (Task 34): boot kernel-fs.elf against shell-fs.img
with --input piping the canonical session "ls /bin / echo hi > /tmp/x
/ cat /tmp/x / rm /tmp/x / exit" through the UART RX path. Asserts
prompt+command echo, ls visibility of the binaries we shipped in /bin,
the cat round-trip ("hi"), and a clean halt.
The shell harness mirrors tests/e2e/fs.zig's spawn + collect + landmark
shape; build.zig wires e2e-shell after e2e-fs and depends on the
shell-fs-img + ccc + kernel-fs.elf install steps.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After 0f84951 routed sysWrite through file.write, every user proc needs ofile[0..2] pointing at a Console-typed file or write(1, ...) silently fails with -1. The FS_DEMO and FORK_DEMO branches already do this; the plain Phase 2 branch (kernel.elf single-proc + kernel-multi.elf two-proc) did not, so e2e-kernel and e2e-multiproc-stub regressed to "ticks observed: N" with no user output. Initialize the file table once, allocate one Console entry, dup it onto each proc's fd 0/1/2. PID 2 gets its own three dups so file.close ref-counting stays accurate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Status block: bump headline to Plan 3.E done; append a 3.E paragraph covering FS write path (writei + bmap lazy alloc + iupdate + ialloc + itrunc + dirlink/dirunlink + fsops glue), console as fd 0/1/2 with cooked-mode line discipline, UART RX through PLIC IRQ #10, the WFI + sepc-advance + paced rx_pump trio that lets --input bytes interleave with shell prompts, the new mkdirat / unlinkat syscalls + openat extensions, the userland stdlib at src/kernel/user/lib/, and the seven new userland binaries; close with the canonical e2e-shell session transcript and Next: Plan 3.F. Layout: add console.zig, fs/fsops.zig, user/init_shell.zig + sh.zig + ls.zig + cat.zig + echo.zig + mkdir.zig + rm.zig, user/lib/ with the four stdlib pieces, userland/shell-fs/ tree, and tests/e2e/shell.zig + shell_input.txt. Building: add the seven kernel-* user-binary targets, shell-fs-img, and e2e-shell. Final regression sweep (all green): test, e2e-shell, e2e-fs, e2e-kernel, e2e-multiproc-stub, e2e-fork, e2e-plic-block, e2e-snake, e2e-hello-elf, e2e, e2e-mul, e2e-trap, riscv-tests, wasm. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three new chapter slides between the 3.D init-from-disk slide and the
epilogue:
- Ch 3.E · FS write path: writei + bmap.for_write lazy alloc, iupdate
/ ialloc / itrunc / iput-on-zero, real dirlink + dirunlink, the
fsops.create/unlink glue, mkdirat/unlinkat + openat O_CREAT/O_TRUNC/
O_APPEND, and the sysWrite reroute through file.write that surfaced
the kernel-multi/kernel-fs Console-fd regression.
- Ch 3.E · console + WFI: console.zig as fd 0/1/2 backing with
cooked-mode line discipline (echo + ^C/^U/^D + \n commit), UART RX
via PLIC #10, and the WFI fixed-point loop that adding wfi to the
scheduler idle window created — broken by sepc+=4 in
s_kernel_trap_dispatch + paced one-byte-at-a-time idleSpin drain.
- Ch 3.E · shell + milestone: the userland stdlib at user/lib/, the
seven binaries (init_shell, sh, ls, cat, echo, mkdir, rm), the
sh main loop, and the canonical e2e-shell session transcript.
TOC + intro caption: bump the Phase 3 progress line to "3.a + 3.b +
3.c + 3.d + 3.e done" with the 3.E summary; add 3.E to the deck-walks
list. Epilogue: extend the Phase 3 paragraph with the 3.E recap, add a
3.E checklist row, and replace "Next · plan 3.e" with "Next · plan 3.f"
describing the editor + e2e-persist work.
Co-Authored-By: Claude Opus 4.7 (1M context) <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.
Summary
writeiwithbmaplazy alloc,iupdate,ialloc,itrunc,iput-on-zero truncate, realdirlink+dirunlink,fs/fsops.zigcreate/unlink glue), console as fd 0/1/2 with cooked-mode line discipline (echo + backspace +^U/^C/^D+\ncommit + Raw arm), UART RX through PLIC IRQ web wasm demo + CI #10 →uart.isr→console.feedByte, scheduler now WFIs in its idle window socpu.idleSpinpaces--inputbyte delivery, new syscallsmkdirat(#34) +unlinkat(#35),openatextended withO_CREAT/O_TRUNC/O_APPEND, andwritenow routes any fd throughfile.write.src/kernel/user/lib/: RV32_start+ 19 syscall stubs +mem*/str*+Stat/O_*constants + minimalprintf. Fed by anaddUserBinarybuild helper that packs every userland binary identically.init_shell(loops fork-exec-sh-wait, exits cleanly on sh status 0),sh(line/token/redirect/builtins/fork+exec, ~250 LoC),ls,cat,echo,mkdir,rm.mkfs.ziglearned--init+ walks every top-level subdir of--root(so/tmp/empty-dir staging carries through).shell-fs.imgis the parallel image baking init_shell as/bin/init+ every utility under/bin/.s_kernel_trap_dispatchadvancessepcpast WFI, andcpu.idleSpinchecks for pending interrupts before draining the pump (so disk-I/O sleeps don't leak--inputbytes mid-boot) and drains one byte per iteration (so cooked-mode echo interleaves with shell prompts instead of bulk-emitting every echo before the second prompt prints).refactor(syscall)Phase 2 kmain branch never wiredofile[0..2]to a Console fd, soe2e-kernelande2e-multiproc-stubhad silently regressed on Task-33 baseline. Both are restored.Milestone:
e2e-shellruns the canonical scripted session againstkernel-fs.elf+shell-fs.img:46 commits: 1 plan + 32 task implementations + 5 debug fixes (mkfs subdir walker, init_shell clean-exit, WFI/sepc/drain trio, kmain Phase 2 console fds) + 1 README + 7 misc (style, docs, fix-ups).
Test plan
zig build test— all unit tests passzig build e2e— RV32I hello worldzig build e2e-mul— RV32IMA demozig build e2e-trap— privilege/trap demozig build e2e-hello-elf— Phase 1 §Definition of donezig build e2e-kernel— Phase 2 §Definition of donezig build e2e-multiproc-stub— Plan 3.B PID 1 + PID 2zig build e2e-fork— Plan 3.C fork/exec/wait/exitzig build e2e-plic-block— Plan 3.A IRQ + block round-tripzig build e2e-snake— snake demo deterministic inputzig build e2e-fs— Plan 3.D motd round-tripzig build e2e-shell— Plan 3.E milestone (this PR's headline gate)zig build riscv-tests— rv32ui/um/ua/mi/si conformancezig build wasm— wasm cross-build (deck demo)🤖 Generated with Claude Code