From patchwork Fri Apr 24 15:36:54 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Frazer Carsley X-Patchwork-Id: 86850 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 2A4A1FED3EA for ; Fri, 24 Apr 2026 15:38:20 +0000 (UTC) Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.25306.1777045094481639254 for ; Fri, 24 Apr 2026 08:38:14 -0700 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: body hash did not verify" header.i=@arm.com header.s=foss header.b=fdqRD2oP; spf=pass (domain: arm.com, ip: 217.140.110.172, mailfrom: frazer.carsley@arm.com) Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 57653358A; Fri, 24 Apr 2026 08:38:08 -0700 (PDT) Received: from e138143.arm.com (unknown [10.57.18.238]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id CD4D03F7B4; Fri, 24 Apr 2026 08:38:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1777045093; bh=RH7UIUhaEinv7fSL/1CT9MxOXStMGHDRK/j/NbGSFws=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=fdqRD2oPu8HMbBCWjrHjDeooJeTZ9h2Kwj/AN1dKAUFUE8dV0T8AXOWve/+FYZDtH PJPGhmTjurrFBcKUUZmLssGgritW+f9K8GUX6q1fyP5Cu104dsUp/dhrc4wXVytu7y Ar4SOhPVy5Hvj3qjCr7xq3bCCSN1U6BwQhKbwjoc= From: Frazer Carsley To: meta-arm@lists.yoctoproject.org Cc: Frazer Carsley Subject: [PATCH 1/3] arm-bsp/trusted-firmware-m:cs1k: Add fixes for GPT library Date: Fri, 24 Apr 2026 16:36:54 +0100 Message-ID: <20260424153656.774555-2-frazer.carsley@arm.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260424153656.774555-1-frazer.carsley@arm.com> References: <20260424153656.774555-1-frazer.carsley@arm.com> MIME-Version: 1.0 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Fri, 24 Apr 2026 15:38:20 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/meta-arm/message/7024 These patches backport bug fixes for the GPT library in TF-M. Signed-off-by: Frazer Carsley --- ...pt-Fix-final-entry-not-being-removed.patch | 114 ++++++ ...lib-gpt-Replace-warnings-with-errors.patch | 37 ++ ...-gpt-Enforce-entry-size-of-128-bytes.patch | 268 +++++++++++++ ...Ensure-block-size-complies-with-spec.patch | 38 ++ ...0050-lib-gpt-Expand-table-validation.patch | 352 ++++++++++++++++++ .../trusted-firmware-m-corstone1000.inc | 5 + 6 files changed, 814 insertions(+) create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0046-lib-gpt-Fix-final-entry-not-being-removed.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0047-lib-gpt-Replace-warnings-with-errors.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0048-lib-gpt-Enforce-entry-size-of-128-bytes.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0049-lib-gpt-Ensure-block-size-complies-with-spec.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0050-lib-gpt-Expand-table-validation.patch diff --git a/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0046-lib-gpt-Fix-final-entry-not-being-removed.patch b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0046-lib-gpt-Fix-final-entry-not-being-removed.patch new file mode 100644 index 00000000..f7e7179d --- /dev/null +++ b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0046-lib-gpt-Fix-final-entry-not-being-removed.patch @@ -0,0 +1,114 @@ +From 4f11567a0152f1ecd98159ca555d8663ee8e5ce0 Mon Sep 17 00:00:00 2001 +From: Frazer Carsley +Date: Fri, 10 Apr 2026 17:20:39 +0100 +Subject: [PATCH] lib: gpt: Fix final entry not being removed + +The final entry would not be removed due to the removals being hidden +behind a check on whether the removed entry was not the final entry, +causing it to be a no-op. Removal operation is no longer hidden behind +that condition. + +Change-Id: Ib264d988d57cb39098f2783c4a001fcf3b004270 +Signed-off-by: Frazer Carsley +Upstream-Status: Backport [f3cf6bda1534b8893620c7b7c0d9ff647b44603e] +--- + lib/gpt/src/gpt.c | 64 +++++++++++++++++++++++------------------------ + 1 file changed, 32 insertions(+), 32 deletions(-) + +diff --git a/lib/gpt/src/gpt.c b/lib/gpt/src/gpt.c +index a9dbb918e..e4e7fde32 100644 +--- a/lib/gpt/src/gpt.c ++++ b/lib/gpt/src/gpt.c +@@ -648,6 +648,7 @@ psa_status_t gpt_entry_remove(const struct efi_guid_t *guid) + * to be modified if the last entry in the array was moved or if it is + * the only LBA used by the partition array + */ ++ const uint64_t array_end_lba = partition_array_last_lba(&primary_gpt);; + if (cached_index != primary_gpt.num_used_partitions - 1 || + cached_index < gpt_entry_per_lba_count()) + { +@@ -669,7 +670,6 @@ psa_status_t gpt_entry_remove(const struct efi_guid_t *guid) + * Use a second buffer to read each consecutive LBA and copy that to + * the global LBA buffer to then write afterwards. + */ +- const uint64_t array_end_lba = partition_array_last_lba(&primary_gpt); + for (uint64_t i = partition_entry_lba(&primary_gpt, cached_index) + 1; + i <= array_end_lba; + ++i) +@@ -705,45 +705,45 @@ psa_status_t gpt_entry_remove(const struct efi_guid_t *guid) + sizeof(array_buf) - primary_gpt.header.entry_size); + memcpy(lba_buf, array_buf, TFM_GPT_BLOCK_SIZE); + } ++ } + +- /* What was the final LBA is now cached and may be empty or partially-filled */ +- cached_lba = array_end_lba; +- write_buffered = false; +- uint32_t entries_in_last_lba = (--primary_gpt.num_used_partitions) % gpt_entry_per_lba_count(); +- if (entries_in_last_lba == 0) { +- /* There's nothing left in this LBA, so zero it all and write it out. +- * There is also no need to do an erase just to zero afterwards. +- */ +- memset(lba_buf, 0, TFM_GPT_BLOCK_SIZE); +- if (backup_gpt_array_lba != 0) { +- int write_ret = plat_flash_driver->write( +- backup_gpt_array_lba + array_end_lba - PRIMARY_GPT_ARRAY_LBA, +- lba_buf); +- if (write_ret != TFM_GPT_BLOCK_SIZE) { +- return PSA_ERROR_STORAGE_FAILURE; +- } +- } +- int write_ret = plat_flash_driver->write(array_end_lba, lba_buf); ++ /* What was the final LBA is now cached and may be empty or partially-filled */ ++ cached_lba = array_end_lba; ++ write_buffered = false; ++ uint32_t entries_in_last_lba = (--primary_gpt.num_used_partitions) % gpt_entry_per_lba_count(); ++ if (entries_in_last_lba == 0) { ++ /* There's nothing left in this LBA, so zero it all and write it out. ++ * There is also no need to do an erase just to zero afterwards. ++ */ ++ memset(lba_buf, 0, TFM_GPT_BLOCK_SIZE); ++ if (backup_gpt_array_lba != 0) { ++ int write_ret = plat_flash_driver->write( ++ backup_gpt_array_lba + array_end_lba - PRIMARY_GPT_ARRAY_LBA, ++ lba_buf); + if (write_ret != TFM_GPT_BLOCK_SIZE) { + return PSA_ERROR_STORAGE_FAILURE; + } +- } else { +- /* Zero what is not needed anymore */ +- memset( +- lba_buf + primary_gpt.header.entry_size * entries_in_last_lba, +- 0, +- (gpt_entry_per_lba_count() - entries_in_last_lba) * primary_gpt.header.entry_size); +- if (backup_gpt_array_lba != 0) { +- ret = write_to_flash(backup_gpt_array_lba + array_end_lba - PRIMARY_GPT_ARRAY_LBA); +- if (ret != PSA_SUCCESS) { +- return ret; +- } +- } +- ret = write_to_flash(array_end_lba); ++ } ++ int write_ret = plat_flash_driver->write(array_end_lba, lba_buf); ++ if (write_ret != TFM_GPT_BLOCK_SIZE) { ++ return PSA_ERROR_STORAGE_FAILURE; ++ } ++ } else { ++ /* Zero what is not needed anymore */ ++ memset( ++ lba_buf + primary_gpt.header.entry_size * entries_in_last_lba, ++ 0, ++ (gpt_entry_per_lba_count() - entries_in_last_lba) * primary_gpt.header.entry_size); ++ if (backup_gpt_array_lba != 0) { ++ ret = write_to_flash(backup_gpt_array_lba + array_end_lba - PRIMARY_GPT_ARRAY_LBA); + if (ret != PSA_SUCCESS) { + return ret; + } + } ++ ret = write_to_flash(array_end_lba); ++ if (ret != PSA_SUCCESS) { ++ return ret; ++ } + } + + /* Update the header after flash changes */ diff --git a/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0047-lib-gpt-Replace-warnings-with-errors.patch b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0047-lib-gpt-Replace-warnings-with-errors.patch new file mode 100644 index 00000000..93b1fb3f --- /dev/null +++ b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0047-lib-gpt-Replace-warnings-with-errors.patch @@ -0,0 +1,37 @@ +From 68874e58811c5d4004492f15b3ac46d2cca186c0 Mon Sep 17 00:00:00 2001 +From: Frazer Carsley +Date: Thu, 16 Apr 2026 09:54:55 +0100 +Subject: [PATCH] lib: gpt: Replace warnings with errors + +These warnings return errors, so it makes more sense to output an error +message. + +Change-Id: I589e24ba8aba2502a3fc86d2aeb648d125c022bd +Signed-off-by: Frazer Carsley +Upstream-Status: Backport [68874e58811c5d4004492f15b3ac46d2cca186c0] +--- + lib/gpt/src/gpt.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/lib/gpt/src/gpt.c b/lib/gpt/src/gpt.c +index 16bda8aba..32c7277bd 100644 +--- a/lib/gpt/src/gpt.c ++++ b/lib/gpt/src/gpt.c +@@ -830,7 +830,7 @@ psa_status_t gpt_defragment(void) + */ + psa_status_t ret = sort_partition_array(&primary_gpt); + if (ret != PSA_SUCCESS) { +- WARN("Unable to defragment flash!\n"); ++ ERROR("Unable to defragment flash!\n"); + return ret; + } + +@@ -926,7 +926,7 @@ psa_status_t gpt_init(struct gpt_flash_driver_t *flash_driver, uint64_t max_part + if (mbr.partitions[0].os_type == MBR_TYPE_GPT) { + ret = read_table_from_flash(&primary_gpt, true); + } else { +- WARN("Unsupported legacy MBR in use\n"); ++ ERROR("Unsupported legacy MBR in use\n"); + ret = PSA_ERROR_NOT_SUPPORTED; + } + diff --git a/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0048-lib-gpt-Enforce-entry-size-of-128-bytes.patch b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0048-lib-gpt-Enforce-entry-size-of-128-bytes.patch new file mode 100644 index 00000000..4cba9e32 --- /dev/null +++ b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0048-lib-gpt-Enforce-entry-size-of-128-bytes.patch @@ -0,0 +1,268 @@ +From ff08a9d998c545a6152789f8ce55bd4200a937cf Mon Sep 17 00:00:00 2001 +From: Frazer Carsley +Date: Wed, 15 Apr 2026 10:59:28 +0100 +Subject: [PATCH] lib: gpt: Enforce entry size of 128 bytes + +Previous to this, the entry size could have been set to any number, even +those deemed illegal by the UEFI spec 2.10 [1], as the library did not +validate it. 128 bytes is the minimum size for a partition entry and +most standard tools that create GPTs will use this. + +[1] https://uefi.org/specs/UEFI/2.10/05_GUID_Partition_Table_Format.html + +Change-Id: Ib6c436353a4b8e00e6c6e63b933a49f11c0a7340 +Signed-off-by: Frazer Carsley +Upstream-Status: Backport [ff08a9d998c545a6152789f8ce55bd4200a937cf] +--- + lib/gpt/inc/gpt.h | 2 + + lib/gpt/src/gpt.c | 65 +++++++++++++++++++++----------- + lib/gpt/unittests/gpt/test_gpt.c | 40 ++++++++++++++++++++ + 3 files changed, 85 insertions(+), 22 deletions(-) + +diff --git a/lib/gpt/inc/gpt.h b/lib/gpt/inc/gpt.h +index baf4767f9..34ce67580 100644 +--- a/lib/gpt/inc/gpt.h ++++ b/lib/gpt/inc/gpt.h +@@ -214,6 +214,7 @@ psa_status_t gpt_entry_remove(const struct efi_guid_t *guid); + * + * \retval PSA_SUCCESS GPT is valid. + * \retval PSA_ERROR_STORAGE_FAILURE I/O error. ++ * \retval PSA_ERROR_NOT_SUPPORTED Entry size is not 128 bytes + * \retval PSA_ERROR_INVALID_SIGNATURE GPT is invalid. + */ + psa_status_t gpt_validate(bool is_primary); +@@ -249,6 +250,7 @@ psa_status_t gpt_defragment(void); + * functions defined by \p flash_driver is NULL. The init + * and uninit functions may be NULL if not required. + * \retval PSA_ERROR_NOT_SUPPORTED Legacy MBR is used and not GPT. ++ * \retval PSA_ERROR_NOT_SUPPORTED Entry size is not 128 bytes + */ + __attribute__((nonnull(1))) + psa_status_t gpt_init(struct gpt_flash_driver_t *flash_driver, +diff --git a/lib/gpt/src/gpt.c b/lib/gpt/src/gpt.c +index 32c7277bd..200e21599 100644 +--- a/lib/gpt/src/gpt.c ++++ b/lib/gpt/src/gpt.c +@@ -459,7 +459,7 @@ psa_status_t gpt_entry_move(const struct efi_guid_t *guid, + + /* Cached LBA */ + for (uint32_t i = 0; i < num_entries_in_cached_lba; ++i) { +- memcpy(&entry, lba_buf + (i * primary_gpt.header.entry_size), GPT_ENTRY_SIZE); ++ memcpy(&entry, lba_buf + (i * GPT_ENTRY_SIZE), GPT_ENTRY_SIZE); + + const struct efi_guid_t ent_guid = entry.unique_guid; + if (efi_guid_cmp(&ent_guid, guid) == 0) { +@@ -658,9 +658,9 @@ psa_status_t gpt_entry_remove(const struct efi_guid_t *guid) + const uint32_t lba_index = cached_index % gpt_entry_per_lba_count(); + if (lba_index + 1 != gpt_entry_per_lba_count()) { + memmove( +- lba_buf + lba_index * primary_gpt.header.entry_size, +- lba_buf + (lba_index + 1) * primary_gpt.header.entry_size, +- (gpt_entry_per_lba_count() - lba_index - 1) * primary_gpt.header.entry_size); ++ lba_buf + lba_index * GPT_ENTRY_SIZE, ++ lba_buf + (lba_index + 1) * GPT_ENTRY_SIZE, ++ (gpt_entry_per_lba_count() - lba_index - 1) * GPT_ENTRY_SIZE); + } + + /* If this is not the last LBA, then read the next LBA into memory and +@@ -683,7 +683,7 @@ psa_status_t gpt_entry_remove(const struct efi_guid_t *guid) + } + + memcpy( +- lba_buf + primary_gpt.header.entry_size * (gpt_entry_per_lba_count() - 1), ++ lba_buf + GPT_ENTRY_SIZE * (gpt_entry_per_lba_count() - 1), + array_buf, + GPT_ENTRY_SIZE); + +@@ -701,8 +701,8 @@ psa_status_t gpt_entry_remove(const struct efi_guid_t *guid) + + memmove( + array_buf, +- array_buf + primary_gpt.header.entry_size, +- sizeof(array_buf) - primary_gpt.header.entry_size); ++ array_buf + GPT_ENTRY_SIZE, ++ sizeof(array_buf) - GPT_ENTRY_SIZE); + memcpy(lba_buf, array_buf, TFM_GPT_BLOCK_SIZE); + } + } +@@ -731,9 +731,9 @@ psa_status_t gpt_entry_remove(const struct efi_guid_t *guid) + } else { + /* Zero what is not needed anymore */ + memset( +- lba_buf + primary_gpt.header.entry_size * entries_in_last_lba, ++ lba_buf + GPT_ENTRY_SIZE * entries_in_last_lba, + 0, +- (gpt_entry_per_lba_count() - entries_in_last_lba) * primary_gpt.header.entry_size); ++ (gpt_entry_per_lba_count() - entries_in_last_lba) * GPT_ENTRY_SIZE); + if (backup_gpt_array_lba != 0) { + ret = write_to_flash(backup_gpt_array_lba + array_end_lba - PRIMARY_GPT_ARRAY_LBA); + if (ret != PSA_SUCCESS) { +@@ -934,6 +934,14 @@ psa_status_t gpt_init(struct gpt_flash_driver_t *flash_driver, uint64_t max_part + goto fail_load; + } + ++ /* Ensure entry size is supported. */ ++ if (primary_gpt.header.entry_size != GPT_ENTRY_SIZE) { ++ ERROR("Unsupported entry size 0x%08x, must be 0x%08x\n", ++ primary_gpt.header.entry_size, GPT_ENTRY_SIZE); ++ ret = PSA_ERROR_NOT_SUPPORTED; ++ goto fail_load; ++ } ++ + /* Count the number of used entries, assuming the array is not sparese */ + ret = count_used_partitions(&primary_gpt, &primary_gpt.num_used_partitions); + if (ret != PSA_SUCCESS) { +@@ -948,6 +956,12 @@ psa_status_t gpt_init(struct gpt_flash_driver_t *flash_driver, uint64_t max_part + if (ret != PSA_SUCCESS) { + goto fail_load; + } ++ if (backup_gpt.header.entry_size != GPT_ENTRY_SIZE) { ++ ERROR("Unsupported entry size 0x%08x, must be 0x%08x\n", ++ backup_gpt.header.entry_size, GPT_ENTRY_SIZE); ++ ret = PSA_ERROR_NOT_SUPPORTED; ++ goto fail_load; ++ } + backup_gpt_array_lba = backup_gpt.header.array_lba; + } else { + WARN("Backup GPT location is unknown!\n"); +@@ -999,11 +1013,7 @@ psa_status_t gpt_uninit(void) + /* Returns the number of partition entries in each LBA */ + static inline uint64_t gpt_entry_per_lba_count(void) + { +- static uint64_t num_entries = 0; +- if (num_entries == 0) { +- num_entries = TFM_GPT_BLOCK_SIZE / primary_gpt.header.entry_size; +- } +- return num_entries; ++ return TFM_GPT_BLOCK_SIZE / GPT_ENTRY_SIZE; + } + + /* Copies information from the entry to the user visible structure */ +@@ -1129,15 +1139,15 @@ static psa_status_t update_header(uint32_t num_partitions) + /* Take the CRC of the partition array */ + uint32_t crc = 0; + for (uint32_t i = 0; i < header->num_partitions; ++i) { +- uint8_t entry_buf[header->entry_size]; +- memset(entry_buf, 0, header->entry_size); ++ uint8_t entry_buf[GPT_ENTRY_SIZE]; ++ memset(entry_buf, 0, GPT_ENTRY_SIZE); + struct gpt_entry_t *entry = (struct gpt_entry_t *)entry_buf; + + psa_status_t ret = read_entry_from_flash(&primary_gpt, i, entry); + if (ret != PSA_SUCCESS) { + return ret; + } +- crc = efi_soft_crc32_update(crc, entry_buf, header->entry_size); ++ crc = efi_soft_crc32_update(crc, entry_buf, GPT_ENTRY_SIZE); + } + header->array_crc = crc; + +@@ -1268,7 +1278,7 @@ static psa_status_t read_entry_from_flash(const struct gpt_t *table, + + memcpy( + entry, +- lba_buf + ((array_index % gpt_entry_per_lba_count()) * table->header.entry_size), ++ lba_buf + ((array_index % gpt_entry_per_lba_count()) * GPT_ENTRY_SIZE), + GPT_ENTRY_SIZE); + + return PSA_SUCCESS; +@@ -1415,7 +1425,7 @@ static psa_status_t write_entry(uint32_t array_index, + + /* Copy into buffer */ + uint32_t index_in_lba = array_index % gpt_entry_per_lba_count(); +- memcpy(lba_buf + index_in_lba * primary_gpt.header.entry_size, entry, GPT_ENTRY_SIZE); ++ memcpy(lba_buf + index_in_lba * GPT_ENTRY_SIZE, entry, GPT_ENTRY_SIZE); + + /* Write on every nth operation. */ + if (++num_writes == gpt_entry_per_lba_count()) { +@@ -1519,18 +1529,29 @@ static psa_status_t validate_table(struct gpt_t *table, bool is_primary) + return PSA_ERROR_INVALID_SIGNATURE; + } + ++ /* Check the entry size. This is not a part of the spec but ensures the ++ * library only supports entry sizes equal to 128. Otherwise, the backup ++ * could be used to restore the primary with an entry size that is different ++ * and break that assumption, or vise-versa ++ */ ++ if (header->entry_size != GPT_ENTRY_SIZE) { ++ ERROR("Unsupported entry size 0x%08x, must be 0x%08x\n", ++ header->entry_size, GPT_ENTRY_SIZE); ++ return PSA_ERROR_NOT_SUPPORTED; ++ } ++ + /* Check the CRC of the partition array */ + calc_crc = 0; + for (uint32_t i = 0; i < header->num_partitions; ++i) { +- uint8_t entry_buf[header->entry_size]; +- memset(entry_buf, 0, header->entry_size); ++ uint8_t entry_buf[GPT_ENTRY_SIZE]; ++ memset(entry_buf, 0, GPT_ENTRY_SIZE); + struct gpt_entry_t *entry = (struct gpt_entry_t *)entry_buf; + + psa_status_t ret = read_entry_from_flash(table, i, entry); + if (ret != PSA_SUCCESS) { + return ret; + } +- calc_crc = efi_soft_crc32_update(calc_crc, (uint8_t *)entry, header->entry_size); ++ calc_crc = efi_soft_crc32_update(calc_crc, (uint8_t *)entry, GPT_ENTRY_SIZE); + } + + if (calc_crc != header->array_crc) { +diff --git a/lib/gpt/unittests/gpt/test_gpt.c b/lib/gpt/unittests/gpt/test_gpt.c +index 2f05a6b4a..db897b967 100644 +--- a/lib/gpt/unittests/gpt/test_gpt.c ++++ b/lib/gpt/unittests/gpt/test_gpt.c +@@ -351,6 +351,30 @@ void test_gpt_init_should_failWhenMbrTypeInvalid(void) + TEST_ASSERT_EQUAL(PSA_ERROR_NOT_SUPPORTED, setup_test_gpt()); + } + ++void test_gpt_init_should_failWhenEntrySizeBad(void) ++{ ++ test_header.entry_size--; ++ /* Expect first a valid MBR read */ ++ register_mocked_read(&test_mbr, sizeof(test_mbr)); ++ ++ /* Expect a GPT header read second */ ++ register_mocked_read(&test_header, sizeof(test_header)); ++ ++ TEST_ASSERT_EQUAL(PSA_ERROR_NOT_SUPPORTED, gpt_init(&mock_driver, TEST_MAX_PARTITIONS)); ++ test_header.entry_size = default_header.entry_size; ++ ++ /* Now do the backup. */ ++ register_mocked_read(&test_mbr, sizeof(test_mbr)); ++ register_mocked_read(&test_header, sizeof(test_header)); ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ ++ /* Expect fourth the backup to be read. Make the entry size bad */ ++ test_header.entry_size = 0; ++ setup_backup_gpt(); ++ ++ TEST_ASSERT_EQUAL(PSA_ERROR_NOT_SUPPORTED, gpt_init(&mock_driver, TEST_MAX_PARTITIONS)); ++} ++ + void test_gpt_init_should_failWhenFlashDriverNotFullyDefined(void) + { + gpt_flash_read_t read_fn = mock_driver.read; +@@ -433,6 +457,22 @@ void test_gpt_validate_should_failWhenLbaPointerBad(void) + TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(false)); + } + ++void test_gpt_validate_should_failWhenBackupEntrySizeInvalid(void) ++{ ++ /* The entry size for the primary GPT is validated on gpt_init and kept ++ * in memory. Therefore, the entry size can only be validated on gpt_validate ++ * for the backup table, which is read ++ */ ++ setup_test_gpt(); ++ struct gpt_header_t backup_header; ++ MAKE_BACKUP_HEADER(backup_header, test_header); ++ backup_header.entry_size--; ++ register_mocked_read(&backup_header, sizeof(backup_header)); ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ ++ TEST_ASSERT_EQUAL(PSA_ERROR_NOT_SUPPORTED, gpt_validate(false)); ++} ++ + void test_gpt_validate_should_failWhenArrayCrcBad(void) + { + test_header.array_crc--; diff --git a/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0049-lib-gpt-Ensure-block-size-complies-with-spec.patch b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0049-lib-gpt-Ensure-block-size-complies-with-spec.patch new file mode 100644 index 00000000..d2258fba --- /dev/null +++ b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0049-lib-gpt-Ensure-block-size-complies-with-spec.patch @@ -0,0 +1,38 @@ +From c07da31be4551ee9b3ce546a1f6adccb19bc3b59 Mon Sep 17 00:00:00 2001 +From: Frazer Carsley +Date: Wed, 15 Apr 2026 11:17:25 +0100 +Subject: [PATCH] lib: gpt: Ensure block size complies with spec + +The UEFI spec 2.10 [1] implicitly requires a block size of at least 512 +in order to fit a legacy Master Boot Record. + +[1] https://uefi.org/specs/UEFI/2.10/05_GUID_Partition_Table_Format.html#legacy-master-boot-record-mbr + +Change-Id: I8218ef3d883b51d8fa14835e0ed884139fdebc7d +Signed-off-by: Frazer Carsley +Upstream-Status: Backport [c07da31be4551ee9b3ce546a1f6adccb19bc3b59] +--- + lib/gpt/src/gpt.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/lib/gpt/src/gpt.c b/lib/gpt/src/gpt.c +index 200e21599..cda9fe358 100644 +--- a/lib/gpt/src/gpt.c ++++ b/lib/gpt/src/gpt.c +@@ -20,11 +20,15 @@ + #include "efi_soft_crc.h" + + /* This needs to be defined by the platform and is used by the GPT library as +- * the number of bytes in a Logical Block Address (LBA) ++ * the number of bytes in a Logical Block Address (LBA). It also must be at least ++ * 512. + */ + #ifndef TFM_GPT_BLOCK_SIZE + #error "TFM_GPT_BLOCK_SIZE must be defined if using GPT library!" + #endif ++#if TFM_GPT_BLOCK_SIZE < 512 ++#error "TFM_GPT_BLOCK_SIZE must be at least 512!" ++#endif + + /* Where Master Boot Record (MBR) is on flash */ + #define MBR_LBA 0ULL diff --git a/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0050-lib-gpt-Expand-table-validation.patch b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0050-lib-gpt-Expand-table-validation.patch new file mode 100644 index 00000000..aefd898b --- /dev/null +++ b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0050-lib-gpt-Expand-table-validation.patch @@ -0,0 +1,352 @@ +From bcce0ce881817b36ad52df550bcbf41b4f0d4938 Mon Sep 17 00:00:00 2001 +From: Frazer Carsley +Date: Wed, 15 Apr 2026 15:09:03 +0100 +Subject: [PATCH] lib: gpt: Expand table validation + +It is implied by the UEFI spec 2.10 [1] that the backup GPT must be +located at the end of storage. To this effect, ensure that it is the +largest known LBA value. + +Additionally, it is also implied that the partition entry arrays, both +primary and backup, must be before or after usable space respectively. + +[1] https://uefi.org/specs/UEFI/2.10/05_GUID_Partition_Table_Format.html + +Change-Id: I5042836822f990a21fbf48b19422879384a844aa +Signed-off-by: Frazer Carsley +Upstream-Status: Backport [bcce0ce881817b36ad52df550bcbf41b4f0d4938] +--- + lib/gpt/src/gpt.c | 115 ++++++++++++++++++++ + lib/gpt/unittests/gpt/test_gpt.c | 179 +++++++++++++++++++++++++++++++ + 2 files changed, 294 insertions(+) + +diff --git a/lib/gpt/src/gpt.c b/lib/gpt/src/gpt.c +index cda9fe358..0335befa7 100644 +--- a/lib/gpt/src/gpt.c ++++ b/lib/gpt/src/gpt.c +@@ -229,6 +229,12 @@ static psa_status_t mbr_load(struct mbr_t *mbr); + static bool gpt_entry_cmp_guid(const struct gpt_entry_t *entry, const void *guid); + static bool gpt_entry_cmp_name(const struct gpt_entry_t *entry, const void *name); + static bool gpt_entry_cmp_type(const struct gpt_entry_t *entry, const void *type); ++static psa_status_t validate_backup_gpt_lba(const uint64_t backup_lba, ++ const uint64_t primary_lba, ++ const uint64_t partition_array_end, ++ const struct gpt_header_t *header); ++static psa_status_t validate_array_lba(const uint64_t partition_array_end, ++ const uint64_t usable_lba_start); + static psa_status_t validate_table(struct gpt_t *table, bool is_primary); + static psa_status_t restore_table(struct gpt_t *restore_from, bool is_primary); + static psa_status_t sort_partition_array(const struct gpt_t *table); +@@ -1498,6 +1504,73 @@ static inline void swap_headers(const struct gpt_header_t *src, struct gpt_heade + primary_gpt.header.array_lba); + } + ++/* Validate that the backup GPT LBA is greater than all other LBAs in the header ++ */ ++static psa_status_t validate_backup_gpt_lba(const uint64_t backup_lba, ++ const uint64_t primary_lba, ++ const uint64_t partition_array_end, ++ const struct gpt_header_t *header) ++{ ++ if (backup_lba <= primary_lba) { ++ ERROR("Backup LBA (0x%08x%08x) must be final LBA on flash, " ++ "primary LBA at 0x%08x%08x\n", ++ (uint32_t)(backup_lba >> 32), ++ (uint32_t)(backup_lba), ++ (uint32_t)(primary_lba >> 32), ++ (uint32_t)(primary_lba)); ++ return PSA_ERROR_INVALID_SIGNATURE; ++ } ++ ++ if (backup_lba <= header->first_lba) { ++ ERROR("Backup LBA (0x%08x%08x) must be final LBA on flash, " ++ "first usable LBA at 0x%08x%08x\n", ++ (uint32_t)(backup_lba >> 32), ++ (uint32_t)(backup_lba), ++ (uint32_t)(header->first_lba >> 32), ++ (uint32_t)(header->first_lba)); ++ return PSA_ERROR_INVALID_SIGNATURE; ++ } ++ ++ if (backup_lba <= header->last_lba) { ++ ERROR("Backup LBA (0x%08x%08x) must be final LBA on flash, " ++ "last usable LBA at 0x%08x%08x\n", ++ (uint32_t)(backup_lba >> 32), ++ (uint32_t)(backup_lba), ++ (uint32_t)(header->last_lba >> 32), ++ (uint32_t)(header->last_lba)); ++ return PSA_ERROR_INVALID_SIGNATURE; ++ } ++ ++ if (backup_lba <= partition_array_end) { ++ ERROR("Backup LBA (0x%08x%08x) must be final LBA on flash, " ++ "partition array ends at LBA at 0x%08x%08x\n", ++ (uint32_t)(backup_lba >> 32), ++ (uint32_t)(backup_lba), ++ (uint32_t)(partition_array_end >> 32), ++ (uint32_t)(partition_array_end)); ++ return PSA_ERROR_INVALID_SIGNATURE; ++ } ++ ++ return PSA_SUCCESS; ++} ++ ++/* Validate partition array is outside the area of usable flash */ ++static psa_status_t validate_array_lba(const uint64_t partition_array_end, ++ const uint64_t usable_lba_start) ++{ ++ if (partition_array_end >= usable_lba_start) { ++ ERROR("GPT partition array must not be in usable space: " ++ "0x%08x%08x >= 0x%08x%08x\n", ++ (uint32_t)(partition_array_end >> 32), ++ (uint32_t)partition_array_end, ++ (uint32_t)(usable_lba_start >> 32), ++ (uint32_t)usable_lba_start); ++ return PSA_ERROR_INVALID_SIGNATURE; ++ } ++ ++ return PSA_SUCCESS; ++} ++ + /* Validates a specific GPT. */ + static psa_status_t validate_table(struct gpt_t *table, bool is_primary) + { +@@ -1564,6 +1637,48 @@ static psa_status_t validate_table(struct gpt_t *table, bool is_primary) + return PSA_ERROR_INVALID_SIGNATURE; + } + ++ /* Check the backup LBA is greater than all other LBAs. Check also ++ * the partition array cannot be overritten by data by ensuring ++ * that it is not between the first and last usable LBAs ++ */ ++ if (is_primary) { ++ psa_status_t ret = validate_backup_gpt_lba( ++ header->backup_lba, ++ header->current_lba, ++ partition_entry_lba(table, header->num_partitions - 1), ++ header); ++ if (ret != PSA_SUCCESS) { ++ return ret; ++ } ++ ++ /* Go to the final LBA of the partition array, including unused entries */ ++ ret = validate_array_lba( ++ partition_entry_lba(table, header->num_partitions - 1), ++ header->first_lba); ++ if (ret != PSA_SUCCESS) { ++ return ret; ++ } ++ } else { ++ psa_status_t ret = validate_backup_gpt_lba( ++ header->current_lba, ++ header->backup_lba, ++ partition_entry_lba(table, header->num_partitions - 1), ++ header); ++ if (ret != PSA_SUCCESS) { ++ return ret; ++ } ++ ++ /* To flip the condition, negate the parameters passed: it becomes ++ * -array_lba >= -last_lba (equivalent to) array_lba < last_lba ++ * (equivalent to) last_lba >= array_lba. This is because the backup array is ++ * after the last_lba ++ */ ++ ret = validate_array_lba(~(header->array_lba), ~(header->last_lba)); ++ if (ret != PSA_SUCCESS) { ++ return ret; ++ } ++ } ++ + if (is_primary) { + /* Any time the primary table is considered valid, cache the backup + * LBA field +diff --git a/lib/gpt/unittests/gpt/test_gpt.c b/lib/gpt/unittests/gpt/test_gpt.c +index db897b967..bd161ec74 100644 +--- a/lib/gpt/unittests/gpt/test_gpt.c ++++ b/lib/gpt/unittests/gpt/test_gpt.c +@@ -493,6 +493,185 @@ void test_gpt_validate_should_failWhenArrayCrcBad(void) + TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(false)); + } + ++void test_gpt_validate_should_failWhenBackupLbaNotAtEndOfDisk(void) ++{ ++ /* First test when the backup lba is before usable disk */ ++ test_header.backup_lba = test_header.first_lba - 1; ++ setup_test_gpt(); ++ ++ /* Each entry will be read in order to check the partition array CRC */ ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(true)); ++ ++ /* Then test when the backup is before in usable disk space */ ++ test_header.backup_lba = test_header.first_lba; ++ setup_test_gpt(); ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(true)); ++ ++ test_header.backup_lba = test_header.first_lba + 1; ++ setup_test_gpt(); ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(true)); ++ ++ test_header.backup_lba = test_header.last_lba - 1; ++ setup_test_gpt(); ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(true)); ++ ++ test_header.backup_lba = test_header.last_lba; ++ setup_test_gpt(); ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(true)); ++ ++ /* Finally, test when the backup is before the end of the partition entry array */ ++ test_header.backup_lba = test_header.array_lba - 1; ++ setup_test_gpt(); ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(true)); ++ ++ /* For this scenario, manually setup the backup header so that the array LBA ++ * (also the backup header LBA) is valid on init and can then be validated ++ * with gpt_validate ++ */ ++ test_header.backup_lba = test_header.array_lba; ++ ++ /* Expect first a valid MBR read */ ++ register_mocked_read(&test_mbr, sizeof(test_mbr)); ++ ++ /* Expect a GPT header read second */ ++ register_mocked_read(&test_header, sizeof(test_header)); ++ ++ /* Expect third each partition is read to find the number in use. This is ++ * also the backup header, which will be cached ++ */ ++ setup_backup_gpt(); ++ ++ TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_init(&mock_driver, TEST_MAX_PARTITIONS)); ++ ++ /* Backup partition array read for crc */ ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(true)); ++ ++ /* Now do the backup gpt header */ ++ struct gpt_header_t backup_header; ++ test_header.backup_lba = test_header.first_lba - 1; ++ MAKE_BACKUP_HEADER(backup_header, test_header); ++ ++ setup_test_gpt(); ++ register_mocked_read(&backup_header, sizeof(backup_header)); ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(false)); ++ ++ test_header.backup_lba = backup_header.first_lba; ++ backup_header.current_lba = backup_header.first_lba; ++ setup_test_gpt(); ++ register_mocked_read(&backup_header, sizeof(backup_header)); ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(false)); ++ ++ test_header.backup_lba = backup_header.first_lba + 1; ++ backup_header.current_lba = backup_header.first_lba + 1; ++ setup_test_gpt(); ++ register_mocked_read(&backup_header, sizeof(backup_header)); ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(false)); ++ ++ test_header.backup_lba = backup_header.last_lba - 1; ++ backup_header.current_lba = backup_header.last_lba - 1; ++ setup_test_gpt(); ++ register_mocked_read(&backup_header, sizeof(backup_header)); ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(false)); ++ ++ test_header.backup_lba = backup_header.last_lba; ++ backup_header.current_lba = backup_header.last_lba; ++ setup_test_gpt(); ++ register_mocked_read(&backup_header, sizeof(backup_header)); ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(false)); ++ ++ test_header.backup_lba = backup_header.array_lba - 1; ++ backup_header.current_lba = backup_header.array_lba - 1; ++ setup_test_gpt(); ++ register_mocked_read(&backup_header, sizeof(backup_header)); ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(false)); ++ ++ test_header.backup_lba = backup_header.array_lba; ++ backup_header.current_lba = backup_header.array_lba; ++ setup_test_gpt(); ++ register_mocked_read(&backup_header, sizeof(backup_header)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(false)); ++} ++ ++void test_gpt_validate_should_failWhenPartitionArrayInUsableDiskSpace(void) ++{ ++ /* First test when the primary partition array is in usable disk space */ ++ test_header.array_lba = test_header.first_lba; ++ setup_test_gpt(); ++ ++ /* Each entry will be read in order to check the partition array CRC */ ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(true)); ++ ++ test_header.array_lba = test_header.first_lba + 1; ++ setup_test_gpt(); ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(true)); ++ ++ test_header.array_lba = test_header.last_lba - 1; ++ setup_test_gpt(); ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(true)); ++ ++ test_header.array_lba = test_header.last_lba; ++ setup_test_gpt(); ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(true)); ++ ++ /* Then test when the primary partition array is after usable disk space */ ++ test_header.array_lba = test_header.last_lba + 1; ++ setup_test_gpt(); ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(true)); ++ ++ /* Now do the backup gpt header, ensuring it is always after usable space */ ++ struct gpt_header_t backup_header; ++ MAKE_BACKUP_HEADER(backup_header, test_header); ++ ++ backup_header.array_lba = test_header.first_lba - 1; ++ setup_test_gpt(); ++ register_mocked_read(&backup_header, sizeof(backup_header)); ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(false)); ++ ++ /* Then test that the backup partition array is after usable disk space */ ++ backup_header.array_lba = test_header.first_lba; ++ setup_test_gpt(); ++ register_mocked_read(&backup_header, sizeof(backup_header)); ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(false)); ++ ++ backup_header.array_lba = test_header.first_lba + 1; ++ setup_test_gpt(); ++ register_mocked_read(&backup_header, sizeof(backup_header)); ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(false)); ++ ++ backup_header.array_lba = test_header.last_lba - 1; ++ setup_test_gpt(); ++ register_mocked_read(&backup_header, sizeof(backup_header)); ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(false)); ++ ++ backup_header.array_lba = test_header.last_lba; ++ setup_test_gpt(); ++ register_mocked_read(&backup_header, sizeof(backup_header)); ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(false)); ++} ++ + void test_gpt_restore_should_restorePrimaryFromBackup(void) + { + /* Start with a valid GPT */ diff --git a/meta-arm-bsp/recipes-bsp/trusted-firmware-m/trusted-firmware-m-corstone1000.inc b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/trusted-firmware-m-corstone1000.inc index 55f48a3c..2bfea84b 100644 --- a/meta-arm-bsp/recipes-bsp/trusted-firmware-m/trusted-firmware-m-corstone1000.inc +++ b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/trusted-firmware-m-corstone1000.inc @@ -77,6 +77,11 @@ SRC_URI:append:corstone1000 = " \ file://0043-plat-cs1k-Create-and-remove-FWU-image-partitions.patch \ file://0044-plat-cs1k-Derive-host-base-addresses-from-offsets.patch \ file://0045-plat-cs1k-Drive-NPU-via-external-system-reset-contro.patch \ + file://0046-lib-gpt-Fix-final-entry-not-being-removed.patch \ + file://0047-lib-gpt-Replace-warnings-with-errors.patch \ + file://0048-lib-gpt-Enforce-entry-size-of-128-bytes.patch \ + file://0049-lib-gpt-Ensure-block-size-complies-with-spec.patch \ + file://0050-lib-gpt-Expand-table-validation.patch \ " SRCREV_tfm-psa-adac:corstone1000 = "f2809ae231be33a1afcd7714f40756c67d846c88" From patchwork Fri Apr 24 15:36:55 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Frazer Carsley X-Patchwork-Id: 86851 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 3D1B3FED3EC for ; Fri, 24 Apr 2026 15:38:20 +0000 (UTC) Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.25307.1777045095566554339 for ; Fri, 24 Apr 2026 08:38:15 -0700 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: body hash did not verify" header.i=@arm.com header.s=foss header.b=cN0xhuyf; spf=pass (domain: arm.com, ip: 217.140.110.172, mailfrom: frazer.carsley@arm.com) Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id ACAFD358A; Fri, 24 Apr 2026 08:38:09 -0700 (PDT) Received: from e138143.arm.com (unknown [10.57.18.238]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id 2D9143F7B4; Fri, 24 Apr 2026 08:38:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1777045095; bh=TpYclEhVHWoahm7d3HrV5Np4XfYEsbYGC81NCf/3LXM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=cN0xhuyfw9yduIs1KSfKb9xsIPYK0hC68Va5p6+U3tF6EDKOwEegLZm/bAk2kIE2c GwO9LpxgipvYkRVRfCoBizg4JO62vYWZgBjtPdMAflPkwgFb9YJHb740nPuFYAz01n ft7QLpByMZg7HjDV2uq0fA6ecLVKuBxFeG2yyb28= From: Frazer Carsley To: meta-arm@lists.yoctoproject.org Cc: Frazer Carsley Subject: [PATCH 2/3] arm-bsp/trusted-firmware-m:cs1k: Add extra GPT library operations Date: Fri, 24 Apr 2026 16:36:55 +0100 Message-ID: <20260424153656.774555-3-frazer.carsley@arm.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260424153656.774555-1-frazer.carsley@arm.com> References: <20260424153656.774555-1-frazer.carsley@arm.com> MIME-Version: 1.0 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Fri, 24 Apr 2026 15:38:20 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/meta-arm/message/7025 These patches add functionality to duplicate GPT partition entries. This combines a "create-write" into a single step, letting the GPT library handle it, useful for the Corstone1000 firmware update process. Signed-off-by: Frazer Carsley --- ...ent-of-GUIDs-in-unittests-more-clear.patch | 216 ++++++ ...Provide-macro-identifying-free-space.patch | 157 ++++ ...t-Add-operation-to-duplicate-entries.patch | 255 +++++++ ...ively-erase-blocks-when-moving-parti.patch | 240 ++++++ .../0055-lib-gpt-Clarify-API-operation.patch | 38 + ...gpt-Add-metadata-only-API-operations.patch | 701 ++++++++++++++++++ .../trusted-firmware-m-corstone1000.inc | 6 + 7 files changed, 1613 insertions(+) create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0051-lib-gpt-Show-intent-of-GUIDs-in-unittests-more-clear.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0052-lib-gpt-Provide-macro-identifying-free-space.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0053-lib-gpt-Add-operation-to-duplicate-entries.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0054-lib-gpt-Consecutively-erase-blocks-when-moving-parti.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0055-lib-gpt-Clarify-API-operation.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0056-lib-gpt-Add-metadata-only-API-operations.patch diff --git a/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0051-lib-gpt-Show-intent-of-GUIDs-in-unittests-more-clear.patch b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0051-lib-gpt-Show-intent-of-GUIDs-in-unittests-more-clear.patch new file mode 100644 index 00000000..1279db9d --- /dev/null +++ b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0051-lib-gpt-Show-intent-of-GUIDs-in-unittests-more-clear.patch @@ -0,0 +1,216 @@ +From ca0d50fc1abbfe165941dc0bd674bb117f236f87 Mon Sep 17 00:00:00 2001 +From: Frazer Carsley +Date: Mon, 30 Mar 2026 14:06:20 +0100 +Subject: [PATCH] lib: gpt: Show intent of GUIDs in unittests more clearly + +The standard EFI_GUID macros used in the unittests do not convey the +full meaning of the why that particular GUID is used. The new macros can +be used to make it clear when a valid GUID is being used or when the +value is just a dummy and has no meaning. + +Change-Id: I1cf041703cbc40e60072e0662baebaef3239d3d1 +Signed-off-by: Frazer Carsley +Upstream-Status: Submitted [https://review.trustedfirmware.org/c/TF-M/trusted-firmware-m/+/50233/1] +--- + lib/gpt/unittests/gpt/test_gpt.c | 55 ++++++++++++++++++-------------- + 1 file changed, 31 insertions(+), 24 deletions(-) + +diff --git a/lib/gpt/unittests/gpt/test_gpt.c b/lib/gpt/unittests/gpt/test_gpt.c +index bd161ec74..32dfb8fb2 100644 +--- a/lib/gpt/unittests/gpt/test_gpt.c ++++ b/lib/gpt/unittests/gpt/test_gpt.c +@@ -69,6 +69,13 @@ + backup.array_lba = TEST_GPT_BACKUP_ARRAY_LBA; \ + } while (0) + ++/* These macros make it clearer in the tests what is happening */ ++#define TEST_GPT_VALID_GUID(...) MAKE_EFI_GUID(__VA_ARGS__) ++#define TEST_GPT_DUMMY_GUID NULL_GUID ++ ++#define TEST_GPT_VALID_TYPE(...) MAKE_EFI_GUID(__VA_ARGS__) ++#define TEST_GPT_DUMMY_TYPE NULL_GUID ++ + /* MBR partition entry */ + struct mbr_entry_t { + /* Indicates if bootable */ +@@ -176,24 +183,24 @@ static struct gpt_header_t test_header; + /* Default entry array. This is valid, though fragmented. */ + static struct gpt_entry_t default_partition_array[TEST_DEFAULT_NUM_PARTITIONS] = { + { +- .type = MAKE_EFI_GUID(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), +- .guid = MAKE_EFI_GUID(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), ++ .type = TEST_GPT_VALID_TYPE(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), ++ .guid = TEST_GPT_VALID_GUID(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), + .start = TEST_GPT_FIRST_PARTITION_START, + .end = TEST_GPT_FIRST_PARTITION_END, + .attr = 0, + .name = "First partition" + }, + { +- .type = MAKE_EFI_GUID(2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), +- .guid = MAKE_EFI_GUID(2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), ++ .type = TEST_GPT_VALID_TYPE(2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), ++ .guid = TEST_GPT_VALID_GUID(2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), + .start = TEST_GPT_SECOND_PARTITION_START, + .end = TEST_GPT_SECOND_PARTITION_END, + .attr = 0, + .name = "Second partition" + }, + { +- .type = MAKE_EFI_GUID(3, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11), +- .guid = MAKE_EFI_GUID(3, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11), ++ .type = TEST_GPT_VALID_TYPE(3, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11), ++ .guid = TEST_GPT_VALID_GUID(3, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11), + .start = TEST_GPT_THIRD_PARTITION_START, + .end = TEST_GPT_THIRD_PARTITION_END, + .attr = 0, +@@ -333,7 +340,7 @@ void test_gpt_init_should_overwriteOldGpt(void) + gpt_uninit(); + + /* Use a different disk GUID */ +- const struct efi_guid_t new_guid = MAKE_EFI_GUID(1, 1, 3, 4, 5, 6 ,7 ,8, 9, 10, 11); ++ const struct efi_guid_t new_guid = TEST_GPT_VALID_GUID(1, 1, 3, 4, 5, 6 ,7 ,8, 9, 10, 11); + test_header.disk_guid = new_guid; + + setup_valid_gpt(); +@@ -744,7 +751,7 @@ void test_gpt_entry_create_should_createNewEntry(void) + + /* Update header. Read each entry for CRC calculation. */ + struct gpt_entry_t new_entry = { +- .type = NULL_GUID, ++ .type = TEST_GPT_DUMMY_TYPE, + .start = TEST_GPT_THIRD_PARTITION_END + 1, + .end = TEST_GPT_THIRD_PARTITION_END + 1, + .attr = 0, +@@ -752,12 +759,12 @@ void test_gpt_entry_create_should_createNewEntry(void) + }; + + /* Mock out the call to create a new GUID */ +- struct efi_guid_t expected_guid = MAKE_EFI_GUID(5, 5, 5, 5, 5, 6, 7, 8, 9, 10, 11); ++ struct efi_guid_t expected_guid = TEST_GPT_VALID_GUID(5, 5, 5, 5, 5, 6, 7, 8, 9, 10, 11); + efi_guid_generate_random_ExpectAnyArgsAndReturn(PSA_SUCCESS); + efi_guid_generate_random_ReturnThruPtr_guid(&expected_guid); + + /* Ensure also the that a new GUID is assigned */ +- struct efi_guid_t new_guid = MAKE_EFI_GUID(4, 4, 4, 4, 5, 6, 7, 8, 9, 10, 11); ++ struct efi_guid_t new_guid = TEST_GPT_DUMMY_GUID; + TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_entry_create( + &expected_guid, + new_entry.start, +@@ -782,7 +789,7 @@ void test_gpt_entry_create_should_createNewEntryNextToLastEntry(void) + + /* Update header. Read each entry for CRC calculation. */ + struct gpt_entry_t new_entry = { +- .type = NULL_GUID, ++ .type = TEST_GPT_DUMMY_TYPE, + .start = TEST_GPT_THIRD_PARTITION_END + 1, + .end = TEST_GPT_THIRD_PARTITION_END + 1, + .attr = 0, +@@ -790,12 +797,12 @@ void test_gpt_entry_create_should_createNewEntryNextToLastEntry(void) + }; + + /* Mock out the call to create a new GUID */ +- struct efi_guid_t expected_guid = MAKE_EFI_GUID(5, 5, 5, 5, 5, 6, 7, 8, 9, 10, 11); ++ struct efi_guid_t expected_guid = TEST_GPT_VALID_GUID(5, 5, 5, 5, 5, 6, 7, 8, 9, 10, 11); + efi_guid_generate_random_ExpectAnyArgsAndReturn(PSA_SUCCESS); + efi_guid_generate_random_ReturnThruPtr_guid(&expected_guid); + + /* Ensure also the that a new GUID is assigned */ +- struct efi_guid_t new_guid = MAKE_EFI_GUID(4, 4, 4, 4, 5, 6, 7, 8, 9, 10, 11); ++ struct efi_guid_t new_guid = TEST_GPT_DUMMY_GUID; + char name[GPT_ENTRY_NAME_LENGTH] = {'\0'}; + name[0] = 'a'; + +@@ -823,7 +830,7 @@ void test_gpt_entry_create_should_failToCreateEntryWhenLowestFreeLbaDoesNotHaveS + register_mocked_read(&test_partition_array, sizeof(test_partition_array)); + + /* Ensure also the that a new GUID is assigned */ +- struct efi_guid_t existing_guid = MAKE_EFI_GUID(4, 4, 4, 4, 5, 6, 7, 8, 9, 10, 11); ++ struct efi_guid_t existing_guid = TEST_GPT_VALID_GUID(4, 4, 4, 4, 5, 6, 7, 8, 9, 10, 11); + struct efi_guid_t new_guid; + char name[GPT_ENTRY_NAME_LENGTH] = {'\0'}; + name[0] = 'a'; +@@ -840,17 +847,17 @@ void test_gpt_entry_create_should_failWhenTableFull(void) + { + /* Start with a full array of entries */ + struct gpt_entry_t new_entry = { +- .type = MAKE_EFI_GUID(4, 4, 4, 4, 5, 6, 7, 8, 9, 10, 11), ++ .type = TEST_GPT_VALID_TYPE(4, 4, 4, 4, 5, 6, 7, 8, 9, 10, 11), + .start = TEST_GPT_THIRD_PARTITION_END + 1, + .end = TEST_GPT_THIRD_PARTITION_END + 1, + .attr = 0, +- .guid = MAKE_EFI_GUID(4, 4, 4, 4, 5, 6, 7, 8, 9, 10, 11), ++ .guid = TEST_GPT_VALID_GUID(4, 4, 4, 4, 5, 6, 7, 8, 9, 10, 11), + .name = "Fourth partition" + }; + test_partition_array[TEST_MAX_PARTITIONS - 1] = new_entry; + setup_valid_gpt(); + +- struct efi_guid_t type = MAKE_EFI_GUID(5, 5, 5, 5, 5, 6, 7, 8, 9, 10, 11); ++ struct efi_guid_t type = TEST_GPT_VALID_TYPE(5, 5, 5, 5, 5, 6, 7, 8, 9, 10, 11); + struct efi_guid_t guid; + char name[GPT_ENTRY_NAME_LENGTH] = {'\0'}; + name[0] = 'a'; +@@ -868,7 +875,7 @@ void test_gpt_entry_create_should_failWhenLbaOffDisk(void) + setup_valid_gpt(); + + /* First start on disk, then go off the disk */ +- struct efi_guid_t type = NULL_GUID; ++ struct efi_guid_t type = TEST_GPT_DUMMY_TYPE; + struct efi_guid_t guid; + char name[GPT_ENTRY_NAME_LENGTH] = {'\0'}; + name[0] = 'a'; +@@ -917,7 +924,7 @@ void test_gpt_entry_create_should_failWhenOverlapping(void) + * 2. start in the middle of a partition and end in free space + */ + register_mocked_read(&test_partition_array, sizeof(test_partition_array)); +- struct efi_guid_t type = NULL_GUID; ++ struct efi_guid_t type = TEST_GPT_DUMMY_TYPE; + struct efi_guid_t guid; + char name[GPT_ENTRY_NAME_LENGTH] = {'\0'}; + name[0] = 'a'; +@@ -944,7 +951,7 @@ void test_gpt_entry_create_should_failWhenNameIsEmpty(void) + /* Start with a populated GPT */ + setup_valid_gpt(); + +- struct efi_guid_t type = NULL_GUID; ++ struct efi_guid_t type = TEST_GPT_DUMMY_TYPE; + struct gpt_entry_t new_entry = { + .type = type, + .start = TEST_GPT_THIRD_PARTITION_END + 1, +@@ -969,7 +976,7 @@ void test_gpt_entry_create_should_failWhenSizeIsZero(void) + /* Start with a populated GPT */ + setup_valid_gpt(); + +- struct efi_guid_t type = NULL_GUID; ++ struct efi_guid_t type = TEST_GPT_DUMMY_TYPE; + + /* Make the size zero */ + struct efi_guid_t new_guid; +@@ -1199,7 +1206,7 @@ void test_gpt_entry_change_type_should_setNewType(void) + /* Type validation is not a function of the library, as this is OS + * dependent, so anything will do here. + */ +- struct efi_guid_t new_type = MAKE_EFI_GUID(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); ++ struct efi_guid_t new_type = TEST_GPT_VALID_TYPE(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_entry_change_type(&test_guid, &new_type)); + } + +@@ -1211,7 +1218,7 @@ void test_gpt_entry_change_type_should_failWhenEntryNotExisting(void) + register_mocked_read(&test_partition_array, sizeof(test_partition_array)); + + struct efi_guid_t non_existing = NULL_GUID; +- struct efi_guid_t new_type = MAKE_EFI_GUID(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); ++ struct efi_guid_t new_type = TEST_GPT_VALID_TYPE(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + TEST_ASSERT_EQUAL(PSA_ERROR_DOES_NOT_EXIST, gpt_entry_change_type(&non_existing, &new_type)); + } + +@@ -1452,7 +1459,7 @@ void test_gpt_entry_read_by_type_should_failWhenEntryNotExisting(void) + + /* Try to read something */ + struct partition_entry_t entry; +- struct efi_guid_t test_type = MAKE_EFI_GUID(11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1); ++ struct efi_guid_t test_type = TEST_GPT_VALID_TYPE(11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1); + TEST_ASSERT_EQUAL(PSA_ERROR_DOES_NOT_EXIST, gpt_entry_read_by_type(&test_type, 0, &entry)); + + /* Now, have a non-empty GPT but search for a type that won't exist */ diff --git a/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0052-lib-gpt-Provide-macro-identifying-free-space.patch b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0052-lib-gpt-Provide-macro-identifying-free-space.patch new file mode 100644 index 00000000..3deaf93a --- /dev/null +++ b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0052-lib-gpt-Provide-macro-identifying-free-space.patch @@ -0,0 +1,157 @@ +From 229313778bae6ca16d6e3b25437c8e87eddf3084 Mon Sep 17 00:00:00 2001 +From: Frazer Carsley +Date: Mon, 30 Mar 2026 14:35:45 +0100 +Subject: [PATCH] lib: gpt: Provide macro identifying free space + +In the unit tests, it is often required to know where free space on the +mocked disk is in order to determine where it is valid to create or move +a new partition. The macro makes it clearer when this is being done. + +Change-Id: I147faf516efa3e5c7fdd49775d0efa878890b771 +Signed-off-by: Frazer Carsley +Upstream-Status: Submitted [https://review.trustedfirmware.org/c/TF-M/trusted-firmware-m/+/50234/1] +--- + lib/gpt/unittests/gpt/test_gpt.c | 42 +++++++++++++++++--------------- + 1 file changed, 23 insertions(+), 19 deletions(-) + +diff --git a/lib/gpt/unittests/gpt/test_gpt.c b/lib/gpt/unittests/gpt/test_gpt.c +index 32dfb8fb2..0ae660336 100644 +--- a/lib/gpt/unittests/gpt/test_gpt.c ++++ b/lib/gpt/unittests/gpt/test_gpt.c +@@ -58,6 +58,7 @@ + #define TEST_GPT_SECOND_PARTITION_END (TEST_GPT_SECOND_PARTITION_START + 50) + #define TEST_GPT_THIRD_PARTITION_START (TEST_GPT_SECOND_PARTITION_END + 1) + #define TEST_GPT_THIRD_PARTITION_END (TEST_GPT_THIRD_PARTITION_START + 1) ++#define TEST_GPT_DISK_FREE_SPACE_START (TEST_GPT_THIRD_PARTITION_END + 1) + + /* Populates a backup header from a primary header and calculates the new CRC32 */ + #define MAKE_BACKUP_HEADER(backup, primary) \ +@@ -752,8 +753,8 @@ void test_gpt_entry_create_should_createNewEntry(void) + /* Update header. Read each entry for CRC calculation. */ + struct gpt_entry_t new_entry = { + .type = TEST_GPT_DUMMY_TYPE, +- .start = TEST_GPT_THIRD_PARTITION_END + 1, +- .end = TEST_GPT_THIRD_PARTITION_END + 1, ++ .start = TEST_GPT_DISK_FREE_SPACE_START, ++ .end = TEST_GPT_DISK_FREE_SPACE_START, + .attr = 0, + .name = "Fourth partition" + }; +@@ -790,8 +791,8 @@ void test_gpt_entry_create_should_createNewEntryNextToLastEntry(void) + /* Update header. Read each entry for CRC calculation. */ + struct gpt_entry_t new_entry = { + .type = TEST_GPT_DUMMY_TYPE, +- .start = TEST_GPT_THIRD_PARTITION_END + 1, +- .end = TEST_GPT_THIRD_PARTITION_END + 1, ++ .start = TEST_GPT_DISK_FREE_SPACE_START, ++ .end = TEST_GPT_DISK_FREE_SPACE_START, + .attr = 0, + .name = "Fourth partition" + }; +@@ -846,10 +847,11 @@ void test_gpt_entry_create_should_failToCreateEntryWhenLowestFreeLbaDoesNotHaveS + void test_gpt_entry_create_should_failWhenTableFull(void) + { + /* Start with a full array of entries */ ++ const uint64_t new_entry_end = TEST_GPT_DISK_FREE_SPACE_START; + struct gpt_entry_t new_entry = { + .type = TEST_GPT_VALID_TYPE(4, 4, 4, 4, 5, 6, 7, 8, 9, 10, 11), +- .start = TEST_GPT_THIRD_PARTITION_END + 1, +- .end = TEST_GPT_THIRD_PARTITION_END + 1, ++ .start = new_entry_end, ++ .end = new_entry_end, + .attr = 0, + .guid = TEST_GPT_VALID_GUID(4, 4, 4, 4, 5, 6, 7, 8, 9, 10, 11), + .name = "Fourth partition" +@@ -861,9 +863,11 @@ void test_gpt_entry_create_should_failWhenTableFull(void) + struct efi_guid_t guid; + char name[GPT_ENTRY_NAME_LENGTH] = {'\0'}; + name[0] = 'a'; ++ const uint64_t new_free_space = new_entry_end + 1; ++ + TEST_ASSERT_EQUAL(PSA_ERROR_INSUFFICIENT_STORAGE, gpt_entry_create( + &type, +- TEST_GPT_THIRD_PARTITION_END + 4, ++ new_free_space, + 1, + 0, + name, +@@ -881,7 +885,7 @@ void test_gpt_entry_create_should_failWhenLbaOffDisk(void) + name[0] = 'a'; + TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_create( + &type, +- TEST_GPT_THIRD_PARTITION_END + 1, ++ TEST_GPT_DISK_FREE_SPACE_START, + 1000, + 0, + name, +@@ -954,8 +958,8 @@ void test_gpt_entry_create_should_failWhenNameIsEmpty(void) + struct efi_guid_t type = TEST_GPT_DUMMY_TYPE; + struct gpt_entry_t new_entry = { + .type = type, +- .start = TEST_GPT_THIRD_PARTITION_END + 1, +- .end = TEST_GPT_THIRD_PARTITION_END + 1, ++ .start = TEST_GPT_DISK_FREE_SPACE_START, ++ .end = TEST_GPT_DISK_FREE_SPACE_START, + .attr = 0, + }; + +@@ -984,7 +988,7 @@ void test_gpt_entry_create_should_failWhenSizeIsZero(void) + name[0] = 'a'; + TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_create( + &type, +- TEST_GPT_THIRD_PARTITION_END + 1, ++ TEST_GPT_DISK_FREE_SPACE_START, + 0, + 0, + name, +@@ -1016,8 +1020,8 @@ void test_gpt_entry_move_should_moveEntry(void) + /* Do a valid move and resize in one */ + TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_entry_move( + &test_guid, +- TEST_GPT_THIRD_PARTITION_END + 1, +- TEST_GPT_THIRD_PARTITION_END + 1)); ++ TEST_GPT_DISK_FREE_SPACE_START, ++ TEST_GPT_DISK_FREE_SPACE_START)); + } + + void test_gpt_entry_move_should_failWhenEntryNotExisting(void) +@@ -1030,8 +1034,8 @@ void test_gpt_entry_move_should_failWhenEntryNotExisting(void) + struct efi_guid_t non_existing = NULL_GUID; + TEST_ASSERT_EQUAL(PSA_ERROR_DOES_NOT_EXIST, gpt_entry_move( + &non_existing, +- TEST_GPT_THIRD_PARTITION_END + 1, +- TEST_GPT_THIRD_PARTITION_END + 1)); ++ TEST_GPT_DISK_FREE_SPACE_START, ++ TEST_GPT_DISK_FREE_SPACE_START)); + } + + void test_gpt_entry_move_should_failWhenEndLessThanStart(void) +@@ -1041,8 +1045,8 @@ void test_gpt_entry_move_should_failWhenEndLessThanStart(void) + struct efi_guid_t test_guid = test_partition_array[0].guid; + TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_move( + &test_guid, +- TEST_GPT_THIRD_PARTITION_END + 2, +- TEST_GPT_THIRD_PARTITION_END + 1)); ++ TEST_GPT_DISK_FREE_SPACE_START + 1, ++ TEST_GPT_DISK_FREE_SPACE_START)); + } + + void test_gpt_entry_move_should_failWhenLbaOverlapping(void) +@@ -1097,7 +1101,7 @@ void test_gpt_entry_move_should_failWhenLbaOffDisk(void) + /* First start on disk, then go off the disk */ + TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_move( + &test_guid, +- TEST_GPT_THIRD_PARTITION_END + 1, ++ TEST_GPT_DISK_FREE_SPACE_START, + TEST_DISK_NUM_BLOCKS + 1)); + + /* Second, start off the disk entirely */ +@@ -1110,7 +1114,7 @@ void test_gpt_entry_move_should_failWhenLbaOffDisk(void) + TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_move( + &test_guid, + TEST_GPT_PRIMARY_LBA, +- TEST_GPT_THIRD_PARTITION_END + 2)); ++ TEST_GPT_DISK_FREE_SPACE_START + 1)); + + /* Fourth, start in the backup header area */ + TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_move( diff --git a/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0053-lib-gpt-Add-operation-to-duplicate-entries.patch b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0053-lib-gpt-Add-operation-to-duplicate-entries.patch new file mode 100644 index 00000000..09fcb39a --- /dev/null +++ b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0053-lib-gpt-Add-operation-to-duplicate-entries.patch @@ -0,0 +1,255 @@ +From 38daa61f876a6becb3968f1360d403f496634131 Mon Sep 17 00:00:00 2001 +From: Frazer Carsley +Date: Mon, 16 Mar 2026 16:46:43 +0000 +Subject: [PATCH] lib: gpt: Add operation to duplicate entries + +Without this new function, callers of the library would have to first +read the entry they want to duplicate, then attempt to create a new +entry, then manually, with their driver, copy the data of the partition +across. This new function streamlines the latter half of that process +into a single API call which creates, with the same size size, name and +attributes, and copies the data across block by block. + +Change-Id: Ia30700cf6463f0e07e76d4b56ec015b51770459d +Signed-off-by: Frazer Carsley +Upstream-Status: Submitted [https://review.trustedfirmware.org/c/TF-M/trusted-firmware-m/+/50235/1] +--- + lib/gpt/inc/gpt.h | 19 ++++ + lib/gpt/src/gpt.c | 23 +++++ + lib/gpt/unittests/gpt/test_gpt.c | 159 +++++++++++++++++++++++++++++++ + 3 files changed, 201 insertions(+) + +diff --git a/lib/gpt/inc/gpt.h b/lib/gpt/inc/gpt.h +index 34ce67580..334a08f41 100644 +--- a/lib/gpt/inc/gpt.h ++++ b/lib/gpt/inc/gpt.h +@@ -170,6 +170,25 @@ psa_status_t gpt_entry_move(const struct efi_guid_t *guid, + const uint64_t start, + const uint64_t end); + ++/** ++ * \brief Duplicates an existing partition entry into new space. ++ * ++ * \param[in] old_guid Entry to duplicate. ++ * \param[in] start Starting LBA (0 uses the lowest free LBA possible). ++ * \param[out] new_guid GUID populated on success for subsequent API calls. ++ * ++ * \retval PSA_SUCCESS Success. ++ * \retval PSA_ERROR_STORAGE_FAILURE I/O error. ++ * \retval PSA_ERROR_DOES_NOT_EXIST No entry found with the provided GUID. ++ * \retval PSA_ERROR_INSUFFICIENT_STORAGE Maximum number of partitions reached. ++ * \retval PSA_ERROR_INVALID_ARGUMENT New entry would overlap with an existing partition. ++ * \retval PSA_ERROR_INVALID_ARGUMENT Part of the partition would be off flash. ++ */ ++__attribute__((nonnull(1,3))) ++psa_status_t gpt_entry_duplicate(const struct efi_guid_t *old_guid, ++ const uint64_t start, ++ struct efi_guid_t *new_guid); ++ + /** + * \brief Creates a partition entry in the table. + * +diff --git a/lib/gpt/src/gpt.c b/lib/gpt/src/gpt.c +index 0335befa7..920c4ccca 100644 +--- a/lib/gpt/src/gpt.c ++++ b/lib/gpt/src/gpt.c +@@ -516,6 +516,29 @@ psa_status_t gpt_entry_move(const struct efi_guid_t *guid, + return write_entry(cached_index, &cached_entry, false); + } + ++psa_status_t gpt_entry_duplicate(const struct efi_guid_t *old_guid, ++ const uint64_t start, ++ struct efi_guid_t *new_guid) ++{ ++ struct partition_entry_t old_entry; ++ psa_status_t ret = gpt_entry_read(old_guid, &old_entry); ++ if (ret != PSA_SUCCESS) { ++ return ret; ++ } ++ ++ ret = gpt_entry_create(&(old_entry.type_guid), ++ start, ++ old_entry.size, ++ old_entry.attr, ++ old_entry.name, ++ new_guid); ++ if (ret != PSA_SUCCESS) { ++ return ret; ++ } ++ ++ return move_partition(old_entry.start, start, old_entry.size); ++} ++ + psa_status_t gpt_entry_create(const struct efi_guid_t *type, + const uint64_t start, + const uint64_t size, +diff --git a/lib/gpt/unittests/gpt/test_gpt.c b/lib/gpt/unittests/gpt/test_gpt.c +index 0ae660336..5d2c4243f 100644 +--- a/lib/gpt/unittests/gpt/test_gpt.c ++++ b/lib/gpt/unittests/gpt/test_gpt.c +@@ -738,6 +738,165 @@ void test_gpt_defragment_should_succeedWhenNoIOFailure(void) + TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_defragment()); + } + ++void test_gpt_entry_duplicate_should_DuplicateOldEntry(void) ++{ ++ /* Duplicate an entry. It must not overlap with an existing entry and must also ++ * fit on the storage device. The GUID should be populated with something. ++ */ ++ setup_valid_gpt(); ++ struct gpt_entry_t *old_entry = &(test_partition_array[0]); ++ struct efi_guid_t old_guid = old_entry->guid; ++ ++ /* Each entry will be read to find the entry to be duplicated. */ ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ ++ /* The partition data is moved: this means reading each block then writing. ++ * It doesn't matter what the data is ++ */ ++ char unused_read_data = 'X'; ++ register_mocked_read(&unused_read_data, sizeof(unused_read_data)); ++ ++ /* Mock out the call to create a new GUID */ ++ struct efi_guid_t expected_guid = TEST_GPT_VALID_GUID(5, 5, 5, 5, 5, 6, 7, 8, 9, 10, 11); ++ efi_guid_generate_random_ExpectAnyArgsAndReturn(PSA_SUCCESS); ++ efi_guid_generate_random_ReturnThruPtr_guid(&expected_guid); ++ ++ /* Ensure that a new GUID is assigned. To test the duplication was successful ++ * would require reading from flash, which would be mocked anyway and therefore ++ * pointless ++ */ ++ struct efi_guid_t new_guid = TEST_GPT_DUMMY_GUID; ++ TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_entry_duplicate( ++ &old_guid, ++ TEST_GPT_DISK_FREE_SPACE_START, ++ &new_guid)); ++ TEST_ASSERT_EQUAL_MEMORY(&expected_guid, &new_guid, sizeof(new_guid)); ++} ++ ++void test_gpt_entry_duplicate_should_createNewEntryNextToLastEntry(void) ++{ ++ /* Duplicate an entry, allowing the library to choose the start LBA. The ++ * GUID should be populated with something ++ */ ++ setup_valid_gpt(); ++ struct gpt_entry_t *old_entry = &(test_partition_array[0]); ++ struct efi_guid_t old_guid = old_entry->guid; ++ ++ /* Each entry will be read to find the entry to be duplicated. */ ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ ++ /* The partition data is moved: this means reading each block then writing. ++ * It doesn't matter what the data is ++ */ ++ char unused_read_data = 'X'; ++ register_mocked_read(&unused_read_data, sizeof(unused_read_data)); ++ ++ /* Mock out the call to create a new GUID */ ++ struct efi_guid_t expected_guid = TEST_GPT_VALID_GUID(5, 5, 5, 5, 5, 6, 7, 8, 9, 10, 11); ++ efi_guid_generate_random_ExpectAnyArgsAndReturn(PSA_SUCCESS); ++ efi_guid_generate_random_ReturnThruPtr_guid(&expected_guid); ++ ++ /* Ensure that a new GUID is assigned. To test the duplication was successful ++ * would require reading from flash, which would be mocked anyway and therefore ++ * pointless ++ */ ++ struct efi_guid_t new_guid = TEST_GPT_DUMMY_GUID; ++ TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_entry_duplicate( ++ &old_guid, ++ 0, ++ &new_guid)); ++ TEST_ASSERT_EQUAL_MEMORY(&expected_guid, &new_guid, sizeof(new_guid)); ++} ++ ++void test_gpt_entry_duplicate_should_failToCreateEntryWhenLowestFreeLbaDoesNotHaveSpace(void) ++{ ++ /* Duplicate an entry, allowing the library to choose the start LBA. Resize ++ * the last partition to consume over half of the disk, such that duplicating ++ * it won't be possible. ++ */ ++ struct gpt_entry_t *old_entry = &(test_partition_array[TEST_DEFAULT_NUM_PARTITIONS - 1]); ++ old_entry->end = TEST_GPT_THIRD_PARTITION_START + (TEST_DISK_NUM_BLOCKS / 2 ) + 1; ++ struct efi_guid_t old_guid = old_entry->guid; ++ setup_valid_gpt(); ++ ++ /* Each entry will be read to find the entry to be duplicated. */ ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ ++ struct efi_guid_t new_guid; ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_duplicate( ++ &old_guid, ++ 0, ++ &new_guid)); ++} ++ ++void test_gpt_entry_duplicate_should_failWhenEntryNotExisting(void) ++{ ++ setup_valid_gpt(); ++ struct efi_guid_t old_guid = NULL_GUID; ++ ++ /* Each entry will be read to find the entry to be duplicated. */ ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ ++ struct efi_guid_t new_guid; ++ TEST_ASSERT_EQUAL(PSA_ERROR_DOES_NOT_EXIST, gpt_entry_duplicate( ++ &old_guid, ++ TEST_GPT_DISK_FREE_SPACE_START, ++ &new_guid)); ++} ++ ++void test_gpt_entry_duplicate_should_failNewEntryOverlapping(void) ++{ ++ setup_valid_gpt(); ++ struct gpt_entry_t *old_entry = &(test_partition_array[0]); ++ struct efi_guid_t old_guid = old_entry->guid; ++ struct efi_guid_t new_guid; ++ ++ /* Each entry will be read to find the entry to be duplicated. */ ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ ++ /* Since the disk is not fragmented by default, there are two test cases: ++ * 1. start in the middle of a partition and end in the middle of a partition ++ * 2. start in the middle of a partition and end in free space ++ */ ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_duplicate( ++ &old_guid, ++ TEST_GPT_FIRST_PARTITION_START + 1, ++ &new_guid)); ++ ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_duplicate( ++ &old_guid, ++ TEST_GPT_THIRD_PARTITION_START + 1, ++ &new_guid)); ++} ++ ++void test_gpt_entry_duplicate_should_failWhenTableFull(void) ++{ ++ /* Start with a full array of entries */ ++ const uint64_t new_entry_end = TEST_GPT_DISK_FREE_SPACE_START; ++ struct gpt_entry_t new_entry = { ++ .type = TEST_GPT_VALID_TYPE(4, 4, 4, 4, 5, 6, 7, 8, 9, 10, 11), ++ .start = new_entry_end, ++ .end = new_entry_end, ++ .attr = 0, ++ .guid = TEST_GPT_VALID_GUID(4, 4, 4, 4, 5, 6, 7, 8, 9, 10, 11), ++ .name = "Fourth partition" ++ }; ++ test_partition_array[TEST_MAX_PARTITIONS - 1] = new_entry; ++ setup_valid_gpt(); ++ ++ struct gpt_entry_t *old_entry = &(test_partition_array[0]); ++ struct efi_guid_t old_guid = old_entry->guid; ++ struct efi_guid_t new_guid; ++ ++ /* Each entry will be read to find the entry to be duplicated. */ ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ ++ TEST_ASSERT_EQUAL(PSA_ERROR_INSUFFICIENT_STORAGE, gpt_entry_duplicate( ++ &old_guid, ++ new_entry_end + 1, ++ &new_guid)); ++} ++ + void test_gpt_entry_create_should_createNewEntry(void) + { + /* Add an entry. It must not overlap with an existing entry and must also diff --git a/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0054-lib-gpt-Consecutively-erase-blocks-when-moving-parti.patch b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0054-lib-gpt-Consecutively-erase-blocks-when-moving-parti.patch new file mode 100644 index 00000000..53eabd6e --- /dev/null +++ b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0054-lib-gpt-Consecutively-erase-blocks-when-moving-parti.patch @@ -0,0 +1,240 @@ +From 0dcbef3a0800a2a610b32935a54762d4b42203f1 Mon Sep 17 00:00:00 2001 +From: Frazer Carsley +Date: Tue, 17 Mar 2026 11:44:14 +0000 +Subject: [PATCH] lib: gpt: Consecutively erase blocks when moving partitions + +An LBA is typically smaller than a flash sector size, so it becomes +inefficient to erase block by block and also erases the same sector +multiple times. Consecutively erasing blocks allows the platform +implementation of the driver the opportunity to save on erase cycles. + +As a result, the write_to_flash function has had an extra parameter +added to indicate whether an erase is required or not. Under normal +circumstances, it should be false, however there are some reasons where +it is not required, such as if it has been erased before already. + +Change-Id: I80c6d73565ab9bfcb0b286aaa215798dd09725f2 +Signed-off-by: Frazer Carsley +Upstream-Status: Submitted [https://review.trustedfirmware.org/c/TF-M/trusted-firmware-m/+/50236/1] +--- + lib/gpt/src/gpt.c | 102 ++++++++++++++++++++++++++++++++++------------ + 1 file changed, 75 insertions(+), 27 deletions(-) + +diff --git a/lib/gpt/src/gpt.c b/lib/gpt/src/gpt.c +index 920c4ccca..984c8f821 100644 +--- a/lib/gpt/src/gpt.c ++++ b/lib/gpt/src/gpt.c +@@ -207,7 +207,7 @@ static psa_status_t read_entry_from_flash(const struct gpt_t *table, + struct gpt_entry_t *entry); + static psa_status_t read_table_from_flash(struct gpt_t *table, bool is_primary); + static psa_status_t flush_lba_buf(void); +-static psa_status_t write_to_flash(uint64_t lba); ++static psa_status_t write_to_flash(uint64_t lba, bool skip_erase); + static psa_status_t write_entries_to_flash(uint32_t lbas_into_array, bool no_header_update); + static psa_status_t write_entry(uint32_t array_index, + const struct gpt_entry_t *entry, +@@ -221,7 +221,7 @@ static psa_status_t find_gpt_entry(const struct gpt_t *table, + const uint32_t repeat_index, + struct gpt_entry_t *entry, + uint32_t *array_index); +-static psa_status_t move_lba(const uint64_t old_lba, const uint64_t new_lba); ++static psa_status_t move_lba(const uint64_t old_lba, const uint64_t new_lba, const bool skip_erase); + static psa_status_t move_partition(const uint64_t old_lba, + const uint64_t new_lba, + const uint64_t num_blocks); +@@ -722,12 +722,12 @@ psa_status_t gpt_entry_remove(const struct efi_guid_t *guid) + + /* Write to backup first, then primary partition array */ + if (backup_gpt_array_lba != 0) { +- ret = write_to_flash(backup_gpt_array_lba + i - 1 - PRIMARY_GPT_ARRAY_LBA); ++ ret = write_to_flash(backup_gpt_array_lba + i - 1 - PRIMARY_GPT_ARRAY_LBA, false); + if (ret != PSA_SUCCESS) { + return ret; + } + } +- ret = write_to_flash(i - 1); ++ ret = write_to_flash(i - 1, false); + if (ret != PSA_SUCCESS) { + return ret; + } +@@ -750,16 +750,16 @@ psa_status_t gpt_entry_remove(const struct efi_guid_t *guid) + */ + memset(lba_buf, 0, TFM_GPT_BLOCK_SIZE); + if (backup_gpt_array_lba != 0) { +- int write_ret = plat_flash_driver->write( ++ ret = write_to_flash( + backup_gpt_array_lba + array_end_lba - PRIMARY_GPT_ARRAY_LBA, +- lba_buf); +- if (write_ret != TFM_GPT_BLOCK_SIZE) { +- return PSA_ERROR_STORAGE_FAILURE; ++ true); ++ if (ret != PSA_SUCCESS) { ++ return ret; + } + } +- int write_ret = plat_flash_driver->write(array_end_lba, lba_buf); +- if (write_ret != TFM_GPT_BLOCK_SIZE) { +- return PSA_ERROR_STORAGE_FAILURE; ++ ret = write_to_flash(array_end_lba, true); ++ if (ret != PSA_SUCCESS) { ++ return ret; + } + } else { + /* Zero what is not needed anymore */ +@@ -768,12 +768,12 @@ psa_status_t gpt_entry_remove(const struct efi_guid_t *guid) + 0, + (gpt_entry_per_lba_count() - entries_in_last_lba) * GPT_ENTRY_SIZE); + if (backup_gpt_array_lba != 0) { +- ret = write_to_flash(backup_gpt_array_lba + array_end_lba - PRIMARY_GPT_ARRAY_LBA); ++ ret = write_to_flash(array_end_lba, false); + if (ret != PSA_SUCCESS) { + return ret; + } + } +- ret = write_to_flash(array_end_lba); ++ ret = write_to_flash(array_end_lba, false); + if (ret != PSA_SUCCESS) { + return ret; + } +@@ -1121,14 +1121,18 @@ static psa_status_t find_gpt_entry(const struct gpt_t *table, + } + + /* Move a single LBAs data to somewhere else */ +-static psa_status_t move_lba(const uint64_t old_lba, const uint64_t new_lba) ++static psa_status_t move_lba(const uint64_t old_lba, const uint64_t new_lba, const bool skip_erase) + { ++ VERBOSE("Moving from 0x%x%x to 0x%x%x %s erase\n", ++ (uint32_t)(old_lba >> 32), (uint32_t)old_lba, ++ (uint32_t)(new_lba >> 32), (uint32_t)new_lba, ++ skip_erase ? "without" : "with"); + const psa_status_t ret = read_from_flash(old_lba); + if (ret != PSA_SUCCESS) { + return ret; + } + +- return write_to_flash(new_lba); ++ return write_to_flash(new_lba, skip_erase); + } + + /* Moves a partition's data to start from one logical block to another */ +@@ -1140,18 +1144,60 @@ static psa_status_t move_partition(const uint64_t old_lba, + return PSA_SUCCESS; + } + ++ /* If possible, erase all of the LBAs that the data is going to be read ++ * to, so that, in the case an LBA is smaller than a flash sector, the ++ * number of flash erase cycles is reduced. Ignore any errors when erasing, ++ * as the "write" will perform erase anyway. If the areas between where the ++ * partition is now and where it will be does not overlap, then erase all ++ * blocks in the new area. If there is overlap, erase only those which are ++ * not within the old area ++ */ + if (old_lba < new_lba) { ++ /* Attempt consecutive erase */ ++ uint64_t non_overlap_blocks = ++ (old_lba + num_blocks - 1 < new_lba ? num_blocks : new_lba - old_lba); ++ ++ VERBOSE("Erasing 0x%x%x blocks from LBA 0x%x%x\n", ++ (uint32_t)(non_overlap_blocks >> 32), (uint32_t)non_overlap_blocks, ++ (uint32_t)(new_lba >> 32), (uint32_t)new_lba); ++ ++ const ssize_t erase_ret = plat_flash_driver->erase( ++ new_lba + (num_blocks - non_overlap_blocks), ++ (size_t)non_overlap_blocks); ++ if (erase_ret != (ssize_t)non_overlap_blocks) { ++ WARN("Failed to erase all blocks consecutively, only erased %ld. " ++ "Continuing to erase on a per-block basis\n", erase_ret); ++ non_overlap_blocks = 0; ++ } ++ + /* Move block by block backwards */ + for (uint64_t block = num_blocks; block > 0; --block) { +- const psa_status_t ret = move_lba(old_lba + block - 1, new_lba + block - 1); ++ const bool skip_erase = (block < non_overlap_blocks); ++ const psa_status_t ret = move_lba(old_lba + block - 1, new_lba + block - 1, skip_erase); + if (ret != PSA_SUCCESS) { + return ret; + } + } + } else { ++ /* Attempt consecutive erase */ ++ uint64_t non_overlap_blocks = ++ (new_lba + num_blocks - 1 < old_lba ? num_blocks : old_lba - new_lba); ++ ++ VERBOSE("Erasing 0x%x%x blocks from LBA 0x%x%x\n", ++ (uint32_t)(non_overlap_blocks >> 32), (uint32_t)non_overlap_blocks, ++ (uint32_t)(new_lba >> 32), (uint32_t)new_lba); ++ ++ const ssize_t erase_ret = plat_flash_driver->erase(new_lba, (size_t)non_overlap_blocks); ++ if (erase_ret != (ssize_t)non_overlap_blocks) { ++ WARN("Failed to erase all blocks consecutively, only erased %ld. " ++ "Continuing to erase on a per-block basis\n", erase_ret); ++ non_overlap_blocks = 0; ++ } ++ + /* Move block by block forwards */ + for (uint64_t block = 0; block < num_blocks; ++block) { +- const psa_status_t ret = move_lba(old_lba + block, new_lba + block); ++ const bool skip_erase = (block < non_overlap_blocks); ++ const psa_status_t ret = move_lba(old_lba + block, new_lba + block, skip_erase); + if (ret != PSA_SUCCESS) { + return ret; + } +@@ -1372,7 +1418,7 @@ static psa_status_t flush_lba_buf(void) + ret = write_entries_to_flash(cached_lba - backup_gpt_array_lba, false); + } else { + /* Some other LBA is cached, possibly data. Write it anyway */ +- ret = write_to_flash(cached_lba); ++ ret = write_to_flash(cached_lba, false); + } + + in_flush = false; +@@ -1380,13 +1426,15 @@ static psa_status_t flush_lba_buf(void) + } + + /* Write to the flash at the specified LBA */ +-static psa_status_t write_to_flash(uint64_t lba) ++static psa_status_t write_to_flash(uint64_t lba, bool skip_erase) + { +- if (plat_flash_driver->erase(lba, 1) != 1) { +- ERROR("Unable to erase flash at LBA 0x%08x%08x\n", +- (uint32_t)(lba >> 32), +- (uint32_t)lba); +- return PSA_ERROR_STORAGE_FAILURE; ++ if (!skip_erase) { ++ if (plat_flash_driver->erase(lba, 1) != 1) { ++ ERROR("Unable to erase flash at LBA 0x%08x%08x\n", ++ (uint32_t)(lba >> 32), ++ (uint32_t)lba); ++ return PSA_ERROR_STORAGE_FAILURE; ++ } + } + + if (plat_flash_driver->write(lba, lba_buf) != TFM_GPT_BLOCK_SIZE) { +@@ -1408,7 +1456,7 @@ static psa_status_t write_entries_to_flash(uint32_t lbas_into_array, bool no_hea + psa_status_t ret; + + if (backup_gpt_array_lba != 0) { +- ret = write_to_flash(backup_gpt_array_lba + lbas_into_array); ++ ret = write_to_flash(backup_gpt_array_lba + lbas_into_array, false); + if (ret != PSA_SUCCESS) { + ERROR("Unable to write entry to backup partition array\n"); + return ret; +@@ -1417,7 +1465,7 @@ static psa_status_t write_entries_to_flash(uint32_t lbas_into_array, bool no_hea + WARN("Backup array LBA unknown!\n"); + } + +- ret = write_to_flash(PRIMARY_GPT_ARRAY_LBA + lbas_into_array); ++ ret = write_to_flash(PRIMARY_GPT_ARRAY_LBA + lbas_into_array, false); + if (ret != PSA_SUCCESS) { + ERROR("Unable to write entry to primary partition array\n"); + return ret; +@@ -1486,7 +1534,7 @@ static psa_status_t write_header_to_flash(const struct gpt_t *table) + uint8_t temp_buf[GPT_HEADER_SIZE]; + memcpy(temp_buf, lba_buf, GPT_HEADER_SIZE); + memcpy(lba_buf, &(table->header), GPT_HEADER_SIZE); +- const psa_status_t ret = write_to_flash(table->header.current_lba); ++ const psa_status_t ret = write_to_flash(table->header.current_lba, false); + memcpy(lba_buf, temp_buf, GPT_HEADER_SIZE); + + return ret; diff --git a/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0055-lib-gpt-Clarify-API-operation.patch b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0055-lib-gpt-Clarify-API-operation.patch new file mode 100644 index 00000000..29986c43 --- /dev/null +++ b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0055-lib-gpt-Clarify-API-operation.patch @@ -0,0 +1,38 @@ +From 7e2ae2fc4f8ae8a16a24b87d0650c6b4b28fc870 Mon Sep 17 00:00:00 2001 +From: Frazer Carsley +Date: Wed, 8 Apr 2026 17:39:13 +0100 +Subject: [PATCH] lib: gpt: Clarify API operation + +The move and duplicate operations both also move or copy (respectively) +the partition data, which is not immediately obvious. + +Change-Id: If4a0c4a87d30bfceb2534c9d01ba763688e0cc9e +Signed-off-by: Frazer Carsley +Upstream-Status: Submitted [https://review.trustedfirmware.org/c/TF-M/trusted-firmware-m/+/50237/1] +--- + lib/gpt/inc/gpt.h | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/lib/gpt/inc/gpt.h b/lib/gpt/inc/gpt.h +index 334a08f41..c11ecbff2 100644 +--- a/lib/gpt/inc/gpt.h ++++ b/lib/gpt/inc/gpt.h +@@ -152,7 +152,7 @@ __attribute__((nonnull(1))) + psa_status_t gpt_attr_set(const struct efi_guid_t *guid, const uint64_t attr); + + /** +- * \brief Moves (or resizes) a partition entry. ++ * \brief Moves (or resizes) a partition entry, including the partition data. + * + * \param[in] guid Entry to move. + * \param[in] start New start LBA. +@@ -171,7 +171,8 @@ psa_status_t gpt_entry_move(const struct efi_guid_t *guid, + const uint64_t end); + + /** +- * \brief Duplicates an existing partition entry into new space. ++ * \brief Duplicates an existing partition entry into new space, including the ++ * partition data. + * + * \param[in] old_guid Entry to duplicate. + * \param[in] start Starting LBA (0 uses the lowest free LBA possible). diff --git a/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0056-lib-gpt-Add-metadata-only-API-operations.patch b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0056-lib-gpt-Add-metadata-only-API-operations.patch new file mode 100644 index 00000000..e3320caf --- /dev/null +++ b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0056-lib-gpt-Add-metadata-only-API-operations.patch @@ -0,0 +1,701 @@ +From f9badce3570bbc89b141c86a6b8a988d90d81f0c Mon Sep 17 00:00:00 2001 +From: Frazer Carsley +Date: Wed, 8 Apr 2026 17:51:18 +0100 +Subject: [PATCH] lib: gpt: Add metadata-only API operations + +Both move and duplicate functions also move or copy (respectively) the +partition data. This is not always required, for example if the +new location or partition will simply be erased or overwritten anyway. + +Change-Id: I180f8790335444e9925c405616aa5a3c9a4290a8 +Signed-off-by: Frazer Carsley +Upstream-Status: Submitted [https://review.trustedfirmware.org/c/TF-M/trusted-firmware-m/+/50238/1] +--- + lib/gpt/inc/gpt.h | 39 +++++ + lib/gpt/src/gpt.c | 285 ++++++++++++++++++------------- + lib/gpt/unittests/gpt/test_gpt.c | 269 +++++++++++++++++++++++++++++ + 3 files changed, 473 insertions(+), 120 deletions(-) + +diff --git a/lib/gpt/inc/gpt.h b/lib/gpt/inc/gpt.h +index c11ecbff2..c5bddb470 100644 +--- a/lib/gpt/inc/gpt.h ++++ b/lib/gpt/inc/gpt.h +@@ -170,6 +170,25 @@ psa_status_t gpt_entry_move(const struct efi_guid_t *guid, + const uint64_t start, + const uint64_t end); + ++/** ++ * \brief Moves (or resizes) a partition entry, without moving the partition data. ++ * ++ * \param[in] guid Entry to move. ++ * \param[in] start New start LBA. ++ * \param[in] end New end LBA. ++ * ++ * \retval PSA_SUCCESS Success. ++ * \retval PSA_ERROR_STORAGE_FAILURE I/O error. ++ * \retval PSA_ERROR_DOES_NOT_EXIST No entry found with the provided GUID. ++ * \retval PSA_ERROR_INVALID_ARGUMENT Move would overlap with an existing partition. ++ * \retval PSA_ERROR_INVALID_ARGUMENT \p end is less than \p start. ++ * \retval PSA_ERROR_INVALID_ARGUMENT Part of the partition would move off flash. ++ */ ++__attribute__((nonnull(1))) ++psa_status_t gpt_entry_move_no_copy(const struct efi_guid_t *guid, ++ const uint64_t start, ++ const uint64_t end); ++ + /** + * \brief Duplicates an existing partition entry into new space, including the + * partition data. +@@ -190,6 +209,26 @@ psa_status_t gpt_entry_duplicate(const struct efi_guid_t *old_guid, + const uint64_t start, + struct efi_guid_t *new_guid); + ++/** ++ * \brief Duplicates an existing partition entry into new space, without copying ++ * the partition data. ++ * ++ * \param[in] old_guid Entry to duplicate. ++ * \param[in] start Starting LBA (0 uses the lowest free LBA possible). ++ * \param[out] new_guid GUID populated on success for subsequent API calls. ++ * ++ * \retval PSA_SUCCESS Success. ++ * \retval PSA_ERROR_STORAGE_FAILURE I/O error. ++ * \retval PSA_ERROR_DOES_NOT_EXIST No entry found with the provided GUID. ++ * \retval PSA_ERROR_INSUFFICIENT_STORAGE Maximum number of partitions reached. ++ * \retval PSA_ERROR_INVALID_ARGUMENT New entry would overlap with an existing partition. ++ * \retval PSA_ERROR_INVALID_ARGUMENT Part of the partition would be off flash. ++ */ ++__attribute__((nonnull(1,3))) ++psa_status_t gpt_entry_duplicate_no_copy(const struct efi_guid_t *old_guid, ++ const uint64_t start, ++ struct efi_guid_t *new_guid); ++ + /** + * \brief Creates a partition entry in the table. + * +diff --git a/lib/gpt/src/gpt.c b/lib/gpt/src/gpt.c +index 984c8f821..d6528f6a5 100644 +--- a/lib/gpt/src/gpt.c ++++ b/lib/gpt/src/gpt.c +@@ -222,9 +222,17 @@ static psa_status_t find_gpt_entry(const struct gpt_t *table, + struct gpt_entry_t *entry, + uint32_t *array_index); + static psa_status_t move_lba(const uint64_t old_lba, const uint64_t new_lba, const bool skip_erase); +-static psa_status_t move_partition(const uint64_t old_lba, +- const uint64_t new_lba, +- const uint64_t num_blocks); ++static psa_status_t move_partition_data(const uint64_t old_lba, ++ const uint64_t new_lba, ++ const uint64_t num_blocks); ++static psa_status_t move_partition(const struct efi_guid_t *guid, ++ const uint64_t start, ++ const uint64_t end, ++ const bool no_copy); ++static psa_status_t duplicate_partition(const struct efi_guid_t *old_guid, ++ const uint64_t start, ++ const bool no_copy, ++ struct efi_guid_t *new_guid); + static psa_status_t mbr_load(struct mbr_t *mbr); + static bool gpt_entry_cmp_guid(const struct gpt_entry_t *entry, const void *guid); + static bool gpt_entry_cmp_name(const struct gpt_entry_t *entry, const void *name); +@@ -415,128 +423,28 @@ psa_status_t gpt_entry_move(const struct efi_guid_t *guid, + const uint64_t start, + const uint64_t end) + { +- if (end < start) { +- return PSA_ERROR_INVALID_ARGUMENT; +- } +- +- /* Must fit on flash */ +- if (start < primary_gpt.header.first_lba || +- end < primary_gpt.header.first_lba || +- start > primary_gpt.header.last_lba || +- end > primary_gpt.header.last_lba) +- { +- ERROR("Requested move would not be on disk\n"); +- return PSA_ERROR_INVALID_ARGUMENT; +- } +- +- struct gpt_entry_t cached_entry; +- uint32_t cached_index; +- psa_status_t ret = find_gpt_entry( +- &primary_gpt, +- gpt_entry_cmp_guid, +- guid, +- 0, +- &cached_entry, +- &cached_index); +- if (ret != PSA_SUCCESS) { +- return ret; +- } +- +- /* Prevent unecessary I/O */ +- if (start == cached_entry.start && end == cached_entry.end) { +- return PSA_SUCCESS; +- } +- +- /* It is not possible to move a partition such that it overlaps with an +- * existing partition (other than itself). Check the currently cached LBA +- * first, then the others to avoid reading this LBA twice +- */ +- struct gpt_entry_t entry; +- const uint64_t checked_lba = cached_lba; +- const uint64_t array_end_lba = partition_array_last_lba(&primary_gpt); +- uint32_t num_entries_in_cached_lba; +- if (cached_lba == array_end_lba) { +- /* If this is 0, then the last LBA is full */ +- uint32_t num_entries_in_last_lba = primary_gpt.num_used_partitions % gpt_entry_per_lba_count(); +- if (num_entries_in_last_lba == 0) { +- num_entries_in_cached_lba = gpt_entry_per_lba_count(); +- } else { +- num_entries_in_cached_lba = num_entries_in_last_lba; +- } +- } else { +- num_entries_in_cached_lba = gpt_entry_per_lba_count(); +- } +- +- /* Cached LBA */ +- for (uint32_t i = 0; i < num_entries_in_cached_lba; ++i) { +- memcpy(&entry, lba_buf + (i * GPT_ENTRY_SIZE), GPT_ENTRY_SIZE); +- +- const struct efi_guid_t ent_guid = entry.unique_guid; +- if (efi_guid_cmp(&ent_guid, guid) == 0) { +- continue; +- } +- +- if ((start >= entry.start && start <= entry.end) || +- (end >= entry.start && end <= entry.end) || +- (start <= entry.start && end >= entry.end)) +- { +- return PSA_ERROR_INVALID_ARGUMENT; +- } +- } +- +- /* All the rest */ +- for (uint32_t i = 0; i < primary_gpt.num_used_partitions; ++i) { +- if (partition_entry_lba(&primary_gpt, i) == checked_lba) { +- continue; +- } +- +- ret = read_entry_from_flash(&primary_gpt, i, &entry); +- if (ret != PSA_SUCCESS) { +- return ret; +- } +- +- if ((start >= entry.start && start <= entry.end) || +- (end >= entry.start && end <= entry.end) || +- (start <= entry.start && end >= entry.end)) +- { +- return PSA_ERROR_INVALID_ARGUMENT; +- } +- } +- +- ret = move_partition( +- cached_entry.start, +- start, +- end - start + 1); +- if (ret != PSA_SUCCESS) { +- return ret; +- } +- cached_entry.start = start; +- cached_entry.end = end; ++ return move_partition(guid, start, end, false); ++} + +- return write_entry(cached_index, &cached_entry, false); ++psa_status_t gpt_entry_move_no_copy(const struct efi_guid_t *guid, ++ const uint64_t start, ++ const uint64_t end) ++{ ++ return move_partition(guid, start, end, true); + } + + psa_status_t gpt_entry_duplicate(const struct efi_guid_t *old_guid, + const uint64_t start, + struct efi_guid_t *new_guid) + { +- struct partition_entry_t old_entry; +- psa_status_t ret = gpt_entry_read(old_guid, &old_entry); +- if (ret != PSA_SUCCESS) { +- return ret; +- } +- +- ret = gpt_entry_create(&(old_entry.type_guid), +- start, +- old_entry.size, +- old_entry.attr, +- old_entry.name, +- new_guid); +- if (ret != PSA_SUCCESS) { +- return ret; +- } ++ return duplicate_partition(old_guid, start, false, new_guid); ++} + +- return move_partition(old_entry.start, start, old_entry.size); ++psa_status_t gpt_entry_duplicate_no_copy(const struct efi_guid_t *old_guid, ++ const uint64_t start, ++ struct efi_guid_t *new_guid) ++{ ++ return duplicate_partition(old_guid, start, true, new_guid); + } + + psa_status_t gpt_entry_create(const struct efi_guid_t *type, +@@ -885,7 +793,7 @@ psa_status_t gpt_defragment(void) + } + + const uint64_t num_blocks = entry.end - entry.start + 1; +- ret = move_partition(entry.start, prev_end, num_blocks); ++ ret = move_partition_data(entry.start, prev_end, num_blocks); + if (ret != PSA_SUCCESS) { + return ret; + } +@@ -1120,6 +1028,143 @@ static psa_status_t find_gpt_entry(const struct gpt_t *table, + return io_failure ? PSA_ERROR_STORAGE_FAILURE : PSA_ERROR_DOES_NOT_EXIST; + } + ++/* Duplicate a partition, potentially also copying its data but always updating ++ * the header ++ */ ++static psa_status_t duplicate_partition(const struct efi_guid_t *old_guid, ++ const uint64_t start, ++ const bool no_copy, ++ struct efi_guid_t *new_guid) ++{ ++ struct partition_entry_t old_entry; ++ psa_status_t ret = gpt_entry_read(old_guid, &old_entry); ++ if (ret != PSA_SUCCESS) { ++ return ret; ++ } ++ ++ ret = gpt_entry_create(&(old_entry.type_guid), ++ start, ++ old_entry.size, ++ old_entry.attr, ++ old_entry.name, ++ new_guid); ++ if (ret != PSA_SUCCESS || no_copy) { ++ return ret; ++ } ++ ++ return move_partition_data(old_entry.start, start, old_entry.size); ++} ++ ++/* Move a partition, potentially also copying its data but always updating the header */ ++static psa_status_t move_partition(const struct efi_guid_t *guid, ++ const uint64_t start, ++ const uint64_t end, ++ const bool no_copy) ++{ ++ if (end < start) { ++ return PSA_ERROR_INVALID_ARGUMENT; ++ } ++ ++ /* Must fit on flash */ ++ if (start < primary_gpt.header.first_lba || ++ end < primary_gpt.header.first_lba || ++ start > primary_gpt.header.last_lba || ++ end > primary_gpt.header.last_lba) ++ { ++ ERROR("Requested move would not be on disk\n"); ++ return PSA_ERROR_INVALID_ARGUMENT; ++ } ++ ++ struct gpt_entry_t cached_entry; ++ uint32_t cached_index; ++ psa_status_t ret = find_gpt_entry( ++ &primary_gpt, ++ gpt_entry_cmp_guid, ++ guid, ++ 0, ++ &cached_entry, ++ &cached_index); ++ if (ret != PSA_SUCCESS) { ++ return ret; ++ } ++ ++ /* Prevent unecessary I/O */ ++ if (start == cached_entry.start && end == cached_entry.end) { ++ return PSA_SUCCESS; ++ } ++ ++ /* It is not possible to move a partition such that it overlaps with an ++ * existing partition (other than itself). Check the currently cached LBA ++ * first, then the others to avoid reading this LBA twice ++ */ ++ struct gpt_entry_t entry; ++ const uint64_t checked_lba = cached_lba; ++ const uint64_t array_end_lba = partition_array_last_lba(&primary_gpt); ++ uint32_t num_entries_in_cached_lba; ++ if (cached_lba == array_end_lba) { ++ /* If this is 0, then the last LBA is full */ ++ uint32_t num_entries_in_last_lba = primary_gpt.num_used_partitions % gpt_entry_per_lba_count(); ++ if (num_entries_in_last_lba == 0) { ++ num_entries_in_cached_lba = gpt_entry_per_lba_count(); ++ } else { ++ num_entries_in_cached_lba = num_entries_in_last_lba; ++ } ++ } else { ++ num_entries_in_cached_lba = gpt_entry_per_lba_count(); ++ } ++ ++ /* Cached LBA */ ++ for (uint32_t i = 0; i < num_entries_in_cached_lba; ++i) { ++ memcpy(&entry, lba_buf + (i * GPT_ENTRY_SIZE), GPT_ENTRY_SIZE); ++ ++ const struct efi_guid_t ent_guid = entry.unique_guid; ++ if (efi_guid_cmp(&ent_guid, guid) == 0) { ++ continue; ++ } ++ ++ if ((start >= entry.start && start <= entry.end) || ++ (end >= entry.start && end <= entry.end) || ++ (start <= entry.start && end >= entry.end)) ++ { ++ return PSA_ERROR_INVALID_ARGUMENT; ++ } ++ } ++ ++ /* All the rest */ ++ for (uint32_t i = 0; i < primary_gpt.num_used_partitions; ++i) { ++ if (partition_entry_lba(&primary_gpt, i) == checked_lba) { ++ continue; ++ } ++ ++ ret = read_entry_from_flash(&primary_gpt, i, &entry); ++ if (ret != PSA_SUCCESS) { ++ return ret; ++ } ++ ++ if ((start >= entry.start && start <= entry.end) || ++ (end >= entry.start && end <= entry.end) || ++ (start <= entry.start && end >= entry.end)) ++ { ++ return PSA_ERROR_INVALID_ARGUMENT; ++ } ++ } ++ ++ if (!no_copy) { ++ ret = move_partition_data( ++ cached_entry.start, ++ start, ++ end - start + 1); ++ if (ret != PSA_SUCCESS) { ++ return ret; ++ } ++ } ++ ++ cached_entry.start = start; ++ cached_entry.end = end; ++ ++ return write_entry(cached_index, &cached_entry, false); ++} ++ + /* Move a single LBAs data to somewhere else */ + static psa_status_t move_lba(const uint64_t old_lba, const uint64_t new_lba, const bool skip_erase) + { +@@ -1136,7 +1181,7 @@ static psa_status_t move_lba(const uint64_t old_lba, const uint64_t new_lba, con + } + + /* Moves a partition's data to start from one logical block to another */ +-static psa_status_t move_partition(const uint64_t old_lba, ++static psa_status_t move_partition_data(const uint64_t old_lba, + const uint64_t new_lba, + const uint64_t num_blocks) + { +@@ -1780,7 +1825,7 @@ static psa_status_t restore_table(struct gpt_t *restore_from, bool is_primary) + swap_headers(&(restore_from->header), &(restore_to.header)); + + /* Copy the partition array as well */ +- ret = move_partition( ++ ret = move_partition_data( + restore_from->header.array_lba, + restore_to.header.array_lba, + (restore_from->header.num_partitions + +diff --git a/lib/gpt/unittests/gpt/test_gpt.c b/lib/gpt/unittests/gpt/test_gpt.c +index 5d2c4243f..bd9eb8ba1 100644 +--- a/lib/gpt/unittests/gpt/test_gpt.c ++++ b/lib/gpt/unittests/gpt/test_gpt.c +@@ -897,6 +897,153 @@ void test_gpt_entry_duplicate_should_failWhenTableFull(void) + &new_guid)); + } + ++void test_gpt_entry_duplicate_no_copy_should_DuplicateOldEntry(void) ++{ ++ /* Duplicate an entry. It must not overlap with an existing entry and must also ++ * fit on the storage device. The GUID should be populated with something. ++ */ ++ setup_valid_gpt(); ++ struct gpt_entry_t *old_entry = &(test_partition_array[0]); ++ struct efi_guid_t old_guid = old_entry->guid; ++ ++ /* Each entry will be read to find the entry to be duplicated. */ ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ ++ /* Mock out the call to create a new GUID */ ++ struct efi_guid_t expected_guid = TEST_GPT_VALID_GUID(5, 5, 5, 5, 5, 6, 7, 8, 9, 10, 11); ++ efi_guid_generate_random_ExpectAnyArgsAndReturn(PSA_SUCCESS); ++ efi_guid_generate_random_ReturnThruPtr_guid(&expected_guid); ++ ++ /* Ensure that a new GUID is assigned. To test the duplication was successful ++ * would require reading from flash, which would be mocked anyway and therefore ++ * pointless ++ */ ++ struct efi_guid_t new_guid = TEST_GPT_DUMMY_GUID; ++ TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_entry_duplicate_no_copy( ++ &old_guid, ++ TEST_GPT_DISK_FREE_SPACE_START, ++ &new_guid)); ++ TEST_ASSERT_EQUAL_MEMORY(&expected_guid, &new_guid, sizeof(new_guid)); ++} ++ ++void test_gpt_entry_duplicate_no_copy_should_createNewEntryNextToLastEntry(void) ++{ ++ /* Duplicate an entry, allowing the library to choose the start LBA. The ++ * GUID should be populated with something ++ */ ++ setup_valid_gpt(); ++ struct gpt_entry_t *old_entry = &(test_partition_array[0]); ++ struct efi_guid_t old_guid = old_entry->guid; ++ ++ /* Each entry will be read to find the entry to be duplicated. */ ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ ++ /* Mock out the call to create a new GUID */ ++ struct efi_guid_t expected_guid = TEST_GPT_VALID_GUID(5, 5, 5, 5, 5, 6, 7, 8, 9, 10, 11); ++ efi_guid_generate_random_ExpectAnyArgsAndReturn(PSA_SUCCESS); ++ efi_guid_generate_random_ReturnThruPtr_guid(&expected_guid); ++ ++ /* Ensure that a new GUID is assigned. To test the duplication was successful ++ * would require reading from flash, which would be mocked anyway and therefore ++ * pointless ++ */ ++ struct efi_guid_t new_guid = TEST_GPT_DUMMY_GUID; ++ TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_entry_duplicate_no_copy( ++ &old_guid, ++ 0, ++ &new_guid)); ++ TEST_ASSERT_EQUAL_MEMORY(&expected_guid, &new_guid, sizeof(new_guid)); ++} ++ ++void test_gpt_entry_duplicate_no_copy_should_failToCreateEntryWhenLowestFreeLbaDoesNotHaveSpace(void) ++{ ++ /* Duplicate an entry, allowing the library to choose the start LBA. Resize ++ * the last partition to consume over half of the disk, such that duplicating ++ * it won't be possible. ++ */ ++ struct gpt_entry_t *old_entry = &(test_partition_array[TEST_DEFAULT_NUM_PARTITIONS - 1]); ++ old_entry->end = TEST_GPT_THIRD_PARTITION_START + (TEST_DISK_NUM_BLOCKS / 2 ) + 1; ++ struct efi_guid_t old_guid = old_entry->guid; ++ setup_valid_gpt(); ++ ++ /* Each entry will be read to find the entry to be duplicated. */ ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ ++ struct efi_guid_t new_guid; ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_duplicate_no_copy( ++ &old_guid, ++ 0, ++ &new_guid)); ++} ++ ++void test_gpt_entry_duplicate_no_copy_should_failWhenEntryNotExisting(void) ++{ ++ setup_valid_gpt(); ++ struct efi_guid_t old_guid = NULL_GUID; ++ ++ /* Each entry will be read to find the entry to be duplicated. */ ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ ++ struct efi_guid_t new_guid; ++ TEST_ASSERT_EQUAL(PSA_ERROR_DOES_NOT_EXIST, gpt_entry_duplicate_no_copy( ++ &old_guid, ++ TEST_GPT_DISK_FREE_SPACE_START, ++ &new_guid)); ++} ++ ++void test_gpt_entry_duplicate_no_copy_should_failNewEntryOverlapping(void) ++{ ++ setup_valid_gpt(); ++ struct gpt_entry_t *old_entry = &(test_partition_array[0]); ++ struct efi_guid_t old_guid = old_entry->guid; ++ struct efi_guid_t new_guid; ++ ++ /* Each entry will be read to find the entry to be duplicated. */ ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ ++ /* Since the disk is not fragmented by default, there are two test cases: ++ * 1. start in the middle of a partition and end in the middle of a partition ++ * 2. start in the middle of a partition and end in free space ++ */ ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_duplicate_no_copy( ++ &old_guid, ++ TEST_GPT_FIRST_PARTITION_START + 1, ++ &new_guid)); ++ ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_duplicate_no_copy( ++ &old_guid, ++ TEST_GPT_THIRD_PARTITION_START + 1, ++ &new_guid)); ++} ++ ++void test_gpt_entry_duplicate_no_copy_should_failWhenTableFull(void) ++{ ++ /* Start with a full array of entries */ ++ const uint64_t new_entry_end = TEST_GPT_DISK_FREE_SPACE_START; ++ struct gpt_entry_t new_entry = { ++ .type = TEST_GPT_VALID_TYPE(4, 4, 4, 4, 5, 6, 7, 8, 9, 10, 11), ++ .start = new_entry_end, ++ .end = new_entry_end, ++ .attr = 0, ++ .guid = TEST_GPT_VALID_GUID(4, 4, 4, 4, 5, 6, 7, 8, 9, 10, 11), ++ .name = "Fourth partition" ++ }; ++ test_partition_array[TEST_MAX_PARTITIONS - 1] = new_entry; ++ setup_valid_gpt(); ++ ++ struct gpt_entry_t *old_entry = &(test_partition_array[0]); ++ struct efi_guid_t old_guid = old_entry->guid; ++ struct efi_guid_t new_guid; ++ ++ /* Each entry will be read to find the entry to be duplicated. */ ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ ++ TEST_ASSERT_EQUAL(PSA_ERROR_INSUFFICIENT_STORAGE, gpt_entry_duplicate_no_copy( ++ &old_guid, ++ new_entry_end + 1, ++ &new_guid)); ++} ++ + void test_gpt_entry_create_should_createNewEntry(void) + { + /* Add an entry. It must not overlap with an existing entry and must also +@@ -1282,6 +1429,128 @@ void test_gpt_entry_move_should_failWhenLbaOffDisk(void) + TEST_GPT_BACKUP_LBA + 1)); + } + ++void test_gpt_entry_move_no_copy_should_moveEntry(void) ++{ ++ /* Start with a populated GPT */ ++ setup_valid_gpt(); ++ struct gpt_entry_t *test_entry = &(test_partition_array[TEST_DEFAULT_NUM_PARTITIONS - 1]); ++ struct efi_guid_t test_guid = test_entry->guid; ++ ++ /* First all entries are read to determine for overlap */ ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ ++ /* Header update - reads partition array to calculate crc32 and also then ++ * reads the header to modify and write back ++ */ ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ register_mocked_read(&test_header, sizeof(test_header)); ++ ++ /* Do a valid move and resize in one */ ++ TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_entry_move_no_copy( ++ &test_guid, ++ TEST_GPT_DISK_FREE_SPACE_START, ++ TEST_GPT_DISK_FREE_SPACE_START)); ++} ++ ++void test_gpt_entry_move_no_copy_should_failWhenEntryNotExisting(void) ++{ ++ setup_valid_gpt(); ++ ++ /* Read every entry */ ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ ++ struct efi_guid_t non_existing = NULL_GUID; ++ TEST_ASSERT_EQUAL(PSA_ERROR_DOES_NOT_EXIST, gpt_entry_move_no_copy( ++ &non_existing, ++ TEST_GPT_DISK_FREE_SPACE_START, ++ TEST_GPT_DISK_FREE_SPACE_START)); ++} ++ ++void test_gpt_entry_move_no_copy_should_failWhenEndLessThanStart(void) ++{ ++ setup_valid_gpt(); ++ ++ struct efi_guid_t test_guid = test_partition_array[0].guid; ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_move_no_copy( ++ &test_guid, ++ TEST_GPT_DISK_FREE_SPACE_START + 1, ++ TEST_GPT_DISK_FREE_SPACE_START)); ++} ++ ++void test_gpt_entry_move_no_copy_should_failWhenLbaOverlapping(void) ++{ ++ setup_valid_gpt(); ++ ++ /* Try to move an entry. Each entry is read to determine for overlap */ ++ size_t test_index = 1; ++ struct gpt_entry_t *test_entry = &(test_partition_array[test_index]); ++ struct efi_guid_t test_guid = test_entry->guid; ++ register_mocked_read(&test_partition_array, sizeof(test_partition_array)); ++ ++ /* Try to move the test entry into the middle of the entry just read. ++ * Starting at the same LBA ++ */ ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_move_no_copy( ++ &test_guid, ++ TEST_GPT_FIRST_PARTITION_START, ++ TEST_GPT_SECOND_PARTITION_END)); ++ ++ /* Try to move the test entry into the middle of the entry just read. ++ * Starting in the middle ++ */ ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_move_no_copy( ++ &test_guid, ++ TEST_GPT_FIRST_PARTITION_START + 1, ++ TEST_GPT_SECOND_PARTITION_END)); ++ ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_move_no_copy( ++ &test_guid, ++ TEST_GPT_SECOND_PARTITION_START, ++ TEST_GPT_THIRD_PARTITION_START)); ++ ++ /* Try to move the test entry into the middle of the entry just read. ++ * Starting and ending in the middle. ++ */ ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_move_no_copy( ++ &test_guid, ++ TEST_GPT_FIRST_PARTITION_START + 1, ++ TEST_GPT_FIRST_PARTITION_START + 1)); ++} ++ ++void test_gpt_entry_move_no_copy_should_failWhenLbaOffDisk(void) ++{ ++ setup_valid_gpt(); ++ ++ /* Try to move an entry. */ ++ size_t test_index = 1; ++ struct gpt_entry_t *test_entry = &(test_partition_array[test_index]); ++ struct efi_guid_t test_guid = test_entry->guid; ++ ++ /* First start on disk, then go off the disk */ ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_move_no_copy( ++ &test_guid, ++ TEST_GPT_DISK_FREE_SPACE_START, ++ TEST_DISK_NUM_BLOCKS + 1)); ++ ++ /* Second, start off the disk entirely */ ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_move_no_copy( ++ &test_guid, ++ TEST_DISK_NUM_BLOCKS + 1, ++ TEST_DISK_NUM_BLOCKS + 2)); ++ ++ /* Third, do the same but in the header area */ ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_move_no_copy( ++ &test_guid, ++ TEST_GPT_PRIMARY_LBA, ++ TEST_GPT_DISK_FREE_SPACE_START + 1)); ++ ++ /* Fourth, start in the backup header area */ ++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_move_no_copy( ++ &test_guid, ++ TEST_GPT_BACKUP_LBA, ++ TEST_GPT_BACKUP_LBA + 1)); ++} ++ + void test_gpt_attr_set_should_setAttributes(void) + { + /* Start with a populated GPT */ diff --git a/meta-arm-bsp/recipes-bsp/trusted-firmware-m/trusted-firmware-m-corstone1000.inc b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/trusted-firmware-m-corstone1000.inc index 2bfea84b..4a8ec99b 100644 --- a/meta-arm-bsp/recipes-bsp/trusted-firmware-m/trusted-firmware-m-corstone1000.inc +++ b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/trusted-firmware-m-corstone1000.inc @@ -82,6 +82,12 @@ SRC_URI:append:corstone1000 = " \ file://0048-lib-gpt-Enforce-entry-size-of-128-bytes.patch \ file://0049-lib-gpt-Ensure-block-size-complies-with-spec.patch \ file://0050-lib-gpt-Expand-table-validation.patch \ + file://0051-lib-gpt-Show-intent-of-GUIDs-in-unittests-more-clear.patch \ + file://0052-lib-gpt-Provide-macro-identifying-free-space.patch \ + file://0053-lib-gpt-Add-operation-to-duplicate-entries.patch \ + file://0054-lib-gpt-Consecutively-erase-blocks-when-moving-parti.patch \ + file://0055-lib-gpt-Clarify-API-operation.patch \ + file://0056-lib-gpt-Add-metadata-only-API-operations.patch \ " SRCREV_tfm-psa-adac:corstone1000 = "f2809ae231be33a1afcd7714f40756c67d846c88" From patchwork Fri Apr 24 15:36:56 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Frazer Carsley X-Patchwork-Id: 86849 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 296E4FED3E9 for ; Fri, 24 Apr 2026 15:38:20 +0000 (UTC) Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.25734.1777045096772931335 for ; Fri, 24 Apr 2026 08:38:16 -0700 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: body hash did not verify" header.i=@arm.com header.s=foss header.b=F1jSHulL; spf=pass (domain: arm.com, ip: 217.140.110.172, mailfrom: frazer.carsley@arm.com) Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id BD58C358D; Fri, 24 Apr 2026 08:38:10 -0700 (PDT) Received: from e138143.arm.com (unknown [10.57.18.238]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id 939A73F7B4; Fri, 24 Apr 2026 08:38:15 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1777045096; bh=6fnzVeUgBkxHvUripBXif+wg/0GrDBpvCONjpjH1otE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=F1jSHulLJ20+mILV5mKPShaqQOVrJmIgqLiDXis6RwIJgb2B1VYk4YwBbwd40ha5o hVFTC9NajPutmmlNekxPGJpX8z1cAkQMRekwSRx90XFLR9kwk0JoLLpVZUXotkYms9 8sPPLcx6geC2D3aTqVXhFIOB7Fle6rSrnNNw5Ii0= From: Frazer Carsley To: meta-arm@lists.yoctoproject.org Cc: Frazer Carsley Subject: [PATCH 3/3] arm-bsp/trusted-firmware-m:cs1k: Use new GPT duplicate functionality Date: Fri, 24 Apr 2026 16:36:56 +0100 Message-ID: <20260424153656.774555-4-frazer.carsley@arm.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260424153656.774555-1-frazer.carsley@arm.com> References: <20260424153656.774555-1-frazer.carsley@arm.com> MIME-Version: 1.0 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Fri, 24 Apr 2026 15:38:20 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/meta-arm/message/7026 Uses the new GPT duplicate operation during a firmware update. This also adds flash erase protections so that the operation is not too slow and erasing flash multiple times redundantly. Signed-off-by: Frazer Carsley --- ...plat-cs1k-Add-flash-erase-protection.patch | 98 ++++++++ ...-unused-FWU-partitions-upon-version-.patch | 153 ++++++++++++ ...lat-cs1k-Duplicate-old-images-in-FWU.patch | 217 ++++++++++++++++++ .../trusted-firmware-m-corstone1000.inc | 3 + 4 files changed, 471 insertions(+) create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0057-plat-cs1k-Add-flash-erase-protection.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0058-plat-cs1k-Remove-unused-FWU-partitions-upon-version-.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0059-plat-cs1k-Duplicate-old-images-in-FWU.patch diff --git a/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0057-plat-cs1k-Add-flash-erase-protection.patch b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0057-plat-cs1k-Add-flash-erase-protection.patch new file mode 100644 index 00000000..3bca1d90 --- /dev/null +++ b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0057-plat-cs1k-Add-flash-erase-protection.patch @@ -0,0 +1,98 @@ +From 60277832aa6d4a205a5b1180f0513de0eb7c84c6 Mon Sep 17 00:00:00 2001 +From: Frazer Carsley +Date: Tue, 17 Mar 2026 11:05:45 +0000 +Subject: [PATCH] plat: cs1k: Add flash erase protection + +The GPT library deals in blocks, whereas flash deals in sectors. On +cs1k, eight blocks make up a sector. So, when the GPT library requests a +block erase from the platform driver, it actually erases the entire +sector, and then rewrites the data that was there for the blocks that +weren't being erased. + +This means that if all eight blocks in a sector were erased in a row, +then the same sector would be erased eight times, which is both slow and +also redundant, wearing out the flash device without need. These +protections prevent flash erasure if the data is already equal to the +erased value. + +Change-Id: Id8efe515647f45ac8e65cc95ef1bf58f9160aca2 +Signed-off-by: Frazer Carsley +Upstream-Status: Submitted [https://review.trustedfirmware.org/c/TF-M/trusted-firmware-m/+/50259/1] +--- + .../ext/target/arm/corstone1000/io/io_gpt.c | 42 +++++++++++++++++-- + 1 file changed, 39 insertions(+), 3 deletions(-) + +diff --git a/platform/ext/target/arm/corstone1000/io/io_gpt.c b/platform/ext/target/arm/corstone1000/io/io_gpt.c +index 513c77016..f7c3d79d2 100644 +--- a/platform/ext/target/arm/corstone1000/io/io_gpt.c ++++ b/platform/ext/target/arm/corstone1000/io/io_gpt.c +@@ -53,6 +53,32 @@ static uint8_t sector_buf[FLASH_SECTOR_SIZE]; + /* From io_gpt.h - the driver given to the GPT library */ + struct gpt_flash_driver_t io_gpt_flash_driver = {0}; + ++/* Read the bytes that need to be erased. If they are already erased_value, ++ * report that an erase is not required. This is to reduce the number of flash ++ * erase cyles. If the read fails in any way, report erase required. ++ */ ++static bool erase_required(uint32_t erase_addr, ++ size_t num_bytes) ++{ ++ if (num_bytes > FLASH_SECTOR_SIZE) { ++ return true; ++ } ++ ++ int32_t ret = flash_driver->ReadData(erase_addr, sector_buf, num_bytes); ++ if (ret < 0 || (uint32_t)ret != num_bytes) { ++ return true; ++ } ++ ++ uint8_t erased_value = flash_driver->GetInfo()->erased_value; ++ for (size_t offset = 0; offset < num_bytes; ++offset) { ++ if (sector_buf[offset] != erased_value) { ++ return true; ++ } ++ } ++ ++ return false; ++} ++ + /* Erases TFM_GPT_BLOCK_SIZE bytes from offset within the sector beginning at + * sector_addr. This is done via a read-erase-write pattern whereby data is read, + * the sector is erased, and data written back to the parts of the sector that +@@ -61,6 +87,10 @@ struct gpt_flash_driver_t io_gpt_flash_driver = {0}; + static gpt_flash_err_t partially_erase_sector(uint32_t sector_addr, + uint32_t offset) + { ++ if (!erase_required(sector_addr + offset, TFM_GPT_BLOCK_SIZE)) { ++ return GPT_FLASH_SUCCESS; ++ } ++ + if (flash_driver->ReadData( + sector_addr, + sector_buf, +@@ -139,6 +169,10 @@ static ssize_t flash_erase(uint64_t lba, size_t num_blocks) + + /* Whole sector erases until last sector */ + for (size_t i = 0; i < num_sectors - 1; ++i) { ++ if (!erase_required(erase_addr + i * FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE)) { ++ continue; ++ } ++ + int32_t ret = flash_driver->EraseSector(erase_addr + i * FLASH_SECTOR_SIZE); + if (ret != ARM_DRIVER_OK) { + return i; +@@ -146,9 +180,11 @@ static ssize_t flash_erase(uint64_t lba, size_t num_blocks) + } + + if (num_blocks % LBAS_PER_SECTOR == 0 && lba % LBAS_PER_SECTOR == 0) { +- /* Fully erase final sector */ +- if (flash_driver->EraseSector(last_erase_addr) != ARM_DRIVER_OK) { +- return (num_sectors - 1) * LBAS_PER_SECTOR; ++ /* Fully erase final sector if required */ ++ if (erase_required(last_erase_addr, FLASH_SECTOR_SIZE)) { ++ if (flash_driver->EraseSector(last_erase_addr) != ARM_DRIVER_OK) { ++ return (num_sectors - 1) * LBAS_PER_SECTOR; ++ } + } + } else { + /* Partial erase of final sector */ diff --git a/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0058-plat-cs1k-Remove-unused-FWU-partitions-upon-version-.patch b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0058-plat-cs1k-Remove-unused-FWU-partitions-upon-version-.patch new file mode 100644 index 00000000..69719044 --- /dev/null +++ b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0058-plat-cs1k-Remove-unused-FWU-partitions-upon-version-.patch @@ -0,0 +1,153 @@ +From 281f6799d6de19e63cbcf175ad848b8c8f2cc220 Mon Sep 17 00:00:00 2001 +From: Frazer Carsley +Date: Fri, 10 Apr 2026 17:15:36 +0100 +Subject: [PATCH] plat: cs1k: Remove unused FWU partitions upon version + rejection + +If a firmware update (FWU) is attempted and the version of any image is +lower or equal to the current version, the entire capsule is rejected +and the previous bank used to continue booting. The partitions created +during staging for the to-be images therefore can be removed and the +space free'd up. + +Change-Id: I9b74c2ed5efee938c14dbdd0380d8d094e71c10e +Signed-off-by: Frazer Carsley +Upstream-Status: Submitted [https://review.trustedfirmware.org/c/TF-M/trusted-firmware-m/+/50260/1] +--- + .../bootloader/mcuboot/tfm_mcuboot_fwu.c | 108 ++++++++++++------ + 1 file changed, 75 insertions(+), 33 deletions(-) + +diff --git a/platform/ext/target/arm/corstone1000/bootloader/mcuboot/tfm_mcuboot_fwu.c b/platform/ext/target/arm/corstone1000/bootloader/mcuboot/tfm_mcuboot_fwu.c +index d85590c71..557e48d07 100644 +--- a/platform/ext/target/arm/corstone1000/bootloader/mcuboot/tfm_mcuboot_fwu.c ++++ b/platform/ext/target/arm/corstone1000/bootloader/mcuboot/tfm_mcuboot_fwu.c +@@ -1105,6 +1105,54 @@ static psa_status_t erase_image(uint32_t image_offset, uint32_t image_size) + return PSA_SUCCESS; + } + ++#ifndef BL1_BUILD ++/* stale index is the index of the partition to remove within the partition entry ++ * array, which could be representing either bank 0 or bank 1. name_index is the ++ * index of partition to remove within the fwu_images image_names index, which is ++ * fixed at compile time in the structure ++ */ ++static psa_status_t remove_all_stale_partitions(const uint32_t stale_index, ++ const uint32_t name_index) ++{ ++ psa_status_t ret; ++ ++ for (int i = 0; i < NR_OF_IMAGES_IN_FW_BANK; ++i) { ++ struct partition_entry_t part; ++ ret = gpt_entry_read_by_type(&(fwu_image[i].image_type), stale_index, &part); ++ ++ if (ret == PSA_ERROR_DOES_NOT_EXIST) { ++ FWU_LOG_MSG("%s: Unable to find partition '%s', skipping removal\r\n", ++ __func__, fwu_image[i].image_names[name_index]); ++ continue; ++ } else if (ret == PSA_ERROR_STORAGE_FAILURE) { ++ FWU_LOG_MSG("%s: Flash error whilst reading GPT partition '%s'\r\n", ++ __func__, fwu_image[i].image_names[name_index]); ++ return ret; ++ } else if (ret < 0) { ++ FWU_LOG_MSG("%s: Unable to read partition '%s'\r\n", ++ __func__, fwu_image[i].image_names[name_index]); ++ return ret; ++ } ++ ++ ret = gpt_entry_remove(&(part.partition_guid)); ++ if (ret == PSA_ERROR_STORAGE_FAILURE) { ++ FWU_LOG_MSG("%s: Flash error whilst removing GPT partition '%s'\r\n", ++ __func__, fwu_image[i].image_names[name_index]); ++ return ret; ++ } else if (ret < 0) { ++ FWU_LOG_MSG("%s: Unable to remove partition '%s'\r\n", ++ __func__, fwu_image[i].image_names[name_index]); ++ return ret; ++ } ++ ++ FWU_LOG_MSG("%s: Removed GPT partition '%s'\r\n", ++ __func__, fwu_image[i].image_names[name_index]); ++ } ++ ++ return ret; ++} ++#endif ++ + static psa_status_t fwu_select_previous( + struct fwu_metadata *metadata, + struct fwu_private_metadata *priv_metadata) +@@ -1162,40 +1210,12 @@ static psa_status_t fwu_select_previous( + + #ifndef BL1_BUILD + /* Remove the GPT partitions for the rejected images. It is always the newer +- * (second) partitions that are rejected, as they are created during the +- * fwu process ++ * (previous active) partitions that are rejected, as they are created during ++ * the fwu process + */ +- for (int i = 0; i < NR_OF_IMAGES_IN_FW_BANK; ++i) { +- struct partition_entry_t part; +- ret = gpt_entry_read_by_type(&(fwu_image[i].image_type), 1, &part); +- +- if (ret == PSA_ERROR_DOES_NOT_EXIST) { +- FWU_LOG_MSG("%s: Unable to find partition '%s'\r\n", +- __func__, fwu_image[i].image_names[index]); +- return ret; +- } else if (ret == PSA_ERROR_STORAGE_FAILURE) { +- FWU_LOG_MSG("%s: Flash error whilst reading GPT partition '%s'\r\n", +- __func__, fwu_image[i].image_names[index]); +- return ret; +- } else if (ret < 0) { +- FWU_LOG_MSG("%s: Unable to read partition '%s'\r\n", +- __func__, fwu_image[i].image_names[index]); +- return ret; +- } +- +- ret = gpt_entry_remove(&(part.partition_guid)); +- if (ret == PSA_ERROR_STORAGE_FAILURE) { +- FWU_LOG_MSG("%s: Flash error whilst removing GPT partition '%s'\r\n", +- __func__, fwu_image[i].image_names[index]); +- return ret; +- } else if (ret < 0) { +- FWU_LOG_MSG("%s: Unable to remove partition '%s'\r\n", +- __func__, fwu_image[i].image_names[index]); +- return ret; +- } +- +- FWU_LOG_MSG("%s: Removed GPT partition '%s'\r\n", +- __func__, fwu_image[i].image_names[index]); ++ ret = remove_all_stale_partitions(1, metadata->previous_active_index); ++ if (ret != PSA_SUCCESS) { ++ return ret; + } + #endif /* BL1_BUILD */ + +@@ -1971,6 +1991,28 @@ psa_status_t fwu_bootloader_load_image(psa_fwu_component_t component, + priv_metadata.fmp_last_attempt_status[fwu_image_index]); + + FWU_LOG_MSG("ERROR: %s: version error\n\r",__func__); ++ ++#ifndef BL1_BUILD ++ /* The FWU process short circuits at this point, so remove all images, ++ * effecitvely treating them all as rejected. Ignore return code and ++ * in order to return PSA_OPERATION_INCOMPLETE as per PSA FWU API. ++ */ ++ uint32_t previous_active_index; ++ if (active_index == BANK_0) { ++ previous_active_index = BANK_1; ++ } else if (active_index == BANK_1) { ++ previous_active_index = BANK_0; ++ } else { ++ FWU_LOG_MSG("ERROR: %s: active_index %d\n\r",__func__,active_index); ++ ret = PSA_ERROR_DATA_INVALID; ++ goto out; ++ } ++ ++ /* The newer index should be removed as that was just created in the ++ * staging phase. ++ */ ++ (void)remove_all_stale_partitions(1, previous_active_index); ++#endif + ret = PSA_OPERATION_INCOMPLETE; + goto out; + } diff --git a/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0059-plat-cs1k-Duplicate-old-images-in-FWU.patch b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0059-plat-cs1k-Duplicate-old-images-in-FWU.patch new file mode 100644 index 00000000..3fcf8560 --- /dev/null +++ b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0059-plat-cs1k-Duplicate-old-images-in-FWU.patch @@ -0,0 +1,217 @@ +From 3c7cb3432084df3b75b6382e543f3fc2352e32cf Mon Sep 17 00:00:00 2001 +From: Frazer Carsley +Date: Fri, 13 Mar 2026 13:42:16 +0000 +Subject: [PATCH] plat: cs1k: Duplicate old images in FWU + +When copying existing partitions during a partial firmware update, the +GPT library is now used to duplicate the old partitions and then rename +them accordingly. This streamlines the steps of + 1. creating a new partition for the image to be copied into and + 2. the copying itself +into a single library call. + +Change-Id: Ibd169dcc14ed1c946bbd6c30b6962c89055d0e8e +Signed-off-by: Frazer Carsley +Upstream-Status: Submitted [https://review.trustedfirmware.org/c/TF-M/trusted-firmware-m/+/50261/1] +--- + .../bootloader/mcuboot/tfm_mcuboot_fwu.c | 144 +++++++++--------- + 1 file changed, 68 insertions(+), 76 deletions(-) + +diff --git a/platform/ext/target/arm/corstone1000/bootloader/mcuboot/tfm_mcuboot_fwu.c b/platform/ext/target/arm/corstone1000/bootloader/mcuboot/tfm_mcuboot_fwu.c +index 557e48d07..c48d51b32 100644 +--- a/platform/ext/target/arm/corstone1000/bootloader/mcuboot/tfm_mcuboot_fwu.c ++++ b/platform/ext/target/arm/corstone1000/bootloader/mcuboot/tfm_mcuboot_fwu.c +@@ -39,7 +39,6 @@ + * This is used when bank consistency is maintained during partial capsule update + */ + #define FLASH_CHUNK_SIZE 512 +-static uint8_t flash_data_buf[FLASH_CHUNK_SIZE]; + + /* Possible states of the bank. + * Naming convention here matches the implementation in U-Boot +@@ -2171,94 +2170,24 @@ out: + return ret; + } + ++#ifdef BL1_BUILD + static psa_status_t copy_image_from_other_bank(int image_index, + uint32_t active_index, + uint32_t previous_active_index) + { + FWU_LOG_FUNC_ENTER; + ++ /* Use offsets directly */ + uint32_t bank_offset[NR_OF_FW_BANKS] = {BANK_0_PARTITION_OFFSET, BANK_1_PARTITION_OFFSET}; + psa_status_t ret; + +-#ifdef BL1_BUILD + /* Use offsets directly */ ++ uint8_t data[FLASH_CHUNK_SIZE]; + size_t remaining_size = fwu_image[image_index].image_size; + size_t data_size; + size_t offset_read = bank_offset[active_index] + fwu_image[image_index].image_offset; + size_t offset_write = bank_offset[previous_active_index] + fwu_image[image_index].image_offset; + int data_transferred_count; +-#else +- /* Use GPT to find the correct image */ +- struct partition_entry_t active_part; +- ret = gpt_entry_read_by_type( +- &(fwu_image[image_index].image_type), +- 0, +- &active_part); +- if (ret == PSA_ERROR_DOES_NOT_EXIST) { +- FWU_LOG_MSG("%s: Unable to find partition '%s'\r\n", +- __func__, fwu_image[image_index].image_names[active_index]); +- return ret; +- } else if (ret == PSA_ERROR_STORAGE_FAILURE) { +- FWU_LOG_MSG("%s: Flash error whilst reading GPT partition '%s'\r\n", +- __func__, fwu_image[image_index].image_names[active_index]); +- return ret; +- } else if (ret < 0) { +- FWU_LOG_MSG("%s: Unable to read partition '%s'\r\n", +- __func__, fwu_image[image_index].image_names[active_index]); +- return ret; +- } +- +- struct partition_entry_t prev_active_part; +- ret = gpt_entry_read_by_type( +- &(fwu_image[image_index].image_type), +- 1, +- &prev_active_part); +- +- if (ret == PSA_ERROR_DOES_NOT_EXIST) { +- /* Create the partition in the expected space */ +- struct efi_guid_t new_guid = {0}; +- char unicode_name[GPT_ENTRY_NAME_LENGTH] = {'\0'}; +- ascii_to_unicode(fwu_image[image_index].image_names[previous_active_index], unicode_name); +- +- ret = gpt_entry_create(&(fwu_image[image_index].image_type), +- (bank_offset[previous_active_index] + fwu_image[image_index].image_offset) / TFM_GPT_BLOCK_SIZE, +- 1 + ((fwu_image[image_index].image_size - 1) / TFM_GPT_BLOCK_SIZE), +- 0, +- unicode_name, +- &new_guid); +- if (ret == PSA_ERROR_INSUFFICIENT_STORAGE) { +- FWU_LOG_MSG("%s: No space left on device!\r\n", __func__); +- return ret; +- } else if (ret == PSA_ERROR_STORAGE_FAILURE) { +- FWU_LOG_MSG("%s: Flash error whilst creating GPT partition '%s'!\r\n", +- __func__, fwu_image[image_index].image_names[previous_active_index]); +- return ret; +- } else if (ret < 0) { +- return ret; +- } +- +- ret = gpt_entry_read(&new_guid, &prev_active_part); +- if (ret == PSA_ERROR_STORAGE_FAILURE) { +- FWU_LOG_MSG("%s: Flash error whilst reading GPT partition '%s'\r\n", +- __func__, fwu_image[image_index].image_names[previous_active_index]); +- return ret; +- } else if (ret < 0) { +- return ret; +- } +- } else if (ret == PSA_ERROR_STORAGE_FAILURE) { +- FWU_LOG_MSG("%s: Flash error whilst reading GPT partition '%s'\r\n", +- __func__, fwu_image[image_index].image_names[previous_active_index]); +- return ret; +- } else if (ret < 0) { +- return ret; +- } +- +- size_t remaining_size = prev_active_part.size * TFM_GPT_BLOCK_SIZE; +- size_t data_size; +- size_t offset_read = active_part.start * TFM_GPT_BLOCK_SIZE; +- size_t offset_write = prev_active_part.start * TFM_GPT_BLOCK_SIZE; +- int data_transferred_count; +-#endif /* BL1_BUILD */ + + ret = erase_image(offset_write, remaining_size); + if (ret != PSA_SUCCESS) { +@@ -2270,7 +2199,7 @@ static psa_status_t copy_image_from_other_bank(int image_index, + data_size = (remaining_size > FLASH_CHUNK_SIZE) ? FLASH_CHUNK_SIZE : remaining_size; + + /* read image data from flash */ +- data_transferred_count = FWU_METADATA_FLASH_DEV.ReadData(offset_read, flash_data_buf, data_size); ++ data_transferred_count = FWU_METADATA_FLASH_DEV.ReadData(offset_read, data, data_size); + if (data_transferred_count < 0) { + FWU_LOG_MSG("%s: ERROR - Flash read failed (ret = %d)\n\r", __func__, data_transferred_count); + return PSA_ERROR_STORAGE_FAILURE; +@@ -2285,7 +2214,7 @@ static psa_status_t copy_image_from_other_bank(int image_index, + offset_read += data_size; + + /* write image data to flash */ +- data_transferred_count = FWU_METADATA_FLASH_DEV.ProgramData(offset_write, flash_data_buf, data_size); ++ data_transferred_count = FWU_METADATA_FLASH_DEV.ProgramData(offset_write, data, data_size); + if (data_transferred_count < 0) { + FWU_LOG_MSG("%s: ERROR - Flash read failed (ret = %d)\n\r", __func__, data_transferred_count); + return PSA_ERROR_STORAGE_FAILURE; +@@ -2304,6 +2233,69 @@ static psa_status_t copy_image_from_other_bank(int image_index, + FWU_LOG_MSG("%s: exit \n\r", __func__); + return PSA_SUCCESS; + } ++#else ++static psa_status_t copy_image_from_other_bank(int image_index, ++ uint32_t active_index, ++ uint32_t previous_active_index) ++{ ++ FWU_LOG_FUNC_ENTER; ++ ++ /* Use GPT to find and copy the correct image */ ++ uint32_t bank_offset[NR_OF_FW_BANKS] = {BANK_0_PARTITION_OFFSET, BANK_1_PARTITION_OFFSET}; ++ uint64_t new_lba = ++ (bank_offset[previous_active_index] + fwu_image[image_index].image_offset) / TFM_GPT_BLOCK_SIZE; ++ ++ struct partition_entry_t active_part; ++ psa_status_t ret = gpt_entry_read_by_type( ++ &(fwu_image[image_index].image_type), ++ 0, ++ &active_part); ++ if (ret == PSA_ERROR_DOES_NOT_EXIST) { ++ FWU_LOG_MSG("%s: Unable to find partition '%s'\r\n", ++ __func__, fwu_image[image_index].image_names[active_index]); ++ return ret; ++ } else if (ret == PSA_ERROR_STORAGE_FAILURE) { ++ FWU_LOG_MSG("%s: Flash error whilst reading GPT partition '%s'\r\n", ++ __func__, fwu_image[image_index].image_names[active_index]); ++ return ret; ++ } else if (ret < 0) { ++ FWU_LOG_MSG("%s: Unable to read partition '%s'\r\n", ++ __func__, fwu_image[image_index].image_names[active_index]); ++ return ret; ++ } ++ ++ struct efi_guid_t new_guid; ++ ret = gpt_entry_duplicate(&(active_part.partition_guid), new_lba, &new_guid); ++ if (ret == PSA_ERROR_STORAGE_FAILURE) { ++ FWU_LOG_MSG("%s: Flash error whilst creating GPT partition '%s'\r\n", ++ __func__, fwu_image[image_index].image_names[previous_active_index]); ++ return ret; ++ } else if (ret < 0) { ++ FWU_LOG_MSG("%s: Unable to create partition '%s'\r\n", ++ __func__, fwu_image[image_index].image_names[previous_active_index]); ++ return ret; ++ } ++ ++ char unicode_name[GPT_ENTRY_NAME_LENGTH] = {'\0'}; ++ ascii_to_unicode(fwu_image[image_index].image_names[previous_active_index], unicode_name); ++ ret = gpt_entry_rename(&new_guid, unicode_name); ++ if (ret != PSA_SUCCESS) { ++ FWU_LOG_MSG("%s: Unable to rename partition to '%s'\r\n", ++ __func__, fwu_image[image_index].image_names[previous_active_index]); ++ ++ /* Delete the newly created partition as there is code that relies on the naming */ ++ ret = gpt_entry_remove(&new_guid); ++ if (ret != PSA_SUCCESS) { ++ FWU_LOG_MSG("%s: Catastrophic failure: unable to remove duplicate partition '%s'\r\n", ++ __func__, fwu_image[image_index].image_names[active_index]); ++ } ++ return ret; ++ } ++ ++ FWU_LOG_MSG("%s: exit \n\r", __func__); ++ return PSA_SUCCESS; ++} ++#endif /* BL1_BUILD */ + + static psa_status_t maintain_bank_consistency(void) + { diff --git a/meta-arm-bsp/recipes-bsp/trusted-firmware-m/trusted-firmware-m-corstone1000.inc b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/trusted-firmware-m-corstone1000.inc index 4a8ec99b..6ee13b30 100644 --- a/meta-arm-bsp/recipes-bsp/trusted-firmware-m/trusted-firmware-m-corstone1000.inc +++ b/meta-arm-bsp/recipes-bsp/trusted-firmware-m/trusted-firmware-m-corstone1000.inc @@ -88,6 +88,9 @@ SRC_URI:append:corstone1000 = " \ file://0054-lib-gpt-Consecutively-erase-blocks-when-moving-parti.patch \ file://0055-lib-gpt-Clarify-API-operation.patch \ file://0056-lib-gpt-Add-metadata-only-API-operations.patch \ + file://0057-plat-cs1k-Add-flash-erase-protection.patch \ + file://0058-plat-cs1k-Remove-unused-FWU-partitions-upon-version-.patch \ + file://0059-plat-cs1k-Duplicate-old-images-in-FWU.patch \ " SRCREV_tfm-psa-adac:corstone1000 = "f2809ae231be33a1afcd7714f40756c67d846c88"