[Concept,10/16] scripts: ubuntu-iso-to-uboot: Drop kernel/initrd ESP copy

Message ID 20260421183511.2044469-11-sjg@u-boot.org
State New
Headers
Series efi-x86: Boot Ubuntu live ISOs via U-Boot + BLS, end to end |

Commit Message

Simon Glass April 21, 2026, 6:34 p.m. UTC
  From: Simon Glass <sjg@chromium.org>

The script copies /casper/vmlinuz and /casper/initrd onto the rewritten
EFI system partition because BLS paths resolve against the partition
holding loader/entry.conf and, before isofs streaming was fixed, U-Boot
could not reach the ISO 9660 partition cleanly. With isofs streaming
now fixed and the BLS iterator covering all partitions on this target,
those two copies are no longer needed.

Drop the kernel and initrd copy, shrink the ESP to 4 MiB (enough for
the U-Boot binary), and instead add /loader/entry.conf to the ISO 9660
tree in the same xorriso pass via its -map predicate. The kernel and
initrd stay where Ubuntu places them on the ISO 9660 side; U-Boot reads
them directly via its isofs driver. The resulting ISO no longer
duplicates ~90 MiB of kernel and initrd data.

Update the documentation to match.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

 doc/usage/os/ubuntu-live.rst   | 110 +++++++++++----------------------
 scripts/ubuntu-iso-to-uboot.py | 104 ++++++++++---------------------
 2 files changed, 71 insertions(+), 143 deletions(-)

-- 
2.43.0
  

Patch

diff --git a/doc/usage/os/ubuntu-live.rst b/doc/usage/os/ubuntu-live.rst
index bd4e85e4c8a..fad476b4832 100644
--- a/doc/usage/os/ubuntu-live.rst
+++ b/doc/usage/os/ubuntu-live.rst
@@ -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/>`_.
diff --git a/scripts/ubuntu-iso-to-uboot.py b/scripts/ubuntu-iso-to-uboot.py
index ec32ac7b0dd..ec2e3cdd2c3 100755
--- a/scripts/ubuntu-iso-to-uboot.py
+++ b/scripts/ubuntu-iso-to-uboot.py
@@ -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)')