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