Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ on its own: whoever runs a guest supplies its init.
Diagnostic input is different from release content. A test or experiment may use a caller
supplied initrd, a temporary guest helper, a disposable overlay, or a separately built
firmware image when it is isolated from `task build` and `task release`. State clearly what
is diagnostic, who supplies it, and what it is measuring. The qboot probe is the model:
it does not change a release tree and compares both variants with the same diagnostic initrd.
is diagnostic, who supplies it, and what it is measuring. `task boot:firmware` is the model:
it leaves the release tree as it is and boots each firmware with the same diagnostic initrd.

**Keep consumer-specific implementation out of this tree, but record real contracts.** Do
not copy consumer code, paths, or an ADR as a substitute for an explanation. It is correct to
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ One tarball:
| `SOURCES` | every upstream source by version, URL and SHA-256, and the written offer |
| `packages.txt` | every package and exact version in the base image |

`LICENSE` and `NOTICE` sit at the root of the tarball, next to `install.sh`.
`LICENSE` and `NOTICE` sit at the root of the tarball.

`task build` writes that same tree into `_output/`, byte for byte the layout above, and
`machine.OpenRelease` reads either. There is one layout: nothing rearranges the files on the way
Expand Down
2 changes: 1 addition & 1 deletion docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## CI

Five workflows, and the split is about cost. `ci.yml` runs on every push and builds none of
The workflows are split by cost. `ci.yml` runs on every push and builds none of
the three artefacts — it is `task lint` and `task test`, which is fast and catches most
mistakes. It also *pulls* one: `task qemu:fetch` unpacks the published QEMU of the pinned
version in seconds, and `task verify:args` hands it the command line `machine.Spec.Args`
Expand Down
4 changes: 2 additions & 2 deletions qemu/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@ deliberately not shipped — it exports a disk over NBD, which nothing here does

**PVH, not BIOS.** The kernel is an ELF `vmlinux` with Xen PVH notes and QEMU enters it
through `pvh.bin`. There is no bootloader and no UEFI: the same guest under UEFI + Secure
Boot was measured at +356 ms and rejected. Replacing SeaBIOS with qboot was measured too and
cannot run this machine; see [qboot/README.md](qboot/README.md).
Boot was measured at +356 ms and rejected. The BIOS is qboot, patched and built here, with
SeaBIOS shipped as the fallback; see [qboot/README.md](qboot/README.md).
39 changes: 5 additions & 34 deletions qemu/qboot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,42 +62,13 @@ Measured on the lab runner (AMD Ryzen 9 5900X, run 36650049960, 2026-09-29), 20
interleaved: SeaBIOS 121.89 ms p50, qboot 114.76, 7.14 ms saved; and `report`'s whole matrix,
106 rows, boots on qboot (run 36650559189).

## Measurements, 2026-09-21

QEMU 11.1.1, KVM, i9-13900HK, q35 with SATA and SMBus disabled, 2 vCPUs,
2 GiB, CPU host, no disks or NICs, vmgenid present. The diagnostic initrd used a
static BusyBox and printed the marker after mounting proc/sysfs and filtering
dmesg. No cache dropping or warmup phase; 20 boots per firmware per run for the
first two rows and 10 for the third, interleaved, host wall clock before spawning
QEMU to receipt of SPIN-READY. These are diagnostic init timings, not PVH-entry,
systemd readiness, SSH, or full machine topology measurements.

| Run | Firmware built by | SeaBIOS p50 / p95 | Patched qboot p50 / p95 | p50 reduction |
|---|---|---|---|---|
| Initial | host GCC 15.2.0 | 101.32 / 112.25 ms | 96.50 / 105.25 ms | 4.83 ms |
| Rebuilt using checked-in recipe and probe | host GCC 15.2.0 | 100.89 / 107.40 ms | 94.57 / 101.68 ms | 6.32 ms |
| Containerised recipe, 10 boots, 2026-09-26 | Debian 14.2.0 in `Dockerfile` | 98.43 / 100.41 ms | 94.57 / 97.62 ms | 3.86 ms |

All three reproduced the original stall and observed reseeding after restoration. Raw
samples and artifact hashes for the first two are in `measurements.json`.

## The toolchain is part of the number

The first two rows were built by whatever GCC the host had — 15.2.0 — and produced
`7a316e3c…` for the patched binary. `Dockerfile` pins Debian trixie, whose GCC is
14.2.0, and produces `bf7ddddc…`. Same commit, same patch, same flags, different
compiler, and the saving moved from 6.32 ms to 3.86 ms: **the compiler accounts for
more of the difference than a third of what qboot itself saves.**

This is the same result the SeaBIOS experiment recorded in `boot/phases.go` — two
builds of one firmware differing by as much as the change being measured — and it is
why the recipe is pinned rather than convenient. It also means a number in this file
is only comparable to another number built the same way, and the third row is the only
one that can be reproduced from the repository as it stands.

The 3.9–6.3 ms observed reduction is useful but is not evidence of a larger saving in a
complete userspace boot: measured against this machine's real boot, the firmware is
about 10 ms of a few hundred.
The same commit, patch and flags saved 6.3 ms over SeaBIOS built by a host's GCC 15.2.0 and
3.9 ms built by Debian trixie's GCC 14.2.0 (i9-13900HK, a diagnostic initrd, 2026-09-21 and
-26): the compiler moved the result by more than a third of what qboot saves. That is why the
recipe is pinned rather than convenient, and why a number here compares only with one built
the same way. Against this machine's real boot, the firmware is about 10 ms of a few hundred.

Firmware contents are part of Spec.Fingerprint — the BIOS and pvh.bin, by content — so
two firmware builds are two machines, and a checkpoint saved under one does not resume
Expand Down
103 changes: 0 additions & 103 deletions qemu/qboot/measurements.json

This file was deleted.

4 changes: 4 additions & 0 deletions versions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
# git: a repository built here; version the branch it is on, pin the commit.
# download: a file; source its URL with {version} (and {major}), pin its sha256.
# date: a point in time; version the date, no pin.
# module: a Go command; source its package, version the module's, no pin (the
# checksum database holds it to its bytes).
# track how check finds the newest:
# digest the tag's digest now: the version stays, the image moves
# today today's date
Expand All @@ -29,6 +31,8 @@
# commit <repo> <branch>
# the commit <branch> of <repo> is at now: a download whose version is
# a commit, from a project whose releases do not carry the file
# github-release <repo>
# the newest release of a GitHub repository
# note why it is pinned where it is, and what a bump has to be checked with.

- name: qemu
Expand Down
2 changes: 0 additions & 2 deletions versions/gate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ func TestVersionsYAMLIsTheOnlyPin(t *testing.T) {
// has to rebase the patch and restate both; versions.yaml's note says so.
"NOTICE",
"qemu/qboot/README.md",
// The hashes of the artefacts a measurement was taken with.
"qemu/qboot/measurements.json",
}}
if err := g.Check(); err != nil {
t.Error(err)
Expand Down