SchOS/tools/windows/ttyshot.ps1
Nexoniarz 651899220c boot: the BIOS loader is gone
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.
2026-08-11 10:20:28 +02:00

137 lines
5.7 KiB
PowerShell

# Photograph the framebuffer console after driving it with escape sequences.
#
# The serial line and the framebuffer are two different consumers of console
# output, and they behave differently on purpose: escapes are passed through
# to the serial line verbatim (a real terminal emulator is on the other end
# and understands them better than we do) and *interpreted* for the
# framebuffer. So the serial transcript can never tell you whether the escape
# parser works -- it contains the raw bytes either way. Only a picture can.
#
# tools/ttyshot.ps1
param(
[int]$BootSeconds = 22,
[string]$Out = "build/ttyshot.ppm",
[string]$Qemu
)
# QEMU, on whichever host this is.
#
# PowerShell 7 runs on Linux, macOS and the BSDs as well as Windows, so these
# scripts are usable from all of them -- but the old default was a hardcoded
# scoop path under $env:USERPROFILE, which exists on exactly one of those. PATH
# first, because a package-managed qemu is on it everywhere; the scoop location
# is only a fallback for the Windows install that does not add itself to PATH.
function Find-Qemu {
$exe = if ($IsWindows -or $env:OS -eq 'Windows_NT') { 'qemu-system-x86_64.exe' }
else { 'qemu-system-x86_64' }
$onPath = Get-Command $exe -ErrorAction SilentlyContinue
if ($onPath) { return $onPath.Source }
if ($env:USERPROFILE) {
$scoop = Join-Path (Join-Path $env:USERPROFILE 'scoop/apps/qemu/current') $exe
if (Test-Path $scoop) { return $scoop }
}
foreach ($p in @('/usr/bin', '/usr/local/bin', '/opt/homebrew/bin')) {
$c = Join-Path $p $exe
if (Test-Path $c) { return $c }
}
throw "qemu-system-x86_64 not found on PATH. Install QEMU, or pass -Qemu <path>."
}
if (-not $Qemu) { $Qemu = Find-Qemu }
$ErrorActionPreference = 'Stop'
# Repo root is two levels up: this script lives in tools/windows/.
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
$esp = Join-Path $root 'build/esp'
# OVMF and the staged ESP, because there is one boot path now. The old
# schos.img was a raw disk with the boot sector at LBA 0 -- a layout only the
# BIOS loader could use, and it went with it.
$code = Join-Path $root 'build/OVMF_CODE.fd'
$vars = Join-Path $root 'build/OVMF_VARS.fd'
foreach ($f in @($code, $vars)) {
if (-not (Test-Path $f)) { throw "no $f -- run 'make qemu-uefi' once to fetch OVMF" }
}
if (-not (Test-Path (Join-Path $esp 'EFI/BOOT/BOOTX64.EFI'))) {
throw "no staged ESP at $esp -- run 'make esp' first"
}
$runVars = Join-Path $root "build/ttyshot-OVMF_VARS.fd"
Copy-Item -Force $vars $runVars
# The root filesystem. SchFS is the only root there is: the kernel mounts
# device 1 in forkret() and panics if it is not a SchFS volume, so this is
# not a choice and there is no -SchFS switch any more. It used to default to
# build/fs.img, which stopped being bootable the moment SchFS became root.
$hdb = Join-Path $root 'build/schfs.img'
$log = Join-Path $root 'build/ttyshot.log'
$ppm = Join-Path $root $Out
$monPort = 55571
# Each line exercises one thing the console could not do before: colour,
# bright colour, background colour, cursor addressing, erase-to-end-of-line,
# and non-ASCII text.
$commands = @(
'clear'
'printf ''\033[31mRED \033[32mGREEN \033[34mBLUE \033[35mMAGENTA \033[36mCYAN \033[0mdefault\n'''
'printf ''\033[1;31mbold red \033[1;32mbold green \033[1;34mbold blue\033[0m\n'''
'printf ''\033[37;44m white on blue \033[0m normal\n'''
'printf ''caf\303\251 \303\251t\303\251 na\303\257ve \303\274ber sch\303\266n a\303\261o\n'''
'printf ''ERASED-THIS\033[13G\033[Kkept this\n'''
'printf ''\033[12;40Hcursor addressed to row 12 col 40\n'''
# Tab used to be drawn as glyph 9, which in CP437 is a hollow circle --
# so pressing Tab printed a circle instead of moving the cursor. Every
# other control character had the same problem waiting behind it.
'printf ''A\tB\tC\tD -- tabs, not circles\n'''
)
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = $Qemu
$psi.Arguments = @(
"-drive if=pflash,format=raw,unit=0,readonly=on,file=`"$code`""
"-drive if=pflash,format=raw,unit=1,file=`"$runVars`""
"-drive file=fat:rw:`"$esp`",index=0,media=disk"
"-drive file=`"$hdb`",format=raw,index=1,media=disk"
'-m 1G -smp 1 -cpu qemu64,+rdtscp -machine pc-i440fx-9.2'
'-display none'
"-chardev stdio,id=c0,logfile=`"$log`""
'-serial chardev:c0'
"-monitor tcp:127.0.0.1:$monPort,server,nowait"
) -join ' '
$psi.RedirectStandardInput = $true
$psi.UseShellExecute = $false
Write-Host '==> booting' -ForegroundColor Cyan
$p = [System.Diagnostics.Process]::Start($psi)
try {
Start-Sleep -Seconds $BootSeconds
foreach ($cmd in $commands) {
Write-Host " > $cmd" -ForegroundColor DarkGray
foreach ($ch in ($cmd + "`n").ToCharArray()) {
$p.StandardInput.Write($ch)
$p.StandardInput.Flush()
Start-Sleep -Milliseconds 20 # serial has no flow control
}
Start-Sleep -Seconds 2
}
Write-Host '==> capturing the framebuffer' -ForegroundColor Cyan
$client = New-Object System.Net.Sockets.TcpClient('127.0.0.1', $monPort)
$writer = New-Object System.IO.StreamWriter($client.GetStream())
$writer.AutoFlush = $true
Start-Sleep -Milliseconds 500
$writer.WriteLine("screendump $ppm")
Start-Sleep -Seconds 2
$writer.WriteLine('quit')
Start-Sleep -Milliseconds 500
$client.Close()
} finally {
if (-not $p.HasExited) { $p.Kill() }
Start-Sleep -Milliseconds 800
}
if (Test-Path $ppm) {
Write-Host "==> $ppm ($((Get-Item $ppm).Length) bytes)" -ForegroundColor Green
} else {
Write-Host 'FAIL: no screendump was produced' -ForegroundColor Red
exit 1
}