[PATCH 01/32] Convert build-efi script to Python
Heinrich Schuchardt
xypron.glpk at gmx.de
Sun Feb 9 19:55:40 CET 2025
Am 3. Februar 2025 18:41:54 MEZ schrieb Simon Glass <sjg at chromium.org>:
>Before this gets any longer, convert it to Python so it is easier to
>maintain.
>
>Signed-off-by: Simon Glass <sjg at chromium.org>
>---
>
> MAINTAINERS | 2 +-
> doc/develop/uefi/u-boot_on_efi.rst | 4 +-
> scripts/build-efi.py | 258 +++++++++++++++++++++++++++++
> scripts/build-efi.sh | 207 -----------------------
> 4 files changed, 261 insertions(+), 210 deletions(-)
> create mode 100755 scripts/build-efi.py
> delete mode 100755 scripts/build-efi.sh
>
>diff --git a/MAINTAINERS b/MAINTAINERS
>index 9ba0c98cef2..a8e81577090 100644
>--- a/MAINTAINERS
>+++ b/MAINTAINERS
>@@ -1072,7 +1072,7 @@ F: doc/develop/uefi/u-boot_on_efi.rst
> F: drivers/block/efi-media-uclass.c
> F: drivers/block/sb_efi_media.c
> F: lib/efi/efi_app.c
>-F: scripts/build-efi.sh
>+F: scripts/build-efi.py
> F: test/dm/efi_media.c
>
> EFI LOGGING
>diff --git a/doc/develop/uefi/u-boot_on_efi.rst b/doc/develop/uefi/u-boot_on_efi.rst
>index 245b4af1fa3..9d441cdc2c5 100644
>--- a/doc/develop/uefi/u-boot_on_efi.rst
>+++ b/doc/develop/uefi/u-boot_on_efi.rst
>@@ -96,7 +96,7 @@ that EFI does not support booting a 64-bit application from a 32-bit
> EFI (or vice versa). Also it will often fail to print an error message if
> you get this wrong.
>
>-You may find the script `scripts/build-efi.sh` helpful for building and testing
>+You may find the script `scripts/build-efi.py` helpful for building and testing
> U-Boot on UEFI on QEMU. It also includes links to UEFI binaries dating from
> 2021.
>
>@@ -201,7 +201,7 @@ Example run
>
> This shows running with serial enabled (see `include/configs/efi-x86_app.h`)::
>
>- $ scripts/build-efi.sh -wsPr
>+ $ scripts/build-efi.py -wsPr
> Packaging efi-x86_app32
> Running qemu-system-i386
>
>diff --git a/scripts/build-efi.py b/scripts/build-efi.py
>new file mode 100755
>index 00000000000..495817bc064
>--- /dev/null
>+++ b/scripts/build-efi.py
>@@ -0,0 +1,258 @@
>+#!/usr/bin/env python
>+# SPDX-License-Identifier: GPL-2.0+
>+"""
>+Script to build an EFI thing suitable for booting with QEMU, possibly running
>+it also.
>+
>+UEFI binaries for QEMU used for testing this script:
>+
>+OVMF-pure-efi.i386.fd at
>+https://drive.google.com/file/d/1jWzOAZfQqMmS2_dAK2G518GhIgj9r2RY/view?usp=sharing
>+
>+OVMF-pure-efi.x64.fd at
>+https://drive.google.com/file/d/1c39YI9QtpByGQ4V0UNNQtGqttEzS-eFV/view?usp=sharing
Who would dare to download binaries of unknown origin and execute these?
The filenames do not match what is created by upstream EDK II.
These references should be removed.
Instead you could provide the EDK II version that you used for testing and the file name created by EDK II.
>+
>+Use ~/.build-efi to configure the various paths used by this script.
>+"""
>+
>+from argparse import ArgumentParser
>+import configparser
>+import glob
>+import os
>+import re
>+import shutil
>+import sys
>+import time
>+
>+
>+OUR_PATH = os.path.dirname(os.path.realpath(__file__))
>+OUR1_PATH = os.path.dirname(OUR_PATH)
>+
>+# Bring in the patman and dtoc libraries (but don't override the first path
>+# in PYTHONPATH)
>+sys.path.insert(2, os.path.join(OUR1_PATH, 'tools'))
>+
>+
>+# pylint: disable=C0413
>+from u_boot_pylib import command
>+from u_boot_pylib import tools
>+
>+
>+def parse_args():
>+ """Parse the program arguments
>+
>+ Return:
>+ Namespace object
>+ """
>+ parser = ArgumentParser(
>+ epilog='Script for running U-Boot as an EFI app/payload')
>+ parser.add_argument('-a', '--app', action='store_true',
>+ help='Package up the app')
>+ parser.add_argument('-k', '--kernel', action='store_true',
>+ help='Add a kernel')
>+ parser.add_argument('-o', '--old', action='store_true',
>+ help='Use old EFI app build (before 32/64 split)')
>+ parser.add_argument('-p', '--payload', action='store_true',
>+ help='Package up the payload')
>+ parser.add_argument('-P', '--partition', action='store_true',
>+ help='Create a partition table')
>+ parser.add_argument('-r', '--run', action='store_true',
>+ help='Run QEMU with the image')
>+ parser.add_argument('-s', '--serial', action='store_true',
>+ help='Run QEMU with serial only (no display)')
>+ parser.add_argument('-w', '--word', action='store_true',
>+ help='Use word version (32-bit) rather than 64-bit')
>+
>+ args = parser.parse_args()
>+
>+ if args.app and args.payload:
>+ raise ValueError('Please choose either app or payload, not both')
>+ return args
>+
>+
>+def get_settings():
>+ """Get settings from the settings file
>+
>+ Return:
>+ ConfigParser containing settings
>+ """
>+ settings = configparser.ConfigParser()
>+ fname = f'{os.getenv("HOME")}/.build-efi'
>+ if not os.path.exists(fname):
>+ print('No config file found ~/.build-efi\nCreating one...\n')
>+ tools.write_file(fname, '''[build-efi]
>+# Mount path for the temporary image
>+mount_point = /mnt/test-efi
>+
>+# Image-output filename
>+image_file = try.img
>+
>+# Set ubdir to the build directory where you build U-Boot out-of-tree
>+# We avoid in-tree build because it gets confusing trying different builds
>+build_dir = /tmp/b
>+
>+# Build the kernel with: make O=/tmp/kernel
>+bzimage = /tmp/kernel/arch/x86/boot/bzImage
>+
>+# Place where OVMF-pure-efi.i386.fd etc. are kept
Is this i386 only? What would you use for arm64 and amd64?
>+efi_dir = .
>+''', binary=False)
>+ settings.read(fname)
>+ return settings
>+
>+
>+class BuildEfi:
>+ """Class to collect together the various bits of state while running"""
>+ def __init__(self, settings, args):
>+ self.settings = settings
>+ self.img = self.get_setting('image_file', 'try.img')
>+ self.build_dir = self.get_setting("build_dir", '/tmp')
>+ self.mnt = self.get_setting("mount_point", '/mnt/test-efi')
>+ self.tmp = None
>+ self.args = args
>+
>+ def get_setting(self, name, fallback=None):
>+ """Get a setting by name
>+
>+ Args:
>+ name (str): Name of setting to retrieve
>+ fallback (str or None): Value to return if the setting is missing
>+ """
>+ return self.settings.get('build-efi', name, fallback=fallback)
>+
>+ def run_qemu(self, bitness, serial_only):
>+ """Run QEMU
>+
>+ Args:
>+ bitness (int): Bitness to use, 32 or 64
>+ serial_only (bool): True to run without a display
>+ """
>+ extra = []
>+ efi_dir = self.get_setting("efi_dir")
>+ if bitness == 64:
>+ qemu = 'qemu-system-x86_64'
>+ bios = 'OVMF-pure-efi.x64.fd'
This is not a file name used by EDK II.
And what about arm64?
>+ else:
>+ qemu = 'qemu-system-i386'
>+ bios = 'OVMF-pure-efi.i386.fd'
>+ if serial_only:
>+ extra = ['-display', 'none', '-serial', 'mon:stdio']
>+ serial_msg = ' (Ctrl-a x to quit)'
>+ else:
>+ extra = ['-serial', 'mon:stdio']
>+ serial_msg = ''
>+ print(f'Running {qemu}{serial_msg}')
>+
>+ # Use 512MB since U-Boot EFI likes to have 256MB to play with
>+ cmd = [qemu, '-bios', os.path.join(efi_dir), bios)]
For EDK II you should use pflash.
>+ cmd += '-m', '512'
>+ cmd += '-drive', f'id=disk,file={self.img},if=none,format=raw'
>+ cmd += '-device', 'ahci,id=ahci'
>+ cmd += '-device', 'ide-hd,drive=disk,bus=ahci.0'
How outdated is this? Use virtio.
>+ cmd += '-nic', 'none'
Why none? Use virtio.
>+ cmd += extra
>+ command.run(*cmd)
>+
>+ def setup_files(self, build, build_type):
>+ """Set up files in the staging area
>+
>+ Args:
>+ build (str): Name of build being packaged, e.g. 'efi-x86_app32'
>+ build_type (str): Build type ('app' or 'payload')
>+ """
>+ print(f'Packaging {build}')
>+ if not os.path.exists(self.tmp):
>+ os.mkdir(self.tmp)
>+ fname = f'u-boot-{build_type}.efi'
>+ tools.write_file(f'{self.tmp}/startup.nsh', f'fs0:{fname}',
>+ binary=False)
>+ shutil.copy(f'{self.build_dir}/{build}/{fname}', self.tmp)
>+
>+ def copy_files(self):
>+ """Copy files into the filesystem"""
>+ command.run('sudo', 'cp', *glob.glob(f'{self.tmp}/*'), self.mnt)
>+ if self.args.kernel:
>+ bzimage = self.get_setting('bzimage_file', 'bzImage')
>+ command.run('sudo', 'cp', bzimage, f'{self.mnt}/vmlinuz')
Please, do not use sudo.
You can create partitioned disks with files without falling back to sudo.
Best regards
Heinrich
>+
>+ def setup_raw(self):
>+ """Create a filesystem on a raw device and copy in the files"""
>+ command.output('mkfs.vfat', self.img)
>+ command.run('sudo', 'mount', '-o', 'loop', self.img, self.mnt)
>+ self.copy_files()
>+ command.run('sudo', 'umount', self.mnt)
>+
>+ def setup_part(self):
>+ """Set up a partition table
>+
>+ Create a partition table and put the filesystem in the first partition
>+ then copy in the files
>+ """
>+
>+ # Create a gpt partition table with one partition
>+ command.run('parted', self.img, 'mklabel', 'gpt', capture_stderr=True)
>+
>+ # This doesn't work correctly. It creates:
>+ # Number Start End Size File system Name Flags
>+ # 1 1049kB 24.1MB 23.1MB boot msftdata
>+ # Odd if the same is entered interactively it does set the FS type
>+ command.run('parted', '-s', '-a', 'optimal', '--',
>+ self.img, 'mkpart', 'boot', 'fat32', '1MiB', '23MiB')
>+
>+ # Map this partition to a loop device. Output is something like:
>+ # add map loop48p1 (252:3): 0 45056 linear 7:48 2048
>+ out = command.output('sudo', 'kpartx', '-av', self.img)
>+ m = re.search(r'(loop.*p.)', out)
>+ if not m:
>+ raise ValueError(f'Invalid output from kpartx: {out}')
>+
>+ boot_dev = m.group(1)
>+ dev = f'/dev/mapper/{boot_dev}'
>+
>+ command.output('mkfs.vfat', dev)
>+
>+ command.run('sudo', 'mount', '-o', 'loop', dev, self.mnt)
>+
>+ try:
>+ self.copy_files()
>+ finally:
>+ # Sync here since this makes kpartx more likely to work the first time
>+ command.run('sync')
>+ command.run('sudo', 'umount', self.mnt)
>+
>+ # For some reason this needs a sleep or it sometimes fails, if it was
>+ # run recently (in the last few seconds)
>+ try:
>+ cmd = 'sudo', 'kpartx', '-d', self.img
>+ command.output(*cmd)
>+ except command.CommandExc:
>+ time.sleep(0.5)
>+ command.output(*cmd)
>+
>+ def start(self):
>+ """This does all the work"""
>+ args = self.args
>+ bitness = 32 if args.word else 64
>+ build_type = 'payload' if args.payload else 'app'
>+ self.tmp = f'{self.build_dir}/efi{bitness}{build_type}'
>+ build = f'efi-x86_{build_type}{bitness}'
>+
>+ if args.old and bitness == 32:
>+ build = f'efi-x86_{build_type}'
>+
>+ self.setup_files(build, build_type)
>+
>+ command.output('qemu-img', 'create', self.img, '24M')
>+
>+ if args.partition:
>+ self.setup_part()
>+ else:
>+ self.setup_raw()
>+
>+ if args.run:
>+ self.run_qemu(bitness, args.serial)
>+
>+
>+if __name__ == "__main__":
>+ efi = BuildEfi(get_settings(), parse_args())
>+ efi.start()
>diff --git a/scripts/build-efi.sh b/scripts/build-efi.sh
>deleted file mode 100755
>index 6b7df2e9bfe..00000000000
>--- a/scripts/build-efi.sh
>+++ /dev/null
>@@ -1,207 +0,0 @@
>-#!/bin/bash
>-# SPDX-License-Identifier: GPL-2.0+
>-#
>-# Script to build an EFI thing suitable for booting with QEMU, possibly running
>-# it also.
>-
>-# This just an example. It assumes that
>-
>-# - you build U-Boot in ${ubdir}/<name> where <name> is the U-Boot board config
>-# - /mnt/x is a directory used for mounting
>-# - you have access to the 'pure UEFI' builds for QEMU
>-#
>-# UEFI binaries for QEMU used for testing this script:
>-#
>-# OVMF-pure-efi.i386.fd at
>-# https://drive.google.com/file/d/1jWzOAZfQqMmS2_dAK2G518GhIgj9r2RY/view?usp=sharing
>-
>-# OVMF-pure-efi.x64.fd at
>-# https://drive.google.com/file/d/1c39YI9QtpByGQ4V0UNNQtGqttEzS-eFV/view?usp=sharing
>-
>-bzimage_fname=/tmp/kernel/arch/x86/boot/bzImage
>-
>-set -e
>-
>-usage() {
>- echo "Usage: $0 [-a | -p] [other opts]" 1>&2
>- echo 1>&2
>- echo " -a - Package up the app" 1>&2
>- echo " -k - Add a kernel" 1>&2
>- echo " -o - Use old EFI app build (before 32/64 split)" 1>&2
>- echo " -p - Package up the payload" 1>&2
>- echo " -P - Create a partition table" 1>&2
>- echo " -r - Run QEMU with the image" 1>&2
>- echo " -s - Run QEMU with serial only (no display)" 1>&2
>- echo " -w - Use word version (32-bit)" 1>&2
>- exit 1
>-}
>-
>-# 32- or 64-bit EFI
>-bitness=64
>-
>-# app or payload ?
>-type=app
>-
>-# create a partition table and put the filesystem in that (otherwise put the
>-# filesystem in the raw device)
>-part=
>-
>-# run the image with QEMU
>-run=
>-
>-# run QEMU without a display (U-Boot must be set to stdout=serial)
>-serial=
>-
>-# before the 32/64 split of the app
>-old=
>-
>-# package up a kernel as well
>-kernel=
>-
>-# Set ubdir to the build directory where you build U-Boot out-of-tree
>-# We avoid in-tree build because it gets confusing trying different builds
>-ubdir=/tmp/b/
>-
>-while getopts "akopPrsw" opt; do
>- case "${opt}" in
>- a)
>- type=app
>- ;;
>- p)
>- type=payload
>- ;;
>- k)
>- kernel=1
>- ;;
>- r)
>- run=1
>- ;;
>- s)
>- serial=1
>- ;;
>- w)
>- bitness=32
>- ;;
>- o)
>- old=1
>- ;;
>- P)
>- part=1
>- ;;
>- *)
>- usage
>- ;;
>- esac
>-done
>-
>-run_qemu() {
>- extra=
>- if [[ "${bitness}" = "64" ]]; then
>- qemu=qemu-system-x86_64
>- bios=OVMF-pure-efi.x64.fd
>- else
>- qemu=qemu-system-i386
>- bios=OVMF-pure-efi.i386.fd
>- fi
>- if [[ -n "${serial}" ]]; then
>- extra="-display none -serial mon:stdio"
>- else
>- extra="-serial mon:stdio"
>- fi
>- echo "Running ${qemu}"
>- # Use 512MB since U-Boot EFI likes to have 256MB to play with
>- "${qemu}" -bios "${bios}" \
>- -m 512 \
>- -drive id=disk,file="${IMG}",if=none,format=raw \
>- -nic none -device ahci,id=ahci \
>- -device ide-hd,drive=disk,bus=ahci.0 ${extra}
>-}
>-
>-setup_files() {
>- echo "Packaging ${BUILD}"
>- mkdir -p $TMP
>- cat >$TMP/startup.nsh <<EOF
>-fs0:u-boot-${type}.efi
>-EOF
>- sudo cp ${ubdir}/${BUILD}/u-boot-${type}.efi $TMP
>-
>- # Can copy in other files here:
>- #sudo cp ${ubdir}/$BUILD/image.bin $TMP/chromeos.rom
>- #sudo cp /boot/vmlinuz-5.4.0-77-generic $TMP/vmlinuz
>-}
>-
>-# Copy files into the filesystem
>-copy_files() {
>- sudo cp $TMP/* $MNT
>- if [[ -n "${kernel}" ]]; then
>- sudo cp ${bzimage_fname} $MNT/vmlinuz
>- fi
>-}
>-
>-# Create a filesystem on a raw device and copy in the files
>-setup_raw() {
>- mkfs.vfat "${IMG}" >/dev/null
>- sudo mount -o loop "${IMG}" $MNT
>- copy_files
>- sudo umount $MNT
>-}
>-
>-# Create a partition table and put the filesystem in the first partition
>-# then copy in the files
>-setup_part() {
>- # Create a gpt partition table with one partition
>- parted "${IMG}" mklabel gpt 2>/dev/null
>-
>- # This doesn't work correctly. It creates:
>- # Number Start End Size File system Name Flags
>- # 1 1049kB 24.1MB 23.1MB boot msftdata
>- # Odd if the same is entered interactively it does set the FS type
>- parted -s -a optimal -- "${IMG}" mkpart boot fat32 1MiB 23MiB
>-
>- # Map this partition to a loop device
>- kp="$(sudo kpartx -av ${IMG})"
>- read boot_dev<<<$(grep -o 'loop.*p.' <<< "${kp}")
>- test "${boot_dev}"
>- dev="/dev/mapper/${boot_dev}"
>-
>- mkfs.vfat "${dev}" >/dev/null
>-
>- sudo mount -o loop "${dev}" $MNT
>-
>- copy_files
>-
>- # Sync here since this makes kpartx more likely to work the first time
>- sync
>- sudo umount $MNT
>-
>- # For some reason this needs a sleep or it sometimes fails, if it was
>- # run recently (in the last few seconds)
>- if ! sudo kpartx -d "${IMG}" > /dev/null; then
>- sleep .5
>- sudo kpartx -d "${IMG}" > /dev/null || \
>- echo "Failed to remove ${boot_dev}, use: sudo kpartx -d ${IMG}"
>- fi
>-}
>-
>-TMP="/tmp/efi${bitness}${type}"
>-MNT=/mnt/x
>-BUILD="efi-x86_${type}${bitness}"
>-IMG=try.img
>-
>-if [[ -n "${old}" && "${bitness}" = "32" ]]; then
>- BUILD="efi-x86_${type}"
>-fi
>-
>-setup_files
>-
>-qemu-img create "${IMG}" 24M >/dev/null
>-
>-if [[ -n "${part}" ]]; then
>- setup_part
>-else
>- setup_raw
>-fi
>-
>-if [[ -n "${run}" ]]; then
>- run_qemu
>-fi
More information about the U-Boot
mailing list