Compare commits

...

6 Commits
extm ... script

Author SHA1 Message Date
Simon Glass
fd087fd131 scripts: Convert the build-qemu script to Python
Convert this to Python and make use of the build_helpers module. Update
that module to remove old options and improve the ordering of options.

The script doubles in size, part of which is being a lot more friendly
with virtiofsd problems, as well as adding a verbose mode.

Update the documentation as well.

Series-to: u-boot
Cover-letter:
script: Improve the QEMU scripts to Python
This series updates the two QEMU scripts available in U-Boot:

- Converts build-qemu to Python
- Converts build-efi to avoid sudo (unless SCT is used)
- Introduces a proper settings file shared between both scripts

It also includes a few documentation updates.

The next step is to get these scripts running in CI.
END

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-05-11 15:32:10 +02:00
Simon Glass
b33d5ef5f6 u_boot_pylib: Support a fatal level in tout
It is convenient to be able to print a message and exit. Add a new
'fatal' level to support this.

Update some assumptions about the level, so that the tools continue to
work as now.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-05-11 15:32:00 +02:00
Simon Glass
a893e75eaf scripts: Create a common settings file for QEMU scripts
Move the settings into a common file so they can be used by all tools.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-05-11 14:43:15 +02:00
Simon Glass
60d86f3cf9 scripts: Convert the build-efi script to avoid sudo
Make use of the mk_fs() function to avoid needing to use sudo to mount
the loopback partition. This makes the script a little more friendly for
those nervous about sudo in their tools.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-05-11 14:43:15 +02:00
Simon Glass
b6b75b2d56 scripts: Rename build-efi.py to drop the file extension
The .py extension isn't very useful. Drop it and update the shebang to
specify Python 3

Update the docs for this and also drop the old reference to 2021 images.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-05-11 14:42:13 +02:00
Simon Glass
4dff28c552 test: Update fs_helper to support passing in the image
This function is useful for other scripts, so add a parameter to specify
the image name, if desired.

Also add a flag to quieten the output of some of the tools, since in
many cases we only want to see warnings / errors.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-05-11 13:54:39 +02:00
10 changed files with 660 additions and 386 deletions

View File

@@ -1074,7 +1074,7 @@ F: doc/develop/uefi/u-boot_on_efi.rst
F: drivers/block/efi-media-uclass.c F: drivers/block/efi-media-uclass.c
F: drivers/block/sb_efi_media.c F: drivers/block/sb_efi_media.c
F: lib/efi/ F: lib/efi/
F: scripts/build-efi.py F: scripts/build-efi
F: test/dm/efi_media.c F: test/dm/efi_media.c
EFI LOGGING EFI LOGGING
@@ -1126,7 +1126,8 @@ S: Maintained
W: https://docs.u-boot.org/en/latest/board/emulation/script.html W: https://docs.u-boot.org/en/latest/board/emulation/script.html
F: configs/qemu_x86* F: configs/qemu_x86*
F: doc/board/emulation/script.rst F: doc/board/emulation/script.rst
F: scripts/build-qemu.sh F: scripts/build-qemu
F: scripts/build_helper.py
ENVIRONMENT ENVIRONMENT
M: Joe Hershberger <joe.hershberger@ni.com> M: Joe Hershberger <joe.hershberger@ni.com>

View File

