Convert build-efi script to Python
Before this gets any longer, convert it to Python so it is easier to maintain. Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
259
scripts/build-efi.py
Executable file
259
scripts/build-efi.py
Executable file
@@ -0,0 +1,259 @@
|
||||
#!/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
|
||||
|
||||
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
|
||||
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'
|
||||
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)]
|
||||
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'
|
||||
cmd += '-nic', 'none'
|
||||
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')
|
||||
|
||||
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', 'mkdir', '-p', self.mnt)
|
||||
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()
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user