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
3 changes: 2 additions & 1 deletion NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ https://github.com/bonzini/qboot, commit
8ca302e86d685fa05b16e2b208888243da319941, under GPL-2.0 (see qemu/qboot/COPYING).
write-pointer.patch modifies fw_cfg.c, include/fw_cfg.h and tables.c to implement
WRITE_POINTER, DMA writes and error/bounds checks; pam.patch modifies hwsetup.c to
set q35's PAM registers in three configuration writes. The firmware built with
set q35's PAM registers in three configuration writes; mtrr.patch modifies main.c
to enable the MTRRs, write-back by default and the 32-bit PCI hole uncacheable. The firmware built with
them ships in releases as qemu/qboot.bin, the patches beside it. The Apache-2.0
licence does not apply to those patches or COPYING.

Expand Down
6 changes: 4 additions & 2 deletions qemu/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -459,14 +459,15 @@ RUN git clone --quiet --branch "${QBOOT_VERSION}" https://github.com/bonzini/qbo
git -C upstream archive "${QBOOT_COMMIT}" > upstream.tar && \
echo "${QBOOT_ARCHIVE_SHA256} upstream.tar" | sha256sum -c -

COPY qemu/qboot/write-pointer.patch qemu/qboot/pam.patch /build/
COPY qemu/qboot/write-pointer.patch qemu/qboot/pam.patch qemu/qboot/mtrr.patch /build/

# Upstream's meson.build flags, with -Os fixed rather than taken from a build type.
RUN set -eux; \
mkdir -p src out; \
tar -xf upstream.tar -C src; \
patch -p1 --fuzz=0 -d src < write-pointer.patch; \
patch -p1 --fuzz=0 -d src < pam.patch; \
patch -p1 --fuzz=0 -d src < mtrr.patch; \
cd src; \
for f in *.c *.S; do \
gcc -Os -m32 -march=i386 -mregparm=3 -fno-stack-protector \
Expand All @@ -482,12 +483,13 @@ RUN set -eux; \
# fail here - it fails as a guest that prints nothing - so the size is checked; and the patches,
# because a patch that applied to nothing still leaves a tree that compiles: without the first
# the firmware hangs every machine that has vmgenid, without the second it is slower and says
# nothing.
# nothing, and without the third every device memory a guest maps reads uncached.
RUN set -eux; \
size=$(stat -c%s /build/qboot.bin); \
test "$size" -eq 65536 || { echo "qboot.bin is $size bytes, not 65536" >&2; exit 1; }; \
grep -q 'CMD_WRITE_PTR' src/tables.c && grep -q 'fw_cfg_write_file' src/fw_cfg.c || { echo "the WRITE_POINTER patch is not in the tree that was compiled" >&2; exit 1; }; \
grep -q '0x33333330' src/hwsetup.c || { echo "the PAM patch is not in the tree that was compiled" >&2; exit 1; }; \
grep -q 'setup_mtrr();' src/main.c || { echo "the MTRR patch is not in the tree that was compiled" >&2; exit 1; }; \
sha256sum /build/qboot.bin

FROM ${ALPINE_IMAGE} AS runtime
Expand Down
13 changes: 12 additions & 1 deletion qemu/qboot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,23 @@ byte. The values are immediates: once PAM0 is ram, 0xf0000-0x100000 reads zeroes
`setup_hw` has shadowed the BIOS, so a table in `.rodata` would be read back as zeroes.
i440fx keeps the loop; this machine is q35 and nothing here boots the other.

## MTRRs

Stock qboot never touches the MTRRs, so the guest boots with them disabled ("MTRRs disabled by
BIOS"). Linux then maps every range it is asked to map write-back but does not know as RAM
uncached-minus: a virtio-pmem region read at 8.3 MB/s, against 1.1 GB/s under SeaBIOS, which
programs them (lab runs 36818224412 and 36818721809, 2026-10-01). `mtrr.patch` does what SeaBIOS
does, on the boot CPU only - Linux gives its own MTRR state to the CPUs it starts: enabled,
write-back by default, and the 32-bit PCI hole, from the top of low RAM to 4 GiB, uncacheable in
naturally aligned power-of-two ranges. Fixed-range MTRRs stay off. If the hole does not fit the
variable ranges the CPU has, they are left disabled rather than caching MMIO.

## Build

`task qemu:build` builds it, in the `qboot` stage of `qemu/Dockerfile`, and it lands beside
the SeaBIOS blobs as `_output/qemu/qboot.bin`, where a release, `task qemu:fetch` and CI all
find it. The stage clones the pinned commit, verifies the SHA-256 of the tar `git archive`
writes for it, applies `write-pointer.patch` and then `pam.patch` with no fuzz, and compiles with upstream's
writes for it, applies `write-pointer.patch`, `pam.patch` and `mtrr.patch` with no fuzz, and compiles with upstream's
meson.build flags plus `-Os`. It asserts on what came out: 65536 bytes, the ROM window it is
linked for, and the patch's code in the tree that was compiled.

Expand Down
95 changes: 95 additions & 0 deletions qemu/qboot/mtrr.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
diff --git a/main.c b/main.c
index afa2200..1a156f6 100644
--- a/main.c
+++ b/main.c
@@ -77,6 +77,82 @@ static void extract_e820(void)
e820_seg = ((uintptr_t) e820) >> 4;
}

