Skip to content

Commit 31c3c9f

Browse files
committed
Vendor in original builder-hex0
1 parent 594f7b8 commit 31c3c9f

13 files changed

Lines changed: 4351 additions & 70 deletions

.github/workflows/build.yml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,20 @@ env:
1515
QEMU_VERSION: "10.1.4"
1616

1717
jobs:
18+
lint:
19+
runs-on: ubuntu-24.04
20+
steps:
21+
- uses: actions/checkout@v5
22+
- uses: fsfe/reuse-action@v5
23+
1824
build-and-test:
25+
needs: lint
1926
runs-on: ubuntu-24.04
2027
strategy:
2128
matrix:
2229
include:
30+
- arch: x86
31+
board: bios
2332
- arch: riscv64
2433
board: virt
2534
- arch: riscv64
@@ -42,16 +51,16 @@ jobs:
4251
uses: actions/cache@v4
4352
with:
4453
path: qemu-local
45-
key: qemu-riscv64-aarch64-${{ env.QEMU_VERSION }}
54+
key: qemu-x86-riscv64-aarch64-${{ env.QEMU_VERSION }}
4655

47-
- name: Build QEMU (riscv64 + aarch64)
56+
- name: Build QEMU (x86_64 + riscv64 + aarch64)
4857
if: steps.cache-qemu.outputs.cache-hit != 'true'
4958
run: |
5059
pip install meson
5160
git clone --depth 1 --branch v${{ env.QEMU_VERSION }} \
5261
https://gitlab.com/qemu-project/qemu.git /tmp/qemu-build
5362
cd /tmp/qemu-build
54-
./configure --target-list=riscv64-softmmu,aarch64-softmmu \
63+
./configure --target-list=x86_64-softmmu,riscv64-softmmu,aarch64-softmmu \
5564
--prefix=${{ github.workspace }}/qemu-local \
5665
--disable-docs --disable-gtk --disable-sdl \
5766
--disable-opengl --disable-virglrenderer
@@ -64,6 +73,7 @@ jobs:
6473

6574
- name: Test
6675
run: >
76+
QEMU_X86=qemu-local/bin/qemu-system-x86_64
6777
QEMU_RISCV64=qemu-local/bin/qemu-system-riscv64
6878
QEMU_AARCH64=qemu-local/bin/qemu-system-aarch64
6979
make test-${{ matrix.arch }}-${{ matrix.board }}

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# SPDX-FileCopyrightText: 2026 Alexandre Gomes Gaigalas <alganet@gmail.com>
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
15
__pycache__/
26
*.pyc
37
hex2/hex2

DEVELOP.md

Lines changed: 82 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ SPDX-License-Identifier: Apache-2.0
88

99
## Build Chain
1010

11+
### x86 (vendored)
12+
13+
No build step — hex0 source files are included directly:
14+
* `builder-hex0-x86-stage1-bios.hex0` — stage 1 (MBR boot + hex0 compiler)
15+
* `builder-hex0-x86-stage2.hex0` — stage 2 (full builder with shell)
16+
* `builder-hex0-x86-mini.hex0` — minimal hex0-only compiler (512 bytes)
17+
1118
### RISC-V 64-bit
1219

