diff --git a/arch.mk b/arch.mk index 1d5970cea0..0cfef99cb7 100644 --- a/arch.mk +++ b/arch.mk @@ -1548,7 +1548,19 @@ ifneq ($(filter nxp_t1024 nxp_t1040,$(TARGET)),) OBJS+=src/fdt.o OBJS+=src/pci.o CFLAGS+=-DWOLFBOOT_USE_PCI - UPDATE_OBJS:=src/update_ram.o + # Disk boot from SD card (eSDHC controller, driver hal/nxp_esdhc.c). + # src/gpt.o is already linked for all PPC targets above. The driver is + # kept out of the size-constrained stage1 loader. + ifneq ($(filter 1,$(DISK_SDCARD) $(DISK_EMMC)),) + CFLAGS+=-D"WOLFBOOT_UPDATE_DISK" -D"MAX_DISKS=1" + UPDATE_OBJS:=src/update_disk.o + OBJS+=src/disk.o + ifneq ($(STAGE1),1) + OBJS+=hal/nxp_esdhc.o + endif + else + UPDATE_OBJS:=src/update_ram.o + endif SPI_TARGET=nxp OPTIMIZATION_LEVEL=0 # using default -Os causes issues with alignment diff --git a/config/examples/nxp-t1040-sdcard.config b/config/examples/nxp-t1040-sdcard.config new file mode 100644 index 0000000000..da2d401418 --- /dev/null +++ b/config/examples/nxp-t1040-sdcard.config @@ -0,0 +1,82 @@ +# NXP QorIQ T1040 (4 core) with SD card disk boot +# +# Same as nxp-t1040.config, but the signed application image is loaded +# from an SD card (eSDHC controller) instead of NOR flash. The card is +# GPT (or MBR) partitioned; the signed image sits at offset 0 of the +# first two partitions (BOOT_PART_A / BOOT_PART_B, 0-based indexes into +# the partition table). wolfBoot picks the slot with the higher version, +# verifies it and boots it from RAM (src/update_disk.c). + +ARCH=PPC +TARGET=nxp_t1040 +SIGN?=ECC384 +HASH?=SHA384 +IMAGE_HEADER_SIZE?=512 +DEBUG?=0 +DEBUG_UART?=1 +VTOR?=1 +CORTEX_M0?=0 +NO_ASM?=0 +EXT_FLASH?=0 +SPI_FLASH?=0 +NO_XIP?=0 +UART_FLASH?=0 +ALLOW_DOWNGRADE?=0 +NVM_FLASH_WRITEONCE?=0 +WOLFBOOT_VERSION?=0 +NO_MPU?=0 +SPMATH?=0 +SPMATHALL?=1 +RAM_CODE?=0 +DUALBANK_SWAP?=0 +WOLFTPM?=0 +ELF?=1 +DEBUG_ELF=0 + +# SD card disk boot (Freescale eSDHC driver, hal/nxp_esdhc.c) +DISK_SDCARD=1 +# Upper bound for the unauthenticated image size read from disk +WOLFBOOT_RAMBOOT_MAX_SIZE?=0xF00000 +# Boot slots: partition table indexes (0-based) and read chunk size +CFLAGS_EXTRA+=-DBOOT_PART_A=0 -DBOOT_PART_B=1 +CFLAGS_EXTRA+=-DDISK_BLOCK_SIZE=0x8000 +# eSDHC bring-up trace on the DUART console +#CFLAGS_EXTRA+=-DDEBUG_ESDHC + +# NOR Base Address (128MB NOR at 0xE8000000 - 0xEFFFFFFF) +ARCH_FLASH_OFFSET?=0xE8000000 + +# Flash Sector Size (128KB) +WOLFBOOT_SECTOR_SIZE=0x20000 + +# wolfBoot start address (same as T1024 - NOR top is 0xEFFFFFFF) +WOLFBOOT_ORIGIN=0xEFF40000 +# wolfBoot partition size (custom) +BOOTLOADER_PARTITION_SIZE=0xC0000 + +# Application Partition Size (15MB) +WOLFBOOT_PARTITION_SIZE?=0xF00000 +# Location in Flash for Application Partition (unused for disk boot, +# kept for target.h consistency) +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0xEE000000 +# Load Partition to RAM Address +WOLFBOOT_LOAD_ADDRESS?=0x70000000 + +# Location in Flash for Update Partition +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0xEEF00000 + +# Location of temporary sector used during updates +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0xE80F0000 + +# Stage 1 loader settings (16KB) +WOLFBOOT_STAGE1_SIZE=0x4000 +# Location in Flash for stage 1 loader (XIP from boot ROM) +WOLFBOOT_STAGE1_FLASH_ADDR=0xEFFFC000 +# Address in RAM to load wolfBoot (end of DDR at 2GB-1MB for 32-bit addressing) +WOLFBOOT_STAGE1_LOAD_ADDR=0x7FF00000 + +# DTS (Device Tree) +WOLFBOOT_DTS_BOOT_ADDRESS?=0xE8800000 +WOLFBOOT_DTS_UPDATE_ADDRESS?=0xE8820000 +# DTS Load to RAM Address +WOLFBOOT_LOAD_DTS_ADDRESS?=0x7F100000 diff --git a/docs/Targets.md b/docs/Targets.md index 8f28417fa2..6aa798627c 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -6221,6 +6221,23 @@ Flash factory_custom.bin to NOR base 0xEC00_0000 Flash factory_custom.bin to NOR base 0xE800_0000 +### T1040 SD Card Boot (eSDHC) + +The T1040 can also load the signed application image from an SD card using the on-chip Freescale eSDHC controller (driver: `hal/nxp_esdhc.c`, included by the HAL translation unit). Use `config/examples/nxp-t1040-sdcard.config`, which enables `DISK_SDCARD=1` so wolfBoot links the disk boot path (`src/update_disk.c`) instead of the RAM loader. + +The card is GPT (or MBR) partitioned. The signed image is written at offset 0 of the boot partitions; `BOOT_PART_A` and `BOOT_PART_B` are 0-based indexes into the partition table. wolfBoot reads the image header from both slots, picks the higher version, loads it to `WOLFBOOT_LOAD_ADDRESS`, verifies the signature and boots it. + +Example card layout with two 16 MB raw slots: + +``` +sgdisk -Z /dev/sdX +sgdisk -n 1:0:+16M -t 1:8300 -n 2:0:+16M -t 2:8300 /dev/sdX +dd if=test-app/image_v1_signed.bin of=/dev/sdX1 +dd if=test-app/image_v2_signed.bin of=/dev/sdX2 +``` + +`WOLFBOOT_RAMBOOT_MAX_SIZE` bounds the image size read from disk before authentication. Define `DEBUG_ESDHC` (see the config) for controller bring-up trace on the DUART console. + ## NXP QorIQ T2080 PPC diff --git a/hal/nxp_esdhc.c b/hal/nxp_esdhc.c new file mode 100644 index 0000000000..2a43e2995d --- /dev/null +++ b/hal/nxp_esdhc.c @@ -0,0 +1,723 @@ +/* nxp_esdhc.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 3 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 + */ + +/* Freescale/NXP eSDHC block driver for QorIQ (T1040, T1024, T2080). + * + * Provides the four entry points src/disk.c expects (disk_init, disk_read, + * disk_write, disk_close) so the disk boot path, and therefore DISK_FS, can + * read a signed image from an SD card. + * + * This is NOT the Cadence controller driven by src/sdhci.c. The register map + * is entirely different: eSDHC keeps the command index and transfer setup in + * one XFERTYP register, combines block size and count into BLKATTR, and has + * a watermark register with no standard-SDHCI equivalent. + * + * Included from hal/nxp_t10xx.c rather than compiled separately, because the + * clock helpers it needs (hal_get_bus_clk) are static to that translation + * unit. + * + * Transfers use PIO through DATPORT rather than DMA. On e5500 with the MMU + * enabled a DMA descriptor would need cache maintenance on the destination, + * and boot-time throughput is dominated by media latency rather than by the + * copy, so PIO is the simpler and safer choice. + */ + +#ifndef _WOLFBOOT_NXP_ESDHC_C_ +#define _WOLFBOOT_NXP_ESDHC_C_ + +#ifdef DISK_SDCARD + +#include +#include + +#include "disk.h" +#include "printf.h" +#include "nxp_ppc.h" + +#ifdef DEBUG_ESDHC +#define ESDHC_DBG(_f_, ...) wolfBoot_printf(_f_, ##__VA_ARGS__) +#else +#define ESDHC_DBG(_f_, ...) do{}while(0) +#endif + +/* --------------------------------------------------------------------- + * Register map. CCSRBAR comes from nxp_ppc.h for the selected target. + * + * The block presents its registers big-endian and the e5500 is big-endian, + * so a native 32-bit access reads them correctly with no swapping. The one + * exception is DATPORT, whose byte order is selected by PROCTL[EMODE] and is + * configured below. + * --------------------------------------------------------------------- */ +#ifndef ESDHC_BASE +#define ESDHC_BASE (CCSRBAR + 0x114000) +#endif +#define ESDHC_REG(off) ((volatile uint32_t*)(ESDHC_BASE + (off))) + +#define ESDHC_DSADDR 0x00 +#define ESDHC_BLKATTR 0x04 +#define ESDHC_CMDARG 0x08 +#define ESDHC_XFERTYP 0x0C +#define ESDHC_CMDRSP0 0x10 +#define ESDHC_CMDRSP1 0x14 +#define ESDHC_CMDRSP2 0x18 +#define ESDHC_CMDRSP3 0x1C +#define ESDHC_DATPORT 0x20 +#define ESDHC_PRSSTAT 0x24 +#define ESDHC_PROCTL 0x28 +#define ESDHC_SYSCTL 0x2C +#define ESDHC_IRQSTAT 0x30 +#define ESDHC_IRQSTATEN 0x34 +#define ESDHC_IRQSIGEN 0x38 +#define ESDHC_HOSTCAPBLT 0x40 +#define ESDHC_WML 0x44 +#define ESDHC_HOSTVER 0xFC + +/* BLKATTR: block count in the high half, block size in the low 13 bits */ +#define ESDHC_BLKATTR_CNT(x) (((uint32_t)(x)) << 16) +#define ESDHC_BLKATTR_SIZE(x) ((uint32_t)(x) & 0x1FFFU) + +/* XFERTYP */ +#define ESDHC_XFERTYP_CMDINX(x) (((uint32_t)(x)) << 24) +#define ESDHC_XFERTYP_DPSEL (1U << 21) /* data present */ +#define ESDHC_XFERTYP_CICEN (1U << 20) /* check command index */ +#define ESDHC_XFERTYP_CCCEN (1U << 19) /* check command CRC */ +#define ESDHC_XFERTYP_RSPTYP_NONE (0U << 16) +#define ESDHC_XFERTYP_RSPTYP_136 (1U << 16) +#define ESDHC_XFERTYP_RSPTYP_48 (2U << 16) +#define ESDHC_XFERTYP_RSPTYP_48B (3U << 16) +#define ESDHC_XFERTYP_MSBSEL (1U << 5) /* multi-block */ +#define ESDHC_XFERTYP_DTDSEL (1U << 4) /* 1 = read */ +#define ESDHC_XFERTYP_AC12EN (1U << 2) /* auto CMD12 */ +#define ESDHC_XFERTYP_BCEN (1U << 1) /* block count enable */ + +/* PRSSTAT */ +#define ESDHC_PRSSTAT_CIHB (1U << 0) /* command inhibit (CMD) */ +#define ESDHC_PRSSTAT_CDIHB (1U << 1) /* command inhibit (DAT) */ +#define ESDHC_PRSSTAT_DLA (1U << 2) /* data line active */ +#define ESDHC_PRSSTAT_SDSTB (1U << 3) /* SD clock stable */ +#define ESDHC_PRSSTAT_BREN (1U << 11) /* buffer read enable */ +#define ESDHC_PRSSTAT_CINS (1U << 16) /* card inserted */ + +/* PROCTL */ +#define ESDHC_PROCTL_DTW_1BIT (0U << 1) +#define ESDHC_PROCTL_DTW_4BIT (1U << 1) +#define ESDHC_PROCTL_DTW_MASK (3U << 1) +/* EMODE selects the byte order of DATPORT. Big-endian mode delivers bytes + * in media order when the word is stored natively by this big-endian core. + * Verified on T1040D4RDB silicon: little-endian mode read every aligned + * 4-byte group byte-reversed (MBR signature came back AA55). */ +#define ESDHC_PROCTL_EMODE_BE (0U << 4) +#define ESDHC_PROCTL_EMODE_MASK (3U << 4) + +/* SYSCTL */ +#define ESDHC_SYSCTL_IPGEN (1U << 0) +#define ESDHC_SYSCTL_HCKEN (1U << 1) +#define ESDHC_SYSCTL_PEREN (1U << 2) +#define ESDHC_SYSCTL_SDCLKEN (1U << 3) +#define ESDHC_SYSCTL_DTOCV(x) (((uint32_t)(x) & 0xFU) << 16) +#define ESDHC_SYSCTL_SDCLKFS(x) (((uint32_t)(x) & 0xFFU) << 8) +#define ESDHC_SYSCTL_DVS(x) (((uint32_t)(x) & 0xFU) << 4) +#define ESDHC_SYSCTL_RSTA (1U << 24) /* reset all */ +#define ESDHC_SYSCTL_RSTC (1U << 25) /* reset command line */ +#define ESDHC_SYSCTL_RSTD (1U << 26) /* reset data line */ +#define ESDHC_SYSCTL_INITA (1U << 27) /* send 80 init clocks */ + +/* IRQSTAT */ +#define ESDHC_IRQSTAT_CC (1U << 0) /* command complete */ +#define ESDHC_IRQSTAT_TC (1U << 1) /* transfer complete */ +#define ESDHC_IRQSTAT_BRR (1U << 5) /* buffer read ready */ +#define ESDHC_IRQSTAT_CTOE (1U << 16) /* command timeout */ +#define ESDHC_IRQSTAT_CCE (1U << 17) /* command CRC error */ +#define ESDHC_IRQSTAT_CEBE (1U << 18) +#define ESDHC_IRQSTAT_CIE (1U << 19) +#define ESDHC_IRQSTAT_DTOE (1U << 20) /* data timeout */ +#define ESDHC_IRQSTAT_DCE (1U << 21) /* data CRC error */ +#define ESDHC_IRQSTAT_DEBE (1U << 22) +#define ESDHC_IRQSTAT_ALL 0xFFFFFFFFU + +#define ESDHC_IRQSTAT_CMD_ERR (ESDHC_IRQSTAT_CTOE | ESDHC_IRQSTAT_CCE | \ + ESDHC_IRQSTAT_CEBE | ESDHC_IRQSTAT_CIE) +#define ESDHC_IRQSTAT_DAT_ERR (ESDHC_IRQSTAT_DTOE | ESDHC_IRQSTAT_DCE | \ + ESDHC_IRQSTAT_DEBE) + +/* SD commands used here */ +#define SD_CMD_GO_IDLE 0 +#define SD_CMD_ALL_SEND_CID 2 +#define SD_CMD_SEND_REL_ADDR 3 +#define SD_CMD_SELECT_CARD 7 +#define SD_CMD_SEND_IF_COND 8 +#define SD_CMD_SEND_CSD 9 +#define SD_CMD_SET_BLOCKLEN 16 +#define SD_CMD_READ_SINGLE 17 +#define SD_CMD_READ_MULTI 18 +#define SD_CMD_APP_CMD 55 +#define SD_ACMD_SET_BUS_WIDTH 6 +#define SD_ACMD_SEND_OP_COND 41 + +#define SD_BLOCK_SIZE 512U +#define SD_OCR_BUSY (1UL << 31) +#define SD_OCR_HCS (1UL << 30) +#define SD_IF_COND_ARG 0x000001AAU /* 2.7-3.6V, check pattern 0xAA */ + +/* Card state discovered during init */ +static uint32_t g_esdhc_rca; /* relative card address, in the high half */ +static int g_esdhc_hc; /* 1 when the card is high capacity (SDHC) */ +static int g_esdhc_ready; + +/* --------------------------------------------------------------------- + * Timebase. The e5500 time base increments at the platform (CCB) clock + * divided by 16; TIMEBASE_HZ comes from nxp_ppc.c in this translation + * unit. Verified on T1040D4RDB silicon: CCB 600 MHz, timebase reads + * 37500000 Hz (600 MHz / 16). + * --------------------------------------------------------------------- */ + +static uint64_t esdhc_timebase(void) +{ + uint32_t hi, lo, hi2; + + /* Re-read the upper half to guard against a carry between the two + * reads; the pair is not atomic. */ + do { + __asm__ __volatile__("mfspr %0, 269" : "=r"(hi)); + __asm__ __volatile__("mfspr %0, 268" : "=r"(lo)); + __asm__ __volatile__("mfspr %0, 269" : "=r"(hi2)); + } while (hi != hi2); + + return ((uint64_t)hi << 32) | (uint64_t)lo; +} + +static uint64_t esdhc_timer_us(void) +{ + uint32_t tb_hz = TIMEBASE_HZ; + + if (tb_hz == 0U) { + return 0; + } + return (esdhc_timebase() * 1000000ULL) / (uint64_t)tb_hz; +} + +static void esdhc_udelay(uint32_t us) +{ + uint64_t end = esdhc_timer_us() + (uint64_t)us; + + while (esdhc_timer_us() < end) { + /* spin */ + } +} + +/* --------------------------------------------------------------------- + * Low level helpers + * --------------------------------------------------------------------- */ + +/* Wait for a set of IRQSTAT bits, or for any error bit. Returns 0 on the + * expected completion, or a negative value on error or timeout. */ +static int esdhc_wait_irq(uint32_t want, uint32_t err_mask, uint32_t timeout_us) +{ + uint64_t end = esdhc_timer_us() + (uint64_t)timeout_us; + uint32_t stat; + + for (;;) { + stat = *ESDHC_REG(ESDHC_IRQSTAT); + if ((stat & err_mask) != 0U) { + ESDHC_DBG("esdhc: irq error %x\r\n", stat); + return -1; + } + if ((stat & want) == want) { + return 0; + } + if (esdhc_timer_us() > end) { + ESDHC_DBG("esdhc: irq timeout, stat %x\r\n", stat); + return -1; + } + } +} + +/* Wait until the controller will accept a new command. */ +static int esdhc_wait_ready(int need_dat, uint32_t timeout_us) +{ + uint64_t end = esdhc_timer_us() + (uint64_t)timeout_us; + uint32_t mask = ESDHC_PRSSTAT_CIHB; + + if (need_dat != 0) { + mask |= ESDHC_PRSSTAT_CDIHB | ESDHC_PRSSTAT_DLA; + } + while ((*ESDHC_REG(ESDHC_PRSSTAT) & mask) != 0U) { + if (esdhc_timer_us() > end) { + return -1; + } + } + return 0; +} + +/** + * @brief Issue one command and collect its response. + * + * @param resp Receives up to four response words when non-NULL. A 136-bit + * response is returned as the controller presents it, which is + * shifted left by 8 bits relative to the card's CID/CSD. + */ +static int esdhc_send_cmd(uint32_t idx, uint32_t arg, uint32_t xfertyp, + uint32_t *resp) +{ + uint32_t cmd; + int ret; + + if (esdhc_wait_ready((xfertyp & ESDHC_XFERTYP_DPSEL) != 0U, + 1000000U) != 0) { + ESDHC_DBG("esdhc: controller busy before CMD%u\r\n", idx); + return -1; + } + + /* Clear any stale status before starting. */ + *ESDHC_REG(ESDHC_IRQSTAT) = ESDHC_IRQSTAT_ALL; + + *ESDHC_REG(ESDHC_CMDARG) = arg; + cmd = ESDHC_XFERTYP_CMDINX(idx) | xfertyp; + *ESDHC_REG(ESDHC_XFERTYP) = cmd; + + ret = esdhc_wait_irq(ESDHC_IRQSTAT_CC, ESDHC_IRQSTAT_CMD_ERR, 1000000U); + if (ret != 0) { + return ret; + } + + if (resp != NULL) { + resp[0] = *ESDHC_REG(ESDHC_CMDRSP0); + resp[1] = *ESDHC_REG(ESDHC_CMDRSP1); + resp[2] = *ESDHC_REG(ESDHC_CMDRSP2); + resp[3] = *ESDHC_REG(ESDHC_CMDRSP3); + } + return 0; +} + +/* Set the SD clock. The divider is SDCLKFS (base 2 prescaler) times DVS. */ +static void esdhc_set_clock(uint32_t target_hz) +{ + uint32_t base = hal_get_bus_clk(); + uint32_t pre = 2, div = 1, sysctl; + + if (target_hz == 0U) { + return; + } + /* Stop the clock while the divider changes. */ + sysctl = *ESDHC_REG(ESDHC_SYSCTL); + *ESDHC_REG(ESDHC_SYSCTL) = sysctl & ~ESDHC_SYSCTL_SDCLKEN; + + while ((pre < 256U) && ((base / pre) > target_hz)) { + pre <<= 1; + } + while ((div < 16U) && (((base / pre) / div) > target_hz)) { + div++; + } + + sysctl = *ESDHC_REG(ESDHC_SYSCTL); + sysctl &= ~(ESDHC_SYSCTL_SDCLKFS(0xFF) | ESDHC_SYSCTL_DVS(0xF)); + sysctl |= ESDHC_SYSCTL_SDCLKFS(pre >> 1) | ESDHC_SYSCTL_DVS(div - 1U); + sysctl |= ESDHC_SYSCTL_DTOCV(0xE); + sysctl |= ESDHC_SYSCTL_IPGEN | ESDHC_SYSCTL_HCKEN | ESDHC_SYSCTL_PEREN; + *ESDHC_REG(ESDHC_SYSCTL) = sysctl; + + /* Wait for the divider to take effect, then re-enable the card clock. */ + esdhc_udelay(100); + *ESDHC_REG(ESDHC_SYSCTL) = *ESDHC_REG(ESDHC_SYSCTL) | + ESDHC_SYSCTL_SDCLKEN; + esdhc_udelay(100); + + ESDHC_DBG("esdhc: clock %u Hz (pre %u, div %u)\r\n", + (base / pre) / div, pre, div); +} + + +/* --------------------------------------------------------------------- + * Card initialisation + * --------------------------------------------------------------------- */ + +/* Reset the controller and bring the bus up at the 400 kHz identification + * clock, 1-bit wide. */ +static int esdhc_host_init(void) +{ + uint32_t proctl; + uint64_t end; + + /* Reset all. The bit self-clears when the reset completes. */ + *ESDHC_REG(ESDHC_SYSCTL) = *ESDHC_REG(ESDHC_SYSCTL) | ESDHC_SYSCTL_RSTA; + end = esdhc_timer_us() + 1000000ULL; + while ((*ESDHC_REG(ESDHC_SYSCTL) & ESDHC_SYSCTL_RSTA) != 0U) { + if (esdhc_timer_us() > end) { + ESDHC_DBG("esdhc: controller reset timeout\r\n"); + return -1; + } + } + + /* Mask interrupt delivery but enable status reporting: this driver + * polls IRQSTAT rather than taking interrupts. */ + *ESDHC_REG(ESDHC_IRQSTATEN) = ESDHC_IRQSTAT_ALL; + *ESDHC_REG(ESDHC_IRQSIGEN) = 0; + *ESDHC_REG(ESDHC_IRQSTAT) = ESDHC_IRQSTAT_ALL; + + /* 1-bit bus for identification, and set the data-port byte order. */ + proctl = *ESDHC_REG(ESDHC_PROCTL); + proctl &= ~(ESDHC_PROCTL_DTW_MASK | ESDHC_PROCTL_EMODE_MASK); + proctl |= ESDHC_PROCTL_DTW_1BIT | ESDHC_PROCTL_EMODE_BE; + *ESDHC_REG(ESDHC_PROCTL) = proctl; + + esdhc_set_clock(400000U); + + /* Drive the 80 initialisation clocks the card needs before CMD0. */ + *ESDHC_REG(ESDHC_SYSCTL) = *ESDHC_REG(ESDHC_SYSCTL) | ESDHC_SYSCTL_INITA; + end = esdhc_timer_us() + 1000000ULL; + while ((*ESDHC_REG(ESDHC_SYSCTL) & ESDHC_SYSCTL_INITA) != 0U) { + if (esdhc_timer_us() > end) { + ESDHC_DBG("esdhc: INITA timeout\r\n"); + return -1; + } + } + return 0; +} + +/* CMD55 + the given application command. */ +static int esdhc_send_acmd(uint32_t idx, uint32_t arg, uint32_t xfertyp, + uint32_t *resp) +{ + int ret; + + ret = esdhc_send_cmd(SD_CMD_APP_CMD, g_esdhc_rca, + ESDHC_XFERTYP_RSPTYP_48 | ESDHC_XFERTYP_CICEN | ESDHC_XFERTYP_CCCEN, + NULL); + if (ret != 0) { + return ret; + } + return esdhc_send_cmd(idx, arg, xfertyp, resp); +} + +/** + * @brief Take the card from idle to transfer state. + * + * CMD0 -> CMD8 -> ACMD41 -> CMD2 -> CMD3 -> CMD7 -> ACMD6 -> CMD16. + * CMD8 is what distinguishes an SD v2 card, and only a card that answered + * it may be told HCS in ACMD41; a v1 card must not see that bit set. + */ +static int esdhc_card_init(void) +{ + uint32_t resp[4]; + uint32_t arg; + int v2 = 0; + int ret; + uint64_t end; + + g_esdhc_rca = 0; + g_esdhc_hc = 0; + + ret = esdhc_send_cmd(SD_CMD_GO_IDLE, 0, ESDHC_XFERTYP_RSPTYP_NONE, NULL); + if (ret != 0) { + ESDHC_DBG("esdhc: CMD0 failed\r\n"); + return ret; + } + esdhc_udelay(2000); + + /* CMD8. A card that does not respond is pre-v2; that is not an error. */ + if (esdhc_send_cmd(SD_CMD_SEND_IF_COND, SD_IF_COND_ARG, + ESDHC_XFERTYP_RSPTYP_48 | ESDHC_XFERTYP_CICEN | + ESDHC_XFERTYP_CCCEN, resp) == 0) { + if ((resp[0] & 0xFFU) != 0xAAU) { + ESDHC_DBG("esdhc: CMD8 check pattern %x\r\n", resp[0]); + return -1; + } + v2 = 1; + } + else { + /* CMD8 leaves the command line in error state on a v1 card. */ + *ESDHC_REG(ESDHC_SYSCTL) = *ESDHC_REG(ESDHC_SYSCTL) | + ESDHC_SYSCTL_RSTC; + *ESDHC_REG(ESDHC_IRQSTAT) = ESDHC_IRQSTAT_ALL; + } + + /* ACMD41 until the card leaves busy. Only a v2 card may be offered + * HCS; setting it for a v1 card is out of spec. */ + arg = 0x00FF8000U; /* 2.7-3.6V window */ + if (v2 != 0) { + arg |= SD_OCR_HCS; + } + end = esdhc_timer_us() + 2000000ULL; /* spec allows up to 1 s */ + for (;;) { + ret = esdhc_send_acmd(SD_ACMD_SEND_OP_COND, arg, + ESDHC_XFERTYP_RSPTYP_48, resp); + if (ret != 0) { + ESDHC_DBG("esdhc: ACMD41 failed\r\n"); + return ret; + } + if ((resp[0] & SD_OCR_BUSY) != 0U) { + break; + } + if (esdhc_timer_us() > end) { + ESDHC_DBG("esdhc: ACMD41 busy timeout\r\n"); + return -1; + } + esdhc_udelay(1000); + } + /* CCS in the OCR says block addressing rather than byte addressing. */ + g_esdhc_hc = ((resp[0] & SD_OCR_HCS) != 0U) ? 1 : 0; + + ret = esdhc_send_cmd(SD_CMD_ALL_SEND_CID, 0, + ESDHC_XFERTYP_RSPTYP_136 | ESDHC_XFERTYP_CCCEN, resp); + if (ret != 0) { + ESDHC_DBG("esdhc: CMD2 failed\r\n"); + return ret; + } + + ret = esdhc_send_cmd(SD_CMD_SEND_REL_ADDR, 0, + ESDHC_XFERTYP_RSPTYP_48 | ESDHC_XFERTYP_CICEN | ESDHC_XFERTYP_CCCEN, + resp); + if (ret != 0) { + ESDHC_DBG("esdhc: CMD3 failed\r\n"); + return ret; + } + g_esdhc_rca = resp[0] & 0xFFFF0000U; + ESDHC_DBG("esdhc: RCA %x, %s capacity\r\n", g_esdhc_rca, + (g_esdhc_hc != 0) ? "high" : "standard"); + + ret = esdhc_send_cmd(SD_CMD_SELECT_CARD, g_esdhc_rca, + ESDHC_XFERTYP_RSPTYP_48B | ESDHC_XFERTYP_CICEN | ESDHC_XFERTYP_CCCEN, + NULL); + if (ret != 0) { + ESDHC_DBG("esdhc: CMD7 failed\r\n"); + return ret; + } + + /* 4-bit bus. Card first, then the controller, so the two never + * disagree about the width mid-transfer. */ + if (esdhc_send_acmd(SD_ACMD_SET_BUS_WIDTH, 2U, + ESDHC_XFERTYP_RSPTYP_48 | ESDHC_XFERTYP_CICEN | + ESDHC_XFERTYP_CCCEN, NULL) == 0) { + uint32_t proctl = *ESDHC_REG(ESDHC_PROCTL); + proctl &= ~ESDHC_PROCTL_DTW_MASK; + proctl |= ESDHC_PROCTL_DTW_4BIT; + *ESDHC_REG(ESDHC_PROCTL) = proctl; + } + + /* Harmless on a high-capacity card, which is fixed at 512. */ + (void)esdhc_send_cmd(SD_CMD_SET_BLOCKLEN, SD_BLOCK_SIZE, + ESDHC_XFERTYP_RSPTYP_48 | ESDHC_XFERTYP_CICEN | ESDHC_XFERTYP_CCCEN, + NULL); + + /* Identification is done; run at full speed. */ + esdhc_set_clock(25000000U); + return 0; +} + +/* --------------------------------------------------------------------- + * Block read + * --------------------------------------------------------------------- */ + +/* Drain one block from the data port. + * + * PROCTL[EMODE] is set to big-endian above, so a native 32-bit read of + * DATPORT returns the four media bytes already in order and they can be + * stored as-is. Silicon-verified: little-endian mode returned every + * aligned 4-byte group byte-reversed. */ +static int esdhc_read_block(uint8_t *buf) +{ + uint32_t i, word; + int ret; + + ret = esdhc_wait_irq(ESDHC_IRQSTAT_BRR, ESDHC_IRQSTAT_DAT_ERR, 1000000U); + if (ret != 0) { + return ret; + } + for (i = 0; i < (SD_BLOCK_SIZE / 4U); i++) { + word = *ESDHC_REG(ESDHC_DATPORT); + memcpy(buf + (i * 4U), &word, 4U); + } + *ESDHC_REG(ESDHC_IRQSTAT) = ESDHC_IRQSTAT_BRR; + return 0; +} + +/* One transfer: count is bounded by the caller to the 16-bit block count + * field of BLKATTR. */ +static int esdhc_read_blocks_chunk(uint64_t lba, uint32_t count, uint8_t *buf) +{ + uint32_t xfertyp, i; + uint32_t arg; + int ret; + + if ((count == 0U) || (buf == NULL)) { + return -1; + } + + /* Standard-capacity cards are addressed in bytes, high-capacity in + * blocks. Getting this backwards reads from a wildly wrong offset. */ + arg = (g_esdhc_hc != 0) ? (uint32_t)lba + : (uint32_t)(lba * SD_BLOCK_SIZE); + + /* Read watermark in words. */ + *ESDHC_REG(ESDHC_WML) = (SD_BLOCK_SIZE / 4U); + *ESDHC_REG(ESDHC_BLKATTR) = ESDHC_BLKATTR_CNT(count) | + ESDHC_BLKATTR_SIZE(SD_BLOCK_SIZE); + + xfertyp = ESDHC_XFERTYP_DPSEL | ESDHC_XFERTYP_DTDSEL | + ESDHC_XFERTYP_RSPTYP_48 | ESDHC_XFERTYP_CICEN | ESDHC_XFERTYP_CCCEN; + if (count > 1U) { + xfertyp |= ESDHC_XFERTYP_MSBSEL | ESDHC_XFERTYP_BCEN | + ESDHC_XFERTYP_AC12EN; + ret = esdhc_send_cmd(SD_CMD_READ_MULTI, arg, xfertyp, NULL); + } + else { + ret = esdhc_send_cmd(SD_CMD_READ_SINGLE, arg, xfertyp, NULL); + } + if (ret != 0) { + ESDHC_DBG("esdhc: read cmd failed at lba %u\r\n", (uint32_t)lba); + return ret; + } + + for (i = 0; i < count; i++) { + ret = esdhc_read_block(buf + ((size_t)i * SD_BLOCK_SIZE)); + if (ret != 0) { + return ret; + } + } + + return esdhc_wait_irq(ESDHC_IRQSTAT_TC, ESDHC_IRQSTAT_DAT_ERR, 5000000U); +} + +/* BLKATTR encodes the block count in a 16-bit field, so split larger + * requests into multiple transfers. */ +#define ESDHC_MAX_BLK_CNT 0xFFFFU + +static int esdhc_read_blocks(uint64_t lba, uint32_t count, uint8_t *buf) +{ + uint32_t chunk; + int ret; + + if ((count == 0U) || (buf == NULL)) { + return -1; + } + while (count > 0U) { + chunk = count; + if (chunk > ESDHC_MAX_BLK_CNT) { + chunk = ESDHC_MAX_BLK_CNT; + } + ret = esdhc_read_blocks_chunk(lba, chunk, buf); + if (ret != 0) { + return ret; + } + lba += chunk; + buf += (size_t)chunk * SD_BLOCK_SIZE; + count -= chunk; + } + return 0; +} + +/* --------------------------------------------------------------------- + * disk.c interface + * --------------------------------------------------------------------- */ + +int disk_init(int drv) +{ + if (drv != 0) { + return -1; + } + if (g_esdhc_ready != 0) { + return 0; + } +#ifdef DEBUG_ESDHC + /* Bring-up probes, in dependency order (see docs). The two timebase + * markers must land ~1 s apart on a timestamped console; if not, + * ESDHC_TB_DIV is wrong and every timeout below is wrong with it. + * HOSTVER/HOSTCAPBLT prove the base address and register byte order + * before any card interaction; CINS proves the socket sees a card. */ + ESDHC_DBG("esdhc: timebase check begin (expect 1s gap)\r\n"); + esdhc_udelay(1000000U); + ESDHC_DBG("esdhc: timebase check end\r\n"); + ESDHC_DBG("esdhc: HOSTVER %x HOSTCAPBLT %x PRSSTAT %x (CINS %u)\r\n", + *ESDHC_REG(ESDHC_HOSTVER), *ESDHC_REG(ESDHC_HOSTCAPBLT), + *ESDHC_REG(ESDHC_PRSSTAT), + (*ESDHC_REG(ESDHC_PRSSTAT) & ESDHC_PRSSTAT_CINS) != 0U ? 1U : 0U); +#endif + if (esdhc_host_init() != 0) { + return -1; + } + if (esdhc_card_init() != 0) { + return -1; + } + g_esdhc_ready = 1; + return 0; +} + +/* Byte-granular read on top of a block device. start and count need not be + * sector aligned, so the head and tail are staged through a bounce block. */ +int disk_read(int drv, uint64_t start, uint32_t count, uint8_t *buf) +{ + uint8_t block[SD_BLOCK_SIZE]; + uint64_t lba; + uint32_t done = 0, chunk, off, whole; + + if ((drv != 0) || (buf == NULL)) { + return -1; + } + if (g_esdhc_ready == 0) { + return -1; + } + + while (done < count) { + lba = (start + done) / SD_BLOCK_SIZE; + off = (uint32_t)((start + done) % SD_BLOCK_SIZE); + + if ((off == 0U) && ((count - done) >= SD_BLOCK_SIZE)) { + /* Aligned run: read whole blocks straight into the caller's + * buffer, no bounce. */ + whole = (count - done) / SD_BLOCK_SIZE; + if (esdhc_read_blocks(lba, whole, buf + done) != 0) { + return -1; + } + done += whole * SD_BLOCK_SIZE; + continue; + } + + if (esdhc_read_blocks(lba, 1U, block) != 0) { + return -1; + } + chunk = SD_BLOCK_SIZE - off; + if (chunk > (count - done)) { + chunk = count - done; + } + memcpy(buf + done, block + off, chunk); + done += chunk; + } + return (int)done; +} + +int disk_write(int drv, uint64_t start, uint32_t count, const uint8_t *buf) +{ + /* wolfBoot only reads from the boot media on this target. Returning an + * error is deliberate: a silent success would let a caller believe an + * update had been written. */ + (void)drv; (void)start; (void)count; (void)buf; + return -1; +} + +void disk_close(int drv) +{ + (void)drv; + g_esdhc_ready = 0; +} + +#endif /* DISK_SDCARD */ + +#endif /* _WOLFBOOT_NXP_ESDHC_C_ */ diff --git a/hal/nxp_ppc.c b/hal/nxp_ppc.c index 0b68d7b0bb..830d113081 100644 --- a/hal/nxp_ppc.c +++ b/hal/nxp_ppc.c @@ -43,7 +43,9 @@ static uint32_t hal_get_core_clk(void) core_clk = SYS_CLK * core_ratio; return core_clk; } -static uint32_t RAMFUNCTION hal_get_plat_clk(void) +/* Non-static: prototyped in nxp_ppc.h so drivers built as standalone + * objects (e.g. hal/nxp_esdhc.c) can use the clock helpers. */ +uint32_t RAMFUNCTION hal_get_plat_clk(void) { /* compute platform clock: system_input * (SYS_PLL_RAT / 2) */ uint32_t plat_clk; @@ -52,14 +54,12 @@ static uint32_t RAMFUNCTION hal_get_plat_clk(void) plat_clk = SYS_CLK * plat_ratio; return plat_clk; } -static uint32_t hal_get_bus_clk(void) +uint32_t hal_get_bus_clk(void) { return hal_get_plat_clk() / 2; } #endif /* ENABLE_BUS_CLK_CALC */ -#define TIMEBASE_CLK_DIV 16 -#define TIMEBASE_HZ (hal_get_plat_clk() / TIMEBASE_CLK_DIV) #define DELAY_US (TIMEBASE_HZ / 1000000) static void RAMFUNCTION udelay(uint32_t delay_us) { diff --git a/hal/nxp_ppc.h b/hal/nxp_ppc.h index 6fff992fe0..4eb17a50a3 100644 --- a/hal/nxp_ppc.h +++ b/hal/nxp_ppc.h @@ -769,6 +769,17 @@ #ifndef __ASSEMBLER__ +/* Clock helpers, defined in nxp_ppc.c (built into the target HAL) when + * ENABLE_BUS_CLK_CALC is set. Prototyped here so drivers compiled as + * standalone objects (e.g. hal/nxp_esdhc.c) can use them. */ +#if defined(CORE_E5500) || defined(CORE_E6500) +unsigned int hal_get_plat_clk(void); +unsigned int hal_get_bus_clk(void); +/* The e5500/e6500 time base increments at the platform clock / 16 */ +#define TIMEBASE_CLK_DIV 16 +#define TIMEBASE_HZ (hal_get_plat_clk() / TIMEBASE_CLK_DIV) +#endif + /* The data barrier / coherency safe functions for reading and writing */ static inline int get8(const volatile unsigned char *addr) { diff --git a/hal/nxp_t1024.ld b/hal/nxp_t1024.ld index d7cf8794ac..934e0e5091 100644 --- a/hal/nxp_t1024.ld +++ b/hal/nxp_t1024.ld @@ -6,7 +6,7 @@ MEMORY { /* DDR4 - 2GB (offset by destination address and 4KB boot region) */ DRAM (rwx) : ORIGIN = @WOLFBOOT_STAGE1_LOAD_ADDR@, - LENGTH = 0x7FFFFFFF - 4K - @WOLFBOOT_STAGE1_LOAD_ADDR@ + LENGTH = 0x80000000 - 4K - @WOLFBOOT_STAGE1_LOAD_ADDR@ /* L1 SRAM - 16KB */ L1RAM (rwx) : ORIGIN = 0xF8F80000, LENGTH = 0x4000 diff --git a/hal/nxp_t1024_stage1.ld b/hal/nxp_t1024_stage1.ld index f3092e3842..e0e7ca657a 100644 --- a/hal/nxp_t1024_stage1.ld +++ b/hal/nxp_t1024_stage1.ld @@ -25,7 +25,7 @@ MEMORY PSRAM (rwx) : ORIGIN = 0xFDFC0000, LENGTH = 0x28000 /* DDR - 2GB: Start at 16MB to avoid using 0x0 (NULL) addresses */ - DRAM (rwx) : ORIGIN = 0x1000000, LENGTH = 0x7FFFFFFF - 0x1000000 + DRAM (rwx) : ORIGIN = 0x1000000, LENGTH = 0x80000000 - 0x1000000 } SECTIONS diff --git a/hal/nxp_t1040.ld b/hal/nxp_t1040.ld index 487fa6a047..793a4a1cb9 100644 --- a/hal/nxp_t1040.ld +++ b/hal/nxp_t1040.ld @@ -6,7 +6,7 @@ MEMORY { /* DDR4 - 8GB physical (32-bit addressing limits window to <2GB) */ DRAM (rwx) : ORIGIN = @WOLFBOOT_STAGE1_LOAD_ADDR@, - LENGTH = 0x7FFFFFFF - 4K - @WOLFBOOT_STAGE1_LOAD_ADDR@ + LENGTH = 0x80000000 - 4K - @WOLFBOOT_STAGE1_LOAD_ADDR@ /* L1 locked dcache - 16KB */ L1RAM (rwx) : ORIGIN = 0xFDFC0000, LENGTH = 0x4000 diff --git a/hal/nxp_t1040_stage1.ld b/hal/nxp_t1040_stage1.ld index 976bae798e..d4fd7418a6 100644 --- a/hal/nxp_t1040_stage1.ld +++ b/hal/nxp_t1040_stage1.ld @@ -26,7 +26,7 @@ MEMORY /* DDR - 8GB physical, 32-bit addressing limits window to <2GB. * Start at 16MB to avoid using 0x0 (NULL) addresses */ - DRAM (rwx) : ORIGIN = 0x1000000, LENGTH = 0x7FFFFFFF - 0x1000000 + DRAM (rwx) : ORIGIN = 0x1000000, LENGTH = 0x80000000 - 0x1000000 } SECTIONS diff --git a/include/gpt.h b/include/gpt.h index a0936492c1..d5be09a12c 100644 --- a/include/gpt.h +++ b/include/gpt.h @@ -26,12 +26,40 @@ #include +/* Every field in an MBR, a GPT structure, a FAT32 BPB and an ext4 superblock + * is little-endian ON DISK. Casting a sector to a packed struct and + * dereferencing it is therefore only correct on a little-endian host, so all + * on-disk values are read through these instead. One definition, used by the + * partition layer and by the filesystem parsers alike. */ +static inline uint16_t gpt_le16(const uint8_t *p) +{ + return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8)); +} + +static inline uint32_t gpt_le32(const uint8_t *p) +{ + return ((uint32_t)p[0]) | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +static inline uint64_t gpt_le64(const uint8_t *p) +{ + return (uint64_t)gpt_le32(p) | ((uint64_t)gpt_le32(p + 4) << 32); +} + + /* GPT Constants */ #define GPT_SECTOR_SIZE 512 /* 0x200 */ #define GPT_SIGNATURE 0x5452415020494645ULL /* "EFI PART" */ #define GPT_PTYPE_PROTECTIVE 0xEE #define GPT_PART_NAME_SIZE 36 #define GPT_MBR_ENTRY_START 0x01BE + +/* MBR partition entry field offsets, shared by src/gpt.c and src/disk.c. */ +#define GPT_MBR_PTE_PTYPE 0x04 +#define GPT_MBR_PTE_LBA_FIRST 0x08 +#define GPT_MBR_PTE_LBA_SIZE 0x0C +#define GPT_MBR_PTE_SIZE 0x10 #define GPT_MBR_BOOTSIG_OFFSET 0x01FE #define GPT_MBR_BOOTSIG_VALUE 0xAA55 #define GPT_PART_ENTRY_SIZE 256 diff --git a/options.mk b/options.mk index 954ac338f4..ef58ed128a 100644 --- a/options.mk +++ b/options.mk @@ -799,9 +799,14 @@ ifeq ($(DISK_EMMC),1) CFLAGS+=-D"DISK_EMMC=1" endif -# Add SDHCI driver if SD card or eMMC is enabled (only add once) +# Add SDHCI driver if SD card or eMMC is enabled (only add once). +# PPC targets provide their own eSDHC driver inside the HAL translation +# unit (hal/nxp_esdhc.c), so the Cadence SDHCI driver must not be linked +# there (its disk_* entry points would collide). ifneq ($(filter 1,$(DISK_SDCARD) $(DISK_EMMC)),) - OBJS+= src/sdhci.o + ifneq ($(ARCH),PPC) + OBJS+= src/sdhci.o + endif endif ifeq ($(UART_FLASH),1) diff --git a/src/disk.c b/src/disk.c index 6b9cb5c22b..096743bae3 100644 --- a/src/disk.c +++ b/src/disk.c @@ -60,26 +60,33 @@ static struct disk_drive Drives[MAX_DISKS] = {0}; static int disk_open_mbr(struct disk_drive *drive, const uint8_t *mbr_sector) { uint32_t i; - const struct gpt_mbr_part_entry *pte; + const uint8_t *pte; + uint8_t ptype; + uint32_t lba_first, lba_size; for (i = 0; i < 4; i++) { - pte = (const struct gpt_mbr_part_entry *)(mbr_sector + - GPT_MBR_ENTRY_START + (i * sizeof(struct gpt_mbr_part_entry))); + /* MBR fields are little-endian on disk, so they are read byte-wise + * rather than through a packed struct, which would only be correct + * on a little-endian host. */ + pte = mbr_sector + GPT_MBR_ENTRY_START + (i * GPT_MBR_PTE_SIZE); + ptype = pte[GPT_MBR_PTE_PTYPE]; + lba_first = gpt_le32(pte + GPT_MBR_PTE_LBA_FIRST); + lba_size = gpt_le32(pte + GPT_MBR_PTE_LBA_SIZE); /* Skip empty entries (type 0) and extended partition types */ - if (pte->ptype == 0x00 || pte->ptype == 0x05 || pte->ptype == 0x0F || - pte->ptype == 0x85) { + if (ptype == 0x00 || ptype == 0x05 || ptype == 0x0F || + ptype == 0x85) { continue; } - if (pte->lba_first == 0 || pte->lba_size == 0) { + if (lba_first == 0 || lba_size == 0) { continue; } { uint32_t n = drive->n_parts; - uint64_t start_bytes = (uint64_t)pte->lba_first * GPT_SECTOR_SIZE; + uint64_t start_bytes = (uint64_t)lba_first * GPT_SECTOR_SIZE; uint64_t end_bytes = start_bytes + - ((uint64_t)pte->lba_size * GPT_SECTOR_SIZE) - 1; + ((uint64_t)lba_size * GPT_SECTOR_SIZE) - 1; if (n >= MAX_PARTITIONS) break; @@ -91,9 +98,9 @@ static int disk_open_mbr(struct disk_drive *drive, const uint8_t *mbr_sector) drive->n_parts++; wolfBoot_printf(" MBR part %u: type=0x%02x, start=0x%x, " - "size=%uMB\r\n", i + 1, pte->ptype, + "size=%uMB\r\n", i + 1, ptype, (uint32_t)start_bytes, - (uint32_t)(pte->lba_size / 2048)); + (uint32_t)(lba_size / 2048)); } } @@ -248,11 +255,11 @@ int disk_open(int drv) } } } else { - const uint16_t *boot_sig = (const uint16_t *)(sector + - GPT_MBR_BOOTSIG_OFFSET); - - /* Check MBR boot signature (0xAA55) */ - if (*boot_sig != GPT_MBR_BOOTSIG_VALUE) { + /* Check MBR boot signature (0xAA55). Read byte-wise: it is stored + * little-endian on disk, so a uint16_t cast reads it swapped on a + * big-endian host. */ + if (gpt_le16(sector + GPT_MBR_BOOTSIG_OFFSET) != + GPT_MBR_BOOTSIG_VALUE) { wolfBoot_printf("No valid partition table found\r\n"); Drives[drv].is_open = 0; return -1; diff --git a/src/gpt.c b/src/gpt.c index b2c7bf588a..097673c03b 100644 --- a/src/gpt.c +++ b/src/gpt.c @@ -34,6 +34,25 @@ #include "gpt.h" +#define GPT_HDR_SIGNATURE 0x00 +#define GPT_HDR_REVISION 0x08 +#define GPT_HDR_SIZE 0x0C +#define GPT_HDR_CRC32 0x10 +#define GPT_HDR_MAIN_LBA 0x18 +#define GPT_HDR_BACKUP_LBA 0x20 +#define GPT_HDR_FIRST_USABLE 0x28 +#define GPT_HDR_LAST_USABLE 0x30 +#define GPT_HDR_START_ARRAY 0x48 +#define GPT_HDR_N_PART 0x50 +#define GPT_HDR_ARRAY_SZ 0x54 +#define GPT_HDR_PART_CRC 0x58 +#define GPT_HDR_MIN_SIZE 0x5C + +#define GPT_PE_TYPE 0x00 +#define GPT_PE_FIRST 0x20 +#define GPT_PE_LAST 0x28 +#define GPT_PE_NAME 0x38 + void gpt_crc32_init(struct gpt_crc32_ctx *ctx) { if (ctx != NULL) { @@ -82,8 +101,7 @@ uint32_t gpt_crc32_final(const struct gpt_crc32_ctx *ctx) */ int gpt_check_mbr_protective(const uint8_t *mbr_sector, uint32_t *gpt_lba) { - const struct gpt_mbr_part_entry *pte; - const uint16_t *boot_sig; + const uint8_t *pte; uint32_t i; int found = 0; uint32_t lba = 0; @@ -93,17 +111,16 @@ int gpt_check_mbr_protective(const uint8_t *mbr_sector, uint32_t *gpt_lba) } /* Check boot signature at offset 0x1FE */ - boot_sig = (const uint16_t *)(mbr_sector + GPT_MBR_BOOTSIG_OFFSET); - if (*boot_sig != GPT_MBR_BOOTSIG_VALUE) { + if (gpt_le16(mbr_sector + GPT_MBR_BOOTSIG_OFFSET) != + GPT_MBR_BOOTSIG_VALUE) { return -1; } /* Scan all 4 MBR partition entries for protective GPT type (0xEE) */ for (i = 0; i < 4; i++) { - pte = (const struct gpt_mbr_part_entry *)(mbr_sector + - GPT_MBR_ENTRY_START + (i * sizeof(struct gpt_mbr_part_entry))); - if (pte->ptype == GPT_PTYPE_PROTECTIVE) { - lba = pte->lba_first; + pte = mbr_sector + GPT_MBR_ENTRY_START + (i * GPT_MBR_PTE_SIZE); + if (pte[GPT_MBR_PTE_PTYPE] == GPT_PTYPE_PROTECTIVE) { + lba = gpt_le32(pte + GPT_MBR_PTE_LBA_FIRST); found = 1; break; } @@ -132,35 +149,49 @@ int gpt_check_mbr_protective(const uint8_t *mbr_sector, uint32_t *gpt_lba) */ int gpt_parse_header(const uint8_t *sector, struct guid_ptable *hdr) { - const struct guid_ptable *src; - struct guid_ptable tmp; + uint8_t tmp[GPT_SECTOR_SIZE]; struct gpt_crc32_ctx crc; + uint32_t hdr_size; if (sector == NULL || hdr == NULL) { return -1; } - src = (const struct guid_ptable *)sector; - /* Validate GPT signature */ - if (src->signature != GPT_SIGNATURE) { + if (gpt_le64(sector + GPT_HDR_SIGNATURE) != GPT_SIGNATURE) { return -1; } - if (src->hdr_size < 0x5C || src->hdr_size > GPT_SECTOR_SIZE) { + hdr_size = gpt_le32(sector + GPT_HDR_SIZE); + if (hdr_size < GPT_HDR_MIN_SIZE || hdr_size > GPT_SECTOR_SIZE) { return -1; } - memcpy(&tmp, src, sizeof(tmp)); - tmp.hdr_crc32 = 0; + /* The header CRC is computed over the on-disk bytes with its own field + * zeroed, so it has to run on a copy of the raw sector rather than on a + * host-order struct. Same stack cost as the struct copy it replaces. */ + memcpy(tmp, sector, sizeof(tmp)); + memset(tmp + GPT_HDR_CRC32, 0, 4); gpt_crc32_init(&crc); - gpt_crc32_update(&crc, (const uint8_t *)&tmp, src->hdr_size); - if (gpt_crc32_final(&crc) != src->hdr_crc32) { + gpt_crc32_update(&crc, tmp, hdr_size); + if (gpt_crc32_final(&crc) != gpt_le32(sector + GPT_HDR_CRC32)) { return -1; } - /* Copy header to output */ - memcpy(hdr, src, sizeof(struct guid_ptable)); + /* Populate the output in HOST order. */ + memset(hdr, 0, sizeof(struct guid_ptable)); + hdr->signature = gpt_le64(sector + GPT_HDR_SIGNATURE); + hdr->revision = gpt_le32(sector + GPT_HDR_REVISION); + hdr->hdr_size = hdr_size; + hdr->hdr_crc32 = gpt_le32(sector + GPT_HDR_CRC32); + hdr->main_lba = gpt_le64(sector + GPT_HDR_MAIN_LBA); + hdr->backup_lba = gpt_le64(sector + GPT_HDR_BACKUP_LBA); + hdr->first_usable = gpt_le64(sector + GPT_HDR_FIRST_USABLE); + hdr->last_usable = gpt_le64(sector + GPT_HDR_LAST_USABLE); + hdr->start_array = gpt_le64(sector + GPT_HDR_START_ARRAY); + hdr->n_part = gpt_le32(sector + GPT_HDR_N_PART); + hdr->array_sz = gpt_le32(sector + GPT_HDR_ARRAY_SZ); + hdr->part_crc = gpt_le32(sector + GPT_HDR_PART_CRC); return 0; } @@ -180,7 +211,8 @@ int gpt_parse_header(const uint8_t *sector, struct guid_ptable *hdr) int gpt_parse_partition(const uint8_t *entry_data, uint32_t entry_size, struct gpt_part_info *part) { - const struct gpt_part_entry *pe; + uint64_t first, last; + uint32_t i; if (entry_data == NULL || part == NULL) { return -1; @@ -190,34 +222,41 @@ int gpt_parse_partition(const uint8_t *entry_data, uint32_t entry_size, return -1; } - pe = (const struct gpt_part_entry *)entry_data; - - /* Check if partition entry is empty (type GUID is all zeros) */ - if (pe->type[0] == 0 && pe->type[1] == 0) { + /* Check if partition entry is empty (type GUID is all zeros). The GUID + * is compared as raw bytes, so its byte order does not matter. */ + if ((gpt_le64(entry_data + GPT_PE_TYPE) == 0) && + (gpt_le64(entry_data + GPT_PE_TYPE + 8) == 0)) { return -1; } + first = gpt_le64(entry_data + GPT_PE_FIRST); + last = gpt_le64(entry_data + GPT_PE_LAST); + /* Validate geometry */ - if (pe->first > pe->last) { + if (first > last) { return -1; } /* LBA 0 is the protective MBR; no valid partition can end there */ - if (pe->last == 0) { + if (last == 0) { return -1; } - /* Reject extents whose byte offset would overflow uint64_t: (pe->last + 1) - * must not wrap and (pe->last + 1) * GPT_SECTOR_SIZE must stay representable. - * Without this, pe->last = UINT64_MAX yields part->end = UINT64_MAX, which - * defeats the bounds check in disk_part_read(). pe->first <= pe->last, so - * bounding pe->last also bounds the part->start multiply. */ - if (pe->last >= (UINT64_MAX / GPT_SECTOR_SIZE)) { + /* Reject extents whose byte offset would overflow uint64_t: (last + 1) + * must not wrap and (last + 1) * GPT_SECTOR_SIZE must stay representable. + * Without this, last = UINT64_MAX yields part->end = UINT64_MAX, which + * defeats the bounds check in disk_part_read(). first <= last, so + * bounding last also bounds the part->start multiply. */ + if (last >= (UINT64_MAX / GPT_SECTOR_SIZE)) { return -1; } /* Extract partition info (convert LBA to byte offsets) */ - part->start = pe->first * GPT_SECTOR_SIZE; - part->end = ((pe->last + 1) * GPT_SECTOR_SIZE) - 1; - memcpy(part->name, pe->name, sizeof(part->name)); + part->start = first * GPT_SECTOR_SIZE; + part->end = ((last + 1) * GPT_SECTOR_SIZE) - 1; + /* The name is UTF-16LE on disk; store it in host order so + * gpt_part_name_eq() can compare code units directly. */ + for (i = 0; i < GPT_PART_NAME_SIZE; i++) { + part->name[i] = gpt_le16(entry_data + GPT_PE_NAME + (i * 2U)); + } return 0; } diff --git a/tools/unit-tests/unit-disk.c b/tools/unit-tests/unit-disk.c index 086c5f9429..c4d6bc4748 100644 --- a/tools/unit-tests/unit-disk.c +++ b/tools/unit-tests/unit-disk.c @@ -86,18 +86,72 @@ static uint32_t test_crc32(const uint8_t *data, uint32_t len) return ~crc; } -static void finalize_gpt_header_crc(struct guid_ptable *hdr) +/* Every on-disk field is little-endian regardless of host byte order, so the + * fixture writes bytes explicitly instead of assigning packed struct members. + * Assigning members only produces correct on-disk bytes on a little-endian + * host, which would make these tests unable to run big-endian. */ +static void d_put16(uint8_t *p, uint16_t v) { - hdr->hdr_crc32 = 0; - hdr->hdr_crc32 = test_crc32((const uint8_t *)hdr, hdr->hdr_size); + p[0] = (uint8_t)(v & 0xFFU); + p[1] = (uint8_t)((v >> 8) & 0xFFU); } -static void finalize_gpt_part_array_crc(struct guid_ptable *hdr) +static void d_put32(uint8_t *p, uint32_t v) { - uint32_t array_len = hdr->n_part * hdr->array_sz; - uint8_t *array = fake_disk + (hdr->start_array * GPT_SECTOR_SIZE); + p[0] = (uint8_t)(v & 0xFFU); + p[1] = (uint8_t)((v >> 8) & 0xFFU); + p[2] = (uint8_t)((v >> 16) & 0xFFU); + p[3] = (uint8_t)((v >> 24) & 0xFFU); +} + +static void d_put64(uint8_t *p, uint64_t v) +{ + d_put32(p, (uint32_t)(v & 0xFFFFFFFFU)); + d_put32(p + 4, (uint32_t)((v >> 32) & 0xFFFFFFFFU)); +} - hdr->part_crc = test_crc32(array, array_len); +/* GPT on-disk offsets (see src/gpt.c) */ +#define D_HDR_SIGNATURE 0x00 +#define D_HDR_REVISION 0x08 +#define D_HDR_SIZE 0x0C +#define D_HDR_CRC32 0x10 +#define D_HDR_START_ARRAY 0x48 +#define D_HDR_N_PART 0x50 +#define D_HDR_ARRAY_SZ 0x54 +#define D_HDR_PART_CRC 0x58 +#define D_PE_TYPE 0x00 +#define D_PE_UUID 0x10 +#define D_PE_FIRST 0x20 +#define D_PE_LAST 0x28 +#define D_PE_NAME 0x38 + +static uint32_t d_get32(const uint8_t *p) +{ + return ((uint32_t)p[0]) | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +static uint64_t d_get64(const uint8_t *p) +{ + return ((uint64_t)d_get32(p)) | ((uint64_t)d_get32(p + 4) << 32); +} + +static void finalize_gpt_header_crc(uint8_t *hdr) +{ + uint32_t hdr_size = d_get32(hdr + D_HDR_SIZE); + + d_put32(hdr + D_HDR_CRC32, 0); + d_put32(hdr + D_HDR_CRC32, test_crc32(hdr, hdr_size)); +} + +static void finalize_gpt_part_array_crc(uint8_t *hdr) +{ + uint64_t start_array = d_get64(hdr + D_HDR_START_ARRAY); + uint32_t n_part = d_get32(hdr + D_HDR_N_PART); + uint32_t array_sz = d_get32(hdr + D_HDR_ARRAY_SZ); + uint8_t *array = fake_disk + (start_array * GPT_SECTOR_SIZE); + + d_put32(hdr + D_HDR_PART_CRC, test_crc32(array, n_part * array_sz)); } /* --- Helpers to build fake disk layouts --- */ @@ -106,60 +160,57 @@ static void finalize_gpt_part_array_crc(struct guid_ptable *hdr) * Uses memcpy to avoid unaligned-pointer warnings on packed structs. */ static void write_utf16(void *dst, const char *ascii, unsigned int max) { - uint16_t *p = (uint16_t *)dst; + uint8_t *p = (uint8_t *)dst; unsigned int i; - uint16_t val; + memset(dst, 0, max * sizeof(uint16_t)); for (i = 0; i < max && ascii[i]; i++) { - val = (uint16_t)ascii[i]; - memcpy(&p[i], &val, sizeof(val)); + d_put16(p + (i * 2U), (uint16_t)ascii[i]); } } /* Populate fake_disk with a valid protective-MBR + GPT header + N entries. */ static void build_gpt_disk(void) { - struct gpt_mbr_part_entry *mbr_entry; - uint16_t *boot_sig; - struct guid_ptable *gpt_hdr; - struct gpt_part_entry *pe; + uint8_t *mbr_entry; + uint8_t *gpt_hdr; + uint8_t *pe; memset(fake_disk, 0, FAKE_DISK_SIZE); /* --- Sector 0: MBR --- */ - mbr_entry = (struct gpt_mbr_part_entry *)(fake_disk + GPT_MBR_ENTRY_START); - mbr_entry->ptype = GPT_PTYPE_PROTECTIVE; /* 0xEE */ - mbr_entry->lba_first = 1; - mbr_entry->lba_size = 0xFFFFFFFF; + mbr_entry = fake_disk + GPT_MBR_ENTRY_START; + mbr_entry[0x04] = GPT_PTYPE_PROTECTIVE; /* 0xEE */ + d_put32(mbr_entry + 0x08, 1); /* lba_first */ + d_put32(mbr_entry + 0x0C, 0xFFFFFFFF); /* lba_size */ - boot_sig = (uint16_t *)(fake_disk + GPT_MBR_BOOTSIG_OFFSET); - *boot_sig = GPT_MBR_BOOTSIG_VALUE; /* 0xAA55 */ + d_put16(fake_disk + GPT_MBR_BOOTSIG_OFFSET, GPT_MBR_BOOTSIG_VALUE); /* --- Sector 1: GPT header --- */ - gpt_hdr = (struct guid_ptable *)(fake_disk + GPT_SECTOR_SIZE); - gpt_hdr->signature = GPT_SIGNATURE; - gpt_hdr->revision = 0x00010000; - gpt_hdr->hdr_size = 92; - gpt_hdr->start_array = 2; /* partition entries start at LBA 2 */ - gpt_hdr->n_part = 2; - gpt_hdr->array_sz = 128; /* bytes per entry */ + gpt_hdr = fake_disk + GPT_SECTOR_SIZE; + d_put64(gpt_hdr + D_HDR_SIGNATURE, GPT_SIGNATURE); + d_put32(gpt_hdr + D_HDR_REVISION, 0x00010000); + d_put32(gpt_hdr + D_HDR_SIZE, 92); + d_put64(gpt_hdr + D_HDR_START_ARRAY, 2); /* entries start at LBA 2 */ + d_put32(gpt_hdr + D_HDR_N_PART, 2); + d_put32(gpt_hdr + D_HDR_ARRAY_SZ, 128); /* bytes per entry */ /* --- Sector 2: Partition entries --- */ /* Entry 0: name "boot" */ - pe = (struct gpt_part_entry *)(fake_disk + 2 * GPT_SECTOR_SIZE); - pe->type[0] = 0x0001020304050607ULL; /* non-zero type GUID */ - pe->type[1] = 0x08090A0B0C0D0E0FULL; - pe->first = PART0_OFF; - pe->last = PART0_END; - write_utf16(pe->name, "boot", GPT_PART_NAME_SIZE); + pe = fake_disk + (2 * GPT_SECTOR_SIZE); + d_put64(pe + D_PE_TYPE, 0x0001020304050607ULL); /* non-zero type GUID */ + d_put64(pe + D_PE_TYPE + 8, 0x08090A0B0C0D0E0FULL); + d_put64(pe + D_PE_FIRST, PART0_OFF); + d_put64(pe + D_PE_LAST, PART0_END); + write_utf16(pe + D_PE_NAME, "boot", GPT_PART_NAME_SIZE); /* Entry 1: name "rootfs" */ - pe = (struct gpt_part_entry *)(fake_disk + 2 * GPT_SECTOR_SIZE + 128); - pe->type[0] = 0x1011121314151617ULL; - pe->type[1] = 0x18191A1B1C1D1E1FULL; - pe->first = PART1_OFF; - pe->last = PART1_END; - write_utf16(pe->name, "rootfs", GPT_PART_NAME_SIZE); + pe = fake_disk + (2 * GPT_SECTOR_SIZE) + 128; + d_put64(pe + D_PE_TYPE, 0x1011121314151617ULL); + d_put64(pe + D_PE_TYPE + 8, 0x18191A1B1C1D1E1FULL); + d_put64(pe + D_PE_FIRST, PART1_OFF); + d_put64(pe + D_PE_LAST, PART1_END); + write_utf16(pe + D_PE_NAME, "rootfs", GPT_PART_NAME_SIZE); /* Fill partition data areas with known patterns for read tests. * GPT last LBA is inclusive, so partition spans @@ -179,26 +230,24 @@ static void build_gpt_disk(void) */ static void build_mbr_disk(void) { - struct gpt_mbr_part_entry *pte; - uint16_t *boot_sig; + uint8_t *pte; memset(fake_disk, 0, FAKE_DISK_SIZE); /* MBR entry 0: FAT32 LBA */ - pte = (struct gpt_mbr_part_entry *)(fake_disk + GPT_MBR_ENTRY_START); - pte->ptype = 0x0C; /* FAT32 LBA */ - pte->lba_first = 16; - pte->lba_size = 32; + pte = (uint8_t *)(fake_disk + GPT_MBR_ENTRY_START); + pte[0x04] = (uint8_t)(0x0C); /* FAT32 LBA */ + d_put32(pte + 0x08, 16); + d_put32(pte + 0x0C, 32); /* MBR entry 1: Linux */ - pte = (struct gpt_mbr_part_entry *)(fake_disk + GPT_MBR_ENTRY_START + + pte = (uint8_t *)(fake_disk + GPT_MBR_ENTRY_START + sizeof(struct gpt_mbr_part_entry)); - pte->ptype = 0x83; /* Linux */ - pte->lba_first = 48; - pte->lba_size = 64; + pte[0x04] = (uint8_t)(0x83); /* Linux */ + d_put32(pte + 0x08, 48); + d_put32(pte + 0x0C, 64); - boot_sig = (uint16_t *)(fake_disk + GPT_MBR_BOOTSIG_OFFSET); - *boot_sig = GPT_MBR_BOOTSIG_VALUE; + d_put16(fake_disk + GPT_MBR_BOOTSIG_OFFSET, GPT_MBR_BOOTSIG_VALUE); } /* ============================================================ @@ -240,23 +289,21 @@ START_TEST(test_gpt_parse_header) /* Corrupt a header field without updating CRC */ { - struct guid_ptable *gpt_hdr = - (struct guid_ptable *)(fake_disk + GPT_SECTOR_SIZE); - gpt_hdr->n_part = 3; + uint8_t *gpt_hdr = (uint8_t *)(fake_disk + GPT_SECTOR_SIZE); + d_put32(gpt_hdr + D_HDR_N_PART, 3); ck_assert_int_eq( gpt_parse_header(fake_disk + GPT_SECTOR_SIZE, &hdr), -1); - gpt_hdr->n_part = 2; + d_put32(gpt_hdr + D_HDR_N_PART, 2); finalize_gpt_header_crc(gpt_hdr); } /* Corrupt signature in the real header */ { - struct guid_ptable *gpt_hdr = - (struct guid_ptable *)(fake_disk + GPT_SECTOR_SIZE); - gpt_hdr->signature = 0; + uint8_t *gpt_hdr = (uint8_t *)(fake_disk + GPT_SECTOR_SIZE); + d_put64(gpt_hdr + D_HDR_SIGNATURE, 0); ck_assert_int_eq( gpt_parse_header(fake_disk + GPT_SECTOR_SIZE, &hdr), -1); - gpt_hdr->signature = GPT_SIGNATURE; + d_put64(gpt_hdr + D_HDR_SIGNATURE, GPT_SIGNATURE); finalize_gpt_header_crc(gpt_hdr); } @@ -342,13 +389,13 @@ END_TEST START_TEST(test_disk_open_gpt_rejects_part_array_crc_mismatch) { - struct gpt_part_entry *pe; + uint8_t *pe; build_gpt_disk(); - pe = (struct gpt_part_entry *)(fake_disk + 2 * GPT_SECTOR_SIZE); - pe->first = PART1_OFF; - pe->last = PART1_END; + pe = (uint8_t *)(fake_disk + 2 * GPT_SECTOR_SIZE); + d_put64(pe + D_PE_FIRST, PART1_OFF); + d_put64(pe + D_PE_LAST, PART1_END); ck_assert_int_eq(disk_open(0), -1); ck_assert_int_eq(Drives[0].is_open, 0); @@ -512,14 +559,14 @@ START_TEST(test_gpt_partition_end_inclusive) /* GPT spec: last LBA is inclusive. A partition with first=10, last=20 * spans 11 sectors. End byte = (20+1)*512 - 1. */ uint8_t entry[128]; - struct gpt_part_entry *pe = (struct gpt_part_entry *)entry; + uint8_t *pe = (uint8_t *)entry; struct gpt_part_info info; memset(entry, 0, sizeof(entry)); - pe->type[0] = 0x0001020304050607ULL; - pe->type[1] = 0x08090A0B0C0D0E0FULL; - pe->first = 10; - pe->last = 20; + d_put64(pe + D_PE_TYPE, 0x0001020304050607ULL); + d_put64(pe + D_PE_TYPE + 8, 0x08090A0B0C0D0E0FULL); + d_put64(pe + D_PE_FIRST, 10); + d_put64(pe + D_PE_LAST, 20); ck_assert_int_eq(gpt_parse_partition(entry, 128, &info), 0); ck_assert_uint_eq(info.start, 10 * 512); @@ -532,13 +579,13 @@ END_TEST START_TEST(test_disk_open_failure_clears_is_open) { /* If GPT header parse fails, is_open must be reset to 0. */ - struct guid_ptable *gpt_hdr; + uint8_t *gpt_hdr; build_gpt_disk(); /* Corrupt the GPT header signature */ - gpt_hdr = (struct guid_ptable *)(fake_disk + GPT_SECTOR_SIZE); - gpt_hdr->signature = 0xDEADBEEF; + gpt_hdr = (uint8_t *)(fake_disk + GPT_SECTOR_SIZE); + d_put64(gpt_hdr + D_HDR_SIGNATURE, 0xDEADBEEF); ck_assert_int_eq(disk_open(0), -1); ck_assert_int_eq(Drives[0].is_open, 0); @@ -550,14 +597,14 @@ START_TEST(test_gpt_parse_partition_last_zero) /* If first=0, last=0 with non-zero type GUID, (last+1)*512 - 1 would * be 511 but LBA 0 is the protective MBR — must reject. */ uint8_t entry[128]; - struct gpt_part_entry *pe = (struct gpt_part_entry *)entry; + uint8_t *pe = (uint8_t *)entry; struct gpt_part_info info; memset(entry, 0, sizeof(entry)); - pe->type[0] = 0x0001020304050607ULL; - pe->type[1] = 0x08090A0B0C0D0E0FULL; - pe->first = 0; - pe->last = 0; + d_put64(pe + D_PE_TYPE, 0x0001020304050607ULL); + d_put64(pe + D_PE_TYPE + 8, 0x08090A0B0C0D0E0FULL); + d_put64(pe + D_PE_FIRST, 0); + d_put64(pe + D_PE_LAST, 0); ck_assert_int_eq(gpt_parse_partition(entry, 128, &info), -1); } @@ -565,18 +612,18 @@ END_TEST START_TEST(test_gpt_parse_partition_last_overflow) { - /* pe->last = UINT64_MAX passes the first>last and last==0 guards, but - * (pe->last + 1) * GPT_SECTOR_SIZE - 1 would wrap to UINT64_MAX, defeating + /* last = UINT64_MAX passes the first>last and last==0 guards, but + * (last + 1) * GPT_SECTOR_SIZE - 1 would wrap to UINT64_MAX, defeating * the bounds check in disk_part_read. Must be rejected. */ uint8_t entry[128]; - struct gpt_part_entry *pe = (struct gpt_part_entry *)entry; + uint8_t *pe = (uint8_t *)entry; struct gpt_part_info info; memset(entry, 0, sizeof(entry)); - pe->type[0] = 0x0001020304050607ULL; - pe->type[1] = 0x08090A0B0C0D0E0FULL; - pe->first = 1; - pe->last = 0xFFFFFFFFFFFFFFFFULL; + d_put64(pe + D_PE_TYPE, 0x0001020304050607ULL); + d_put64(pe + D_PE_TYPE + 8, 0x08090A0B0C0D0E0FULL); + d_put64(pe + D_PE_FIRST, 1); + d_put64(pe + D_PE_LAST, 0xFFFFFFFFFFFFFFFFULL); ck_assert_int_eq(gpt_parse_partition(entry, 128, &info), -1); } @@ -597,13 +644,13 @@ START_TEST(test_disk_open_mbr_bad_bootsig) { /* MBR disk without valid 0xAA55 boot signature and no protective 0xEE. * Falls through GPT check, then fails on boot_sig validation. */ - struct gpt_mbr_part_entry *pte; + uint8_t *pte; memset(fake_disk, 0, FAKE_DISK_SIZE); - pte = (struct gpt_mbr_part_entry *)(fake_disk + GPT_MBR_ENTRY_START); - pte->ptype = 0x0C; - pte->lba_first = 16; - pte->lba_size = 32; + pte = (uint8_t *)(fake_disk + GPT_MBR_ENTRY_START); + pte[0x04] = (uint8_t)(0x0C); + d_put32(pte + 0x08, 16); + d_put32(pte + 0x0C, 32); /* No boot signature set — 0x0000 instead of 0xAA55 */ ck_assert_int_eq(disk_open(0), -1); @@ -615,12 +662,12 @@ START_TEST(test_disk_open_gpt_excess_partitions) { /* GPT header claims more partitions than MAX_PARTITIONS. disk_open * must cap n_parts to MAX_PARTITIONS. */ - struct guid_ptable *gpt_hdr; + uint8_t *gpt_hdr; build_gpt_disk(); - gpt_hdr = (struct guid_ptable *)(fake_disk + GPT_SECTOR_SIZE); - gpt_hdr->n_part = MAX_PARTITIONS + 10; + gpt_hdr = (uint8_t *)(fake_disk + GPT_SECTOR_SIZE); + d_put32(gpt_hdr + D_HDR_N_PART, MAX_PARTITIONS + 10); finalize_gpt_part_array_crc(gpt_hdr); finalize_gpt_header_crc(gpt_hdr); @@ -635,12 +682,12 @@ START_TEST(test_disk_open_gpt_large_array_sz) { /* GPT header with array_sz larger than GPT_PART_ENTRY_SIZE (256). * Loop must break immediately without reading entries. */ - struct guid_ptable *gpt_hdr; + uint8_t *gpt_hdr; build_gpt_disk(); - gpt_hdr = (struct guid_ptable *)(fake_disk + GPT_SECTOR_SIZE); - gpt_hdr->array_sz = GPT_PART_ENTRY_SIZE + 1; /* 257 > 256 */ + gpt_hdr = (uint8_t *)(fake_disk + GPT_SECTOR_SIZE); + d_put32(gpt_hdr + D_HDR_ARRAY_SZ, GPT_PART_ENTRY_SIZE + 1); /* 257 > 256 */ finalize_gpt_part_array_crc(gpt_hdr); finalize_gpt_header_crc(gpt_hdr); @@ -654,13 +701,13 @@ START_TEST(test_disk_open_gpt_rejects_huge_part_array) * enormous n_part * array_sz must be rejected before the partition-entry * CRC scan loop runs, otherwise it forces a pre-auth DoS via one disk * read per 512-byte chunk of the (here 8 MB) declared array. */ - struct guid_ptable *gpt_hdr; + uint8_t *gpt_hdr; build_gpt_disk(); - gpt_hdr = (struct guid_ptable *)(fake_disk + GPT_SECTOR_SIZE); - gpt_hdr->n_part = 0x10000; /* 65536 entries ... */ - gpt_hdr->array_sz = 128; /* ... * 128 bytes = 8 MB */ + gpt_hdr = (uint8_t *)(fake_disk + GPT_SECTOR_SIZE); + d_put32(gpt_hdr + D_HDR_N_PART, 0x10000); /* 65536 entries ... */ + d_put32(gpt_hdr + D_HDR_ARRAY_SZ, 128); /* ... * 128 bytes = 8 MB */ finalize_gpt_header_crc(gpt_hdr); /* attacker can always fix header CRC */ disk_read_count = 0; @@ -680,14 +727,14 @@ START_TEST(test_disk_open_gpt_lba_no_overflow) * gpt_lba >= 0x800000: 512 * 0x800001 wraps to 0x200, silently * redirecting the read back to LBA 1 (the real GPT header) instead of * the out-of-range LBA the field actually names. */ - struct gpt_mbr_part_entry *mbr_entry; + uint8_t *mbr_entry; build_gpt_disk(); /* Point the protective entry at an LBA whose 512* product overflows a * 32-bit unsigned back to 0x200 (LBA 1). */ - mbr_entry = (struct gpt_mbr_part_entry *)(fake_disk + GPT_MBR_ENTRY_START); - mbr_entry->lba_first = 0x800001; + mbr_entry = (uint8_t *)(fake_disk + GPT_MBR_ENTRY_START); + d_put32(mbr_entry + 0x08, 0x800001); /* With correct 64-bit arithmetic the header read targets byte * 0x100000200, far past the fake disk, so disk_open must fail rather @@ -701,19 +748,19 @@ START_TEST(test_disk_open_gpt_empty_entry_mid_table) { /* GPT header says 3 partitions but entry[1] has zeroed type GUID. * gpt_parse_partition returns -1 → loop breaks, only 1 partition found. */ - struct guid_ptable *gpt_hdr; - struct gpt_part_entry *pe; + uint8_t *gpt_hdr; + uint8_t *pe; build_gpt_disk(); - gpt_hdr = (struct guid_ptable *)(fake_disk + GPT_SECTOR_SIZE); - gpt_hdr->n_part = 3; + gpt_hdr = (uint8_t *)(fake_disk + GPT_SECTOR_SIZE); + d_put32(gpt_hdr + D_HDR_N_PART, 3); finalize_gpt_header_crc(gpt_hdr); /* Zero out entry 1's type GUID */ - pe = (struct gpt_part_entry *)(fake_disk + 2 * GPT_SECTOR_SIZE + 128); - pe->type[0] = 0; - pe->type[1] = 0; + pe = (uint8_t *)(fake_disk + 2 * GPT_SECTOR_SIZE + 128); + d_put64(pe + D_PE_TYPE, 0); + d_put64(pe + D_PE_TYPE + 8, 0); finalize_gpt_part_array_crc(gpt_hdr); finalize_gpt_header_crc(gpt_hdr); @@ -724,33 +771,31 @@ END_TEST START_TEST(test_disk_open_mbr_zero_lba_entry) { /* MBR entry with lba_first=0 must be skipped. */ - struct gpt_mbr_part_entry *pte; - uint16_t *boot_sig; + uint8_t *pte; memset(fake_disk, 0, FAKE_DISK_SIZE); /* Entry 0: valid */ - pte = (struct gpt_mbr_part_entry *)(fake_disk + GPT_MBR_ENTRY_START); - pte->ptype = 0x0C; - pte->lba_first = 16; - pte->lba_size = 32; + pte = (uint8_t *)(fake_disk + GPT_MBR_ENTRY_START); + pte[0x04] = (uint8_t)(0x0C); + d_put32(pte + 0x08, 16); + d_put32(pte + 0x0C, 32); /* Entry 1: lba_first=0, should be skipped */ - pte = (struct gpt_mbr_part_entry *)(fake_disk + GPT_MBR_ENTRY_START + + pte = (uint8_t *)(fake_disk + GPT_MBR_ENTRY_START + sizeof(struct gpt_mbr_part_entry)); - pte->ptype = 0x83; - pte->lba_first = 0; - pte->lba_size = 64; + pte[0x04] = (uint8_t)(0x83); + d_put32(pte + 0x08, 0); + d_put32(pte + 0x0C, 64); /* Entry 2: lba_size=0, should also be skipped */ - pte = (struct gpt_mbr_part_entry *)(fake_disk + GPT_MBR_ENTRY_START + + pte = (uint8_t *)(fake_disk + GPT_MBR_ENTRY_START + 2 * sizeof(struct gpt_mbr_part_entry)); - pte->ptype = 0x83; - pte->lba_first = 48; - pte->lba_size = 0; + pte[0x04] = (uint8_t)(0x83); + d_put32(pte + 0x08, 48); + d_put32(pte + 0x0C, 0); - boot_sig = (uint16_t *)(fake_disk + GPT_MBR_BOOTSIG_OFFSET); - *boot_sig = GPT_MBR_BOOTSIG_VALUE; + d_put16(fake_disk + GPT_MBR_BOOTSIG_OFFSET, GPT_MBR_BOOTSIG_VALUE); ck_assert_int_eq(disk_open(0), 1); /* only entry 0 counted */ } @@ -864,11 +909,10 @@ START_TEST(test_gpt_check_mbr_bad_bootsig) /* Valid MBR structure but corrupt boot signature */ memset(sector, 0, sizeof(sector)); { - struct gpt_mbr_part_entry *pte = - (struct gpt_mbr_part_entry *)(sector + GPT_MBR_ENTRY_START); - pte->ptype = GPT_PTYPE_PROTECTIVE; - pte->lba_first = 1; - pte->lba_size = 0xFFFFFFFF; + uint8_t *pte = (uint8_t *)(sector + GPT_MBR_ENTRY_START); + pte[0x04] = (uint8_t)(GPT_PTYPE_PROTECTIVE); + d_put32(pte + 0x08, 1); + d_put32(pte + 0x0C, 0xFFFFFFFF); } /* boot sig left as 0x0000 — not 0xAA55 */ @@ -879,14 +923,14 @@ END_TEST START_TEST(test_gpt_parse_partition_first_gt_last) { uint8_t entry[128]; - struct gpt_part_entry *pe = (struct gpt_part_entry *)entry; + uint8_t *pe = (uint8_t *)entry; struct gpt_part_info info; memset(entry, 0, sizeof(entry)); - pe->type[0] = 0x0001020304050607ULL; - pe->type[1] = 0x08090A0B0C0D0E0FULL; - pe->first = 100; - pe->last = 50; /* first > last → invalid */ + d_put64(pe + D_PE_TYPE, 0x0001020304050607ULL); + d_put64(pe + D_PE_TYPE + 8, 0x08090A0B0C0D0E0FULL); + d_put64(pe + D_PE_FIRST, 100); + d_put64(pe + D_PE_LAST, 50); /* first > last → invalid */ ck_assert_int_eq(gpt_parse_partition(entry, 128, &info), -1); } @@ -895,14 +939,14 @@ END_TEST START_TEST(test_gpt_parse_partition_first_eq_last) { uint8_t entry[128]; - struct gpt_part_entry *pe = (struct gpt_part_entry *)entry; + uint8_t *pe = (uint8_t *)entry; struct gpt_part_info info; memset(entry, 0, sizeof(entry)); - pe->type[0] = 0x0001020304050607ULL; - pe->type[1] = 0x08090A0B0C0D0E0FULL; - pe->first = 5; - pe->last = 5; /* first == last is a valid single-sector partition */ + d_put64(pe + D_PE_TYPE, 0x0001020304050607ULL); + d_put64(pe + D_PE_TYPE + 8, 0x08090A0B0C0D0E0FULL); + d_put64(pe + D_PE_FIRST, 5); + d_put64(pe + D_PE_LAST, 5); /* first == last is a valid single-sector partition */ ck_assert_int_eq(gpt_parse_partition(entry, 128, &info), 0); ck_assert_uint_eq(info.start, 5 * GPT_SECTOR_SIZE); @@ -995,6 +1039,79 @@ START_TEST(test_sfdisk_gpt_last_lba_access) } END_TEST +/* Lock the on-disk field offsets. Everything below is written as explicit + * little-endian bytes and then read back through the parser, so a change to + * an offset, a width, or the byte order fails here rather than silently + * mis-parsing a real disk. This is what guards the byte-wise conversion in + * src/gpt.c against a future refactor. */ +START_TEST(test_gpt_header_field_offsets) +{ + uint8_t sector[GPT_SECTOR_SIZE]; + struct guid_ptable hdr; + + memset(sector, 0, sizeof(sector)); + d_put64(sector + D_HDR_SIGNATURE, GPT_SIGNATURE); + d_put32(sector + D_HDR_REVISION, 0x00010000); + d_put32(sector + D_HDR_SIZE, 92); + d_put64(sector + D_HDR_START_ARRAY, 0x1122334455667788ULL); + d_put32(sector + D_HDR_N_PART, 0x11223344); + d_put32(sector + D_HDR_ARRAY_SZ, 128); + finalize_gpt_header_crc(sector); + + ck_assert_int_eq(gpt_parse_header(sector, &hdr), 0); + /* Values come back in HOST order, whatever the host byte order is. */ + ck_assert_uint_eq(hdr.hdr_size, 92); + ck_assert(hdr.signature == GPT_SIGNATURE); + ck_assert_uint_eq(hdr.revision, 0x00010000U); + ck_assert(hdr.start_array == 0x1122334455667788ULL); + ck_assert_uint_eq(hdr.n_part, 0x11223344U); + ck_assert_uint_eq(hdr.array_sz, 128U); +} +END_TEST + +/* A single flipped byte in the header must fail the CRC. */ +START_TEST(test_gpt_header_crc_rejects_tamper) +{ + uint8_t sector[GPT_SECTOR_SIZE]; + struct guid_ptable hdr; + + memset(sector, 0, sizeof(sector)); + d_put64(sector + D_HDR_SIGNATURE, GPT_SIGNATURE); + d_put32(sector + D_HDR_SIZE, 92); + d_put64(sector + D_HDR_START_ARRAY, 2); + d_put32(sector + D_HDR_N_PART, 2); + d_put32(sector + D_HDR_ARRAY_SZ, 128); + finalize_gpt_header_crc(sector); + ck_assert_int_eq(gpt_parse_header(sector, &hdr), 0); + + /* Flip one bit in a field the CRC covers. */ + sector[D_HDR_N_PART] ^= 0x01; + ck_assert_int_eq(gpt_parse_header(sector, &hdr), -1); +} +END_TEST + +START_TEST(test_gpt_partition_field_offsets) +{ + uint8_t entry[128]; + struct gpt_part_info info; + + memset(entry, 0, sizeof(entry)); + d_put64(entry + D_PE_TYPE, 0x0001020304050607ULL); + d_put64(entry + D_PE_TYPE + 8, 0x08090A0B0C0D0E0FULL); + d_put64(entry + D_PE_FIRST, 0x0000000000000022ULL); + d_put64(entry + D_PE_LAST, 0x0000000000000041ULL); + write_utf16(entry + D_PE_NAME, "rootfs", GPT_PART_NAME_SIZE); + + ck_assert_int_eq(gpt_parse_partition(entry, sizeof(entry), &info), 0); + ck_assert(info.start == 0x22ULL * GPT_SECTOR_SIZE); + ck_assert(info.end == ((0x41ULL + 1U) * GPT_SECTOR_SIZE) - 1U); + /* The name is UTF-16LE on disk and host-order after parsing. */ + ck_assert_int_eq(gpt_part_name_eq(info.name, "rootfs"), 1); + ck_assert_int_eq(gpt_part_name_eq(info.name, "boot"), 0); +} +END_TEST + + /* ============================================================ * Suite setup * ============================================================ */ @@ -1050,6 +1167,9 @@ Suite *wolfboot_suite(void) tcase_add_test(tc_cov, test_gpt_parse_partition_first_gt_last); tcase_add_test(tc_cov, test_gpt_parse_partition_first_eq_last); tcase_add_test(tc_cov, test_gpt_part_name_eq_label_too_long); + tcase_add_test(tc_cov, test_gpt_header_field_offsets); + tcase_add_test(tc_cov, test_gpt_header_crc_rejects_tamper); + tcase_add_test(tc_cov, test_gpt_partition_field_offsets); tcase_add_test(tc_cov, test_gpt_part_name_eq_not_null_terminated); suite_add_tcase(s, tc_cov);