1
0
mirror of https://git.yoctoproject.org/meta-arm synced 2026-06-05 02:20:30 +00:00

arm-bsp/tf-m:cs1k: Add GPT library

The patches added in this commit add a generic GPT library for use in
flash devices. Corstone1000 could use these to manage partitions during
a firmware update.

The patches are all backports from trusted-firmware-m (TF-M) main
branch and can be removed if Corstone1000 upgrades when the next version
of TF-M is released.

Signed-off-by: Frazer Carsley <frazer.carsley@arm.com>
Signed-off-by: Jon Mason <jon.mason@arm.com>
This commit is contained in:
Frazer Carsley
2026-03-26 13:28:39 +00:00
committed by Jon Mason
parent 7b687574a1
commit 5fc4c29db9
18 changed files with 5197 additions and 0 deletions
@@ -0,0 +1,217 @@
From 5fc4f3857739a9fe93819cedc4a8108d33bf8241 Mon Sep 17 00:00:00 2001
From: Frazer Carsley <frazer.carsley@arm.com>
Date: Thu, 27 Nov 2025 10:35:58 +0000
Subject: [PATCH] lib: efi_guid: Added EFI GUID library
This library can be used to generate version 4 UUID/GUIDs according to
RFC 4122 by using a PSA crypto driver. A separate header is provided to
give access to the struct without the need of a PSA config.
Change-Id: Ief35b2a4f565889ba2ea0de82e20dc12f6e824e8
Signed-off-by: Frazer Carsley <frazer.carsley@arm.com>
Upstream-Status: Backport [3bdd2562ebad3115457ea75e58d0554802e12dba]
---
CMakeLists.txt | 1 +
lib/efi_guid/CMakeLists.txt | 26 ++++++++++++
lib/efi_guid/inc/efi_guid.h | 36 +++++++++++++++++
lib/efi_guid/inc/efi_guid_structs.h | 62 +++++++++++++++++++++++++++++
lib/efi_guid/src/efi_guid.c | 33 +++++++++++++++
5 files changed, 158 insertions(+)
create mode 100644 lib/efi_guid/CMakeLists.txt
create mode 100644 lib/efi_guid/inc/efi_guid.h
create mode 100644 lib/efi_guid/inc/efi_guid_structs.h
create mode 100644 lib/efi_guid/src/efi_guid.c
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2560dd15e..b120de697 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -44,6 +44,7 @@ project("Trusted Firmware M" VERSION ${TFM_VERSION} LANGUAGES C CXX ASM)
add_subdirectory(lib/backtrace)
add_subdirectory(lib/ext)
+add_subdirectory(lib/efi_guid)
add_subdirectory(lib/fih)
add_subdirectory(lib/tfm_log)
add_subdirectory(lib/tfm_log_unpriv)
diff --git a/lib/efi_guid/CMakeLists.txt b/lib/efi_guid/CMakeLists.txt
new file mode 100644
index 000000000..656eb72ea
--- /dev/null
+++ b/lib/efi_guid/CMakeLists.txt
@@ -0,0 +1,26 @@
+#-------------------------------------------------------------------------------
+# SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+
+add_library(tfm_efi_guid STATIC)
+
+target_sources(tfm_efi_guid
+ PRIVATE
+ src/efi_guid.c
+)
+
+target_include_directories(tfm_efi_guid
+ PUBLIC
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/inc>
+ PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}/src
+)
+
+target_link_libraries(tfm_efi_guid
+ PUBLIC
+ psa_interface
+ psa_crypto_config
+)
diff --git a/lib/efi_guid/inc/efi_guid.h b/lib/efi_guid/inc/efi_guid.h
new file mode 100644
index 000000000..81f6ad507
--- /dev/null
+++ b/lib/efi_guid/inc/efi_guid.h
@@ -0,0 +1,36 @@
+/*
+ * SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef __TFM_EFI_GUID_H__
+#define __TFM_EFI_GUID_H__
+
+#include "psa/crypto.h"
+#include "efi_guid_structs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \brief Generates a random version 4 UUID/GUID using a PSA crypto driver.
+ *
+ * \note See RFC 4112 for details on UUID/GUIDs.
+ *
+ * \note See psa_generate_random for possible failures.
+ *
+ * \param[out] guid Pointer populated with the generated UUID/GUID.
+ *
+ * \return PSA_SUCCESS on success or a PSA error code on failure.
+ *
+ */
+psa_status_t efi_guid_generate_random(struct efi_guid_t *guid);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __TFM_EFI_GUID_H__ */
diff --git a/lib/efi_guid/inc/efi_guid_structs.h b/lib/efi_guid/inc/efi_guid_structs.h
new file mode 100644
index 000000000..c89e8f692
--- /dev/null
+++ b/lib/efi_guid/inc/efi_guid_structs.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2021, Linaro Limited
+ * SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef __TFM_EFI_GUID_STRUCTS_H__
+#define __TFM_EFI_GUID_STRUCTS_H__
+
+#include <stdint.h>
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define EFI_GUID_NODE_LEN 6
+
+/**
+ * \brief Representation that makes creating GUIDs slightly easier.
+ */
+struct efi_guid_t {
+ uint32_t time_low; /**< Low 32-bits of timestamp. */
+ uint16_t time_mid; /**< Middle 16-bits of timestamp. */
+ uint16_t time_hi_and_version; /**< High 12-bits of timestamp with 4-bit version. */
+ uint8_t clock_seq_hi_and_reserved; /**< High 4-bits of clock sequence with 4-bit variant. */
+ uint8_t clock_seq_low; /**< Low 8-bits of clock sequence. */
+ uint8_t node[EFI_GUID_NODE_LEN]; /**< 48-bit spatially unique node identifier. */
+};
+
+static inline int efi_guid_cmp(const struct efi_guid_t *g1,
+ const struct efi_guid_t *g2)
+{
+ return memcmp(g1, g2, sizeof(struct efi_guid_t));
+}
+
+static inline void *efi_guid_cpy(const struct efi_guid_t *src,
+ struct efi_guid_t *dst)
+{
+ return memcpy(dst, src, sizeof(struct efi_guid_t));
+}
+
+/** \brief Helper macro to build an EFI GUID structure from individual values. */
+#define MAKE_EFI_GUID(a, b, c, d0, d1, e0, e1, e2, e3, e4, e5) \
+ { \
+ (a) & 0xffffffff, (b)&0xffff, (c)&0xffff, d0, d1, { \
+ (e0), (e1), (e2), (e3), (e4), (e5) \
+ } \
+ }
+
+/** \brief Helper macro to build the EFI GUID 00000000-0000-0000-0000-0000000000. */
+#define NULL_GUID \
+ MAKE_EFI_GUID(0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __TFM_EFI_GUID_STRUCTS_H__ */
diff --git a/lib/efi_guid/src/efi_guid.c b/lib/efi_guid/src/efi_guid.c
new file mode 100644
index 000000000..cb8730a51
--- /dev/null
+++ b/lib/efi_guid/src/efi_guid.c
@@ -0,0 +1,33 @@
+/*
+ * SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <stdint.h>
+
+#include "psa/crypto.h"
+
+#include "efi_guid_structs.h"
+#include "efi_guid.h"
+
+psa_status_t efi_guid_generate_random(struct efi_guid_t *guid)
+{
+ const psa_status_t ret = psa_generate_random((uint8_t *)guid, sizeof(*guid));
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+
+ /* According to RFC 4122, counting bits from the right:
+ * - Bits 6 and 7 of clock_seq_hi_and_reserved need to be set to
+ * 0 and 1 respecitively
+ * - Bits 12 through 15 of time_hi_and_version need to be set to
+ * 0b0100
+ */
+ guid->clock_seq_hi_and_reserved &= 0x3F;
+ guid->clock_seq_hi_and_reserved |= 0x80;
+ guid->time_hi_and_version &= 0x0FFF;
+ guid->time_hi_and_version |= 0x4000;
+
+ return PSA_SUCCESS;
+}
@@ -0,0 +1,136 @@
From 4a47d926420bfec4a0e687b6ecc4cf52419e4f58 Mon Sep 17 00:00:00 2001
From: Frazer Carsley <frazer.carsley@arm.com>
Date: Thu, 27 Nov 2025 10:37:28 +0000
Subject: [PATCH] lib: efi_soft_crc: Added EFI CRC library
This library can be used to perform CRC32 calculations using the
standard CRC32 polynomial specified by the UEFI spec 2.10. It does not
use a lookup table to save on memory. The polynomial used is in reverse
order to match little-endian machines.
Change-Id: Ifce5a1cbbb3ab394bc748305be213a8467610015
Signed-off-by: Frazer Carsley <frazer.carsley@arm.com>
Upstream-Status: Backport [3f2b707eaadef8008f333bd5c519c1b2544458ab]
---
lib/ext/CMakeLists.txt | 1 +
lib/ext/efi_soft_crc/CMakeLists.txt | 18 ++++++++++++++
lib/ext/efi_soft_crc/inc/efi_soft_crc.h | 33 +++++++++++++++++++++++++
lib/ext/efi_soft_crc/src/efi_soft_crc.c | 32 ++++++++++++++++++++++++
4 files changed, 84 insertions(+)
create mode 100644 lib/ext/efi_soft_crc/CMakeLists.txt
create mode 100644 lib/ext/efi_soft_crc/inc/efi_soft_crc.h
create mode 100644 lib/ext/efi_soft_crc/src/efi_soft_crc.c
diff --git a/lib/ext/CMakeLists.txt b/lib/ext/CMakeLists.txt
index 05f6b8f14..1dffbacfb 100644
--- a/lib/ext/CMakeLists.txt
+++ b/lib/ext/CMakeLists.txt
@@ -10,6 +10,7 @@ add_subdirectory(qcbor)
add_subdirectory(t_cose)
add_subdirectory(mbedcrypto)
add_subdirectory(cmsis)
+add_subdirectory(efi_soft_crc)
if(BL2)
add_subdirectory(mcuboot)
endif()
diff --git a/lib/ext/efi_soft_crc/CMakeLists.txt b/lib/ext/efi_soft_crc/CMakeLists.txt
new file mode 100644
index 000000000..47fd2d507
--- /dev/null
+++ b/lib/ext/efi_soft_crc/CMakeLists.txt
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------------
+# SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+
+add_library(tfm_efi_soft_crc STATIC)
+
+target_sources(tfm_efi_soft_crc
+ PRIVATE
+ src/efi_soft_crc.c
+)
+
+target_include_directories(tfm_efi_soft_crc
+ PUBLIC
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/inc>
+)
diff --git a/lib/ext/efi_soft_crc/inc/efi_soft_crc.h b/lib/ext/efi_soft_crc/inc/efi_soft_crc.h
new file mode 100644
index 000000000..77baa8987
--- /dev/null
+++ b/lib/ext/efi_soft_crc/inc/efi_soft_crc.h
@@ -0,0 +1,33 @@
+/*
+ * SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef __TFM_EFI_SOFT_CRC_H__
+#define __TFM_EFI_SOFT_CRC_H__
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \brief Updates a CRC32 calculation using ISO 3309 CRC-32.
+ *
+ * \param[in] old_crc Existing CRC value (0 for the first calculation).
+ * \param[in] buf Buffer of bytes to perform the calculation over.
+ * \param[in] len Length of \p buf in bytes.
+ *
+ * \return The calculated CRC32 value.
+ */
+uint32_t efi_soft_crc32_update(uint32_t old_crc, const uint8_t *buf, size_t len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __TFM_EFI_SOFT_CRC_H__ */
diff --git a/lib/ext/efi_soft_crc/src/efi_soft_crc.c b/lib/ext/efi_soft_crc/src/efi_soft_crc.c
new file mode 100644
index 000000000..041a321cd
--- /dev/null
+++ b/lib/ext/efi_soft_crc/src/efi_soft_crc.c
@@ -0,0 +1,32 @@
+/* Copyright (C) 2013 Henry S. Warren Jr. You are free to use, copy,
+ * and distribute any of the code on this web site, whether modified
+ * by you or not.
+ */
+
+#include "efi_soft_crc.h"
+
+/* The standard polynomial, in reverse */
+#define POLYNOMIAL 0xEDB88320
+
+/* Algorithmic approach to CRC calculation: avoids lookup tables, slow. Byte
+ * reversal is avoided by shifting the crc register right instead of left and
+ * by using a reversed 32-bit word to represent the polynomial.
+ *
+ * Derived from work by Henry S. Warren Jr.
+ */
+uint32_t efi_soft_crc32_update(uint32_t old_crc32, const uint8_t *buf, size_t len)
+{
+ register uint32_t crc32 = ~old_crc32;
+ uint32_t mask;
+
+ for ( ; len; --len, ++buf)
+ {
+ crc32 ^= *buf;
+ for (size_t i = 0; i < 8; ++i) {
+ mask = -(crc32 & 1);
+ crc32 = (crc32 >> 1) ^ (POLYNOMIAL & mask);
+ }
+ }
+
+ return ~crc32;
+}
@@ -0,0 +1,308 @@
From b3775fd8aa2078e5e86d5cdb4164e6fc31fabbfc Mon Sep 17 00:00:00 2001
From: Frazer Carsley <frazer.carsley@arm.com>
Date: Wed, 11 Feb 2026 11:16:13 +0000
Subject: [PATCH] lib: gpt: Expanded how GPT partition can be identified
The GUID of a partition is not always known, particularly for tables
that have been created outside the control of the user. Therefore,
allowing the name or type to be used to identify a partition gives the
user greater flexibility. These may not be unique, however, and so must
be indexed. A single entry is returned rather than a list so as to not
dynamically allocate such memory.
Change-Id: I7687bfc737b244f6e589c4fe6b61c370daf1b062
Signed-off-by: Frazer Carsley <frazer.carsley@arm.com>
Upstream-Status: Backport [aede42c1c58b5681fe50bbec3a797f69256d108e]
---
lib/gpt/inc/gpt.h | 34 +++++++++
lib/gpt/src/gpt.c | 68 +++++++++++++++--
lib/gpt/unittests/gpt/test_gpt.c | 127 +++++++++++++++++++++++++++++++
3 files changed, 223 insertions(+), 6 deletions(-)
diff --git a/lib/gpt/inc/gpt.h b/lib/gpt/inc/gpt.h
index d98abe980..947a6b341 100644
--- a/lib/gpt/inc/gpt.h
+++ b/lib/gpt/inc/gpt.h
@@ -48,6 +48,40 @@ __attribute__((nonnull(1,2)))
psa_status_t gpt_entry_read(const struct efi_guid_t *guid,
struct partition_entry_t *partition_entry);
+/**
+ * \brief Reads the contents of a partition entry identified by name.
+ *
+ * \param[in] name Name of the partition to read in unicode.
+ * \param[in] index Index to read when multiple entries share the same name.
+ * \param[out] partition_entry Populated partition entry on success.
+ *
+ * \retval PSA_SUCCESS Success.
+ * \retval PSA_ERROR_STORAGE_FAILURE I/O error.
+ * \retval PSA_ERROR_DOES_NOT_EXIST No entry found with the provided name at \p index. For example,
+ * \p index was 1 (second entry) but only one entry was found.
+ */
+__attribute__((nonnull(1,3)))
+psa_status_t gpt_entry_read_by_name(const char name[GPT_ENTRY_NAME_LENGTH],
+ const uint32_t index,
+ struct partition_entry_t *partition_entry);
+
+/**
+ * \brief Reads the contents of a partition entry identified by type.
+ *
+ * \param[in] type Type of the partition to read.
+ * \param[in] index Index to read when multiple entries share the same type.
+ * \param[out] partition_entry Populated partition entry on success.
+ *
+ * \retval PSA_SUCCESS Success.
+ * \retval PSA_ERROR_STORAGE_FAILURE I/O error.
+ * \retval PSA_ERROR_DOES_NOT_EXIST No entry found with the provided type at \p index. For example,
+ * \p index was 1 (second entry) but only one entry was found.
+ */
+__attribute__((nonnull(1,3)))
+psa_status_t gpt_entry_read_by_type(const struct efi_guid_t *type,
+ const uint32_t index,
+ struct partition_entry_t *partition_entry);
+
/**
* \brief Reads the GPT header from the second block (LBA 1).
*
diff --git a/lib/gpt/src/gpt.c b/lib/gpt/src/gpt.c
index 99d735797..1662b81c2 100644
--- a/lib/gpt/src/gpt.c
+++ b/lib/gpt/src/gpt.c
@@ -177,6 +177,8 @@ static inline uint64_t partition_entry_lba(const struct gpt_t *table,
static inline uint64_t gpt_entry_per_lba_count(void);
static psa_status_t count_used_partitions(const struct gpt_t *table,
uint32_t *num_used);
+static inline void parse_entry(struct gpt_entry_t *entry,
+ struct partition_entry_t *partition_entry);
static psa_status_t read_from_flash(uint64_t required_lba);
static psa_status_t read_entry_from_flash(const struct gpt_t *table,
uint32_t array_index,
@@ -190,6 +192,8 @@ static psa_status_t find_gpt_entry(const struct gpt_t *table,
uint32_t *array_index);
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);
/* PUBLIC API FUNCTIONS */
@@ -202,12 +206,37 @@ psa_status_t gpt_entry_read(const struct efi_guid_t *guid,
return ret;
}
- partition_entry->start = cached_entry.start;
- partition_entry->size = cached_entry.end - cached_entry.start + 1;
- memcpy(partition_entry->name, cached_entry.name, GPT_ENTRY_NAME_LENGTH);
- partition_entry->attr = cached_entry.attr;
- partition_entry->partition_guid = cached_entry.unique_guid;
- partition_entry->type_guid = cached_entry.partition_type;
+ parse_entry(&cached_entry, partition_entry);
+
+ return PSA_SUCCESS;
+}
+
+psa_status_t gpt_entry_read_by_name(const char name[GPT_ENTRY_NAME_LENGTH],
+ const uint32_t index,
+ struct partition_entry_t *partition_entry)
+{
+ struct gpt_entry_t cached_entry;
+ const psa_status_t ret = find_gpt_entry(&primary_gpt, gpt_entry_cmp_name, name, index, &cached_entry, NULL);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+
+ parse_entry(&cached_entry, partition_entry);
+
+ return PSA_SUCCESS;
+}
+
+psa_status_t gpt_entry_read_by_type(const struct efi_guid_t *type,
+ const uint32_t index,
+ struct partition_entry_t *partition_entry)
+{
+ struct gpt_entry_t cached_entry;
+ const psa_status_t ret = find_gpt_entry(&primary_gpt, gpt_entry_cmp_type, type, index, &cached_entry, NULL);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+
+ parse_entry(&cached_entry, partition_entry);
return PSA_SUCCESS;
}
@@ -322,6 +351,18 @@ static inline uint64_t gpt_entry_per_lba_count(void)
return num_entries;
}
+/* Copies information from the entry to the user visible structure */
+static inline void parse_entry(struct gpt_entry_t *entry,
+ struct partition_entry_t *partition_entry)
+{
+ partition_entry->start = entry->start;
+ partition_entry->size = entry->end - entry->start + 1;
+ memcpy(partition_entry->name, entry->name, GPT_ENTRY_NAME_LENGTH);
+ partition_entry->attr = entry->attr;
+ partition_entry->partition_guid = entry->unique_guid;
+ partition_entry->type_guid = entry->partition_type;
+}
+
/* Compare the entry with the given guid */
static bool gpt_entry_cmp_guid(const struct gpt_entry_t *entry, const void *guid)
{
@@ -331,6 +372,21 @@ static bool gpt_entry_cmp_guid(const struct gpt_entry_t *entry, const void *guid
return efi_guid_cmp(&entry_guid, cmp_guid) == 0;
}
+/* Compare the entry with the given name */
+static bool gpt_entry_cmp_name(const struct gpt_entry_t *entry, const void *name)
+{
+ return memcmp(name, entry->name, GPT_ENTRY_NAME_LENGTH) == 0;
+}
+
+/* Compare the entry with the given type */
+static bool gpt_entry_cmp_type(const struct gpt_entry_t *entry, const void *type)
+{
+ const struct efi_guid_t *cmp_type = (const struct efi_guid_t *)type;
+ const struct efi_guid_t entry_type = entry->partition_type;
+
+ return efi_guid_cmp(&entry_type, cmp_type) == 0;
+}
+
/* Read entry with given GUID from given table and return it if found. */
static psa_status_t find_gpt_entry(const struct gpt_t *table,
gpt_entry_cmp_t compare,
diff --git a/lib/gpt/unittests/gpt/test_gpt.c b/lib/gpt/unittests/gpt/test_gpt.c
index 325887fae..d6671f3fc 100644
--- a/lib/gpt/unittests/gpt/test_gpt.c
+++ b/lib/gpt/unittests/gpt/test_gpt.c
@@ -381,3 +381,130 @@ void test_gpt_entry_read_should_failWhenEntryNotExisting(void)
register_mocked_read(&test_partition_array, sizeof(test_partition_array));
TEST_ASSERT_EQUAL(PSA_ERROR_DOES_NOT_EXIST, gpt_entry_read(&non_existing, &entry));
}
+
+void test_gpt_entry_read_by_name_should_populateEntry(void)
+{
+ /* Start with a populated GPT */
+ setup_valid_gpt();
+
+ /* Ensure an entry is found, even with repeat names */
+ struct partition_entry_t entry;
+ struct gpt_entry_t *desired1 = &(test_partition_array[0]);
+ struct efi_guid_t test_guid1 = desired1->guid;
+ struct efi_guid_t test_type1 = desired1->type;
+
+ /* Change the name of something else and ensure it is found */
+ struct gpt_entry_t *desired2 = &(test_partition_array[1]);
+ struct efi_guid_t test_guid2 = desired2->guid;
+ struct efi_guid_t test_type2 = desired2->type;
+ memcpy(desired2->name, desired1->name, GPT_ENTRY_NAME_LENGTH);
+
+ register_mocked_read(&test_partition_array, sizeof(test_partition_array));
+ TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_entry_read_by_name(desired1->name, 0, &entry));
+
+ /* Ensure this is the correct entry */
+ TEST_ASSERT_EQUAL(0, efi_guid_cmp(&test_guid1, &(entry.partition_guid)));
+ TEST_ASSERT_EQUAL(0, efi_guid_cmp(&test_type1, &(entry.type_guid)));
+ TEST_ASSERT_EQUAL(desired1->start, entry.start);
+ TEST_ASSERT_EQUAL(desired1->end, entry.start + entry.size - 1);
+
+ TEST_ASSERT_EQUAL_MEMORY(desired1->name, entry.name, GPT_ENTRY_NAME_LENGTH);
+
+ /* Do again but the next entry */
+ TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_entry_read_by_name(desired1->name, 1, &entry));
+
+ /* Ensure this is the correct entry */
+ TEST_ASSERT_EQUAL(0, efi_guid_cmp(&test_guid2, &(entry.partition_guid)));
+ TEST_ASSERT_EQUAL(0, efi_guid_cmp(&test_type2, &(entry.type_guid)));
+ TEST_ASSERT_EQUAL(desired2->start, entry.start);
+ TEST_ASSERT_EQUAL(desired2->end, entry.start + entry.size - 1);
+
+ TEST_ASSERT_EQUAL_MEMORY(desired2->name, entry.name, GPT_ENTRY_NAME_LENGTH);
+}
+
+void test_gpt_entry_read_by_name_should_failWhenEntryNotExisting(void)
+{
+ /* Start with an empty GPT */
+ setup_empty_gpt();
+
+ /* Try to read something */
+ struct partition_entry_t entry;
+ char test_name[GPT_ENTRY_NAME_LENGTH] = {'\0'};
+ TEST_ASSERT_EQUAL(PSA_ERROR_DOES_NOT_EXIST, gpt_entry_read_by_name(test_name, 0, &entry));
+
+ /* Now, have a non-empty GPT but search for a name that won't exist */
+ setup_valid_gpt();
+
+ /* Each entry should be read */
+ register_mocked_read(&test_partition_array, sizeof(test_partition_array));
+ TEST_ASSERT_EQUAL(PSA_ERROR_DOES_NOT_EXIST, gpt_entry_read_by_name(test_name, 0, &entry));
+
+ /* Finally, search for the second entry of a name that appears only once */
+ memcpy(test_name, test_partition_array[0].name, GPT_ENTRY_NAME_LENGTH);
+ TEST_ASSERT_EQUAL(PSA_ERROR_DOES_NOT_EXIST, gpt_entry_read_by_name(test_name, 1, &entry));
+ TEST_ASSERT_EQUAL(PSA_ERROR_DOES_NOT_EXIST, gpt_entry_read_by_name(test_name, TEST_DEFAULT_NUM_PARTITIONS, &entry));
+}
+
+void test_gpt_entry_read_by_type_should_populateEntry(void)
+{
+ /* Start with a populated GPT */
+ setup_valid_gpt();
+
+ /* Ensure an entry is found, even with repeat types */
+ struct partition_entry_t entry;
+ struct gpt_entry_t *desired1 = &(test_partition_array[0]);
+ struct efi_guid_t test_guid1 = desired1->guid;
+ struct efi_guid_t test_type1 = desired1->type;
+
+ struct gpt_entry_t *desired2 = &(test_partition_array[1]);
+ struct efi_guid_t test_guid2 = desired2->guid;
+ struct efi_guid_t test_type2 = test_type1;
+ desired2->type = test_type2;
+
+ register_mocked_read(&test_partition_array, sizeof(test_partition_array));
+ TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_entry_read_by_type(&test_type1, 0, &entry));
+
+ /* Ensure this is the correct entry */
+ TEST_ASSERT_EQUAL(0, efi_guid_cmp(&test_guid1, &(entry.partition_guid)));
+ TEST_ASSERT_EQUAL(0, efi_guid_cmp(&test_type1, &(entry.type_guid)));
+ TEST_ASSERT_EQUAL(desired1->start, entry.start);
+ TEST_ASSERT_EQUAL(desired1->end, entry.start + entry.size - 1);
+
+ /* Name is unicode */
+ TEST_ASSERT_EQUAL_MEMORY(desired1->name, entry.name, GPT_ENTRY_NAME_LENGTH);
+
+ /* Do again but the next entry */
+ TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_entry_read_by_type(&test_type1, 1, &entry));
+
+ /* Ensure this is the correct entry */
+ TEST_ASSERT_EQUAL(0, efi_guid_cmp(&test_guid2, &(entry.partition_guid)));
+ TEST_ASSERT_EQUAL(0, efi_guid_cmp(&test_type2, &(entry.type_guid)));
+ TEST_ASSERT_EQUAL(desired2->start, entry.start);
+ TEST_ASSERT_EQUAL(desired2->end, entry.start + entry.size - 1);
+
+ TEST_ASSERT_EQUAL_MEMORY(desired2->name, entry.name, GPT_ENTRY_NAME_LENGTH);
+}
+
+void test_gpt_entry_read_by_type_should_failWhenEntryNotExisting(void)
+{
+ /* Start with an empty GPT */
+ setup_empty_gpt();
+
+ /* 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);
+ 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 */
+ setup_valid_gpt();
+
+ /* Each entry should be read */
+ register_mocked_read(&test_partition_array, sizeof(test_partition_array));
+ TEST_ASSERT_EQUAL(PSA_ERROR_DOES_NOT_EXIST, gpt_entry_read_by_type(&test_type, 0, &entry));
+
+ /* Finally, search for the second entry of a type that appears only once */
+ struct efi_guid_t existing_type = test_partition_array[0].type;
+ efi_guid_cpy(&existing_type, &test_type);
+ TEST_ASSERT_EQUAL(PSA_ERROR_DOES_NOT_EXIST, gpt_entry_read_by_type(&test_type, 1, &entry));
+ TEST_ASSERT_EQUAL(PSA_ERROR_DOES_NOT_EXIST, gpt_entry_read_by_type(&test_type, TEST_DEFAULT_NUM_PARTITIONS, &entry));
+}
@@ -0,0 +1,961 @@
From 02ec696240d0225ed6473ea5b01ec6617d897a41 Mon Sep 17 00:00:00 2001
From: Frazer Carsley <frazer.carsley@arm.com>
Date: Wed, 11 Feb 2026 11:23:31 +0000
Subject: [PATCH] lib: gpt: Added operations to modify partitions
The operations added allow the user to modify existing GPT partitions,
apart from moving them; that is, they are all metadata changes. The
library also handles updating both primary and backup partition entry
arrays and headers after each modification.
Each modification on an entry is buffered in order to reduce the number
of flash erase operations. A simple heuristic of "write on every n
operations" is used to prevent infinite buffering. The buffering is most
effective when consecutive entries are operated on. If the mapping of
LBA to flash sector is not 1:1, the flash driver the platform registers
with the library may implement its own buffering for further
optimisation.
Change-Id: Ie358464427d66883e1681497a2630a8ef281e528
Signed-off-by: Frazer Carsley <frazer.carsley@arm.com>
Upstream-Status: Backport [ab942ecb7cb62bcfe1e5701f3f3adbf1eebd330a]
---
lib/gpt/CMakeLists.txt | 1 +
lib/gpt/inc/gpt.h | 69 +++++
lib/gpt/inc/gpt_flash.h | 41 ++-
lib/gpt/src/gpt.c | 419 +++++++++++++++++++++++++++++-
lib/gpt/unittests/gpt/test_gpt.c | 187 +++++++++++++
lib/gpt/unittests/gpt/utcfg.cmake | 2 +
6 files changed, 712 insertions(+), 7 deletions(-)
diff --git a/lib/gpt/CMakeLists.txt b/lib/gpt/CMakeLists.txt
index 2b5d6af8f..25d0de9cd 100644
--- a/lib/gpt/CMakeLists.txt
+++ b/lib/gpt/CMakeLists.txt
@@ -37,4 +37,5 @@ target_link_libraries(tfm_gpt
tfm_log_headers
PRIVATE
tfm_efi_guid
+ tfm_efi_soft_crc
)
diff --git a/lib/gpt/inc/gpt.h b/lib/gpt/inc/gpt.h
index 947a6b341..6e7bb360e 100644
--- a/lib/gpt/inc/gpt.h
+++ b/lib/gpt/inc/gpt.h
@@ -82,6 +82,75 @@ psa_status_t gpt_entry_read_by_type(const struct efi_guid_t *type,
const uint32_t index,
struct partition_entry_t *partition_entry);
+/**
+ * \brief Renames a partition entry.
+ *
+ * \param[in] guid Entry to rename.
+ * \param[in] name New unicode name for the entry.
+ *
+ * \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 Empty name.
+ */
+__attribute__((nonnull(1,2)))
+psa_status_t gpt_entry_rename(const struct efi_guid_t *guid,
+ const char name[GPT_ENTRY_NAME_LENGTH]);
+
+/**
+ * \brief Changes the type of a partition.
+ *
+ * \param[in] guid Entry to update.
+ * \param[in] type New type GUID.
+ *
+ * \retval PSA_SUCCESS Success.
+ * \retval PSA_ERROR_STORAGE_FAILURE I/O error.
+ * \retval PSA_ERROR_INVALID_ARGUMENT \p type is the null GUID.
+ * \retval PSA_ERROR_DOES_NOT_EXIST No entry found with the provided GUID.
+ */
+__attribute__((nonnull(1,2)))
+psa_status_t gpt_entry_change_type(const struct efi_guid_t *guid,
+ const struct efi_guid_t *type);
+
+/**
+ * \brief Adds attributes to a partition entry.
+ *
+ * \param[in] guid Entry to modify.
+ * \param[in] attr Attributes to add.
+ *
+ * \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.
+ */
+__attribute__((nonnull(1)))
+psa_status_t gpt_attr_add(const struct efi_guid_t *guid, const uint64_t attr);
+
+/**
+ * \brief Removes attributes from a partition entry.
+ *
+ * \param[in] guid Entry to modify.
+ * \param[in] attr Attributes to remove.
+ *
+ * \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.
+ */
+__attribute__((nonnull(1)))
+psa_status_t gpt_attr_remove(const struct efi_guid_t *guid, const uint64_t attr);
+
+/**
+ * \brief Sets attributes for a partition entry.
+ *
+ * \param[in] guid Entry to modify.
+ * \param[in] attr Attributes to set.
+ *
+ * \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.
+ */
+__attribute__((nonnull(1)))
+psa_status_t gpt_attr_set(const struct efi_guid_t *guid, const uint64_t attr);
+
/**
* \brief Reads the GPT header from the second block (LBA 1).
*
diff --git a/lib/gpt/inc/gpt_flash.h b/lib/gpt/inc/gpt_flash.h
index 2b43390c1..ada5f6932 100644
--- a/lib/gpt/inc/gpt_flash.h
+++ b/lib/gpt/inc/gpt_flash.h
@@ -57,13 +57,48 @@ __attribute__((nonnull(2)))
typedef ssize_t (*gpt_flash_read_t)(uint64_t lba,
void *buf);
+/**
+ * \brief Function that writes to a logical block address.
+ *
+ * \param[in] lba Logical block address to write to.
+ * \param[in] buf Buffer to write from. Must be at least the size of an LBA.
+ *
+ * \return Number of bytes written on success or a negative error code on failure.
+ * \retval GPT_FLASH_NOT_INIT The flash driver has not been initialised.
+ * \retval GPT_FLASH_UNAVAILABLE The flash driver is unavailable.
+ * \retval GPT_FLASH_BAD_PARAM \p lba is not a valid address (for example larger than the flash size).
+ * \retval GPT_FLASH_GENERIC_ERROR Unspecified error.
+ */
+__attribute__((nonnull(2)))
+typedef ssize_t (*gpt_flash_write_t)(uint64_t lba,
+ const void *buf);
+
+/**
+ * \brief Function that erases consecutive logical blocks.
+ *
+ * \param[in] lba Starting logical block address.
+ * \param[in] num_blocks Number of blocks to erase.
+ *
+ * \return Number of blocks erased on success or a negative error code on failure.
+ * \retval GPT_FLASH_NOT_INIT The flash driver has not been initialised.
+ * \retval GPT_FLASH_UNAVAILABLE The flash driver is unavailable.
+ * \retval GPT_FLASH_BAD_PARAM \p num_blocks is zero.
+ * \retval GPT_FLASH_BAD_PARAM One of the requested blocks is invalid (for example,
+ * \p lba + \p num_blocks exceeds the flash size).
+ * \retval GPT_FLASH_BAD_PARAM \p lba is not a valid address (for example larger than the flash size).
+ * \retval GPT_FLASH_GENERIC_ERROR Unspecified error.
+ */
+typedef ssize_t (*gpt_flash_erase_t)(uint64_t lba, size_t num_blocks);
+
/**
* \brief Interface for interacting with the flash driver.
*/
struct gpt_flash_driver_t {
- gpt_flash_init_t init; /**< Flash initialisation routine. */
- gpt_flash_uninit_t uninit; /**< Flash deinitialisation routine. */
- gpt_flash_read_t read; /**< Routine used to read a logical block. */
+ gpt_flash_init_t init; /**< Flash initialisation routine. */
+ gpt_flash_uninit_t uninit; /**< Flash deinitialisation routine. */
+ gpt_flash_read_t read; /**< Routine used to read a logical block. */
+ gpt_flash_write_t write; /**< Routine used to write a logical block. */
+ gpt_flash_erase_t erase; /**< Routine used to erase logical blocks. */
};
#ifdef __cplusplus
diff --git a/lib/gpt/src/gpt.c b/lib/gpt/src/gpt.c
index 1662b81c2..2cfcdff07 100644
--- a/lib/gpt/src/gpt.c
+++ b/lib/gpt/src/gpt.c
@@ -14,6 +14,7 @@
#include "gpt_flash.h"
#include "tfm_log.h"
#include "efi_guid_structs.h"
+#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)
@@ -146,6 +147,15 @@ typedef bool (*gpt_entry_cmp_t)(const struct gpt_entry_t *, const void *);
/* The LBA for the backup table */
static uint64_t backup_gpt_lba = 0;
+/* The LBA for the partition array for the backup table. Rather than storing
+ * the entire table header in memory, just this is stored so that updates can
+ * be done without reading from flash
+ */
+static uint64_t backup_gpt_array_lba = 0;
+
+/* CRC for backup header. Because the LBAs differ, so too will the CRC */
+static uint32_t backup_crc32 = 0;
+
/* The flash driver, used to perform I/O */
static struct gpt_flash_driver_t *plat_flash_driver = NULL;
@@ -165,6 +175,9 @@ static uint8_t lba_buf[TFM_GPT_BLOCK_SIZE] = {0};
*/
static uint64_t cached_lba = 0;
+/* True if write was buffered */
+static bool write_buffered = false;
+
/* Helper function prototypes */
__attribute__((unused))
static void print_guid(struct efi_guid_t guid);
@@ -174,7 +187,9 @@ __attribute__((unused))
static psa_status_t unicode_to_ascii(const char *unicode, char *ascii);
static inline uint64_t partition_entry_lba(const struct gpt_t *table,
uint32_t array_index);
+static inline uint64_t partition_array_last_lba(const struct gpt_t *table);
static inline uint64_t gpt_entry_per_lba_count(void);
+static inline void swap_headers(const struct gpt_header_t *src, struct gpt_header_t *dst);
static psa_status_t count_used_partitions(const struct gpt_t *table,
uint32_t *num_used);
static inline void parse_entry(struct gpt_entry_t *entry,
@@ -184,6 +199,15 @@ static psa_status_t read_entry_from_flash(const struct gpt_t *table,
uint32_t array_index,
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_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,
+ bool no_header_update);
+static psa_status_t write_header_to_flash(const struct gpt_t *table);
+static psa_status_t write_headers_to_flash(void);
+static psa_status_t update_header(uint32_t num_partitions);
static psa_status_t find_gpt_entry(const struct gpt_t *table,
gpt_entry_cmp_t compare,
const void *attr,
@@ -241,16 +265,146 @@ psa_status_t gpt_entry_read_by_type(const struct efi_guid_t *type,
return PSA_SUCCESS;
}
+psa_status_t gpt_entry_rename(const struct efi_guid_t *guid, const char name[GPT_ENTRY_NAME_LENGTH])
+{
+ if (name[0] == '\0') {
+ return PSA_ERROR_INVALID_ARGUMENT;
+ }
+
+ struct gpt_entry_t cached_entry;
+ uint32_t cached_index;
+ const 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;
+ }
+
+ /* NOOP if no name change. Prevents header update. */
+ if (memcmp(cached_entry.name, name, GPT_ENTRY_NAME_LENGTH) == 0) {
+ return PSA_SUCCESS;
+ }
+
+ memcpy(cached_entry.name, name, GPT_ENTRY_NAME_LENGTH);
+ cached_entry.name[GPT_ENTRY_NAME_LENGTH - 1] = '\0';
+ cached_entry.name[GPT_ENTRY_NAME_LENGTH - 2] = '\0';
+ return write_entry(cached_index, &cached_entry, false);
+}
+
+psa_status_t gpt_entry_change_type(const struct efi_guid_t *guid, const struct efi_guid_t *type)
+{
+ struct efi_guid_t null_type = NULL_GUID;
+ if (efi_guid_cmp(&null_type, type) == 0) {
+ ERROR("Cannot set type to null-GUID; delete instead\n");
+ return PSA_ERROR_INVALID_ARGUMENT;
+ }
+
+ struct gpt_entry_t cached_entry;
+ uint32_t cached_index;
+ const 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;
+ }
+
+ cached_entry.partition_type = *type;
+
+ return write_entry(cached_index, &cached_entry, false);
+}
+
+psa_status_t gpt_attr_add(const struct efi_guid_t *guid, const uint64_t attr)
+{
+ /* This quick check prevents I/O from happening for a no-op */
+ if (attr == 0) {
+ return PSA_SUCCESS;
+ }
+
+ struct gpt_entry_t cached_entry;
+ uint32_t cached_index;
+ const 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;
+ }
+
+ cached_entry.attr |= attr;
+
+ return write_entry(cached_index, &cached_entry, false);
+}
+
+psa_status_t gpt_attr_remove(const struct efi_guid_t *guid, const uint64_t attr)
+{
+ /* This quick check prevents I/O from happening for a no-op */
+ if (attr == 0) {
+ return PSA_SUCCESS;
+ }
+
+ struct gpt_entry_t cached_entry;
+ uint32_t cached_index;
+ const 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;
+ }
+
+ cached_entry.attr &= ~(attr);
+
+ return write_entry(cached_index, &cached_entry, false);
+}
+
+psa_status_t gpt_attr_set(const struct efi_guid_t *guid, const uint64_t attr)
+{
+ struct gpt_entry_t cached_entry;
+ uint32_t cached_index;
+ const 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;
+ }
+
+ cached_entry.attr = attr;
+
+ return write_entry(cached_index, &cached_entry, false);
+}
+
/* Initialises GPT from first block. */
psa_status_t gpt_init(struct gpt_flash_driver_t *flash_driver, uint64_t max_partitions)
{
cached_lba = 0;
+ write_buffered = false;
if (max_partitions < GPT_MIN_PARTITIONS) {
ERROR("Minimum number of partitions is %d\n", GPT_MIN_PARTITIONS);
return PSA_ERROR_INVALID_ARGUMENT;
}
- if (flash_driver->read == NULL) {
+ if (flash_driver->read == NULL ||
+ flash_driver->write == NULL ||
+ flash_driver->erase == NULL)
+ {
ERROR("I/O functions must be defined\n");
return PSA_ERROR_INVALID_ARGUMENT;
}
@@ -303,6 +457,7 @@ psa_status_t gpt_init(struct gpt_flash_driver_t *flash_driver, uint64_t max_part
if (ret != PSA_SUCCESS) {
goto fail_load;
}
+ backup_gpt_array_lba = backup_gpt.header.array_lba;
} else {
WARN("Backup GPT location is unknown!\n");
}
@@ -314,7 +469,9 @@ fail_load:
plat_flash_driver = NULL;
plat_max_partitions = 0;
backup_gpt_lba = 0;
+ backup_gpt_array_lba = 0;
cached_lba = 0;
+ write_buffered = false;
return ret;
}
@@ -324,6 +481,11 @@ psa_status_t gpt_uninit(void)
psa_status_t ret = PSA_SUCCESS;
if (plat_flash_driver) {
+ /* Flush the in-memory buffer */
+ if (write_buffered) {
+ ret = flush_lba_buf();
+ }
+
/* Uninitialise driver if function provided */
if (plat_flash_driver->uninit != NULL) {
if (plat_flash_driver->uninit() != 0) {
@@ -336,7 +498,9 @@ psa_status_t gpt_uninit(void)
plat_flash_driver = NULL;
plat_max_partitions = 0;
backup_gpt_lba = 0;
+ backup_gpt_array_lba = 0;
cached_lba = 0;
+ write_buffered = false;
return ret;
}
@@ -422,6 +586,47 @@ static psa_status_t find_gpt_entry(const struct gpt_t *table,
return io_failure ? PSA_ERROR_STORAGE_FAILURE : PSA_ERROR_DOES_NOT_EXIST;
}
+/* Updates the header of the GPT based on new number of partitions */
+static psa_status_t update_header(uint32_t num_partitions)
+{
+ primary_gpt.num_used_partitions = num_partitions;
+ struct gpt_header_t *header = &(primary_gpt.header);
+
+ /* 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);
+ 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);
+ }
+ header->array_crc = crc;
+
+ /* Calculate new CRC32 for primary header */
+ header->header_crc = 0;
+ header->header_crc = efi_soft_crc32_update(0, (uint8_t *)header, GPT_HEADER_SIZE);
+
+ /* Calculate new CRC32 for backup header */
+ struct gpt_header_t backup_header = {0};
+ swap_headers(header, &backup_header);
+ backup_header.header_crc = 0;
+ backup_crc32 = efi_soft_crc32_update(0, (uint8_t *)&backup_header, GPT_HEADER_SIZE);
+
+ /* Write headers */
+ const psa_status_t ret = write_headers_to_flash();
+ if (ret != PSA_SUCCESS) {
+ ERROR("Unable to write headers to flash\n");
+ return ret;
+ }
+
+ return PSA_SUCCESS;
+}
+
/* Load MBR from flash */
static psa_status_t mbr_load(struct mbr_t *mbr)
{
@@ -452,10 +657,16 @@ static psa_status_t mbr_load(struct mbr_t *mbr)
*/
static psa_status_t read_from_flash(uint64_t required_lba)
{
- ssize_t ret;
-
if (required_lba != cached_lba) {
- ret = plat_flash_driver->read(required_lba, lba_buf);
+ if (write_buffered && cached_lba != 0) {
+ psa_status_t ret = flush_lba_buf();
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+ }
+ write_buffered = false;
+
+ ssize_t ret = plat_flash_driver->read(required_lba, lba_buf);
if (ret != TFM_GPT_BLOCK_SIZE) {
ERROR("Unable to read from flash at block 0x%08x%08x\n",
(uint32_t)(required_lba >> 32),
@@ -477,6 +688,14 @@ static uint64_t inline partition_entry_lba(const struct gpt_t *table,
return table->header.array_lba + (array_index / gpt_entry_per_lba_count());
}
+/* Returns the last LBA used by the partition entry array */
+static uint64_t inline partition_array_last_lba(const struct gpt_t *table)
+{
+ return (table->num_used_partitions == 0 ?
+ table->header.array_lba :
+ partition_entry_lba(table, table->num_used_partitions - 1));
+}
+
/* Returns the number of partition entries used in the array, assuming the
* array is not sparse
*/
@@ -539,6 +758,198 @@ static psa_status_t read_table_from_flash(struct gpt_t *table, bool is_primary)
return PSA_SUCCESS;
}
+/* Writes the in-memory LBA buffer to flash, taking care of multiple writes if
+ * needed.
+ */
+static psa_status_t flush_lba_buf(void)
+{
+ /* Prevent recursive calls, as the various writes below may attempt to
+ * flush, particularly those making multiple writes
+ */
+ static bool in_flush = false;
+ if (in_flush) {
+ return PSA_SUCCESS;
+ }
+ in_flush = true;
+ write_buffered = false;
+ psa_status_t ret = PSA_SUCCESS;
+
+ /* Commit to flash what is in the buffer. Also update the backup table if
+ * the cached LBA was part of the primary table (or vise-versa)
+ */
+ uint64_t array_size = partition_array_last_lba(&primary_gpt) - primary_gpt.header.array_lba + 1;
+
+ if (cached_lba == PRIMARY_GPT_LBA || (backup_gpt_lba != 0 && cached_lba == backup_gpt_lba)) {
+ /* Write both backup and primary headers */
+ ret = write_headers_to_flash();
+ } else if (PRIMARY_GPT_ARRAY_LBA <= cached_lba &&
+ cached_lba <= partition_array_last_lba(&primary_gpt))
+ {
+ /* Primary array entry. Write to backup and primary array */
+ ret = write_entries_to_flash(cached_lba - PRIMARY_GPT_ARRAY_LBA, false);
+ } else if (backup_gpt_array_lba != 0 &&
+ backup_gpt_array_lba <= cached_lba &&
+ cached_lba <= backup_gpt_array_lba + array_size - 1)
+ {
+ /* Backup array entry. Write to backup and primary array */
+ ret = write_entries_to_flash(cached_lba - backup_gpt_array_lba, false);
+ } else {
+ /* Shouldn't be possible */
+ ERROR("Unknown data in LBA cache, discarding\n");
+ }
+
+ in_flush = false;
+ return ret;
+}
+
+/* Write to the flash at the specified LBA */
+static psa_status_t write_to_flash(uint64_t lba)
+{
+ 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) {
+ ERROR("Unable to program flash at LBA 0x%08x%08x\n",
+ (uint32_t)(lba >> 32),
+ (uint32_t)lba);
+ return PSA_ERROR_STORAGE_FAILURE;
+ }
+
+ return PSA_SUCCESS;
+}
+
+/* Writes the in-memory buffer to both the primary and backup partition arrays.
+ * This should only be used when it is certain that the cached lba is part of
+ * either the primary or backup partition array
+ */
+static psa_status_t write_entries_to_flash(uint32_t lbas_into_array, bool no_header_update)
+{
+ psa_status_t ret;
+
+ if (backup_gpt_array_lba != 0) {
+ ret = write_to_flash(backup_gpt_array_lba + lbas_into_array);
+ if (ret != PSA_SUCCESS) {
+ ERROR("Unable to write entry to backup partition array\n");
+ return ret;
+ }
+ } else {
+ WARN("Backup array LBA unknown!\n");
+ }
+
+ ret = write_to_flash(PRIMARY_GPT_ARRAY_LBA + lbas_into_array);
+ if (ret != PSA_SUCCESS) {
+ ERROR("Unable to write entry to primary partition array\n");
+ return ret;
+ }
+
+ /* Update the header unless the user specifies not to, This might be useful
+ * if it is known that multiple entries are being written, such as on removal
+ * or defragmentation operations
+ */
+ if (!no_header_update) {
+ return update_header(primary_gpt.num_used_partitions);
+ }
+
+ return PSA_SUCCESS;
+}
+
+/* Writes a GPT entry to flash or the in-memory buffer. The buffer is flushed
+ * to both the primary and backup partition entry arrays ocassionally. When the
+ * buffer is flushed, the header is updated unless no_header_update is true.
+ */
+static psa_status_t write_entry(uint32_t array_index,
+ const struct gpt_entry_t *entry,
+ bool no_header_update)
+{
+ /* Use this for a very simple, very dumb buffering heuristic. Flush every
+ * time an LBA's worth of entries have been written (flush every nth
+ * operation).
+ */
+ static uint32_t num_writes = 0;
+
+ /* First, ensure the entry is part of the buffered block. In most cases,
+ * this will be a no-op
+ */
+ psa_status_t ret = read_from_flash(partition_entry_lba(&primary_gpt, array_index));
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+
+ /* 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);
+
+ /* Write on every nth operation. */
+ if (++num_writes == gpt_entry_per_lba_count()) {
+ /* Write the buffer to flash */
+ num_writes = 0;
+ write_buffered = false;
+
+ ret = write_entries_to_flash(cached_lba - PRIMARY_GPT_ARRAY_LBA, no_header_update);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+ } else {
+ write_buffered = true;
+ }
+
+ return PSA_SUCCESS;
+}
+
+/* Writes GPT header to flash. Returns PSA_SUCCESS on success or a PSA error on failure */
+static psa_status_t write_header_to_flash(const struct gpt_t *table)
+{
+ /* Ensure the in-memory LBA buffer has the header. Because the header is
+ * also in memory, there is no need to read it again before writing.
+ */
+ 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);
+ memcpy(lba_buf, temp_buf, GPT_HEADER_SIZE);
+
+ return ret;
+}
+
+/* Writes GPT headers for backup and primary tables to flash. */
+static psa_status_t write_headers_to_flash(void)
+{
+ /* Backup table first, then primary */
+ struct gpt_t backup_gpt;
+ swap_headers(&(primary_gpt.header), &(backup_gpt.header));
+ backup_gpt.header.header_crc = backup_crc32;
+ psa_status_t ret = write_header_to_flash(&backup_gpt);
+ if (ret != PSA_SUCCESS) {
+ ERROR("Unable to write backup GPT header\n");
+ return ret;
+ }
+
+ ret = write_header_to_flash(&primary_gpt);
+ if (ret != PSA_SUCCESS) {
+ ERROR("Unable to write primary GPT header\n");
+ }
+
+ return ret;
+}
+
+/* Copies one header to another and swaps the fields referring to self
+ * and alternate headers. This is useful for primary to backup copies
+ * and vice-versa.
+ */
+static inline void swap_headers(const struct gpt_header_t *src, struct gpt_header_t *dst)
+{
+ memcpy(dst, src, GPT_HEADER_SIZE);
+ dst->backup_lba = src->current_lba;
+ dst->current_lba = src->backup_lba;
+ dst->array_lba = (src->current_lba == PRIMARY_GPT_LBA ?
+ backup_gpt_array_lba :
+ primary_gpt.header.array_lba);
+}
+
/* Converts unicode string to valid ascii */
static psa_status_t unicode_to_ascii(const char *unicode, char *ascii)
{
diff --git a/lib/gpt/unittests/gpt/test_gpt.c b/lib/gpt/unittests/gpt/test_gpt.c
index d6671f3fc..1121f7c73 100644
--- a/lib/gpt/unittests/gpt/test_gpt.c
+++ b/lib/gpt/unittests/gpt/test_gpt.c
@@ -10,6 +10,7 @@
#include "unity.h"
+#include "mock_efi_soft_crc.h"
#include "mock_tfm_log.h"
#include "mock_tfm_vprintf.h"
@@ -133,12 +134,16 @@ struct gpt_header_t {
static void register_mocked_read(void *buf, size_t num_bytes);
static ssize_t test_driver_read(uint64_t lba, void *buf);
+static ssize_t test_driver_write(uint64_t lba, const void *buf);
+static ssize_t test_driver_erase(uint64_t lba, size_t num_blocks);
/* LBA driver used in test module */
static struct gpt_flash_driver_t mock_driver = {
.init = NULL,
.uninit = NULL,
.read = test_driver_read,
+ .write = test_driver_write,
+ .erase = test_driver_erase,
};
/* Valid MBR. Only signature is required to be valid */
@@ -227,6 +232,17 @@ static ssize_t test_driver_read(uint64_t lba, void *buf)
return TEST_BLOCK_SIZE;
}
+/* Driver function that always succeeds in writing all data */
+static ssize_t test_driver_write(uint64_t lba, const void *buf)
+{
+ return TEST_BLOCK_SIZE;
+}
+
+static ssize_t test_driver_erase(uint64_t lba, size_t num_blocks)
+{
+ return num_blocks;
+}
+
/* Creates backup table from test table and registers a read for it */
static void setup_backup_gpt(void)
{
@@ -285,6 +301,9 @@ void setUp(void)
test_mbr.partitions[0].os_type = TEST_MBR_TYPE_GPT;
+ /* Any time this is called, return the same number and ignore the arguments */
+ efi_soft_crc32_update_IgnoreAndReturn(test_header.header_crc);
+
/* Ignore all logging calls */
tfm_log_Ignore();
@@ -337,6 +356,174 @@ void test_gpt_init_should_failWhenFlashDriverNotFullyDefined(void)
mock_driver.read = NULL;
TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_init(&mock_driver, TEST_MAX_PARTITIONS));
mock_driver.read = read_fn;
+
+ gpt_flash_write_t write_fn = mock_driver.write;
+ mock_driver.write = NULL;
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_init(&mock_driver, TEST_MAX_PARTITIONS));
+ mock_driver.write = write_fn;
+
+ gpt_flash_erase_t erase_fn = mock_driver.erase;
+ mock_driver.erase = NULL;
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_init(&mock_driver, TEST_MAX_PARTITIONS));
+ mock_driver.erase = erase_fn;
+}
+
+void test_gpt_attr_set_should_setAttributes(void)
+{
+ /* Start with a populated GPT */
+ setup_valid_gpt();
+
+ /* Entries are read */
+ register_mocked_read(&test_partition_array, sizeof(test_partition_array));
+
+ struct efi_guid_t guid = test_partition_array[0].guid;
+ TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_attr_set(&guid, 0x1));
+}
+
+void test_gpt_attr_set_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_attr_set(&non_existing, 0x1));
+}
+
+void test_gpt_attr_remove_should_removeAttributes(void)
+{
+ /* Start with a populated GPT */
+ struct gpt_entry_t *test_entry = &(test_partition_array[0]);
+ uint64_t test_attr = 0x1;
+ test_entry->attr = test_attr;
+ setup_valid_gpt();
+
+ /* First entry is read */
+ register_mocked_read(&test_partition_array, sizeof(test_partition_array));
+
+ struct efi_guid_t test_guid = test_entry->guid;
+ TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_attr_set(&test_guid, test_attr));
+}
+
+void test_gpt_attr_remove_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_attr_remove(&non_existing, 0x1));
+}
+
+void test_gpt_attr_add_should_addAttributes(void)
+{
+ /* Start with a populated GPT */
+ struct gpt_entry_t *test_entry = &(test_partition_array[0]);
+ setup_valid_gpt();
+
+ /* First entry is read */
+ register_mocked_read(&test_partition_array, sizeof(test_partition_array));
+
+ struct efi_guid_t test_guid = test_entry->guid;
+ TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_attr_add(&test_guid, 0x1));
+}
+
+void test_gpt_attr_add_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_attr_add(&non_existing, 0x1));
+}
+
+void test_gpt_entry_change_type_should_setNewType(void)
+{
+ /* Start with a populated GPT */
+ struct gpt_entry_t *test_entry = &(test_partition_array[0]);
+ setup_valid_gpt();
+
+ /* First entry is read */
+ register_mocked_read(&test_partition_array, sizeof(test_partition_array));
+
+ struct efi_guid_t test_guid = test_entry->guid;
+
+ /* 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);
+ TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_entry_change_type(&test_guid, &new_type));
+}
+
+void test_gpt_entry_change_type_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;
+ struct efi_guid_t new_type = MAKE_EFI_GUID(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));
+}
+
+void test_gpt_entry_change_type_should_failWhenSettingTypeToNullGuid(void)
+{
+ setup_valid_gpt();
+
+ struct gpt_entry_t *test_entry = &(test_partition_array[0]);
+
+ /* First entry is read */
+ register_mocked_read(&test_partition_array, sizeof(test_partition_array));
+
+ struct efi_guid_t test_guid = test_entry->guid;
+ struct efi_guid_t new_type = NULL_GUID;
+
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_change_type(&test_guid, &new_type));
+}
+
+void test_gpt_entry_rename_should_renameEntry(void)
+{
+ /* Start with a populated GPT */
+ struct gpt_entry_t *test_entry = &(test_partition_array[0]);
+ setup_valid_gpt();
+
+ /* First entry is read */
+ register_mocked_read(&test_partition_array, sizeof(test_partition_array));
+
+ char new_name[GPT_ENTRY_NAME_LENGTH] = {'\0'};
+ new_name[0] = 'a';
+ struct efi_guid_t test_guid = test_entry->guid;
+ TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_entry_rename(&test_guid, new_name));
+}
+
+void test_gpt_entry_rename_should_failWhenNameIsEmpty(void)
+{
+ /* Start with a populated GPT */
+ struct gpt_entry_t *test_entry = &(test_partition_array[0]);
+ setup_valid_gpt();
+
+ /* Try to change name to an empty string */
+ struct efi_guid_t test_guid = test_entry->guid;
+ char name[GPT_ENTRY_NAME_LENGTH] = {'\0'};
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_rename(&test_guid, name));
+}
+
+void test_gpt_entry_rename_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;
+ char new_name[GPT_ENTRY_NAME_LENGTH] = {'\0'};
+ new_name[0] = 'a';
+ TEST_ASSERT_EQUAL(PSA_ERROR_DOES_NOT_EXIST, gpt_entry_rename(&non_existing, new_name));
}
void test_gpt_entry_read_should_populateEntry(void)
diff --git a/lib/gpt/unittests/gpt/utcfg.cmake b/lib/gpt/unittests/gpt/utcfg.cmake
index 37d72d138..620d15b4c 100644
--- a/lib/gpt/unittests/gpt/utcfg.cmake
+++ b/lib/gpt/unittests/gpt/utcfg.cmake
@@ -15,12 +15,14 @@ set(UNIT_TEST_SUITE ${CMAKE_CURRENT_LIST_DIR}/test_gpt.c)
list(APPEND UNIT_TEST_INCLUDE_DIRS ${TFM_ROOT_DIR}/interface/include)
list(APPEND UNIT_TEST_INCLUDE_DIRS ${TFM_ROOT_DIR}/lib/efi_guid/inc)
list(APPEND UNIT_TEST_INCLUDE_DIRS ${TFM_ROOT_DIR}/lib/gpt/inc)
+list(APPEND UNIT_TEST_INCLUDE_DIRS ${TFM_ROOT_DIR}/lib/ext/efi_soft_crc/inc)
list(APPEND UNIT_TEST_INCLUDE_DIRS ${TFM_ROOT_DIR}/lib/tfm_log/inc)
list(APPEND UNIT_TEST_INCLUDE_DIRS ${TFM_ROOT_DIR}/lib/tfm_vprintf/inc)
# Headers to be mocked
list(APPEND MOCK_HEADERS ${TFM_ROOT_DIR}/lib/tfm_log/inc/tfm_log.h)
list(APPEND MOCK_HEADERS ${TFM_ROOT_DIR}/lib/tfm_vprintf/inc/tfm_vprintf.h)
+list(APPEND MOCK_HEADERS ${TFM_ROOT_DIR}/lib/ext/efi_soft_crc/inc/efi_soft_crc.h)
# Compile-time definitions
list(APPEND UNIT_TEST_COMPILE_DEFS LOG_LEVEL=LOG_LEVEL_VERBOSE)
@@ -0,0 +1,374 @@
From 7a453edd736c919eccc40eec567e8e97c0597bba Mon Sep 17 00:00:00 2001
From: Frazer Carsley <frazer.carsley@arm.com>
Date: Tue, 30 Dec 2025 09:13:07 +0000
Subject: [PATCH] lib: gpt: Added operation to move entry
The operation to move or resize an entry is not just a metadata change
and actually moves the data the partition entry points to as well.
Change-Id: Id6b98dcb3d77366db19e453acfbe4af59697eaf6
Signed-off-by: Frazer Carsley <frazer.carsley@arm.com>
Upstream-Status: Backport [cbab5fd7757e9c06952c15ac7d30b0809b21406c]
---
lib/gpt/inc/gpt.h | 19 ++++
lib/gpt/src/gpt.c | 156 ++++++++++++++++++++++++++++++-
lib/gpt/unittests/gpt/test_gpt.c | 128 +++++++++++++++++++++++++
3 files changed, 301 insertions(+), 2 deletions(-)
diff --git a/lib/gpt/inc/gpt.h b/lib/gpt/inc/gpt.h
index 6e7bb360e..85f9bed9c 100644
--- a/lib/gpt/inc/gpt.h
+++ b/lib/gpt/inc/gpt.h
@@ -151,6 +151,25 @@ psa_status_t gpt_attr_remove(const struct efi_guid_t *guid, const uint64_t attr)
__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.
+ *
+ * \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(const struct efi_guid_t *guid,
+ const uint64_t start,
+ const uint64_t end);
+
/**
* \brief Reads the GPT header from the second block (LBA 1).
*
diff --git a/lib/gpt/src/gpt.c b/lib/gpt/src/gpt.c
index 2cfcdff07..40b73f3e9 100644
--- a/lib/gpt/src/gpt.c
+++ b/lib/gpt/src/gpt.c
@@ -214,6 +214,10 @@ 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_partition(const uint64_t old_lba,
+ const uint64_t new_lba,
+ const uint64_t num_blocks);
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);
@@ -391,6 +395,111 @@ psa_status_t gpt_attr_set(const struct efi_guid_t *guid, const uint64_t attr)
return write_entry(cached_index, &cached_entry, false);
}
+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 * primary_gpt.header.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 write_entry(cached_index, &cached_entry, false);
+}
+
/* Initialises GPT from first block. */
psa_status_t gpt_init(struct gpt_flash_driver_t *flash_driver, uint64_t max_partitions)
{
@@ -586,6 +695,49 @@ static psa_status_t find_gpt_entry(const struct gpt_t *table,
return io_failure ? PSA_ERROR_STORAGE_FAILURE : PSA_ERROR_DOES_NOT_EXIST;
}
+/* 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 psa_status_t ret = read_from_flash(old_lba);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+
+ return write_to_flash(new_lba);
+}
+
+/* Moves a partition's data to start from one logical block to another */
+static psa_status_t move_partition(const uint64_t old_lba,
+ const uint64_t new_lba,
+ const uint64_t num_blocks)
+{
+ if (old_lba == new_lba) {
+ return PSA_SUCCESS;
+ }
+
+ if (old_lba < new_lba) {
+ /* 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);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+ }
+ } else {
+ /* 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);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+
+ write_buffered = false;
+
+ return PSA_SUCCESS;
+}
+
/* Updates the header of the GPT based on new number of partitions */
static psa_status_t update_header(uint32_t num_partitions)
{
@@ -794,8 +946,8 @@ static psa_status_t flush_lba_buf(void)
/* Backup array entry. Write to backup and primary array */
ret = write_entries_to_flash(cached_lba - backup_gpt_array_lba, false);
} else {
- /* Shouldn't be possible */
- ERROR("Unknown data in LBA cache, discarding\n");
+ /* Some other LBA is cached, possibly data. Write it anyway */
+ ret = write_to_flash(cached_lba);
}
in_flush = false;
diff --git a/lib/gpt/unittests/gpt/test_gpt.c b/lib/gpt/unittests/gpt/test_gpt.c
index 1121f7c73..251b04ee9 100644
--- a/lib/gpt/unittests/gpt/test_gpt.c
+++ b/lib/gpt/unittests/gpt/test_gpt.c
@@ -368,6 +368,134 @@ void test_gpt_init_should_failWhenFlashDriverNotFullyDefined(void)
mock_driver.erase = erase_fn;
}
+void test_gpt_entry_move_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));
+
+ /* Move the partition. Read each block to then write. It doesn't matter what
+ * the data is
+ */
+ char unused_read_data = 'X';
+ register_mocked_read(&unused_read_data, sizeof(unused_read_data));
+
+ /* 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(
+ &test_guid,
+ TEST_GPT_THIRD_PARTITION_END + 1,
+ TEST_GPT_THIRD_PARTITION_END + 1));
+}
+
+void test_gpt_entry_move_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(
+ &non_existing,
+ TEST_GPT_THIRD_PARTITION_END + 1,
+ TEST_GPT_THIRD_PARTITION_END + 1));
+}
+
+void test_gpt_entry_move_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(
+ &test_guid,
+ TEST_GPT_THIRD_PARTITION_END + 2,
+ TEST_GPT_THIRD_PARTITION_END + 1));
+}
+
+void test_gpt_entry_move_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(
+ &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(
+ &test_guid,
+ TEST_GPT_FIRST_PARTITION_START + 1,
+ TEST_GPT_SECOND_PARTITION_END));
+
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_move(
+ &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(
+ &test_guid,
+ TEST_GPT_FIRST_PARTITION_START + 1,
+ TEST_GPT_FIRST_PARTITION_START + 1));
+}
+
+void test_gpt_entry_move_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(
+ &test_guid,
+ TEST_GPT_THIRD_PARTITION_END + 1,
+ TEST_DISK_NUM_BLOCKS + 1));
+
+ /* Second, start off the disk entirely */
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_move(
+ &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(
+ &test_guid,
+ TEST_GPT_PRIMARY_LBA,
+ TEST_GPT_THIRD_PARTITION_END + 2));
+
+ /* Fourth, start in the backup header area */
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_move(
+ &test_guid,
+ TEST_GPT_BACKUP_LBA,
+ TEST_GPT_BACKUP_LBA + 1));
+}
+
void test_gpt_attr_set_should_setAttributes(void)
{
/* Start with a populated GPT */
@@ -0,0 +1,713 @@
From bec8f44a2732c20e8f69a06841637673af90c37e Mon Sep 17 00:00:00 2001
From: Frazer Carsley <frazer.carsley@arm.com>
Date: Tue, 30 Dec 2025 10:16:21 +0000
Subject: [PATCH] lib: gpt: Added ability to create and remove partitions
With this change, new GPT partitions can be created and added to the
table and old partitions can be removed. Both of these are metadata
changes and only make changes to the partition entry array and header
(primary and backup).
New partitions must be created in empty space only. The data that the
partition points to is not cleared and if desired this should be done by
the caller before or after creation.
When a partition is removed, the data it pointed to is not cleared so
this should be done by the caller if desired. After removal, that data
is considered empty space.
Change-Id: Iecccb814aaf8f48cbdd8e29b3d2b54fb5b58aae8
Signed-off-by: Frazer Carsley <frazer.carsley@arm.com>
Upstream-Status: Backport [4d69babc99917e3cc2df284a311ec37004c3ee3f]
---
lib/gpt/CMakeLists.txt | 1 +
lib/gpt/inc/gpt.h | 37 +++
lib/gpt/src/gpt.c | 249 ++++++++++++++-
lib/gpt/unittests/gpt/test_gpt.c | 284 ++++++++++++++++++
lib/gpt/unittests/gpt/utcfg.cmake | 2 +
.../include/mbedtls/mbedtls_config.h | 18 ++
6 files changed, 590 insertions(+), 1 deletion(-)
create mode 100644 lib/gpt/unittests/include/mbedtls/mbedtls_config.h
diff --git a/lib/gpt/CMakeLists.txt b/lib/gpt/CMakeLists.txt
index 25d0de9cd..5befc346f 100644
--- a/lib/gpt/CMakeLists.txt
+++ b/lib/gpt/CMakeLists.txt
@@ -23,6 +23,7 @@ target_sources(tfm_gpt
target_include_directories(tfm_gpt
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/inc>
+ $<INSTALL_INTERFACE:inc>
)
target_compile_definitions(tfm_gpt
diff --git a/lib/gpt/inc/gpt.h b/lib/gpt/inc/gpt.h
index 85f9bed9c..f04643957 100644
--- a/lib/gpt/inc/gpt.h
+++ b/lib/gpt/inc/gpt.h
@@ -170,6 +170,43 @@ psa_status_t gpt_entry_move(const struct efi_guid_t *guid,
const uint64_t start,
const uint64_t end);
+/**
+ * \brief Creates a partition entry in the table.
+ *
+ * \param[in] type Partition type.
+ * \param[in] start Starting LBA (0 uses the lowest free LBA possible).
+ * \param[in] size Size of the partition in LBAs.
+ * \param[in] attr Attributes for the partition.
+ * \param[in] name Partition name in unicode.
+ * \param[out] guid GUID populated on success for subsequent API calls.
+ *
+ * \retval PSA_SUCCESS Success.
+ * \retval PSA_ERROR_STORAGE_FAILURE I/O error.
+ * \retval PSA_ERROR_INSUFFICIENT_STORAGE Maximum number of partitions reached.
+ * \retval PSA_ERROR_INVALID_ARGUMENT Partition would extend beyond the flash.
+ * \retval PSA_ERROR_INVALID_ARGUMENT New entry would overlap an existing partition, the name is empty,
+ * or \p size is zero.
+ */
+__attribute__((nonnull(1,5,6)))
+psa_status_t gpt_entry_create(const struct efi_guid_t *type,
+ const uint64_t start,
+ const uint64_t size,
+ const uint64_t attr,
+ const char name[GPT_ENTRY_NAME_LENGTH],
+ struct efi_guid_t *guid);
+
+/**
+ * \brief Removes a partition entry from the table.
+ *
+ * \param[in] guid Entry to remove.
+ *
+ * \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.
+ */
+__attribute__((nonnull(1)))
+psa_status_t gpt_entry_remove(const struct efi_guid_t *guid);
+
/**
* \brief Reads the GPT header from the second block (LBA 1).
*
diff --git a/lib/gpt/src/gpt.c b/lib/gpt/src/gpt.c
index 40b73f3e9..676f04fd6 100644
--- a/lib/gpt/src/gpt.c
+++ b/lib/gpt/src/gpt.c
@@ -9,11 +9,12 @@
#include <string.h>
#include <inttypes.h>
-#include "psa/error.h"
+#include "psa/crypto.h"
#include "gpt.h"
#include "gpt_flash.h"
#include "tfm_log.h"
#include "efi_guid_structs.h"
+#include "efi_guid.h"
#include "efi_soft_crc.h"
/* This needs to be defined by the platform and is used by the GPT library as
@@ -500,6 +501,252 @@ 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_create(const struct efi_guid_t *type,
+ const uint64_t start,
+ const uint64_t size,
+ const uint64_t attr,
+ const char name[GPT_ENTRY_NAME_LENGTH],
+ struct efi_guid_t *guid)
+{
+ /* Using inequlity here handles when reading an initial GPT has more than
+ * the maximum defined number of partitions.
+ */
+ if (primary_gpt.num_used_partitions >= plat_max_partitions) {
+ ERROR("Maximum number of partitions reached\n");
+ return PSA_ERROR_INSUFFICIENT_STORAGE;
+ }
+ if (size == 0) {
+ ERROR("Cannot create entry of size 0\n");
+ return PSA_ERROR_INVALID_ARGUMENT;
+ }
+ if (name[0] == '\0') {
+ ERROR("Cannot create entry with no name\n");
+ return PSA_ERROR_INVALID_ARGUMENT;
+ }
+
+ uint64_t start_lba = start;
+ psa_status_t ret = PSA_SUCCESS;
+ if (start_lba == 0) {
+ /* Use the lowest free LBA possible. Each partition uses contiguous space,
+ * so if there is a gap between partitions, that will be shown by the end
+ * and start not being contiguous.
+ */
+ uint64_t prev_end = primary_gpt.header.first_lba;
+ for (uint32_t i = 0; i < primary_gpt.header.num_partitions; ++i) {
+ struct gpt_entry_t entry = {0};
+ ret = read_entry_from_flash(&primary_gpt, i, &entry);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+
+ if (entry.start - 1 > prev_end) {
+ start_lba = prev_end + 1;
+ break;
+ }
+ prev_end = entry.end;
+ }
+
+ if (start_lba != prev_end + 1) {
+ /* No free space */
+ ERROR("No free space on device!\n");
+ return PSA_ERROR_INSUFFICIENT_STORAGE;
+ }
+ }
+
+ /* Must fit on flash */
+ const uint64_t end_lba = start_lba + size - 1;
+ if (start_lba < primary_gpt.header.first_lba ||
+ end_lba < primary_gpt.header.first_lba ||
+ start_lba > primary_gpt.header.last_lba ||
+ end_lba > primary_gpt.header.last_lba)
+ {
+ ERROR("Requested partition would not be on disk\n");
+ return PSA_ERROR_INVALID_ARGUMENT;
+ }
+
+ /* Do not allow overlapping partitions */
+ struct gpt_entry_t entry;
+ for (uint32_t i = 0; i < primary_gpt.num_used_partitions; ++i) {
+ ret = read_entry_from_flash(&primary_gpt, i, &entry);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+
+ if ((start_lba >= entry.start && start_lba <= entry.end) ||
+ (end_lba >= entry.start && end_lba <= entry.end) ||
+ (start_lba <= entry.start && end_lba >= entry.end))
+ {
+ return PSA_ERROR_INVALID_ARGUMENT;
+ }
+ }
+
+ /* Generate the new random GUID */
+ if (efi_guid_generate_random(guid) != PSA_SUCCESS) {
+ ERROR("Unable to generate GUID\n");
+ return PSA_ERROR_STORAGE_FAILURE;
+ }
+
+ /* Set new entry's metadata */
+ struct gpt_entry_t new_entry = {0};
+ new_entry.start = start_lba;
+ new_entry.end = end_lba;
+ new_entry.attr = attr;
+ memcpy(new_entry.name, name, GPT_ENTRY_NAME_LENGTH);
+ new_entry.partition_type = *type;
+ new_entry.unique_guid = *guid;
+
+ /* Write the new entry. Skip header update as it is explicitely called
+ * below with new number of partitions
+ */
+ ret = write_entry(primary_gpt.num_used_partitions++, &new_entry, true);
+ if (ret != PSA_SUCCESS) {
+ --primary_gpt.num_used_partitions;
+ return ret;
+ }
+
+ /* Flush the buffered LBA if not done so. This will cause the header to be
+ * updated
+ */
+ if (write_buffered) {
+ /* flush_lba_buf will update the header */
+ ret = flush_lba_buf();
+ } else {
+ ret = update_header(primary_gpt.num_used_partitions);
+ }
+
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+
+ return PSA_SUCCESS;
+}
+
+psa_status_t gpt_entry_remove(const struct efi_guid_t *guid)
+{
+ 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;
+ }
+
+ /* Shuffle the remainder of the array up. This will overwrite the
+ * most previous entry.
+ *
+ * The first LBA to potentially modify is in memory. It doesn't need
+ * to be modified if the last entry in the array was moved or if it is
+ * the only LBA used by the partition array
+ */
+ if (cached_index != primary_gpt.num_used_partitions - 1 ||
+ cached_index < gpt_entry_per_lba_count())
+ {
+ /* Shuffle up the remainder of the LBA. If it was the last entry
+ * in the LBA, there is nothing to do.
+ */
+ 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);
+ }
+
+ /* If this is not the last LBA, then read the next LBA into memory and
+ * place it's first element in the final slot of the currently modified
+ * LBA. Repeat this for each LBA read.
+ *
+ * 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)
+ {
+ uint8_t array_buf[TFM_GPT_BLOCK_SIZE] = {0};
+ int read_ret = plat_flash_driver->read(i, array_buf);
+ if (read_ret != TFM_GPT_BLOCK_SIZE) {
+ ERROR("Unable to read LBA 0x%08x%08x\n",
+ (uint32_t)(i >> 32), (uint32_t)i);
+ return PSA_ERROR_STORAGE_FAILURE;
+ }
+
+ memcpy(
+ lba_buf + primary_gpt.header.entry_size * (gpt_entry_per_lba_count() - 1),
+ array_buf,
+ GPT_ENTRY_SIZE);
+
+ /* 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);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+ }
+ ret = write_to_flash(i - 1);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+
+ memmove(
+ array_buf,
+ array_buf + primary_gpt.header.entry_size,
+ 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);
+ 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 */
+ ret = update_header(primary_gpt.num_used_partitions);
+
+ return ret;
+}
+
/* Initialises GPT from first block. */
psa_status_t gpt_init(struct gpt_flash_driver_t *flash_driver, uint64_t max_partitions)
{
diff --git a/lib/gpt/unittests/gpt/test_gpt.c b/lib/gpt/unittests/gpt/test_gpt.c
index 251b04ee9..18cf2c8f3 100644
--- a/lib/gpt/unittests/gpt/test_gpt.c
+++ b/lib/gpt/unittests/gpt/test_gpt.c
@@ -10,6 +10,7 @@
#include "unity.h"
+#include "mock_efi_guid.h"
#include "mock_efi_soft_crc.h"
#include "mock_tfm_log.h"
#include "mock_tfm_vprintf.h"
@@ -368,6 +369,260 @@ void test_gpt_init_should_failWhenFlashDriverNotFullyDefined(void)
mock_driver.erase = erase_fn;
}
+void test_gpt_entry_create_should_createNewEntry(void)
+{
+ /* Add 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();
+
+ /* Each entry will be read in order to check that it doesn't overlap with
+ * any of them
+ */
+ register_mocked_read(&test_partition_array, sizeof(test_partition_array));
+
+ /* Update header. Read each entry for CRC calculation. */
+ struct gpt_entry_t new_entry = {
+ .type = NULL_GUID,
+ .start = TEST_GPT_THIRD_PARTITION_END + 1,
+ .end = TEST_GPT_THIRD_PARTITION_END + 1,
+ .attr = 0,
+ .name = "Fourth partition"
+ };
+
+ /* 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);
+ 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);
+ TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_entry_create(
+ &expected_guid,
+ new_entry.start,
+ new_entry.end - new_entry.start + 1,
+ new_entry.attr,
+ new_entry.name,
+ &new_guid));
+ TEST_ASSERT_EQUAL_MEMORY(&expected_guid, &new_guid, sizeof(new_guid));
+}
+
+void test_gpt_entry_create_should_createNewEntryNextToLastEntry(void)
+{
+ /* Add an entry, allowing the library to choose the start LBA.
+ * The GUID should be populated with something.
+ */
+ setup_valid_gpt();
+
+ /* Each entry will be read in order to check that it doesn't overlap with
+ * any of them
+ */
+ register_mocked_read(&test_partition_array, sizeof(test_partition_array));
+
+ /* Update header. Read each entry for CRC calculation. */
+ struct gpt_entry_t new_entry = {
+ .type = NULL_GUID,
+ .start = TEST_GPT_THIRD_PARTITION_END + 1,
+ .end = TEST_GPT_THIRD_PARTITION_END + 1,
+ .attr = 0,
+ .name = "Fourth partition"
+ };
+
+ /* 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);
+ 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);
+ char name[GPT_ENTRY_NAME_LENGTH] = {'\0'};
+ name[0] = 'a';
+
+ /* Ensure also the that a new GUID is assigned */
+ TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_entry_create(
+ &expected_guid,
+ 0,
+ 1,
+ new_entry.attr,
+ name,
+ &new_guid));
+ TEST_ASSERT_EQUAL_MEMORY(&expected_guid, &new_guid, sizeof(new_guid));
+}
+
+void test_gpt_entry_create_should_failToCreateEntryWhenLowestFreeLbaDoesNotHaveSpace(void)
+{
+ /* Add an entry, allowing the library to choose the start LBA.
+ * The GUID should be populated with something.
+ */
+ setup_valid_gpt();
+
+ /* Each entry will be read in order to check that it doesn't overlap with
+ * any of them
+ */
+ 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 new_guid;
+ char name[GPT_ENTRY_NAME_LENGTH] = {'\0'};
+ name[0] = 'a';
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_create(
+ &(existing_guid),
+ 0,
+ TEST_DISK_NUM_BLOCKS,
+ 0,
+ name,
+ &(new_guid)));
+}
+
+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),
+ .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),
+ .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 guid;
+ char name[GPT_ENTRY_NAME_LENGTH] = {'\0'};
+ name[0] = 'a';
+ TEST_ASSERT_EQUAL(PSA_ERROR_INSUFFICIENT_STORAGE, gpt_entry_create(
+ &type,
+ TEST_GPT_THIRD_PARTITION_END + 4,
+ 1,
+ 0,
+ name,
+ &guid));
+}
+
+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 guid;
+ char name[GPT_ENTRY_NAME_LENGTH] = {'\0'};
+ name[0] = 'a';
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_create(
+ &type,
+ TEST_GPT_THIRD_PARTITION_END + 1,
+ 1000,
+ 0,
+ name,
+ &guid));
+
+ /* Second, start off the disk entirely */
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_create(
+ &type,
+ TEST_DISK_NUM_BLOCKS + 100,
+ 1,
+ 0,
+ name,
+ &guid));
+
+ /* Third, do the same but in the header area */
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_create(
+ &type,
+ TEST_GPT_PRIMARY_LBA,
+ 1,
+ 0,
+ name,
+ &guid));
+
+ /* Fourth, start in the backup header area */
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_create(
+ &type,
+ TEST_GPT_BACKUP_LBA,
+ 1,
+ 0,
+ name,
+ &guid));
+}
+
+void test_gpt_entry_create_should_failWhenOverlapping(void)
+{
+ setup_valid_gpt();
+
+ /* 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
+ */
+ register_mocked_read(&test_partition_array, sizeof(test_partition_array));
+ struct efi_guid_t type = NULL_GUID;
+ struct efi_guid_t guid;
+ char name[GPT_ENTRY_NAME_LENGTH] = {'\0'};
+ name[0] = 'a';
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_create(
+ &type,
+ TEST_GPT_FIRST_PARTITION_START,
+ TEST_GPT_FIRST_PARTITION_END - TEST_GPT_FIRST_PARTITION_START + 1,
+ 0,
+ name,
+ &guid));
+
+ register_mocked_read(&test_partition_array, sizeof(test_partition_array));
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_create(
+ &type,
+ TEST_GPT_FIRST_PARTITION_START,
+ TEST_GPT_LAST_USABLE_LBA - TEST_GPT_FIRST_PARTITION_START,
+ 0,
+ name,
+ &guid));
+}
+
+void test_gpt_entry_create_should_failWhenNameIsEmpty(void)
+{
+ /* Start with a populated GPT */
+ setup_valid_gpt();
+
+ struct efi_guid_t type = NULL_GUID;
+ struct gpt_entry_t new_entry = {
+ .type = type,
+ .start = TEST_GPT_THIRD_PARTITION_END + 1,
+ .end = TEST_GPT_THIRD_PARTITION_END + 1,
+ .attr = 0,
+ };
+
+ /* Make an entry with an empty name */
+ char name[GPT_ENTRY_NAME_LENGTH] = {'\0'};
+ struct efi_guid_t new_guid;
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_create(
+ &type,
+ new_entry.start,
+ 1,
+ 0,
+ name,
+ &new_guid));
+}
+
+void test_gpt_entry_create_should_failWhenSizeIsZero(void)
+{
+ /* Start with a populated GPT */
+ setup_valid_gpt();
+
+ struct efi_guid_t type = NULL_GUID;
+
+ /* Make the size zero */
+ struct efi_guid_t new_guid;
+ char name[GPT_ENTRY_NAME_LENGTH] = {'\0'};
+ name[0] = 'a';
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_entry_create(
+ &type,
+ TEST_GPT_THIRD_PARTITION_END + 1,
+ 0,
+ 0,
+ name,
+ &new_guid));
+}
+
void test_gpt_entry_move_should_moveEntry(void)
{
/* Start with a populated GPT */
@@ -654,6 +909,35 @@ void test_gpt_entry_rename_should_failWhenEntryNotExisting(void)
TEST_ASSERT_EQUAL(PSA_ERROR_DOES_NOT_EXIST, gpt_entry_rename(&non_existing, new_name));
}
+void test_gpt_entry_remove_should_removeEntry(void)
+{
+ /* Start with a populated GPT */
+ setup_valid_gpt();
+
+ /* Each entry is read */
+ struct gpt_entry_t *test_entry = &(default_partition_array[TEST_DEFAULT_NUM_PARTITIONS - 1]);
+ struct efi_guid_t test_guid = test_entry->guid;
+ register_mocked_read(&test_partition_array, sizeof(test_partition_array));
+
+ TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_entry_remove(&test_guid));
+}
+
+void test_gpt_entry_remove_should_failWhenEntryNotExisting(void)
+{
+ /* Start by trying to remove from an empty table */
+ setup_empty_gpt();
+
+ struct efi_guid_t non_existing = NULL_GUID;
+ TEST_ASSERT_EQUAL(PSA_ERROR_DOES_NOT_EXIST, gpt_entry_remove(&non_existing));
+
+ /* Now, have a non-empty GPT but search for a non-existing GUID */
+ setup_valid_gpt();
+
+ /* Each entry should be read. */
+ register_mocked_read(&test_partition_array, sizeof(test_partition_array));
+ TEST_ASSERT_EQUAL(PSA_ERROR_DOES_NOT_EXIST, gpt_entry_remove(&non_existing));
+}
+
void test_gpt_entry_read_should_populateEntry(void)
{
/* Start with a populated GPT */
diff --git a/lib/gpt/unittests/gpt/utcfg.cmake b/lib/gpt/unittests/gpt/utcfg.cmake
index 620d15b4c..f3f4e7dcc 100644
--- a/lib/gpt/unittests/gpt/utcfg.cmake
+++ b/lib/gpt/unittests/gpt/utcfg.cmake
@@ -15,11 +15,13 @@ set(UNIT_TEST_SUITE ${CMAKE_CURRENT_LIST_DIR}/test_gpt.c)
list(APPEND UNIT_TEST_INCLUDE_DIRS ${TFM_ROOT_DIR}/interface/include)
list(APPEND UNIT_TEST_INCLUDE_DIRS ${TFM_ROOT_DIR}/lib/efi_guid/inc)
list(APPEND UNIT_TEST_INCLUDE_DIRS ${TFM_ROOT_DIR}/lib/gpt/inc)
+list(APPEND UNIT_TEST_INCLUDE_DIRS ${TFM_ROOT_DIR}/lib/gpt/unittests/include)
list(APPEND UNIT_TEST_INCLUDE_DIRS ${TFM_ROOT_DIR}/lib/ext/efi_soft_crc/inc)
list(APPEND UNIT_TEST_INCLUDE_DIRS ${TFM_ROOT_DIR}/lib/tfm_log/inc)
list(APPEND UNIT_TEST_INCLUDE_DIRS ${TFM_ROOT_DIR}/lib/tfm_vprintf/inc)
# Headers to be mocked
+list(APPEND MOCK_HEADERS ${TFM_ROOT_DIR}/lib/efi_guid/inc/efi_guid.h)
list(APPEND MOCK_HEADERS ${TFM_ROOT_DIR}/lib/tfm_log/inc/tfm_log.h)
list(APPEND MOCK_HEADERS ${TFM_ROOT_DIR}/lib/tfm_vprintf/inc/tfm_vprintf.h)
list(APPEND MOCK_HEADERS ${TFM_ROOT_DIR}/lib/ext/efi_soft_crc/inc/efi_soft_crc.h)
diff --git a/lib/gpt/unittests/include/mbedtls/mbedtls_config.h b/lib/gpt/unittests/include/mbedtls/mbedtls_config.h
new file mode 100644
index 000000000..5144daae3
--- /dev/null
+++ b/lib/gpt/unittests/include/mbedtls/mbedtls_config.h
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef MBEDTLS_CONFIG_H
+#define MBEDTLS_CONFIG_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
@@ -0,0 +1,412 @@
From c0037dc1779d181ce7d7dbd5871519c9d218440a Mon Sep 17 00:00:00 2001
From: Frazer Carsley <frazer.carsley@arm.com>
Date: Tue, 30 Dec 2025 10:29:47 +0000
Subject: [PATCH] lib: gpt: Added table validation operations
Added validate and restore operations. Both of these operate on the
entirety of the table and allow the caller to:
1. determine if the GPT is valid or not.
2. restore a GPT from the alternative at the other end of the storage
device.
Both of these operations can be performed on either the primary or
backup table to allow full recovery.
Change-Id: Ie5ac2fdc42858cb439a2dcd08f4203eb181d4e88
Signed-off-by: Frazer Carsley <frazer.carsley@arm.com>
Upstream-Status: Backport [62a038e80574e094e64617953748633737cd760d]
---
lib/gpt/inc/gpt.h | 22 ++++
lib/gpt/src/gpt.c | 184 +++++++++++++++++++++++++++++++
lib/gpt/unittests/gpt/test_gpt.c | 135 +++++++++++++++++++++++
3 files changed, 341 insertions(+)
diff --git a/lib/gpt/inc/gpt.h b/lib/gpt/inc/gpt.h
index f04643957..0ce4d04ab 100644
--- a/lib/gpt/inc/gpt.h
+++ b/lib/gpt/inc/gpt.h
@@ -207,6 +207,28 @@ psa_status_t gpt_entry_create(const struct efi_guid_t *type,
__attribute__((nonnull(1)))
psa_status_t gpt_entry_remove(const struct efi_guid_t *guid);
+/**
+ * \brief Validates the GPT.
+ *
+ * \param[in] is_primary True to validate the primary table, false to validate the backup.
+ *
+ * \retval PSA_SUCCESS GPT is valid.
+ * \retval PSA_ERROR_STORAGE_FAILURE I/O error.
+ * \retval PSA_ERROR_INVALID_SIGNATURE GPT is invalid.
+ */
+psa_status_t gpt_validate(bool is_primary);
+
+/**
+ * \brief Restores either the primary or backup GPT from the other copy.
+ *
+ * \param[in] is_primary True to restore the primary table, false to restore the backup.
+ *
+ * \retval PSA_SUCCESS GPT restored.
+ * \retval PSA_ERROR_STORAGE_FAILURE I/O error.
+ * \retval PSA_ERROR_INVALID_SIGNATURE Restoring GPT invalid; cannot restore.
+ */
+psa_status_t gpt_restore(bool is_primary);
+
/**
* \brief Reads the GPT header from the second block (LBA 1).
*
diff --git a/lib/gpt/src/gpt.c b/lib/gpt/src/gpt.c
index 676f04fd6..1bc592bb3 100644
--- a/lib/gpt/src/gpt.c
+++ b/lib/gpt/src/gpt.c
@@ -223,6 +223,8 @@ 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_table(struct gpt_t *table, bool is_primary);
+static psa_status_t restore_table(struct gpt_t *restore_from, bool is_primary);
/* PUBLIC API FUNCTIONS */
@@ -747,6 +749,76 @@ psa_status_t gpt_entry_remove(const struct efi_guid_t *guid)
return ret;
}
+psa_status_t gpt_validate(bool is_primary)
+{
+ if (!is_primary && (backup_gpt_lba == 0 || backup_gpt_array_lba == 0)) {
+ ERROR("Backup GPT location unknown!\n");
+ return PSA_ERROR_STORAGE_FAILURE;
+ }
+
+ /* Flush and invalidate the in-memory buffer before attempting to validate
+ * a table. The in-memory header needs to be updated if the flushed LBA was
+ * part of the entry array
+ */
+ psa_status_t ret;
+ if (write_buffered) {
+ ret = flush_lba_buf();
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+ write_buffered = false;
+ }
+ cached_lba = 0;
+
+ if (is_primary) {
+ return validate_table(&primary_gpt, true);
+ } else {
+ struct gpt_t backup_gpt;
+ ret = read_table_from_flash(&backup_gpt, false);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+ return validate_table(&backup_gpt, false);
+ }
+}
+
+psa_status_t gpt_restore(bool is_primary)
+{
+ if (!is_primary && (backup_gpt_lba == 0 || backup_gpt_array_lba == 0)) {
+ ERROR("Backup GPT location unknown!\n");
+ return PSA_ERROR_STORAGE_FAILURE;
+ }
+
+ /* Flush and invalidate the in-memory buffer before attempting to restore a
+ * table. The in-memory header needs to be updated if the flushed LBA was
+ * part of the entry array
+ */
+ psa_status_t ret;
+ if (write_buffered) {
+ ret = flush_lba_buf();
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+ write_buffered = false;
+ }
+ cached_lba = 0;
+
+ if (is_primary) {
+ struct gpt_t backup_gpt;
+ ret = read_table_from_flash(&backup_gpt, false);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+ ret = count_used_partitions(&backup_gpt, &backup_gpt.num_used_partitions);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+ return restore_table(&backup_gpt, false);
+ } else {
+ return restore_table(&primary_gpt, true);
+ }
+}
+
/* Initialises GPT from first block. */
psa_status_t gpt_init(struct gpt_flash_driver_t *flash_driver, uint64_t max_partitions)
{
@@ -1349,6 +1421,118 @@ static inline void swap_headers(const struct gpt_header_t *src, struct gpt_heade
primary_gpt.header.array_lba);
}
+/* Validates a specific GPT. */
+static psa_status_t validate_table(struct gpt_t *table, bool is_primary)
+{
+ struct gpt_header_t *header = &(table->header);
+
+ /* Check signature */
+ if (strncmp(header->signature, GPT_SIG, GPT_SIG_LEN) != 0) {
+ ERROR("Invalid GPT signature\n");
+ return PSA_ERROR_INVALID_SIGNATURE;
+ }
+
+ /* Check header CRC */
+ uint32_t calc_crc = 0;
+ uint32_t old_crc = header->header_crc;
+ header->header_crc = 0;
+ calc_crc = efi_soft_crc32_update(calc_crc, (uint8_t *)header, GPT_HEADER_SIZE);
+ header->header_crc = old_crc;
+
+ if (old_crc != calc_crc) {
+ ERROR("CRC of header does not match, expected 0x%x got 0x%x\n",
+ old_crc, calc_crc);
+ return PSA_ERROR_INVALID_SIGNATURE;
+ }
+
+ /* Check MyLBA field points to this table */
+ const uint64_t table_lba = (is_primary ? PRIMARY_GPT_LBA : backup_gpt_lba);
+ if (table_lba != header->current_lba) {
+ ERROR("MyLBA not pointing to this GPT, expected 0x%08x%08x, got 0x%08x%08x\n",
+ (uint32_t)(table_lba >> 32),
+ (uint32_t)table_lba,
+ (uint32_t)(header->current_lba >> 32),
+ (uint32_t)(header->current_lba));
+ return PSA_ERROR_INVALID_SIGNATURE;
+ }
+
+ /* 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);
+ 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);
+ }
+
+ if (calc_crc != header->array_crc) {
+ ERROR("CRC of partition array does not match, expected 0x%x got 0x%x\n",
+ calc_crc, header->array_crc);
+ return PSA_ERROR_INVALID_SIGNATURE;
+ }
+
+ if (is_primary) {
+ /* Any time the primary table is considered valid, cache the backup
+ * LBA field
+ */
+ backup_gpt_lba = header->backup_lba;
+ } else {
+ /* Any time backup table is considered valid, cache its array LBA
+ * field and crc32
+ */
+ backup_gpt_array_lba = header->array_lba;
+ backup_crc32 = header->header_crc;
+ }
+ return PSA_SUCCESS;
+}
+
+/* Restore a table from another. The second parameter indicates whether the
+ * restoring table is the primary GPT or not
+ */
+static psa_status_t restore_table(struct gpt_t *restore_from, bool is_primary)
+{
+ /* Determine if the restoring GPT is valid */
+ psa_status_t ret = validate_table(restore_from, is_primary);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+
+ struct gpt_t restore_to;
+ swap_headers(&(restore_from->header), &(restore_to.header));
+
+ /* Copy the partition array as well */
+ ret = move_partition(
+ restore_from->header.array_lba,
+ restore_to.header.array_lba,
+ (restore_from->header.num_partitions +
+ gpt_entry_per_lba_count() - 1) / gpt_entry_per_lba_count());
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+
+ /* Write the header */
+ ret = write_header_to_flash(&restore_to);
+ if (ret != PSA_SUCCESS) {
+ ERROR("Unable to write %s GPT header\n", is_primary ? "backup" : "primary");
+ return ret;
+ }
+
+ /* The primary GPT is cached in memory */
+ if (!is_primary) {
+ memcpy(&(primary_gpt.header), &(restore_to.header), GPT_HEADER_SIZE);
+ primary_gpt.num_used_partitions = restore_from->num_used_partitions;
+ }
+
+ INFO("Successfully restored %s GPT table\n", is_primary ? "backup" : "primary");
+
+ return 0;
+}
+
/* Converts unicode string to valid ascii */
static psa_status_t unicode_to_ascii(const char *unicode, char *ascii)
{
diff --git a/lib/gpt/unittests/gpt/test_gpt.c b/lib/gpt/unittests/gpt/test_gpt.c
index 18cf2c8f3..f15a0a737 100644
--- a/lib/gpt/unittests/gpt/test_gpt.c
+++ b/lib/gpt/unittests/gpt/test_gpt.c
@@ -369,6 +369,141 @@ void test_gpt_init_should_failWhenFlashDriverNotFullyDefined(void)
mock_driver.erase = erase_fn;
}
+void test_gpt_validate_should_validateWhenGptGood(void)
+{
+ 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_SUCCESS, gpt_validate(true));
+
+ /* Now do the backup */
+ setup_backup_gpt();
+ register_mocked_read(&test_partition_array, sizeof(test_partition_array));
+
+ TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_validate(false));
+}
+
+void test_gpt_validate_should_failWhenGptSigBad(void)
+{
+ test_header.signature[0] = '\0';
+ setup_test_gpt();
+
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(true));
+
+ /* Now do the backup */
+ setup_backup_gpt();
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(false));
+}
+
+void test_gpt_validate_should_failWhenHeaderCrcBad(void)
+{
+ test_header.header_crc--;
+ setup_test_gpt();
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(true));
+
+ /* Now do the backup */
+ struct gpt_header_t backup_header;
+ MAKE_BACKUP_HEADER(backup_header, test_header);
+ backup_header.header_crc--;
+ register_mocked_read(&backup_header, sizeof(backup_header));
+
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(false));
+}
+
+void test_gpt_validate_should_failWhenLbaPointerBad(void)
+{
+ test_header.current_lba = 2;
+ test_header.backup_lba = 3;
+ setup_test_gpt();
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(true));
+
+ /* Now set the backup LBA to be something different that what it should be
+ * to force a mismatch
+ */
+ test_header.current_lba = default_header.current_lba;
+ test_header.backup_lba = default_header.backup_lba - 1;
+
+ /* Now do the backup */
+ struct gpt_header_t backup_header;
+ MAKE_BACKUP_HEADER(backup_header, test_header);
+ register_mocked_read(&backup_header, sizeof(backup_header));
+
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_validate(false));
+}
+
+void test_gpt_validate_should_failWhenArrayCrcBad(void)
+{
+ test_header.array_crc--;
+ 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));
+
+ /* Now do the backup */
+ struct gpt_header_t backup_header;
+ MAKE_BACKUP_HEADER(backup_header, test_header);
+ backup_header.array_crc--;
+ register_mocked_read(&backup_header, sizeof(test_partition_array));
+ 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 */
+ setup_valid_gpt();
+
+ /* The backup table is read and checked for validity, including taking
+ * CRC32 of partition array
+ */
+ setup_backup_gpt();
+ register_mocked_read(&test_partition_array, sizeof(test_partition_array));
+
+ TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_restore(true));
+}
+
+void test_gpt_restore_should_failToRestoreWhenBackupIsBad(void)
+{
+ /* Start with a valid GPT */
+ setup_valid_gpt();
+
+ /* The backup table is read and checked for validity. Corrupt it in
+ * various ways
+ */
+ struct gpt_header_t backup_header;
+
+ /* Bad signature */
+ MAKE_BACKUP_HEADER(backup_header, test_header);
+ backup_header.signature[0] = '\0';
+ register_mocked_read(&backup_header, sizeof(backup_header));
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_restore(true));
+
+ /* Bad header CRC */
+ MAKE_BACKUP_HEADER(backup_header, test_header);
+ backup_header.header_crc = 0;
+ register_mocked_read(&backup_header, sizeof(backup_header));
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_restore(true));
+
+ /* Bad LBA */
+ test_header.backup_lba = 2;
+ MAKE_BACKUP_HEADER(backup_header, test_header);
+ register_mocked_read(&backup_header, sizeof(backup_header));
+ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_restore(true));
+ test_header.backup_lba = default_header.backup_lba;
+
+ /* Bad array CRC. Will involve reading array entries */
+ MAKE_BACKUP_HEADER(backup_header, test_header);
+ backup_header.array_crc = 0;
+ 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_restore(true));
+}
+
void test_gpt_entry_create_should_createNewEntry(void)
{
/* Add an entry. It must not overlap with an existing entry and must also
@@ -0,0 +1,280 @@
From ba38f0f304ad69047ce9830b81a4f9cfbccb8fcb Mon Sep 17 00:00:00 2001
From: Frazer Carsley <frazer.carsley@arm.com>
Date: Tue, 30 Dec 2025 10:51:57 +0000
Subject: [PATCH] lib: gpt: Added defragmentation operation
Defragmentation moves all of used data of the device to the beginning
such that all of the free space is afterwards towards the end. This is
less meaningful on flash devices however is still provided as an
operation for completeness. This is done by ordering the partition entry
array first, so that it is safe to shuffle data in one direction without
risk of data loss/overwriting.
Change-Id: Ic401c79ac8c1fba2489ab2680efc02139c759c66
Signed-off-by: Frazer Carsley <frazer.carsley@arm.com>
Upstream-Status: Backport [21ff1f85c0b5463c1ea75fd61d872c0029eed902]
---
lib/gpt/inc/gpt.h | 8 ++
lib/gpt/src/gpt.c | 190 +++++++++++++++++++++++++++++++
lib/gpt/unittests/gpt/test_gpt.c | 7 ++
3 files changed, 205 insertions(+)
diff --git a/lib/gpt/inc/gpt.h b/lib/gpt/inc/gpt.h
index 0ce4d04ab..baf4767f9 100644
--- a/lib/gpt/inc/gpt.h
+++ b/lib/gpt/inc/gpt.h
@@ -229,6 +229,14 @@ psa_status_t gpt_validate(bool is_primary);
*/
psa_status_t gpt_restore(bool is_primary);
+/**
+ * \brief Defragments the GPT, ensuring free space becomes contiguous.
+ *
+ * \retval PSA_SUCCESS Success.
+ * \retval PSA_ERROR_STORAGE_FAILURE I/O failure.
+ */
+psa_status_t gpt_defragment(void);
+
/**
* \brief Reads the GPT header from the second block (LBA 1).
*
diff --git a/lib/gpt/src/gpt.c b/lib/gpt/src/gpt.c
index 1bc592bb3..b1d4597cd 100644
--- a/lib/gpt/src/gpt.c
+++ b/lib/gpt/src/gpt.c
@@ -6,6 +6,7 @@
#include <stdbool.h>
#include <stdint.h>
+#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
@@ -225,6 +226,7 @@ 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_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(struct gpt_t *table);
/* PUBLIC API FUNCTIONS */
@@ -819,6 +821,66 @@ psa_status_t gpt_restore(bool is_primary)
}
}
+psa_status_t gpt_defragment(void)
+{
+ /* First, sort the partition array according to start LBA. This means that
+ * moving partitions towards the start of the flash sequentially is safe
+ * and will not result in lost data.
+ */
+ psa_status_t ret = sort_partition_array(&primary_gpt);
+ if (ret != PSA_SUCCESS) {
+ WARN("Unable to defragment flash!\n");
+ return ret;
+ }
+
+ uint64_t prev_end = primary_gpt.header.first_lba;
+ struct gpt_entry_t entry;
+
+ for (uint32_t i = 0; i < primary_gpt.num_used_partitions; ++i) {
+ ret = read_entry_from_flash(&primary_gpt, i, &entry);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+
+ /* Move to be next to previous entry. Continue if already where it
+ * needs to be.
+ */
+ if (prev_end == entry.start) {
+ prev_end = entry.end + 1;
+ continue;
+ }
+
+ const uint64_t num_blocks = entry.end - entry.start + 1;
+ ret = move_partition(entry.start, prev_end, num_blocks);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+
+ /* Update header information */
+ entry.start = prev_end;
+ entry.end = entry.start + num_blocks - 1;
+ prev_end = entry.end + 1;
+
+ /* Write the entry change, skipping header update until every entry
+ * written
+ */
+ ret = write_entry(i, &entry, true);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* Write everything to flash after defragmentation if not done so already.
+ * The previous loop will write the last entry to the LBA buffer, which may
+ * or not may not be flushed
+ */
+ if (write_buffered) {
+ return flush_lba_buf();
+ }
+
+ return update_header(primary_gpt.num_used_partitions);
+}
+
/* Initialises GPT from first block. */
psa_status_t gpt_init(struct gpt_flash_driver_t *flash_driver, uint64_t max_partitions)
{
@@ -1533,6 +1595,134 @@ static psa_status_t restore_table(struct gpt_t *restore_from, bool is_primary)
return 0;
}
+/* Comparison function to pass to qsort */
+static int cmp_u64(const void *a, const void *b)
+{
+ const uint64_t *a_u64 = (const uint64_t *)a;
+ const uint64_t *b_u64 = (const uint64_t *)b;
+ return (*a_u64 > *b_u64) - (*a_u64 < *b_u64);
+}
+
+/* bsearch but returns the index rather than the item */
+static int64_t bsearch_index(uint64_t arr[], uint32_t len, uint64_t key)
+{
+ uint32_t l = 0;
+ uint32_t r = len;
+
+ while (l < r) {
+ uint32_t m = l + (r - l) / 2;
+ uint64_t item = arr[m];
+
+ if (item < key) {
+ l = m + 1;
+ } else if (item > key) {
+ r = m;
+ } else {
+ return (int64_t)m;
+ }
+ }
+
+ return -1;
+}
+
+/* Sorts the partition array for the given table by the start LBA for each
+ * partition. This makes defragmentation easier.
+ */
+static psa_status_t sort_partition_array(struct gpt_t *table)
+{
+ /* To avoid as much I/O as possible, the LBA's for each entry are sorted in
+ * memory and then the entries rearranged on flash after
+ */
+ uint64_t lba_arr[table->num_used_partitions];
+ psa_status_t ret;
+ for (uint32_t i = 0; i < table->num_used_partitions; ++i) {
+ struct gpt_entry_t entry;
+ ret = read_entry_from_flash(table, i, &entry);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+ lba_arr[i] = entry.start;
+ }
+
+ qsort(lba_arr, table->num_used_partitions, sizeof(uint64_t), cmp_u64);
+
+ /* Now read and place the entries in the correct spot, starting with the
+ * first. Each entry is dealt with as it is encountered. When an entry is
+ * found and already in the correct spot, the next smallest index not yet
+ * handled becomes the next.
+ */
+ struct gpt_entry_t saved_entry = {0};
+ struct gpt_entry_t curr_entry;
+ uint8_t handled_indices[table->num_used_partitions];
+ memset(handled_indices, 0, table->num_used_partitions);
+
+ ret = read_entry_from_flash(table, 0, &curr_entry);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+
+ for (uint32_t i = 0; i < table->num_used_partitions; ++i) {
+ const int64_t new_index = bsearch_index(
+ lba_arr,
+ table->num_used_partitions,
+ curr_entry.start);
+ if (new_index < 0) {
+ ERROR("Encountered unknown partition entry!\n");
+ return PSA_ERROR_STORAGE_FAILURE;
+ }
+
+ /* For final entry, just write it out */
+ if (i == table->num_used_partitions - 1) {
+ ret = write_entry((uint32_t)new_index, &curr_entry, false);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+ break;
+ }
+
+ /* Replace the entry in the new_index place with the current entry */
+ ret = read_entry_from_flash(table, (uint32_t)new_index, &saved_entry);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+
+ struct efi_guid_t saved_guid = saved_entry.unique_guid;
+ struct efi_guid_t curr_guid = curr_entry.unique_guid;
+ if (efi_guid_cmp(&saved_guid, &curr_guid) == 0) {
+ /* This entry is already where it needs to be, so try the smallest
+ * index not yet handled next
+ */
+ handled_indices[new_index] = 1;
+ uint32_t next_entry = 0;
+ while(next_entry < table->num_used_partitions && handled_indices[next_entry]) {
+ ++next_entry;
+ }
+
+ if (next_entry == table->num_used_partitions) {
+ /* Done everything */
+ break;
+ }
+
+ ret = read_entry_from_flash(table, next_entry, &saved_entry);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+ } else {
+ /* Write, skipping header update until very end */
+ ret = write_entry((uint32_t)new_index, &curr_entry, true);
+ if (ret != PSA_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* Ready up for the next loop */
+ curr_entry = saved_entry;
+ handled_indices[new_index] = 1;
+ }
+
+ return PSA_SUCCESS;
+}
+
/* Converts unicode string to valid ascii */
static psa_status_t unicode_to_ascii(const char *unicode, char *ascii)
{
diff --git a/lib/gpt/unittests/gpt/test_gpt.c b/lib/gpt/unittests/gpt/test_gpt.c
index f15a0a737..2f05a6b4a 100644
--- a/lib/gpt/unittests/gpt/test_gpt.c
+++ b/lib/gpt/unittests/gpt/test_gpt.c
@@ -504,6 +504,13 @@ void test_gpt_restore_should_failToRestoreWhenBackupIsBad(void)
TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_restore(true));
}
+void test_gpt_defragment_should_succeedWhenNoIOFailure(void)
+{
+ setup_valid_gpt();
+
+ TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_defragment());
+}
+
void test_gpt_entry_create_should_createNewEntry(void)
{
/* Add an entry. It must not overlap with an existing entry and must also
@@ -0,0 +1,74 @@
From f9e4d1f48a4553278dc4de82b0d2f4d82b1e5193 Mon Sep 17 00:00:00 2001
From: Antonio de Angelis <antonio.deangelis@arm.com>
Date: Wed, 4 Mar 2026 13:05:51 +0000
Subject: [PATCH] lib: GPT: Fix cppcheck warnings
Change-Id: Ia9e99dc59cbf869b804a24ba029f19f7170860b4
Signed-off-by: Antonio de Angelis <antonio.deangelis@arm.com>
Upstream-Status: Backport [92d5b6b7d296c174364e684508089c0a0678e3bb]
---
lib/gpt/src/gpt.c | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/lib/gpt/src/gpt.c b/lib/gpt/src/gpt.c
index b1d4597cd..a9dbb918e 100644
--- a/lib/gpt/src/gpt.c
+++ b/lib/gpt/src/gpt.c
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-3-Clause
*/
+#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
@@ -194,7 +195,7 @@ static inline uint64_t gpt_entry_per_lba_count(void);
static inline void swap_headers(const struct gpt_header_t *src, struct gpt_header_t *dst);
static psa_status_t count_used_partitions(const struct gpt_t *table,
uint32_t *num_used);
-static inline void parse_entry(struct gpt_entry_t *entry,
+static inline void parse_entry(const struct gpt_entry_t *entry,
struct partition_entry_t *partition_entry);
static psa_status_t read_from_flash(uint64_t required_lba);
static psa_status_t read_entry_from_flash(const struct gpt_t *table,
@@ -226,7 +227,7 @@ 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_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(struct gpt_t *table);
+static psa_status_t sort_partition_array(const struct gpt_t *table);
/* PUBLIC API FUNCTIONS */
@@ -1006,7 +1007,7 @@ static inline uint64_t gpt_entry_per_lba_count(void)
}
/* Copies information from the entry to the user visible structure */
-static inline void parse_entry(struct gpt_entry_t *entry,
+static inline void parse_entry(const struct gpt_entry_t *entry,
struct partition_entry_t *partition_entry)
{
partition_entry->start = entry->start;
@@ -1604,7 +1605,7 @@ static int cmp_u64(const void *a, const void *b)
}
/* bsearch but returns the index rather than the item */
-static int64_t bsearch_index(uint64_t arr[], uint32_t len, uint64_t key)
+static int64_t bsearch_index(const uint64_t arr[], uint32_t len, uint64_t key)
{
uint32_t l = 0;
uint32_t r = len;
@@ -1628,8 +1629,12 @@ static int64_t bsearch_index(uint64_t arr[], uint32_t len, uint64_t key)
/* Sorts the partition array for the given table by the start LBA for each
* partition. This makes defragmentation easier.
*/
-static psa_status_t sort_partition_array(struct gpt_t *table)
+static psa_status_t sort_partition_array(const struct gpt_t *table)
{
+ if (table->num_used_partitions == 0) {
+ assert(table->num_used_partitions > 0);
+ return PSA_ERROR_INVALID_ARGUMENT;
+ }
/* To avoid as much I/O as possible, the LBA's for each entry are sorted in
* memory and then the entries rearranged on flash after
*/
@@ -0,0 +1,28 @@
From 31cd6d9eb82792efd5af7d99802581eace13a083 Mon Sep 17 00:00:00 2001
From: Frazer Carsley <frazer.carsley@arm.com>
Date: Fri, 13 Mar 2026 17:10:25 +0000
Subject: [PATCH] lib: efi_guid: Remove unecessary include folder
There are no header files in the src folder, so there is nothing to
include. This line was redundant.
Change-Id: I5289932119629fc1ef6a71f0a0ceb63e5a466c96
Signed-off-by: Frazer Carsley <frazer.carsley@arm.com>
Upstream-Status: Backport [e74a1f467317c16487b9d41f43f147dd41985cb9]
---
lib/efi_guid/CMakeLists.txt | 2 --
1 file changed, 2 deletions(-)
diff --git a/lib/efi_guid/CMakeLists.txt b/lib/efi_guid/CMakeLists.txt
index 656eb72ea..9c4771cba 100644
--- a/lib/efi_guid/CMakeLists.txt
+++ b/lib/efi_guid/CMakeLists.txt
@@ -15,8 +15,6 @@ target_sources(tfm_efi_guid
target_include_directories(tfm_efi_guid
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/inc>
- PRIVATE
- ${CMAKE_CURRENT_SOURCE_DIR}/src
)
target_link_libraries(tfm_efi_guid
@@ -0,0 +1,28 @@
From 0b6a2e681b0f76c425896990ebc4afcbc65419d3 Mon Sep 17 00:00:00 2001
From: Frazer Carsley <frazer.carsley@arm.com>
Date: Fri, 13 Mar 2026 17:11:05 +0000
Subject: [PATCH] lib: efi_guid: Correct included folder
The use of the variables is unecessary and has no real effect. This
change is therefore simplified.
Change-Id: I0811b4768502d15b62f1af3ef90a56db3e9df525
Signed-off-by: Frazer Carsley <frazer.carsley@arm.com>
Upstream-Status: Backport [7bb11e81cd69718df3b11a3f8b42a3b5edf4c42d]
---
lib/efi_guid/CMakeLists.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/efi_guid/CMakeLists.txt b/lib/efi_guid/CMakeLists.txt
index 9c4771cba..95d7b8f1e 100644
--- a/lib/efi_guid/CMakeLists.txt
+++ b/lib/efi_guid/CMakeLists.txt
@@ -14,7 +14,7 @@ target_sources(tfm_efi_guid
target_include_directories(tfm_efi_guid
PUBLIC
- $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/inc>
+ inc
)
target_link_libraries(tfm_efi_guid
@@ -0,0 +1,25 @@
From f1862245d39a726c8e37f58498dd433c6fd99d1b Mon Sep 17 00:00:00 2001
From: Frazer Carsley <frazer.carsley@arm.com>
Date: Fri, 13 Mar 2026 17:13:07 +0000
Subject: [PATCH] lib: efi_soft_crc: Correct include directory
The variables had no real effect, so this is therefore simplified.
Change-Id: Ifa03bc23e0419f9d6d598e7753f23dfde57c7bf6
Signed-off-by: Frazer Carsley <frazer.carsley@arm.com>
Upstream-Status: Backport [3856d55117b727c6c21bb821fa4236b113fb08ef]
---
lib/ext/efi_soft_crc/CMakeLists.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/ext/efi_soft_crc/CMakeLists.txt b/lib/ext/efi_soft_crc/CMakeLists.txt
index 47fd2d507..d77310167 100644
--- a/lib/ext/efi_soft_crc/CMakeLists.txt
+++ b/lib/ext/efi_soft_crc/CMakeLists.txt
@@ -14,5 +14,5 @@ target_sources(tfm_efi_soft_crc
target_include_directories(tfm_efi_soft_crc
PUBLIC
- $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/inc>
+ inc
)
@@ -0,0 +1,27 @@
From 343bba6c20802555dfb92c3e2b22a7e07c10a57a Mon Sep 17 00:00:00 2001
From: Frazer Carsley <frazer.carsley@arm.com>
Date: Fri, 13 Mar 2026 17:16:00 +0000
Subject: [PATCH] lib: gpt: Add missing link library
tfm_log requires tfm_vprintf. When building for a platform with logging
enabled, these headers are therefore required.
Change-Id: I63b35d5af818ea5ad7b1fe76200250ad98584a63
Signed-off-by: Frazer Carsley <frazer.carsley@arm.com>
Upstream-Status: Backport [92a3738634fe121766705599d66e4a8b772ce06e]
---
lib/gpt/CMakeLists.txt | 1 +
1 file changed, 1 insertion(+)
diff --git a/lib/gpt/CMakeLists.txt b/lib/gpt/CMakeLists.txt
index 5befc346f..ed31adfbe 100644
--- a/lib/gpt/CMakeLists.txt
+++ b/lib/gpt/CMakeLists.txt
@@ -36,6 +36,7 @@ target_compile_definitions(tfm_gpt
target_link_libraries(tfm_gpt
PUBLIC
tfm_log_headers
+ tfm_vprintf_headers
PRIVATE
tfm_efi_guid
tfm_efi_soft_crc
@@ -0,0 +1,24 @@
From 1ae97df8db8babcaa999e47d9641c83c4ee8424a Mon Sep 17 00:00:00 2001
From: Frazer Carsley <frazer.carsley@arm.com>
Date: Fri, 13 Mar 2026 17:17:33 +0000
Subject: [PATCH] lib: gpt: Correct variable name used
Change-Id: I86d616b3c86cf0a1fd053b732565477278030d89
Signed-off-by: Frazer Carsley <frazer.carsley@arm.com>
Upstream-Status: Backport [bc9522f5c47c7e3f3a92189073166e73323010e9]
---
lib/gpt/unittests/gpt/utcfg.cmake | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/gpt/unittests/gpt/utcfg.cmake b/lib/gpt/unittests/gpt/utcfg.cmake
index f3f4e7dcc..69a1d10bd 100644
--- a/lib/gpt/unittests/gpt/utcfg.cmake
+++ b/lib/gpt/unittests/gpt/utcfg.cmake
@@ -27,6 +27,6 @@ list(APPEND MOCK_HEADERS ${TFM_ROOT_DIR}/lib/tfm_vprintf/inc/tfm_vprintf.h)
list(APPEND MOCK_HEADERS ${TFM_ROOT_DIR}/lib/ext/efi_soft_crc/inc/efi_soft_crc.h)
# Compile-time definitions
-list(APPEND UNIT_TEST_COMPILE_DEFS LOG_LEVEL=LOG_LEVEL_VERBOSE)
+list(APPEND UNIT_TEST_COMPILE_DEFS GPT_LOG_LEVEL=LOG_LEVEL_VERBOSE)
list(APPEND UNIT_TEST_COMPILE_DEFS TFM_GPT_BLOCK_SIZE=512)
@@ -0,0 +1,27 @@
From eb934a532bbc8385cd9b062fc80cece61350f086 Mon Sep 17 00:00:00 2001
From: Frazer Carsley <frazer.carsley@arm.com>
Date: Fri, 13 Mar 2026 17:19:50 +0000
Subject: [PATCH] lib: gpt: Correct include directory
The variables have no real effect, so therefore this is simplified.
Change-Id: Ie912941f8eafd9cd5bf4dd88927640ad70d0676a
Signed-off-by: Frazer Carsley <frazer.carsley@arm.com>
Upstream-Status: Backport [369c897be0b9102cec8141c53b3d3fe6abb56b6e]
---
lib/gpt/CMakeLists.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/gpt/CMakeLists.txt b/lib/gpt/CMakeLists.txt
index ed31adfbe..19cfe5bc2 100644
--- a/lib/gpt/CMakeLists.txt
+++ b/lib/gpt/CMakeLists.txt
@@ -22,7 +22,7 @@ target_sources(tfm_gpt
target_include_directories(tfm_gpt
PUBLIC
- $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/inc>
+ inc
$<INSTALL_INTERFACE:inc>
)
@@ -0,0 +1,51 @@
From ce3a4f3dd8900c068f2bfe951d55b895e3c10d43 Mon Sep 17 00:00:00 2001
From: Frazer Carsley <frazer.carsley@arm.com>
Date: Fri, 13 Mar 2026 17:29:04 +0000
Subject: [PATCH] lib: gpt: Move contents of CMake config file
A config.cmake used in a library can be a little confusing, especially
considering that such files are used for platform configurations. The
contents of the file are placed directly in the CMakeLists.txt that was
including it.
Change-Id: I8a88971feebaf37498828af5854de94e74e16f2c
Signed-off-by: Frazer Carsley <frazer.carsley@arm.com>
Upstream-Status: Backport [4b569b02b98a577e45e833f4dd9886e54df71a7d]
---
lib/gpt/CMakeLists.txt | 6 +++---
lib/gpt/config.cmake | 8 --------
2 files changed, 3 insertions(+), 11 deletions(-)
delete mode 100644 lib/gpt/config.cmake
diff --git a/lib/gpt/CMakeLists.txt b/lib/gpt/CMakeLists.txt
index 19cfe5bc2..4a15173fd 100644
--- a/lib/gpt/CMakeLists.txt
+++ b/lib/gpt/CMakeLists.txt
@@ -9,10 +9,10 @@ cmake_minimum_required(VERSION 3.21)
add_library(tfm_gpt STATIC)
-include(./config.cmake)
+set(GPT_LOG_LEVEL LOG_LEVEL_INFO CACHE STRING "Set default log level for the GPT library")
-if(NOT DEFINED TFM_GPT_BLOCK_SIZE OR NOT DEFINED GPT_LOG_LEVEL)
- message(FATAL_ERROR "TFM_GPT_BLOCK_SIZE and GPT_LOG_LEVEL must be defined to use GPT library")
+if(NOT DEFINED TFM_GPT_BLOCK_SIZE)
+ message(FATAL_ERROR "TFM_GPT_BLOCK_SIZE must be defined to use GPT library")
endif()
target_sources(tfm_gpt
diff --git a/lib/gpt/config.cmake b/lib/gpt/config.cmake
deleted file mode 100644
index 9575aa8a8..000000000
--- a/lib/gpt/config.cmake
+++ /dev/null
@@ -1,8 +0,0 @@
-#-------------------------------------------------------------------------------
-# SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
-#
-# SPDX-License-Identifier: BSD-3-Clause
-#
-#-------------------------------------------------------------------------------
-
-set(GPT_LOG_LEVEL LOG_LEVEL_INFO CACHE STRING "Set default log level for the GPT library")
@@ -48,6 +48,23 @@ SRC_URI:append:corstone1000 = " \
file://0014-Workaround-compile-errors-in-AES.patch \
file://0015-CC312-Add-barrier-before-first-AO-lock-write.patch \
file://0016-Platform-CS1K-make-mutlicore-support-platform-generi.patch \
file://0017-lib-efi_guid-Added-EFI-GUID-library.patch \
file://0018-lib-efi_soft_crc-Added-EFI-CRC-library.patch \
file://0019-lib-gpt-Implemented-generic-GPT-parser-for-flash.patch \
file://0020-lib-gpt-Expanded-how-GPT-partition-can-be-identified.patch \
file://0021-lib-gpt-Added-operations-to-modify-partitions.patch \
file://0022-lib-gpt-Added-operation-to-move-entry.patch \
file://0023-lib-gpt-Added-ability-to-create-and-remove-partition.patch \
file://0024-lib-gpt-Added-table-validation-operations.patch \
file://0025-lib-gpt-Added-defragmentation-operation.patch \
file://0026-lib-GPT-Fix-cppcheck-warnings.patch \
file://0027-lib-efi_guid-Remove-unecessary-include-folder.patch \
file://0028-lib-efi_guid-Correct-included-folder.patch \
file://0029-lib-efi_soft_crc-Correct-include-directory.patch \
file://0030-lib-gpt-Add-missing-link-library.patch \
file://0031-lib-gpt-Correct-variable-name-used.patch \
file://0032-lib-gpt-Correct-include-directory.patch \
file://0033-lib-gpt-Move-contents-of-CMake-config-file.patch \
"
SRCREV_tfm-psa-adac:corstone1000 = "f2809ae231be33a1afcd7714f40756c67d846c88"