1320
```
@@ -29,21 +36,26 @@ hex2_word.c, so AArch64 pre-resolves all offsets in Python).
2936

3037
## Source Files
3138

32-
| File | Purpose |
33-
|------|---------|
34-
| builder-hex0-riscv64-stage1-virt.S | RISC-V QEMU virt stage 1 (VirtIO) |
35-
| builder-hex0-riscv64-stage1-sifive_u.S | RISC-V SiFive sifive_u stage 1 (SPI+SD) |
36-
| builder-hex0-riscv64-stage2.S | RISC-V portable stage 2 kernel |
37-
| rv64-asm2hex2.py | RISC-V assembly-to-hex2 converter |
38-
| asm.py | RISC-V instruction encoder library |
39-
| builder-hex0-aarch64-stage1-virt.S | AArch64 QEMU virt stage 1 (VirtIO) |
40-
| builder-hex0-aarch64-stage1-raspi3b.S | AArch64 RPi 3B stage 1 (SDHCI, core parking) |
41-
| builder-hex0-aarch64-stage2.S | AArch64 portable stage 2 kernel |
42-
| a64-asm2hex2.py | AArch64 assembly-to-hex2 converter (two-pass) |
43-
| a64_asm.py | AArch64 instruction encoder library |
44-
| hex2tohex0.py | hex2-to-hex0 converter with comments (shared) |
45-
| hex2/ | Vendored hex2 linker (C source, shared) |
46-
| Makefile | Multi-architecture build (`ARCH=riscv64\|aarch64`) |
39+
| File | Purpose |
40+
|----------------------------------------|-------------------------------------------------|
41+
| builder-hex0-x86-stage1-bios.hex0 | x86 BIOS stage 1 (vendored, Rick Masters) |
42+
| builder-hex0-x86-stage2.hex0 | x86 full builder (vendored, Rick Masters) |
43+
| builder-hex0-x86-mini.hex0 | x86 mini hex0 compiler (vendored, Rick Masters) |
44+
| builder-hex0-riscv64-stage1-virt.S | RISC-V QEMU virt stage 1 (VirtIO) |
45+
| builder-hex0-riscv64-stage1-sifive_u.S | RISC-V SiFive sifive_u stage 1 (SPI+SD) |
46+
| builder-hex0-riscv64-stage2.S | RISC-V portable stage 2 kernel |
47+
| rv64-asm2hex2.py | RISC-V assembly-to-hex2 converter |
48+
| asm.py | RISC-V instruction encoder library |
49+
| builder-hex0-aarch64-stage1-virt.S | AArch64 QEMU virt stage 1 (VirtIO) |
50+
| builder-hex0-aarch64-stage1-raspi3b.S | AArch64 RPi 3B stage 1 (SDHCI, core parking) |
51+
| builder-hex0-aarch64-stage2.S | AArch64 portable stage 2 kernel |
52+
| a64-asm2hex2.py | AArch64 assembly-to-hex2 converter (two-pass) |
53+
| a64_asm.py | AArch64 instruction encoder library |
54+
| hex2tohex0.py | hex2-to-hex0 converter with comments (shared) |
55+
| hex0-to-src.sh | Generate shell script for hex0 self-compilation |
56+
| build-self.sh | Boot kernel and hex0-compile a source file |
57+
| hex2/ | Vendored hex2 linker (C source, shared) |
58+
| Makefile | Multi-architecture build with per-board targets |
4759

4860

4961
## Two-Stage Boot
@@ -52,12 +64,12 @@ hex2_word.c, so AArch64 pre-resolves all offsets in Python).
5264

5365
Each board has its own stage 1. Stage 1 is architecture- and board-specific.
5466

55-
| | RISC-V | AArch64 (virt) | AArch64 (raspi3b) |
56-
|---|---|---|---|
57-
| Load address | `0x80200000` (by OpenSBI) | `0x40080000` (by QEMU) | `0x00080000` (by QEMU) |
58-
| Entry state | `a0`=hartid, `a1`=dtb | `x0`=dtb | EL2, all 4 cores |
59-
| Stage 2 address | `0x80210000` | `0x40210000` | `0x00210000` |
60-
| Exit convention | `a0`=sector, `a1`=DTB | `x0`=sector, `x1`=DTB | `x0`=sector, `x1`=0, `x2`=SDHCI base, `x3`=3 |
67+
| | RISC-V | AArch64 (virt) | AArch64 (raspi3b) |
68+
|-----------------|---------------------------|------------------------|----------------------------------------------|
69+
| Load address | `0x80200000` (by OpenSBI) | `0x40080000` (by QEMU) | `0x00080000` (by QEMU) |
70+
| Entry state | `a0`=hartid, `a1`=dtb | `x0`=dtb | EL2, all 4 cores |
71+
| Stage 2 address | `0x80210000` | `0x40210000` | `0x00210000` |
72+
| Exit convention | `a0`=sector, `a1`=DTB | `x0`=sector, `x1`=DTB | `x0`=sector, `x1`=0, `x2`=SDHCI base, `x3`=3 |
6173

6274
Responsibilities:
6375
1. Find and initialize the storage device (board-specific)
@@ -148,6 +160,47 @@ uses PSCI HVC, AArch64 raspi3b uses the BCM2835 power management watchdog
148160
(writing PM_RSTS, PM_WDOG, PM_RSTC with the PM password `0x5A`).
149161

150162

163+
## Self-Build Reproducibility
164+
165+
Each architecture can prove its hex0 compiler produces identical output to the
166+
host toolchain. The `make self-test` targets automate this.
167+
168+
### x86
169+
170+
The x86 chain follows the original builder-hex0 pattern:
171+
172+
1. Host compiles `builder-hex0-x86-mini.hex0` to binary using `xxd` (seed)
173+
2. Mini seed boots in QEMU, compiles `stage1-bios.hex0` → writes to disk
174+
3. Host also compiles `stage1-bios.hex0` using `xxd`
175+
4. Diff the two binaries — proves mini's hex0 compiler matches host
176+
177+
The x86 also has a "full builds full" chain (the full builder can compile its
178+
own hex0 source via the `hex0` shell command), matching the original
179+
builder-hex0 Makefile.
180+
181+
### RISC-V and AArch64
182+
183+
These architectures don't have a "mini" variant. Stage 1 IS the hex0 compiler,
184+
but it can't self-build in isolation because:
185+
- It needs QEMU `-kernel` to load (not MBR boot)
186+
- The compiled output goes to a fixed RAM address, not back to disk
187+
- Stage 1 has no "write to disk" capability
188+
189+
Instead, self-builds go through the full stage 2 kernel:
190+
191+
1. Host compiles stage 1 and stage 2 from hex0 source (seed binaries)
192+
2. Stage 1 seed boots → compiles stage 2 hex0 → stage 2 kernel runs
193+
3. Internal shell loads a hex0 file via `src`, compiles it via `hex0`, flushes
194+
to disk via `f`
195+
4. Extract the result from the disk image and diff against the seed binary
196+
197+
This is one extra layer compared to x86 mini, but achieves the same proof:
198+
the hex0 compiler (embedded in the kernel) reproduces the binary from source.
199+
200+
Both stage 1 and stage 2 are tested:
201+
- `make self-test-{arch}-{board}` runs both stage1 and stage2 self-builds
202+
203+
151204
## Porting
152205

153206
### New board (same architecture)
@@ -157,7 +210,7 @@ uses PSCI HVC, AArch64 raspi3b uses the BCM2835 power management watchdog
157210
3. If using a new storage type, add a driver to stage 2 implementing
158211
`xx_read_sector(a0=sector, a1=buffer)` and `xx_write_sector`
159212
4. Add detection logic in `fdt_find_storage` for the new DTB node pattern
160-
5. Add a test target (`test-boot-{board}`)
213+
5. Add a test target (`test-{arch}-{board}`) and self-test target (`self-test-{arch}-{board}`)
161214

162215
### New architecture
163216

@@ -172,12 +225,16 @@ uses PSCI HVC, AArch64 raspi3b uses the BCM2835 power management watchdog
172225

173226
```
174227
make clean && make # all architectures
228+
make x86 # x86 (vendored, no build step)
175229
make riscv64 # riscv64 only
176230
make aarch64 # aarch64 only
177231
178-
make test # all architectures
179-
make test-riscv64 # riscv64 only
180-
make test-aarch64 # aarch64 only
232+
make test # boot tests, all arch+board combos
233+
make test-x86-bios # one board
234+
make test-aarch64-raspi3b # one board
235+
236+
make self-test # self-build reproducibility, all
237+
make self-test-aarch64-virt # one board
181238
```
182239

183240
The RISC-V sifive_u test requires QEMU >= 10.1.

0 commit comments

Comments
 (0)