From 2cb873bd8e4eb7bf6cb0b54b5a64ce536fa595d3 Mon Sep 17 00:00:00 2001 From: David Garske Date: Wed, 7 Oct 2026 13:27:13 -0700 Subject: [PATCH 1/3] x86 FSP: generate ACPI tables (RSDP/XSDT/FADT/MADT/MCFG/DSDT) for the Linux payload --- arch.mk | 3 + include/x86/acpi.h | 37 ++++ include/x86/acpi_dsdt.h | 72 ++++++ include/x86/linux_loader.h | 1 + options.mk | 6 + src/x86/acpi.c | 441 +++++++++++++++++++++++++++++++++++++ src/x86/acpi_dsdt.asl | 89 ++++++++ src/x86/linux_loader.c | 65 +++++- 8 files changed, 711 insertions(+), 3 deletions(-) create mode 100644 include/x86/acpi.h create mode 100644 include/x86/acpi_dsdt.h create mode 100644 src/x86/acpi.c create mode 100644 src/x86/acpi_dsdt.asl diff --git a/arch.mk b/arch.mk index c291fde827..c5fc2a8344 100644 --- a/arch.mk +++ b/arch.mk @@ -2302,6 +2302,9 @@ ifeq ("${FSP}", "1") OBJS += src/gpt.o OBJS += src/disk.o OBJS += src/x86/mptable.o + ifeq ($(ACPI),1) + OBJS += src/x86/acpi.o + endif OBJS += src/stage2_params.o OBJS += src/x86/exceptions.o OBJS += src/x86/gdt.o diff --git a/include/x86/acpi.h b/include/x86/acpi.h new file mode 100644 index 0000000000..676353df5b --- /dev/null +++ b/include/x86/acpi.h @@ -0,0 +1,37 @@ +/* acpi.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ +#ifndef WOLFBOOT_ACPI_H +#define WOLFBOOT_ACPI_H + +#include + +/* ACPI tables go in high RAM below tolum, clear of the payload; the loader + * reserves it in e820. Overridable for the host unit test. */ +#ifndef ACPI_TABLE_BASE +#define ACPI_TABLE_BASE 0x60000000UL +#endif +#define ACPI_TABLE_SIZE 0x00010000UL /* 64 KiB */ + +/* Build RSDP -> XSDT -> { FADT, MADT, MCFG, DSDT } at ACPI_TABLE_BASE and + * return the RSDP physical address for boot_params.acpi_rsdp_addr. */ +uint64_t acpi_setup(void); + +#endif /* WOLFBOOT_ACPI_H */ diff --git a/include/x86/acpi_dsdt.h b/include/x86/acpi_dsdt.h new file mode 100644 index 0000000000..71b4ebbc20 --- /dev/null +++ b/include/x86/acpi_dsdt.h @@ -0,0 +1,72 @@ +/* acpi_dsdt.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ +/* Generated from src/x86/acpi_dsdt.asl by iasl + xxd -i; do not edit by hand. + * Regenerate: iasl src/x86/acpi_dsdt.asl && + * xxd -i -n acpi_dsdt_aml src/x86/acpi_dsdt.aml (append below this header). */ +static const unsigned char acpi_dsdt_aml[] = { + 0x44, 0x53, 0x44, 0x54, 0x1e, 0x02, 0x00, 0x00, 0x02, 0x48, 0x57, 0x4f, + 0x4c, 0x46, 0x42, 0x54, 0x4e, 0x41, 0x49, 0x54, 0x47, 0x4c, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x49, 0x4e, 0x54, 0x4c, 0x28, 0x06, 0x23, 0x20, + 0x10, 0x49, 0x1f, 0x5f, 0x53, 0x42, 0x5f, 0x5b, 0x82, 0x41, 0x1f, 0x50, + 0x43, 0x49, 0x30, 0x08, 0x5f, 0x48, 0x49, 0x44, 0x0c, 0x41, 0xd0, 0x0a, + 0x08, 0x08, 0x5f, 0x43, 0x49, 0x44, 0x0c, 0x41, 0xd0, 0x0a, 0x03, 0x08, + 0x5f, 0x53, 0x45, 0x47, 0x00, 0x08, 0x5f, 0x42, 0x42, 0x4e, 0x00, 0x08, + 0x5f, 0x55, 0x49, 0x44, 0x00, 0x14, 0x46, 0x12, 0x5f, 0x50, 0x52, 0x54, + 0x00, 0xa4, 0x12, 0x4d, 0x11, 0x17, 0x12, 0x0b, 0x04, 0x0c, 0xff, 0xff, + 0x02, 0x00, 0x00, 0x00, 0x0a, 0x10, 0x12, 0x0b, 0x04, 0x0c, 0xff, 0xff, + 0x04, 0x00, 0x00, 0x00, 0x0a, 0x10, 0x12, 0x0b, 0x04, 0x0c, 0xff, 0xff, + 0x0d, 0x00, 0x00, 0x00, 0x0a, 0x10, 0x12, 0x0b, 0x04, 0x0c, 0xff, 0xff, + 0x14, 0x00, 0x00, 0x00, 0x0a, 0x10, 0x12, 0x0b, 0x04, 0x0c, 0xff, 0xff, + 0x14, 0x00, 0x01, 0x00, 0x0a, 0x11, 0x12, 0x0b, 0x04, 0x0c, 0xff, 0xff, + 0x15, 0x00, 0x00, 0x00, 0x0a, 0x1b, 0x12, 0x0b, 0x04, 0x0c, 0xff, 0xff, + 0x15, 0x00, 0x01, 0x00, 0x0a, 0x28, 0x12, 0x0b, 0x04, 0x0c, 0xff, 0xff, + 0x16, 0x00, 0x00, 0x00, 0x0a, 0x10, 0x12, 0x0b, 0x04, 0x0c, 0xff, 0xff, + 0x17, 0x00, 0x00, 0x00, 0x0a, 0x10, 0x12, 0x0b, 0x04, 0x0c, 0xff, 0xff, + 0x19, 0x00, 0x00, 0x00, 0x0a, 0x1f, 0x12, 0x0c, 0x04, 0x0c, 0xff, 0xff, + 0x19, 0x00, 0x0a, 0x02, 0x00, 0x0a, 0x21, 0x12, 0x0b, 0x04, 0x0c, 0xff, + 0xff, 0x1c, 0x00, 0x00, 0x00, 0x0a, 0x10, 0x12, 0x0b, 0x04, 0x0c, 0xff, + 0xff, 0x1c, 0x00, 0x01, 0x00, 0x0a, 0x11, 0x12, 0x0c, 0x04, 0x0c, 0xff, + 0xff, 0x1c, 0x00, 0x0a, 0x02, 0x00, 0x0a, 0x12, 0x12, 0x0c, 0x04, 0x0c, + 0xff, 0xff, 0x1c, 0x00, 0x0a, 0x03, 0x00, 0x0a, 0x13, 0x12, 0x0b, 0x04, + 0x0c, 0xff, 0xff, 0x1d, 0x00, 0x00, 0x00, 0x0a, 0x10, 0x12, 0x0b, 0x04, + 0x0c, 0xff, 0xff, 0x1d, 0x00, 0x01, 0x00, 0x0a, 0x11, 0x12, 0x0c, 0x04, + 0x0c, 0xff, 0xff, 0x1d, 0x00, 0x0a, 0x02, 0x00, 0x0a, 0x12, 0x12, 0x0c, + 0x04, 0x0c, 0xff, 0xff, 0x1d, 0x00, 0x0a, 0x03, 0x00, 0x0a, 0x13, 0x12, + 0x0b, 0x04, 0x0c, 0xff, 0xff, 0x1e, 0x00, 0x00, 0x00, 0x0a, 0x10, 0x12, + 0x0b, 0x04, 0x0c, 0xff, 0xff, 0x1e, 0x00, 0x01, 0x00, 0x0a, 0x11, 0x12, + 0x0c, 0x04, 0x0c, 0xff, 0xff, 0x1e, 0x00, 0x0a, 0x02, 0x00, 0x0a, 0x24, + 0x12, 0x0b, 0x04, 0x0c, 0xff, 0xff, 0x1f, 0x00, 0x00, 0x00, 0x0a, 0x10, + 0x08, 0x5f, 0x43, 0x52, 0x53, 0x11, 0x48, 0x09, 0x0a, 0x94, 0x88, 0x0d, + 0x00, 0x02, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x88, 0x0d, 0x00, 0x01, 0x0c, 0x03, 0x00, 0x00, 0x00, 0x00, + 0xf7, 0x0c, 0x00, 0x00, 0xf8, 0x0c, 0x88, 0x0d, 0x00, 0x01, 0x0c, 0x03, + 0x00, 0x00, 0x00, 0x0d, 0xff, 0xff, 0x00, 0x00, 0x00, 0xf3, 0x87, 0x17, + 0x00, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, + 0xff, 0xff, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x87, 0x17, 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0xff, 0xff, 0xff, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x40, 0x8a, 0x2b, 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x79, 0x00 +}; +static const unsigned int acpi_dsdt_aml_len = 542; diff --git a/include/x86/linux_loader.h b/include/x86/linux_loader.h index 7ad0dda037..5b2ae3eb65 100644 --- a/include/x86/linux_loader.h +++ b/include/x86/linux_loader.h @@ -37,6 +37,7 @@ struct boot_e820_entry { enum e820_type { E820_TYPE_RAM = 1, E820_TYPE_RESERVED = 2, + E820_TYPE_ACPI = 3, }; struct setup_header { diff --git a/options.mk b/options.mk index 77c0af941c..2f854a9a82 100644 --- a/options.mk +++ b/options.mk @@ -51,6 +51,12 @@ endif WOLFBOOT_TEST_FILLER?=0 WOLFBOOT_TIME_TEST?=0 +# Generate a minimal ACPI table set (RSDP/XSDT/FADT/MADT/MCFG + DSDT) for the +# x86 FSP Linux payload, so the OS gets the interrupt model and PCI routing. +ifeq ($(ACPI),1) + CFLAGS+=-D"WOLFBOOT_ACPI" +endif + ifeq ($(USE_CLANG),1) ifeq ($(USE_GCC),1) $(error USE_CLANG=1 is incompatible with USE_GCC=1; set USE_GCC=0) diff --git a/src/x86/acpi.c b/src/x86/acpi.c new file mode 100644 index 0000000000..8fa4129694 --- /dev/null +++ b/src/x86/acpi.c @@ -0,0 +1,441 @@ +/* acpi.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * + * Minimal ACPI table generation for the x86 FSP (Tiger Lake) target. The FSP + * does silicon init only and returns no usable ACPI table set, so wolfBoot + * builds RSDP -> XSDT -> { FADT, MADT, MCFG, DSDT } in low reserved memory and + * points boot_params.acpi_rsdp_addr at the RSDP. The MADT interrupt model and + * the DSDT _PRT routing are the values measured on this board; see docs and + * acpi_dsdt.asl. Only the DSDT needs AML, embedded here as a precompiled blob. + */ + +#include +#include + +#include +#include + +/* Local APIC and IOAPIC as the MP table already encodes them (mptable.c). */ +#define ACPI_LAPIC_ADDR 0xFEE00000UL +#define ACPI_IOAPIC_ADDR 0xFEC00000UL +#define ACPI_IOAPIC_ID 0x02 +#define ACPI_CPU_COUNT 4 + +/* ECAM base and bus range, matching MCFG to config PCI_ECAM_BASE. */ +#ifndef PCI_ECAM_BASE +#define PCI_ECAM_BASE 0xC0000000UL +#endif +#define ACPI_PCI_END_BUS 0x17 + +/* MADT subtable types. */ +#define MADT_TYPE_LAPIC 0 +#define MADT_TYPE_IOAPIC 1 +#define MADT_TYPE_ISO 2 +#define MADT_TYPE_LAPIC_NMI 4 + +#define MADT_LAPIC_ENABLED 1u +#define MADT_FLAG_PCAT_COMPAT 1u + +/* ACPI Generic Address Structure space ids. */ +#define ACPI_GAS_SYSTEM_MEMORY 0 +#define ACPI_GAS_SYSTEM_IO 1 + +/* FADT: HW-reduced ACPI, a reset register (RESET_REG_SUP, or the OS ignores + * it) and the legacy-devices boot-architecture flag. */ +#define FADT_HW_REDUCED_ACPI (1u << 20) +#define FADT_RESET_REG_SUP (1u << 10) +#define ACPI_RESET_PORT 0xCF9 +/* CF9 cold reset: SYS_RST (bit 2) with full/CPU reset, as src/x86/common.c + * writes. A value with bit 2 clear does not trigger a reset. */ +#define ACPI_RESET_VAL 0x0E +#define ACPI_FADT_IAPC_BOOT 0x0001 + +struct acpi_header { + char signature[4]; + uint32_t length; + uint8_t revision; + uint8_t checksum; + char oem_id[6]; + char oem_table_id[8]; + uint32_t oem_revision; + char creator_id[4]; + uint32_t creator_revision; +} __attribute__((packed)); + +struct acpi_rsdp { + char signature[8]; /* "RSD PTR " */ + uint8_t checksum; /* over the first 20 bytes */ + char oem_id[6]; + uint8_t revision; /* 2 */ + uint32_t rsdt_address; /* 0 - XSDT is used */ + uint32_t length; /* full table length */ + uint64_t xsdt_address; + uint8_t ext_checksum; /* over the full length */ + uint8_t reserved[3]; +} __attribute__((packed)); + +struct acpi_gas { /* Generic Address Structure */ + uint8_t space_id; + uint8_t bit_width; + uint8_t bit_offset; + uint8_t access_size; + uint64_t address; +} __attribute__((packed)); + +struct acpi_madt { + struct acpi_header header; + uint32_t lapic_address; + uint32_t flags; +} __attribute__((packed)); + +struct madt_lapic { + uint8_t type; + uint8_t length; + uint8_t acpi_processor_id; + uint8_t apic_id; + uint32_t flags; +} __attribute__((packed)); + +struct madt_ioapic { + uint8_t type; + uint8_t length; + uint8_t ioapic_id; + uint8_t reserved; + uint32_t ioapic_address; + uint32_t gsi_base; +} __attribute__((packed)); + +struct madt_iso { + uint8_t type; + uint8_t length; + uint8_t bus; + uint8_t source; + uint32_t gsi; + uint16_t flags; +} __attribute__((packed)); + +struct madt_lapic_nmi { + uint8_t type; + uint8_t length; + uint8_t acpi_processor_id; + uint16_t flags; + uint8_t lint; +} __attribute__((packed)); + +struct acpi_mcfg_alloc { + uint64_t base_address; + uint16_t segment; + uint8_t start_bus; + uint8_t end_bus; + uint32_t reserved; +} __attribute__((packed)); + +struct acpi_mcfg { + struct acpi_header header; + uint64_t reserved; + struct acpi_mcfg_alloc alloc; +} __attribute__((packed)); + +/* HW-reduced ACPI has no FACS, so the FADT leaves firmware_ctrl zero. */ + +/* ACPI 6.x FADT (fixed layout to the hypervisor id, 276 bytes). */ +struct acpi_fadt { + struct acpi_header header; + uint32_t firmware_ctrl; + uint32_t dsdt; + uint8_t reserved0; + uint8_t preferred_pm_profile; + uint16_t sci_int; + uint32_t smi_cmd; + uint8_t acpi_enable; + uint8_t acpi_disable; + uint8_t s4bios_req; + uint8_t pstate_cnt; + uint32_t pm1a_evt_blk; + uint32_t pm1b_evt_blk; + uint32_t pm1a_cnt_blk; + uint32_t pm1b_cnt_blk; + uint32_t pm2_cnt_blk; + uint32_t pm_tmr_blk; + uint32_t gpe0_blk; + uint32_t gpe1_blk; + uint8_t pm1_evt_len; + uint8_t pm1_cnt_len; + uint8_t pm2_cnt_len; + uint8_t pm_tmr_len; + uint8_t gpe0_blk_len; + uint8_t gpe1_blk_len; + uint8_t gpe1_base; + uint8_t cst_cnt; + uint16_t p_lvl2_lat; + uint16_t p_lvl3_lat; + uint16_t flush_size; + uint16_t flush_stride; + uint8_t duty_offset; + uint8_t duty_width; + uint8_t day_alrm; + uint8_t mon_alrm; + uint8_t century; + uint16_t iapc_boot_arch; + uint8_t reserved1; + uint32_t flags; + struct acpi_gas reset_reg; + uint8_t reset_value; + uint16_t arm_boot_arch; + uint8_t fadt_minor_version; + uint64_t x_firmware_ctrl; + uint64_t x_dsdt; + struct acpi_gas x_pm1a_evt_blk; + struct acpi_gas x_pm1b_evt_blk; + struct acpi_gas x_pm1a_cnt_blk; + struct acpi_gas x_pm1b_cnt_blk; + struct acpi_gas x_pm2_cnt_blk; + struct acpi_gas x_pm_tmr_blk; + struct acpi_gas x_gpe0_blk; + struct acpi_gas x_gpe1_blk; + struct acpi_gas sleep_control_reg; + struct acpi_gas sleep_status_reg; + uint64_t hypervisor_vendor_id; +} __attribute__((packed)); + +/* acpi_processor_id -> Local APIC id: 4 cores with HT off (APIC ids + * 0/2/4/6). Listing absent threads stalls the kernel per missing AP. */ +static const uint8_t acpi_lapic_ids[ACPI_CPU_COUNT] = { + 0, 2, 4, 6 +}; + +static void acpi_set_checksum(struct acpi_header *h) +{ + uint8_t *bytes = (uint8_t *)h; + uint8_t sum = 0; + uint32_t i; + + h->checksum = 0; + for (i = 0; i < h->length; i++) { + sum = (uint8_t)(sum + bytes[i]); + } + h->checksum = (uint8_t)(0 - sum); +} + +static void acpi_fill_header(struct acpi_header *h, const char *sig, + uint32_t length, uint8_t revision) +{ + memset(h, 0, sizeof(*h)); + memcpy(h->signature, sig, 4); + h->length = length; + h->revision = revision; + memcpy(h->oem_id, "WOLFBT", 6); + memcpy(h->oem_table_id, "NAITGL ", 8); + h->oem_revision = 1; + memcpy(h->creator_id, "WBT ", 4); + h->creator_revision = 1; +} + +/* Round p up to a 16-byte boundary so each table starts aligned. */ +static uint8_t *acpi_align(uint8_t *p) +{ + uintptr_t v = (uintptr_t)p; + v = (v + 0xF) & ~(uintptr_t)0xF; + return (uint8_t *)v; +} + +static uint8_t *acpi_build_madt(uint8_t *p) +{ + struct acpi_madt *madt = (struct acpi_madt *)p; + uint8_t *e = p + sizeof(*madt); + struct madt_lapic *lapic; + struct madt_ioapic *ioapic; + struct madt_iso *iso; + struct madt_lapic_nmi *nmi; + uint32_t i; + + madt->lapic_address = (uint32_t)ACPI_LAPIC_ADDR; + madt->flags = MADT_FLAG_PCAT_COMPAT; + + for (i = 0; i < ACPI_CPU_COUNT; i++) { + lapic = (struct madt_lapic *)e; + memset(lapic, 0, sizeof(*lapic)); + lapic->type = MADT_TYPE_LAPIC; + lapic->length = sizeof(*lapic); + lapic->acpi_processor_id = (uint8_t)i; + lapic->apic_id = acpi_lapic_ids[i]; + lapic->flags = MADT_LAPIC_ENABLED; + e += sizeof(*lapic); + } + + ioapic = (struct madt_ioapic *)e; + memset(ioapic, 0, sizeof(*ioapic)); + ioapic->type = MADT_TYPE_IOAPIC; + ioapic->length = sizeof(*ioapic); + ioapic->ioapic_id = ACPI_IOAPIC_ID; + ioapic->ioapic_address = (uint32_t)ACPI_IOAPIC_ADDR; + ioapic->gsi_base = 0; + e += sizeof(*ioapic); + + /* ISA IRQ0 -> GSI2, polarity/trigger conform. */ + iso = (struct madt_iso *)e; + memset(iso, 0, sizeof(*iso)); + iso->type = MADT_TYPE_ISO; + iso->length = sizeof(*iso); + iso->bus = 0; + iso->source = 0; + iso->gsi = 2; + iso->flags = 0; + e += sizeof(*iso); + + /* SCI IRQ9 -> GSI9, active-high level. */ + iso = (struct madt_iso *)e; + memset(iso, 0, sizeof(*iso)); + iso->type = MADT_TYPE_ISO; + iso->length = sizeof(*iso); + iso->bus = 0; + iso->source = 9; + iso->gsi = 9; + iso->flags = 0x000D; /* active high (01) | level (11 << 2) */ + e += sizeof(*iso); + + /* Local APIC NMI on LINT1 for all processors, active-high edge. */ + nmi = (struct madt_lapic_nmi *)e; + memset(nmi, 0, sizeof(*nmi)); + nmi->type = MADT_TYPE_LAPIC_NMI; + nmi->length = sizeof(*nmi); + nmi->acpi_processor_id = 0xFF; + nmi->flags = 0x0005; /* active high (01) | edge (01 << 2) */ + nmi->lint = 1; + e += sizeof(*nmi); + + acpi_fill_header(&madt->header, "APIC", (uint32_t)(e - p), 4); + acpi_set_checksum(&madt->header); + return e; +} + +static uint8_t *acpi_build_mcfg(uint8_t *p) +{ + struct acpi_mcfg *mcfg = (struct acpi_mcfg *)p; + + memset(mcfg, 0, sizeof(*mcfg)); + mcfg->alloc.base_address = (uint64_t)PCI_ECAM_BASE; + mcfg->alloc.segment = 0; + mcfg->alloc.start_bus = 0; + mcfg->alloc.end_bus = ACPI_PCI_END_BUS; + acpi_fill_header(&mcfg->header, "MCFG", sizeof(*mcfg), 1); + acpi_set_checksum(&mcfg->header); + return p + sizeof(*mcfg); +} + +static void acpi_gas_io(struct acpi_gas *g, uint8_t width, uint64_t addr) +{ + g->space_id = ACPI_GAS_SYSTEM_IO; + g->bit_width = width; + g->bit_offset = 0; + g->access_size = 0; + g->address = addr; +} + +static uint8_t *acpi_build_fadt(uint8_t *p, uint64_t facs_addr, + uint64_t dsdt_addr) +{ + struct acpi_fadt *fadt = (struct acpi_fadt *)p; + + (void)facs_addr; /* HW-reduced ACPI has no FACS */ + + /* HW-reduced: there is no SMM handler for the legacy ACPI enable, so + * only the DSDT and a reset register are provided (no S5 soft-off). */ + memset(fadt, 0, sizeof(*fadt)); + fadt->dsdt = (uint32_t)dsdt_addr; + fadt->x_dsdt = dsdt_addr; + fadt->preferred_pm_profile = 2; /* Mobile */ + fadt->iapc_boot_arch = ACPI_FADT_IAPC_BOOT; + fadt->flags = FADT_HW_REDUCED_ACPI | FADT_RESET_REG_SUP; + acpi_gas_io(&fadt->reset_reg, 8, ACPI_RESET_PORT); + fadt->reset_value = ACPI_RESET_VAL; + fadt->fadt_minor_version = 3; /* ACPI 6.3 */ + acpi_fill_header(&fadt->header, "FACP", sizeof(*fadt), 6); + acpi_set_checksum(&fadt->header); + return p + sizeof(*fadt); +} + +uint64_t acpi_setup(void) +{ + uint8_t *base = (uint8_t *)(uintptr_t)ACPI_TABLE_BASE; + uint8_t *p = base; + uint8_t *dsdt; + uint8_t *fadt; + uint8_t *madt; + uint8_t *mcfg; + struct acpi_header *xsdt; + struct acpi_rsdp *rsdp; + uint64_t *xsdt_entries; + uint8_t *bytes; + uint8_t sum; + uint32_t i; + + /* DSDT first, so the FADT can point at its address. */ + dsdt = p; + memcpy(dsdt, acpi_dsdt_aml, acpi_dsdt_aml_len); + p = acpi_align(dsdt + acpi_dsdt_aml_len); + + fadt = p; + p = acpi_align(acpi_build_fadt(p, 0, (uint64_t)(uintptr_t)dsdt)); + + madt = p; + p = acpi_align(acpi_build_madt(p)); + + mcfg = p; + p = acpi_align(acpi_build_mcfg(p)); + + /* XSDT lists the top-level tables; the DSDT is reached via the FADT. */ + xsdt = (struct acpi_header *)p; + xsdt_entries = (uint64_t *)((uint8_t *)xsdt + sizeof(*xsdt)); + xsdt_entries[0] = (uint64_t)(uintptr_t)fadt; + xsdt_entries[1] = (uint64_t)(uintptr_t)madt; + xsdt_entries[2] = (uint64_t)(uintptr_t)mcfg; + acpi_fill_header(xsdt, "XSDT", + (uint32_t)(sizeof(*xsdt) + 3 * sizeof(uint64_t)), 1); + acpi_set_checksum(xsdt); + p = acpi_align((uint8_t *)xsdt + xsdt->length); + + rsdp = (struct acpi_rsdp *)p; + memset(rsdp, 0, sizeof(*rsdp)); + memcpy(rsdp->signature, "RSD PTR ", 8); + memcpy(rsdp->oem_id, "WOLFBT", 6); + rsdp->revision = 2; + rsdp->rsdt_address = 0; + rsdp->length = sizeof(*rsdp); + rsdp->xsdt_address = (uint64_t)(uintptr_t)xsdt; + + /* v1 checksum over the first 20 bytes. */ + bytes = (uint8_t *)rsdp; + sum = 0; + for (i = 0; i < 20; i++) { + sum = (uint8_t)(sum + bytes[i]); + } + rsdp->checksum = (uint8_t)(0 - sum); + + /* Extended checksum over the whole structure. */ + sum = 0; + for (i = 0; i < sizeof(*rsdp); i++) { + sum = (uint8_t)(sum + bytes[i]); + } + rsdp->ext_checksum = (uint8_t)(0 - sum); + + return (uint64_t)(uintptr_t)rsdp; +} diff --git a/src/x86/acpi_dsdt.asl b/src/x86/acpi_dsdt.asl new file mode 100644 index 0000000000..0a9b66f121 --- /dev/null +++ b/src/x86/acpi_dsdt.asl @@ -0,0 +1,89 @@ +/* acpi_dsdt.asl + * + * Minimal DSDT for the NAI 68INT6: the PCI root bus with its _CRS and the + * _PRT INTx routing. Compiled offline with iasl and committed as a C array. + * + * Regenerate after editing: + * iasl src/x86/acpi_dsdt.asl + * xxd -i -n acpi_dsdt_aml src/x86/acpi_dsdt.aml > include/x86/acpi_dsdt.h + * (then hand-add the wolfSSL GPL header to acpi_dsdt.h) + * + * _PRT (pin 0=INTA..3=INTD -> GSI) is the FSP's default PCH routing, as + * measured on the stock BIOS. _CRS covers wolfBoot's PCI windows and buses. + */ +DefinitionBlock ("", "DSDT", 2, "WOLFBT", "NAITGL", 0x00000001) +{ + Scope (\_SB) + { + Device (PCI0) + { + Name (_HID, EisaId ("PNP0A08")) /* PCIe host bridge */ + Name (_CID, EisaId ("PNP0A03")) /* PCI host bridge compat */ + Name (_SEG, Zero) + Name (_BBN, Zero) + Name (_UID, Zero) + + Method (_PRT, 0, NotSerialized) + { + Return (Package () + { + Package () { 0x0002FFFF, 0, Zero, 16 }, /* iGPU INTA */ + Package () { 0x0004FFFF, 0, Zero, 16 }, /* DTT INTA */ + Package () { 0x000DFFFF, 0, Zero, 16 }, /* TBT INTA */ + Package () { 0x0014FFFF, 0, Zero, 16 }, /* xHCI INTA */ + Package () { 0x0014FFFF, 1, Zero, 17 }, /* xHCI INTB */ + Package () { 0x0015FFFF, 0, Zero, 27 }, /* I2C0 INTA */ + Package () { 0x0015FFFF, 1, Zero, 40 }, /* I2C1 INTB */ + Package () { 0x0016FFFF, 0, Zero, 16 }, /* HECI INTA */ + Package () { 0x0017FFFF, 0, Zero, 16 }, /* SATA INTA */ + Package () { 0x0019FFFF, 0, Zero, 31 }, /* I2C4 INTA */ + Package () { 0x0019FFFF, 2, Zero, 33 }, /* UART2 INTC */ + Package () { 0x001CFFFF, 0, Zero, 16 }, /* RP1 INTA */ + Package () { 0x001CFFFF, 1, Zero, 17 }, /* RP INTB */ + Package () { 0x001CFFFF, 2, Zero, 18 }, /* RP INTC */ + Package () { 0x001CFFFF, 3, Zero, 19 }, /* RP INTD */ + Package () { 0x001DFFFF, 0, Zero, 16 }, /* RP9 INTA */ + Package () { 0x001DFFFF, 1, Zero, 17 }, /* RP10 INTB */ + Package () { 0x001DFFFF, 2, Zero, 18 }, /* RP INTC */ + Package () { 0x001DFFFF, 3, Zero, 19 }, /* RP INTD */ + Package () { 0x001EFFFF, 0, Zero, 16 }, /* UART0 INTA */ + Package () { 0x001EFFFF, 1, Zero, 17 }, /* UART1 INTB */ + Package () { 0x001EFFFF, 2, Zero, 36 }, /* GSPI INTC */ + Package () { 0x001FFFFF, 0, Zero, 16 }, /* PCH INTA */ + }) + } + + Name (_CRS, ResourceTemplate () + { + /* Bus range covered by the MCFG (bus 0..0x17). */ + WordBusNumber (ResourceProducer, MinFixed, MaxFixed, PosDecode, + 0x0000, 0x0000, 0x0017, 0x0000, 0x0018) + + /* Legacy IO below the PCI config ports, then the rest of IO + * space; wolfBoot assigns device IO from PCI_IO32_BASE. */ + WordIO (ResourceProducer, MinFixed, MaxFixed, PosDecode, EntireRange, + 0x0000, 0x0000, 0x0CF7, 0x0000, 0x0CF8) + WordIO (ResourceProducer, MinFixed, MaxFixed, PosDecode, EntireRange, + 0x0000, 0x0D00, 0xFFFF, 0x0000, 0xF300) + + /* Low 1 MB (VGA/legacy). */ + DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, + Cacheable, ReadWrite, + 0x00000000, 0x000A0000, 0x000BFFFF, 0x00000000, 0x00020000) + + /* 32-bit device MMIO window: PCI_MMIO32_BASE (0x80000000) up + * to just below the ECAM region at 0xC0000000. */ + DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, + NonCacheable, ReadWrite, + 0x00000000, 0x80000000, 0xBFFFFFFF, 0x00000000, 0x40000000) + + /* 64-bit window (as stock) so the kernel can move the LPSS BARs + * out of the FSP-reserved range; UART2 then binds as ttyS4. */ + QWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, + NonCacheable, ReadWrite, + 0x0000000000000000, 0x0000004000000000, 0x0000007FFFFFFFFF, + 0x0000000000000000, 0x0000004000000000) + }) + } + } +} diff --git a/src/x86/linux_loader.c b/src/x86/linux_loader.c index 02c177529c..1fcf94a400 100644 --- a/src/x86/linux_loader.c +++ b/src/x86/linux_loader.c @@ -36,6 +36,10 @@ #include #endif /* WOLFBOOT_FSP */ +#ifdef WOLFBOOT_ACPI +#include +#endif + #define ENDLINE "\r\n" /* Optional container so one signed image can carry a kernel plus an initrd: @@ -199,8 +203,8 @@ static int ranges_overlap(uint64_t a, uint64_t alen, uint64_t b, uint64_t blen) } /* Reject a kernel/initrd load target that would land on wolfBoot's own image - * (code, rodata, data, bss) or on the verified payload being copied from. - * Returns nonzero on conflict. */ + * (code, rodata, data, bss), on the verified payload being copied from, or on + * the generated ACPI tables. Returns nonzero on conflict. */ static int linux_load_conflicts(uint64_t dst, uint64_t len, const uint8_t *payload, uint32_t payload_len) { @@ -212,6 +216,13 @@ static int linux_load_conflicts(uint64_t dst, uint64_t len, if (ranges_overlap(dst, len, (uint64_t)(uintptr_t)payload, (uint64_t)payload_len)) return 1; +#ifdef WOLFBOOT_ACPI + /* Keep the kernel copy and initrd off the ACPI tables (the e820 + * reservation only steers the kernel's allocator). */ + if (ranges_overlap(dst, len, (uint64_t)ACPI_TABLE_BASE, + (uint64_t)ACPI_TABLE_SIZE)) + return 1; +#endif return 0; } @@ -230,6 +241,9 @@ void load_linux(uint8_t *linux_image, uint32_t image_size, void *params, int ret; #if defined(WOLFBOOT_64BIT) uint32_t map_size; +#endif +#ifdef WOLFBOOT_ACPI + uint64_t acpi_rsdp; #endif (void)cmd_line; @@ -239,7 +253,7 @@ void load_linux(uint8_t *linux_image, uint32_t image_size, void *params, kernel_image = linux_image; kernel_avail = image_size; phdr = (struct linux_payload_hdr *)linux_image; - if (phdr->magic == LINUX_PAYLOAD_MAGIC) { + if (image_size >= sizeof(*phdr) && phdr->magic == LINUX_PAYLOAD_MAGIC) { /* Bound the trusted header fields against the verified image so a * malformed container cannot make the kernel or initrd reads run past * it (or wrap the pointer arithmetic). Sum in 64-bit. */ @@ -284,6 +298,43 @@ void load_linux(uint8_t *linux_image, uint32_t image_size, void *params, wolfBoot_panic(); } +#ifdef WOLFBOOT_ACPI + /* Fail fast if the fixed ACPI base is above usable RAM or overlaps the + * verified payload. */ +#ifdef WOLFBOOT_FSP + { + uint64_t acpi_top = (uint64_t)ACPI_TABLE_BASE + (uint64_t)ACPI_TABLE_SIZE; + uint64_t tolum = (uint64_t)((struct stage2_parameter *)params)->tolum; + if (tolum != 0 && acpi_top > tolum) { + wolfBoot_printf("ACPI region above usable RAM" ENDLINE); + wolfBoot_panic(); + } + } +#endif + if (ranges_overlap((uint64_t)ACPI_TABLE_BASE, (uint64_t)ACPI_TABLE_SIZE, + (uint64_t)(uintptr_t)linux_image, (uint64_t)image_size)) { + wolfBoot_printf("ACPI region overlaps payload" ENDLINE); + wolfBoot_panic(); + } + /* Build the ACPI tables, point the kernel at the RSDP, and reserve the + * region in the e820 map so the kernel keeps its allocator off it. */ +#if defined(WOLFBOOT_64BIT) + x86_paging_map_memory(ACPI_TABLE_BASE, ACPI_TABLE_BASE, ACPI_TABLE_SIZE); +#endif + acpi_rsdp = acpi_setup(); + param.acpi_rsdp_addr = acpi_rsdp; + /* Without the e820 reservation the kernel would treat the tables as free + * RAM, so a full map is fatal. */ + if (param.e820_entries >= E820_MAX_ENTRIES_ZEROPAGE) { + wolfBoot_printf("no e820 slot to reserve ACPI tables" ENDLINE); + wolfBoot_panic(); + } + param.e820_table[param.e820_entries].addr = ACPI_TABLE_BASE; + param.e820_table[param.e820_entries].size = ACPI_TABLE_SIZE; + param.e820_table[param.e820_entries].type = E820_TYPE_ACPI; + param.e820_entries++; +#endif /* WOLFBOOT_ACPI */ + if (param.hdr.setup_sects != 0) { param_size = (param.hdr.setup_sects + 1) * 512; } else { @@ -324,6 +375,14 @@ void load_linux(uint8_t *linux_image, uint32_t image_size, void *params, wolfBoot_printf("kernel decompress extent exceeds usable RAM" ENDLINE); wolfBoot_panic(); } +#ifdef WOLFBOOT_ACPI + if (ranges_overlap((uint64_t)param.hdr.pref_address, + (uint64_t)param.hdr.init_size, + (uint64_t)ACPI_TABLE_BASE, (uint64_t)ACPI_TABLE_SIZE)) { + wolfBoot_printf("kernel decompress window overlaps ACPI tables" ENDLINE); + wolfBoot_panic(); + } +#endif memcpy((uint8_t *)KERNEL_LOAD_ADDRESS, kernel_image + param_size, kernel_size); From aa6fdb8fa94b12fa1eda280284012c288d200d0a Mon Sep 17 00:00:00 2001 From: David Garske Date: Wed, 7 Oct 2026 13:47:32 -0700 Subject: [PATCH 2/3] x86 FSP: add TPM-NV anti-rollback with a persistent version floor and boot audit counters --- .../test-build-kontron-vx3060-s2.yml | 4 - .github/workflows/test-x86-fsp-qemu.yml | 27 +- .gitignore | 2 + arch.mk | 4 + .../examples/x86_fsp_qemu_antirollback.config | 45 +++ include/boot_state.h | 85 +++++ include/tpm.h | 3 + options.mk | 14 + src/boot_state.c | 264 ++++++++++++++++ src/boot_state_tpm.c | 156 +++++++++ src/tpm.c | 11 +- src/update_disk.c | 33 ++ tools/scripts/x86_fsp/qemu/qemu.sh | 2 +- tools/scripts/x86_fsp/qemu/qemu_build_fsp.sh | 9 +- tools/scripts/x86_fsp/qemu/test_qemu.sh | 71 ++++- tools/scripts/x86_fsp/tgl/tgl_download_fsp.sh | 4 +- tools/unit-tests/Makefile | 22 +- tools/unit-tests/stubs-boot-state/loader.h | 27 ++ tools/unit-tests/stubs-boot-state/printf.h | 27 ++ .../stubs-boot-state/user_settings.h | 26 ++ tools/unit-tests/unit-boot-state.c | 296 ++++++++++++++++++ 21 files changed, 1102 insertions(+), 30 deletions(-) create mode 100644 config/examples/x86_fsp_qemu_antirollback.config create mode 100644 include/boot_state.h create mode 100644 src/boot_state.c create mode 100644 src/boot_state_tpm.c create mode 100644 tools/unit-tests/stubs-boot-state/loader.h create mode 100644 tools/unit-tests/stubs-boot-state/printf.h create mode 100644 tools/unit-tests/stubs-boot-state/user_settings.h create mode 100644 tools/unit-tests/unit-boot-state.c diff --git a/.github/workflows/test-build-kontron-vx3060-s2.yml b/.github/workflows/test-build-kontron-vx3060-s2.yml index a4547e696a..3eff7df973 100644 --- a/.github/workflows/test-build-kontron-vx3060-s2.yml +++ b/.github/workflows/test-build-kontron-vx3060-s2.yml @@ -46,10 +46,6 @@ jobs: export DEBIAN_FRONTEND=noninteractive sudo apt-get update -o Acquire::Retries=3 sudo apt-get install --no-install-recommends -y -q -o Acquire::Retries=3 nasm gcc-multilib - - name: setup git - run: | - git config --global user.email "you@example.com" - git config --global user.name "Your Name" - name: run test run: | cp config/examples/kontron_vx3060_s2.config .config diff --git a/.github/workflows/test-x86-fsp-qemu.yml b/.github/workflows/test-x86-fsp-qemu.yml index 543ea82f88..9691dca4ba 100644 --- a/.github/workflows/test-x86-fsp-qemu.yml +++ b/.github/workflows/test-x86-fsp-qemu.yml @@ -46,10 +46,6 @@ jobs: export DEBIAN_FRONTEND=noninteractive sudo apt-get update -o Acquire::Retries=3 sudo apt-get install --no-install-recommends -y -q -o Acquire::Retries=3 nasm gcc-multilib qemu-system-x86 swtpm uuid-dev - - name: setup git - run: | - git config --global user.email "you@example.com" - git config --global user.name "Your Name" - name: run test run: | ./tools/scripts/x86_fsp/qemu/test_qemu.sh @@ -70,10 +66,25 @@ jobs: export DEBIAN_FRONTEND=noninteractive sudo apt-get update -o Acquire::Retries=3 sudo apt-get install --no-install-recommends -y -q -o Acquire::Retries=3 nasm gcc-multilib qemu-system-x86 uuid-dev dosfstools mtools e2fsprogs - - name: setup git - run: | - git config --global user.email "you@example.com" - git config --global user.name "Your Name" - name: run test run: | ./tools/scripts/x86_fsp/qemu/test_qemu.sh -f + + # TPM-NV anti-rollback: the floor anchors on a fresh swtpm, survives a TPM + # restart, and a validly signed downgrade boots and is counted. + fsp_qemu_antirollback_test: + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: install req + run: | + export DEBIAN_FRONTEND=noninteractive + sudo apt-get update -o Acquire::Retries=3 + sudo apt-get install --no-install-recommends -y -q -o Acquire::Retries=3 nasm gcc-multilib qemu-system-x86 swtpm uuid-dev + - name: run test + run: | + ./tools/scripts/x86_fsp/qemu/test_qemu.sh -r diff --git a/.gitignore b/.gitignore index c909ebd02f..9fa8b7a715 100644 --- a/.gitignore +++ b/.gitignore @@ -181,6 +181,8 @@ tools/unit-tests/unit-update-flash-enc tools/unit-tests/unit-update-flash-db tools/unit-tests/unit-update-flash-enc-full tools/unit-tests/unit-update-ram +tools/unit-tests/unit-boot-state +tools/unit-tests/unit-boot-state-halt tools/unit-tests/unit-boot-x86-fsp tools/unit-tests/unit-image-rsa tools/unit-tests/unit-multiboot diff --git a/arch.mk b/arch.mk index c5fc2a8344..84eac55ddc 100644 --- a/arch.mk +++ b/arch.mk @@ -2312,6 +2312,10 @@ ifeq ("${FSP}", "1") OBJS += src/x86/fsp_s.o UPDATE_OBJS := src/update_disk.o CFLAGS+=-DWOLFBOOT_UPDATE_DISK + ifeq ($(ANTI_ROLLBACK),1) + OBJS += src/boot_state.o + OBJS += src/boot_state_tpm.o + endif ifeq ($(64BIT),1) LDFLAGS += -m elf_x86_64 --oformat elf64-x86-64 CFLAGS += -m64 diff --git a/config/examples/x86_fsp_qemu_antirollback.config b/config/examples/x86_fsp_qemu_antirollback.config new file mode 100644 index 0000000000..e246cf6527 --- /dev/null +++ b/config/examples/x86_fsp_qemu_antirollback.config @@ -0,0 +1,45 @@ +ARCH=x86_64 +TARGET=x86_fsp_qemu +WOLFBOOT_SMALL_STACK=1 +SIGN=ECC256 +HASH=SHA256 +DEBUG=1 +SPMATH=1 +FORCE_32BIT=1 +ENCRYPTION=0 +WOLFBOOT_NO_PARTITIONS=1 + +# TPM Keystore options +#WOLFBOOT_TPM_KEYSTORE?=1 +#WOLFBOOT_TPM_KEYSTORE_NV_BASE?=0x01800200 +#WOLFBOOT_TPM_POLICY_NV_INDEX?=0x01800201 + +# 4gb - 8mb +WOLFBOOT_LOAD_BASE=0x2000000 +WOLFBOOT_LOAD_ADDRESS=0x1000000 + +# required for keytools +WOLFBOOT_SECTOR_SIZE?=0x1000 +WOLFBOOT_DATA_ADDRESS=0x1000000 + +FSP_M_BASE=0xffe30000 +FSP_S_BASE=0xffed6000 +FSP_T_BASE=0xfffe0000 +FSP_S_LOAD_BASE=0x0FED5F00 +WOLFBOOT_ORIGIN=0xfff80000 + +BOOTLOADER_PARTITION_SIZE=0xe0000 +BIOS_REGION_SIZE=0x800000 +MACHINE_OBJ=src/x86/qemu_fsp.o +FSP_T_BIN=./src/x86/fsp_t.bin +FSP_M_BIN=./src/x86/fsp_m.bin +FSP_S_BIN=./src/x86/fsp_s.bin +STAGE1_AUTH=1 +64BIT=1 +ELF=1 +MULTIBOOT2=1 + +# TPM-NV anti-rollback (version floor and audit counters), tested by +# tools/scripts/x86_fsp/qemu/test_qemu.sh -r +ANTI_ROLLBACK=1 +ANTI_ROLLBACK_HALT_ON_FAILURE=1 diff --git a/include/boot_state.h b/include/boot_state.h new file mode 100644 index 0000000000..593e76180d --- /dev/null +++ b/include/boot_state.h @@ -0,0 +1,85 @@ +/* boot_state.h + * + * Persistent anti-rollback reference and boot audit counters. + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Anti-rollback state: anchor = highest version ever booted (trust on first + * use), record = last booted. See the Anti-rollback section of Targets.md. */ + +#ifndef BOOT_STATE_H +#define BOOT_STATE_H + +#include + +#define BOOT_STATE_MAGIC 0x42535431UL /* "BST1" */ +#define BOOT_STATE_VERSION 1 + +/* Result of loading the persisted state. */ +#define BOOT_STATE_OK 0 /* loaded and trustworthy */ +#define BOOT_STATE_UNPROVISIONED 1 /* nothing stored yet; not an error */ +#define BOOT_STATE_CORRUPT (-1) /* stored but unusable -- fatal */ +#define BOOT_STATE_UNAVAILABLE (-2) /* backend could not be reached */ + +/* Failure codes recorded in last_fail_code. */ +#define BOOT_FAIL_NONE 0 +#define BOOT_FAIL_NO_IMAGE 1 +#define BOOT_FAIL_VERIFY_PRIMARY 2 +#define BOOT_FAIL_VERIFY_BOTH 3 +#define BOOT_FAIL_ROLLBACK 4 + +/* On-storage layout. Packed and explicitly sized so the representation does + * not shift between the bootloader and any provisioning tool. */ +struct boot_state { + uint32_t magic; + uint32_t anchor; + uint32_t record; + uint32_t rollback_count; + uint32_t last_rollback_from; + uint32_t last_rollback_to; + uint32_t boot_fail_count; + uint32_t last_fail_code; + uint16_t struct_version; + uint16_t reserved; + uint32_t crc; +} __attribute__((packed)); + +/* Backend, implemented per platform. v1 uses TPM NV (src/boot_state_tpm.c). */ +int boot_state_backend_read(uint8_t *buf, uint32_t len); +int boot_state_backend_write(const uint8_t *buf, uint32_t len); +/* Write-locks the record until the next TPM reset, so the booted OS cannot + * rewrite it. */ +int boot_state_backend_lock(void); + +/* Load the persisted state. Returns one of the BOOT_STATE_* codes above. */ +int boot_state_load(struct boot_state *st); + +/* Called once an image has verified, immediately before handing off. + * Applies the rollback check, updates anchor and record, and persists. */ +void boot_state_on_boot(uint32_t booted_version); +/* Lock the stored state against the OS without recording a boot (golden + * slot). Halts on failure with ANTI_ROLLBACK_HALT_ON_FAILURE. */ +void boot_state_lock(void); + +/* Called when the boot policy has decided no image may be booted. Records the + * failure so the evidence survives the power cycle. */ +void boot_state_on_failure(uint32_t fail_code); + +#endif /* BOOT_STATE_H */ diff --git a/include/tpm.h b/include/tpm.h index 96847a5013..7b3a2f2e52 100644 --- a/include/tpm.h +++ b/include/tpm.h @@ -40,6 +40,9 @@ extern WOLFTPM2_KEY wolftpm_srk; #ifndef WOLFBOOT_TPM_SEAL_NV_BASE #define WOLFBOOT_TPM_SEAL_NV_BASE 0x01400300 #endif +#ifndef WOLFBOOT_TPM_BOOT_STATE_NV_BASE + #define WOLFBOOT_TPM_BOOT_STATE_NV_BASE 0x01400400 +#endif /* The PCR bank algorithm and its digest size must always agree. They used to * be defined together under a single #ifndef on the algorithm, which meant * overriding only WOLFBOOT_TPM_PCR_ALG left WOLFBOOT_TPM_PCR_DIG_SZ undefined diff --git a/options.mk b/options.mk index 2f854a9a82..350620f243 100644 --- a/options.mk +++ b/options.mk @@ -57,6 +57,20 @@ ifeq ($(ACPI),1) CFLAGS+=-D"WOLFBOOT_ACPI" endif +# TPM NV version floor and boot audit counters (see docs/Targets.md). +# HALT_ON_FAILURE also halts when the TPM state cannot be read or locked. +ifeq ($(ANTI_ROLLBACK),1) + ifeq ($(filter $(TARGET),x86_fsp_qemu kontron_vx3060_s2 nai_68int6),) + $(error ANTI_ROLLBACK=1 is only wired into the x86 FSP disk boot) + endif + CFLAGS+=-D"WOLFBOOT_ANTI_ROLLBACK" + # The backend is TPM NV (src/boot_state_tpm.c), so wolfTPM is required. + WOLFTPM:=1 + ifeq ($(ANTI_ROLLBACK_HALT_ON_FAILURE),1) + CFLAGS+=-D"WOLFBOOT_ANTI_ROLLBACK_HALT_ON_FAILURE" + endif +endif + ifeq ($(USE_CLANG),1) ifeq ($(USE_GCC),1) $(error USE_CLANG=1 is incompatible with USE_GCC=1; set USE_GCC=0) diff --git a/src/boot_state.c b/src/boot_state.c new file mode 100644 index 0000000000..9c44dafbea --- /dev/null +++ b/src/boot_state.c @@ -0,0 +1,264 @@ +/* boot_state.c + * + * Persistent anti-rollback reference and boot audit counters. + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include "user_settings.h" +#include "boot_state.h" +#include "printf.h" +#include "loader.h" +#include + +#ifdef WOLFBOOT_ANTI_ROLLBACK + +#define CRC32_POLY_REVERSED 0xEDB88320UL +#define CRC32_INIT 0xFFFFFFFFUL +#define CRC32_BITS_PER_BYTE 8 + +/* Bitwise CRC-32 (IEEE 802.3). Table-free: the record is 40 bytes and this + * runs once or twice per boot, so the table is not worth the footprint. */ +static uint32_t boot_state_crc32(const uint8_t *buf, uint32_t len) +{ + uint32_t crc = CRC32_INIT; + uint32_t i; + int bit; + + for (i = 0; i < len; i++) { + crc ^= (uint32_t)buf[i]; + for (bit = 0; bit < CRC32_BITS_PER_BYTE; bit++) { + if (crc & 1U) { + crc = (crc >> 1) ^ CRC32_POLY_REVERSED; + } + else { + crc >>= 1; + } + } + } + return crc ^ CRC32_INIT; +} + +/* The CRC covers every byte of the record except the trailing crc field. */ +static uint32_t boot_state_compute_crc(const struct boot_state *st) +{ + return boot_state_crc32((const uint8_t *)st, + (uint32_t)(sizeof(struct boot_state) + - sizeof(st->crc))); +} + +static void boot_state_log(const char *msg) +{ + wolfBoot_printf("%s\r\n", msg); +} + +static void boot_state_log_val(const char *msg, uint32_t value) +{ + wolfBoot_printf("%s%u\r\n", msg, value); +} + +int boot_state_load(struct boot_state *st) +{ + int ret; + + if (st == NULL) { + return BOOT_STATE_UNAVAILABLE; + } + + memset(st, 0, sizeof(struct boot_state)); + + ret = boot_state_backend_read((uint8_t *)st, sizeof(struct boot_state)); + if (ret == BOOT_STATE_UNPROVISIONED) { + return BOOT_STATE_UNPROVISIONED; + } + /* A present but malformed record cannot be trusted: corrupt, which halts, + * not unavailable. */ + if (ret == BOOT_STATE_CORRUPT) { + return BOOT_STATE_CORRUPT; + } + if (ret != 0) { + return BOOT_STATE_UNAVAILABLE; + } + + /* Storage exists. From here on anything unexpected means the reference + * cannot be trusted, which is the one condition that must stop the boot. */ + if (st->magic != BOOT_STATE_MAGIC) { + return BOOT_STATE_CORRUPT; + } + if (st->struct_version != BOOT_STATE_VERSION) { + return BOOT_STATE_CORRUPT; + } + if (st->crc != boot_state_compute_crc(st)) { + return BOOT_STATE_CORRUPT; + } + return BOOT_STATE_OK; +} + +/* Persist, refreshing the CRC first. Failures are logged but not fatal: losing + * an audit write must not turn a good image into an unbootable unit. */ +static void boot_state_save(struct boot_state *st) +{ + st->magic = BOOT_STATE_MAGIC; + st->struct_version = BOOT_STATE_VERSION; + st->reserved = 0; + st->crc = boot_state_compute_crc(st); + + if (boot_state_backend_write((const uint8_t *)st, + sizeof(struct boot_state)) != 0) { +#if defined(WOLFBOOT_ANTI_ROLLBACK_HALT_ON_FAILURE) + boot_state_log("could not persist anti-rollback state, refusing to boot"); + /* Never returns. */ + wolfBoot_panic(); +#else + boot_state_log("WARNING: could not persist boot state"); +#endif + } +} + +/* Load for update. A corrupt reference halts here, which is the whole point + * of the check. Unprovisioned is a clean slate, not a fault. */ +static int boot_state_load_for_update(struct boot_state *st) +{ + int status = boot_state_load(st); + + if (status == BOOT_STATE_CORRUPT) { + boot_state_log("Version reference is corrupt, refusing to boot"); + /* Never returns. */ + wolfBoot_panic(); + } + if (status == BOOT_STATE_UNAVAILABLE) { +#if defined(WOLFBOOT_ANTI_ROLLBACK_HALT_ON_FAILURE) + boot_state_log("anti-rollback state unavailable, refusing to boot"); + /* Never returns. */ + wolfBoot_panic(); +#else + boot_state_log("WARNING: boot state backend unavailable"); +#endif + } + return status; +} + +static int boot_state_update(uint32_t booted_version) +{ + struct boot_state st; + struct boot_state before; + int status; + + status = boot_state_load_for_update(&st); + if (status == BOOT_STATE_UNAVAILABLE) { + return status; + } + + if (status == BOOT_STATE_UNPROVISIONED) { + /* Trust on first use: this image defines the floor. */ + memset(&st, 0, sizeof(st)); + st.anchor = booted_version; + st.record = booted_version; + boot_state_log_val("Anti-rollback anchor initialised at version ", + booted_version); + boot_state_save(&st); + return status; + } + + /* Never persist over anything but a trustworthy record (a corrupt one + * already halted above). */ + if (status != BOOT_STATE_OK) { + return status; + } + + /* Snapshot so the write can be skipped when nothing actually changes. */ + memcpy(&before, &st, sizeof(before)); + + if (booted_version < st.anchor) { + /* Explicitly allowed: an older but validly signed image boots. The + * audit event is the deliverable, not a veto. */ + st.rollback_count++; + st.last_rollback_from = st.anchor; + st.last_rollback_to = booted_version; + boot_state_log_val("ROLLBACK: booting version ", booted_version); + boot_state_log_val("ROLLBACK: highest version previously booted was ", + st.anchor); + boot_state_log_val("ROLLBACK: total rollbacks recorded ", + st.rollback_count); + } + else if (booted_version > st.anchor) { + st.anchor = booted_version; + } + + st.record = booted_version; + + /* Skip the NV write when nothing changed (the CRC is recomputed on + * save). */ + if (memcmp(&before, &st, sizeof(st) - sizeof(st.crc)) == 0) { + return status; + } + + boot_state_save(&st); + return status; +} + +void boot_state_on_boot(uint32_t booted_version) +{ + (void)boot_state_update(booted_version); + boot_state_lock(); +} + +void boot_state_lock(void) +{ + /* On every boot, even with nothing written or the state unreadable, so + * the OS can never move the floor. */ + if (boot_state_backend_lock() != 0) { +#if defined(WOLFBOOT_ANTI_ROLLBACK_HALT_ON_FAILURE) + boot_state_log("could not lock anti-rollback state, refusing to boot"); + /* Never returns. */ + wolfBoot_panic(); +#else + boot_state_log("WARNING: could not lock boot state"); +#endif + } +} + +void boot_state_on_failure(uint32_t fail_code) +{ + struct boot_state st; + int status; + + status = boot_state_load(&st); + if (status == BOOT_STATE_UNAVAILABLE) { + return; + } + if (status == BOOT_STATE_CORRUPT) { + /* Do not persist: a fresh record over a corrupt one would reset the + * floor to zero on the next boot. */ + boot_state_log("Boot failure with a corrupt version reference; " + "not persisting over it"); + return; + } + if (status == BOOT_STATE_UNPROVISIONED) { + /* Nothing stored yet: start a record to capture the failure. */ + memset(&st, 0, sizeof(st)); + } + + st.boot_fail_count++; + st.last_fail_code = fail_code; + boot_state_log_val("Recording boot failure, code ", fail_code); + boot_state_save(&st); +} + +#endif /* WOLFBOOT_ANTI_ROLLBACK */ diff --git a/src/boot_state_tpm.c b/src/boot_state_tpm.c new file mode 100644 index 0000000000..12920561f6 --- /dev/null +++ b/src/boot_state_tpm.c @@ -0,0 +1,156 @@ +/* boot_state_tpm.c + * + * TPM NV backend for the anti-rollback reference and boot audit counters. + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* TPM NV storage backend for src/boot_state.c; another root of trust can + * replace this file without touching the policy or src/tpm.c. */ + +#include "user_settings.h" + +#ifdef WOLFBOOT_ANTI_ROLLBACK + +#include "boot_state.h" +#include "tpm.h" +#include +#include + +#define BOOT_STATE_NV_WRITE_MASK (TPMA_NV_PLATFORMCREATE | TPMA_NV_PPWRITE | \ + TPMA_NV_OWNERWRITE | TPMA_NV_AUTHWRITE | TPMA_NV_POLICYWRITE | \ + TPMA_NV_TPM_NT) +#define BOOT_STATE_NV_WRITE_ATTR (TPMA_NV_PLATFORMCREATE | TPMA_NV_PPWRITE) + +int boot_state_backend_read(uint8_t *buf, uint32_t len) +{ + WOLFTPM2_DEV *dev = &wolftpm_dev; + WOLFTPM2_NV nv; + word32 readSz; + int rc; + + memset(&nv, 0, sizeof(nv)); + nv.handle.hndl = WOLFBOOT_TPM_BOOT_STATE_NV_BASE; + + /* Only an undefined index (format-1 TPM_RC_HANDLE) is unprovisioned; any + * other error must not reset the floor through trust-on-first-use. */ + rc = wolfTPM2_NVOpen(dev, &nv, WOLFBOOT_TPM_BOOT_STATE_NV_BASE, NULL, 0); + if (rc != 0) { + if ((rc & RC_FMT1) && ((rc & RC_MAX_FMT1) == TPM_RC_HANDLE)) { + return BOOT_STATE_UNPROVISIONED; + } + return BOOT_STATE_UNAVAILABLE; + } + /* Only an index this backend created can hold the floor: platform-created + * and writable by the platform hierarchy alone. */ + if ((nv.attributes & BOOT_STATE_NV_WRITE_MASK) != BOOT_STATE_NV_WRITE_ATTR) { + return BOOT_STATE_CORRUPT; + } + + wolfTPM2_SetAuthHandle(dev, 0, &nv.handle); + readSz = len; + rc = wolfTPM2_NVReadAuth(dev, &nv, WOLFBOOT_TPM_BOOT_STATE_NV_BASE, + buf, &readSz, 0); + /* Clear the auth slot the read set, so it does not carry into later ops. */ + wolfTPM2_UnsetAuth(dev, 0); + /* An index smaller than the record (TPM_RC_NV_RANGE) is malformed: + * corrupt, which halts, not unavailable. */ + if (rc == TPM_RC_NV_RANGE) { + return BOOT_STATE_CORRUPT; + } + if (rc != 0) { + return BOOT_STATE_UNAVAILABLE; + } + if (readSz != len) { + return BOOT_STATE_CORRUPT; + } + return 0; +} + +int boot_state_backend_write(const uint8_t *buf, uint32_t len) +{ + WOLFTPM2_DEV *dev = &wolftpm_dev; + WOLFTPM2_HANDLE parent; + WOLFTPM2_NV nv; + NV_Write_In in; + word32 nvAttributes = 0; + int rc; + + if (len > sizeof(in.data.buffer)) { + return -1; + } + memset(&parent, 0, sizeof(parent)); + memset(&nv, 0, sizeof(nv)); + parent.hndl = TPM_RH_PLATFORM; + nv.handle.hndl = WOLFBOOT_TPM_BOOT_STATE_NV_BASE; + + wolfTPM2_GetNvAttributesTemplate(TPM_RH_PLATFORM, &nvAttributes); + nvAttributes &= ~TPMA_NV_AUTHWRITE; + + /* Created on first write (trust-on-first-use). No TPMA_NV_WRITEDEFINE: + * the record is rewritten as versions change. */ + rc = wolfTPM2_NVCreateAuth(dev, &parent, &nv, + WOLFBOOT_TPM_BOOT_STATE_NV_BASE, + nvAttributes, len, NULL, 0); + if (rc == TPM_RC_NV_DEFINED) { + rc = 0; + } + wolfTPM2_UnsetAuth(dev, 1); + if (rc != 0) { + return -1; + } + + /* TPMA_NV_PPWRITE: the platform authorizes the write. Its auth is empty + * until boot_state_backend_lock() disables the hierarchy. */ + memset(&in, 0, sizeof(in)); + in.authHandle = TPM_RH_PLATFORM; + in.nvIndex = WOLFBOOT_TPM_BOOT_STATE_NV_BASE; + in.data.size = (UINT16)len; + memcpy(in.data.buffer, buf, len); + wolfTPM2_SetAuthPassword(dev, 0, NULL); + rc = TPM2_NV_Write(&in); + wolfTPM2_UnsetAuth(dev, 0); + if (rc != 0) { + return -1; + } + return 0; +} + +int boot_state_backend_lock(void) +{ + WOLFTPM2_DEV *dev = &wolftpm_dev; + HierarchyControl_In in; + int rc; + + /* Disable the platform hierarchy until the next Startup(CLEAR) (TCG PC + * Client Platform spec, section 10): the OS cannot write or undefine. */ + memset(&in, 0, sizeof(in)); + in.authHandle = TPM_RH_PLATFORM; + in.enable = TPM_RH_PLATFORM; + in.state = 0; + wolfTPM2_SetAuthPassword(dev, 0, NULL); + rc = TPM2_HierarchyControl(&in); + wolfTPM2_UnsetAuth(dev, 0); + if (rc != 0) { + return -1; + } + return 0; +} + +#endif /* WOLFBOOT_ANTI_ROLLBACK */ diff --git a/src/tpm.c b/src/tpm.c index 047b80c034..c44c1767ae 100644 --- a/src/tpm.c +++ b/src/tpm.c @@ -1781,11 +1781,12 @@ int wolfBoot_tpm2_init(void) void wolfBoot_tpm2_deinit(void) { #ifdef WOLFBOOT_TPM_KEYSTORE - #if !defined(ARCH_SIM) && !defined(WOLFBOOT_TPM_NO_CHG_PLAT_AUTH) - /* Enable parameter encryption for session */ - int rc = wolfTPM2_SetAuthSession(&wolftpm_dev, 0, &wolftpm_session, - (TPMA_SESSION_decrypt | TPMA_SESSION_encrypt | - TPMA_SESSION_continueSession)); + /* Anti-rollback has already disabled the platform hierarchy. */ + #if !defined(ARCH_SIM) && !defined(WOLFBOOT_TPM_NO_CHG_PLAT_AUTH) && \ + !defined(WOLFBOOT_ANTI_ROLLBACK) + /* Password (empty platform auth) in slot 0; ChangePlatformAuth puts the + * parameter-encryption session in slot 1. */ + int rc = wolfTPM2_SetAuthPassword(&wolftpm_dev, 0, NULL); if (rc == 0) { /* Change platform auth to random value, to prevent application * from being able to use platform hierarchy. This is defined in diff --git a/src/update_disk.c b/src/update_disk.c index 8cdacb7a6a..d0343aaa29 100644 --- a/src/update_disk.c +++ b/src/update_disk.c @@ -83,6 +83,10 @@ static uint8_t disk_encrypt_nonce[ENCRYPT_NONCE_SIZE]; #include "pci.h" #include "x86/tgl_fsp.h" +#ifdef WOLFBOOT_ANTI_ROLLBACK +#include "boot_state.h" +#endif + #ifdef TARGET_kontron_vx3060_s2 #define BOOT_PART_A 5 #define BOOT_PART_B 6 @@ -829,6 +833,9 @@ void RAMFUNCTION wolfBoot_start(void) #ifdef DISK_ENCRYPT disk_decrypted_header_clear(dec_hdr); disk_crypto_clear(); +#endif +#if defined(WOLFBOOT_ANTI_ROLLBACK) && defined(WOLFBOOT_FSP) + boot_state_on_failure(BOOT_FAIL_NO_IMAGE); #endif wolfBoot_panic(); #endif @@ -898,6 +905,9 @@ void RAMFUNCTION wolfBoot_start(void) #ifdef DISK_ENCRYPT disk_decrypted_header_clear(dec_hdr); disk_crypto_clear(); +#endif +#if defined(WOLFBOOT_ANTI_ROLLBACK) && defined(WOLFBOOT_FSP) + boot_state_on_failure(BOOT_FAIL_ROLLBACK); #endif wolfBoot_panic(); return; @@ -1075,6 +1085,9 @@ void RAMFUNCTION wolfBoot_start(void) disk_crypto_clear(); #endif wolfBoot_printf("Unable to find a valid partition!\r\n"); +#if defined(WOLFBOOT_ANTI_ROLLBACK) && defined(WOLFBOOT_FSP) + boot_state_on_failure(BOOT_FAIL_VERIFY_BOTH); +#endif wolfBoot_panic(); return; } @@ -1307,6 +1320,26 @@ void RAMFUNCTION wolfBoot_start(void) #ifndef WOLFBOOT_SKIP_BOOT_VERIFY PART_SANITY_CHECK(&os_image); #endif +#if defined(WOLFBOOT_ANTI_ROLLBACK) && defined(WOLFBOOT_FSP) + /* Last step before handoff, so a failure above never moves the floor. + * The golden slot only locks. */ +#ifdef DISK_GOLDEN_SLOT + if (SLOT_IS_GOLDEN(selected)) + boot_state_lock(); + else +#endif + { + /* Use the version from the verified header, not the early + * pre-verification reads. */ + uint32_t booted_ver; +#ifdef DISK_ENCRYPT + booted_ver = get_decrypted_blob_version(dec_hdr); +#else + booted_ver = wolfBoot_get_blob_version((uint8_t *)hdr_ptr); +#endif + boot_state_on_boot(booted_ver); + } +#endif #ifdef DISK_ENCRYPT disk_decrypted_header_clear(dec_hdr); disk_crypto_clear(); diff --git a/tools/scripts/x86_fsp/qemu/qemu.sh b/tools/scripts/x86_fsp/qemu/qemu.sh index d803fbad63..b3624cc2aa 100755 --- a/tools/scripts/x86_fsp/qemu/qemu.sh +++ b/tools/scripts/x86_fsp/qemu/qemu.sh @@ -40,7 +40,7 @@ set -e set -x # Parse command line options -while getopts "d:wpt" opt; do +while getopts "d:wptc" opt; do case "$opt" in d) DEBUG_STAGE="$OPTARG" diff --git a/tools/scripts/x86_fsp/qemu/qemu_build_fsp.sh b/tools/scripts/x86_fsp/qemu/qemu_build_fsp.sh index ac1c6e2e24..d3ee84b7ef 100755 --- a/tools/scripts/x86_fsp/qemu/qemu_build_fsp.sh +++ b/tools/scripts/x86_fsp/qemu/qemu_build_fsp.sh @@ -12,6 +12,9 @@ CONFIG_FILE=${CONFIG_FILE:-"${WOLFBOOT_DIR}/.config"} set -e +# Identity for the patch commits in the scratch edk2 clone only +GIT_ID="-c user.name=wolfBoot -c user.email=wolfboot@wolfssl.com" + if [ ! -f "${WOLFBOOT_DIR}/arch.mk" ]; then echo "Error: Current directory is not the root directory of the source tree" exit 1 @@ -37,7 +40,7 @@ download_edkii() { git clone "${EDKII_REPO}" edk2) else (cd "$WORK_DIR/edk2" && - git stash save --include-untracked "Auto-stashed on $(date)" && + git $GIT_ID stash save --include-untracked "Auto-stashed on $(date)" && git clean -fd) # This will remove untracked files/directories fi @@ -49,9 +52,9 @@ download_edkii() { download_sbl_patch_and_patch_edkii() { (cd "$WORK_DIR/edk2" && curl -L -o 0001-Build-QEMU-FSP-2.0-binaries.patch ${SBL_PATCH_URL}; - git am --keep-cr --whitespace=nowarn 0001-Build-QEMU-FSP-2.0-binaries.patch) + git $GIT_ID am --keep-cr --whitespace=nowarn 0001-Build-QEMU-FSP-2.0-binaries.patch) (cd "$WORK_DIR/edk2" && - git am --keep-cr --whitespace=nowarn "${SCRIPT_DIR}"/0002-add-W-no-warnings-to-compile-with-gcc-12.patch "${SCRIPT_DIR}"/0003-disable-optmizaion-patch-for-edk2.patch "${SCRIPT_DIR}"/0004-fix-PatchFv-make-regex-match-both-8-and-16-length-ad.patch; + git $GIT_ID am --keep-cr --whitespace=nowarn "${SCRIPT_DIR}"/0002-add-W-no-warnings-to-compile-with-gcc-12.patch "${SCRIPT_DIR}"/0003-disable-optmizaion-patch-for-edk2.patch "${SCRIPT_DIR}"/0004-fix-PatchFv-make-regex-match-both-8-and-16-length-ad.patch; mkdir -p MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/c/include/) } diff --git a/tools/scripts/x86_fsp/qemu/test_qemu.sh b/tools/scripts/x86_fsp/qemu/test_qemu.sh index ff29bf4518..63545daafb 100755 --- a/tools/scripts/x86_fsp/qemu/test_qemu.sh +++ b/tools/scripts/x86_fsp/qemu/test_qemu.sh @@ -4,7 +4,7 @@ set -e WOLFBOOT_DIR=$(pwd) # Parse command line options -while getopts "bf" opt; do +while getopts "bfr" opt; do case "$opt" in b) REBUILD_ONLY=true @@ -12,10 +12,14 @@ while getopts "bf" opt; do f) FS_BOOT=true ;; + r) + ANTI_ROLLBACK=true + ;; *) - echo "Usage: $0 [-b] [-f]" + echo "Usage: $0 [-b] [-f] [-r]" echo "-b : rebuild only (skip FSP build, key gen, ecc)" echo "-f : boot from a file on a read-only filesystem (DISK_FS)" + echo "-r : TPM-NV anti-rollback across three boots" exit 1 ;; esac @@ -25,6 +29,8 @@ done if [ "$REBUILD_ONLY" != true ]; then if [ "$FS_BOOT" = true ]; then CONFIG=x86_fsp_qemu_fs.config + elif [ "$ANTI_ROLLBACK" = true ]; then + CONFIG=x86_fsp_qemu_antirollback.config else CONFIG=x86_fsp_qemu_seal.config fi @@ -33,7 +39,7 @@ if [ "$REBUILD_ONLY" != true ]; then cp "config/examples/${CONFIG}" .config ./tools/scripts/x86_fsp/qemu/qemu_build_fsp.sh make keytools - if [ "$FS_BOOT" = true ]; then + if [ "$FS_BOOT" = true ] || [ "$ANTI_ROLLBACK" = true ]; then # The filesystem config does not use the TPM, so only the image key is # needed here. It must match SIGN= in x86_fsp_qemu_fs.config (ECC256), # or the generated src/keystore.c raises a key-algorithm mismatch. @@ -46,7 +52,7 @@ if [ "$REBUILD_ONLY" != true ]; then fi fi -if [ "$FS_BOOT" = true ]; then +if [ "$FS_BOOT" = true ] || [ "$ANTI_ROLLBACK" = true ]; then make else # manual add ECC256 for TPM @@ -56,7 +62,7 @@ fi # test-app make test-app/image.elf -if [ "$FS_BOOT" != true ]; then +if [ "$FS_BOOT" != true ] && [ "$ANTI_ROLLBACK" != true ]; then # compute pcr0 value PCR0=$(python ./tools/scripts/x86_fsp/compute_pcr.py --target qemu wolfboot_stage1.bin | tail -n 1) echo $PCR0 @@ -67,11 +73,66 @@ fi if [ "$FS_BOOT" = true ]; then FS=1 IMAGE=test-app/image.elf SIGN=--ecc256 \ ./tools/scripts/x86_fsp/qemu/make_hd.sh +elif [ "$ANTI_ROLLBACK" = true ]; then + IMAGE=test-app/image.elf SIGN=--ecc256 \ + ./tools/scripts/x86_fsp/qemu/make_hd.sh else IMAGE=test-app/image.elf SIGN=--ecc384 \ ./tools/scripts/x86_fsp/qemu/make_hd.sh fi +# Boot once with the swtpm state kept from the previous boot, wait for the +# test app, and leave the console log in /tmp/qemu_output. +run_qemu_tpm() { + ./tools/scripts/x86_fsp/qemu/qemu.sh -t -p $1 > /tmp/qemu_output 2>&1 & + for i in $(seq 1 30); do + sleep 1 + grep -q "wolfBoot QEMU x86 FSP test app" /tmp/qemu_output && break + grep -q "PANIC" /tmp/qemu_output && break + done + timeout 5 echo 'quit' > /tmp/qemu_mon.in || true + wait + cat /tmp/qemu_output +} + +expect_log() { + if ! grep -q "$1" /tmp/qemu_output; then + echo -e "\e[31mTEST FAILED: missing '$1'\e[0m" + exit 255 + fi +} + +reject_log() { + if grep -q "$1" /tmp/qemu_output; then + echo -e "\e[31mTEST FAILED: unexpected '$1'\e[0m" + exit 255 + fi +} + +if [ "$ANTI_ROLLBACK" = true ]; then + # Boot 1, fresh TPM: slot B (version 2) boots and anchors the floor. + run_qemu_tpm -c + expect_log "Anti-rollback anchor initialised at version 2" + expect_log "wolfBoot QEMU x86 FSP test app" + + # Boot 2: both slots v1. It boots, the floor survived the TPM restart and + # the downgrade is written (platform hierarchy enabled again at startup). + dd if=image_v1_signed.bin of=app.bin bs=512 seek=34816 conv=notrunc + run_qemu_tpm + expect_log "ROLLBACK: booting version 1" + expect_log "ROLLBACK: total rollbacks recorded 1" + expect_log "wolfBoot QEMU x86 FSP test app" + + # Boot 3: the counter keeps counting across boots. + run_qemu_tpm + expect_log "ROLLBACK: total rollbacks recorded 2" + expect_log "wolfBoot QEMU x86 FSP test app" + reject_log "WARNING: could not" + reject_log "PANIC" + echo -e "\e[32mTEST OK\e[0m" + exit 0 +fi + echo "RUNNING QEMU" # launch qemu in background # -t starts an emulated TPM. The seal path needs it; the filesystem config diff --git a/tools/scripts/x86_fsp/tgl/tgl_download_fsp.sh b/tools/scripts/x86_fsp/tgl/tgl_download_fsp.sh index ad6191f6be..12d3f00fc1 100755 --- a/tools/scripts/x86_fsp/tgl/tgl_download_fsp.sh +++ b/tools/scripts/x86_fsp/tgl/tgl_download_fsp.sh @@ -54,7 +54,9 @@ download_split_tool() { } patch_tgl_fsp() { - (cd "$WORK_DIR"/FSP && git am --keep-cr "${PATCH}") + # Identity for the patch commit in the scratch FSP clone only + (cd "$WORK_DIR"/FSP && git -c user.name=wolfBoot \ + -c user.email=wolfboot@wolfssl.com am --keep-cr "${PATCH}") } rebase_fsp_component() { diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 5ccc3509b1..5a4a9c4336 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -240,6 +240,8 @@ TESTS+=unit-xspi-tfd-index TESTS+=unit-pic32-pfswap TESTS+=unit-secure-handoff TESTS+=unit-imx-rt700 +TESTS+=unit-boot-state +TESTS+=unit-boot-state-halt # linux_loader.c is x86 32-bit only, so its unit tests need a working 32-bit # (multilib) toolchain. Probe whether "gcc -m32" can link, and only add the @@ -868,6 +870,20 @@ unit-linux-loader-initrd: ../../include/target.h unit-linux-loader-initrd.c -DWOLFBOOT_LOAD_BASE=0x100000 \ -Wl,--defsym,_end_wb=_start_wolfboot+0x100000 +# unit-boot-state exercises the anti-rollback state machine in src/boot_state.c +# through a mock storage backend. Built standalone (not via the common CFLAGS) +# so the stubs-boot-state/ headers shadow user_settings.h/printf.h/loader.h and the +# host build needs no target configuration. boot_state.c has no inline asm, so +# no -m32 is required. +unit-boot-state: unit-boot-state.c ../../src/boot_state.c + gcc -o $@ unit-boot-state.c -Istubs-boot-state -I../../src -I../../include \ + -g -Wall -DUNIT_TEST -DWOLFBOOT_ANTI_ROLLBACK + +unit-boot-state-halt: unit-boot-state.c ../../src/boot_state.c + gcc -o $@ unit-boot-state.c -Istubs-boot-state -I../../src -I../../include \ + -g -Wall -DUNIT_TEST -DWOLFBOOT_ANTI_ROLLBACK \ + -DWOLFBOOT_ANTI_ROLLBACK_HALT_ON_FAILURE + unit-boot-x86-fsp: ../../include/target.h unit-boot-x86_fsp.c gcc -o $@ $^ $(CFLAGS) -DWOLFBOOT_LOAD_BASE=0x100000 -DWOLFBOOT_FSP \ -DUCODE0_ADDRESS=0 -ffunction-sections -fdata-sections $(LDFLAGS) \ @@ -1700,14 +1716,14 @@ unit-p1021-read-badblock: unit-p1021-read-badblock.c p1021_read_extract.h \ gcc -o $@ unit-p1021-read-badblock.c $(CFLAGS) $(LDFLAGS) # unit-kontron-tgl-spi runs the real tgl_lock_bios_region() from -# hal/kontron_vx3060_s2.c against mocked PCI config space and an +# hal/x86_fsp_tgl.c against mocked PCI config space and an # MMIO array at the SPI BAR address (F-12104: the lock was never # applied - no hal_flash_protect() override - and targeted the # wrong register space). -kontron_spi_extract.h: ../../hal/kontron_vx3060_s2.c +kontron_spi_extract.h: ../../hal/x86_fsp_tgl.c sed -n '/^#define SPI_PCI_DEV/,/^#define SPI_FLOCKDN/p' $< > $@ -kontron_spi_fn_extract.h: ../../hal/kontron_vx3060_s2.c +kontron_spi_fn_extract.h: ../../hal/x86_fsp_tgl.c sed -n '/^int tgl_lock_bios_region/,/^}/p' $< > $@ sed -n '/^int hal_flash_protect/,/^}/p' $< >> $@ diff --git a/tools/unit-tests/stubs-boot-state/loader.h b/tools/unit-tests/stubs-boot-state/loader.h new file mode 100644 index 0000000000..76c8e4a622 --- /dev/null +++ b/tools/unit-tests/stubs-boot-state/loader.h @@ -0,0 +1,27 @@ +/* hal.h (host test stub) + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* The build's loader.h pulls in hooks.h and the platform panic; + * boot_state.c only needs the declaration of the panic it calls. */ +#ifndef UNIT_BOOT_STATE_LOADER_H +#define UNIT_BOOT_STATE_LOADER_H +void wolfBoot_panic(void); +#endif diff --git a/tools/unit-tests/stubs-boot-state/printf.h b/tools/unit-tests/stubs-boot-state/printf.h new file mode 100644 index 0000000000..956a88774c --- /dev/null +++ b/tools/unit-tests/stubs-boot-state/printf.h @@ -0,0 +1,27 @@ +/* printf.h (host test stub) + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* boot_state.c logs its decisions with wolfBoot_printf; the host test asserts + * on return values, so the log is dropped to keep PASS/FAIL output clean. */ +#ifndef UNIT_BOOT_STATE_PRINTF_H +#define UNIT_BOOT_STATE_PRINTF_H +#define wolfBoot_printf(...) do {} while (0) +#endif diff --git a/tools/unit-tests/stubs-boot-state/user_settings.h b/tools/unit-tests/stubs-boot-state/user_settings.h new file mode 100644 index 0000000000..9a332b3b86 --- /dev/null +++ b/tools/unit-tests/stubs-boot-state/user_settings.h @@ -0,0 +1,26 @@ +/* user_settings.h (host test stub) + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Shadows the build's user_settings.h so boot_state.c builds on the host + * without a target configuration. */ +#ifndef UNIT_BOOT_STATE_USER_SETTINGS_H +#define UNIT_BOOT_STATE_USER_SETTINGS_H +#endif diff --git a/tools/unit-tests/unit-boot-state.c b/tools/unit-tests/unit-boot-state.c new file mode 100644 index 0000000000..df09133eb5 --- /dev/null +++ b/tools/unit-tests/unit-boot-state.c @@ -0,0 +1,296 @@ +/* unit-boot-state.c + * + * Host unit tests for the anti-rollback state machine in src/boot_state.c: + * the bitwise CRC-32, the load/validate path and its BOOT_STATE_* codes, + * trust-on-first-use anchoring, the rollback-audit path and the + * skip-write-when-unchanged comparison. The storage backend is isolated behind + * boot_state_backend_{read,write}(), so a mock backend exercises the whole + * policy on the host. Built with plain gcc; boot_state.c has no inline asm. + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#include +#include + +#include "boot_state.h" + +/* Mock backend and halt stub. A nonzero mock_read_code is returned by the + * read; zero serves the record from mock_nv. */ +static uint8_t mock_nv[sizeof(struct boot_state)]; +static int mock_read_code; +static int mock_write_fail; +static int mock_write_called; +static uint8_t mock_write_buf[sizeof(struct boot_state)]; +static jmp_buf halt_env; +static int halted; +#if defined(WOLFBOOT_ANTI_ROLLBACK_HALT_ON_FAILURE) +static const int halt_on_failure = 1; +#else +static const int halt_on_failure = 0; +#endif +static int mock_lock_called; +static int mock_lock_fail; + +int boot_state_backend_read(uint8_t *buf, uint32_t len) +{ + if (mock_read_code != 0) + return mock_read_code; + memcpy(buf, mock_nv, len); + return 0; +} + +int boot_state_backend_write(const uint8_t *buf, uint32_t len) +{ + mock_write_called++; + if (len <= sizeof(mock_write_buf)) { + memcpy(mock_write_buf, buf, len); + memcpy(mock_nv, buf, len); + } + return mock_write_fail ? -1 : 0; +} + +int boot_state_backend_lock(void) +{ + mock_lock_called++; + return mock_lock_fail ? -1 : 0; +} + +void wolfBoot_panic(void) +{ + halted = 1; + longjmp(halt_env, 1); +} + +#include "../../src/boot_state.c" + +static int fails; +#define CHECK(cond, msg) \ + do { if (!(cond)) { printf("FAIL: %s\n", (msg)); fails++; } } while (0) + +static void make_valid(struct boot_state *st, uint32_t anchor, uint32_t record) +{ + memset(st, 0, sizeof(*st)); + st->magic = BOOT_STATE_MAGIC; + st->struct_version = BOOT_STATE_VERSION; + st->anchor = anchor; + st->record = record; + st->crc = boot_state_compute_crc(st); +} + +static void reset_mock(void) +{ + memset(mock_nv, 0, sizeof(mock_nv)); + mock_read_code = 0; + mock_write_fail = 0; + mock_write_called = 0; + memset(mock_write_buf, 0, sizeof(mock_write_buf)); + halted = 0; + mock_lock_called = 0; + mock_lock_fail = 0; +} + +int main(void) +{ + struct boot_state st, rec; + + /* CRC-32 known-answer: the IEEE 802.3 check value of "123456789". */ + CHECK(boot_state_crc32((const uint8_t *)"123456789", 9) == 0xCBF43926u, + "crc32 KAT"); + + /* Unprovisioned passes through unchanged. */ + reset_mock(); + mock_read_code = BOOT_STATE_UNPROVISIONED; + CHECK(boot_state_load(&st) == BOOT_STATE_UNPROVISIONED, + "load unprovisioned"); + + /* A well-formed record loads OK with its fields intact. */ + reset_mock(); + make_valid(&rec, 5, 5); + memcpy(mock_nv, &rec, sizeof(rec)); + CHECK(boot_state_load(&st) == BOOT_STATE_OK, "load ok"); + CHECK(st.anchor == 5 && st.record == 5, "load ok fields"); + + /* Wrong magic is corrupt (checked before the CRC). */ + reset_mock(); + make_valid(&rec, 5, 5); + rec.magic ^= 0xFFu; + memcpy(mock_nv, &rec, sizeof(rec)); + CHECK(boot_state_load(&st) == BOOT_STATE_CORRUPT, "load bad magic"); + + /* A CRC mismatch is corrupt. */ + reset_mock(); + make_valid(&rec, 7, 7); + rec.crc ^= 0xA5A5A5A5u; + memcpy(mock_nv, &rec, sizeof(rec)); + CHECK(boot_state_load(&st) == BOOT_STATE_CORRUPT, "load bad crc"); + + /* A present but malformed record (short read) is CORRUPT, not + * UNAVAILABLE. */ + reset_mock(); + mock_read_code = BOOT_STATE_CORRUPT; + CHECK(boot_state_load(&st) == BOOT_STATE_CORRUPT, "load backend corrupt"); + + /* An unreachable backend is UNAVAILABLE. */ + reset_mock(); + mock_read_code = BOOT_STATE_UNAVAILABLE; + CHECK(boot_state_load(&st) == BOOT_STATE_UNAVAILABLE, + "load backend unavailable"); + + /* An unreachable backend writes nothing but is still locked. + * HALT_ON_FAILURE refuses to boot without it. */ + reset_mock(); + mock_read_code = BOOT_STATE_UNAVAILABLE; + if (setjmp(halt_env) == 0) { + boot_state_on_boot(7); + CHECK(!halt_on_failure, "unavailable did not halt with HALT_ON_FAILURE"); + } + else { + CHECK(halt_on_failure, "unavailable halted without HALT_ON_FAILURE"); + } + CHECK(mock_write_called == 0, "unavailable does not write"); + CHECK(mock_lock_called == (halt_on_failure ? 0 : 1), + "unavailable still locks unless it halted"); + + /* boot_state_lock (golden slot) locks without reading or writing. */ + reset_mock(); + boot_state_lock(); + CHECK(mock_write_called == 0 && mock_lock_called == 1, "lock only"); + + /* A failed write warns and still locks, or halts with HALT_ON_FAILURE. */ + reset_mock(); + mock_read_code = BOOT_STATE_UNPROVISIONED; + mock_write_fail = 1; + if (setjmp(halt_env) == 0) { + boot_state_on_boot(7); + CHECK(!halt_on_failure, "write failure did not halt with HALT_ON_FAILURE"); + CHECK(mock_lock_called == 1, "write failure still locks"); + } + else { + CHECK(halt_on_failure, "write failure halted without HALT_ON_FAILURE"); + CHECK(mock_lock_called == 0, "HALT_ON_FAILURE write failure halts before lock"); + } + + /* A failed lock warns and boots; HALT_ON_FAILURE halts. */ + reset_mock(); + make_valid(&rec, 10, 10); + memcpy(mock_nv, &rec, sizeof(rec)); + mock_lock_fail = 1; + if (setjmp(halt_env) == 0) { + boot_state_on_boot(10); + CHECK(!halt_on_failure, "lock failure did not halt with HALT_ON_FAILURE"); + } + else { + CHECK(halt_on_failure, "lock failure halted without HALT_ON_FAILURE"); + } + CHECK(mock_lock_called == 1, "lock attempted"); + + /* Trust on first use: the first booted image sets anchor and record. */ + reset_mock(); + mock_read_code = BOOT_STATE_UNPROVISIONED; + boot_state_on_boot(42); + CHECK(mock_write_called == 1, "tofu writes"); + memcpy(&st, mock_write_buf, sizeof(st)); + CHECK(st.anchor == 42 && st.record == 42, "tofu anchor/record"); + CHECK(mock_lock_called == 1, "tofu locks"); + + /* A higher version advances the anchor. */ + reset_mock(); + make_valid(&rec, 10, 10); + memcpy(mock_nv, &rec, sizeof(rec)); + boot_state_on_boot(20); + CHECK(mock_write_called == 1, "advance writes"); + memcpy(&st, mock_write_buf, sizeof(st)); + CHECK(st.anchor == 20 && st.record == 20, "advance fields"); + + /* Re-booting the same version changes nothing and skips the write. */ + reset_mock(); + make_valid(&rec, 10, 10); + memcpy(mock_nv, &rec, sizeof(rec)); + boot_state_on_boot(10); + CHECK(mock_write_called == 0, "same version skips write"); + CHECK(mock_lock_called == 1, "same version still locks"); + + /* A validly signed older image still boots, keeps the anchor, and leaves + * an audit event behind. */ + reset_mock(); + make_valid(&rec, 10, 10); + memcpy(mock_nv, &rec, sizeof(rec)); + boot_state_on_boot(4); + CHECK(mock_write_called == 1, "rollback writes audit"); + memcpy(&st, mock_write_buf, sizeof(st)); + CHECK(st.anchor == 10, "rollback keeps anchor"); + CHECK(st.record == 4, "rollback records booted"); + CHECK(st.rollback_count == 1, "rollback counted"); + CHECK(mock_lock_called == 1, "rollback locks"); + CHECK(st.last_rollback_from == 10 && st.last_rollback_to == 4, + "rollback from/to"); + + /* A corrupt reference halts the boot and is not overwritten. */ + reset_mock(); + make_valid(&rec, 9, 9); + rec.crc ^= 1u; + memcpy(mock_nv, &rec, sizeof(rec)); + if (setjmp(halt_env) == 0) { + boot_state_on_boot(9); + CHECK(0, "corrupt reference did not halt"); + } + else { + CHECK(halted == 1, "corrupt reference halted"); + CHECK(mock_write_called == 0, "corrupt reference not overwritten"); + } + + /* on_failure leaves a corrupt reference untouched and does not halt. */ + reset_mock(); + make_valid(&rec, 9, 9); + rec.crc ^= 1u; + memcpy(mock_nv, &rec, sizeof(rec)); + boot_state_on_failure(BOOT_FAIL_VERIFY_BOTH); + CHECK(mock_write_called == 0, "failure keeps corrupt reference"); + CHECK(halted == 0, "failure on corrupt does not halt"); + + /* on_failure when unprovisioned starts a fresh record with the code. */ + reset_mock(); + mock_read_code = BOOT_STATE_UNPROVISIONED; + boot_state_on_failure(BOOT_FAIL_NO_IMAGE); + CHECK(mock_write_called == 1, "failure unprovisioned writes"); + memcpy(&st, mock_write_buf, sizeof(st)); + CHECK(st.boot_fail_count == 1, "failure count set"); + CHECK(st.last_fail_code == BOOT_FAIL_NO_IMAGE, "failure code recorded"); + + /* on_failure on a valid record bumps the counter, keeps anchor/record. */ + reset_mock(); + make_valid(&rec, 10, 8); + memcpy(mock_nv, &rec, sizeof(rec)); + boot_state_on_failure(BOOT_FAIL_VERIFY_BOTH); + CHECK(mock_write_called == 1, "failure valid writes"); + memcpy(&st, mock_write_buf, sizeof(st)); + CHECK(st.boot_fail_count == 1, "failure valid count"); + CHECK(st.last_fail_code == BOOT_FAIL_VERIFY_BOTH, "failure valid code"); + CHECK(st.anchor == 10 && st.record == 8, "failure preserves anchor/record"); + + if (fails == 0) { + printf("PASS\n"); + return 0; + } + printf("%d check(s) failed\n", fails); + return 1; +} From 363483ed54ee2936c665ad87fec24e254b31ac3f Mon Sep 17 00:00:00 2001 From: David Garske Date: Wed, 7 Oct 2026 14:21:43 -0700 Subject: [PATCH 3/3] nai_68int6: add wolfBoot target for the NAI 68INT6 (Intel Tiger Lake UP3) OpenVPX SBC --- arch.mk | 13 +- config/examples/nai_68int6.config | 96 ++ docs/Targets.md | 69 ++ hal/kontron_vx3060_s2.c | 81 -- ...ontron_vx3060_s2_loader.c => nai_68int6.c} | 47 +- hal/{x86_fsp_qemu_loader.c => stub_loader.c} | 27 +- hal/x86_fsp_tgl.c | 87 +- include/x86/fsp_config.h | 44 + include/x86/gpio_config.h | 1097 +++++++++++++++++ include/x86/mptable.h | 2 +- include/x86/tgl_fsp.h | 1 + options.mk | 9 + src/boot_x86_fsp_payload.c | 9 + src/pci.c | 14 +- src/tpm.c | 5 +- src/update_disk.c | 5 + src/x86/mptable.c | 4 +- src/x86/tgl_fsp.c | 81 +- tools/scripts/x86_fsp/select_fsp.sh | 109 ++ tools/scripts/x86_fsp/tgl/nai_extract_fsp.sh | 97 ++ tools/unit-tests/unit-kontron-tgl-spi.c | 4 +- 21 files changed, 1771 insertions(+), 130 deletions(-) create mode 100644 config/examples/nai_68int6.config rename hal/{kontron_vx3060_s2_loader.c => nai_68int6.c} (66%) rename hal/{x86_fsp_qemu_loader.c => stub_loader.c} (72%) create mode 100644 include/x86/fsp_config.h create mode 100644 include/x86/gpio_config.h create mode 100755 tools/scripts/x86_fsp/select_fsp.sh create mode 100755 tools/scripts/x86_fsp/tgl/nai_extract_fsp.sh diff --git a/arch.mk b/arch.mk index 84eac55ddc..c31eab8f90 100644 --- a/arch.mk +++ b/arch.mk @@ -2207,13 +2207,13 @@ ifeq ($(USE_GCC),1) endif OUTPUT_FLAG?=-o -ifeq ($(filter $(TARGET),x86_fsp_qemu kontron_vx3060_s2),$(TARGET)) +ifeq ($(filter $(TARGET),x86_fsp_qemu kontron_vx3060_s2 nai_68int6),$(TARGET)) FSP=1 CFLAGS+=-ffunction-sections -fdata-sections -ffreestanding -nostdlib -static # some std libc have headers that bring in extra symbols used in # FORTIFY_SOURCE realated checks. Use -U_FORTIFY_SOURCE to avoid that. CFLAGS+=-U_FORTIFY_SOURCE - ifeq ($(TARGET), kontron_vx3060_s2) + ifeq ($(filter $(TARGET),kontron_vx3060_s2 nai_68int6),$(TARGET)) FSP_TGL=1 CFLAGS+=-DWOLFBOOT_TGL=1 endif @@ -2263,13 +2263,13 @@ ifeq ("${FSP}", "1") OBJS += src/image.o OBJS += src/keystore.o OBJS += src/sig_wolfboot_raw.o - ifeq ($(TARGET), kontron_vx3060_s2) - OBJS += hal/kontron_vx3060_s2_loader.o - endif OBJS += $(WOLFCRYPT_OBJS) CFLAGS+=-DSTAGE1_AUTH endif + # Generic stage1 loader HAL (stubs); shared by all x86 FSP targets. + OBJS += hal/stub_loader.o + CFLAGS += -fno-stack-protector -m32 -fno-PIC -fno-pie -mno-mmx -mno-sse -DDEBUG_UART CFLAGS += -DFSP_M_BASE=$(FSP_M_BASE) ifeq ($(FSP_TGL), 1) @@ -2277,9 +2277,6 @@ ifeq ("${FSP}", "1") OBJS+=src/ucode0.o CFLAGS += -DUCODE0_ADDRESS=$(UCODE0_BASE) endif - ifeq ($(TARGET),x86_fsp_qemu) - OBJS += hal/x86_fsp_qemu_loader.o - endif else # building wolfBoot ifeq ($(FSP_TGL), 1) diff --git a/config/examples/nai_68int6.config b/config/examples/nai_68int6.config new file mode 100644 index 0000000000..286f0301e4 --- /dev/null +++ b/config/examples/nai_68int6.config @@ -0,0 +1,96 @@ +# NAI 68INT6 3U OpenVPX SBC (Tiger Lake UP3); needs the board's own FSP +# (nai_extract_fsp.sh). See the NAI 68INT6 section of docs/Targets.md. + +ARCH=x86_64 +TARGET=nai_68int6 +WOLFBOOT_SMALL_STACK=0 + +# ECC P-384 signature, SHA-384 digest (measured boot uses SHA-256 on an +# SLB9670). See the integrity-algorithm table in docs/Targets.md. +SIGN=ECC384 +HASH=SHA384 + +DEBUG=1 +# FSP-M UPD dump on the console before FspMemInit (bring-up aid). +DUMP_FSP_UPD=0 +SPMATH=1 +ENCRYPTION=0 +WOLFBOOT_NO_PARTITIONS=1 +# Disk boot: signed kernels in GPT partitions 3/4, Ubuntu root in partition 2. +# The A/B boot-partition indices are set per target in src/update_disk.c. +WOLFBOOT_BOOT_DISK=0 +FORCE_32BIT=1 + +########## Flash layout (measured from the factory descriptor, 32 MB) ########## +WOLFBOOT_PARTITION_BOOT_ADDRESS=0xff400000 +WOLFBOOT_PARTITION_SWAP_ADDRESS=0x0 +WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x0 +WOLFBOOT_LOAD_BASE=0x58000200 +WOLFBOOT_SECTOR_SIZE?=0x1000 +WOLFBOOT_DATA_ADDRESS=0x1000000 + +FSP_CONFIG_BASE=0xffe9f800 +FSP_T_BASE=0xfff59000 +FSP_M_BASE=0xfff60000 + +WOLFBOOT_ORIGIN=0xfff00000 +BOOTLOADER_PARTITION_SIZE=0x600000 +# Inert (the flash descriptor is used); kept for config parity. +BIOS_REGION_SIZE=0xc00000 + +UCODE0_BASE=0xffd90000 +UCODE0_BIN=src/x86/ucode0.bin + +FSP_T_BIN=./src/x86/fsp_t.bin +FSP_M_BIN=./src/x86/fsp_m.bin +FSP_S_BIN=./src/x86/fsp_s.bin +FSP_S_UPD_DATA_BIN=./src/x86/fsp_s_upd_data.bin +# Boot Guard is left to the board owner: supply an ACM and key here to build a +# Boot-Guard image. Unset, wolfBoot builds and boots without it. +ACM_BIN= +FSP_S_LOAD_BASE=0x0FED5F00 + +POLICY_BASE=0xffaa4000 +KEYSTORE_OFFSET=0x3FE00 +KEYSTORE_STAGE1_OFFSET=0x400 + +########## Console: LPSS UART2 (PCI 00:19.2), verified on hardware ########## +X86_UART_NUMBER=2 +X86_UART_BASE=0xFE032000 +X86_UART_REG_WIDTH=4 +X86_UART_MMIO=1 + +########## PCH / PCI ########## +PCH_PCR_BASE=0xFD000000 +PCI_ECAM_BASE=0xC0000000 +PCI_USE_ECAM=1 +PCH_HAS_PCR=1 +# Generate ACPI tables so Linux gets the interrupt model and PCI routing. +ACPI=1 + +########## Payload: signed 64-bit Linux bzImage ########## +64BIT=1 +ELF=1 +DEBUG_ELF=0 +MULTIBOOT2=1 +LINUX_PAYLOAD=1 + +########## Security ########## +STAGE1_AUTH=1 +WOLFTPM=1 +MEASURED_BOOT=1 +MEASURED_PCR_A=0 +# Measure the verified OS image into PCR4, alongside the firmware in PCR0. +MEASURED_PCR_OS=4 +WOLFBOOT_UNIVERSAL_KEYSTORE=1 +WOLFBOOT_SKIP_AUTH_CHECK=0 +ENABLE_DEBUG_CONSENT=0 + +# Deferred: TPM sealing and ATA disk lock/erase. +WOLFBOOT_TPM_SEAL=0 +DISK_LOCK=0 +DISK_ERASE=0 + +# Anti-rollback: persistent version floor + audit counters in TPM NV +# (trust-on-first-use; provision before first boot where that matters). +ANTI_ROLLBACK=1 diff --git a/docs/Targets.md b/docs/Targets.md index dd70453cfc..f0be226010 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -14,6 +14,7 @@ This README describes configuration of supported targets. * [Infineon AURIX TC4xx](#infineon-aurix-tc4xx) * [Intel x86-64 Intel FSP](#intel-x86_64-with-intel-fsp-support) * [Kontron VX3060-S2](#kontron-vx3060-s2) +* [NAI 68INT6 (Intel Tiger Lake)](#nai-68int6-intel-tiger-lake) * [Microchip PIC32CK](#microchip-pic32ck) * [Microchip PIC32CZ](#microchip-pic32cz) * [Microchip PolarFire SoC](#microchip-polarfire-soc) @@ -9609,6 +9610,74 @@ Attempting boot from partition A ``` At this point, the kernel image in partition "A" is verified and staged and you should be seeing the log messages of your OS booting. +## NAI 68INT6 (Intel Tiger Lake) + +wolfBoot target `nai_68int6` for the North Atlantic Industries 68INT6, a 3U OpenVPX SOSA-aligned single board computer (Intel Core i7-1185GRE, Tiger Lake UP3). It uses the Intel FSP support described [above](#intel-x86_64-with-intel-fsp-support) and shares the Tiger Lake path with the Kontron target. wolfBoot boots a signed 64-bit Linux from the SATA disk with A/B update, measured boot and TPM anti-rollback, verified on a Rev D5 bench board. The public Tiger Lake FSP does not train this board's memory, so the FSP is extracted from a 68INT6 BIOS image. + +| | | +|---|---| +| CPU | i7-1185GRE (`06-8C-01`), Tiger Lake UP3, PCH-LP | +| Memory | 2 x 32 GB DDR4-3200 SODIMM, non-ECC | +| BIOS flash | Rev C/D5: 2 x W25R256JV (32 MB), selected by JP36. Next revision: one 64 MB device | +| TPM | SLB9670 (SHA-1/256), or SLB9672, which adds a SHA-384 bank | +| Storage | Onboard SATA SSD, AHCI at `00:17.0` | +| Console | LPSS UART2, PCI `00:19.2`, RS-232; `X86_UART_NUMBER=2` | + +### Building + +``` +cp config/examples/nai_68int6.config .config +./tools/scripts/x86_fsp/tgl/tgl_download_fsp.sh # FSP UPD headers, first time only +./tools/scripts/x86_fsp/tgl/nai_extract_fsp.sh <68INT6.bin> # board's own FSP blobs +./tools/scripts/x86_fsp/tgl/assemble_image.sh -k # generate signing keys +make +./tools/scripts/x86_fsp/tgl/assemble_image.sh -n <68INT6.bin> +``` + +`nai_extract_fsp.sh` takes FSP-T/M/S and the microcode from a 68INT6 BIOS image. Without it the build uses the public FSP, which compiles but does not train memory. The `-n` stitch replaces the top 6 MB (`BOOTLOADER_PARTITION_SIZE`) of the 12 MB BIOS region and keeps the descriptor and CSME byte for byte. The board has no recovery region (recovery is the second flash device), so use `-n`, not `-m`. The result is `final_image.bin`. + +### Integrity algorithms + +ECC P-384 and SHA-384 match what Intel Boot Guard uses for the Initial Boot Block. + +| Layer | Algorithm | +|---|---| +| Image signature | ECC P-384 (`SIGN=ECC384`) | +| Image digest | SHA-384 (`HASH=SHA384`) | +| Measured boot PCR | SHA-256 (the SLB9670 has only SHA-1 and SHA-256 banks) | + +`wolfBoot_image_measure()` re-hashes the authenticated digest with the PCR bank's algorithm, so one image serves either TPM. `MEASURED_PCR_ALG=SHA384` needs an SLB9672 with its SHA-384 bank allocated (`TPM2_PCR_Allocate` under platform auth, a provisioning step). + +### Configuration + +Notable options in [config/examples/nai_68int6.config](../config/examples/nai_68int6.config): + +| Option | Meaning | +|---|---| +| `X86_UART_NUMBER=2` | LPSS UART2 for all three FSP phases (Kontron uses 0) | +| `ACPI=1` | Required. Generates ACPI tables (RSDP/XSDT/FADT/MADT/MCFG and a minimal DSDT); the board's PCI interrupt routing is only in the DSDT `_PRT`, since the MP table is the Kontron one | +| `ANTI_ROLLBACK=1` | Persistent version floor and boot audit counters in TPM NV | +| `MEASURED_BOOT=1`, `MEASURED_PCR_OS=4` | Firmware into PCR 0, the verified OS image into PCR 4 | +| `WOLFBOOT_BOOT_DISK=0` | Boot from GPT partitions; the A/B indices (sda3/sda4) are set in `src/update_disk.c` | + +### BIOS region lock + +Before handoff, `hal_flash_protect()` in the shared Tiger Lake HAL (`hal/x86_fsp_tgl.c`, as on Kontron) sets SPI Flash Protected Range 0 over the BIOS region with read and write protection, then FLOCKDN. Both hold until the next platform reset, so the booted OS cannot read or reprogram the BIOS region; update it with an external programmer. + +### Anti-rollback + +`ANTI_ROLLBACK=1` keeps a version floor and boot audit counters in a TPM NV index; the backend is `src/boot_state_tpm.c` and the policy is `src/boot_state.c`. The floor is the highest version ever booted, set trust-on-first-use. A validly signed older image still boots and records a rollback event. Only an untrustworthy reference halts the boot; `ANTI_ROLLBACK_HALT_ON_FAILURE=1` also halts when the TPM is unreachable, a write fails or the platform hierarchy cannot be disabled. + +TPM NV is used instead of the partition state trailer because the floor must survive a full reimage of the disk or flash. `DISK_BOOT_CONFIRM` still handles per-slot confirmation, and the backend can move to a root-of-trust device later without changing the policy. + +`ALLOW_DOWNGRADE` is left unset, the secure default: if the higher-versioned slot fails to verify, wolfBoot halts (`Rollback to lower version not allowed`) rather than boot the lower one. Building with `ALLOW_DOWNGRADE=1` allows that fall-back and leaves the TPM floor as the only rollback guard. + +The booted OS cannot lower the floor. The index is platform-created and writable only under platform auth (`TPMA_NV_PPWRITE`), and on every boot wolfBoot disables the platform hierarchy (`TPM2_HierarchyControl`) until the next TPM reset, as in section 10 of the TCG PC Client Platform specification. The OS can then neither write nor undefine the index. An index with any other write attributes is treated as corrupt. Platform-auth provisioning, such as `TPM2_PCR_Allocate`, must be done before wolfBoot is installed. + +### Boot, programming and recovery + +wolfBoot boots the highest-versioned signed 64-bit `bzImage` that verifies from the two GPT boot partitions. It falls back to the other slot only if that is not a downgrade (see Anti-rollback), and halts if neither boots. Program the flash with a Dediprog SF100 on JP4 while the board is off; the BIOS region lock blocks `flashrom` from the booted OS. JP36 selects which bank boots and which one the programmer sees; move it only with the board off. + ## Infineon PSOC Control C3 The [PSOC Control C3](https://www.infineon.com/products/microcontroller/32-bit-psoc-arm-cortex/32-bit-psoc-control-arm-cortex-m33-mcu/psoc-control-c3-main-line) family (PSC3, CAT1B) is an Arm Cortex-M33 motor-control MCU. wolfBoot runs as the first application the BootROM launches, verifies the signed firmware in the BOOT partition and jumps to it. diff --git a/hal/kontron_vx3060_s2.c b/hal/kontron_vx3060_s2.c index fcdefab5cc..3812f65b5d 100644 --- a/hal/kontron_vx3060_s2.c +++ b/hal/kontron_vx3060_s2.c @@ -31,87 +31,6 @@ #ifdef __WOLFBOOT -#define SPI_PCI_DEV 31 -#define SPI_PCI_FUN 5 -#define SPI_BAR_OFF 0x10 -/* Tiger Lake SPI controller register offsets, memory-mapped at the - * BAR0 base. FREG1 holds the BIOS flash region base/limit (region 0 - * is the flash descriptor); FPR0 is the protected range register - * with the same base/limit layout. Offsets per the Intel PCH SPI - * register map (see drivers/spi/spi-intel.c in the Linux kernel): - * FDATA0-15 at 0x10-0x4C, FRACC at 0x50, FREG0-7 at 0x54-0x74, - * FPR0-4 at 0x84-0x9C. */ -#define SPI_FREG1 0x58 -#define SPI_FREG_BASE_MASK (0x7fffU << 0) -#define SPI_FREG_LIMIT_MASK (0x7fffU << 16) -#define SPI_FREG_LIMIT_SHIFT (16) -#define SPI_FREG_ADDR_SHIFT (12) -#define SPI_FPR0 (0x84) -#define SPI_FPR_WPE (1U << 31) -#define SPI_FPR_RPE (1U << 15) -#define SPI_BIOS_HSFSTS_CTL (0x4) -#define SPI_FLOCKDN (1U << 15) - -int tgl_lock_bios_region() -{ - uint32_t spi_bar, spi_cmd; - uint32_t reg; - int ret = 0; - -#if defined(DEBUG) - uint32_t bios_reg_base, bios_reg_lim; -#endif - - spi_bar = pci_config_read32(0, SPI_PCI_DEV, SPI_PCI_FUN, PCI_BAR0_OFFSET); - spi_bar &= PCI_BAR_MASK; - spi_cmd = - pci_config_read32(0, SPI_PCI_DEV, SPI_PCI_FUN, PCI_COMMAND_OFFSET); - pci_config_write32(0, SPI_PCI_DEV, SPI_PCI_FUN, PCI_COMMAND_OFFSET, - spi_cmd | PCI_COMMAND_MEM_SPACE); - - /* The Flash Protected Range register has the same base/limit - * layout as the Flash Region register: take the BIOS region - * (FREG1, flash region 1) and enable read and write protection - * on it. The SPI registers live in the BAR's memory-mapped - * space, not in PCI configuration space. */ - reg = mmio_read32(spi_bar + SPI_FREG1); -#if defined(DEBUG) - bios_reg_base = (reg & SPI_FREG_BASE_MASK) << SPI_FREG_ADDR_SHIFT; - bios_reg_lim = ((reg & SPI_FREG_LIMIT_MASK) >> SPI_FREG_LIMIT_SHIFT) - << SPI_FREG_ADDR_SHIFT; - wolfBoot_printf("Bios reg base: 0x%x lim: 0x%x\r\n", bios_reg_base, - bios_reg_lim); -#endif - reg |= (SPI_FPR_RPE) | (SPI_FPR_WPE); - mmio_write32(spi_bar + SPI_FPR0, reg); - if ((mmio_read32(spi_bar + SPI_FPR0) & - (SPI_FPR_RPE | SPI_FPR_WPE)) != (SPI_FPR_RPE | SPI_FPR_WPE)) { - ret = -1; - } - - /* lock down BIOS register configuration */ - reg = mmio_read32(spi_bar + SPI_BIOS_HSFSTS_CTL); - reg |= SPI_FLOCKDN; - mmio_write32(spi_bar + SPI_BIOS_HSFSTS_CTL, reg); - if ((mmio_read32(spi_bar + SPI_BIOS_HSFSTS_CTL) & SPI_FLOCKDN) == 0) { - ret = -1; - } - - /* restore original cmd */ - pci_config_write32(0, SPI_PCI_DEV, SPI_PCI_FUN, PCI_COMMAND_OFFSET, spi_cmd); - return ret; -} - -int hal_flash_protect(haladdr_t address, int len) -{ - (void)address; - (void)len; - - /* The TGL BIOS region covers the bootloader partition, so the - * hook's address/len are the same range FREG1 describes. */ - return tgl_lock_bios_region(); -} - void hal_init(void) { gdt_setup_table(); diff --git a/hal/kontron_vx3060_s2_loader.c b/hal/nai_68int6.c similarity index 66% rename from hal/kontron_vx3060_s2_loader.c rename to hal/nai_68int6.c index 5e6b75698f..3b90d90981 100644 --- a/hal/kontron_vx3060_s2_loader.c +++ b/hal/nai_68int6.c @@ -1,4 +1,6 @@ -/* kontron_vx3060_s2_loader.c +/* nai_68int6.c + * + * HAL for the NAI 68INT6 3U OpenVPX SBC (Intel Tiger Lake UP3). * * Copyright (C) 2026 wolfSSL Inc. * @@ -6,7 +8,7 @@ * * wolfBoot is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or + * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * wolfBoot is distributed in the hope that it will be useful, @@ -20,47 +22,57 @@ */ #include +#include #include -#include #include - -#ifdef __WOLFBOOT #include +#include +#include +#include +#include - -static void panic(void); +#ifdef __WOLFBOOT void hal_init(void) { + gdt_setup_table(); + gdt_update_segments(); + fsp_init_silicon(); } void hal_prepare_boot(void) { } -#endif +#endif /* __WOLFBOOT */ -int hal_flash_write(uint32_t address, const uint8_t *data, int len) +int hal_flash_write(uintptr_t address, const uint8_t *data, int len) { + (void)address; + (void)data; + (void)len; return 0; } -void hal_flash_unlock(void) +int hal_flash_erase(uintptr_t address, int len) { + (void)address; + (void)len; + return 0; } -void hal_flash_lock(void) +void hal_flash_unlock(void) { } -int hal_flash_erase(uint32_t address, int len) +void hal_flash_lock(void) { - return 0; } +/* No automatic fallback: a verification failure is terminal on this + * target rather than silently reverting. */ int wolfBoot_fallback_is_possible(void) { return 0; - } int wolfBoot_dualboot_candidate(void) @@ -75,7 +87,7 @@ void* hal_get_primary_address(void) void* hal_get_update_address(void) { - return (void*)0; + return (void*)0; } void *hal_get_dts_address(void) @@ -87,8 +99,3 @@ void *hal_get_dts_update_address(void) { return 0; } - -static void panic(void) -{ - while(1) {} -} diff --git a/hal/x86_fsp_qemu_loader.c b/hal/stub_loader.c similarity index 72% rename from hal/x86_fsp_qemu_loader.c rename to hal/stub_loader.c index bae48681c5..41e840c820 100644 --- a/hal/x86_fsp_qemu_loader.c +++ b/hal/stub_loader.c @@ -1,4 +1,6 @@ -/* x86_fsp_qemu_loader.c +/* stub_loader.c + * + * Generic stage1 loader HAL for x86 FSP targets. * * Copyright (C) 2026 wolfSSL Inc. * @@ -6,7 +8,7 @@ * * wolfBoot is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or + * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * wolfBoot is distributed in the hope that it will be useful, @@ -19,10 +21,14 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +/* x86 FSP stage1 runs XIP before memory init and only authenticates stage2, + * so every HAL entry point is a stub. */ + #include +#include +#include #include #include -#include #ifdef __WOLFBOOT #include @@ -34,10 +40,13 @@ void hal_init(void) void hal_prepare_boot(void) { } -#endif +#endif /* __WOLFBOOT */ -int hal_flash_write(uintptr_t address, const uint8_t *data, int len) +int hal_flash_write(haladdr_t address, const uint8_t *data, int len) { + (void)address; + (void)data; + (void)len; return 0; } @@ -49,15 +58,16 @@ void hal_flash_lock(void) { } -int hal_flash_erase(uintptr_t address, int len) +int hal_flash_erase(haladdr_t address, int len) { + (void)address; + (void)len; return 0; } int wolfBoot_fallback_is_possible(void) { return 0; - } int wolfBoot_dualboot_candidate(void) @@ -72,7 +82,7 @@ void* hal_get_primary_address(void) void* hal_get_update_address(void) { - return (void*)0; + return (void*)0; } void *hal_get_dts_address(void) @@ -84,4 +94,3 @@ void *hal_get_dts_update_address(void) { return 0; } - diff --git a/hal/x86_fsp_tgl.c b/hal/x86_fsp_tgl.c index 8ff2832d29..b0f5889d43 100644 --- a/hal/x86_fsp_tgl.c +++ b/hal/x86_fsp_tgl.c @@ -27,6 +27,8 @@ #include "uart_drv.h" #include "printf.h" #include "loader.h" +#include "hal.h" +#include "pci.h" #ifdef WOLFBOOT_FSP #include "x86/ahci.h" @@ -35,7 +37,7 @@ #include "x86/tgl_fsp.h" #endif -#if defined(TARGET_kontron_vx3060_s2) +#if defined(TARGET_kontron_vx3060_s2) || defined(TARGET_nai_68int6) #define PCI_AHCI_BUS 0 #define PCI_AHCI_DEV 0x17 #define PCI_AHCI_FUN 0 @@ -106,5 +108,88 @@ int x86_fsp_tgl_init_sata(uint32_t *bar) *bar = sata_bar; return 0; } + +#ifdef WOLFBOOT_TGL +#define SPI_PCI_DEV 31 +#define SPI_PCI_FUN 5 +#define SPI_BAR_OFF 0x10 +/* Tiger Lake SPI controller register offsets, memory-mapped at the + * BAR0 base. FREG1 holds the BIOS flash region base/limit (region 0 + * is the flash descriptor); FPR0 is the protected range register + * with the same base/limit layout. Offsets per the Intel PCH SPI + * register map (see drivers/spi/spi-intel.c in the Linux kernel): + * FDATA0-15 at 0x10-0x4C, FRACC at 0x50, FREG0-7 at 0x54-0x74, + * FPR0-4 at 0x84-0x9C. */ +#define SPI_FREG1 0x58 +#define SPI_FREG_BASE_MASK (0x7fffU << 0) +#define SPI_FREG_LIMIT_MASK (0x7fffU << 16) +#define SPI_FREG_LIMIT_SHIFT (16) +#define SPI_FREG_ADDR_SHIFT (12) +#define SPI_FPR0 (0x84) +#define SPI_FPR_WPE (1U << 31) +#define SPI_FPR_RPE (1U << 15) +#define SPI_BIOS_HSFSTS_CTL (0x4) +#define SPI_FLOCKDN (1U << 15) + +int tgl_lock_bios_region(void) +{ + uint32_t spi_bar, spi_cmd; + uint32_t reg; + int ret = 0; + +#if defined(DEBUG) + uint32_t bios_reg_base, bios_reg_lim; +#endif + + spi_bar = pci_config_read32(0, SPI_PCI_DEV, SPI_PCI_FUN, PCI_BAR0_OFFSET); + spi_bar &= PCI_BAR_MASK; + spi_cmd = + pci_config_read32(0, SPI_PCI_DEV, SPI_PCI_FUN, PCI_COMMAND_OFFSET); + pci_config_write32(0, SPI_PCI_DEV, SPI_PCI_FUN, PCI_COMMAND_OFFSET, + spi_cmd | PCI_COMMAND_MEM_SPACE); + + /* The Flash Protected Range register has the same base/limit + * layout as the Flash Region register: take the BIOS region + * (FREG1, flash region 1) and enable read and write protection + * on it. The SPI registers live in the BAR's memory-mapped + * space, not in PCI configuration space. */ + reg = mmio_read32(spi_bar + SPI_FREG1); +#if defined(DEBUG) + bios_reg_base = (reg & SPI_FREG_BASE_MASK) << SPI_FREG_ADDR_SHIFT; + bios_reg_lim = ((reg & SPI_FREG_LIMIT_MASK) >> SPI_FREG_LIMIT_SHIFT) + << SPI_FREG_ADDR_SHIFT; + wolfBoot_printf("Bios reg base: 0x%x lim: 0x%x\r\n", bios_reg_base, + bios_reg_lim); +#endif + reg |= (SPI_FPR_RPE) | (SPI_FPR_WPE); + mmio_write32(spi_bar + SPI_FPR0, reg); + if ((mmio_read32(spi_bar + SPI_FPR0) & + (SPI_FPR_RPE | SPI_FPR_WPE)) != (SPI_FPR_RPE | SPI_FPR_WPE)) { + ret = -1; + } + + /* lock down BIOS register configuration */ + reg = mmio_read32(spi_bar + SPI_BIOS_HSFSTS_CTL); + reg |= SPI_FLOCKDN; + mmio_write32(spi_bar + SPI_BIOS_HSFSTS_CTL, reg); + if ((mmio_read32(spi_bar + SPI_BIOS_HSFSTS_CTL) & SPI_FLOCKDN) == 0) { + ret = -1; + } + + /* restore original cmd */ + pci_config_write32(0, SPI_PCI_DEV, SPI_PCI_FUN, PCI_COMMAND_OFFSET, spi_cmd); + return ret; +} + +int hal_flash_protect(haladdr_t address, int len) +{ + (void)address; + (void)len; + + /* The TGL BIOS region covers the bootloader partition, so the + * hook's address/len are the same range FREG1 describes. */ + return tgl_lock_bios_region(); +} +#endif /* WOLFBOOT_TGL */ #endif diff --git a/include/x86/fsp_config.h b/include/x86/fsp_config.h new file mode 100644 index 0000000000..3689c638fd --- /dev/null +++ b/include/x86/fsp_config.h @@ -0,0 +1,44 @@ +#ifndef FSP_CONFIG_H +#define FSP_CONFIG_H + +#include + +#ifndef FSP_HYPERTHREADING +#define FSP_HYPERTHREADING 0 +#endif +#if FSP_HYPERTHREADING +/* The MADT and MP table list four single-thread cores (APIC IDs 0/2/4/6). */ +#error "FSP_HYPERTHREADING=1 needs sibling threads in the MADT and MP table" +#endif + +#ifndef FSP_INTEL_SPEED_STEP +#define FSP_INTEL_SPEED_STEP 0 +#endif + +#ifndef FSP_INTEL_SPEED_SHIFT +#define FSP_INTEL_SPEED_SHIFT 0 +#endif + +#define FSP_BOOT_FREQ_MAX_NO_TURBO 0x1 +#ifndef FSP_BOOT_PERFORMANCE_MODE +#define FSP_BOOT_PERFORMANCE_MODE FSP_BOOT_FREQ_MAX_NO_TURBO +#endif + +#ifndef FSP_C_STATES +#define FSP_C_STATES 0 +#endif + +#ifndef FSP_TXT +#define FSP_TXT 0 +#endif + +#ifndef FSP_IN_BAND_ECC +#define FSP_IN_BAND_ECC 1 +#endif + +/* TM1/TM2 throttling at the thermal-control limit, the FSP default */ +#ifndef FSP_THERMAL_THROTTLING +#define FSP_THERMAL_THROTTLING 1 +#endif + +#endif diff --git a/include/x86/gpio_config.h b/include/x86/gpio_config.h new file mode 100644 index 0000000000..73bcd468fe --- /dev/null +++ b/include/x86/gpio_config.h @@ -0,0 +1,1097 @@ +/** + * @file gpio_config.h + * + */ + +#ifndef GPIO_CONFIG_H +#define GPIO_CONFIG_H + +#include + +#define GPIO_COMM_4_PORT_ID 0x6a +#define GPIO_COMM_0_PORT_ID (0x6e) +#define GPIO_COMM_1_PORT_ID (0x6d) +#define GPIO_COMM_5_PORT_ID (0x69) +#define GPIO_MODE_NATIVE_1 0x01 +#define GPIO_MODE_GPIO (0x0) +#define GPIO_RESET_PLTRST 0x02 +#define GPIO_RESET_HOSTDEEPRESET (0x01) +#define GPIO_DIR_INPUT (0x1) +#define GPIO_DIR_OUTPUT (0x2) +#define GPIO_INTERRUPT_DISABLE (0x0) +#define GPIO_INTERRUPT_SCI (1 << 2) +#define GPIO_TERM_NONE (0x0) +#define GPIO_RXEVCONF_LEVEL (0) +#define GPIO_MODE_SHIFT 10 +#define GPIO_MODE_MASK (0x7) << GPIO_MODE_SHIFT +#define GPIO_RESET_SHIFT 30 +#define GPIO_RESET_MASK (0x3) << GPIO_RESET_SHIFT +#define GPIO_DIR_SHIFT (0x8) +#define GPIO_DIR_MASK (0x3) << GPIO_DIR_SHIFT +#define GPIO_RXINV_SHIFT (23) +#define GPIO_RXINV_MASK (0x1) << 23 +#define GPIO_INTERRUPT_SHIFT (17) +#define GPIO_INTERRUPT_MASK (0xf) << GPIO_INTERRUPT_SHIFT +#define GPIO_TERM_SHIFT (10) +#define GPIO_TERM_MASK (0xf) << GPIO_TERM_SHIFT +#define GPIO_RXEVCONF_SHIFT (25) +#define GPIO_RXEVCONF_MASK (0x3) << GPIO_RXEVCONF_SHIFT + +#define GPIO_GPPC_B9_CFG_OFF (0x790) +#define GPIO_GPPC_B10_CFG_OFF (0x7a0) +#define GPIO_GPPC_C6_CFG_OFF (0x760) +#define GPIO_GPPC_C7_CFG_OFF (0x770) +#define GPIO_GPPC_C8_CFG_OFF (0x780) +#define GPIO_GPPC_C9_CFG_OFF (0x790) +#define GPIO_GPPC_C10_CFG_OFF (0x7a0) +#define GPIO_GPPC_C11_CFG_OFF (0x7b0) +#define GPIO_GPPC_C12_CFG_OFF (0x7c0) +#define GPIO_GPPC_C13_CFG_OFF (0x7d0) +#define GPIO_GPPC_C14_CFG_OFF (0x7e0) +#define GPIO_GPPC_C15_CFG_OFF (0x7f0) +#define GPIO_GPPC_C20_CFG_OFF (0x840) +#define GPIO_GPPC_C21_CFG_OFF (0x850) +#define GPIO_GPPC_C22_CFG_OFF (0x860) +#define GPIO_GPPC_D0_CFG_OFF (0x900) +#define GPIO_GPPC_D1_CFG_OFF (0x910) +#define GPIO_GPPC_D2_CFG_OFF (0x920) +#define GPIO_GPP_R0_CFG_OFF (0x700) +#define GPIO_GPP_R1_CFG_OFF (0x710) +#define GPIO_GPP_R2_CFG_OFF (0x720) +#define GPIO_GPP_R3_CFG_OFF (0x730) +#define GPIO_GPP_R4_CFG_OFF (0x740) +#define GPIO_GPP_R5_CFG_OFF (0x750) +#define GPIO_GPPC_A8_CFG_OFF (0xa20) +#define GPIO_GPPC_E12_CFG_OFF (0xb30) +#define GPIO_GPPC_E15_CFG_OFF (0xb60) +#define GPIO_GPPC_E16_CFG_OFF (0xb70) +#define GPIO_GPPC_F2_CFG_OFF (0xba0) +#define GPIO_GPPC_F4_CFG_OFF (0xbc0) +#define GPIO_GPPC_F5_CFG_OFF (0xbd0) +#define GPIO_GPPC_F9_CFG_OFF (0x910) +#define GPIO_GPPC_A9_CFG_OFF (0xa30) +#define GPIO_GPPC_T2_CFG_OFF (0x8c0) +#define GPIO_GPPC_T3_CFG_OFF (0x8d0) + +#define GPIO_OWN_MASK (0x3) +/* struct tgl_gpio, enum gpio_config_flags and struct tgl_gpio_conf are defined + * by the includer (src/x86/tgl_fsp.c); only the pad tables live here. */ + +#if defined (TARGET_kontron_vx3060_s2) || defined (TARGET_nai_68int6) + +#if defined (TARGET_nai_68int6) +/* Pad settings snapshotted from a running 68INT6 with the stock BIOS. + * HOSTSW_OWN and PADCFGLOCK are not captured. */ + +/* Console UART2 pads (C20/C21), muxed before anything prints. */ +static const struct tgl_gpio_conf gpio_table_tempram[] = { + /* PAD C20 (DW0=0x44000702 DW1=0x00020122) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C20_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = 0x03, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD C21 (DW0=0x44000700 DW1=0x00000123) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C21_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = 0x03, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, +}; + +/* Nothing is required between tempram and memory init on this board. */ +static const struct tgl_gpio_conf gpio_table_premem[] = { +}; + +/* The rest of the stock pad settings, applied after silicon init. */ +static const struct tgl_gpio_conf gpio_table_presilicon[] = { + /* PAD C8 (DW0=0x44000702 DW1=0x00020176) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C8_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = 0x03, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD C9 (DW0=0x44000700 DW1=0x00000177) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C9_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = 0x03, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD C10 (DW0=0x44000300 DW1=0x00000018) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C10_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = 0x03, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD C11 (DW0=0x44000300 DW1=0x00000019) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C11_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = 0x03, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD C12 (DW0=0x44000702 DW1=0x0002011A) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C12_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = 0x03, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD C13 (DW0=0x44000700 DW1=0x0000011B) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C13_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = 0x03, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD C14 (DW0=0x40100102 DW1=0x0000301C) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C14_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_term = 0x0C, + .gpio_interrupt = 0x08, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x00, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD C15 (DW0=0x84000201 DW1=0x0000001D) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C15_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_OUTPUT, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_PLTRST}, + /* PAD F2 (DW0=0x44001600 DW1=0x00003C3F) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_F2_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = 0x05, + .gpio_dir = GPIO_DIR_OUTPUT, + .gpio_term = 0x0F, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD A8 (DW0=0x44000B00 DW1=0x00000048) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_0_PORT_ID, + .cfg_offset = GPIO_GPPC_A8_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = 0x02, + .gpio_dir = 0x03, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD E12 (DW0=0x84000201 DW1=0x00000038) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_E12_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_OUTPUT, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_PLTRST}, + /* PAD E15 (DW0=0x44000B00 DW1=0x0000003B) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_E15_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = 0x02, + .gpio_dir = 0x03, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD E16 (DW0=0x84000102 DW1=0x0000003C) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_E16_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_PLTRST}, + /* PAD F9 (DW0=0x84000201 DW1=0x0000005F) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_F9_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_OUTPUT, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_PLTRST}, + /* PAD A9 (DW0=0x44000B00 DW1=0x00000049) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_0_PORT_ID, + .cfg_offset = GPIO_GPPC_A9_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = 0x02, + .gpio_dir = 0x03, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD F4 (DW0=0x44001600 DW1=0x00003C41) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_F4_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = 0x05, + .gpio_dir = GPIO_DIR_OUTPUT, + .gpio_term = 0x0F, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD F5 (DW0=0x84000200 DW1=0x00000042) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_F5_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_OUTPUT, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_PLTRST}, + /* PAD T2 (DW0=0x44000B00 DW1=0x00001032) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_0_PORT_ID, + .cfg_offset = GPIO_GPPC_T2_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = 0x02, + .gpio_dir = 0x03, + .gpio_term = 0x04, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD T3 (DW0=0x44000B00 DW1=0x00001033) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_0_PORT_ID, + .cfg_offset = GPIO_GPPC_T3_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = 0x02, + .gpio_dir = 0x03, + .gpio_term = 0x04, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD C22 (DW0=0x44000102 DW1=0x00000024) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C22_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD B10 (DW0=0x44000300 DW1=0x00000022) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_0_PORT_ID, + .cfg_offset = GPIO_GPPC_B10_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = 0x03, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD B9 (DW0=0x44000300 DW1=0x00000021) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_0_PORT_ID, + .cfg_offset = GPIO_GPPC_B9_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = 0x03, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD C6 (DW0=0x04000702 DW1=0x00000074) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C6_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = 0x03, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = 0x00}, + /* PAD C7 (DW0=0x04000702 DW1=0x00000075) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C7_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = 0x03, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = 0x00}, + /* PAD D1 (DW0=0x84000200 DW1=0x0000002D) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_1_PORT_ID, + .cfg_offset = GPIO_GPPC_D1_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_OUTPUT, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_PLTRST}, + /* PAD D0 (DW0=0x84000102 DW1=0x0000002C) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_1_PORT_ID, + .cfg_offset = GPIO_GPPC_D0_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_PLTRST}, + /* PAD D2 (DW0=0x84000201 DW1=0x0000002E) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_1_PORT_ID, + .cfg_offset = GPIO_GPPC_D2_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_OUTPUT, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_PLTRST}, + /* PAD R0 (DW0=0x44000700 DW1=0x0003C058) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_5_PORT_ID, + .cfg_offset = GPIO_GPP_R0_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = 0x03, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD R1 (DW0=0x44000700 DW1=0x0003FC59) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_5_PORT_ID, + .cfg_offset = GPIO_GPP_R1_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = 0x03, + .gpio_term = 0x0F, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD R2 (DW0=0x44000600 DW1=0x0003FC5A) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_5_PORT_ID, + .cfg_offset = GPIO_GPP_R2_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = GPIO_DIR_OUTPUT, + .gpio_term = 0x0F, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD R3 (DW0=0x44000700 DW1=0x0003FC5B) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_5_PORT_ID, + .cfg_offset = GPIO_GPP_R3_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = 0x03, + .gpio_term = 0x0F, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD R4 (DW0=0x44000700 DW1=0x0003C05C) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_5_PORT_ID, + .cfg_offset = GPIO_GPP_R4_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = 0x03, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD R5 (DW0=0x84000200 DW1=0x0000005D) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_5_PORT_ID, + .cfg_offset = GPIO_GPP_R5_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_OUTPUT, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_PLTRST}, +}; + +#else /* TARGET_kontron_vx3060_s2 */ +static const struct tgl_gpio_conf gpio_table_tempram[] = { + /* UART 0 */ + {.gpio = + { + /* PAD C8 */ + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C8_CFG_OFF, + }, + .flags = + (GPIO_SET_DIRECTION | GPIO_SET_MODE | GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_term = GPIO_TERM_NONE, + .gpio_reset = GPIO_RESET_PLTRST}, + {.gpio = + { + /* PAD C9 */ + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C9_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = GPIO_DIR_OUTPUT, + .gpio_term = GPIO_TERM_NONE, + .gpio_reset = GPIO_RESET_PLTRST}, + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C10_CFG_OFF, + }, + .flags = + (GPIO_SET_DIRECTION | GPIO_SET_MODE | GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_term = GPIO_TERM_NONE, + .gpio_dir = GPIO_DIR_OUTPUT, + .gpio_reset = GPIO_RESET_PLTRST}, + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C11_CFG_OFF, + }, + .flags = + (GPIO_SET_DIRECTION | GPIO_SET_MODE | GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_term = GPIO_TERM_NONE, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_reset = GPIO_RESET_PLTRST}, + /* UART - 1*/ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C12_CFG_OFF, + }, + .flags = + (GPIO_SET_DIRECTION | GPIO_SET_MODE | GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_term = GPIO_TERM_NONE, + .gpio_reset = GPIO_RESET_PLTRST}, + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C13_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = GPIO_DIR_OUTPUT, + .gpio_term = GPIO_TERM_NONE, + .gpio_reset = GPIO_RESET_PLTRST}, + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C14_CFG_OFF, + }, + .flags = + (GPIO_SET_DIRECTION | GPIO_SET_MODE | GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_term = GPIO_TERM_NONE, + .gpio_dir = GPIO_DIR_OUTPUT, + .gpio_reset = GPIO_RESET_PLTRST}, + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C15_CFG_OFF, + }, + .flags = + (GPIO_SET_DIRECTION | GPIO_SET_MODE | GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_term = GPIO_TERM_NONE, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_reset = GPIO_RESET_PLTRST}, + /* UART - 2*/ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C20_CFG_OFF, + }, + .flags = + (GPIO_SET_DIRECTION | GPIO_SET_MODE | GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_term = GPIO_TERM_NONE, + .gpio_reset = GPIO_RESET_PLTRST}, + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C21_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = GPIO_DIR_OUTPUT, + .gpio_term = GPIO_TERM_NONE, + .gpio_reset = GPIO_RESET_PLTRST}, +}; + +static const struct tgl_gpio_conf gpio_table_premem[] = { +}; + +static const struct tgl_gpio_conf gpio_table_presilicon[] = { + /* Disable CNVi (Bluetooth Radio Interface) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_F2_CFG_OFF, + }, + .flags = (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_INTERRUPT | + GPIO_SET_RESET), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_OUTPUT, + .gpio_interrupt = GPIO_INTERRUPT_DISABLE, + .gpio_reset = GPIO_RESET_PLTRST}, + /* set to GPIO mode as native functions aren't used */ + {.gpio = + { + .comm_port_id = GPIO_COMM_0_PORT_ID, + .cfg_offset = GPIO_GPPC_A8_CFG_OFF, + }, + .flags = (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_INTERRUPT | + GPIO_SET_RESET), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_interrupt = GPIO_INTERRUPT_DISABLE, + .gpio_reset = GPIO_RESET_PLTRST}, + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_E12_CFG_OFF, + }, + .flags = (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_INTERRUPT | + GPIO_SET_RESET), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_interrupt = GPIO_INTERRUPT_DISABLE, + .gpio_reset = GPIO_RESET_PLTRST}, + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_E15_CFG_OFF, + }, + .flags = (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_INTERRUPT | + GPIO_SET_RESET), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_interrupt = GPIO_INTERRUPT_DISABLE, + .gpio_reset = GPIO_RESET_PLTRST}, + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_E16_CFG_OFF, + }, + .flags = (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_INTERRUPT | + GPIO_SET_RESET), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_interrupt = GPIO_INTERRUPT_DISABLE, + .gpio_reset = GPIO_RESET_PLTRST}, + + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_F9_CFG_OFF, + }, + .flags = (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_INTERRUPT | + GPIO_SET_RESET), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_interrupt = GPIO_INTERRUPT_DISABLE, + .gpio_reset = GPIO_RESET_PLTRST}, + /* test points */ + {.gpio = + { + .comm_port_id = GPIO_COMM_0_PORT_ID, + .cfg_offset = GPIO_GPPC_A9_CFG_OFF, + }, + .flags = (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_INTERRUPT | + GPIO_SET_RESET), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_OUTPUT, + .gpio_interrupt = GPIO_INTERRUPT_DISABLE, + .gpio_reset = GPIO_RESET_PLTRST}, + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_F4_CFG_OFF, + }, + .flags = (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_INTERRUPT | + GPIO_SET_RESET), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_OUTPUT, + .gpio_interrupt = GPIO_INTERRUPT_DISABLE, + .gpio_reset = GPIO_RESET_PLTRST}, + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_F5_CFG_OFF, + }, + .flags = (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_INTERRUPT | + GPIO_SET_RESET), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_OUTPUT, + .gpio_interrupt = GPIO_INTERRUPT_DISABLE, + .gpio_reset = GPIO_RESET_PLTRST}, + /* FUSA_DIAGTEST pads (T2/T3) are intentionally not programmed. */ + /* TPM */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C22_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RXINV | GPIO_SET_RESET | + GPIO_SET_RXEVCONF | GPIO_SET_TERM | GPIO_SET_INTERRUPT), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_rxinv = 1, + .gpio_rxevconf = GPIO_RXEVCONF_LEVEL, + .gpio_interrupt = GPIO_INTERRUPT_SCI, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET, + .gpio_term = GPIO_TERM_NONE}, + /* PCH I2C5 */ + {.gpio = + { + .comm_port_id = GPIO_COMM_0_PORT_ID, + .cfg_offset = GPIO_GPPC_B10_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_INTERRUPT | GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_interrupt = GPIO_INTERRUPT_DISABLE, + .gpio_reset = GPIO_RESET_PLTRST, + .gpio_term = GPIO_TERM_NONE}, + {.gpio = + { + .comm_port_id = GPIO_COMM_0_PORT_ID, + .cfg_offset = GPIO_GPPC_B9_CFG_OFF, + }, + .flags = (GPIO_SET_MODE | GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_reset = GPIO_RESET_PLTRST, + .gpio_term = GPIO_TERM_NONE}, + /* SMLINK1 to PD */ + {/* SM1 CLK */ + .gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C6_CFG_OFF, + }, + .flags = (GPIO_SET_MODE | GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_reset = GPIO_RESET_PLTRST, + .gpio_term = GPIO_TERM_NONE}, + {.gpio = + { + /* SM1 DATA */ + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C7_CFG_OFF, + }, + .flags = (GPIO_SET_MODE | GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_reset = GPIO_RESET_PLTRST, + .gpio_term = GPIO_TERM_NONE}, + /* watchdog */ + {.gpio = + { + /* PLD_WDT_IRQ0 */ + .comm_port_id = GPIO_COMM_1_PORT_ID, + .cfg_offset = GPIO_GPPC_D1_CFG_OFF, + }, + .flags = (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_INTERRUPT | + GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_interrupt = GPIO_INTERRUPT_DISABLE, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET, + .gpio_term = GPIO_TERM_NONE}, + {.gpio = + { + /* PLD_WDT_IRQ1 */ + .comm_port_id = GPIO_COMM_1_PORT_ID, + .cfg_offset = GPIO_GPPC_D0_CFG_OFF, + }, + .flags = (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_INTERRUPT | + GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_interrupt = GPIO_INTERRUPT_DISABLE, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET, + .gpio_term = GPIO_TERM_NONE}, + /* wake from i225/E810/i210/M2_TOP/M2_BOT/XMC */ + {.gpio = + { + .comm_port_id = GPIO_COMM_1_PORT_ID, + .cfg_offset = GPIO_GPPC_D2_CFG_OFF, + }, + .flags = (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_INTERRUPT | + GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_interrupt = GPIO_INTERRUPT_DISABLE, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET, + .gpio_term = GPIO_TERM_NONE}, + /* Audio, disabled */ + {.gpio = + { + .comm_port_id = GPIO_COMM_5_PORT_ID, + .cfg_offset = GPIO_GPP_R0_CFG_OFF, + }, + .flags = (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_INTERRUPT | + GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_interrupt = GPIO_INTERRUPT_DISABLE, + .gpio_reset = GPIO_RESET_PLTRST, + .gpio_term = GPIO_TERM_NONE}, + {.gpio = + { + .comm_port_id = GPIO_COMM_5_PORT_ID, + .cfg_offset = GPIO_GPP_R1_CFG_OFF, + }, + .flags = (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_INTERRUPT | + GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_interrupt = GPIO_INTERRUPT_DISABLE, + .gpio_reset = GPIO_RESET_PLTRST, + .gpio_term = GPIO_TERM_NONE}, + {.gpio = + { + .comm_port_id = GPIO_COMM_5_PORT_ID, + .cfg_offset = GPIO_GPP_R2_CFG_OFF, + }, + .flags = (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_INTERRUPT | + GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_interrupt = GPIO_INTERRUPT_DISABLE, + .gpio_reset = GPIO_RESET_PLTRST, + .gpio_term = GPIO_TERM_NONE}, + {.gpio = + { + .comm_port_id = GPIO_COMM_5_PORT_ID, + .cfg_offset = GPIO_GPP_R3_CFG_OFF, + }, + .flags = (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_INTERRUPT | + GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_interrupt = GPIO_INTERRUPT_DISABLE, + .gpio_reset = GPIO_RESET_PLTRST, + .gpio_term = GPIO_TERM_NONE}, + {.gpio = + { + .comm_port_id = GPIO_COMM_5_PORT_ID, + .cfg_offset = GPIO_GPP_R4_CFG_OFF, + }, + .flags = (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_INTERRUPT | + GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_interrupt = GPIO_INTERRUPT_DISABLE, + .gpio_reset = GPIO_RESET_PLTRST, + .gpio_term = GPIO_TERM_NONE}, + {.gpio = + { + .comm_port_id = GPIO_COMM_5_PORT_ID, + .cfg_offset = GPIO_GPP_R5_CFG_OFF, + }, + .flags = (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_INTERRUPT | + GPIO_SET_RESET | GPIO_SET_TERM), + .gpio_mode = GPIO_MODE_GPIO, + .gpio_dir = GPIO_DIR_INPUT, + .gpio_interrupt = GPIO_INTERRUPT_DISABLE, + .gpio_reset = GPIO_RESET_PLTRST, + .gpio_term = GPIO_TERM_NONE}, + +}; +#endif /* TARGET_nai_68int6 */ + +#endif /* TARGET_kontron_vx3060_s2 || TARGET_nai_68int6 */ + +#endif // GPIO_CONFIG_H diff --git a/include/x86/mptable.h b/include/x86/mptable.h index 41dab2f75f..29ad25e53c 100644 --- a/include/x86/mptable.h +++ b/include/x86/mptable.h @@ -150,7 +150,7 @@ enum mp_irq_source_types { #define MP_CPU_NUM_ENTRY 1 #endif -#ifdef TARGET_kontron_vx3060_s2 +#if defined(TARGET_kontron_vx3060_s2) || defined(TARGET_nai_68int6) #define MP_IOAPIC_NUM_ENTRY 1 #define MP_INTSRC_NUM_ENTRY 17 #define MP_LINTSRC_NUM_ENTRY 0 diff --git a/include/x86/tgl_fsp.h b/include/x86/tgl_fsp.h index b3b2b14861..5145fbc185 100644 --- a/include/x86/tgl_fsp.h +++ b/include/x86/tgl_fsp.h @@ -24,5 +24,6 @@ #include int x86_fsp_tgl_init_sata(uint32_t *bar); +int tgl_lock_bios_region(void); #endif /* TGL_FSP_H */ diff --git a/options.mk b/options.mk index 350620f243..691ebcfd84 100644 --- a/options.mk +++ b/options.mk @@ -54,8 +54,16 @@ WOLFBOOT_TIME_TEST?=0 # Generate a minimal ACPI table set (RSDP/XSDT/FADT/MADT/MCFG + DSDT) for the # x86 FSP Linux payload, so the OS gets the interrupt model and PCI routing. ifeq ($(ACPI),1) + ifneq ($(TARGET),nai_68int6) + $(error ACPI=1 tables (CPU list, DSDT routing) are specific to nai_68int6) + endif CFLAGS+=-D"WOLFBOOT_ACPI" endif +ifeq ($(TARGET),nai_68int6) + ifneq ($(ACPI),1) + $(error nai_68int6 needs ACPI=1: its PCI interrupt routing is only in the DSDT) + endif +endif # TPM NV version floor and boot audit counters (see docs/Targets.md). # HALT_ON_FAILURE also halts when the TPM state cannot be read or locked. @@ -1601,6 +1609,7 @@ ifeq ($(FSP), 1) X86_UART_BASE \ X86_UART_REG_WIDTH \ X86_UART_MMIO \ + X86_UART_NUMBER \ PCH_HAS_PCR \ PCI_USE_ECAM \ PCH_PCR_BASE \ diff --git a/src/boot_x86_fsp_payload.c b/src/boot_x86_fsp_payload.c index d7fb793ea1..c976687fc3 100644 --- a/src/boot_x86_fsp_payload.c +++ b/src/boot_x86_fsp_payload.c @@ -64,6 +64,15 @@ uint8_t mb2_boot_info[MAX_MB2_BOOT_INFO_SIZE]; #ifdef TARGET_kontron_vx3060_s2 static char *cmdline = "apic=verbose acpi=no pci=debug console=ttyS0,115200 debug"; +#elif defined(TARGET_nai_68int6) +#define CMDLINE_STR_(x) #x +#define CMDLINE_STR(x) CMDLINE_STR_(x) +/* earlycon covers LPSS UART2 at its FSP-assigned base until the 8250 driver + * binds it as ttyS4 */ +static char *cmdline = + "earlycon=uart8250,mmio32," CMDLINE_STR(X86_UART_BASE) ",115200n8 " + "console=ttyS4,115200 console=tty0 " + "root=/dev/sda2 rw rootwait"; #elif TARGET_x86_fsp_qemu static char *cmdline = "console=ttyS0,115200 pci=earlydump debug"; #else diff --git a/src/pci.c b/src/pci.c index 934f8ed38a..c969e772e5 100644 --- a/src/pci.c +++ b/src/pci.c @@ -390,10 +390,18 @@ static int pci_pre_enum_cb(uint8_t bus, uint8_t dev, uint8_t fun) #ifdef WOLFBOOT_TGL if (bus != 0) return 0; - /* don't change UART mapping */ - if (dev == 0x1e && (fun == 0 || fun == 1)) { + /* Leave the console UART BAR at X86_UART_BASE. LPSS UART0/1 are + * 00:1e.0/1, UART2 is 00:19.2. */ +#if defined(X86_UART_NUMBER) && (X86_UART_NUMBER == 2) + if (dev == 0x19 && fun == 2) return 1; - } +#elif defined(X86_UART_NUMBER) && (X86_UART_NUMBER == 1) + if (dev == 0x1e && fun == 1) + return 1; +#else + if (dev == 0x1e && fun == 0) + return 1; +#endif /* PMC BARs shouldn't be programmed as per FSP integration guide */ if (dev == 31 && fun == 2) return 1; diff --git a/src/tpm.c b/src/tpm.c index c44c1767ae..3df6b66a9c 100644 --- a/src/tpm.c +++ b/src/tpm.c @@ -477,7 +477,7 @@ int wolfBoot_tpm2_extend(uint8_t pcrIndex, uint8_t* hash, int line) int rc; #ifdef WOLFBOOT_DEBUG_TPM uint8_t digest[WOLFBOOT_TPM_PCR_DIG_SZ]; - int digestSz = 0; + int digestSz = (int)sizeof(digest); #endif /* clear auth session for PCR */ @@ -502,7 +502,8 @@ int wolfBoot_tpm2_extend(uint8_t pcrIndex, uint8_t* hash, int line) wolfBoot_printf("PCR %d: Res %d, Digest Sz %d\n", pcrIndex, read_rc, digestSz); - wolfBoot_print_bin(digest, digestSz); + if (read_rc == 0) + wolfBoot_print_bin(digest, digestSz); } else { wolfBoot_printf("Measure boot failed! Index %d, %x (%s)\n", diff --git a/src/update_disk.c b/src/update_disk.c index d0343aaa29..4e3aedb2b6 100644 --- a/src/update_disk.c +++ b/src/update_disk.c @@ -91,6 +91,11 @@ static uint8_t disk_encrypt_nonce[ENCRYPT_NONCE_SIZE]; #define BOOT_PART_A 5 #define BOOT_PART_B 6 #endif +#ifdef TARGET_nai_68int6 + /* Signed kernels live in the 3rd and 4th GPT partitions (sda3/sda4). */ + #define BOOT_PART_A 2 + #define BOOT_PART_B 3 +#endif #endif /* WOLFBOOT_FSP */ /* Default values for BOOT_DISK, BOOT_PART_A and BOOT_PART_B */ diff --git a/src/x86/mptable.c b/src/x86/mptable.c index 0edd871d9e..2d1f294724 100644 --- a/src/x86/mptable.c +++ b/src/x86/mptable.c @@ -27,7 +27,7 @@ #define LOCAL_APIC_ID 0xfee00020 #define LOCAL_APIC_VER 0xfee00030 -#ifdef TARGET_kontron_vx3060_s2 +#if defined(TARGET_kontron_vx3060_s2) || defined(TARGET_nai_68int6) #define ISA_BUS 0x4 /* TGL mptable */ @@ -518,7 +518,7 @@ void mptable_setup(void) _mp->mpce_processor[0]._res[0] = _mp->mpce_processor[0]._res[1] = 0; -#ifdef TARGET_kontron_vx3060_s2 +#if defined(TARGET_kontron_vx3060_s2) || defined(TARGET_nai_68int6) { int i; for (i = 1; i < 4; ++i) { diff --git a/src/x86/tgl_fsp.c b/src/x86/tgl_fsp.c index 4b6f88a96f..03066e6f82 100644 --- a/src/x86/tgl_fsp.c +++ b/src/x86/tgl_fsp.c @@ -47,6 +47,9 @@ #include #include #include +#ifdef TARGET_nai_68int6 +#include +#endif #define PCR_DMI_PORT_ID 0x88 #define PCR_DMI_LPCLGIR1 0x2730 @@ -238,6 +241,9 @@ static int fsp_set_memory_cfg(FSPM_UPD *udp) FSP_M_CONFIG *mem_cfg; mem_cfg = &udp->FspmConfig; + /* Kontron VX3060-S2 DQ maps and DDR tuning; other boards keep the FSP-M + * defaults. */ +#ifdef TARGET_kontron_vx3060_s2 mem_cfg->PlatformMemorySize = 93806592; mem_cfg->DqMapCpu2DramMc0Ch0[0] = 13; mem_cfg->DqMapCpu2DramMc0Ch0[1] = 12; @@ -367,6 +373,7 @@ static int fsp_set_memory_cfg(FSPM_UPD *udp) mem_cfg->DqMapCpu2DramMc1Ch3[13] = 9; mem_cfg->DqMapCpu2DramMc1Ch3[14] = 8; mem_cfg->DqMapCpu2DramMc1Ch3[15] = 13; +#endif /* TARGET_kontron_vx3060_s2 */ mem_cfg->TsegSize = 8388608; mem_cfg->SpdAddressTable[0] = 160; mem_cfg->SpdAddressTable[1] = 0; @@ -394,8 +401,10 @@ static int fsp_set_memory_cfg(FSPM_UPD *udp) mem_cfg->VtdBaseAddress[7] = 0; mem_cfg->VtdBaseAddress[8] = 0; mem_cfg->UserBd = 5; +#ifdef TARGET_kontron_vx3060_s2 mem_cfg->RMT = 1; mem_cfg->NModeSupport = 2; +#endif /* TARGET_kontron_vx3060_s2 */ mem_cfg->PchHdaEnable = 0; mem_cfg->GttMmAdr = 2147483648; mem_cfg->DdiPort2Hpd = 1; @@ -404,14 +413,22 @@ static int fsp_set_memory_cfg(FSPM_UPD *udp) mem_cfg->DdiPort4Ddc = 1; mem_cfg->EnableC6Dram = 0; mem_cfg->HyperThreading = 0; +#ifdef TARGET_kontron_vx3060_s2 mem_cfg->CpuRatio = 0; mem_cfg->FClkFrequency = 1; +#endif /* TARGET_kontron_vx3060_s2 */ +#ifdef TARGET_nai_68int6 + mem_cfg->VmxEnable = 1; +#else mem_cfg->VmxEnable = 0; +#endif mem_cfg->BiosGuard = 0; mem_cfg->EnableSgx = 0; +#ifdef TARGET_kontron_vx3060_s2 mem_cfg->TxtDprMemorySize = 4194304; mem_cfg->BiosAcmBase = 4285267968; mem_cfg->ConfigTdpLevel = 2; +#endif /* TARGET_kontron_vx3060_s2 */ mem_cfg->PcieClkSrcUsage[0] = 128; mem_cfg->PcieClkSrcUsage[1] = 128; mem_cfg->PcieClkSrcUsage[2] = 128; @@ -434,7 +451,9 @@ static int fsp_set_memory_cfg(FSPM_UPD *udp) * set from X86_UART_NUMBER or the console changes between phases - output * appears for part of the boot and then stops, reading as a hang. */ mem_cfg->SerialIoUartDebugControllerNumber = X86_UART_NUMBER; +#ifdef TARGET_kontron_vx3060_s2 mem_cfg->MrcSafeConfig = 1; +#endif /* TARGET_kontron_vx3060_s2 */ mem_cfg->TcssItbtPcie0En = 0; mem_cfg->TcssItbtPcie1En = 0; mem_cfg->TcssItbtPcie2En = 0; @@ -442,9 +461,12 @@ static int fsp_set_memory_cfg(FSPM_UPD *udp) mem_cfg->TcssXdciEn = 1; mem_cfg->TcssDma0En = 0; mem_cfg->TcssDma1En = 0; +#ifdef TARGET_kontron_vx3060_s2 mem_cfg->RMC = 0; mem_cfg->EccSupport = 0; +#endif /* TARGET_kontron_vx3060_s2 */ mem_cfg->Ibecc = 1; +#ifdef TARGET_kontron_vx3060_s2 mem_cfg->IbeccParity = 1; mem_cfg->RankInterleave = 0; mem_cfg->EnhancedInterleave = 0; @@ -452,9 +474,12 @@ static int fsp_set_memory_cfg(FSPM_UPD *udp) mem_cfg->ChHashMask = 12492; mem_cfg->PowerDownMode = 0; mem_cfg->SafeMode = 1; +#endif /* TARGET_kontron_vx3060_s2 */ mem_cfg->UsbTcPortEnPreMem = 15; mem_cfg->WrcFeatureEnable = 0; +#ifdef TARGET_kontron_vx3060_s2 mem_cfg->McParity = 1; +#endif /* TARGET_kontron_vx3060_s2 */ mem_cfg->PchHdaSdiEnable[0] = 1; mem_cfg->PchHdaSdiEnable[1] = 1; mem_cfg->PchHdaAudioLinkDmicEnable[0] = 0; @@ -470,7 +495,9 @@ static int fsp_set_memory_cfg(FSPM_UPD *udp) mem_cfg->PchHdaAudioLinkSndwEnable[1] = 1; mem_cfg->PchHdaAudioLinkSndwEnable[2] = 0; mem_cfg->PchHdaAudioLinkSndwEnable[3] = 0; +#ifdef TARGET_kontron_vx3060_s2 mem_cfg->SkipCpuReplacementCheck = 1; +#endif /* TARGET_kontron_vx3060_s2 */ mem_cfg->SerialIoUartDebugMode = 4; mem_cfg->PcieRefPllSsc = 0; return 0; @@ -497,7 +524,9 @@ static void fsp_set_silicon_cfg(FSPS_UPD *fsps) { FSP_S_CONFIG *upd = &fsps->FspsConfig; +#ifdef TARGET_kontron_vx3060_s2 upd->GraphicsConfigPtr = 2024131364; +#endif /* TARGET_kontron_vx3060_s2 */ upd->SataPortsEnable[0] = 1; upd->SataPortsEnable[1] = 1; @@ -661,18 +690,33 @@ static void fsp_set_silicon_cfg(FSPS_UPD *fsps) upd->IomTypeCPortPadCfg[7] = 0; upd->UsbTcPortEn = 15; upd->AesEnable = 0; +#ifdef TARGET_kontron_vx3060_s2 upd->PchWriteProtectionEnable[0] = 1; upd->PchWriteProtectionEnable[1] = 1; +#else + upd->PchWriteProtectionEnable[0] = 0; + upd->PchWriteProtectionEnable[1] = 0; +#endif /* TARGET_kontron_vx3060_s2 */ upd->PchWriteProtectionEnable[2] = 0; upd->PchWriteProtectionEnable[3] = 0; upd->PchWriteProtectionEnable[4] = 0; +#ifdef TARGET_kontron_vx3060_s2 upd->PchProtectedRangeLimit[0] = 13679; upd->PchProtectedRangeLimit[1] = 16383; +#else + upd->PchProtectedRangeLimit[0] = 0; + upd->PchProtectedRangeLimit[1] = 0; +#endif /* TARGET_kontron_vx3060_s2 */ upd->PchProtectedRangeLimit[2] = 0; upd->PchProtectedRangeLimit[3] = 0; upd->PchProtectedRangeLimit[4] = 0; +#ifdef TARGET_kontron_vx3060_s2 upd->PchProtectedRangeBase[0] = 13312; upd->PchProtectedRangeBase[1] = 13744; +#else + upd->PchProtectedRangeBase[0] = 0; + upd->PchProtectedRangeBase[1] = 0; +#endif /* TARGET_kontron_vx3060_s2 */ upd->PchProtectedRangeBase[2] = 0; upd->PchProtectedRangeBase[3] = 0; upd->PchProtectedRangeBase[4] = 0; @@ -953,9 +997,16 @@ int fsp_machine_update_s_parameters(uint8_t *default_s_params) upd->MicrocodeRegionBase = 0x0; upd->MicrocodeRegionSize = 0x0; +#ifdef TARGET_nai_68int6 + /* No table: the FSP's built-in PCH interrupt routing matches the DSDT + * _PRT; a partial table leaves other devices' INTx unrouted. */ + upd->DevIntConfigPtr = 0; + upd->NumOfDevIntConfig = 0; +#else /* we can assume that is under 4gb */ upd->DevIntConfigPtr = (uint32_t)(uintptr_t)mPchHDevIntConfig; upd->NumOfDevIntConfig = sizeof(mPchHDevIntConfig)/sizeof(mPchHDevIntConfig[0]); +#endif upd->SataEnable = 1; upd->SataMode = 0; upd->SataSalpSupport = 0; @@ -978,6 +1029,13 @@ int fsp_machine_update_s_parameters(uint8_t *default_s_params) memset(upd->PcieRpHotPlug, 0, sizeof(upd->PcieRpHotPlug)); memset(upd->CpuPcieRpHotPlug, 0, sizeof(upd->CpuPcieRpHotPlug)); +#ifdef TARGET_nai_68int6 + upd->Eist = FSP_INTEL_SPEED_STEP; + upd->Hwp = FSP_INTEL_SPEED_SHIFT; + upd->Cx = FSP_C_STATES; + upd->TxtEnable = FSP_TXT; + upd->ThermalMonitor = FSP_THERMAL_THROTTLING; +#endif return 0; } /** @@ -1470,6 +1528,8 @@ static const struct tgl_gpio_conf gpio_table_presilicon[] = { .gpio_term = GPIO_TERM_NONE}, }; +#elif defined(TARGET_nai_68int6) +#include #endif /* TARGET_kontron_vx3060_s2 */ /** * @brief Configure GPIO settings for a specific device. @@ -1655,6 +1715,16 @@ int post_temp_ram_init_cb(void) kontron_ask_for_recovery(); +#elif defined(TARGET_nai_68int6) + unsigned int i; + unsigned int n; + + /* No CPLD or SuperIO: the LTC2870 transceiver resets to RS-232, so only + * the console pads need setting up. */ + n = sizeof(gpio_table_tempram)/sizeof(gpio_table_tempram[0]); + for (i = 0; i < n; i++) { + tgl_gpio_configure(&gpio_table_tempram[i]); + } #else (void)tgl_gpio_configure; #endif /* TARGET_kontron_vx3060_s2 */ @@ -1691,6 +1761,11 @@ int fsp_machine_update_m_parameters(uint8_t *default_m_params, new_udp->FspmArchUpd.NvsBufferPtr = 0; new_udp->FspmArchUpd.StackBase = mem_base; new_udp->FspmArchUpd.StackSize = mem_size; +#ifdef TARGET_nai_68int6 + new_udp->FspmConfig.HyperThreading = FSP_HYPERTHREADING; + new_udp->FspmConfig.BootFrequency = FSP_BOOT_PERFORMANCE_MODE; + new_udp->FspmConfig.Ibecc = FSP_IN_BAND_ECC; +#endif return 0; } @@ -1698,8 +1773,12 @@ int fsp_machine_update_m_parameters(uint8_t *default_m_params, int fsp_pre_mem_init_cb(void) { unsigned int i; + unsigned int n; - for (i = 0; i < sizeof(gpio_table_premem)/sizeof(gpio_table_premem[0]); i++) { + /* via a variable: the table is empty on some targets, and comparing + * against the literal 0 that sizeof folds to trips -Wtype-limits. */ + n = sizeof(gpio_table_premem)/sizeof(gpio_table_premem[0]); + for (i = 0; i < n; i++) { tgl_gpio_configure(&gpio_table_premem[i]); } diff --git a/tools/scripts/x86_fsp/select_fsp.sh b/tools/scripts/x86_fsp/select_fsp.sh new file mode 100755 index 0000000000..70805a43f0 --- /dev/null +++ b/tools/scripts/x86_fsp/select_fsp.sh @@ -0,0 +1,109 @@ +#!/bin/bash +# +# Switch the in-tree FSP set (UPD headers and FSP binaries) between QEMU, +# Tiger Lake and NAI; their generators all write the same paths. +# +# Usage: +# select_fsp.sh save {tgl|qemu|nai} snapshot the current in-tree set +# select_fsp.sh use {tgl|qemu|nai} restore a snapshotted set into the tree +# select_fsp.sh show report what is cached and what is in-tree +# +set -e + +CACHE="${WOLFBOOT_FSP_CACHE:-$HOME/.cache/wolfboot-fsp}" +ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" +HDRS="FspUpd.h FspmUpd.h FspsUpd.h FsptUpd.h" +# Headers only some generators emit; each set restores only its own, so one +# cannot leak into another. +HDRS_OPT_ALL="MemInfoHob.h" +HDRS_OPT_tgl="MemInfoHob.h" +HDRS_OPT_nai="MemInfoHob.h" +BINS="fsp_t.bin fsp_m.bin fsp_s.bin" +# ucode0.bin is only produced by the TGL flow +EXTRA_tgl="ucode0.bin" +# NAI: the TGL flow with NAI's own FSP and microcode (nai_extract_fsp.sh) +EXTRA_nai="ucode0.bin" + +usage() { sed -n '3,9p' "$0" | sed 's/^# \{0,1\}//'; exit 1; } + +check_set() { + case "$1" in + tgl|qemu|nai) ;; + *) echo "error: set must be 'tgl', 'qemu' or 'nai'" >&2; exit 1 ;; + esac +} + +do_save() { + local s="$1" extra opt n=0 + eval "extra=\${EXTRA_$s:-}" + eval "opt=\${HDRS_OPT_$s:-}" + mkdir -p "$CACHE/$s" + for f in $HDRS; do + [ -f "$ROOT/include/x86/fsp/$f" ] || { echo "error: missing include/x86/fsp/$f" >&2; exit 1; } + cp "$ROOT/include/x86/fsp/$f" "$CACHE/$s/"; n=$((n+1)) + done + for f in $HDRS_OPT_ALL; do + rm -f "$CACHE/$s/$f" + done + for f in $opt; do + [ -f "$ROOT/include/x86/fsp/$f" ] && { cp "$ROOT/include/x86/fsp/$f" "$CACHE/$s/"; n=$((n+1)); } + done + for f in $BINS $extra; do + [ -f "$ROOT/src/x86/$f" ] || { echo "error: missing src/x86/$f" >&2; exit 1; } + cp "$ROOT/src/x86/$f" "$CACHE/$s/"; n=$((n+1)) + done + echo "saved $n files to $CACHE/$s" +} + +do_use() { + local s="$1" extra opt n=0 + eval "extra=\${EXTRA_$s:-}" + eval "opt=\${HDRS_OPT_$s:-}" + [ -d "$CACHE/$s" ] || { echo "error: no snapshot for '$s'. Run the generator then 'select_fsp.sh save $s'." >&2; exit 1; } + # Check every required file before copying any, so a partial cache fails + # here rather than leaving a half-updated tree. + for f in $HDRS $BINS $extra; do + [ -f "$CACHE/$s/$f" ] || { echo "error: cached '$s' set is missing $f; re-run the generator then 'select_fsp.sh save $s'" >&2; exit 1; } + done + for f in $HDRS; do + cp "$CACHE/$s/$f" "$ROOT/include/x86/fsp/"; n=$((n+1)) + done + for f in $HDRS_OPT_ALL; do + rm -f "$ROOT/include/x86/fsp/$f" + done + for f in $opt; do + [ -f "$CACHE/$s/$f" ] && { cp "$CACHE/$s/$f" "$ROOT/include/x86/fsp/"; n=$((n+1)); } + done + for f in $BINS $extra; do + cp "$CACHE/$s/$f" "$ROOT/src/x86/"; n=$((n+1)) + done + echo "restored $n files of the '$s' FSP set" +} + +do_show() { + echo "cache: $CACHE" + for s in tgl qemu nai; do + if [ -d "$CACHE/$s" ]; then + echo " $s: $(ls "$CACHE/$s" | wc -l) files" + else + echo " $s: (not snapshotted)" + fi + done + echo "in-tree include/x86/fsp/FspmUpd.h:" + if [ -f "$ROOT/include/x86/fsp/FspmUpd.h" ]; then + # the TGL header is thousands of lines; the QEMU one is a small stub + printf " %s lines -> looks like %s\n" \ + "$(wc -l < "$ROOT/include/x86/fsp/FspmUpd.h")" \ + "$([ "$(wc -l < "$ROOT/include/x86/fsp/FspmUpd.h")" -gt 1000 ] && echo TGL || echo QEMU)" + else + echo " (absent)" + fi +} + +[ $# -ge 1 ] || usage +case "$1" in + save) [ $# -eq 2 ] || usage; check_set "$2"; do_save "$2" ;; + use) [ $# -eq 2 ] || usage; check_set "$2"; do_use "$2" ;; + show) do_show ;; + *) usage ;; +esac diff --git a/tools/scripts/x86_fsp/tgl/nai_extract_fsp.sh b/tools/scripts/x86_fsp/tgl/nai_extract_fsp.sh new file mode 100755 index 0000000000..c113e0f0f0 --- /dev/null +++ b/tools/scripts/x86_fsp/tgl/nai_extract_fsp.sh @@ -0,0 +1,97 @@ +#!/bin/bash +# +# Extract FSP-T/M/S and microcode from a 68INT6 BIOS image and rebase them +# to the .config addresses (the public TGL_IOT FSP does not train this board). +# +# Usage: tools/scripts/x86_fsp/tgl/nai_extract_fsp.sh <68INT6_bios_image.bin> + +set -eu + +IMG=${1:?usage: $0 } +WORK_DIR=/tmp/tgl_fsp +EDK2_COMMIT_ID=df25a5457f04ec465dce97428cfee96f462676e7 +FSP_TOOL_URL=https://github.com/tianocore/edk2/raw/${EDK2_COMMIT_ID}/IntelFsp2Pkg/Tools/SplitFspBin.py +SCRIPT_DIR=$(readlink -f "$(dirname "$0")") +WOLFBOOT_DIR="${SCRIPT_DIR}/../../../.." +CFG="${WOLFBOOT_DIR}/.config" + +[ -f "$IMG" ] || { echo "error: image not found: $IMG" >&2; exit 1; } +[ -f "$CFG" ] || { echo "error: no .config in ${WOLFBOOT_DIR}; copy config/examples/nai_68int6.config there first" >&2; exit 1; } + +cfgval() { grep -E "^$1=" "$CFG" | tail -1 | cut -d= -f2 | tr -d ' '; } +FSP_T_BASE=$(cfgval FSP_T_BASE) +FSP_M_BASE=$(cfgval FSP_M_BASE) +FSP_S_LOAD_BASE=$(cfgval FSP_S_LOAD_BASE) +for v in FSP_T_BASE FSP_M_BASE FSP_S_LOAD_BASE; do + [ -n "${!v}" ] || { echo "error: $v not set in .config" >&2; exit 1; } +done + +mkdir -p "$WORK_DIR" +if [ ! -f "$WORK_DIR/SplitFspBin.py" ]; then + echo "fetching SplitFspBin.py (edk2 ${EDK2_COMMIT_ID:0:12})" + curl -sL -o "$WORK_DIR/SplitFspBin.py" "$FSP_TOOL_URL" +fi + +# Locate FSP-T/M/S by their FSP_INFO_HEADER (type = ComponentAttribute bits +# 15:12; the FV starts 0x94 before the header, inner FV for FSP-S). +python3 - "$IMG" "$WORK_DIR" <<'PY' +import struct, sys +img = open(sys.argv[1], 'rb').read() +work = sys.argv[2] +names = {1: 'T', 2: 'M', 3: 'S'} +found = {} +i = 0 +while True: + i = img.find(b'FSPH', i) + if i < 0: + break + hlen = struct.unpack_from('> 12) & 0xF) + if kind and imgid.strip(b'\0') and 0 < imgsize < 0x200000: + fv = i - 0x94 + found.setdefault(kind, (fv, imgsize, imgid, imgbase, imgrev)) + i += 4 +for k in 'TMS': + if k not in found: + print("error: FSP-%s not found in image" % k); sys.exit(1) + fv, size, imgid, base, rev = found[k] + open("%s/NAI_FSP_%s.fd" % (work, k), 'wb').write(img[fv:fv + size]) + print(" FSP-%s %-9r rev %X.%X.%X.%X at 0x%08X size 0x%06X linked at 0x%08X" + % (k, imgid, (rev >> 24) & 0xFF, (rev >> 16) & 0xFF, (rev >> 8) & 0xFF, rev & 0xFF, fv, size, base)) + +# Newest microcode for this CPU from the same image. +best = None +for off in range(0, len(img) - 48, 16): + if struct.unpack_from(' 0x40000 or off + tsz > len(img): + continue + blob = img[off:off + tsz] + if sum(struct.unpack_from('<%dI' % (tsz // 4), blob)) & 0xFFFFFFFF: + continue + if best is None or urev > best[0]: + best = (urev, date, blob) +if best is None: + print("error: no valid 06-8c-01 microcode found in image"); sys.exit(1) +urev, date, blob = best +open("%s/NAI_ucode0.bin" % work, 'wb').write(blob) +print(" ucode 06-8c-01 rev 0x%02X date %08X %d bytes" % (urev, date, len(blob))) +PY + +rebase() { + local c=$1 base=$2 + (cd "$WORK_DIR" && python3 SplitFspBin.py rebase -f "NAI_FSP_${c}.fd" -b "$base" \ + -c "${c,,}" -o . -n "NAI_FSP_${c}_rebased.fd" >/dev/null) + cp "$WORK_DIR/NAI_FSP_${c}_rebased.fd" "${WOLFBOOT_DIR}/src/x86/fsp_${c,,}.bin" + echo " FSP-$c rebased to $base -> src/x86/fsp_${c,,}.bin" +} +rebase T "$FSP_T_BASE" +rebase M "$FSP_M_BASE" +rebase S "$FSP_S_LOAD_BASE" +cp "$WORK_DIR/NAI_ucode0.bin" "${WOLFBOOT_DIR}/src/x86/ucode0.bin" +echo " microcode -> src/x86/ucode0.bin" +echo +echo "Done. Snapshot it: tools/scripts/x86_fsp/select_fsp.sh save nai" diff --git a/tools/unit-tests/unit-kontron-tgl-spi.c b/tools/unit-tests/unit-kontron-tgl-spi.c index 13c984b526..12c022114d 100644 --- a/tools/unit-tests/unit-kontron-tgl-spi.c +++ b/tools/unit-tests/unit-kontron-tgl-spi.c @@ -39,7 +39,7 @@ typedef uintptr_t haladdr_t; #define MMIO_BASE 0xFED40000UL /* SPI PCI device constants + register offsets from - * hal/kontron_vx3060_s2.c (extracted by the Makefile). */ + * hal/x86_fsp_tgl.c (extracted by the Makefile). */ #include "kontron_spi_extract.h" /* TGL register offsets used by the model. */ @@ -122,7 +122,7 @@ static uint32_t mmio_read32(uintptr_t address) } /* The real tgl_lock_bios_region() + hal_flash_protect() from - * hal/kontron_vx3060_s2.c (extracted). */ + * hal/x86_fsp_tgl.c (extracted). */ #include "kontron_spi_fn_extract.h" /* The lock must land in the MMIO space: FPR0 carries the FREG1