@@ -3,17 +3,16 @@
Booting Ubuntu live ISOs via U-Boot
===================================
-U-Boot can replace GRUB as the bootloader on an Ubuntu live ISO. The
-stock ISO has an appended EFI system partition (ESP) containing shim,
-GRUB and their configuration. ``scripts/ubuntu-iso-to-uboot.py`` rewrites
-that ESP with a U-Boot EFI application, a :doc:`Boot Loader Specification
-Type #1 </usage/bls>` entry and the casper kernel and initrd, leaving the
-rest of the ISO untouched so casper still finds its squashfs by disc
-label at runtime.
-
-The flow only touches the appended partition. BIOS El Torito, the
-grub2 MBR, the GPT layout and the ISO 9660 tree are preserved verbatim
-by xorriso's ``-boot_image any replay``
+U-Boot can replace GRUB as the bootloader on an Ubuntu live ISO.
+``scripts/ubuntu-iso-to-uboot.py`` rewrites the ISO so the appended EFI
+system partition holds a U-Boot EFI application and the ISO 9660 tree
+carries a :doc:`Boot Loader Specification Type #1 </usage/bls>` entry
+pointing at the casper kernel and initrd that Ubuntu already places on
+the disc.
+
+All other boot records (BIOS El Torito, grub2 MBR, GPT layout) are
+preserved verbatim by xorriso's ``-boot_image any replay``, and casper
+still finds its squashfs by disc label at runtime.
Host prerequisites
------------------
@@ -26,17 +25,17 @@ Building the U-Boot EFI application
-----------------------------------
The ``efi-x86_app64`` target enables ``CONFIG_BOOTMETH_BLS=y``,
-``CONFIG_FS_ISOFS=y`` and ``CONFIG_JOLIET=y`` by default, so no
-Kconfig tweaks are required. Build with::
+``CONFIG_FS_ISOFS=y`` and ``CONFIG_JOLIET=y`` by default, so no Kconfig
+tweaks are required. Build with::
make O=/tmp/b/efi-x86_app64 efi-x86_app64_defconfig
make O=/tmp/b/efi-x86_app64 -j$(nproc)
-The output is ``/tmp/b/efi-x86_app64/u-boot-app.efi``, a PE32+ x86_64 EFI
-application.
+The output is ``/tmp/b/efi-x86_app64/u-boot-app.efi``, a PE32+ x86_64
+EFI application.
If ``rustc`` is not installed, also disable the rust example build
-before ``make``::
+before the main ``make``::
scripts/config --file /tmp/b/efi-x86_app64/.config \\
-d RUST_EXAMPLES -d EXAMPLES
@@ -58,13 +57,14 @@ The script:
1. Reads the input ISO's boot record with ``xorriso -report_el_torito``
to pick up the volume label and the EFI system partition GUID.
-2. Extracts ``/casper/vmlinuz`` and ``/casper/initrd`` via ``xorriso -osirrox``
-3. Builds a fresh FAT32 ESP (auto-sized to fit) containing
- ``/EFI/BOOT/BOOTX64.EFI`` (the U-Boot app), ``/casper/vmlinuz``,
- ``/casper/initrd`` and ``/loader/entry.conf``
-4. Writes a new ISO with ``xorriso -indev ... -outdev ... -boot_image
- any replay -append_partition 2 ...``, replacing the original ESP
- while preserving all other boot metadata.
+2. Builds a small FAT ESP (4 MiB by default) containing just
+ ``/EFI/BOOT/BOOTX64.EFI`` -- the U-Boot EFI application.
+3. Writes a new ISO with
+ ``xorriso -indev ... -outdev ... -boot_image any replay
+ -append_partition 2 ... -map entry.conf /loader/entry.conf``,
+ which replaces the original ESP and adds ``/loader/entry.conf`` to
+ the ISO 9660 tree. The kernel and initrd stay in ``/casper/`` on the
+ ISO 9660 tree; U-Boot reads them directly via its isofs driver.
Relevant options:
@@ -72,14 +72,15 @@ Relevant options:
* ``-o PATH`` -- the output ISO (required).
* ``-a ARGS`` -- override the kernel command line written to
``loader/entry.conf``. The default is
- ``console=ttyS0,115200 console=tty0 --- quiet``, which logs the kernel output
- serial and video. Duplicate the ``console=`` arguments after ``---`` as well
- if you want casper and the running system logged to serial too.
+ ``console=ttyS0,115200 console=tty0 --- quiet``, which logs the
+ kernel to serial and video. Duplicate the ``console=`` arguments
+ after ``---`` as well if you want casper and the running system
+ logged to serial too.
* ``-k PATH`` / ``-i PATH`` -- override the kernel and initrd paths
inside the ISO if a distribution uses something other than
``casper/vmlinuz`` and ``casper/initrd``.
-* ``-s MiB`` -- force an ESP size; the default auto-sizes to fit the
- kernel, initrd and U-Boot app with 16 MiB of headroom.
+* ``-s MiB`` -- force an ESP size; the default is 4 MiB, enough for the
+ U-Boot binary.
* ``-t TITLE`` -- override the BLS entry title.
Testing under QEMU + OVMF
@@ -95,60 +96,23 @@ Testing under QEMU + OVMF
The expected boot trace on the serial console is roughly::
- BdsDxe: loading Boot0001 "UEFI Misc Device" from PciRoot(0x0)/Pci(0x3,0x0)
- BdsDxe: starting Boot0001 "UEFI Misc Device" from PciRoot(0x0)/Pci(0x3,0x0)
- Laceboot EFI App (using allocated RAM address 6beef000) starting
-
-
U-Boot Concept 2026.02
-
- CPU: x86_64, vendor <invalid cpu vendor>, device 0h
- Model: EFI x86 Application
- DRAM: 256 MiB
- Core: 22 devices, 13 uclasses, devicetree: separate
- EFI: disks 2, partitions 3
- Loading Environment from FAT... Unable to use efi 0:0...
- Video: 1280x800x32 @ 0
- Hit any key to stop autoboot: 2
- Hit any key to stop autoboot: 1
- Hit any key to stop autoboot: 0
- Scanning for bootflows in all bootdevs
- Seq Method State Uclass Part Ent E Name Filename
- --- ----------- ------ -------- ---- --- - ------------------------ ----------------
- Hunting with: fs
+ ...
Scanning bootdev 'efi_media_0.bootdev':
- 0 bls ready pci 2 0 efi_media_0.bootdev.part_ /loader/entry.conf
- ** Booting bootflow 'efi_media_0.bootdev.part_2' with bls
+ 0 bls ready pci 1 0 efi_media_0.bootdev.part_ /loader/entry.conf
+ ** Booting bootflow 'efi_media_0.bootdev.part_1' with bls
Retrieving file: /casper/vmlinuz
Retrieving file: /casper/initrd
- Valid Boot Flag
- Magic signature found
- Linux kernel version 6.8.0-41-generic (buildd@lcy02-amd64-100) #41-Ubuntu SMP PREEMPT_DYNAMIC Fri Aug 2 20:41:06 UTC 2024
- Building boot_params at 90000
- Loading bzImage at address 100000 (14926336 bytes)
- Initial RAM disk at linear address 8000000, size 47a003e (75104318 bytes)
- Kernel command line: "console=ttyS0,115200 console=tty0 --- console=ttyS0,115200 quiet"
-
+ Linux kernel version 6.8.0-41-generic ... Ubuntu
Starting kernel ...
- ...
-
-
-Why the kernel and initrd live on the ESP
------------------------------------------
-
-BLS paths are resolved against the partition holding ``loader/entry.conf``
-
-OVMF only exposes the EFI system partition as an ``EFI_BLOCK_IO`` protocol
-handle on CD media, so U-Boot's ``efi_media`` bootdev cannot see the ISO 9660
-partition directly and would fail to resolve ``/casper/vmlinuz`` if the entry
-were placed there. Copying the kernel and initrd onto the ESP sidesteps this;
-the ISO 9660 side remains bootable in its own right, and casper finds its
-squashfs on the original media at runtime.
+The bootflow appears on ``part_1`` -- the ISO 9660 partition -- because
+``entry.conf`` lives in the ISO 9660 tree and U-Boot reads kernel and
+initrd from the same partition via isofs.
See also
--------
-* :doc:`/usage/bls` — the U-Boot Boot Loader Specification bootmeth.
+* :doc:`/usage/bls` -- the U-Boot Boot Loader Specification bootmeth.
* `Boot Loader Specification
<https://uapi-group.org/specifications/specs/boot_loader_specification/>`_.
@@ -8,17 +8,18 @@ the EFI system partition. Each entry names a kernel, initrd and command
line; U-Boot's BOOTMETH_BLS scans the ESP and presents them in its boot
menu, replacing the shim/grub chain that Ubuntu ships by default.
-The ISO's appended EFI System Partition is replaced with a fresh ESP
-containing:
+Two things change in the rewritten ISO:
- /EFI/BOOT/BOOTX64.EFI U-Boot EFI app (built with CONFIG_BOOTMETH_BLS=y)
- /casper/vmlinuz copied from the input ISO
- /casper/initrd copied from the input ISO
- /loader/entry.conf BLS Type #1 entry pointing at the above
+ EFI system partition /EFI/BOOT/BOOTX64.EFI replaced with the U-Boot
+ EFI app
+ ISO 9660 tree /loader/entry.conf added, pointing at the
+ existing /casper/vmlinuz
+ and /casper/initrd
-The ISO9660 tree is preserved unchanged, so casper still finds its squashfs
-by disc label at runtime. All other boot records (BIOS El Torito, grub2 MBR,
-GPT layout) are preserved by xorriso's -boot_image any replay.
+The kernel and initrd stay where Ubuntu put them. U-Boot reads them off
+the ISO 9660 partition directly via its isofs driver. All other boot
+records (BIOS El Torito, grub2 MBR, GPT layout) are preserved by
+xorriso's -boot_image any replay.
Quick start (run from the root of the U-Boot tree)::
@@ -114,65 +115,32 @@ def parse_boot_report(iso: Path) -> tuple[str, str]:
return m_vol.group(1), m_esp.group(1)
-def extract_from_iso(iso: Path, src: str, dst: Path) -> None:
- """Pull a single file out of the ISO9660 tree via xorriso -osirrox"""
- dst.parent.mkdir(parents=True, exist_ok=True)
- command.run(
- 'xorriso', '-osirrox', 'on', '-indev', str(iso),
- '-extract', f'/{src.lstrip("/")}', str(dst),
- )
-
-
-def build_esp(
- esp_path: Path,
- size_mib: int,
- uboot_efi: Path,
- vmlinuz: Path,
- initrd: Path,
- entry_conf: str,
-) -> None:
- """Create a fresh FAT32 ESP at esp_path populated with BLS + kernel.
+def build_esp(esp_path: Path, size_mib: int, uboot_efi: Path) -> None:
+ """Create a fresh FAT ESP containing only the U-Boot EFI application.
Args:
esp_path (Path): Output file to hold the new ESP image
size_mib (int): Size of the ESP in mebibytes
uboot_efi (Path): U-Boot EFI app to install as /EFI/BOOT/BOOTX64.EFI
- vmlinuz (Path): Kernel image to install under /casper/vmlinuz
- initrd (Path): Initrd image to install under /casper/initrd
- entry_conf (str): Contents for /loader/entry.conf (BLS Type #1)
"""
with esp_path.open('wb') as f:
f.truncate(size_mib * MIB)
- command.output('mkfs.vfat', '-F32', '-n', 'ESP', str(esp_path))
- command.run('mmd', '-i', str(esp_path),
- '::EFI', '::EFI/BOOT', '::loader', '::casper')
-
- def put(src: Path, dst: str) -> None:
- command.run('mcopy', '-i', str(esp_path), str(src), f'::{dst}')
-
- put(uboot_efi, 'EFI/BOOT/BOOTX64.EFI')
- put(vmlinuz, 'casper/vmlinuz')
- put(initrd, 'casper/initrd')
-
- entry = Path(esp_path.parent) / 'entry.conf'
- entry.write_text(entry_conf)
- put(entry, 'loader/entry.conf')
-
-
-def auto_esp_size(files: list[Path], headroom_mib: int = 16) -> int:
- """Sum file sizes + headroom, round up to MiB, floor at 64 MiB (FAT32)"""
- total = sum(f.stat().st_size for f in files) + headroom_mib * MIB
- mib = (total + MIB - 1) // MIB
- return max(mib, 64)
+ # FAT12 is enough for the small ESP we emit (just a U-Boot binary).
+ command.output('mkfs.vfat', '-F12', '-n', 'ESP', str(esp_path))
+ command.run('mmd', '-i', str(esp_path), '::EFI', '::EFI/BOOT')
+ command.run('mcopy', '-i', str(esp_path), str(uboot_efi),
+ '::EFI/BOOT/BOOTX64.EFI')
def repack_iso(
in_iso: Path, out_iso: Path, esp_img: Path, esp_guid: str,
+ entry_conf: Path,
) -> None:
- """Stream the input ISO to a new ISO, replacing partition 2's data.
+ """Stream the input ISO to a new ISO with the ESP and BLS entry replaced.
-boot_image any replay preserves every other boot record (BIOS El Torito,
- grub2 MBR, GPT layout); only the bytes behind partition 2 are rewritten
+ grub2 MBR, GPT layout); only the bytes behind partition 2 are rewritten,
+ plus /loader/entry.conf is added to the ISO 9660 tree.
"""
command.run(
'xorriso',
@@ -180,6 +148,7 @@ def repack_iso(
'-outdev', str(out_iso),
'-boot_image', 'any', 'replay',
'-append_partition', '2', esp_guid, str(esp_img),
+ '-map', str(entry_conf), '/loader/entry.conf',
'-commit',
)
@@ -202,7 +171,7 @@ def main() -> None:
p.add_argument('-t', '--title', default=None,
help='BLS entry title (default: derived from volume label)')
p.add_argument('-s', '--esp-size', type=int, default=None,
- help='ESP size in MiB (default: auto-size to fit)')
+ help='ESP size in MiB (default: 4 MiB)')
args = p.parse_args()
if not args.iso.is_file():
@@ -220,27 +189,22 @@ def main() -> None:
with tempfile.TemporaryDirectory(prefix='iso2uboot.') as td:
work = Path(td)
- vmlinuz = work / 'vmlinuz'
- initrd = work / 'initrd'
- print(f'=> Extracting /{args.kernel} and /{args.initrd}')
- extract_from_iso(args.iso, args.kernel, vmlinuz)
- extract_from_iso(args.iso, args.initrd, initrd)
-
- esp_mib = args.esp_size or auto_esp_size([vmlinuz, initrd, args.uboot])
+ esp_mib = args.esp_size or 4
print(f'=> Building {esp_mib} MiB ESP')
-
- entry_conf = f'''\
-title {title}
-linux /casper/vmlinuz
-initrd /casper/initrd
-options {args.cmdline}
-'''
esp = work / 'esp.img'
- build_esp(esp, esp_mib, args.uboot, vmlinuz, initrd, entry_conf)
+ build_esp(esp, esp_mib, args.uboot)
+
+ entry = work / 'entry.conf'
+ entry.write_text(
+ f'title {title}\n'
+ f'linux /{args.kernel}\n'
+ f'initrd /{args.initrd}\n'
+ f'options {args.cmdline}\n'
+ )
print(f'=> Repacking to {args.out}')
- repack_iso(args.iso, args.out, esp, esp_guid)
+ repack_iso(args.iso, args.out, esp, esp_guid, entry)
size_mib = args.out.stat().st_size / MIB
print(f'=> Done: {args.out} ({size_mib:.1f} MiB)')