@@ -3,25 +3,66 @@
Script for building and running Script for building and running
=============================== ===============================
You may find the script `scripts/build-qemu.sh` helpful for building and testing You may find the script `scripts/build-qemu` helpful for building and testing
U-Boot on QEMU. U-Boot on QEMU.
If uses a environment variables to control how it works: If uses a settings file `~/.u_boot_qemu` to control how it works:
ubdir build_dir
base directory for building U-Boot, with each board being in its own base directory for building U-Boot, with each board being in its own
subdirectory subdirectory
imagedir image_dir
directory containing OS images, containin a subdirectory for each distro directory containing OS images, containing a subdirectory for each distro
type (e.g. ubuntu/ type (e.g. `ubuntu/`)
bzimage
path to a bzImage file to supply to boot x86 Linux
efi_image_file
output filename for the disk image containing an EFI app / payload
efi_dir
directory when pre-built UEFI images are kept, e.g. OVMF-pure-efi.i386.fd
sct_dir
directory when the UEFI Self-Certification Test (SCT) is kept
sct_mnt
temporary mount point for building SCT: note this requires sudo
A sample file is written if you don't have one, e.g.::
# U-Boot QEMU-scripts config
[DEFAULT]
# Set to the build directory where you build U-Boot out-of-tree
# We avoid in-tree build because it gets confusing trying different builds
# Each board gets a build in a separate subdir
build_dir = /tmp/b
# Image directory (for OS images)
image_dir = ~/dev/os
# Build the kernel with: make O=/tmp/kernel
bzimage = /tmp/kernel/arch/x86/boot/bzImage
# EFI image-output filename
efi_image_file = try.img
# Directory where OVMF-pure-efi.i386.fd etc. are kept
efi_dir = ~/dev/efi
# Directory where SCT image (sct.img) is kept
sct_dir = ~/dev/efi/sct
# Directory where the SCT image is temporarily mounted for modification
sct_mnt = /mnt/sct
Once configured, you can build and run QEMU for arm64 like this:: Once configured, you can build and run QEMU for arm64 like this::
scripts/build-qemu.sh -rsw scripts/build-qemu -rsw
No support is currently included for specifying a root disk, so this script can
only be used to start installers.
Options Options
~~~~~~~ ~~~~~~~
@@ -34,6 +75,15 @@ Options are available to control the script:
-B -B
Don't build; assume a build exists Don't build; assume a build exists
-d/--disk DISK
Root disk image file to use with QEMU
-e/--sct-run
Package an run UEFI Self-Certification Test (SCT)
-E/--use-tianocore
Run Tianocore (OVMF) instead of U-Boot
-k -k
Use kvm - kernel-based Virtual Machine. By default QEMU uses its own Use kvm - kernel-based Virtual Machine. By default QEMU uses its own
emulator emulator
@@ -52,10 +102,8 @@ Options are available to control the script:
-s -s
Use serial only (no display) Use serial only (no display)
-S/--sct-seq SCT_SEQ
SCT sequence-file to be written into the SCT image if -e
-w -w
Use word version (32-bit). By default, 64-bit is used Use word version (32-bit). By default, 64-bit is used
.. note::
Note: For now this is a shell script, but if it expands it might be better
as Python, accepting the slower startup.

View File

@@ -97,9 +97,8 @@ 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 EFI (or vice versa). Also it will often fail to print an error message if
you get this wrong. you get this wrong.
You may find the script `scripts/build-efi.py` helpful for building and testing You may find the script `scripts/build-efi` helpful for building and testing
U-Boot on UEFI on QEMU. It also includes links to UEFI binaries dating from U-Boot on UEFI on QEMU.
2021.
See `Example run`_ for an example run. See `Example run`_ for an example run.
@@ -202,7 +201,7 @@ Example run
This shows running with serial enabled (see `include/configs/efi-x86_app.h`):: This shows running with serial enabled (see `include/configs/efi-x86_app.h`)::
$ scripts/build-efi.py -wsPr $ scripts/build-efi -wsPr
Packaging efi-x86_app32 Packaging efi-x86_app32
Running qemu-system-i386 Running qemu-system-i386

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0+ # SPDX-License-Identifier: GPL-2.0+
""" """
Script to build an EFI thing suitable for booting with QEMU, possibly running Script to build an EFI thing suitable for booting with QEMU, possibly running
@@ -16,22 +16,10 @@ Use ~/.build-efi to configure the various paths used by this script.
""" """
from argparse import ArgumentParser from argparse import ArgumentParser
import configparser
import glob
import os import os
import re
import shutil 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'))
from build_helper import Helper
# pylint: disable=C0413 # pylint: disable=C0413
from u_boot_pylib import command from u_boot_pylib import command
@@ -74,56 +62,15 @@ def parse_args():
return args 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 BuildEfi:
"""Class to collect together the various bits of state while running""" """Class to collect together the various bits of state while running"""
def __init__(self, settings, args): def __init__(self, args):
self.settings = settings self.helper = Helper()
self.img = self.get_setting('image_file', 'try.img') self.helper.read_settings()
self.build_dir = self.get_setting("build_dir", '/tmp') self.img = self.helper.get_setting('efi_image_file', 'efi.img')
self.mnt = self.get_setting("mount_point", '/mnt/test-efi') self.build_dir = self.helper.get_setting("build_dir", '/tmp')
self.tmp = None
self.args = args 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): def run_qemu(self, bitness, serial_only):
"""Run QEMU """Run QEMU
@@ -132,7 +79,7 @@ class BuildEfi:
serial_only (bool): True to run without a display serial_only (bool): True to run without a display
""" """
extra = [] extra = []
efi_dir = self.get_setting("efi_dir") efi_dir = self.helper.get_setting('efi_dir')
if self.args.arm: if self.args.arm:
qemu_arch = 'aarch64' qemu_arch = 'aarch64'
extra += ['--machine', 'virt', '-cpu', 'max'] extra += ['--machine', 'virt', '-cpu', 'max']
@@ -174,83 +121,18 @@ class BuildEfi:
cmd += extra cmd += extra
command.run(*cmd) command.run(*cmd)
def setup_files(self, build, build_type): def setup_files(self, build, build_type, dst):
"""Set up files in the staging area """Set up files in the staging area
Args: Args:
build (str): Name of build being packaged, e.g. 'efi-x86_app32' build (str): Name of build being packaged, e.g. 'efi-x86_app32'
build_type (str): Build type ('app' or 'payload') build_type (str): Build type ('app' or 'payload')
dst (str): Destination directory
""" """
print(f'Packaging {build}') print(f'Packaging {build}')
if not os.path.exists(self.tmp):
os.mkdir(self.tmp)
fname = f'u-boot-{build_type}.efi' fname = f'u-boot-{build_type}.efi'
tools.write_file(f'{self.tmp}/startup.nsh', f'fs0:{fname}', tools.write_file(f'{dst}/startup.nsh', f'fs0:{fname}', binary=False)
binary=False) shutil.copy(f'{self.build_dir}/{build}/{fname}', dst)
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)
cmd = 'sudo', 'kpartx', '-d', self.img
command.output(*cmd)
def do_build(self, build): def do_build(self, build):
"""Build U-Boot for the selected board""" """Build U-Boot for the selected board"""
@@ -267,7 +149,6 @@ class BuildEfi:
bitness = 32 if args.word else 64 bitness = 32 if args.word else 64
arch = 'arm' if args.arm else 'x86' arch = 'arm' if args.arm else 'x86'
build_type = 'payload' if args.payload else 'app' build_type = 'payload' if args.payload else 'app'
self.tmp = f'{self.build_dir}/efi{bitness}{build_type}'
build = f'efi-{arch}_{build_type}{bitness}' build = f'efi-{arch}_{build_type}{bitness}'
if not args.no_build: if not args.no_build:
@@ -276,19 +157,17 @@ class BuildEfi:
if args.old and bitness == 32: if args.old and bitness == 32:
build = f'efi-{arch}_{build_type}' build = f'efi-{arch}_{build_type}'
self.setup_files(build, build_type) with self.helper.make_disk(self.img, fs_type='vfat',
use_part=args.partition) as dirpath:
command.output('qemu-img', 'create', self.img, '24M') self.setup_files(build, build_type, dirpath)
if self.args.kernel:
if args.partition: bzimage = self.helper.get_setting('bzimage_file', 'bzImage')
self.setup_part() command.run('cp', bzimage, f'{dirpath}/vmlinuz')
else:
self.setup_raw()
if args.run: if args.run:
self.run_qemu(bitness, args.serial) self.run_qemu(bitness, args.serial)
if __name__ == "__main__": if __name__ == "__main__":
efi = BuildEfi(get_settings(), parse_args()) efi = BuildEfi(parse_args())
efi.start() efi.start()

