$ git commit -m "$(cat <<'EOF' Phase 3: ATA disk driver + tinyfs filesystem Adds a polled ATA PIO (LBA28) driver and tinyfs: a minimal flat-namespace filesystem (superblock + free-block bitmap + fixed inode table + direct data blocks, 512-byte blocks, 4KB max file size). The core format logic in kernel/fs/tinyfs.c is compiled twice - freestanding for the kernel (via ata.c) and natively for tools/mkfs_tinyfs, a host build-time tool that formats the disk image and seeds it with an initial file - so there is exactly one implementation of the on-disk format, not two. Also fixes a latent bug: the raw disk-loaded kernel image was never guaranteed to have zero-initialized .bss (objcopy -O binary drops NOBITS sections), so entry.S now zeroes .bss explicitly before calling kmain. kmain mounts tinyfs at boot and the self-test now covers list/read of the seeded file plus a create/write/read/delete roundtrip, verified green in headless QEMU via tools/run_tests.sh. EOF )"
[main a3b80d1] Phase 3: ATA disk driver + tinyfs filesystem 13 files changed, 620 insertions(+), 8 deletions(-) create mode 100644 docs/tinyfs.md create mode 100644 kernel/ata.c create mode 100644 kernel/ata.h create mode 100644 kernel/fs/tinyfs.c create mode 100644 kernel/fs/tinyfs.h create mode 100644 kernel/fs/tinyfs_disk.c create mode 100644 kernel/fs/tinyfs_disk.h create mode 100644 tools/mkfs_tinyfs.c
{
"taskId": "4",
"status": "completed"
}Updated task #4 status
{
"taskId": "5",
"status": "in_progress"
}Updated task #5 status
$ which socat nc ncat python3 2>/dev/null; echo "---"; qemu-system-i386 -monitor help 2>&1 | head -5; echo "---"; echo 'test' | timeout 1 bash -c 'exec 3<>/dev/tcp/127.0.0.1/1 2>&1' ; echo "bash /dev/tcp support: $?"
/bin/socat /bin/nc /bin/python3 --- qemu-system-i386: -monitor help: 'help' is not a valid char driver qemu-system-i386: -monitor help: parse error: help --- bash: connect: Connection refused bash: line 1: /dev/tcp/127.0.0.1/1: Connection refused bash /dev/tcp support: 1
$ cd /home/martin/workspace/prototypes/boot-to-claude
rm -f build/mon.sock build/serial.log
timeout 6 qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial file:build/serial.log -monitor unix:build/mon.sock,server,nowait -no-reboot &
QPID=$!
for i in $(seq 1 25); do [ -S build/mon.sock ] && break; sleep 0.2; done
sleep 0.5
python3 - <<'EOF'
import socket, time
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect("build/mon.sock")
time.sleep(0.2)
print("banner:", s.recv(4096))
s.sendall(b"sendkey a\n")
time.sleep(0.2)
print("resp:", s.recv(4096))
s.close()
EOF
wait $QPID 2>/dev/null
cat build/serial.logbanner: b"QEMU 10.1.0 monitor - type 'help' for more information\r\n(qemu) " resp: b's\x1b[K\x1b[Dse\x1b[K\x1b[D\x1b[Dsen\x1b[K\x1b[D\x1b[D\x1b[Dsend\x1b[K\x1b[D\x1b[D\x1b[D\x1b[Dsendk\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Dsendke\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Dsendkey\x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Dsendkey \x1b[K\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[Dsendkey a\x1b[K\r\n(qemu) ' qemu-system-i386: terminating on signal 15 from pid 1877182 (timeout) tiny-os: kernel entered selftest: running SELFTEST PASS
$ mkdir -p /home/martin/workspace/prototypes/boot-to-claude/kernel/shell
(Bash completed with no output)
#pragma once /* Blocks until a key is pressed and returns its ASCII value. Polled PS/2, * scancode set 1, US layout subset - no interrupts, matching the rest of * this single-tasking kernel. */ char keyboard_getchar(void);
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/keyboard.h (file state is current in your context — no need to Read it back)
#include "keyboard.h"
#include <stddef.h>
#include <stdint.h>
#define KBD_DATA 0x60
#define KBD_STATUS 0x64
#define SC_LSHIFT 0x2A
#define SC_RSHIFT 0x36
static inline uint8_t inb(uint16_t port) {
uint8_t ret;
__asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
return ret;
}
/* Scancode set 1, indexed by make code. 0 = unmapped/non-printable. */
static const char lower_table[] = {
0, 27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
'\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
0 /*ctrl*/, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`',
0 /*lshift*/, '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/',
0 /*rshift*/, '*', 0 /*alt*/, ' ',
};
static const char upper_table[] = {
0, 27, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b',
'\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n',
0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~',
0, '|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?',
0, '*', 0, ' ',
};
#define TABLE_LEN (sizeof(lower_table) / sizeof(lower_table[0]))
static int shift_down = 0;
static int scancode_ready(void) {
return inb(KBD_STATUS) & 1;
}
char keyboard_getchar(void) {
for (;;) {
while (!scancode_ready()) {}
uint8_t sc = inb(KBD_DATA);
int release = sc & 0x80;
uint8_t code = sc & 0x7F;
if (code == SC_LSHIFT || code == SC_RSHIFT) {
shift_down = !release;
continue;
}
if (release || code >= TABLE_LEN) {
continue;
}
char c = shift_down ? upper_table[code] : lower_table[code];
if (c == 0) {
continue;
}
return c;
}
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/keyboard.c (file state is current in your context — no need to Read it back)
- void console_putc(char c) {
- if (c == '\n') {
- cursor_col = 0;
- cursor_row++;
- } else {
+ void console_putc(char c) {
+ if (c == '\n') {
+ cursor_col = 0;
+ cursor_row++;
+ } else if (c == '\b') {
+ if (cursor_col > 0) {
+ cursor_col--;
+ } else if (cursor_row > 0) {
+ cursor_row--;
+ cursor_col = VGA_COLS - 1;
+ }
+ } else {The file /home/martin/workspace/prototypes/boot-to-claude/kernel/console.c has been updated successfully. (file state is current in your context — no need to Read it back)
#pragma once /* Interactive command loop. Never returns. */ void shell_run(void);
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/shell/shell.h (file state is current in your context — no need to Read it back)
#include "shell.h"
#include <stdint.h>
#include "console.h"
#include "fs/tinyfs.h"
#include "keyboard.h"
#include "serial.h"
#include "string.h"
#define LINE_MAX 128
static inline void outb(uint16_t port, uint8_t val) {
__asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
}
static inline uint8_t inb(uint16_t port) {
uint8_t ret;
__asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
return ret;
}
static void echo_char(char c) {
console_putc(c);
serial_putc(c);
}
static void print_line(const char *s) {
console_write(s);
console_write("\n");
serial_write(s);
serial_write("\n");
}
static int read_line(char *buf, int max_len) {
int len = 0;
for (;;) {
char c = keyboard_getchar();
if (c == '\n') {
echo_char('\n');
buf[len] = '\0';
return len;
} else if (c == '\b') {
if (len > 0) {
len--;
echo_char('\b');
echo_char(' ');
echo_char('\b');
}
} else if (c >= 32 && c < 127 && len < max_len - 1) {
buf[len++] = c;
echo_char(c);
}
}
}
static void skip_spaces(const char **p) {
while (**p == ' ') {
(*p)++;
}
}
static int next_word(const char **p, char *out, int max_len) {
skip_spaces(p);
int n = 0;
while (**p && **p != ' ' && n < max_len - 1) {
out[n++] = *(*p)++;
}
out[n] = '\0';
return n;
}
static void cmd_help(void) {
print_line("commands: ls, cat <file>, write <file> <text>, rm <file>, help, clear, reboot");
}
static void cmd_ls(void) {
tinyfs_dirent_t entries[TINYFS_NUM_INODES];
int n = tinyfs_list(entries, TINYFS_NUM_INODES);
char numbuf[16];
for (int i = 0; i < n; i++) {
console_write(entries[i].name);
console_write(" ");
console_write(itoa((int)entries[i].size, numbuf, 10));
console_write("\n");
serial_write(entries[i].name);
serial_write(" ");
serial_write(itoa((int)entries[i].size, numbuf, 10));
serial_write("\n");
}
}
static void cmd_cat(const char *name) {
static char buf[TINYFS_MAX_FILE_SIZE + 1];
uint32_t len = 0;
if (tinyfs_read(name, buf, sizeof(buf) - 1, &len) != 0) {
print_line("cat: no such file");
return;
}
buf[len] = '\0';
console_write(buf);
serial_write(buf);
if (len == 0 || buf[len - 1] != '\n') {
console_write("\n");
serial_write("\n");
}
}
static void cmd_write(const char *name, const char *contents) {
tinyfs_create(name); /* ignored if it already exists - write() below overwrites it */
uint32_t len = (uint32_t)strlen(contents);
if (len > TINYFS_MAX_FILE_SIZE) {
print_line("write: content too long");
return;
}
if (tinyfs_write(name, contents, len) != 0) {
print_line("write: failed (no space or bad name)");
return;
}
print_line("write: ok");
}
static void cmd_rm(const char *name) {
if (tinyfs_delete(name) != 0) {
print_line("rm: no such file");
} else {
print_line("rm: ok");
}
}
static void cmd_reboot(void) {
print_line("rebooting...");
uint8_t status;
do {
status = inb(0x64);
} while (status & 0x02);
outb(0x64, 0xFE); /* pulse the keyboard controller's reset line */
for (;;) {
__asm__ volatile ("hlt");
}
}
void shell_run(void) {
static char line[LINE_MAX];
print_line("tiny-os shell. type 'help' for commands.");
for (;;) {
console_write("> ");
serial_write("> ");
read_line(line, sizeof(line));
const char *p = line;
char cmd[32];
if (next_word(&p, cmd, sizeof(cmd)) == 0) {
continue;
}
if (strcmp(cmd, "help") == 0) {
cmd_help();
} else if (strcmp(cmd, "ls") == 0) {
cmd_ls();
} else if (strcmp(cmd, "clear") == 0) {
console_clear();
} else if (strcmp(cmd, "reboot") == 0) {
cmd_reboot();
} else if (strcmp(cmd, "cat") == 0) {
char name[TINYFS_NAME_MAX];
if (next_word(&p, name, sizeof(name)) == 0) {
print_line("usage: cat <file>");
} else {
cmd_cat(name);
}
} else if (strcmp(cmd, "rm") == 0) {
char name[TINYFS_NAME_MAX];
if (next_word(&p, name, sizeof(name)) == 0) {
print_line("usage: rm <file>");
} else {
cmd_rm(name);
}
} else if (strcmp(cmd, "write") == 0) {
char name[TINYFS_NAME_MAX];
if (next_word(&p, name, sizeof(name)) == 0) {
print_line("usage: write <file> <text>");
} else {
skip_spaces(&p);
cmd_write(name, p);
}
} else {
print_line("unknown command (type 'help')");
}
}
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/shell/shell.c (file state is current in your context — no need to Read it back)
- #include "console.h" - #include "fs/tinyfs_disk.h" - #include "gdt.h" - #include "idt.h" - #include "memory.h" - #include "selftest.h" - #include "serial.h" + #include "console.h" + #include "fs/tinyfs_disk.h" + #include "gdt.h" + #include "idt.h" + #include "memory.h" + #include "selftest.h" + #include "serial.h" + #include "shell/shell.h"
The file /home/martin/workspace/prototypes/boot-to-claude/kernel/kmain.c has been updated successfully. (file state is current in your context — no need to Read it back)
- selftest_run();
-
- for (;;) {
- __asm__ volatile ("hlt");
- }
- }
+ selftest_run();
+
+ shell_run();
+ }The file /home/martin/workspace/prototypes/boot-to-claude/kernel/kmain.c has been updated successfully. (file state is current in your context — no need to Read it back)
- KERNEL_C_SRCS := kernel/kmain.c kernel/console.c kernel/serial.c kernel/gdt.c \ - kernel/idt.c kernel/string.c kernel/memory.c kernel/selftest.c \ - kernel/ata.c kernel/fs/tinyfs.c kernel/fs/tinyfs_disk.c + KERNEL_C_SRCS := kernel/kmain.c kernel/console.c kernel/serial.c kernel/gdt.c \ + kernel/idt.c kernel/string.c kernel/memory.c kernel/selftest.c \ + kernel/ata.c kernel/fs/tinyfs.c kernel/fs/tinyfs_disk.c \ + kernel/keyboard.c kernel/shell/shell.c
The file /home/martin/workspace/prototypes/boot-to-claude/Makefile has been updated successfully. (file state is current in your context — no need to Read it back)
- $(BUILD) $(BUILD)/fs: - mkdir -p $@ - - $(BOOT_BIN): boot/boot.S | $(BUILD) - $(AS) -f bin -o $@ $< - - $(STAGE2_BIN): boot/stage2.S | $(BUILD) - $(AS) -f bin -o $@ $< - - $(BUILD)/%.o: kernel/%.S | $(BUILD) $(BUILD)/fs - $(AS) -f elf32 -o $@ $< - - $(BUILD)/%.o: kernel/%.c | $(BUILD) $(BUILD)/fs - $(CC) $(CFLAGS) -c -o $@ $< + $(BUILD) $(BUILD)/fs $(BUILD)/shell: + mkdir -p $@ + + $(BOOT_BIN): boot/boot.S | $(BUILD) + $(AS) -f bin -o $@ $< + + $(STAGE2_BIN): boot/stage2.S | $(BUILD) + $(AS) -f bin -o $@ $< + + $(BUILD)/%.o: kernel/%.S | $(BUILD) $(BUILD)/fs $(BUILD)/shell + $(AS) -f elf32 -o $@ $< + + $(BUILD)/%.o: kernel/%.c | $(BUILD) $(BUILD)/fs $(BUILD)/shell + $(CC) $(CFLAGS) -c -o $@ $<
The file /home/martin/workspace/prototypes/boot-to-claude/Makefile has been updated successfully. (file state is current in your context — no need to Read it back)
$ make clean >/dev/null && make 2>&1
mkdir -p build nasm -f bin -o build/boot.bin boot/boot.S nasm -f bin -o build/stage2.bin boot/stage2.S mkdir -p build/fs mkdir -p build/shell nasm -f elf32 -o build/entry.o kernel/entry.S nasm -f elf32 -o build/idt_stubs.o kernel/idt_stubs.S gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/kmain.o kernel/kmain.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/console.o kernel/console.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/serial.o kernel/serial.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/gdt.o kernel/gdt.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/idt.o kernel/idt.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/string.o kernel/string.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/memory.o kernel/memory.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/selftest.o kernel/selftest.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/ata.o kernel/ata.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/fs/tinyfs.o kernel/fs/tinyfs.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/fs/tinyfs_disk.o kernel/fs/tinyfs_disk.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/keyboard.o kernel/keyboard.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/shell/shell.o kernel/shell/shell.c ld -m elf_i386 -T kernel/link.ld -nostdlib -o build/kernel.elf build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o ld: warning: build/kernel.elf has a LOAD segment with RWX permissions objcopy -O binary build/kernel.elf build/kernel.bin gcc -Wall -Wextra -O2 -c -o build/host_tinyfs.o kernel/fs/tinyfs.c gcc -Wall -Wextra -O2 -c -o build/host_mkfs_tinyfs.o tools/mkfs_tinyfs.c gcc -o build/mkfs_tinyfs build/host_tinyfs.o build/host_mkfs_tinyfs.o dd if=/dev/zero of=build/disk.img bs=512 count=4096 status=none dd if=build/boot.bin of=build/disk.img conv=notrunc status=none dd if=build/stage2.bin of=build/disk.img bs=512 seek=1 conv=notrunc status=none dd if=build/kernel.bin of=build/disk.img bs=512 seek=5 conv=notrunc status=none build/mkfs_tinyfs build/disk.img mkfs_tinyfs: formatted build/disk.img (3958 data blocks available)
$ stat -c%s build/kernel.bin timeout 4 qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial stdio -no-reboot; echo "exit=$?"
13504 tiny-os: kernel entered selftest: running SELFTEST PASS tiny-os shell. type 'help' for commands. > qemu-system-i386: terminating on signal 15 from pid 1878330 (timeout) exit=124
#!/usr/bin/env python3
"""Types text into a running QEMU guest via its HMP monitor's `sendkey`
command, over a unix socket (-monitor unix:PATH,server,nowait). Used by
tools/run_tests.sh to drive the kernel shell headlessly - there is no
other way to feed the polled PS/2 keyboard driver without real hardware
or a display.
"""
import socket
import sys
import time
KEYMAP = {
" ": "spc",
".": "dot",
"\n": "ret",
"-": "minus",
"_": "shift-minus",
",": "comma",
"/": "slash",
}
def key_name(c):
if c in KEYMAP:
return KEYMAP[c]
if c.isalpha():
return c.lower()
if c.isdigit():
return c
raise ValueError(f"qemu_type: no key mapping for {c!r}")
def main():
if len(sys.argv) != 3:
print(f"usage: {sys.argv[0]} <monitor-socket> <text>", file=sys.stderr)
sys.exit(1)
sock_path, text = sys.argv[1], sys.argv[2]
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect(sock_path)
time.sleep(0.2)
s.recv(4096) # monitor banner
for c in text:
s.sendall(f"sendkey {key_name(c)}\n".encode())
time.sleep(0.05)
s.recv(4096)
s.close()
if __name__ == "__main__":
main()
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/tools/qemu_type.py (file state is current in your context — no need to Read it back)
#!/usr/bin/env bash
# Boots the kernel headlessly in QEMU, checks the kernel self-test result,
# then drives the interactive shell via QEMU's monitor `sendkey` (see
# tools/qemu_type.py - there's no other way to feed the polled PS/2
# keyboard driver without real hardware) and asserts on serial output.
set -euo pipefail
cd "$(dirname "$0")/.."
make >/dev/null
BUILD=build
LOG="$BUILD/serial.log"
MON="$BUILD/qemu-monitor.sock"
rm -f "$LOG" "$MON"
qemu-system-i386 \
-drive file="$BUILD/disk.img",format=raw \
-display none \
-serial file:"$LOG" \
-monitor unix:"$MON",server,nowait \
-no-reboot &
QEMU_PID=$!
cleanup() {
kill "$QEMU_PID" 2>/dev/null || true
wait "$QEMU_PID" 2>/dev/null || true
}
trap cleanup EXIT
fail() {
echo "run_tests: FAIL - $1"
echo "--- serial log ---"
cat "$LOG" 2>/dev/null || true
exit 1
}
wait_for() {
local pattern="$1"
local max_tries="${2:-50}" # 50 * 0.2s = 10s
local i=0
while ! grep -qF "$pattern" "$LOG" 2>/dev/null; do
i=$((i + 1))
if [ "$i" -ge "$max_tries" ]; then
fail "timed out waiting for '$pattern'"
fi
sleep 0.2
done
}
type_line() {
python3 tools/qemu_type.py "$MON" "$1"$'\n'
}
for i in $(seq 1 25); do
[ -S "$MON" ] && break
sleep 0.2
done
[ -S "$MON" ] || fail "qemu monitor socket never appeared"
wait_for "SELFTEST PASS"
wait_for "type 'help'"
type_line "ls"
wait_for "hello.txt"
type_line "cat hello.txt"
wait_for "Hello, tinyfs!"
type_line "write greet.txt hi there"
wait_for "write: ok"
type_line "cat greet.txt"
wait_for "hi there"
type_line "rm greet.txt"
wait_for "rm: ok"
type_line "cat greet.txt"
wait_for "cat: no such file"
type_line "help"
wait_for "commands:"
echo "run_tests: PASS"
The file /home/martin/workspace/prototypes/boot-to-claude/tools/run_tests.sh has been updated successfully. (file state is current in your context — no need to Read it back)
$ chmod +x /home/martin/workspace/prototypes/boot-to-claude/tools/run_tests.sh /home/martin/workspace/prototypes/boot-to-claude/tools/qemu_type.py ./tools/run_tests.sh
run_tests: PASS qemu-system-i386: terminating on signal 15 from pid 1881176 (bash)
$ git add -A && git status --short
M Makefile M kernel/console.c A kernel/keyboard.c A kernel/keyboard.h M kernel/kmain.c A kernel/shell/shell.c A kernel/shell/shell.h A tools/qemu_type.py M tools/run_tests.sh
$ git commit -m "$(cat <<'EOF' Phase 4: polled keyboard driver + interactive shell Adds a polled PS/2 keyboard driver (scancode set 1, US layout, shift support) and a shell REPL: ls, cat, write, rm, help, clear, reboot, backed by tinyfs. console_putc now handles backspace so the shell's line editor can erase characters on both VGA and serial. tools/run_tests.sh now drives the shell headlessly through QEMU's HMP monitor `sendkey` command (tools/qemu_type.py) over a unix socket - the only way to feed the polled keyboard driver without real hardware or a display - and asserts on serial output for each command. EOF )"
[main 594867b] Phase 4: polled keyboard driver + interactive shell 9 files changed, 411 insertions(+), 21 deletions(-) create mode 100644 kernel/keyboard.c create mode 100644 kernel/keyboard.h create mode 100644 kernel/shell/shell.c create mode 100644 kernel/shell/shell.h create mode 100755 tools/qemu_type.py