[PATCH v5 01/25] RFC: scripts: Add scripts for running QEMU

Simon Glass sjg at chromium.org
Wed May 28 14:32:03 CEST 2025


These is present in my tree. Provide it as a patch to simplify testing
this series with QEMU.

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

(no changes since v1)

 scripts/build-efi       | 173 +++++++++++++++++
 scripts/build-qemu      | 415 ++++++++++++++++++++++++++++++++++++++++
 scripts/build_helper.py | 126 ++++++++++++
 3 files changed, 714 insertions(+)
 create mode 100755 scripts/build-efi
 create mode 100755 scripts/build-qemu
 create mode 100644 scripts/build_helper.py

diff --git a/scripts/build-efi b/scripts/build-efi
new file mode 100755
index 00000000000..2f4b916c49b
--- /dev/null
+++ b/scripts/build-efi
@@ -0,0 +1,173 @@
+#!/usr/bin/env python3
+# 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 os
+import shutil
+
+from build_helper import Helper
+
+# 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('-A', '--arm', action='store_true',
+                        help='Run on ARM architecture')
+    parser.add_argument('-B', '--no-build', action='store_true',
+                        help="Don't build (an existing build must be present")
+    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
+
+
+class BuildEfi:
+    """Class to collect together the various bits of state while running"""
+    def __init__(self, args):
+        self.helper = Helper()
+        self.helper.read_settings()
+        self.img = self.helper.get_setting('efi_image_file', 'efi.img')
+        self.build_dir = self.helper.get_setting("build_dir", '/tmp')
+        self.args = args
+
+    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.helper.get_setting('efi_dir')
+        if self.args.arm:
+            qemu_arch = 'aarch64'
+            extra += ['--machine', 'virt', '-cpu', 'max']
+            bios = os.path.join(efi_dir, 'OVMF-pure-efi.aarch64.fd.64m')
+            var_store = os.path.join(efi_dir, 'varstore.img')
+            extra += [
+                '-drive', f'if=pflash,format=raw,file={bios},readonly=on',
+                '-drive', f'if=pflash,format=raw,file={var_store}'
+                ]
+            extra += ['-drive',
+                      f'id=hd0,file={self.img},if=none,format=raw',
+                      '-device', 'virtio-blk-device,drive=hd0']
+        else:  # x86
+            if bitness == 64:
+                qemu_arch = 'x86_64'
+                bios = 'OVMF-pure-efi.x64.fd'
+            else:
+                qemu_arch = 'i386'
+                bios = 'OVMF-pure-efi.i386.fd'
+            extra += ['-bios', os.path.join(efi_dir, bios)]
+            extra += ['-drive', f'id=disk,file={self.img},if=none,format=raw']
+            extra += ['-device', 'ahci,id=ahci']
+            extra += ['-device', 'ide-hd,drive=disk,bus=ahci.0']
+        qemu = f'qemu-system-{qemu_arch}'
+        if serial_only:
+            extra += ['-display', 'none', '-serial', 'mon:stdio']
+            serial_msg = ' (Ctrl-a x to quit)'
+        else:
+            if self.args.arm:
+                extra += ['-device', 'virtio-gpu-pci']
+            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]
+        cmd += '-m', '512'
+        cmd += '-nic', 'none'
+        cmd += extra
+        command.run(*cmd)
+
+    def setup_files(self, build, build_type, dst):
+        """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')
+            dst (str): Destination directory
+        """
+        print(f'Packaging {build}')
+        fname = f'u-boot-{build_type}.efi'
+        tools.write_file(f'{dst}/startup.nsh', f'fs0:{fname}', binary=False)
+        shutil.copy(f'{self.build_dir}/{build}/{fname}', dst)
+
+    def do_build(self, build):
+        """Build U-Boot for the selected board"""
+        res = command.run_one('buildman', '-w', '-o',
+                              f'{self.build_dir}/{build}', '--board', build,
+                              '-I', raise_on_error=False)
+        if res.return_code and res.return_code != 101:  # Allow warnings
+            raise ValueError(
+                f'buildman exited with {res.return_code}: {res.combined}')
+
+    def start(self):
+        """This does all the work"""
+        args = self.args
+        bitness = 32 if args.word else 64
+        arch = 'arm' if args.arm else 'x86'
+        build_type = 'payload' if args.payload else 'app'
+        build = f'efi-{arch}_{build_type}{bitness}'
+
+        if not args.no_build:
+            self.do_build(build)
+
+        if args.old and bitness == 32:
+            build = f'efi-{arch}_{build_type}'
+
+        with self.helper.make_disk(self.img, fs_type='vfat',
+                                   use_part=args.partition) as dirpath:
+            self.setup_files(build, build_type, dirpath)
+            if self.args.kernel:
+                bzimage = self.helper.get_setting('bzimage_file', 'bzImage')
+                command.run('cp', bzimage, f'{dirpath}/vmlinuz')
+
+        if args.run:
+            self.run_qemu(bitness, args.serial)
+
+
+if __name__ == "__main__":
+    efi = BuildEfi(parse_args())
+    efi.start()
diff --git a/scripts/build-qemu b/scripts/build-qemu
new file mode 100755
index 00000000000..5798da3775c
--- /dev/null
+++ b/scripts/build-qemu
@@ -0,0 +1,415 @@
+#!/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')
+        proc = None
+        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()
diff --git a/scripts/build_helper.py b/scripts/build_helper.py
new file mode 100644
index 00000000000..a8385ed1545
--- /dev/null
+++ b/scripts/build_helper.py
@@ -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)
-- 
2.43.0



More information about the U-Boot mailing list