+/*
+ * MTRRs: enabled, write-back by default, and the 32-bit PCI hole - from the top of low
+ * RAM to 4 GiB - uncacheable, as SeaBIOS sets them. Left disabled, Linux maps every
+ * range it memremaps write-back but does not know as RAM - a virtio-pmem region, for
+ * one - uncached-minus, and reads it at 8 MB/s. Only this CPU: Linux gives its own
+ * state to the APs it starts. Fixed-range MTRRs stay off, so below 1 MiB is the
+ * default type too.
+ */
+#define MSR_MTRRcap 0xfe
+#define MSR_MTRRdefType 0x2ff
+#define MTRR_PHYSBASE(n) (0x200 + 2 * (n))
+#define MTRR_PHYSMASK(n) (0x201 + 2 * (n))
+#define MTRR_TYPE_UC 0
+#define MTRR_TYPE_WB 6
+#define MTRR_DEF_ENABLE 0x800
+#define MTRR_MASK_VALID 0x800
+
+static inline void cpuid(uint32_t leaf, uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d)
+{
+ asm volatile("cpuid" : "=a"(*a), "=b"(*b), "=c"(*c), "=d"(*d) : "0"(leaf), "2"(0));
+}
+
+static inline uint64_t rdmsr(uint32_t msr)
+{
+ uint32_t lo, hi;
+ asm volatile("rdmsr" : "=a"(lo), "=d"(hi) : "c"(msr));
+ return ((uint64_t)hi << 32) | lo;
+}
+
+static inline void wrmsr(uint32_t msr, uint64_t val)
+{
+ asm volatile("wrmsr" : : "c"(msr), "a"((uint32_t)val), "d"((uint32_t)(val >> 32)));
+}
+
+static void setup_mtrr(void)
+{
+ uint32_t a, b, c, d;
+ uint64_t phys_mask, base = lowmem, top = 1ull << 32;
+ int vcnt, n = 0;
+
+ cpuid(1, &a, &b, &c, &d);
+ if (!(d & (1 << 12)) || !(d & (1 << 5))) /* MTRR, MSR */
+ return;
+ vcnt = rdmsr(MSR_MTRRcap) & 0xff;
+ cpuid(0x80000000, &a, &b, &c, &d);
+ phys_mask = (1ull << 36) - 1;
+ if (a >= 0x80000008) {
+ cpuid(0x80000008, &a, &b, &c, &d);
+ phys_mask = (1ull << (a & 0xff)) - 1;
+ }
+
+ wrmsr(MSR_MTRRdefType, 0);
+ for (n = 0; n < vcnt; n++) {
+ wrmsr(MTRR_PHYSBASE(n), 0);
+ wrmsr(MTRR_PHYSMASK(n), 0);
+ }
+ /* [lowmem, 4G) in naturally aligned powers of two. */
+ for (n = 0; base < top && n < vcnt; n++) {
+ uint64_t size = top - base;
+ while (size & (size - 1))
+ size &= size - 1;
+ while (base & (size - 1))
+ size >>= 1;
+ wrmsr(MTRR_PHYSBASE(n), base | MTRR_TYPE_UC);
+ wrmsr(MTRR_PHYSMASK(n), (~(size - 1) & phys_mask) | MTRR_MASK_VALID);
+ base += size;
+ }
+ /* Not all of the hole fits: leave them off rather than cache MMIO. */
+ if (base < top) {
+ for (n = 0; n < vcnt; n++)
+ wrmsr(MTRR_PHYSMASK(n), 0);
+ return;
+ }
+ wrmsr(MSR_MTRRdefType, MTRR_DEF_ENABLE | MTRR_TYPE_WB);
+}
+
int __attribute__ ((section (".text.startup"))) main(void)
{
bool have_pci;
@@ -98,6 +174,7 @@ int __attribute__ ((section (".text.startup"))) main(void)
fw_cfg_setup();
extract_acpi();
extract_e820();
+ setup_mtrr();
setup_mptable();
extract_smbios();
boot_from_fwcfg();
2 changes: 1 addition & 1 deletion versions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@
the machine's BIOS, built in qemu/Dockerfile's qboot stage. archive is the sha256 of what
`git archive` writes for the commit with debian's git, which nothing but a build can
compute, so a bump is by hand: the commit, a build, the archive's sum from its failure,
write-pointer.patch and pam.patch rebased onto it, NOTICE and qemu/qboot/README.md restated, and
write-pointer.patch, pam.patch and mtrr.patch rebased onto it, NOTICE and qemu/qboot/README.md restated, and
`task boot:firmware` again.

- name: actionlint
Expand Down
Loading