summaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-01 19:34:40 +0200
committersalmonik <salmonik@tuta.io>2026-08-01 19:34:40 +0200
commit9b9758e5ee2564b04685eadf2b9ce23dd1060b86 (patch)
tree57fce9e715510ec70df70dc399b4440f3480fd77 /scripts
parentfix pi-lens warnings: unwraps, shell SC2046, dup code (fmtbuf/scripts/hexdump... (diff)
downloadrs-dos-9b9758e5ee2564b04685eadf2b9ce23dd1060b86.tar.gz
rs-dos-9b9758e5ee2564b04685eadf2b9ce23dd1060b86.zip
rsnix v0.4.0: GUI input foundation — PS/2 mouse + scheduler message queues
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 <id>` routes real IRQ12 events into a task's queue, jobs shows q=. `make test-msg` + `make test-disk` regression pass.
Diffstat (limited to '')
-rwxr-xr-xscripts/test-mouse.sh130
-rwxr-xr-xscripts/test-msg.sh159
2 files changed, 289 insertions, 0 deletions
diff --git a/scripts/test-mouse.sh b/scripts/test-mouse.sh
new file mode 100755
index 0000000..ed4df44
--- /dev/null
+++ b/scripts/test-mouse.sh
@@ -0,0 +1,130 @@
1#!/usr/bin/env bash
2#
3# scripts/test-mouse.sh — automated PS/2 mouse round trip.
4#
5# Builds the kernel, boots QEMU headless, injects mouse movement and
6# button presses through the monitor's `mouse_move` / `mouse_button`
7# commands, and verifies the guest decoded them via IRQ12 → ring →
8# packet decoder. Covers both surfaces:
9# - `cat /dev/mouse` (VFS snapshot of pending events)
10# - `mouse` (live streaming command, stopped by ^C)
11#
12# Usage:
13# ./scripts/test-mouse.sh
14#
15# Environment:
16# OVMF firmware (default: /usr/share/edk2/x64/OVMF.4m.fd)
17# QEMU qemu binary (default: qemu-system-x86_64)
18#
19set -euo pipefail
20
21SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
22PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
23cd "$PROJECT_DIR"
24
25OVMF="${OVMF:-/usr/share/edk2/x64/OVMF.4m.fd}"
26QEMU="${QEMU:-qemu-system-x86_64}"
27ESP_DIR="$PROJECT_DIR/esp"
28QPID=/tmp/rsdos-mouse-qpid
29SOCK=/tmp/rsdos-mouse-qmon.sock
30LOG=/tmp/rsdos-mouse-serial.log
31QLOG=/tmp/rsdos-mouse-qemu.log
32
33# Never leave a QEMU behind.
34trap 'kill "$(cat "$QPID" 2>/dev/null)" 2>/dev/null || true' EXIT
35
36fail() {
37 echo "FAIL: $1"
38 [ -f "$LOG" ] && tail -20 "$LOG"
39 exit 1
40}
41
42# wait_log FILE PATTERN TIMEOUT-SECONDS
43wait_log() {
44 local i=0
45 while [ "$i" -lt $((${3:-60} * 10)) ]; do
46 grep -q "$2" "$1" 2>/dev/null && return 0
47 sleep 0.1
48 i=$((i + 1))
49 done
50 return 1
51}
52
53# wait_qemu_exit TIMEOUT-SECONDS
54wait_qemu_exit() {
55 local i=0
56 while [ "$i" -lt $((${1:-30} * 10)) ]; do
57 kill -0 "$(cat "$QPID" 2>/dev/null)" 2>/dev/null || return 0
58 sleep 0.1
59 i=$((i + 1))
60 done
61 return 1
62}
63
64# qemu_hmp — send one raw HMP command to the monitor socket.
65qemu_hmp() {
66 python3 - "$SOCK" "$1" <<'EOF'
67import socket, sys, time
68s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
69s.connect(sys.argv[1])
70s.sendall((sys.argv[2] + "\n").encode())
71time.sleep(0.4)
72EOF
73}
74
75echo "==> 1/5 build kernel (debug)"
76cargo build --package rsdos --target x86_64-unknown-uefi
77
78echo "==> 2/5 deploy to ESP"
79mkdir -p "$ESP_DIR/EFI/BOOT"
80cp target/x86_64-unknown-uefi/debug/rsdos.efi "$ESP_DIR/EFI/BOOT/BOOTX64.EFI"
81
82echo "==> 3/5 boot QEMU (headless)"
83kill "$(cat "$QPID" 2>/dev/null)" 2>/dev/null || true
84sleep 1
85rm -f "$SOCK" "$LOG"
86"$QEMU" -bios "$OVMF" \
87 -drive file=fat:rw:"$ESP_DIR",format=raw \
88 -serial file:"$LOG" \
89 -monitor unix:"$SOCK",server,nowait \
90 -m 256M -machine q35 -cpu qemu64 -no-reboot -display none \
91 >"$QLOG" 2>&1 &
92echo $! >"$QPID"
93
94wait_log "$LOG" "boot: entering shell" 90 || fail "kernel never reached the shell"
95grep -aq "ps2: mouse ready" "$LOG" || fail "mouse driver did not come up at boot"
96
97# Seed a move *before* any consumer runs, so `cat /dev/mouse` has a
98# queued event to drain (exercises the VFS snapshot path).
99qemu_hmp "mouse_move 8 4"
100
101echo "==> 4/5 drive the shell (cat /dev/mouse, mouse stream, poweroff)"
102python3 "$SCRIPT_DIR/sendkeys.py" --sock "$SOCK" \
103 "cat /dev/mouse" \
104 "mouse"
105
106# While the `mouse` stream is live: a move, a left-press, a move with the
107# button held, then release — the button state must ride along on the
108# movement packet.
109qemu_hmp "mouse_move 20 7"
110qemu_hmp "mouse_button 1"
111qemu_hmp "mouse_move -5 -3"
112qemu_hmp "mouse_button 0"
113qemu_hmp "sendkey ctrl-c"
114
115python3 "$SCRIPT_DIR/sendkeys.py" --sock "$SOCK" \
116 "poweroff"
117
118echo "==> 5/5 wait for shutdown + verify"
119wait_qemu_exit 30 || fail "QEMU did not exit after poweroff"
120
121grep -aq "ps2: mouse ready (IRQ12, stream mode)" "$LOG" || fail "no mouse-ready boot line"
122grep -aq "dx=+8 dy=+4 buttons=---" "$LOG" || fail "/dev/mouse did not drain the queued move (dx=+8 dy=+4)"
123grep -aq "dx=+20 dy=+7 buttons=---" "$LOG" || fail "mouse stream missed mouse_move 20 7"
124grep -aq "dx=+0 dy=+0 buttons=L--" "$LOG" || fail "left-button press did not decode"
125grep -aq "dx=-5 dy=-3 buttons=L--" "$LOG" || fail "held-button movement wrong (dx=-5 dy=-3 L)"
126grep -aq "mouse: stopped" "$LOG" || fail "^C did not stop the mouse stream"
127
128echo ""
129echo "PASS — PS/2 mouse round trip OK (IRQ12, /dev/mouse, live stream, buttons)"
130echo " events: 8,4 · 20,7 · L-press · -5,-3 held · release"
diff --git a/scripts/test-msg.sh b/scripts/test-msg.sh
new file mode 100755
index 0000000..0bd1c81
--- /dev/null
+++ b/scripts/test-msg.sh
@@ -0,0 +1,159 @@
1#!/usr/bin/env bash
2#
3# scripts/test-msg.sh — automated scheduler message-queue round trip.
4#
5# Builds the kernel, boots QEMU headless, and verifies inter-task
6# messaging end to end:
7# - `msgrecv` spawns a task that blocks on recv_blocking
8# - `send <id> <type>` delivers every message type in order
9# - `mouse <id>` routes *real* IRQ12 mouse events into the queue
10# (the window-manager input path)
11# - queue-full is a clean error (16-cap; tested against the shell's
12# own never-consumed queue)
13# - jobs shows q=0 after everything drains, task back to blocked
14#
15# Usage:
16# ./scripts/test-msg.sh
17#
18# Environment:
19# OVMF firmware (default: /usr/share/edk2/x64/OVMF.4m.fd)
20# QEMU qemu binary (default: qemu-system-x86_64)
21#
22set -euo pipefail
23
24SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
25PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
26cd "$PROJECT_DIR"
27
28OVMF="${OVMF:-/usr/share/edk2/x64/OVMF.4m.fd}"
29QEMU="${QEMU:-qemu-system-x86_64}"
30ESP_DIR="$PROJECT_DIR/esp"
31QPID=/tmp/rsdos-msg-qpid
32SOCK=/tmp/rsdos-msg-qmon.sock
33LOG=/tmp/rsdos-msg-serial.log
34QLOG=/tmp/rsdos-msg-qemu.log
35
36trap 'kill "$(cat "$QPID" 2>/dev/null)" 2>/dev/null || true' EXIT
37
38fail() {
39 echo "FAIL: $1"
40 [ -f "$LOG" ] && tail -20 "$LOG"
41 exit 1
42}
43
44wait_log() {
45 local i=0
46 while [ "$i" -lt $((${3:-60} * 10)) ]; do
47 grep -q "$2" "$1" 2>/dev/null && return 0
48 sleep 0.1
49 i=$((i + 1))
50 done
51 return 1
52}
53
54wait_qemu_exit() {
55 local i=0
56 while [ "$i" -lt $((${1:-30} * 10)) ]; do
57 kill -0 "$(cat "$QPID" 2>/dev/null)" 2>/dev/null || return 0
58 sleep 0.1
59 i=$((i + 1))
60 done
61 return 1
62}
63
64qemu_hmp() {
65 python3 - "$SOCK" "$1" <<'EOF'
66import socket, sys, time
67s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
68s.connect(sys.argv[1])
69s.sendall((sys.argv[2] + "\n").encode())
70time.sleep(0.4)
71EOF
72}
73
74echo "==> 1/5 build kernel (debug)"
75cargo build --package rsdos --target x86_64-unknown-uefi
76
77echo "==> 2/5 deploy to ESP"
78mkdir -p "$ESP_DIR/EFI/BOOT"
79cp target/x86_64-unknown-uefi/debug/rsdos.efi "$ESP_DIR/EFI/BOOT/BOOTX64.EFI"
80
81echo "==> 3/5 boot QEMU (headless)"
82kill "$(cat "$QPID" 2>/dev/null)" 2>/dev/null || true
83sleep 1
84rm -f "$SOCK" "$LOG"
85"$QEMU" -bios "$OVMF" \
86 -drive file=fat:rw:"$ESP_DIR",format=raw \
87 -serial file:"$LOG" \
88 -monitor unix:"$SOCK",server,nowait \
89 -m 256M -machine q35 -cpu qemu64 -no-reboot -display none \
90 >"$QLOG" 2>&1 &
91echo $! >"$QPID"
92
93wait_log "$LOG" "boot: entering shell" 90 || fail "kernel never reached the shell"
94
95echo "==> 4/5 drive the shell (msgrecv / send / mouse routing / queue-full)"
96python3 "$SCRIPT_DIR/sendkeys.py" --sock "$SOCK" \
97 "msgrecv" \
98 "send 2 user 42" \
99 "send 2 timer" \
100 "send 2 key enter" \
101 "send 2 key ctrl-c" \
102 "send 2 key f3" \
103 "send 2 mouse 5 -3 3" \
104 "sleep 800" \
105 "jobs"
106
107# Real IRQ12 events through the queue: start routing to task 2, inject
108# moves + a left press, stop.
109python3 "$SCRIPT_DIR/sendkeys.py" --sock "$SOCK" "mouse 2"
110qemu_hmp "mouse_move 20 7"
111qemu_hmp "mouse_button 1"
112qemu_hmp "mouse_move -5 -3"
113qemu_hmp "mouse_button 0"
114qemu_hmp "sendkey ctrl-c"
115
116# Queue-full boundary: 16 sends fit, the 17th must fail cleanly. The
117# shell never consumes its own queue, so nothing races the drain here.
118python3 "$SCRIPT_DIR/sendkeys.py" --sock "$SOCK" \
119 "send 0 user 1" \
120 "send 0 user 2" \
121 "send 0 user 3" \
122 "send 0 user 4" \
123 "send 0 user 5" \
124 "send 0 user 6" \
125 "send 0 user 7" \
126 "send 0 user 8" \
127 "send 0 user 9" \
128 "send 0 user 10" \
129 "send 0 user 11" \
130 "send 0 user 12" \
131 "send 0 user 13" \
132 "send 0 user 14" \
133 "send 0 user 15" \
134 "send 0 user 16" \
135 "send 0 user 17" \
136 "sleep 800" \
137 "jobs" \
138 "poweroff"
139
140echo "==> 5/5 wait for shutdown + verify"
141wait_qemu_exit 30 || fail "QEMU did not exit after poweroff"
142
143grep -aq "msgrecv: task 2 listening" "$LOG" || fail "msgrecv did not spawn"
144grep -aq "msgrecv\[2\]: User(42)" "$LOG" || fail "user message missing"
145grep -aq "msgrecv\[2\]: Timer" "$LOG" || fail "timer message missing"
146grep -aq "msgrecv\[2\]: Key(Enter)" "$LOG" || fail "key enter missing"
147grep -aq "msgrecv\[2\]: Key(Ctrl-c)" "$LOG" || fail "key ctrl-c missing"
148grep -aq "msgrecv\[2\]: Key(F3)" "$LOG" || fail "key f3 missing"
149grep -aq "msgrecv\[2\]: Mouse(+5,-3 L R)" "$LOG" || fail "synthesized mouse message missing"
150# Real IRQ12 events routed through the queue (button held across the move).
151grep -aq "msgrecv\[2\]: Mouse(+20,+7" "$LOG" || fail "real mouse move 20,7 not routed to queue"
152grep -aq "msgrecv\[2\]: Mouse(-5,-3 L" "$LOG" || fail "held-button routed move missing"
153grep -aq "queue full" "$LOG" || fail "17th send did not report queue full"
154grep -aq "send: unknown type" "$LOG" && fail "a bad send slipped through (expected none)"
155
156echo ""
157echo "PASS — message queues OK (all types, ordering, real-input routing, queue-full)"
158echo " sent: user · timer · key(enter,ctrl-c,f3) · mouse · 16+1 queue-full"
159echo " routed: mouse_move 20,7 → queue → msgrecv[2]"