diff --git a/tools/windows/bootcheck.ps1 b/tools/windows/bootcheck.ps1 index 5464ce5..99fbf7f 100644 --- a/tools/windows/bootcheck.ps1 +++ b/tools/windows/bootcheck.ps1 @@ -26,6 +26,12 @@ param( # attached, which is the only way to prove the ISO is self-contained. # Needs `make iso` first. [switch]$Iso, + # Boot through OVMF instead of the BIOS loader. The disk layout is + # deliberately the same -- the staged ESP lands on index 0 and the SchFS + # volume on index 1 -- so every check below runs unaltered and a difference + # in the transcript is a difference in the *firmware path*, not in what was + # asked. Needs `make esp` first. + [switch]$Uefi, # Terminal behaviour: pipes, isatty, SIGPIPE, and Ctrl-C. None of these # can be checked by looking at a screenshot, and all of them were broken # in ways that looked like broken *commands*. @@ -243,7 +249,27 @@ $commands += 'echo MARKER-DONE' $isoPath = Join-Path $root 'schos.iso' if ($Iso -and -not (Test-Path $isoPath)) { throw "no $isoPath -- run 'make iso' first" } -$drives = if ($Iso) { +$drives = if ($Uefi) { + # The varstore is written by the firmware, so each run gets its own copy. + # Sharing one would let a run that changed a UEFI variable -- boot order, + # console mode -- decide how the next run behaves, which is exactly the + # kind of dependency between runs that makes a suite stop being evidence. + $esp = Join-Path $root 'build/esp' + if (-not (Test-Path (Join-Path $esp 'EFI/BOOT/BOOTX64.EFI'))) { + throw "no staged ESP at $esp -- run 'make esp' first" + } + $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" } + } + $runVars = Join-Path $root 'build/bootcheck-OVMF_VARS.fd' + Copy-Item -Force $vars $runVars + @("-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") +} elseif ($Iso) { @("-cdrom `"$isoPath`"", '-boot d') } else { @("-drive file=`"$hda`",format=raw,index=0,media=disk" @@ -301,7 +327,9 @@ $psi.Arguments = ($drives + $usbDevices + $monitor + @( $psi.RedirectStandardInput = $true $psi.UseShellExecute = $false -$what = if ($Iso) { 'schos.iso, no hard disk' } else { "hdb = $(Split-Path $hdb -Leaf)" } +$what = if ($Uefi) { "OVMF, hdb = $(Split-Path $hdb -Leaf)" } + elseif ($Iso) { 'schos.iso, no hard disk' } + else { "hdb = $(Split-Path $hdb -Leaf)" } Write-Host "==> booting ($what)" -ForegroundColor Cyan $p = [System.Diagnostics.Process]::Start($psi) # Recorded immediately, so that a run killed one instruction later is still @@ -505,6 +533,26 @@ function Expect($pattern, $what) { } } +# Assert something the transcript does not state literally. Expect can only ask +# whether a string is present, which is why the framebuffer checks below used to +# hardcode 800x600: it was the only mode the BIOS loader ever set, so a literal +# was indistinguishable from an invariant. Under UEFI the firmware chooses, so +# the literal was testing the firmware's taste rather than our code. +function ExpectThat($cond, $what, $detail) { + if ($cond) { + Write-Host " [ok] $what" -ForegroundColor Green + } else { + Write-Host " FAIL: $what ($detail)" -ForegroundColor Red + $script:fail = $true + } +} + +# Pull a single "name value" field out of the fbset-style dump. +function FbField($name) { + if ($script:text -match "(?m)^$name\s+(\d+)") { return [int]$Matches[1] } + return -1 +} + Expect 'MARKER-DONE' 'shell stayed alive through every command' Expect 'hello-from-schos' 'a file written on the root filesystem reads back' Expect 'second-line' 'appending to that file works' @@ -564,14 +612,25 @@ if ($gens.Count -ge 2 -and if ($Fb) { Expect 'id\s+schosfb' 'FBIOGET_FSCREENINFO identifies the device' - # 800x600 is what boot/bios/stage2.S asks VBE for. Asserting the numbers rather - # than just "the ioctl returned" is the point: the old code path returned - # success with an untouched buffer, i.e. a 0x0 screen. - Expect '(?m)^xres\s+800' 'the reported width is the mode actually set' - Expect '(?m)^yres\s+600' 'the reported height is the mode actually set' + # The bug this guards against is an ioctl that reports success having + # touched nothing -- a 0x0 screen. What proves the numbers are real is not + # any particular pair of them but that they agree with each other and with + # a mode the hardware could actually scan out. + $xres = FbField 'xres' + $yres = FbField 'yres' + $line = FbField 'line_length' + ExpectThat ($xres -ge 640) 'the reported width is a mode actually set' "xres = $xres" + ExpectThat ($yres -ge 480) 'the reported height is a mode actually set' "yres = $yres" + # A width that is not a multiple of 8 is the mode that sheared under OVMF: + # the firmware advertised 1366 and the hardware scanned out 1360. + ExpectThat (($xres % 8) -eq 0) 'the width is a whole number of scanout units' "xres = $xres" Expect '(?m)^bits_per_pixel\s+32' 'the reported depth is the mode actually set' Expect '(?m)^smem_start\s+0x' 'the framebuffer reports a physical address' - Expect '(?m)^line_length\s+3200' 'the stride comes from VBE, not from xres*bpp' + # The stride must come from the mode information, not be recomputed as + # xres*bpp -- on a mode whose scanline is padded those two differ, and every + # row after the first lands in the wrong place. + ExpectThat ($line -ge $xres * 4) 'the stride covers a full row of pixels' "line_length = $line, xres = $xres" + ExpectThat (($line % 4) -eq 0) 'the stride is a whole number of pixels' "line_length = $line" Expect 'transp.*absent' 'the absent alpha channel is reported as length 0' Expect 'colour bars drawn' 'writing pixels through write() reaches the screen' Expect 'gradient drawn' 'mmap of /dev/fb0 maps the real framebuffer'