@@ -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>
@@ -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;
+}
@@ -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);