1449 lines of boot/bios/, tools/sign.pl and tools/padstage2.pl, plus the build rules, the disk image and the ISO entry that existed to carry them. Everything since roughly 2012 boots UEFI, and CSM -- the firmware-side BIOS emulation that lets a legacy loader run on such a machine at all -- has been going away since Intel dropped support for it in 2020. So this is not a path being retired early; it is one whose remaining hardware is already the exception. The cost was never the code, it was the tax on everything near it. Stage 2 had a 65-sector ceiling it overflowed silently -- 25440 bytes against 24576 -- and because the kernel is written at sector 1 + STAGE2_SECTORS, an overflowing loader was overwritten by the very kernel it was loading. The resolution list could not be shared with UEFI either: VBE mode setting is INT 10h, so matching it needed unreal mode, which is the riskiest code in the tree for a feature that already worked elsewhere. What replaces schos.img is the staged ESP, handed to QEMU as a virtual FAT volume. That is a development arrangement and not an installable disk; a real GPT image with a real FAT partition is the installer's job, and until it exists nothing here writes a disk a machine boots unaided. Secure Boot is untouched too -- the loader is unsigned. The ISO keeps one El Torito entry, the FAT image holding loader and kernel. It never booted from a USB stick via BIOS anyway: the firmware presents the stick as a hard disk while stage2main.c read the kernel back over ATAPI. The 32-bit multiboot entry in entry.S now has no caller and is left in place. Deleting kernel entry code is its own change with its own way of failing silently, and it should not ride along with this one. Ported with it: run.ps1, the six screenshot tools, and schcom_test.sh, each with its own OVMF varstore copy so no run inherits another's UEFI variables. Two things this shook out. OVMF now belongs in `all`: nothing boots without firmware, `make clean` wipes build/ including the copy fetched from the host, and a clean followed by a build left every run script throwing. And it cannot be listed as a prerequisite at the top of the file -- make expands prerequisites when it reads the line, and $(OVMF_CODE) is defined further down, so naming it there appends nothing and says nothing. Verified from a clean tree: 110 [ok] and no failures on the full suite, the ISO boots with no hard disk attached, schfs-check passes, schcom's end-to-end pixel test passes, and fbshot captures a real 1280x800 frame.
146 lines
5.3 KiB
Shell
146 lines
5.3 KiB
Shell
#!/bin/sh
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
# Run Schcom and a client inside SchOS, then check the actual pixels from the
|
|
# host with QEMU's screendump. The host has no connection to the compositor, so
|
|
# it is a real oracle: if the block is the right colour in the right place, the
|
|
# buffer really was shared, composited and presented.
|
|
#
|
|
# One display device on purpose (-vga std, no virtio-gpu): with two, screendump
|
|
# captures whichever QEMU considers active, which need not be the one /dev/fb0
|
|
# maps.
|
|
set -e
|
|
BUILD=${BUILD:-build}
|
|
mkdir -p "$BUILD"
|
|
|
|
LOG=$BUILD/schcom-test.log
|
|
PPM=${1:-$BUILD/schcom.ppm}
|
|
rm -f "$LOG" "$PPM"
|
|
|
|
# The firmware varstore is written by OVMF, so this run gets its own copy
|
|
# rather than sharing the pristine one every other run starts from.
|
|
cp -f build/OVMF_VARS.fd build/schcom-OVMF_VARS.fd
|
|
|
|
PORT=$(python3 - <<'EOF'
|
|
import socket
|
|
s=socket.socket(); s.bind(('127.0.0.1',0)); print(s.getsockname()[1]); s.close()
|
|
EOF
|
|
)
|
|
|
|
(
|
|
sleep 26
|
|
printf 'schcom --frames 24 &\n'; sleep 6
|
|
printf 'schcom-demo --colour 0x00C04020 --at 100 80 --size 240 160 --wait 40 &\n'; sleep 10
|
|
printf 'schcom-demo --colour 0x002060C0 --at 260 180 --size 240 160 --wait 30 &\n'; sleep 12
|
|
printf '\n'
|
|
sleep 40
|
|
) | timeout -k 5 120 qemu-system-x86_64 \
|
|
-machine pc-i440fx-9.2 -cpu qemu64,+rdtscp \
|
|
-drive if=pflash,format=raw,unit=0,readonly=on,file=build/OVMF_CODE.fd \
|
|
-drive if=pflash,format=raw,unit=1,file=build/schcom-OVMF_VARS.fd \
|
|
-drive file=fat:rw:build/esp,index=0,media=disk \
|
|
-drive file=build/schfs.img,format=raw,index=1,media=disk \
|
|
-m 1G -smp 1 \
|
|
-display none -vga std \
|
|
-monitor tcp:127.0.0.1:$PORT,server,nowait \
|
|
-chardev stdio,id=c0,logfile="$LOG" -serial chardev:c0 \
|
|
>/dev/null 2>&1 &
|
|
QPID=$!
|
|
|
|
# Wait until *both* clients have committed, then photograph. Breaking on the
|
|
# first one screenshots a desktop the second client has not reached yet, which
|
|
# looks exactly like a compositor that cannot handle two clients.
|
|
for i in $(seq 1 90); do
|
|
[ "$(grep -ac 'committed' "$LOG" 2>/dev/null)" -ge 2 ] && break
|
|
sleep 1
|
|
done
|
|
sleep 2
|
|
|
|
python3 - "$PORT" "$PPM" <<'EOF'
|
|
import socket, sys, time
|
|
port, ppm = int(sys.argv[1]), sys.argv[2]
|
|
s = socket.create_connection(('127.0.0.1', port), timeout=10)
|
|
time.sleep(0.5)
|
|
s.sendall(b'screendump ' + ppm.encode() + b'\n')
|
|
time.sleep(2.0)
|
|
s.close()
|
|
EOF
|
|
|
|
sleep 1
|
|
kill -9 $QPID 2>/dev/null || true
|
|
wait 2>/dev/null || true
|
|
|
|
echo "=== guest output ==="
|
|
sed 's/\x1b\[[0-9;=]*[A-Za-z]//g' "$LOG" | tr -d '\r' | grep -aE 'schcom|surface|committed|listening' | head -12
|
|
|
|
echo "=== pixel check (host oracle) ==="
|
|
python3 - "$PPM" <<'EOF'
|
|
import sys
|
|
path = sys.argv[1]
|
|
try:
|
|
data = open(path, 'rb').read()
|
|
except FileNotFoundError:
|
|
print("FAIL: no screendump produced"); sys.exit(1)
|
|
|
|
# Binary PPM: P6, whitespace-separated width height maxval, then RGB triples.
|
|
def token(buf, i):
|
|
while buf[i:i+1].isspace(): i += 1
|
|
j = i
|
|
while not buf[j:j+1].isspace(): j += 1
|
|
return buf[i:j], j
|
|
|
|
if data[:2] != b'P6':
|
|
print("FAIL: not a P6 PPM"); sys.exit(1)
|
|
i = 2
|
|
w, i = token(data, i); h, i = token(data, i); mx, i = token(data, i)
|
|
w, h = int(w), int(h)
|
|
px = data[i+1:]
|
|
print(f" screendump {w}x{h}")
|
|
|
|
def at(x, y):
|
|
o = (y*w + x)*3
|
|
return px[o], px[o+1], px[o+2]
|
|
|
|
def near(got, want, tol=8):
|
|
return all(abs(a-b) <= tol for a, b in zip(got, want))
|
|
|
|
fails = 0
|
|
inside = at(150, 120) # middle of the 240x160 block at 100,80
|
|
if near(inside, (0xC0, 0x40, 0x20)):
|
|
print(f" [ok] surface pixel at 150,120 is {inside} -- the client's colour")
|
|
else:
|
|
print(f" FAIL: 150,120 is {inside}, expected (192, 64, 32)"); fails += 1
|
|
|
|
desktop = at(20, 20) # compositor background 0x203040
|
|
if near(desktop, (0x20, 0x30, 0x40)):
|
|
print(f" [ok] desktop pixel at 20,20 is {desktop} -- compositor background")
|
|
else:
|
|
print(f" FAIL: 20,20 is {desktop}, expected (32, 48, 64)"); fails += 1
|
|
|
|
border = at(100, 80) # top-left of the first surface
|
|
if near(border, (0xFF, 0xFF, 0xFF)):
|
|
print(f" [ok] border pixel at 100,80 is {border} -- surface landed exactly")
|
|
else:
|
|
print(f" FAIL: 100,80 is {border}, expected white border"); fails += 1
|
|
|
|
# Second client, created later, so it is above the first where they overlap.
|
|
second = at(460, 320) # inside surface 2, clear of the pointer
|
|
if near(second, (0x20, 0x60, 0xC0)):
|
|
print(f" [ok] second surface at 460,320 is {second} -- two clients composited")
|
|
else:
|
|
print(f" FAIL: 460,320 is {second}, expected (32, 96, 192)"); fails += 1
|
|
|
|
# 260,180..340,240 is where the two overlap. The later surface must win: if
|
|
# stacking were ignored the first client's colour would show through here, and
|
|
# a compositor that cannot order two windows cannot host a window manager.
|
|
overlap = at(300, 220)
|
|
if near(overlap, (0x20, 0x60, 0xC0)):
|
|
print(f" [ok] overlap at 300,220 is {overlap} -- newer surface on top")
|
|
elif near(overlap, (0xC0, 0x40, 0x20)):
|
|
print(f" FAIL: overlap shows the older surface -- stacking order ignored"); fails += 1
|
|
else:
|
|
print(f" FAIL: overlap at 300,220 is {overlap}, expected the newer surface"); fails += 1
|
|
|
|
print("schcom end-to-end: PASSED" if not fails else f"schcom end-to-end: {fails} FAILED")
|
|
sys.exit(1 if fails else 0)
|
|
EOF
|