414
scripts/build-qemu Executable file
View File

@@ -0,0 +1,414 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0+
#
"""Script to build/run U-Boot with QEMU
It assumes that
- you build U-Boot in ${ubdir}/<name> where <name> is the U-Boot
board config
- your OS images are in ${imagedir}/{distroname}/
So far the script supports only ARM and x86
"""
import sys
import os
import argparse
import subprocess
import shlex
import time
from pathlib import Path
from build_helper import Helper
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
from u_boot_pylib import tout
def parse_args():
"""Parses command-line arguments"""
parser = argparse.ArgumentParser(
description='Build and/or run U-Boot with QEMU',
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('-a', '--arch', default='arm', choices=['arm', 'x86'],
help='Select architecture (arm, x86) Default: arm')
parser.add_argument('-B', '--no-build', action='store_true',
help="Don't build; assume a build exists")
parser.add_argument('-d', '--disk',
help='Root disk image file to use with QEMU')
parser.add_argument('-D', '--share-dir', metavar='DIR',
help='Directory to share into the guest via virtiofs')
parser.add_argument('-e', '--sct-run', action='store_true',
help='Run UEFI Self-Certification Test (SCT)')
parser.add_argument('-E', '--use-tianocore', action='store_true',
help='Run Tianocore (OVMF) instead of U-Boot')
parser.add_argument(
'-k', '--kvm', action='store_true',
help='Use KVM (Kernel-based Virtual Machine) for acceleration')
parser.add_argument('-o', '--os', metavar='NAME', choices=['ubuntu'],
help='Run a specified Operating System')
parser.add_argument('-v', '--verbose', action='store_true',
help='Show executed commands')
parser.add_argument('-r', '--run', action='store_true',
help='Run QEMU with the built/specified image')
parser.add_argument(
'-R', '--release', default='24.04.1',
help='Select OS release version (e.g, 24.04) Default: 24.04.1')
parser.add_argument(
'-s', '--serial-only', action='store_true',
help='Use serial console only (no graphical display for QEMU)')
parser.add_argument(
'-S', '--sct-seq',
help='SCT sequence-file to be written into the SCT image if -e')
parser.add_argument('-w', '--word-32bit', action='store_true',
help='Use 32-bit version for the build/architecture')
return parser.parse_args()
class BuildQemu:
"""Build and/or run U-Boot with QEMU based on command line arguments"""
def __init__(self, args):
"""Set up arguments and configure paths"""
self.args = args
self.helper = Helper()
self.helper.read_settings()
self.imagedir = Path(self.helper.get_setting('image_dir', '~/dev'))
self.ubdir = Path(self.helper.get_setting('build_dir', '/tmp/b'))
self.sctdir = Path(self.helper.get_setting('sct_dir', '~/dev/efi/sct'))
self.tiano = Path(self.helper.get_setting('tianocore_dir',
'~/dev/tiano'))
self.mnt = Path(self.helper.get_setting('sct_mnt', '/mnt/sct'))
self.bitness = 32 if args.word_32bit else 64
self.qemu_extra = []
self.mem = '512M' # Default QEMU memory
if args.disk:
self.mem = '4G'
self.qemu_extra.extend(['-smp', '4'])
if args.sct_run:
self.mem = '4G'
self.qemu_extra.extend(['-smp', '4'])
# SCT usually runs headlessly
self.qemu_extra.extend(['-display', 'none'])
# For potential interaction within SCT
self.qemu_extra.extend(['-device', 'qemu-xhci'])
self.qemu_extra.extend(['-device', 'usb-kbd'])
sct_image_path = self.sctdir / 'sct.img'
if not sct_image_path.exists():
tout.fatal(f'Error: SCT image {sct_image_path} not found, '
'required for -e')
self.qemu_extra.extend([
'-drive', f'file={sct_image_path},format=raw,if=none,id=vda',
'-device', 'virtio-blk-pci,drive=vda,bootindex=1'])
# Basic networking for SCT, if needed
self.qemu_extra.extend([
'-device', 'virtio-net-pci,netdev=net0',
'-netdev', 'user,id=net0'])
args.serial_only = True # SCT implies serial output
if args.os:
self.mem = '4G'
self.qemu_extra.extend(['-smp', '4'])
self.kvm_params = []
if args.kvm:
self.kvm_params = ['-enable-kvm', '-cpu', 'host']
bios_override = None
if args.use_tianocore:
bios_override = Path(self.tiano, 'OVMF-pure-efi.x64.fd')
if not bios_override.exists():
tout.fatal(
'Error: Tianocore BIOS specified (-E) but not found at '
f'{bios_override}')
self.seq_fname = Path(args.sct_seq) if args.sct_seq else None
self.img_fname = Path(args.disk) if args.disk else None
# arch-specific setup
if args.arch == 'arm':
self.board = 'qemu_arm'
default_bios = 'u-boot.bin'
self.qemu = 'qemu-system-arm'
self.qemu_extra.extend(['-machine', 'virt'])
if not args.kvm:
self.qemu_extra.extend(['-accel', 'tcg'])
qemu_arch = 'arm'
if self.bitness == 64:
self.board = 'qemu_arm64'
self.qemu = 'qemu-system-aarch64'
self.qemu_extra.extend(['-cpu', 'cortex-a57'])
qemu_arch = 'arm64'
elif args.arch == 'x86':
self.board = 'qemu-x86'
default_bios = 'u-boot.rom'
self.qemu = 'qemu-system-i386'
qemu_arch = 'i386' # For OS image naming
if self.bitness == 64:
self.board = 'qemu-x86_64'
self.qemu = 'qemu-system-x86_64'
qemu_arch = 'amd64'
else:
raise ValueError(f"Invalid arch '{args.arch}'")
self.os_path = None
if args.os == 'ubuntu':
img_name = (f'{args.os}-{args.release}-desktop-{qemu_arch}.iso')
self.os_path = self.imagedir / args.os / img_name
self.build_dir = self.ubdir / self.board
self.bios = (bios_override if bios_override
else self.build_dir / default_bios)
@staticmethod
def execute_command(cmd_list, desc, check=True, **kwargs):
"""Execute a shell command and handle errors
Args:
cmd_list (list of str): The command and its arguments as a list
desc (str): A description of the command being executed
check (bool): Raise CalledProcessError on non-zero exit code
kwargs: Additional arguments for subprocess.run
Return:
subprocess.CompletedProcess: The result of the subprocess.run call
Raises:
SystemExit: If the command is not found or fails and check is True
"""
tout.info(f"Executing: {desc} -> {shlex.join(cmd_list)}")
try:
# Decode stdout/stderr by default if text=True
if 'text' not in kwargs:
kwargs['text'] = True
return subprocess.run(cmd_list, check=check, **kwargs)
except FileNotFoundError:
tout.fatal(f"Error: Command '{cmd_list[0]}' not found")
except subprocess.CalledProcessError as proc:
tout.error(f'Error {desc}: Command failed with exit code '
f'{proc.returncode}')
if proc.stdout:
tout.error(f'Stdout:\n{proc.stdout}')
if proc.stderr:
tout.error(f'Stderr:\n{proc.stderr}')
tout.fatal('Failed')
def build_u_boot(self):
"""Build U-Boot using buildman
"""
self.build_dir.mkdir(parents=True, exist_ok=True)
cmd = ['buildman', '-w', '-o', str(self.build_dir), '--board',
self.board, '-I']
self.execute_command(
cmd,
f'Building U-Boot for {self.board} in {self.build_dir}')
def update_sct_sequence(self):
"""Update the SCT image with a specified sequence file
Requires sudo for loop device setup and mounting
"""
if not (self.args.sct_run and self.seq_fname and
self.seq_fname.exists()):
if (self.args.sct_run and self.seq_fname and
not self.seq_fname.exists()):
tout.warning(f'Warning: SCT sequence file {self.seq_fname}'
'not found')
return
fname = self.sctdir / 'sct.img'
if not fname.exists():
tout.fatal(f'Error: SCT image {fname} not found')
loopdev = None
try:
# Find free loop device and attach
loopdev = command.output_one_line(
'sudo', 'losetup', '--show', '-f', '-P', str(fname))
partition_path_str = f'{loopdev}p1'
uid, gid = os.getuid(), os.getgid()
mount_cmd = ['sudo', 'mount', partition_path_str,
str(self.mnt), '-o', f'uid={uid},gid={gid},rw']
mount_cmd.extend(['-t', 'vfat'])
self.execute_command(mount_cmd,
f'Mounting {partition_path_str} to {self.mnt}')
target_sct_path = self.mnt / self.seq_fname.name
self.execute_command(
['sudo', 'cp', str(self.seq_fname), str(target_sct_path)],
f'Copying {self.seq_fname.name} to {self.mnt}'
)
tout.info(f"Copied {self.seq_fname} to {target_sct_path}")
finally:
if Path(self.mnt).is_mount():
self.execute_command(['sudo', 'umount', str(self.mnt)],
f'Unmounting {self.mnt}', check=False)
if loopdev:
self.execute_command(['sudo', 'losetup', '-d', loopdev],
f'Detaching loop device {loopdev}',
check=False)
def run_qemu(self):
"""Construct and run the QEMU command"""
if not self.bios.exists():
tout.fatal(f"Error: BIOS file '{self.bios}' not found")
qemu_cmd = [str(self.qemu)]
if self.bios:
qemu_cmd.extend(['-bios', str(self.bios)])
qemu_cmd.extend(self.kvm_params)
qemu_cmd.extend(['-m', self.mem])
if not self.args.sct_run:
qemu_cmd.extend(['-netdev', 'user,id=net0,hostfwd=tcp::2222-:22',
'-device', 'virtio-net-pci,netdev=net0'])
# Display and Serial
# If -e (sct_run) is used, "-display none" is in qemu_extra
# If -s (serial_only) is used, we want no display
has_display_option = any(
item.startswith('-display') for item in self.qemu_extra)
if self.args.serial_only and not has_display_option:
qemu_cmd.extend(['-display', 'none'])
if not any(item.startswith('-serial') for item in self.qemu_extra):
qemu_cmd.extend(['-serial', 'mon:stdio'])
# Add other parameters gathered from options
qemu_cmd.extend(self.qemu_extra)
if self.os_path:
if not self.os_path.exists():
tout.error(f'OS image {self.os_path} specified but not found')
qemu_cmd.extend([
'-drive',
f'if=virtio,file={self.os_path},format=raw,id=hd0,readonly=on'])
if self.img_fname:
if self.img_fname.exists():
qemu_cmd.extend([
'-drive',
f'if=virtio,file={self.img_fname},format=raw,id=hd1'])
else:
tout.warning(f"Disk image '{self.img_fname}' not found")
sock = Path('/tmp/virtiofs.sock')
if self.args.share_dir:
virtfs_dir = Path(self.args.share_dir)
if not virtfs_dir.is_dir():
tout.fatal(f'Error: VirtFS share directory {virtfs_dir} '
f'is not a valid directory')
virtiofsd = Path('/usr/libexec/virtiofsd')
if not virtiofsd.exists():
tout.fatal(f'Error: virtiofsd not found at {virtiofsd}')
# Clean up potential old socket file
if sock.exists():
try:
sock.unlink()
tout.info(f'Removed old socket file {sock}')
except OSError as e:
tout.warning(
f'Warning: Could not remove old socket file {sock}: '
f'{e}')
qemu_cmd.extend([
'-chardev', f'socket,id=char0,path={sock}',
'-device',
'vhost-user-fs-pci,queue-size=1024,chardev=char0,tag=hostshare',
'-object',
f'memory-backend-file,id=mem,size={self.mem},mem-path=/dev/shm'
',share=on',
'-numa', 'node,memdev=mem'])
virtiofsd_cmd = [
str(virtiofsd),
'--socket-path', str(sock),
'--shared-dir', str(virtfs_dir),
'--cache', 'auto']
try:
# Use Popen to run virtiofsd in the background
proc = subprocess.Popen(virtiofsd_cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
# Give virtiofsd a moment to start and create the socket
time.sleep(0.5)
if not sock.exists() and proc.poll() is not None:
stdout, stderr = proc.communicate()
tout.error('Error starting virtiofsd. Exit code: '
f'{proc.returncode}')
if stdout:
tout.error(f"virtiofsd stdout:\n{stdout.decode()}")
if stderr:
tout.error(f"virtiofsd stderr:\n{stderr.decode()}")
tout.fatal('Failed')
except (subprocess.CalledProcessError, FileNotFoundError) as exc:
tout.fatal(f'Failed to start virtiofsd: {exc}')
tout.info(f'QEMU:\n{shlex.join(qemu_cmd)}\n')
try:
subprocess.run(qemu_cmd, check=True)
except FileNotFoundError:
tout.fatal(f"Error: QEMU executable '{self.qemu}' not found")
except subprocess.CalledProcessError as e:
tout.fatal(f'QEMU execution failed with exit code {e.returncode}')
finally:
# Clean up virtiofsd process and socket if it was started
if proc:
tout.info('Terminating virtiofsd')
proc.terminate()
try:
proc.wait(timeout=5)
except subprocess.TimeoutExpired:
tout.warning(
'virtiofsd did not terminate gracefully; killing')
proc.kill()
if sock.exists():
try:
sock.unlink()
except OSError as e_os:
tout.warning('Warning: Could not remove virtiofs '
f'socket {sock}: {e_os}')
def start(self):
"""Build and run QEMU"""
if not self.args.no_build and not self.args.use_tianocore:
self.build_u_boot()
# Update SCT sequence if -e and -S are given
if self.args.sct_run and self.seq_fname:
self.update_sct_sequence()
if self.args.run:
self.run_qemu()
def main():
"""Parses arguments and initiates the BuildQemu process
"""
args = parse_args()
tout.init(tout.INFO if args.verbose else tout.WARNING)
qemu = BuildQemu(args)
qemu.start()
if __name__ == '__main__':
main()

View File

@@ -1,211 +0,0 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0+
#
# Script to build U-Boot suitable for booting with QEMU, possibly running
# it, possibly with an OS image
# This just an example. It assumes that
# - you build U-Boot in ${ubdir}/<name> where <name> is the U-Boot board config
# - your OS images are in ${imagedir}/{distroname}/...
# So far the script supports only ARM and x86.
set -e
usage() {
(
if [[ -n "$1" ]]; then
echo "$1"
echo
fi
echo "Usage: $0 -aBkrsw"
echo
echo " -a <arch> - Select architecture (arm, x86)"
echo " -B - Don't build; assume a build exists"
echo " -e - Run UEFI Self-Certification Test (SCT)"
echo " -k - Use kvm (kernel-based Virtual Machine)"
echo " -o <name> - Run Operating System ('ubuntu' only for now)"
echo " -r - Run QEMU with the image"
echo " -R <os> - Select OS release (e.g. 24.04)"
echo " -s - Use serial only (no display)"
echo " -S <seq> - Select SCT sequence-file"
echo " -w - Use word version (32-bit)" ) >&2
exit 1
}
# Directory tree for OS images
imagedir=${imagedir-/vid/software/linux}
# Directory for UEFI Self-Certification Test (SCT)
sctdir=${sctdir-/vid/software/devel/uefi/sct}
# Mount point for use when writing to disk images
mnt=${mnt-/mnt}
# architecture (arm or x86)
arch=arm
# 32- or 64-bit build
bitness=64
# Build U-Boot
build=yes
# Extra setings
extra=
# Operating System to boot (ubuntu)
os=
release=24.04.1
# run the image with QEMU
run=
# run QEMU without a display (U-Boot must be set to stdout=serial)
serial=
# Use kvm
kvm=
# 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=${ubdir-/tmp/b}
while getopts "a:Beko:rR:sS:w" opt; do
case "${opt}" in
a)
arch=$OPTARG
;;
B)
build=
;;
e)
extra+=" -m 4G -smp 4"
extra+=" -display none"
extra+=" -device virtio-gpu-pci"
extra+=" -device qemu-xhci"
extra+=" -device usb-kbd"
extra+=" -drive file=${sctdir}/sct.img,format=raw,if=none,id=vda"
extra+=" -device virtio-blk-device,drive=vda,bootindex=1"
extra+=" -device virtio-net-device,netdev=net0"
extra+=" -netdev user,id=net0"
;;
k)
kvm="-enable-kvm"
;;
o)
os=$OPTARG
# Expand memory and CPUs
extra+=" -m 4G -smp 4"
;;
r)
run=1
;;
R)
release=$OPTARG
;;
s)
serial=1
;;
S)
seq=$OPTARG
;;
w)
bitness=32
;;
*)
usage
;;
esac
done
# Build U-Boot for the selected board
build_u_boot() {
buildman -w -o $DIR --board $BOARD -I || exit $?
}
# Write the SCT test-sequence file into the SCT image
update_sct_seq() {
if [[ -z "${seq}" ]]; then
return
fi
LOOP=$(sudo losetup --show -f -P "${sctdir}/sct.img")
PART="${LOOP}p1"
sudo mount -o loop ${PART} ${mnt} -o uid=$(id -u),gid=$(id -g)
cp "${seq}" "${mnt}/."
sudo umount ${mnt}
sudo losetup -d ${LOOP}
}
# Run QEMU with U-Boot
run_qemu() {
if [[ -n "${os_image}" ]]; then
extra+=" -drive if=virtio,file=${os_image},format=raw,id=hd0"
fi
if [[ -n "${serial}" ]]; then
extra+=" -display none -serial mon:stdio"
else
extra+=" -serial mon:stdio"
fi
echo "Running ${qemu} ${extra}"
"${qemu}" -bios "$DIR/${BIOS}" \
-m 512 \
-nic none \
${kvm} \
${extra}
}
# Check architecture
case "${arch}" in
arm)
BOARD="qemu_arm"
BIOS="u-boot.bin"
qemu=qemu-system-arm
extra+=" -machine virt -accel tcg"
suffix="arm"
if [[ "${bitness}" == "64" ]]; then
BOARD="qemu_arm64"
qemu=qemu-system-aarch64
extra+=" -cpu cortex-a57"
suffix="arm64"
fi
;;
x86)
BOARD="qemu-x86"
BIOS="u-boot.rom"
qemu=qemu-system-i386
suffix="i386"
if [[ "${bitness}" == "64" ]]; then
BOARD="qemu-x86_64"
qemu=qemu-system-x86_64
suffix="amd64"
fi
;;
*)
usage "Unknown architecture '${arch}'"
esac
# Check OS
case "${os}" in
ubuntu)
os_image="${imagedir}/${os}/${os}-${release}-desktop-${suffix}.iso"
;;
"")
;;
*)
usage "Unknown OS '${os}'"
esac
DIR=${ubdir}/${BOARD}
if [[ -n "${build}" ]]; then
build_u_boot
update_sct_seq
fi
if [[ -n "${run}" ]]; then
run_qemu
fi

