<feed xmlns='http://www.w3.org/2005/Atom'>
<title>tashaboot/arch/arm64/kernel/boot.S, branch main</title>
<subtitle>tashaboot multi-stage bootloader framework</subtitle>
<id>https://p10-linux-brads.osuosl.org/tashaboot/atom?h=main</id>
<link rel='self' href='https://p10-linux-brads.osuosl.org/tashaboot/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://p10-linux-brads.osuosl.org/tashaboot/'/>
<updated>2026-10-04T00:32:49Z</updated>
<entry>
<title>tashaboot: image header, EL split, self located load address</title>
<updated>2026-10-04T00:32:49Z</updated>
<author>
<name>Bradley Morgan</name>
<email>brads@mainlining.org</email>
</author>
<published>2026-10-04T00:32:49Z</published>
<link rel='alternate' type='text/html' href='https://p10-linux-brads.osuosl.org/tashaboot/commit/?id=d72c2f898ed4c17aba0080c8bf6a0173cca940dc'/>
<id>urn:sha1:d72c2f898ed4c17aba0080c8bf6a0173cca940dc</id>
<content type='text'>
qemu -kernel parses a raw arm64 blob as a linux Image and enters
at RAMBASE plus whatever text_offset it guesses out of the
garbage, 0x80000 in our case. every wild PC at image+0x80000 in
the debug logs was our own code running from the wrong address.
the binary now carries a real Image header: code0 branches over
it, magic ARM\x64 at 0x38, text_offset 0, image_size stamped
after objcopy by tools/fillsize.py.

the runtime also split by exception level. the C body runs at
EL1, the semihosting hlt is answered by qemu only from EL2, so
the EL2 vector replays the trap there and erets home with the
result. the kernel handoff hvc raises back to EL2 where
booting.rst wants it, the same vector slot dispatches PSCI hvc
from the kernel, boot handoff and semihosting by EC and function
id.

the payload load address was hardcoded 0x40200000, which is where
qemu placed our image, so the load overwrote the running
bootloader with kernel bytes mid flight. the load address is now
__image_copy_end plus 16MB, wherever the image actually runs.

receipt: run /init, tashaboot linux userspace reached, cores: 4,
busybox shell on a 4 cpu virt machine with initrd.
</content>
</entry>
<entry>
<title>tashaboot: arm64 bootloader</title>
<updated>2026-10-03T21:37:00Z</updated>
<author>
<name>Bradley Morgan</name>
<email>brads@mainlining.org</email>
</author>
<published>2026-10-03T20:02:29Z</published>
<link rel='alternate' type='text/html' href='https://p10-linux-brads.osuosl.org/tashaboot/commit/?id=9dbdb15abf8ffb9dbdd972d6bbbcea9a3591e2a3'/>
<id>urn:sha1:9dbdb15abf8ffb9dbdd972d6bbbcea9a3591e2a3</id>
<content type='text'>
A small arm64 bootloader. No board code, no device tree porting, the
architecture manual is the whole story: exception vectors in the
fixed 16 slot layout (Table D1-7), ESR_ELx decoded by exception class
(D1-2172), EL entry and eret chains per the programmers model
(D1-2146), cache maintenance by set/way over the CLIDR_EL1 levels,
semihosting for console and file io per DUI 0203, and the A64 boot
protocol from Documentation/arch/arm64/booting.rst.

The loader boots a stock mainline Image end to end on the qemu virt
machine. Boot receipt with 7.3-rc3 (42MB Image):

  tashaboot 0.1
  loaded 43450368 bytes at 40200000, entry 40200000
  jumping
  [    0.000000] Booting Linux on physical CPU 0x0000000000 [0x411fd070]
  [    0.000000] Linux version 7.3.0-rc3
  [    0.000000] Machine model: linux,dummy-virt
  [    0.000000] earlycon: pl11 MMIO32:0x0000000009000000
  ...
  ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs ]---

The panic is the expected end state, no root filesystem is handed
over yet.

The boot chain, state per stage, start to payload:

+-----------+-----+--------------+----------------------------------+
| stage     | EL  | state        | work                             |
+-----------+-----+--------------+----------------------------------+
| firmware  | any | MMU maybe on | x0 = dtb, jump in               |
+-----------+-----+--------------+----------------------------------+
| tashaboot | 3-2 |              | SCR_EL3.NS = 1, eret to EL2     |
+-----------+-----+--------------+----------------------------------+
|           | 2   | virt scrub   | HCR/CNTHCTL/CPTR/HSTR, CNTFRQ,  |
|           |     |              | VBAR_EL2, MMU off, tlbi alle2   |
+-----------+-----+--------------+----------------------------------+
|           | 2   |              | load Image over semihosting,    |
|           |     |              | validate header, place per      |
|           |     |              | booting.rst                     |
+-----------+-----+--------------+----------------------------------+
|           | 2   | caches clean | flush dcache, inval icache,     |
|           |     |              | args ride x20/x21, regs last    |
+-----------+-----+--------------+----------------------------------+
| payload   | 2   | fresh start  | x0 = dtb, x1-x3 = 0, DAIF       |
|           |     |              | masked, br to image entry       |
+-----------+-----+--------------+----------------------------------+

Two handoff bugs the kernel caught, both AAPCS clobbers in the final
jump. Cache maintenance was called after the register setup, x0-x18
are caller saved, so tb_flush_dcache_all() wiped the dtb pointer and
the kernel spun in setup_machine_fdt() with an invalid device tree
blob. The flush helpers also clobbered x1 (u-boot's void call
convention left mov x1, x0 in cache.S) which handed the kernel a wild
x0. The arguments ride in x20/x21 across the cache calls now, callee
saved, and the register setup is the last thing before the branch.

What is missing on purpose: no SMP bringup (secondary cores park),
no PSCI, no initrd or root filesystem handoff, single serial
console. Those come next.

Signed-off-by: Bradley Morgan &lt;brads@mainlining.org&gt;
</content>
</entry>
</feed>
