[PATCH 00/11] Bootstage and script enhancements
From: Simon Glass <sjg@chromium.org> This series collects together a few improvements to the bootstage subsystem, principally some new tests. It also adds support for ACPI FPDT which is a way of passing timing information to the OS. A bug fix is provided for mouse clicks with expo. Finally the series includes some new options for the build-efi script for networking, spice, etc. Simon Glass (11): expo: Correct handling of mouse clicks bootstage: Move the bootstage record to the header file bootstage: Add some more tests bootstage: Add a way to read the time from a record acpi: Add support for Firmware Performance Data Table acpi: fpdt: Generate the table bootm: Update FPDT boot times before handing off to OS scripts: build-efi: Support spice integration scripts: build-efi: Allow enabling networking scripts: build-efi: Flush output before running QEMU scripts: build-qemu: Allow enabling TKey access boot/bootflow_menu.c | 1 + boot/bootm_final.c | 17 ++++ common/bootstage.c | 59 +++++++++-- include/acpi/acpi_table.h | 76 ++++++++++++++ include/bootstage.h | 55 +++++++++++ lib/acpi/acpi_table.c | 86 ++++++++++++++++ scripts/build-efi | 18 +++- scripts/build-qemu | 5 + test/cmd/Makefile | 1 + test/cmd/bootstage.c | 32 ++++++ test/common/Makefile | 2 + test/common/bootstage.c | 201 ++++++++++++++++++++++++++++++++++++++ test/dm/acpi.c | 73 ++++++++++++++ 13 files changed, 617 insertions(+), 9 deletions(-) create mode 100644 test/cmd/bootstage.c create mode 100644 test/common/bootstage.c -- 2.43.0 base-commit: 7c50fc5afa29cee5782b2a8bac3271b12023af0d branch: secb
From: Simon Glass <sjg@chromium.org> Due to a missing return in bootflow_menu_poll() a click on any object is handled as if it were a click on the settings object. Fix this by returning the correct error code for unrecognised clicks. Signed-off-by: Simon Glass <sjg@chromium.org> Fixes: e94de63e6c8 ("expo: Add a way to select settings") --- boot/bootflow_menu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/boot/bootflow_menu.c b/boot/bootflow_menu.c index 26a2f559958..eda95428bf4 100644 --- a/boot/bootflow_menu.c +++ b/boot/bootflow_menu.c @@ -418,6 +418,7 @@ int bootflow_menu_poll(struct expo *exp, int *seqp) case EXPOACT_CLICK: if (act.select.id == OBJ_SETTINGS) return -ECOMM; /* layout change request */ + return -EAGAIN; case EXPOACT_SETTINGS: return -ECOMM; /* layout change request */ default: -- 2.43.0
From: Simon Glass <sjg@chromium.org> Move struct bootstage_record to the header file so it can be used from tests. Add some more comments for the struct, while we are here. Signed-off-by: Simon Glass <sjg@chromium.org> --- common/bootstage.c | 8 -------- include/bootstage.h | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/common/bootstage.c b/common/bootstage.c index 4532100acea..1fee2edeef3 100644 --- a/common/bootstage.c +++ b/common/bootstage.c @@ -26,14 +26,6 @@ enum { RECORD_COUNT = CONFIG_VAL(BOOTSTAGE_RECORD_COUNT), }; -struct bootstage_record { - ulong time_us; - uint32_t start_us; - const char *name; - int flags; /* see enum bootstage_flags */ - enum bootstage_id id; -}; - struct bootstage_data { uint rec_count; uint next_id; diff --git a/include/bootstage.h b/include/bootstage.h index ad98bffedc3..98bd9024da8 100644 --- a/include/bootstage.h +++ b/include/bootstage.h @@ -220,6 +220,24 @@ enum bootstage_id { BOOTSTAGE_ID_ALLOC, }; +/** + * struct bootstage_record - information about a bootstage timing + * + * @time_us: time in microseconds, either the timestamp or the total accumulated + * time for this ID + * @start_us: timestamp of the current starting point for this ID + * @name: name of the timestamp + * @flags: Flags (enum bootstage_flags) + * @id: Bootstage ID + */ +struct bootstage_record { + ulong time_us; + u32 start_us; + const char *name; + int flags; + enum bootstage_id id; +}; + /* * Return the time since boot in microseconds, This is needed for bootstage * and should be defined in CPU- or board-specific code. If undefined then -- 2.43.0
From: Simon Glass <sjg@chromium.org> There is already a Python test. Add a few C tests as well, for bootstage itself and for the 'bootstage' command. Add helpers to access the internal state. Be careful to zero records when removing them, since if the record is later reused, bootstage expects the time to be zero. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- common/bootstage.c | 37 ++++++++ include/bootstage.h | 24 +++++ test/cmd/Makefile | 1 + test/cmd/bootstage.c | 32 +++++++ test/common/Makefile | 2 + test/common/bootstage.c | 199 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 295 insertions(+) create mode 100644 test/cmd/bootstage.c create mode 100644 test/common/bootstage.c diff --git a/common/bootstage.c b/common/bootstage.c index 1fee2edeef3..e0298991fa9 100644 --- a/common/bootstage.c +++ b/common/bootstage.c @@ -212,6 +212,43 @@ uint32_t bootstage_accum(enum bootstage_id id) return duration; } +uint bootstage_get_rec_count(void) +{ + struct bootstage_data *data = gd->bootstage; + + if (!data) + return 0; + + return data->rec_count; +} + +const struct bootstage_record *bootstage_get_rec(uint index) +{ + struct bootstage_data *data = gd->bootstage; + + if (!data || index >= data->rec_count) + return NULL; + + return &data->record[index]; +} + +void bootstage_set_rec_count(uint count) +{ + struct bootstage_data *data = gd->bootstage; + uint i; + + if (!data || count > RECORD_COUNT) + return; + + /* Clear any records beyond the new count */ + for (i = count; i < data->rec_count; i++) { + data->record[i].time_us = 0; + data->record[i].start_us = 0; + } + + data->rec_count = count; +} + /** * Get a record name as a printable string * diff --git a/include/bootstage.h b/include/bootstage.h index 98bd9024da8..adc7b8f0a35 100644 --- a/include/bootstage.h +++ b/include/bootstage.h @@ -354,6 +354,30 @@ uint32_t bootstage_start(enum bootstage_id id, const char *name); */ uint32_t bootstage_accum(enum bootstage_id id); +/** + * bootstage_get_rec_count() - Get the number of bootstage records + * + * Return: number of bootstage records + */ +uint bootstage_get_rec_count(void); + +/** + * bootstage_get_rec() - Get a bootstage record by index + * + * @index: Index of the record to retrieve (numbered from 0) + * Return: pointer to the record, or NULL if @index is out of range + */ +const struct bootstage_record *bootstage_get_rec(uint index); + +/** + * bootstage_set_rec_count() - Set the number of bootstage records + * + * This can be used to restore the record count after testing + * + * @count: New record count (must be <= RECORD_COUNT) + */ +void bootstage_set_rec_count(uint count); + /* Print a report about boot time */ void bootstage_report(void); diff --git a/test/cmd/Makefile b/test/cmd/Makefile index 3fc07f0cacf..9cd8ea3aaf0 100644 --- a/test/cmd/Makefile +++ b/test/cmd/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_X86) += cpuid.o msr.o obj-$(CONFIG_CMD_ADDR_FIND) += addr_find.o obj-$(CONFIG_CMD_ADDRMAP) += addrmap.o obj-$(CONFIG_CMD_BDI) += bdinfo.o +obj-$(CONFIG_CMD_BOOTSTAGE) += bootstage.o obj-$(CONFIG_CMD_CHID) += chid.o obj-$(CONFIG_COREBOOT_SYSINFO) += coreboot.o obj-$(CONFIG_CMD_FDT) += fdt.o diff --git a/test/cmd/bootstage.c b/test/cmd/bootstage.c new file mode 100644 index 00000000000..dee4a0671fa --- /dev/null +++ b/test/cmd/bootstage.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Tests for bootstage command + * + * Copyright 2025 Canonical Ltd + */ + +#include <bootstage.h> +#include <test/cmd.h> +#include <test/ut.h> + +static int cmd_bootstage_report(struct unit_test_state *uts) +{ + uint count; + + /* Get the current record count */ + count = bootstage_get_rec_count(); + ut_assert(count > 0); + + /* Test the bootstage report command runs successfully */ + ut_assertok(run_command("bootstage report", 0)); + + /* Verify the report contains expected headers and stages */ + ut_assert_nextline("Timer summary in microseconds (%u records):", + count); + ut_assert_nextline(" Mark Elapsed Stage"); + ut_assert_nextline(" 0 0 reset"); + ut_assert_skip_to_line("Accumulated time:"); + + return 0; +} +CMD_TEST(cmd_bootstage_report, UTF_CONSOLE); diff --git a/test/common/Makefile b/test/common/Makefile index 7b5927b5a44..a5df946396a 100644 --- a/test/common/Makefile +++ b/test/common/Makefile @@ -7,6 +7,8 @@ obj-$(CONFIG_$(PHASE_)CMDLINE) += bloblist.o endif endif +obj-$(CONFIG_BOOTSTAGE) += bootstage.o + obj-$(CONFIG_CONSOLE_PAGER) += console.o obj-$(CONFIG_CYCLIC) += cyclic.o obj-$(CONFIG_EVENT_DYNAMIC) += event.o diff --git a/test/common/bootstage.c b/test/common/bootstage.c new file mode 100644 index 00000000000..6e97c3e3a72 --- /dev/null +++ b/test/common/bootstage.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Tests for bootstage API + * + * Copyright 2025 Canonical Ltd + */ + +#include <bootstage.h> +#include <linux/delay.h> +#include <test/common.h> +#include <test/test.h> +#include <test/ut.h> + +/* Test bootstage_mark_name() */ +static int test_bootstage_mark(struct unit_test_state *uts) +{ + const struct bootstage_record *rec; + ulong time; + int count; + + /* Get the current count so we know where our record will be */ + count = bootstage_get_rec_count(); + + /* Mark a stage and verify we get a valid timestamp */ + time = bootstage_mark_name(BOOTSTAGE_ID_USER + 50, "test_stage_mark"); + ut_assert(time > 0); + + /* Verify the count increased by 1 */ + ut_asserteq(count + 1, bootstage_get_rec_count()); + + /* Check that the record was added correctly */ + rec = bootstage_get_rec(count); + ut_assertnonnull(rec); + ut_asserteq(BOOTSTAGE_ID_USER + 50, rec->id); + ut_asserteq_str("test_stage_mark", rec->name); + ut_asserteq(time, rec->time_us); + ut_asserteq(0, rec->flags); + + /* Restore the original count */ + bootstage_set_rec_count(count); + + return 0; +} +COMMON_TEST(test_bootstage_mark, 0); + +/* Test bootstage_error_name() */ +static int test_bootstage_error(struct unit_test_state *uts) +{ + const struct bootstage_record *rec; + ulong time; + int count; + + count = bootstage_get_rec_count(); + + /* Mark an error stage and verify we get a valid timestamp */ + time = bootstage_error_name(BOOTSTAGE_ID_USER + 51, "test_error"); + ut_assert(time > 0); + + /* Check the error record */ + rec = bootstage_get_rec(count); + ut_assertnonnull(rec); + ut_asserteq(BOOTSTAGE_ID_USER + 51, rec->id); + ut_asserteq_str("test_error", rec->name); + ut_asserteq(time, rec->time_us); + ut_asserteq(BOOTSTAGEF_ERROR, rec->flags); + + /* Restore the original count */ + bootstage_set_rec_count(count); + + return 0; +} +COMMON_TEST(test_bootstage_error, 0); + +/* Test bootstage_start() and bootstage_accum() */ +static int test_bootstage_accum(struct unit_test_state *uts) +{ + enum bootstage_id id = BOOTSTAGE_ID_USER + 53; + uint start_time, elapsed1, elapsed2; + const struct bootstage_record *rec; + int index, count; + + count = bootstage_get_rec_count(); + + /* Start an accumulator */ + start_time = bootstage_start(id, "test_accum"); + ut_assert(start_time > 0); + + /* Check the accumulator record was created */ + index = count; + rec = bootstage_get_rec(index); + ut_assertnonnull(rec); + ut_asserteq(id, rec->id); + ut_asserteq_str("test_accum", rec->name); + ut_asserteq(start_time, rec->start_us); + + /* Accumulate the time */ + udelay(1); + elapsed1 = bootstage_accum(id); + ut_assert(elapsed1 >= 0); + + /* Check the accumulated time was recorded */ + ut_asserteq(elapsed1, rec->time_us); + + /* Start and accumulate again */ + bootstage_start(id, "test_accum"); + udelay(1); + elapsed2 = bootstage_accum(id); + ut_assert(elapsed2 >= 0); + + /* Check the total time accumulated */ + rec = bootstage_get_rec(index); + ut_asserteq(rec->time_us, elapsed1 + elapsed2); + + /* Restore the original count */ + bootstage_set_rec_count(count); + + return 0; +} +COMMON_TEST(test_bootstage_accum, 0); + +/* Test bootstage_mark_code() */ +static int test_bootstage_mark_code(struct unit_test_state *uts) +{ + const struct bootstage_record *rec; + ulong time; + int count; + + count = bootstage_get_rec_count(); + + /* Mark with file, function, and line number */ + time = bootstage_mark_code("file.c", __func__, 123); + ut_assert(time > 0); + + /* Check the record */ + rec = bootstage_get_rec(count); + ut_assertnonnull(rec); + ut_asserteq(time, rec->time_us); + ut_asserteq_str("file.c,123: test_bootstage_mark_code", rec->name); + + /* Restore the original count */ + bootstage_set_rec_count(count); + + return 0; +} +COMMON_TEST(test_bootstage_mark_code, 0); + +/* Test bootstage_get_rec_count() */ +static int test_bootstage_get_rec_count(struct unit_test_state *uts) +{ + const struct bootstage_record *rec; + int orig, count; + + /* Get initial count */ + orig = bootstage_get_rec_count(); + ut_assert(orig > 0); + + /* Add a new record */ + bootstage_mark_name(BOOTSTAGE_ID_USER + 52, "test_count"); + + /* Verify count increased */ + count = bootstage_get_rec_count(); + ut_asserteq(orig + 1, count); + + /* Verify the record was added at the correct index */ + rec = bootstage_get_rec(orig); + ut_assertnonnull(rec); + ut_asserteq(BOOTSTAGE_ID_USER + 52, rec->id); + ut_asserteq_str("test_count", rec->name); + + /* Restore the original count */ + bootstage_set_rec_count(orig); + + return 0; +} +COMMON_TEST(test_bootstage_get_rec_count, 0); + +/* Test bootstage_get_rec() */ +static int test_bootstage_get_rec(struct unit_test_state *uts) +{ + const struct bootstage_record *rec; + int count; + + /* Get total count */ + count = bootstage_get_rec_count(); + ut_assert(count > 0); + + /* Get first record (should be "reset") */ + rec = bootstage_get_rec(0); + ut_assertnonnull(rec); + ut_asserteq_str("reset", rec->name); + + /* Test out of bounds access */ + ut_assertnull(bootstage_get_rec(count)); + ut_assertnull(bootstage_get_rec(count + 100)); + ut_assertnull(bootstage_get_rec(-1)); + + return 0; +} +COMMON_TEST(test_bootstage_get_rec, 0); -- 2.43.0
From: Simon Glass <sjg@chromium.org> Add a function which returns the time given a record ID. Signed-off-by: Simon Glass <sjg@chromium.org> --- common/bootstage.c | 14 ++++++++++++++ include/bootstage.h | 13 +++++++++++++ test/common/bootstage.c | 2 ++ 3 files changed, 29 insertions(+) diff --git a/common/bootstage.c b/common/bootstage.c index e0298991fa9..567b084668b 100644 --- a/common/bootstage.c +++ b/common/bootstage.c @@ -249,6 +249,20 @@ void bootstage_set_rec_count(uint count) data->rec_count = count; } +ulong bootstage_get_time(enum bootstage_id id) +{ + struct bootstage_data *data = gd->bootstage; + struct bootstage_record *rec; + + if (!data) + return 0; + rec = find_id(data, id); + if (!rec) + return 0; + + return rec->time_us; +} + /** * Get a record name as a printable string * diff --git a/include/bootstage.h b/include/bootstage.h index adc7b8f0a35..a0f687a9a4b 100644 --- a/include/bootstage.h +++ b/include/bootstage.h @@ -378,6 +378,14 @@ const struct bootstage_record *bootstage_get_rec(uint index); */ void bootstage_set_rec_count(uint count); +/* + * bootstage_get_time() - Get the timestamp for a bootstage ID + * + * @id: Bootstage id to look up + * Return: timestamp in us for that stage, or 0 if not found + */ +ulong bootstage_get_time(enum bootstage_id id); + /* Print a report about boot time */ void bootstage_report(void); @@ -478,6 +486,11 @@ static inline uint32_t bootstage_accum(enum bootstage_id id) return 0; } +static inline ulong bootstage_get_time(enum bootstage_id id) +{ + return 0; +} + static inline void bootstage_report(void) { } diff --git a/test/common/bootstage.c b/test/common/bootstage.c index 6e97c3e3a72..d8f1d1d32f9 100644 --- a/test/common/bootstage.c +++ b/test/common/bootstage.c @@ -35,6 +35,7 @@ static int test_bootstage_mark(struct unit_test_state *uts) ut_asserteq_str("test_stage_mark", rec->name); ut_asserteq(time, rec->time_us); ut_asserteq(0, rec->flags); + ut_asserteq(time, bootstage_get_time(BOOTSTAGE_ID_USER + 50)); /* Restore the original count */ bootstage_set_rec_count(count); @@ -110,6 +111,7 @@ static int test_bootstage_accum(struct unit_test_state *uts) /* Check the total time accumulated */ rec = bootstage_get_rec(index); ut_asserteq(rec->time_us, elapsed1 + elapsed2); + ut_asserteq(rec->time_us, bootstage_get_time(id)); /* Restore the original count */ bootstage_set_rec_count(count); -- 2.43.0
From: Simon Glass <sjg@chromium.org> Add ACPI FPDT support to report firmware boot timing information to the OS. The FPDT table contains timing data for firmware phases from reset through OS handoff. Add some functions to enable generation of this table. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- include/acpi/acpi_table.h | 76 +++++++++++++++++++++++++++++++++++++++ lib/acpi/acpi_table.c | 75 ++++++++++++++++++++++++++++++++++++++ test/dm/acpi.c | 73 +++++++++++++++++++++++++++++++++++++ 3 files changed, 224 insertions(+) diff --git a/include/acpi/acpi_table.h b/include/acpi/acpi_table.h index 850cb8db816..8459fa60c25 100644 --- a/include/acpi/acpi_table.h +++ b/include/acpi/acpi_table.h @@ -760,6 +760,49 @@ struct __packed acpi_bgrt { u32 offset_y; }; +/** + * struct acpi_fpdt - Firmware Performance Data Table (FPDT) header + * + * See ACPI Spec v6.5 section 5.2.24 for details + */ +struct acpi_fpdt { + struct acpi_table_header header; +}; + +/* FPDT Performance Record Types */ +#define FPDT_REC_BOOT 0 + +/* FPDT Performance Record Header */ +struct acpi_fpdt_hdr { + u16 type; + u8 length; + u8 revision; +} __packed; + +/** + * struct acpi_fpdt_boot - Firmware Basic Boot Performance Record + * + * This record describes the boot performance from power-on to OS handoff. + * All timing values are in microseconds since system reset. + * + * @hdr: Record header + * @reserved: Reserved, must be zero + * @reset_end: Timer value at start of firmware (microseconds) + * @loader_start: Start of OS loader load (microseconds) + * @loader_exec: Start of OS loader execution (microseconds) + * @ebs_entry: Entry to ExitBootServices (microseconds) + * @ebs_exit: Exit from ExitBootServices (microseconds) + */ +struct acpi_fpdt_boot { + struct acpi_fpdt_hdr hdr; + u32 reserved; + u64 reset_end; + u64 loader_start; + u64 loader_exec; + u64 ebs_entry; + u64 ebs_exit; +} __packed; + /* Types for PPTT */ #define ACPI_PPTT_TYPE_PROC 0 #define ACPI_PPTT_TYPE_CACHE 1 @@ -952,6 +995,7 @@ enum acpi_tables { ACPITAB_ECDT, ACPITAB_FACS, ACPITAB_FADT, + ACPITAB_FPDT, ACPITAB_GTDT, ACPITAB_HEST, ACPITAB_HPET, @@ -1307,6 +1351,38 @@ void *acpi_get_end(void); */ int acpi_write_bgrt(struct acpi_ctx *ctx); +/** + * acpi_write_fpdt() - Write a Firmware Performance Data Table (FPDT) + * + * This creates an FPDT table with firmware boot timing information + * + * @ctx: ACPI context + * @uboot_start: U-Boot start time in microseconds + * Return: 0 if OK, -ve on error + */ +int acpi_write_fpdt(struct acpi_ctx *ctx, u64 uboot_start); + +/** + * acpi_get_fpdt_boot() - Get pointer to FPDT boot performance record + * + * This allows the caller to update the boot performance timing fields + * after the FPDT table has been created. After updating, call + * acpi_fix_fpdt_checksum() to recalculate the table checksum. + * + * Return: pointer to boot performance record, or NULL if not found + */ +struct acpi_fpdt_boot *acpi_get_fpdt_boot(void); + +/** + * acpi_fix_fpdt_checksum() - Recalculate FPDT table checksum + * + * Call this after updating the boot performance record to fix the + * table checksum. + * + * Return: 0 if OK, -ENOENT if FPDT table not found + */ +int acpi_fix_fpdt_checksum(void); + #endif /* !__ACPI__*/ #include <asm/acpi_table.h> diff --git a/lib/acpi/acpi_table.c b/lib/acpi/acpi_table.c index f2dadd792bd..31d8d314314 100644 --- a/lib/acpi/acpi_table.c +++ b/lib/acpi/acpi_table.c @@ -6,6 +6,7 @@ */ #include <bloblist.h> +#include <bootstage.h> #include <cpu.h> #include <dm.h> #include <efi_api.h> @@ -106,6 +107,8 @@ int acpi_get_table_revision(enum acpi_tables table) return 1; case ACPITAB_GTDT: /* ACPI 6.2: 2, ACPI 6.3: 3 */ return 2; + case ACPITAB_FPDT: /* ACPI 6.4+: 1 */ + return 1; default: return -EINVAL; } @@ -710,3 +713,75 @@ 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; +} diff --git a/test/dm/acpi.c b/test/dm/acpi.c index a274671de02..1409c333eb1 100644 --- a/test/dm/acpi.c +++ b/test/dm/acpi.c @@ -7,6 +7,7 @@ */ #include <bloblist.h> +#include <bootstage.h> #include <console.h> #include <dm.h> #include <efi_log.h> @@ -961,3 +962,75 @@ static int dm_test_acpi_bgrt(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_acpi_bgrt, UTF_SCAN_FDT); + +/* Test ACPI FPDT (Firmware Performance Data Table) generation */ +static int dm_test_acpi_fpdt(struct unit_test_state *uts) +{ + struct acpi_fpdt_boot *rec; + struct acpi_fpdt *fpdt; + struct acpi_ctx ctx; + ulong addr, time; + void *buf; + + addr = 0; + buf = map_sysmem(addr, BUF_SIZE); + + /* Set up context with base tables (RSDP, RSDT, XSDT) */ + ut_assertok(setup_ctx_and_base_tables(uts, &ctx, addr)); + + /* Save where the FPDT will be written */ + fpdt = ctx.current; + + /* Write the FPDT table with test U-Boot start time */ + time = timer_get_boot_us(); + ut_assertok(acpi_write_fpdt(&ctx, 1234)); + + /* Verify the FPDT was written at the saved location */ + ut_asserteq_mem("FPDT", fpdt->header.signature, ACPI_NAME_LEN); + ut_asserteq(1, fpdt->header.revision); + ut_asserteq(sizeof(struct acpi_fpdt) + sizeof(struct acpi_fpdt_boot), + fpdt->header.length); + + /* Verify the boot performance record */ + rec = (struct acpi_fpdt_boot *)(fpdt + 1); + ut_asserteq(FPDT_REC_BOOT, rec->hdr.type); + ut_asserteq(sizeof(struct acpi_fpdt_boot), rec->hdr.length); + ut_asserteq(2, rec->hdr.revision); + + ut_asserteq(1234, rec->reset_end); + ut_assert(rec->loader_start != 0); + ut_assert(rec->loader_exec != 0); + ut_assert(rec->ebs_entry != 0); + ut_assert(rec->ebs_exit != 0); + + /* Verify checksum is valid */ + ut_asserteq(0, table_compute_checksum(fpdt, fpdt->header.length)); + + /* Get pointer to boot record and verify it matches */ + rec = acpi_get_fpdt_boot(); + ut_assertnonnull(rec); + ut_asserteq_ptr(rec, (struct acpi_fpdt_boot *)(fpdt + 1)); + ut_asserteq(1234, rec->reset_end); + + /* Update a timing field */ + rec->ebs_entry = 123; + rec->ebs_exit = 456; + + /* Checksum should now be invalid */ + ut_assert(table_compute_checksum(fpdt, fpdt->header.length) != 0); + + /* Fix the checksum */ + ut_assertok(acpi_fix_fpdt_checksum()); + + /* Checksum should now be valid again */ + ut_asserteq(0, table_compute_checksum(fpdt, fpdt->header.length)); + + /* Verify the updated values are still there */ + ut_asserteq(123, rec->ebs_entry); + ut_asserteq(456, rec->ebs_exit); + + unmap_sysmem(buf); + + return 0; +} +DM_TEST(dm_test_acpi_fpdt, 0); -- 2.43.0
From: Simon Glass <sjg@chromium.org> Add an ACPI writer to automatically generate the FPDT table with boot timing information when ACPI tables are created. This allows the OS to read firmware boot-timing metrics from the FPDT table. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- lib/acpi/acpi_table.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/acpi/acpi_table.c b/lib/acpi/acpi_table.c index 31d8d314314..0c1b8e20cf4 100644 --- a/lib/acpi/acpi_table.c +++ b/lib/acpi/acpi_table.c @@ -785,3 +785,14 @@ int acpi_fix_fpdt_checksum(void) return 0; } + +static int acpi_create_fpdt(struct acpi_ctx *ctx, + const struct acpi_writer *entry) +{ + u64 uboot_start; + + uboot_start = bootstage_get_time(BOOTSTAGE_ID_START_UBOOT_F); + + return acpi_write_fpdt(ctx, uboot_start); +} +ACPI_WRITER(6fpdt, "FPDT", acpi_create_fpdt, 0); -- 2.43.0
From: Simon Glass <sjg@chromium.org> Update the FPDT boot performance record's ebs_entry and ebs_exit fields in bootm_final() to record the time when we're about to hand off to the operating system. The timing is recorded just before we start the kernel, after all device cleanup and before interrupts are disabled, which corresponds to the ExitBootServices phase in UEFI. If EFI is used to boot, a further update could be made, but that is left for later. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- boot/bootm_final.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/boot/bootm_final.c b/boot/bootm_final.c index 881d737ce67..870c16b7853 100644 --- a/boot/bootm_final.c +++ b/boot/bootm_final.c @@ -5,9 +5,11 @@ * Copyright 2025 Simon Glass <sjg@chromium.org> */ +#include <acpi/acpi_table.h> #include <bootm.h> #include <bootstage.h> #include <event.h> +#include <timer.h> #include <usb.h> #include <dm/root.h> @@ -25,6 +27,21 @@ void bootm_final(enum bootm_final_t flags) bootstage_mark_name(BOOTSTAGE_ID_BOOTM_HANDOFF, "start_kernel"); + /* Update FPDT boot performance record if it exists */ + if (IS_ENABLED(CONFIG_GENERATE_ACPI_TABLE)) { + struct acpi_fpdt_boot *fpdt; + + 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(); + } + } + if (IS_ENABLED(CONFIG_BOOTSTAGE_FDT) && IS_ENABLED(CONFIG_CMD_FDT)) bootstage_fdt_add_report(); if (IS_ENABLED(CONFIG_BOOTSTAGE_REPORT)) -- 2.43.0
From: Simon Glass <sjg@chromium.org> Add a -c/--spice flag to enable spice, so that copy/paste works between the guest and host. Signed-off-by: Simon Glass <sjg@chromium.org> --- scripts/build-efi | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/build-efi b/scripts/build-efi index fffe699e80a..359f8ff4052 100755 --- a/scripts/build-efi +++ b/scripts/build-efi @@ -47,6 +47,8 @@ def parse_args(): help='Package up the payload instead of the app') parser.add_argument('-P', '--partition', action='store_true', help='Create a partition table') + parser.add_argument('-c', '--spice', action='store_true', + help='Enable SPICE for clipboard sharing') parser.add_argument('-v', '--verbose', action='store_true', help='Show executed commands') @@ -115,6 +117,12 @@ class BuildEfi: else: # x86 extra += ['-device', 'qemu-xhci', '-device', 'usb-kbd', '-device', 'usb-mouse'] + + # This uses QEMU's GTK clipboard integration with SPICE vdagent + if self.args.spice: + extra += ['-device', 'virtio-serial-pci'] + extra += ['-chardev', 'qemu-vdagent,id=spicechannel0,name=vdagent,clipboard=on'] + extra += ['-device', 'virtserialport,chardev=spicechannel0,name=com.redhat.spice.0'] extra += ['-serial', 'mon:stdio'] serial_msg = '' if self.args.kvm: -- 2.43.0
From: Simon Glass <sjg@chromium.org> Provide a -N/--net flag to enable networking for the guest. Signed-off-by: Simon Glass <sjg@chromium.org> --- scripts/build-efi | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/build-efi b/scripts/build-efi index 359f8ff4052..340ff5ac445 100755 --- a/scripts/build-efi +++ b/scripts/build-efi @@ -49,6 +49,8 @@ def parse_args(): help='Create a partition table') parser.add_argument('-c', '--spice', action='store_true', help='Enable SPICE for clipboard sharing') + parser.add_argument('-N', '--net', action='store_true', + help='Enable networking (with SSH forwarding on port 2222)') parser.add_argument('-v', '--verbose', action='store_true', help='Show executed commands') @@ -142,7 +144,11 @@ class BuildEfi: cmd = [qemu] cmd += '-m', mem - cmd += '-nic', 'none' + if self.args.net: + cmd += '-netdev', 'user,id=net0,hostfwd=tcp::2222-:22' + cmd += '-device', 'virtio-net-pci,netdev=net0' + else: + cmd += '-nic', 'none' cmd += extra self.helper.add_qemu_args(self.args, cmd, base_hd=1) tout.info(' '.join(cmd)) -- 2.43.0
From: Simon Glass <sjg@chromium.org> Complete writing of any output before running QEMU, since otherwise it does not always appear until QEMU exits (e.g. with 'tee') Signed-off-by: Simon Glass <sjg@chromium.org> --- scripts/build-efi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/build-efi b/scripts/build-efi index 340ff5ac445..95a8f456097 100755 --- a/scripts/build-efi +++ b/scripts/build-efi @@ -19,6 +19,7 @@ from argparse import ArgumentParser import os from pathlib import Path import shutil +import sys import build_helper @@ -152,6 +153,7 @@ class BuildEfi: cmd += extra self.helper.add_qemu_args(self.args, cmd, base_hd=1) tout.info(' '.join(cmd)) + sys.stdout.flush() command.run(*cmd) def setup_files(self, build, build_type, dst): -- 2.43.0
From: Simon Glass <sjg@chromium.org> Provide a -T/--tkey option to enable access to a USB-attached Tillitis TKey. Signed-off-by: Simon Glass <sjg@chromium.org> --- scripts/build-qemu | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/build-qemu b/scripts/build-qemu index ddaafc7587f..70dfe968f66 100755 --- a/scripts/build-qemu +++ b/scripts/build-qemu @@ -51,6 +51,8 @@ def parse_args(): help='Run qboot instead of U-Boot') parser.add_argument('-x', '--xpl', action='store_true', help='Use xPL image rather than U-Boot proper') + parser.add_argument('-T', '--tkey', action='store_true', + help='Enable TKey USB passthrough for testing') parser.add_argument( '--sct-seq', help='SCT sequence-file to be written into the SCT image if -e') @@ -153,6 +155,9 @@ class BuildQemu: default_bios = 'u-boot.rom' self.helper.qemu = 'qemu-system-i386' self.qemu_extra.extend(['-machine', 'q35']) + if args.tkey: + # Pass through TKey USB device to QEMU + self.qemu_extra.extend(['-device', 'usb-host,vendorid=0x1207,productid=0x8887']) if self.helper.bitness == 64: self.board = 'qemu-x86_64' self.helper.qemu = 'qemu-system-x86_64' -- 2.43.0
participants (1)
- 
                
Simon Glass