ui_panel was written for this screen during the loader restyle and then never called -- dead code sitting in boot/common/ui.c waiting for the row that would use it. This is that row: F8 -> System Information, showing firmware vendor and revision, UEFI revision, CPU brand, usable memory, current mode and whether ACPI was found. Everything is read when the screen is drawn rather than captured at startup, because the resolution row above it can have changed the mode, and a panel still reporting what the machine booted with is worse than no panel at all. Two facts are deliberately read through the same code that feeds the kernel -- efi_type_to_e820() for what counts as usable memory, and find_rsdp() for ACPI. A panel with its own idea of either would be a second implementation to keep in step, and the ACPI line exists precisely because a missing RSDP means no MADT, no IOAPIC and no SMP, which has already been debugged once from the far end. The screenshot caught a bug in the screen it was taken to verify: UEFI revision read "21587.2286". EFI_TABLE_HEADER begins with a UINT64 Signature, so Revision is at offset 8 and I had read offset 4 -- the upper half of "IBI SYST", formatted as a version number. It now reads 2.7 under OVMF, which is correct. Verified by driving the menu over the QEMU monitor and reading the pixels: five rows with the hint updated to 1-5, the panel centred, and every value cross-checkable -- EDK II, firmware 1.0, UEFI 2.7, "QEMU Virtual CPU version 2.5+" against -cpu qemu64, 1018 MB of the 1 GB given, 1280x800 matching what fbshot captures, ACPI present. The highlight sits 9 px inside both panel borders, symmetric. F8 has a 1.5 s window, so the capture presses it repeatedly through boot rather than guessing when OVMF finishes -- and only F8, since any other key breaks the wait and boots. Also: every ESP drive now says format=raw. QEMU warned about probing the format on each of the fifteen or so places that attach it, which is new noise on what is now the only boot path. 110 [ok], no failures, no warnings.
132 lines
5.1 KiB
PowerShell
132 lines
5.1 KiB
PowerShell
# Draw on /dev/fb0 and photograph the result.
|
|
#
|
|
# bootcheck -Fb proves fbinfo *ran* and that the numbers it read back are the
|
|
# ones the bootloader programmed. It cannot prove the pixels landed where they
|
|
# were meant to: a framebuffer written with the wrong stride still reports
|
|
# every byte accepted, and the only symptom is a picture that shears
|
|
# progressively down the screen. That is a thing you have to look at.
|
|
#
|
|
# tools/fbshot.ps1 gradient through mmap
|
|
# tools/fbshot.ps1 -Mode bars colour bars through write()
|
|
|
|
param(
|
|
[ValidateSet('fill', 'bars')]
|
|
[string]$Mode = 'fill',
|
|
[int]$BootSeconds = 20,
|
|
[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/fbshot-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/fbshot.log'
|
|
$ppm = Join-Path $root "build/fb-$Mode.ppm"
|
|
|
|
Remove-Item -Force $log, $ppm -ErrorAction SilentlyContinue
|
|
|
|
$monPort = 55556 # not crashshot's 55555, so the two can run back to back
|
|
|
|
$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,format=raw"
|
|
"-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)
|
|
|
|
function Send-Line($text) {
|
|
foreach ($ch in ($text + "`n").ToCharArray()) {
|
|
$p.StandardInput.Write($ch)
|
|
$p.StandardInput.Flush()
|
|
Start-Sleep -Milliseconds 30 # serial has no flow control
|
|
}
|
|
}
|
|
|
|
try {
|
|
Start-Sleep -Seconds $BootSeconds
|
|
Write-Host " > fbinfo $Mode" -ForegroundColor DarkGray
|
|
Send-Line "fbinfo $Mode"
|
|
Start-Sleep -Seconds 8 # 1.9 MB a pixel at a time is not fast
|
|
|
|
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 $log) {
|
|
$text = Get-Content $log -Raw
|
|
$idx = $text.IndexOf('fbinfo')
|
|
if ($idx -ge 0) { Write-Host $text.Substring($idx) }
|
|
}
|
|
|
|
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
|
|
}
|