[PATCH 0/7] efi: Minor improvements to QEMU and build scripts
From: Simon Glass <sjg@chromium.org> This series introduces a few improvements: - passing a boot command to the EFI app - FPDT support for EFI app Simon Glass (7): acpi: qfw: Add FPDT support for QEMU builds qfw: Silence the message when opt/u-boot/bootcmd is not found doc: qemu-x86: Document opt/u-boot/bootcmd feature scripts: Support --bootcmd more generally scripts: build-efi: Write uboot.env file for bootcmd support efi-x86_app64: Use bootflow scan for boot command efi-x86_app64: Enable environment in FAT filesystem configs/efi-x86_app64_defconfig | 5 +- doc/board/emulation/qemu-x86.rst | 20 +++++++ drivers/qfw/qfw.c | 26 ++++++--- drivers/qfw/qfw_acpi.c | 10 ++++ include/qfw.h | 20 ++++++- lib/acpi/acpi_extra.c | 92 ++++++++++++++++++++++++++++++++ lib/acpi/acpi_table.c | 90 ------------------------------- scripts/build-efi | 34 ++++++++++++ scripts/build-qemu | 7 --- scripts/build_helper.py | 7 +++ 10 files changed, 204 insertions(+), 107 deletions(-) -- 2.43.0 base-commit: 1002f1fc35605c58cfacd955ac4780bf046d4109 branch: secd
From: Simon Glass <sjg@chromium.org> QEMU creates ACPI tables but doesn't include FPDT (Firmware Performance Data Table). Add FPDT generation in qfw_acpi.c following the same pattern as BGRT. Move the acpi_write_fpdt() function from acpi_table.c to acpi_extra.c so that is available even when CONFIG_ACPIGEN is disabled. This allows QEMU x86_64 builds to provide firmware boot timing information to the operating system. Disable this for qemu-riscv64_smode_acpi as it is near the code-size limit. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/qfw/qfw_acpi.c | 10 +++++ lib/acpi/acpi_extra.c | 92 ++++++++++++++++++++++++++++++++++++++++++ lib/acpi/acpi_table.c | 90 ----------------------------------------- 3 files changed, 102 insertions(+), 90 deletions(-) diff --git a/drivers/qfw/qfw_acpi.c b/drivers/qfw/qfw_acpi.c index 916a59be7fb..7f7dba73155 100644 --- a/drivers/qfw/qfw_acpi.c +++ b/drivers/qfw/qfw_acpi.c @@ -8,6 +8,7 @@ #include <abuf.h> #include <bloblist.h> +#include <bootstage.h> #include <errno.h> #include <malloc.h> #include <mapmem.h> @@ -256,6 +257,15 @@ out: return addr; } + if (!IS_ENABLED(CONFIG_TARGET_QEMU_VIRT)) { + ret = acpi_write_fpdt(ctx, + bootstage_get_time(BOOTSTAGE_ID_START_UBOOT_F)); + if (ret) { + printf("error: failed to write FPDT (err=%dE)\n", ret); + return addr; + } + } + return addr; } diff --git a/lib/acpi/acpi_extra.c b/lib/acpi/acpi_extra.c index 709f64305b5..09b8a251ca2 100644 --- a/lib/acpi/acpi_extra.c +++ b/lib/acpi/acpi_extra.c @@ -7,9 +7,11 @@ #define LOG_CATEGORY LOGC_ACPI +#include <bootstage.h> #include <dm.h> #include <efi_loader.h> #include <mapmem.h> +#include <tables_csum.h> #include <video.h> #include <acpi/acpi_table.h> @@ -87,3 +89,93 @@ int acpi_write_bgrt(struct acpi_ctx *ctx) return 0; } + +int acpi_write_fpdt(struct acpi_ctx *ctx, u64 uboot_start) +{ + struct acpi_fpdt *fpdt; + struct acpi_fpdt_boot *rec; + struct acpi_table_header *header; + u64 current_time; + int size; + + fpdt = ctx->current; + header = &fpdt->header; + + /* Calculate total size: FPDT header + boot performance record */ + size = sizeof(struct acpi_fpdt) + sizeof(struct acpi_fpdt_boot); + + memset(fpdt, '\0', size); + + /* Fill out FPDT header */ + acpi_fill_header(header, "FPDT"); + header->length = size; + header->revision = 1; /* ACPI 6.4+: 1 */ + + /* Add boot performance record right after FPDT header */ + rec = (struct acpi_fpdt_boot *)(fpdt + 1); + + /* Fill in record header */ + rec->hdr.type = FPDT_REC_BOOT; + rec->hdr.length = sizeof(struct acpi_fpdt_boot); + rec->hdr.revision = 2; /* FPDT Boot Performance Record revision */ + + /* Fill in timing data */ + current_time = timer_get_boot_us(); + rec->reset_end = uboot_start; + rec->loader_start = current_time; + rec->loader_exec = current_time; + rec->ebs_entry = current_time; + rec->ebs_exit = current_time; + + header->checksum = table_compute_checksum(fpdt, header->length); + + acpi_inc_align(ctx, size); + acpi_add_table(ctx, fpdt); + + return 0; +} + +struct acpi_fpdt_boot *acpi_get_fpdt_boot(void) +{ + struct acpi_table_header *header; + struct acpi_fpdt *fpdt; + + header = acpi_find_table("FPDT"); + if (!header) + return NULL; + + fpdt = (struct acpi_fpdt *)header; + return (struct acpi_fpdt_boot *)(fpdt + 1); +} + +int acpi_fix_fpdt_checksum(void) +{ + struct acpi_table_header *header; + + header = acpi_find_table("FPDT"); + if (!header) + return -ENOENT; + + header->checksum = 0; + header->checksum = table_compute_checksum(header, header->length); + + return 0; +} + +void acpi_final_fpdt(void) +{ + struct acpi_fpdt_boot *fpdt; + + if (IS_ENABLED(CONFIG_TARGET_QEMU_VIRT)) + return; + + fpdt = acpi_get_fpdt_boot(); + if (fpdt) { + u64 time; + + time = timer_get_boot_us(); + fpdt->ebs_entry = time; + fpdt->ebs_exit = time; + acpi_fix_fpdt_checksum(); + } +} diff --git a/lib/acpi/acpi_table.c b/lib/acpi/acpi_table.c index 99251176d09..94e6647b666 100644 --- a/lib/acpi/acpi_table.c +++ b/lib/acpi/acpi_table.c @@ -715,96 +715,6 @@ static int acpi_create_bgrt(struct acpi_ctx *ctx, ACPI_WRITER(6bgrt, "BGRT", acpi_create_bgrt, 0); #endif -int acpi_write_fpdt(struct acpi_ctx *ctx, u64 uboot_start) -{ - struct acpi_fpdt *fpdt; - struct acpi_fpdt_boot *rec; - struct acpi_table_header *header; - u64 current_time; - int size; - - fpdt = ctx->current; - header = &fpdt->header; - - /* Calculate total size: FPDT header + boot performance record */ - size = sizeof(struct acpi_fpdt) + sizeof(struct acpi_fpdt_boot); - - memset(fpdt, '\0', size); - - /* Fill out FPDT header */ - acpi_fill_header(header, "FPDT"); - header->length = size; - header->revision = acpi_get_table_revision(ACPITAB_FPDT); - - /* Add boot performance record right after FPDT header */ - rec = (struct acpi_fpdt_boot *)(fpdt + 1); - - /* Fill in record header */ - rec->hdr.type = FPDT_REC_BOOT; - rec->hdr.length = sizeof(struct acpi_fpdt_boot); - rec->hdr.revision = 2; /* FPDT Boot Performance Record revision */ - - /* Fill in timing data */ - current_time = timer_get_boot_us(); - rec->reset_end = uboot_start; - rec->loader_start = current_time; - rec->loader_exec = current_time; - rec->ebs_entry = current_time; - rec->ebs_exit = current_time; - - header->checksum = table_compute_checksum(fpdt, header->length); - - acpi_inc_align(ctx, size); - acpi_add_table(ctx, fpdt); - - return 0; -} - -struct acpi_fpdt_boot *acpi_get_fpdt_boot(void) -{ - struct acpi_table_header *header; - struct acpi_fpdt *fpdt; - - header = acpi_find_table("FPDT"); - if (!header) - return NULL; - - fpdt = (struct acpi_fpdt *)header; - return (struct acpi_fpdt_boot *)(fpdt + 1); -} - -int acpi_fix_fpdt_checksum(void) -{ - struct acpi_table_header *header; - - header = acpi_find_table("FPDT"); - if (!header) - return -ENOENT; - - header->checksum = 0; - header->checksum = table_compute_checksum(header, header->length); - - return 0; -} - -void acpi_final_fpdt(void) -{ - struct acpi_fpdt_boot *fpdt; - - if (IS_ENABLED(CONFIG_TARGET_QEMU_VIRT)) - return; - - fpdt = acpi_get_fpdt_boot(); - if (fpdt) { - u64 time; - - time = timer_get_boot_us(); - fpdt->ebs_entry = time; - fpdt->ebs_exit = time; - acpi_fix_fpdt_checksum(); - } -} - /* this board lacks the bootstage timer */ #ifndef CONFIG_TARGET_QEMU_VIRT -- 2.43.0
From: Simon Glass <sjg@chromium.org> Currently qfw_locate_file() always prints error messages when it can't find a file. This causes unwanted error output in qemu_get_bootcmd() when the optional "opt/u-boot/bootcmd" file doesn't exist. Adjust qfw_locate_file() to be silent, with a new qfw_locate_file_msg() that shows messages. This allows callers to choose whether missing files should generate error messages. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/qfw/qfw.c | 26 ++++++++++++++++++-------- include/qfw.h | 20 +++++++++++++++++++- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/drivers/qfw/qfw.c b/drivers/qfw/qfw.c index 2e260d60449..51f25b9ff0b 100644 --- a/drivers/qfw/qfw.c +++ b/drivers/qfw/qfw.c @@ -220,16 +220,12 @@ int qfw_locate_file(struct udevice *dev, const char *fname, /* make sure fw_list is loaded */ ret = qfw_read_firmware_list(dev); - if (ret) { - printf("error: can't read firmware file list\n"); + if (ret) return -EINVAL; - } file = qfw_find_file(dev, fname); - if (!file) { - printf("error: can't find %s\n", fname); + if (!file) return -ENOENT; - } *selectp = be16_to_cpu(file->cfg.select); *sizep = be32_to_cpu(file->cfg.size); @@ -237,13 +233,27 @@ int qfw_locate_file(struct udevice *dev, const char *fname, return 0; } +int qfw_locate_file_msg(struct udevice *dev, const char *fname, + enum fw_cfg_selector *selectp, ulong *sizep) +{ + int ret; + + ret = qfw_locate_file(dev, fname, selectp, sizep); + if (ret == -EINVAL) + printf("error: can't read firmware file list\n"); + else if (ret == -ENOENT) + printf("error: can't find %s\n", fname); + + return ret; +} + int qfw_load_file(struct udevice *dev, const char *fname, ulong addr) { enum fw_cfg_selector select; ulong size; int ret; - ret = qfw_locate_file(dev, fname, &select, &size); + ret = qfw_locate_file_msg(dev, fname, &select, &size); if (ret) return ret; @@ -258,7 +268,7 @@ int qfw_get_file(struct udevice *dev, const char *fname, struct abuf *loader) ulong size; int ret; - ret = qfw_locate_file(dev, fname, &select, &size); + ret = qfw_locate_file_msg(dev, fname, &select, &size); if (ret) return ret; diff --git a/include/qfw.h b/include/qfw.h index 75aada09206..1a634d7ed5d 100644 --- a/include/qfw.h +++ b/include/qfw.h @@ -467,15 +467,33 @@ int qfw_get_file(struct udevice *dev, const char *fname, struct abuf *loader); /** * qfw_locate_file() - Locate a file in the QEMU firmware config * + * This is the silent version that doesn't print error messages + * * @dev: UCLASS_QFW device * @fname: Filename to locate * @selectp: Returns the selector for the file * @sizep: Returns the size of the file - * Return: 0 on success, -EINVAL if firmware list cannot be read or file not found + * Return: 0 on success, -EINVAL if firmware list cannot be read, -ENOENT if + * file not found */ int qfw_locate_file(struct udevice *dev, const char *fname, enum fw_cfg_selector *selectp, ulong *sizep); +/** + * qfw_locate_file_msg() - Locate a file in the QEMU firmware config + * + * This version prints error messages on failure + * + * @dev: UCLASS_QFW device + * @fname: Filename to locate + * @selectp: Returns the selector for the file + * @sizep: Returns the size of the file + * Return: 0 on success, -EINVAL if firmware list cannot be read, -ENOENT if + * file not found + */ +int qfw_locate_file_msg(struct udevice *dev, const char *fname, + enum fw_cfg_selector *selectp, ulong *sizep); + /** * cmd_qfw_e820() - Execute the 'qfw e820' command for x86 * -- 2.43.0
From: Simon Glass <sjg@chromium.org> Add documentation explaining how to use the fw_cfg interface to specify a boot command for QEMU x86. This feature allows automated testing and scripting by providing the boot command directly through QEMU's firmware configuration interface. The documentation includes: - How to create a boot command file - The QEMU command-line syntax with -fw_cfg option - Behavior and limitations of the feature 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --- doc/board/emulation/qemu-x86.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/doc/board/emulation/qemu-x86.rst b/doc/board/emulation/qemu-x86.rst index c604e42990e..c2862e631ee 100644 --- a/doc/board/emulation/qemu-x86.rst +++ b/doc/board/emulation/qemu-x86.rst @@ -113,6 +113,26 @@ sure the specified CPU supports 64-bit like '-cpu core2duo'. Conversely '-cpu pentium' won't work for obvious reasons that the processor only supports 32-bit. +Specifying a boot command +-------------------------- + +It is possible to specify the boot command directly using the fw_cfg interface. +This allows QEMU to control the boot command, which can be useful for automated +testing or scripting. To use this feature, create a file containing the boot +command and pass it to QEMU using the fw_cfg option:: + + $ echo "qfw load; zboot 01000000 - 04000000 1b1ab50" > bootcmd.txt + $ qemu-system-x86_64 -nographic -bios path/to/u-boot.rom \ + -fw_cfg name=opt/u-boot/bootcmd,file=bootcmd.txt + +U-Boot will read the boot command from the firmware configuration and execute it +automatically during the boot process. This bypasses the normal distro boot +sequence. + +Note that the boot command is limited in length and should not exceed the boot +command buffer size. If the command is too long, U-Boot will fail to read it and +fall back to the default boot behavior. + Booting distros --------------- -- 2.43.0
From: Simon Glass <sjg@chromium.org> Add -b as a short alias for --bootcmd to build_helper so it is available in both build-qemu and build-efi Move the fw_cfg bootcmd logic to build_helper as well. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- scripts/build-qemu | 7 ------- scripts/build_helper.py | 7 +++++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/build-qemu b/scripts/build-qemu index 70dfe968f66..3d59012ed5e 100755 --- a/scripts/build-qemu +++ b/scripts/build-qemu @@ -41,8 +41,6 @@ def parse_args(): description='Build and/or run U-Boot with QEMU', formatter_class=argparse.RawTextHelpFormatter) build_helper.add_common_args(parser) - parser.add_argument('--bootcmd', type=str, - help='U-Boot bootcmd to pass via fw_cfg') 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', @@ -297,11 +295,6 @@ class BuildQemu: # Add other parameters gathered from options qemu_cmd.extend(self.qemu_extra) - # Add bootcmd via fw_cfg if specified - if self.args.bootcmd: - qemu_cmd.extend(['-fw_cfg', - f'name=opt/u-boot/bootcmd,string={self.args.bootcmd}']) - self.helper.setup_share(qemu_cmd) self.helper.run(qemu_cmd) diff --git a/scripts/build_helper.py b/scripts/build_helper.py index d401da0215c..8bce9a6183c 100644 --- a/scripts/build_helper.py +++ b/scripts/build_helper.py @@ -189,6 +189,11 @@ sct_mnt = /mnt/sct cmd.extend(['-object', 'rng-random,filename=/dev/urandom,id=rng0', '-device', 'virtio-rng-pci,rng=rng0']) + # Add bootcmd via fw_cfg if specified + if args.bootcmd: + cmd.extend(['-fw_cfg', + f'name=opt/u-boot/bootcmd,string={args.bootcmd}']) + def setup_share(self, qemu_cmd): sock = Path('/tmp/virtiofs.sock') proc = None @@ -285,6 +290,8 @@ def add_common_args(parser): """ parser.add_argument('-a', '--arch', default='arm', choices=['arm', 'x86'], help='Select architecture (arm, x86) Default: arm') + parser.add_argument('-b', '--bootcmd', type=str, + help='U-Boot bootcmd to pass via fw_cfg') parser.add_argument('-B', '--no-build', action='store_true', help="Don't build; assume a build exists") parser.add_argument('--build-dir', help='Directory to use for the build') -- 2.43.0
From: Simon Glass <sjg@chromium.org> When running U-Boot as an EFI application under EDK2/OVMF, U-Boot doesn't have direct access to QEMU's fw_cfg interface. To support the --bootcmd option, write a uboot.env file to the EFI partition containing the boot command. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- scripts/build-efi | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/scripts/build-efi b/scripts/build-efi index 95a8f456097..6c3b7a274f3 100755 --- a/scripts/build-efi +++ b/scripts/build-efi @@ -13,6 +13,13 @@ OVMF-pure-efi.x64.fd at https://drive.google.com/file/d/1c39YI9QtpByGQ4V0UNNQtGqttEzS-eFV/view?usp=s... Use ~/.build-efi to configure the various paths used by this script. + +When --bootcmd is specified, a uboot.env file is created on the EFI partition +containing the boot command. U-Boot needs to be configured to import this file +on startup, for example by adding to CONFIG_PREBOOT or the default bootcmd: + + load ${devtype} ${devnum}:${distro_bootpart} ${loadaddr} uboot.env; \ + env import -t ${loadaddr} ${filesize} """ from argparse import ArgumentParser @@ -20,6 +27,7 @@ import os from pathlib import Path import shutil import sys +import tempfile import build_helper @@ -169,6 +177,32 @@ class BuildEfi: tools.write_file(f'{dst}/startup.nsh', f'fs0:{fname}', binary=False) shutil.copy(f'{self.build_dir}/{fname}', dst) + # Write U-Boot environment file if bootcmd is specified + if self.args.bootcmd: + # Check if mkenvimage is available (local build or system-wide) + mkenvimage = 'tools/mkenvimage' + if not os.path.exists(mkenvimage): + mkenvimage = 'mkenvimage' + if not shutil.which(mkenvimage): + tout.error('Please install u-boot-tools package:') + tout.error(' sudo apt install u-boot-tools') + raise FileNotFoundError('mkenvimage not found') + + # Create text environment file + env_content = f'bootcmd={self.args.bootcmd}\n' + with tempfile.NamedTemporaryFile(mode='w', delete=False, + suffix='.txt') as outf: + outf.write(env_content) + env_fname = outf.name + + try: + # Convert to binary format with CRC using mkenvimage + command.run(mkenvimage, '-s', '0x1000', + '-o', f'{dst}/uboot.env', env_fname) + print(f'Created uboot.env with bootcmd: {self.args.bootcmd}') + finally: + os.unlink(env_fname) + def do_build(self, build): """Build U-Boot for the selected board""" extra = ['-a', '~CONSOLE_PAGER'] if self.args.no_pager else [] -- 2.43.0
From: Simon Glass <sjg@chromium.org> Switch back to the normal boot command for this app. Signed-off-by: Simon Glass <sjg@chromium.org> --- configs/efi-x86_app64_defconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configs/efi-x86_app64_defconfig b/configs/efi-x86_app64_defconfig index 63a53361688..d50c8ac4f2c 100644 --- a/configs/efi-x86_app64_defconfig +++ b/configs/efi-x86_app64_defconfig @@ -17,7 +17,7 @@ CONFIG_BOOTSTD_FULL=y CONFIG_SHOW_BOOT_PROGRESS=y CONFIG_USE_BOOTARGS=y CONFIG_BOOTARGS="root=/dev/sdb3 init=/sbin/init rootwait ro" -CONFIG_BOOTCOMMAND="bootctl run" +CONFIG_BOOTCOMMAND="bootflow scan -lb" CONFIG_SYS_PBSIZE=532 CONFIG_SYS_CONSOLE_INFO_QUIET=y # CONFIG_CONSOLE_PAGER is not set -- 2.43.0
From: Simon Glass <sjg@chromium.org> Enable CONFIG_ENV_IS_IN_FAT to automatically load the environment from the uboot.env file on the FAT filesystem. This makes the build-efi script's --bootcmd option work correctly. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- configs/efi-x86_app64_defconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/configs/efi-x86_app64_defconfig b/configs/efi-x86_app64_defconfig index d50c8ac4f2c..6f038c102b4 100644 --- a/configs/efi-x86_app64_defconfig +++ b/configs/efi-x86_app64_defconfig @@ -30,6 +30,9 @@ CONFIG_CMD_TIME=y CONFIG_CMD_EXT4_WRITE=y CONFIG_MAC_PARTITION=y CONFIG_ENV_OVERWRITE=y +CONFIG_ENV_IS_IN_FAT=y +CONFIG_ENV_FAT_INTERFACE="efi" +CONFIG_ENV_FAT_DEVICE_AND_PART="0:0" CONFIG_SYS_RELOC_GD_ENV_ADDR=y CONFIG_USE_BOOTFILE=y CONFIG_BOOTFILE="bzImage" -- 2.43.0
participants (1)
- 
                
Simon Glass