126
scripts/build_helper.py Normal file
View File

@@ -0,0 +1,126 @@
# SPDX-License-Identifier: GPL-2.0+
#
"""Common script for build- scripts
"""
import configparser
import contextlib
import os
import shutil
import subprocess
import sys
import tempfile
OUR_PATH = os.path.dirname(os.path.realpath(__file__))
OUR1_PATH = os.path.dirname(OUR_PATH)
# Bring in the patman and test libraries (but don't override the first path in
# PYTHONPATH)
sys.path.insert(2, os.path.join(OUR1_PATH, 'tools'))
sys.path.insert(2, os.path.join(OUR1_PATH, 'test/py/tests'))
from u_boot_pylib import command
from u_boot_pylib import tools
import fs_helper
class Helper:
def __init__(self):
self.settings = None
def read_settings(self):
"""Get settings from the settings file"""
self.settings = configparser.ConfigParser()
fname = f'{os.getenv("HOME")}/.u_boot_qemu'
if not os.path.exists(fname):
print('No config file found: {fname}\nCreating one...\n')
tools.write_file(fname, '''# U-Boot QEMU-scripts config
[DEFAULT]
# 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
# Each board gets a build in a separate subdir
build_dir = /tmp/b
# Image directory (for OS images)
image_dir = ~/dev/os
# Build the kernel with: make O=/tmp/kernel
bzimage = /tmp/kernel/arch/x86/boot/bzImage
# EFI image-output filename
efi_image_file = try.img
# Directory where OVMF-pure-efi.i386.fd etc. are kept
efi_dir = ~/dev/efi
# Directory where SCT image (sct.img) is kept
sct_dir = ~/dev/efi/sct
# Directory where the SCT image is temporarily mounted for modification
sct_mnt = /mnt/sct
''', binary=False)
self.settings.read(fname)
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
"""
raw = self.settings.get('DEFAULT', name, fallback=fallback)
return os.path.expandvars(os.path.expanduser(raw))
def stage(self, name):
"""Context manager to count requests across a range of patchwork calls
Args:
name (str): Stage name
Return:
_Stage: contect object
Usage:
with self.stage('name'):
...do things
Note that the output only appears if the -N flag is used
"""
return self._Stage(name)
@contextlib.contextmanager
def make_disk(self, fname, size_mb=20, fs_type='ext4', use_part=False):
"""Create a raw disk image with files on it
Args:
fname (str): Filename to write the images to
fs_type (str): Filesystem type to create (ext4 or vfat)
size_mb (int): Size in MiB
use_part (bool): True to create a partition table, False to use a
raw disk image
Yields:
str: Directory to write the files into
"""
with tempfile.NamedTemporaryFile() as tmp:
with tempfile.TemporaryDirectory(prefix='build_helper.') as dirname:
try:
yield dirname
fs_helper.mk_fs(None, fs_type, size_mb << 20, None, dirname,
fs_img=tmp.name, quiet=True)
finally:
pass
if use_part:
with open(fname, 'wb') as img:
img.truncate(size_mb << 20)
img.seek(1 << 20, 0)
img.write(tools.read_file(tmp.name))
subprocess.run(
['sfdisk', fname], text=True, check=True,
capture_output=True,
input=f'type=c, size={size_mb-1}M, start=1M,bootable')
else:
shutil.copy2(tmp.name, fname)

View File

@@ -8,8 +8,10 @@
import re import re
import os import os
from subprocess import call, check_call, check_output, CalledProcessError from subprocess import call, check_call, check_output, CalledProcessError
from subprocess import DEVNULL
def mk_fs(config, fs_type, size, prefix, src_dir=None, size_gran = 0x100000): def mk_fs(config, fs_type, size, prefix, src_dir=None, size_gran = 0x100000,
fs_img=None, quiet=False):
"""Create a file system volume """Create a file system volume
Args: Args:
@@ -19,12 +21,15 @@ def mk_fs(config, fs_type, size, prefix, src_dir=None, size_gran = 0x100000):
prefix (str): Prefix string of volume's file name prefix (str): Prefix string of volume's file name
src_dir (str): Root directory to use, or None for none src_dir (str): Root directory to use, or None for none
size_gran (int): Size granularity of file system image in bytes size_gran (int): Size granularity of file system image in bytes
fs_img (str or None): Filename for image, or None to invent one
quiet (bool): Suppress non-error output
Raises: Raises:
CalledProcessError: if any error occurs when creating the filesystem CalledProcessError: if any error occurs when creating the filesystem
""" """
fs_img = f'{prefix}.{fs_type}.img' if not fs_img:
fs_img = os.path.join(config.persistent_data_dir, fs_img) leaf = f'{prefix}.{fs_type}.img'
fs_img = os.path.join(config.persistent_data_dir, leaf)
if fs_type == 'fat12': if fs_type == 'fat12':
mkfs_opt = '-F 12' mkfs_opt = '-F 12'
@@ -58,14 +63,17 @@ def mk_fs(config, fs_type, size, prefix, src_dir=None, size_gran = 0x100000):
check_call(f'rm -f {fs_img}', shell=True) check_call(f'rm -f {fs_img}', shell=True)
check_call(f'truncate -s $(( {size_gran} * {count} )) {fs_img}', check_call(f'truncate -s $(( {size_gran} * {count} )) {fs_img}',
shell=True) shell=True)
check_call(f'mkfs.{fs_lnxtype} {mkfs_opt} {fs_img}', shell=True) check_call(f'mkfs.{fs_lnxtype} {mkfs_opt} {fs_img}', shell=True,
stdout=DEVNULL if quiet else None)
if fs_type == 'ext4': if fs_type == 'ext4':
sb_content = check_output(f'tune2fs -l {fs_img}', sb_content = check_output(f'tune2fs -l {fs_img}',
shell=True).decode() shell=True).decode()
if 'metadata_csum' in sb_content: if 'metadata_csum' in sb_content:
check_call(f'tune2fs -O ^metadata_csum {fs_img}', shell=True) check_call(f'tune2fs -O ^metadata_csum {fs_img}', shell=True)
elif fs_lnxtype == 'vfat' and src_dir: elif fs_lnxtype == 'vfat' and src_dir:
check_call(f'mcopy -i {fs_img} -vsmpQ {src_dir}/* ::/', shell=True) flags = f"-smpQ{'' if quiet else 'v'}"
check_call(f'mcopy -i {fs_img} {flags} {src_dir}/* ::/',
shell=True)
elif fs_lnxtype == 'exfat' and src_dir: elif fs_lnxtype == 'exfat' and src_dir:
check_call(f'fattools cp {src_dir}/* {fs_img}', shell=True) check_call(f'fattools cp {src_dir}/* {fs_img}', shell=True)
return fs_img return fs_img

View File

@@ -777,7 +777,7 @@ def Binman(args):
if args.cmd in ['ls', 'extract', 'replace', 'tool', 'sign']: if args.cmd in ['ls', 'extract', 'replace', 'tool', 'sign']:
try: try:
tout.init(args.verbosity) tout.init(args.verbosity + 1)
if args.cmd == 'replace': if args.cmd == 'replace':
tools.prepare_output_dir(args.outdir, args.preserve) tools.prepare_output_dir(args.outdir, args.preserve)
else: else:
@@ -835,9 +835,9 @@ def Binman(args):
args.indir.append(board_pathname) args.indir.append(board_pathname)
try: try:
tout.init(args.verbosity) tout.init(args.verbosity + 1)
elf.debug = args.debug elf.debug = args.debug
cbfs_util.VERBOSE = args.verbosity > 2 cbfs_util.VERBOSE = args.verbosity > tout.NOTICE
state.use_fake_dtb = args.fake_dtb state.use_fake_dtb = args.fake_dtb
# Normally we replace the 'u-boot' etype with 'u-boot-expanded', etc. # Normally we replace the 'u-boot' etype with 'u-boot-expanded', etc.

View File

@@ -9,7 +9,7 @@ import sys
from u_boot_pylib import terminal from u_boot_pylib import terminal
# Output verbosity levels that we support # Output verbosity levels that we support
ERROR, WARNING, NOTICE, INFO, DETAIL, DEBUG = range(6) FATAL, ERROR, WARNING, NOTICE, INFO, DETAIL, DEBUG = range(7)
in_progress = False in_progress = False
@@ -42,12 +42,12 @@ def user_is_present():
Returns: Returns:
True if it thinks the user is there, and False otherwise True if it thinks the user is there, and False otherwise
""" """
return stdout_is_tty and verbose > 0 return stdout_is_tty and verbose > ERROR
def clear_progress(): def clear_progress():
"""Clear any active progress message on the terminal.""" """Clear any active progress message on the terminal."""
global in_progress global in_progress
if verbose > 0 and stdout_is_tty and in_progress: if verbose > ERROR and stdout_is_tty and in_progress:
_stdout.write('\r%s\r' % (" " * len (_progress))) _stdout.write('\r%s\r' % (" " * len (_progress)))
_stdout.flush() _stdout.flush()
in_progress = False in_progress = False
@@ -60,7 +60,7 @@ def progress(msg, warning=False, trailer='...'):
warning: True if this is a warning.""" warning: True if this is a warning."""
global in_progress global in_progress
clear_progress() clear_progress()
if verbose > 0: if verbose > ERROR:
_progress = msg + trailer _progress = msg + trailer
if stdout_is_tty: if stdout_is_tty:
col = _color.YELLOW if warning else _color.GREEN col = _color.YELLOW if warning else _color.GREEN
@@ -87,6 +87,8 @@ def _output(level, msg, color=None):
print(msg, file=sys.stderr) print(msg, file=sys.stderr)
else: else:
print(msg) print(msg)
if level == FATAL:
sys.exit(1)
def do_output(level, msg): def do_output(level, msg):
"""Output a message to the terminal. """Output a message to the terminal.
@@ -98,6 +100,14 @@ def do_output(level, msg):
""" """
_output(level, msg) _output(level, msg)
def fatal(msg):
"""Display an error message and exit
Args:
msg; Message to display.
"""
_output(FATAL, msg, _color.RED)
def error(msg): def error(msg):
"""Display an error message """Display an error message
@@ -153,13 +163,13 @@ def user_output(msg):
Args: Args:
msg; Message to display. msg; Message to display.
""" """
_output(0, msg) _output(ERROR, msg)
def init(_verbose=WARNING, stdout=sys.stdout, allow_colour=True): def init(_verbose=WARNING, stdout=sys.stdout, allow_colour=True):
"""Initialize a new output object. """Initialize a new output object.
Args: Args:
verbose: Verbosity level (0-4). verbose: Verbosity level (0-6).
stdout: File to use for stdout. stdout: File to use for stdout.
""" """
global verbose, _progress, _color, _stdout, stdout_is_tty global verbose, _progress, _color, _stdout, stdout_is_tty