new file mode 100644
@@ -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;
++}
new file mode 100644
@@ -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;
++}
new file mode 100644
@@ -0,0 +1,1495 @@
+From ea6748c8778d21e331b22ddea808f9343a654b4d Mon Sep 17 00:00:00 2001
+From: Frazer Carsley <frazer.carsley@arm.com>
+Date: Mon, 29 Dec 2025 17:36:22 +0000
+Subject: [PATCH] lib: gpt: Implemented generic GPT parser for flash
+
+The library can be used to parse GPT partitions on a flash device by
+giving the library endpoint the GUID that identifies the partition. A
+platform must register with the library a pseudo-device driver that
+defines a read operation, allowing the library to perform I/O.
+
+Change-Id: Id504ceb93856561063751570a773c4aae592b535
+Signed-off-by: Frazer Carsley <frazer.carsley@arm.com>
+Upstream-Status: Backport [2b155c6163f866757971ddf438014b518c4edd05]
+---
+ CMakeLists.txt | 5 +
+ config/config_base.cmake | 2 +
+ lib/gpt/CMakeLists.txt | 40 ++
+ lib/gpt/config.cmake | 8 +
+ lib/gpt/inc/gpt.h | 80 ++++
+ lib/gpt/inc/gpt_flash.h | 73 ++++
+ lib/gpt/src/gpt.c | 592 ++++++++++++++++++++++++++++++
+ lib/gpt/unittests/CMakeLists.txt | 179 +++++++++
+ lib/gpt/unittests/gpt/test_gpt.c | 383 +++++++++++++++++++
+ lib/gpt/unittests/gpt/utcfg.cmake | 28 ++
+ 10 files changed, 1390 insertions(+)
+ create mode 100644 lib/gpt/CMakeLists.txt
+ create mode 100644 lib/gpt/config.cmake
+ create mode 100644 lib/gpt/inc/gpt.h
+ create mode 100644 lib/gpt/inc/gpt_flash.h
+ create mode 100644 lib/gpt/src/gpt.c
+ create mode 100644 lib/gpt/unittests/CMakeLists.txt
+ create mode 100644 lib/gpt/unittests/gpt/test_gpt.c
+ create mode 100644 lib/gpt/unittests/gpt/utcfg.cmake
+
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+index b120de697..9e6e2ceda 100644
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -42,6 +42,11 @@ set(CMAKE_CXX_COMPILER_FORCED true)
+
+ project("Trusted Firmware M" VERSION ${TFM_VERSION} LANGUAGES C CXX ASM)
+
++
++if(PLATFORM_GPT_LIBRARY)
++ add_subdirectory(lib/gpt)
++endif()
++
+ add_subdirectory(lib/backtrace)
+ add_subdirectory(lib/ext)
+ add_subdirectory(lib/efi_guid)
+diff --git a/config/config_base.cmake b/config/config_base.cmake
+index f4b2f3cd9..4cd14ab2b 100644
+--- a/config/config_base.cmake
++++ b/config/config_base.cmake
+@@ -129,6 +129,8 @@ set(PLATFORM_DEFAULT_SYSTEM_RESET_HALT ON CACHE BOOL "Use default
+ set(PLATFORM_DEFAULT_IMAGE_SIGNING ON CACHE BOOL "Use default image signing implementation")
+ set(PLATFORM_DEFAULT_PROV_LINKER_SCRIPT ON CACHE BOOL "Use default provisioning linker script")
+
++set(PLATFORM_GPT_LIBRARY OFF CACHE BOOL "Whether to build the GPT library or not")
++
+ set(TFM_DUMMY_PROVISIONING ON CACHE BOOL "Provision with dummy values. NOT to be used in production")
+
+ set(BL2_HEADER_SIZE 0x000 CACHE STRING "BL2 Header size")
+diff --git a/lib/gpt/CMakeLists.txt b/lib/gpt/CMakeLists.txt
+new file mode 100644
+index 000000000..2b5d6af8f
+--- /dev/null
++++ b/lib/gpt/CMakeLists.txt
+@@ -0,0 +1,40 @@
++#-------------------------------------------------------------------------------
++# SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
++#
++# SPDX-License-Identifier: BSD-3-Clause
++#
++#-------------------------------------------------------------------------------
++
++cmake_minimum_required(VERSION 3.21)
++
++add_library(tfm_gpt STATIC)
++
++include(./config.cmake)
++
++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")
++endif()
++
++target_sources(tfm_gpt
++ PRIVATE
++ src/gpt.c
++)
++
++target_include_directories(tfm_gpt
++ PUBLIC
++ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/inc>
++)
++
++target_compile_definitions(tfm_gpt
++ PUBLIC
++ TFM_GPT_BLOCK_SIZE=${TFM_GPT_BLOCK_SIZE}
++ PRIVATE
++ LOG_LEVEL=${GPT_LOG_LEVEL}
++)
++
++target_link_libraries(tfm_gpt
++ PUBLIC
++ tfm_log_headers
++ PRIVATE
++ tfm_efi_guid
++)
+diff --git a/lib/gpt/config.cmake b/lib/gpt/config.cmake
+new file mode 100644
+index 000000000..9575aa8a8
+--- /dev/null
++++ b/lib/gpt/config.cmake
+@@ -0,0 +1,8 @@
++#-------------------------------------------------------------------------------
++# 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")
+diff --git a/lib/gpt/inc/gpt.h b/lib/gpt/inc/gpt.h
+new file mode 100644
+index 000000000..d98abe980
+--- /dev/null
++++ b/lib/gpt/inc/gpt.h
+@@ -0,0 +1,80 @@
++/*
++ * SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
++ *
++ * SPDX-License-Identifier: BSD-3-Clause
++ *
++ */
++
++#ifndef __TFM_GPT_H__
++#define __TFM_GPT_H__
++
++#include <stdbool.h>
++#include <stdint.h>
++
++#include "psa/error.h"
++#include "gpt_flash.h"
++#include "efi_guid_structs.h"
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++/** \brief Name length of a GPT partition entry. */
++#define GPT_ENTRY_NAME_LENGTH 72
++
++/**
++ * \brief Information about a GPT partition presented to callers.
++ */
++struct partition_entry_t {
++ uint64_t start; /**< Start Logical Block Address (LBA) of the partition. */
++ uint64_t size; /**< Size of the partition in number of LBAs. */
++ char name[GPT_ENTRY_NAME_LENGTH]; /**< Human readable name for the partition in unicode. */
++ uint64_t attr; /**< Attributes associated with the partition. */
++ struct efi_guid_t partition_guid; /**< Unique partition GUID. */
++ struct efi_guid_t type_guid; /**< Partition type GUID. */
++};
++
++/**
++ * \brief Reads the contents of a partition entry identified by a GUID.
++ *
++ * \param[in] guid GUID of the partition entry to read.
++ * \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 GUID.
++ */
++__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 GPT header from the second block (LBA 1).
++ *
++ * \param[in] flash_driver Driver used to perform I/O.
++ * \param[in] max_partitions Maximum number of allowable partitions.
++ *
++ * \retval PSA_SUCCESS Success.
++ * \retval PSA_ERROR_STORAGE_FAILURE I/O error.
++ * \retval PSA_ERROR_INVALID_ARGUMENT \p max_partitions is less than four, or one of the I/O
++ * functions defined by \p flash_driver is NULL. The init
++ * and uninit functions may be NULL if not required.
++ * \retval PSA_ERROR_NOT_SUPPORTED Legacy MBR is used and not GPT.
++ */
++__attribute__((nonnull(1)))
++psa_status_t gpt_init(struct gpt_flash_driver_t *flash_driver,
++ uint64_t max_partitions);
++
++/**
++ * \brief Uninitialises the library.
++ *
++ * \retval PSA_SUCCESS Success.
++ * \retval PSA_ERROR_STORAGE_FAILURE I/O error.
++ */
++psa_status_t gpt_uninit(void);
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif /* __TFM_GPT_H__ */
+diff --git a/lib/gpt/inc/gpt_flash.h b/lib/gpt/inc/gpt_flash.h
+new file mode 100644
+index 000000000..2b43390c1
+--- /dev/null
++++ b/lib/gpt/inc/gpt_flash.h
+@@ -0,0 +1,73 @@
++/*
++ * SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
++ *
++ * SPDX-License-Identifier: BSD-3-Clause
++ *
++ */
++
++#ifndef __TFM_GPT_FLASH_H__
++#define __TFM_GPT_FLASH_H__
++
++#include <sys/types.h>
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++/**
++ * \brief Error codes returned by GPT flash driver operations.
++ */
++typedef enum {
++ GPT_FLASH_SUCCESS = 0, /**< Operation completed successfully. */
++ GPT_FLASH_GENERIC_ERROR = -1, /**< Unspecified error. */
++ GPT_FLASH_NOT_INIT = -2, /**< Flash driver has not been initialised. */
++ GPT_FLASH_UNAVAILABLE = -3, /**< Flash driver is unavailable. */
++ GPT_FLASH_BAD_PARAM = -4, /**< Parameter supplied to the driver is invalid. */
++} gpt_flash_err_t;
++
++/**
++ * \brief Function used to initialise the driver.
++ *
++ * \retval GPT_FLASH_SUCCESS Success.
++ * \retval GPT_FLASH_GENERIC_ERROR Driver-specific failure.
++ */
++typedef gpt_flash_err_t (*gpt_flash_init_t)(void);
++
++/**
++ * \brief Function used to uninitialise the driver.
++ *
++ * \retval GPT_FLASH_SUCCESS Success.
++ * \retval GPT_FLASH_GENERIC_ERROR Driver-specific failure.
++ */
++typedef gpt_flash_err_t (*gpt_flash_uninit_t)(void);
++
++/**
++ * \brief Function that reads a logical block address.
++ *
++ * \param[in] lba Logical block address to read.
++ * \param[out] buf Buffer to populate. Must be at least the size of an LBA.
++ *
++ * \return Number of bytes read 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_read_t)(uint64_t lba,
++ void *buf);
++
++/**
++ * \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. */
++};
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif /* __TFM_GPT_FLASH_H__ */
+diff --git a/lib/gpt/src/gpt.c b/lib/gpt/src/gpt.c
+new file mode 100644
+index 000000000..99d735797
+--- /dev/null
++++ b/lib/gpt/src/gpt.c
+@@ -0,0 +1,592 @@
++/*
++ * SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
++ *
++ * SPDX-License-Identifier: BSD-3-Clause
++ */
++
++#include <stdbool.h>
++#include <stdint.h>
++#include <string.h>
++#include <inttypes.h>
++
++#include "psa/error.h"
++#include "gpt.h"
++#include "gpt_flash.h"
++#include "tfm_log.h"
++#include "efi_guid_structs.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)
++ */
++#ifndef TFM_GPT_BLOCK_SIZE
++#error "TFM_GPT_BLOCK_SIZE must be defined if using GPT library!"
++#endif
++
++/* Where Master Boot Record (MBR) is on flash */
++#define MBR_LBA 0ULL
++
++/* Number of unused bytes at the start of the MBR */
++#define MBR_UNUSED_BYTES 446
++
++/* Cylinder Head Sector (CHS) length for MBR entry */
++#define MBR_CHS_ADDRESS_LEN 3
++
++/* Number of entries in an MBR */
++#define MBR_NUM_ENTRIES 4
++
++/* MBR signature as defined by UEFI spec */
++#define MBR_SIG 0xAA55
++
++/* Type of MBR partition that indicates GPT in use */
++#define MBR_TYPE_GPT 0xEE
++
++/* Default GUID Partition Table (GPT) header size */
++#define GPT_HEADER_SIZE 92
++
++/* "EFI PART" (without null byte) */
++#define GPT_SIG "EFI PART"
++#define GPT_SIG_LEN 8
++
++/* Default partition entry size */
++#define GPT_ENTRY_SIZE 128
++
++/* Minimum number of partition entries according to spec */
++#define GPT_MIN_PARTITIONS 4
++
++/* Logical Block Address (LBA) for primary GPT */
++#define PRIMARY_GPT_LBA 1
++
++/* LBA for primary GPT partition array */
++#define PRIMARY_GPT_ARRAY_LBA 2
++
++/* MBR partition entry - both for protective MBR entry and
++ * legacy MBR entry
++ */
++struct mbr_entry_t {
++ /* Indicates if bootable */
++ uint8_t status;
++ /* For legacy MBR, not used by UEFI firmware. For protective MBR, set to
++ * 0x000200
++ */
++ uint8_t first_sector[MBR_CHS_ADDRESS_LEN];
++ /* Type of partition */
++ uint8_t os_type;
++ /* For legacy MBR, not used by UEFI firmware. For protective MBR, last
++ * block on flash.
++ */
++ uint8_t last_sector[MBR_CHS_ADDRESS_LEN];
++ /* For legacy MBR, starting LBA of partition. For protective MBR, set to
++ * 0x00000001
++ */
++ uint32_t first_lba;
++ /* For legacy MBR, size of partition. For protective MBR, size of flash
++ * minus one
++ */
++ uint32_t size;
++} __attribute__((packed));
++
++/* MBR. This structure is used both for protective MBR and legacy MBR. The boot
++ * code, flash signature and unknown sections are not read because they are
++ * unused and do not change.
++ */
++struct mbr_t {
++ /* Boot code at offset 0 is unused in EFI */
++ /* Unique MBR Disk signature at offset 440 is unused */
++ /* The next 2 bytes are also unused */
++ /* Array of four MBR partition records. For protective MBR, only the first
++ * is valid
++ */
++ struct mbr_entry_t partitions[MBR_NUM_ENTRIES];
++ /* 0xAA55 */
++ uint16_t sig;
++} __attribute__((packed));
++
++/* A GPT partition entry. */
++ struct gpt_entry_t {
++ struct efi_guid_t partition_type; /* Partition type, defining purpose */
++ struct efi_guid_t unique_guid; /* Unique GUID that defines each partition */
++ uint64_t start; /* Starting LBA for partition */
++ uint64_t end; /* Ending LBA for partition */
++ uint64_t attr; /* Attribute bits */
++ char name[GPT_ENTRY_NAME_LENGTH]; /* Human readable name for partition */
++} __attribute__((packed));
++
++/* The GPT header. */
++struct gpt_header_t {
++ char signature[GPT_SIG_LEN]; /* "EFI PART" */
++ uint32_t revision; /* Revision number */
++ uint32_t size; /* Size of this header */
++ uint32_t header_crc; /* CRC of this header */
++ uint32_t reserved; /* Reserved */
++ uint64_t current_lba; /* LBA of this header */
++ uint64_t backup_lba; /* LBA of backup GPT header */
++ uint64_t first_lba; /* First usable LBA */
++ uint64_t last_lba; /* Last usable LBA */
++ struct efi_guid_t flash_guid; /* Disk GUID */
++ uint64_t array_lba; /* First LBA of array of partition entries */
++ uint32_t num_partitions; /* Number of partition entries in array */
++ uint32_t entry_size; /* Size of a single partition entry */
++ uint32_t array_crc; /* CRC of partitions array */
++} __attribute__((packed));
++
++/* A GUID partition table in memory. The array is not stored in memory
++ * due to its size
++ */
++struct gpt_t {
++ struct gpt_header_t header; /* GPT header */
++ uint32_t num_used_partitions; /* Number of in-use partitions */
++};
++
++/* A function for comparing some gpt entry's attribute with something known.
++ * Used to find entries of a certain kind, such as with a particular GUID,
++ * name or type.
++ */
++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 flash driver, used to perform I/O */
++static struct gpt_flash_driver_t *plat_flash_driver = NULL;
++
++/* Maximum partitions on platform */
++static uint32_t plat_max_partitions = 0;
++
++/* The primary GPT (also used if legacy MBR, but GPT header and partition
++ * entries are zero'd)
++ */
++static struct gpt_t primary_gpt = {0};
++
++/* Buffer to use for LBA I/O */
++static uint8_t lba_buf[TFM_GPT_BLOCK_SIZE] = {0};
++
++/* LBA that is cached in the buffer. Zero is valid only for protective MBR, all
++ * other GPT operations must have LBA of one or greater
++ */
++static uint64_t cached_lba = 0;
++
++/* Helper function prototypes */
++__attribute__((unused))
++static void print_guid(struct efi_guid_t guid);
++__attribute__((unused))
++static void dump_table(const struct gpt_t *table, bool header_only);
++__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 gpt_entry_per_lba_count(void);
++static psa_status_t count_used_partitions(const struct gpt_t *table,
++ uint32_t *num_used);
++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,
++ struct gpt_entry_t *entry);
++static psa_status_t read_table_from_flash(struct gpt_t *table, bool is_primary);
++static psa_status_t find_gpt_entry(const struct gpt_t *table,
++ gpt_entry_cmp_t compare,
++ const void *attr,
++ const uint32_t repeat_index,
++ struct gpt_entry_t *entry,
++ 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);
++
++/* PUBLIC API FUNCTIONS */
++
++psa_status_t gpt_entry_read(const struct efi_guid_t *guid,
++ 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_guid, guid, 0, &cached_entry, NULL);
++ if (ret != PSA_SUCCESS) {
++ 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;
++
++ return PSA_SUCCESS;
++}
++
++/* Initialises GPT from first block. */
++psa_status_t gpt_init(struct gpt_flash_driver_t *flash_driver, uint64_t max_partitions)
++{
++ cached_lba = 0;
++ 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) {
++ ERROR("I/O functions must be defined\n");
++ return PSA_ERROR_INVALID_ARGUMENT;
++ }
++
++ /* Retain information needed to perform I/O. */
++ if (plat_flash_driver == NULL) {
++ plat_flash_driver = flash_driver;
++ }
++ if (plat_max_partitions == 0) {
++ plat_max_partitions = max_partitions;
++ }
++ if (plat_flash_driver->init != NULL) {
++ if (plat_flash_driver->init() != 0) {
++ ERROR("Unable to initialise flash driver\n");
++ return PSA_ERROR_STORAGE_FAILURE;
++ }
++ }
++
++ struct mbr_t mbr;
++ psa_status_t ret = mbr_load(&mbr);
++ if (ret != PSA_SUCCESS) {
++ goto fail_load;
++ }
++
++ /* If the first record has type 0xEE (GPT protective), then the flash uses
++ * GPT. Else, treat it as legacy MBR
++ */
++ if (mbr.partitions[0].os_type == MBR_TYPE_GPT) {
++ ret = read_table_from_flash(&primary_gpt, true);
++ } else {
++ WARN("Unsupported legacy MBR in use\n");
++ ret = PSA_ERROR_NOT_SUPPORTED;
++ }
++
++ if (ret != PSA_SUCCESS) {
++ goto fail_load;
++ }
++
++ /* Count the number of used entries, assuming the array is not sparese */
++ ret = count_used_partitions(&primary_gpt, &primary_gpt.num_used_partitions);
++ if (ret != PSA_SUCCESS) {
++ goto fail_load;
++ }
++
++ /* Read the backup GPT and cache necessary values */
++ backup_gpt_lba = primary_gpt.header.backup_lba;
++ if (backup_gpt_lba != 0) {
++ struct gpt_t backup_gpt;
++ ret = read_table_from_flash(&backup_gpt, false);
++ if (ret != PSA_SUCCESS) {
++ goto fail_load;
++ }
++ } else {
++ WARN("Backup GPT location is unknown!\n");
++ }
++
++ return PSA_SUCCESS;
++
++fail_load:
++ /* Reset so that the user can try with something else if desired */
++ plat_flash_driver = NULL;
++ plat_max_partitions = 0;
++ backup_gpt_lba = 0;
++ cached_lba = 0;
++
++ return ret;
++}
++
++psa_status_t gpt_uninit(void)
++{
++ psa_status_t ret = PSA_SUCCESS;
++
++ if (plat_flash_driver) {
++ /* Uninitialise driver if function provided */
++ if (plat_flash_driver->uninit != NULL) {
++ if (plat_flash_driver->uninit() != 0) {
++ ERROR("Unable to uninitialise flash driver\n");
++ ret = PSA_ERROR_STORAGE_FAILURE;
++ }
++ }
++ }
++
++ plat_flash_driver = NULL;
++ plat_max_partitions = 0;
++ backup_gpt_lba = 0;
++ cached_lba = 0;
++
++ return ret;
++}
++
++/* Returns the number of partition entries in each LBA */
++static inline uint64_t gpt_entry_per_lba_count(void)
++{
++ static uint64_t num_entries = 0;
++ if (num_entries == 0) {
++ num_entries = TFM_GPT_BLOCK_SIZE / primary_gpt.header.entry_size;
++ }
++ return num_entries;
++}
++
++/* Compare the entry with the given guid */
++static bool gpt_entry_cmp_guid(const struct gpt_entry_t *entry, const void *guid)
++{
++ const struct efi_guid_t *cmp_guid = (const struct efi_guid_t *)guid;
++ const struct efi_guid_t entry_guid = entry->unique_guid;
++
++ return efi_guid_cmp(&entry_guid, cmp_guid) == 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,
++ const void *cmp_attr,
++ const uint32_t repeat_index,
++ struct gpt_entry_t *entry,
++ uint32_t *array_index)
++{
++ if (table->num_used_partitions == 0) {
++ return PSA_ERROR_DOES_NOT_EXIST;
++ }
++
++ uint32_t num_found = 0;
++ bool io_failure = false;
++ for (uint32_t i = 0; i < table->num_used_partitions; ++i) {
++ const psa_status_t ret = read_entry_from_flash(table, i, entry);
++ if (ret != PSA_SUCCESS) {
++ /* This might not have been the partition being sought after anyway,
++ * so may as well try the rest
++ */
++ io_failure = true;
++ continue;
++ }
++
++ if (compare(entry, cmp_attr) && num_found++ == repeat_index) {
++ if (array_index != NULL) {
++ *array_index = i;
++ }
++ return PSA_SUCCESS;
++ }
++ }
++
++ return io_failure ? PSA_ERROR_STORAGE_FAILURE : PSA_ERROR_DOES_NOT_EXIST;
++}
++
++/* Load MBR from flash */
++static psa_status_t mbr_load(struct mbr_t *mbr)
++{
++ /* Read the beginning of the first block of flash, which will contain either
++ * a legacy MBR or a protective MBR (in the case of GPT). The first
++ * MBR_UNUSED_BYTES are unused and so do not need to be considered.
++ */
++ ssize_t ret = plat_flash_driver->read(MBR_LBA, lba_buf);
++ if (ret != TFM_GPT_BLOCK_SIZE) {
++ ERROR("Unable to read from flash at block 0x%08x%08x\n",
++ (uint32_t)(MBR_LBA >> 32),
++ (uint32_t)MBR_LBA);
++ return PSA_ERROR_STORAGE_FAILURE;
++ }
++ memcpy(mbr, lba_buf + MBR_UNUSED_BYTES, sizeof(*mbr));
++
++ /* Check MBR boot signature */
++ if (mbr->sig != MBR_SIG) {
++ ERROR("MBR signature incorrect\n");
++ return PSA_ERROR_INVALID_ARGUMENT;
++ }
++
++ return PSA_SUCCESS;
++}
++
++/* Reads an LBA from the flash device and caches it. If the requested LBA is
++ * already cached, this is a no-op
++ */
++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 (ret != TFM_GPT_BLOCK_SIZE) {
++ ERROR("Unable to read from flash at block 0x%08x%08x\n",
++ (uint32_t)(required_lba >> 32),
++ (uint32_t)required_lba);
++ return PSA_ERROR_STORAGE_FAILURE;
++ }
++ cached_lba = required_lba;
++ }
++
++ return PSA_SUCCESS;
++}
++
++/* Returns the LBA that a particular partition entry is in based on its position
++ * in the array
++ */
++static uint64_t inline partition_entry_lba(const struct gpt_t *table,
++ uint32_t array_index)
++{
++ return table->header.array_lba + (array_index / gpt_entry_per_lba_count());
++}
++
++/* Returns the number of partition entries used in the array, assuming the
++ * array is not sparse
++ */
++static psa_status_t count_used_partitions(const struct gpt_t *table,
++ uint32_t *num_used)
++{
++ for (uint32_t i = 0; i < table->header.num_partitions; ++i) {
++ struct gpt_entry_t entry = {0};
++ const psa_status_t ret = read_entry_from_flash(table, i, &entry);
++ if (ret != PSA_SUCCESS) {
++ return ret;
++ }
++
++ const struct efi_guid_t null_guid = NULL_GUID;
++ const struct efi_guid_t entry_guid = entry.partition_type;
++ if (efi_guid_cmp(&null_guid, &entry_guid) == 0) {
++ *num_used = i;
++ return PSA_SUCCESS;
++ }
++ }
++
++ *num_used = table->header.num_partitions;
++ return PSA_SUCCESS;
++}
++
++/* Reads a GPT entry from the given table on flash */
++static psa_status_t read_entry_from_flash(const struct gpt_t *table,
++ uint32_t array_index,
++ struct gpt_entry_t *entry)
++{
++ uint64_t required_lba = partition_entry_lba(table, array_index);
++ const psa_status_t ret = read_from_flash(required_lba);
++ if (ret != PSA_SUCCESS) {
++ return ret;
++ }
++
++ memcpy(
++ entry,
++ lba_buf + ((array_index % gpt_entry_per_lba_count()) * table->header.entry_size),
++ GPT_ENTRY_SIZE);
++
++ return PSA_SUCCESS;
++}
++
++/* Reads a GPT table from flash */
++static psa_status_t read_table_from_flash(struct gpt_t *table, bool is_primary)
++{
++ if (!is_primary && backup_gpt_lba == 0) {
++ ERROR("Backup GPT location unknown!\n");
++ return PSA_ERROR_STORAGE_FAILURE;
++ }
++
++ const psa_status_t ret = read_from_flash(is_primary ? PRIMARY_GPT_LBA : backup_gpt_lba);
++ if (ret != PSA_SUCCESS) {
++ return ret;
++ }
++
++ memcpy(&(table->header), lba_buf, GPT_HEADER_SIZE);
++
++ return PSA_SUCCESS;
++}
++
++/* Converts unicode string to valid ascii */
++static psa_status_t unicode_to_ascii(const char *unicode, char *ascii)
++{
++ /* Check whether the unicode string is valid */
++ if (unicode[0] == '\0') {
++ return PSA_ERROR_INVALID_ARGUMENT;
++ }
++ for (int i = 1; i < GPT_ENTRY_NAME_LENGTH; i += 2) {
++ if (unicode[i] != '\0') {
++ return PSA_ERROR_INVALID_ARGUMENT;
++ }
++ }
++
++ /* Convert the unicode string to ascii string */
++ for (int i = 0; i < GPT_ENTRY_NAME_LENGTH; i += 2) {
++ ascii[i >> 1] = unicode[i];
++ if (unicode[i] == '\0') {
++ break;
++ }
++ }
++
++ return PSA_SUCCESS;
++}
++
++/* Prints guid in human readable format. Useful for debugging but should never
++ * be used in production, hence marked as unused
++ */
++static void print_guid(struct efi_guid_t guid)
++{
++ INFO("%04x%04x-%04x-%04x-%04x-%04x%04x%04x\n",
++ ((uint16_t *)&guid)[0],
++ ((uint16_t *)&guid)[1],
++ ((uint16_t *)&guid)[2],
++ ((uint16_t *)&guid)[3],
++ ((uint16_t *)&guid)[4],
++ ((uint16_t *)&guid)[5],
++ ((uint16_t *)&guid)[6],
++ ((uint16_t *)&guid)[7]);
++}
++
++/* Dumps header and optionally meta-data about array. Useful for debugging,
++ * but should never be used in production, hence marked as unused.
++ */
++static void dump_table(const struct gpt_t *table, bool header_only)
++{
++ /* Print the header first */
++ const struct gpt_header_t *header = &(table->header);
++ INFO("----------\n");
++ INFO("Signature: %8s\n", header->signature);
++ INFO("Revision: 0x%08x\n", header->revision);
++ INFO("HeaderSize: 0x%08x\n", header->size);
++ INFO("HeaderCRC32: 0x%08x\n", header->header_crc);
++ INFO("Reserved: 0x%08x\n", header->reserved);
++ INFO("MyLBA: 0x%08x%08x\n",
++ (uint32_t)(header->current_lba >> 32),
++ (uint32_t)(header->current_lba));
++ INFO("AlternateLBA: 0x%08x%08x\n",
++ (uint32_t)(header->backup_lba >> 32),
++ (uint32_t)(header->backup_lba));
++ INFO("FirstUsableLBA: 0x%08x%08x\n",
++ (uint32_t)(header->first_lba >> 32),
++ (uint32_t)(header->first_lba));
++ INFO("LastUsableLBA: 0x%08x%08x\n",
++ (uint32_t)(header->last_lba >> 32),
++ (uint32_t)(header->last_lba));
++ INFO("DiskGUID: ");
++ print_guid(header->flash_guid);
++ INFO("ParitionEntryLBA: 0x%08x%08x\n",
++ (uint32_t)(header->array_lba >> 32),
++ (uint32_t)(header->array_lba));
++ INFO("NumberOfPartitionEntries: 0x%08x\n", header->num_partitions);
++ INFO("SizeOfPartitionEntry: 0x%08x\n", header->entry_size);
++ INFO("PartitionEntryArrayCRC32: 0x%08x\n", header->array_crc);
++ INFO("----------\n");
++
++ if (!header_only) {
++ /* Now print meta-data for each entry, including those not in use */
++ for (uint32_t i = 0; i < table->num_used_partitions; ++i) {
++ struct gpt_entry_t entry;
++ psa_status_t ret = read_entry_from_flash(&primary_gpt, i, &entry);
++ if (ret != PSA_SUCCESS) {
++ continue;
++ }
++
++ INFO("Entry number: %u\n", i);
++ INFO("\tPartitionTypeGUID: ");
++ print_guid(entry.partition_type);
++ INFO("\tUniquePartitionGUID: ");
++ print_guid(entry.unique_guid);
++ INFO("\tStartingLBA: 0x%08x%08x\n",
++ (uint32_t)(entry.start >> 32),
++ (uint32_t)(entry.start));
++ INFO("\tEndingLBA: 0x%08x%08x\n",
++ (uint32_t)(entry.end >> 32),
++ (uint32_t)(entry.end));
++ INFO("\tAttributes: 0x%08x%08x\n",
++ (uint32_t)(entry.attr >> 32),
++ (uint32_t)(entry.attr));
++ char name[GPT_ENTRY_NAME_LENGTH >> 1];
++ if (unicode_to_ascii(entry.name, name) != PSA_SUCCESS) {
++ INFO("\tPartitionName: [Not valid ascii]\n");
++ } else {
++ INFO("\tPartitionName: %s\n", name);
++ }
++ }
++ }
++}
+diff --git a/lib/gpt/unittests/CMakeLists.txt b/lib/gpt/unittests/CMakeLists.txt
+new file mode 100644
+index 000000000..61c7078e5
+--- /dev/null
++++ b/lib/gpt/unittests/CMakeLists.txt
+@@ -0,0 +1,179 @@
++#-------------------------------------------------------------------------------
++# SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
++#
++# SPDX-License-Identifier: BSD-3-Clause
++#
++#-------------------------------------------------------------------------------
++cmake_minimum_required(VERSION 3.21)
++
++if (NOT DEFINED TFM_UNITTESTS_PATHS)
++ message(FATAL_ERROR "Please provide absolute paths to the unittests using -DTFM_UNITTESTS_PATHS=<path1;path2;...>")
++endif()
++
++if (NOT DEFINED TFM_ROOT_DIR)
++ message(FATAL_ERROR "Please provide absolute paths to the TF-M root directory using -DTFM_ROOT_DIR=<path>")
++endif()
++
++list(APPEND CMAKE_MODULE_PATH ${TFM_ROOT_DIR}/cmake)
++
++project(
++ "tfm_gpt_unit_tests"
++ VERSION 1.0.0
++ LANGUAGES C
++)
++
++enable_testing()
++include(CTest)
++
++find_package(Ruby)
++find_program(LCOV lcov REQUIRED)
++find_program(GENHTML genhtml REQUIRED)
++
++set(UNITY_SRC_DIR ${TFM_ROOT_DIR}/platform/ext/target/arm/rse/common/unittests/framework/unity)
++set(CMOCK_SRC_DIR ${TFM_ROOT_DIR}/platform/ext/target/arm/rse/common/unittests/framework/cmock)
++
++add_subdirectory(${UNITY_SRC_DIR} ${UNITY_SRC_DIR})
++add_subdirectory(${CMOCK_SRC_DIR} ${CMOCK_SRC_DIR})
++
++function(generate_test UNIT_NAME UNIT_PATH)
++ include(${UNIT_PATH}/utcfg.cmake)
++
++ # Generate runner for the test
++ file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/unittests/${UNIT_NAME})
++ add_custom_command(
++ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/unittests/${UNIT_NAME}/test_runner.c
++ COMMAND ${Ruby_EXECUTABLE}
++ ${UNITY_PATH}/auto/generate_test_runner.rb
++ ${UNITY_SRC_DIR}/cfg.yml
++ ${UNIT_TEST_SUITE}
++ ${CMAKE_CURRENT_BINARY_DIR}/unittests/${UNIT_NAME}/test_runner.c
++ COMMENT "Generating test runner for ${UNIT_NAME}"
++ )
++
++ # test_<unit-name> is the runner for <unit-name>
++ add_executable(test_${UNIT_NAME} ${UNIT_UNDER_TEST}
++ ${UNIT_TEST_SUITE}
++ ${CMAKE_CURRENT_BINARY_DIR}/unittests/${UNIT_NAME}/test_runner.c
++ ${UNIT_TEST_DEPS}
++ )
++
++ # Enable debug syms & coverage support while compiling
++ target_compile_options(test_${UNIT_NAME}
++ PUBLIC
++ -g3
++ -fprofile-arcs
++ -ftest-coverage
++ )
++
++ target_link_libraries(test_${UNIT_NAME}
++ PRIVATE cmock
++ PRIVATE unity
++ PRIVATE gcov
++ ${UNIT_TEST_LINK_LIBS}
++ )
++
++ target_include_directories(test_${UNIT_NAME}
++ PUBLIC
++ ${UNIT_TEST_INCLUDE_DIRS}
++ )
++
++ # UNIT_TEST must always be defined, each unit test may define more
++ target_compile_definitions(test_${UNIT_NAME}
++ PUBLIC
++ UNIT_TEST
++ ${UNIT_TEST_COMPILE_DEFS}
++ )
++
++ # For every <unit> in, we build test_<unit>
++ add_test(
++ NAME ${UNIT_NAME}
++ COMMAND test_${UNIT_NAME}
++ )
++
++ # Generate mocks
++ foreach(FILEPATH_TO_MOCK ${MOCK_HEADERS})
++ get_filename_component(FILENAME_TO_MOCK ${FILEPATH_TO_MOCK} NAME_WE)
++ set(MOCK_TARGET ${UNIT_NAME}_mock_${FILENAME_TO_MOCK})
++
++ # Invoke CMock to generate mock_<filename>
++ add_custom_command(
++ OUTPUT
++ ${CMAKE_BINARY_DIR}/unittests/${UNIT_NAME}/mock_${FILENAME_TO_MOCK}.c
++ COMMAND ${CMAKE_COMMAND} -E env
++ UNITY_DIR=${UNITY_PATH}
++ ${Ruby_EXECUTABLE}
++ ${CMOCK_PATH}/lib/cmock.rb
++ -o${CMOCK_SRC_DIR}/cfg.yml
++ ${FILEPATH_TO_MOCK}
++ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/unittests/${UNIT_NAME}
++ DEPENDS ${FILEPATH_TO_MOCK}
++ COMMENT "Mocking ${FILEPATH_TO_MOCK} for ${UNIT_NAME}"
++ )
++
++ target_sources(
++ test_${UNIT_NAME} PRIVATE
++ ${CMAKE_BINARY_DIR}/unittests/${UNIT_NAME}/mock_${FILENAME_TO_MOCK}.c
++ )
++
++ target_include_directories(
++ test_${UNIT_NAME} PRIVATE
++ ${CMAKE_BINARY_DIR}/unittests/${UNIT_NAME}
++ )
++ endforeach()
++endfunction()
++
++# unittests target
++# Depends on
++# - All tests detected
++# Performs
++# - Execute all tests
++# - Generate and filter coverage info
++# - Convert report into browsable html
++add_custom_target(unittests)
++
++add_custom_command(
++ TARGET unittests
++ POST_BUILD
++ COMMAND ${CMAKE_CTEST_COMMAND}
++ ARGS
++ --output-on-failure
++ COMMAND ${LCOV}
++ ARGS
++ --capture
++ --directory ${CMAKE_CURRENT_BINARY_DIR}
++ --output-file raw_test_coverage.info
++ > lcov.log 2>&1
++ COMMAND ${LCOV}
++ ARGS
++ --remove raw_test_coverage.info
++ -o test_coverage.info
++ '/usr/*'
++ '${CMAKE_BINARY_DIR}/*'
++ '*tests*'
++ >> lcov.log 2>&1
++ COMMAND ${GENHTML}
++ ARGS
++ test_coverage.info
++ --output-directory ${CMAKE_BINARY_DIR}/coverage_report
++ >> lcov.log 2>&1
++ COMMAND ${CMAKE_COMMAND}
++ ARGS
++ -E echo "Coverage Report: file://${CMAKE_BINARY_DIR}/coverage_report/index.html"
++)
++
++foreach(TFM_UNITTESTS_PATH ${TFM_UNITTESTS_PATHS})
++ if (NOT EXISTS ${TFM_UNITTESTS_PATH})
++ message(FATAL_ERROR "Path ${TFM_UNITTESTS_PATH} does not exist.")
++ endif()
++
++ file(GLOB_RECURSE UNIT_PATHS LIST_DIRECTORIES TRUE "${TFM_UNITTESTS_PATH}/*")
++ foreach(UNIT_PATH ${UNIT_PATHS})
++ # Only for directories that contain a utcfg.cmake
++ if(IS_DIRECTORY ${UNIT_PATH} AND EXISTS ${UNIT_PATH}/utcfg.cmake)
++ get_filename_component(UNIT_NAME ${UNIT_PATH} NAME)
++ message(STATUS "Found ${UNIT_NAME}")
++ generate_test(${UNIT_NAME} ${UNIT_PATH})
++ add_dependencies(unittests test_${UNIT_NAME})
++ endif()
++ endforeach()
++endforeach()
+\ No newline at end of file
+diff --git a/lib/gpt/unittests/gpt/test_gpt.c b/lib/gpt/unittests/gpt/test_gpt.c
+new file mode 100644
+index 000000000..325887fae
+--- /dev/null
++++ b/lib/gpt/unittests/gpt/test_gpt.c
+@@ -0,0 +1,383 @@
++/*
++ * SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
++ *
++ * SPDX-License-Identifier: BSD-3-Clause
++ *
++ */
++
++#include <string.h>
++#include <inttypes.h>
++
++#include "unity.h"
++
++#include "mock_tfm_log.h"
++#include "mock_tfm_vprintf.h"
++
++#include "efi_guid_structs.h"
++#include "gpt_flash.h"
++#include "gpt.h"
++#include "psa/error.h"
++
++/* Basic mocked disk layout and number of partitions */
++#define TEST_BLOCK_SIZE 512
++#define TEST_DISK_NUM_BLOCKS 128
++#define TEST_MAX_PARTITIONS 4
++#define TEST_DEFAULT_NUM_PARTITIONS TEST_MAX_PARTITIONS - 1
++
++/* Maximum number of mocked reads per test */
++#define TEST_MOCK_BUFFER_SIZE 512
++
++/* Master Boot Record (MBR) definitions for test */
++#define TEST_MBR_SIG 0xAA55
++#define TEST_MBR_TYPE_GPT 0xEE
++#define TEST_MBR_CHS_ADDRESS_LEN 3
++#define TEST_MBR_NUM_PARTITIONS 4
++#define TEST_MBR_UNUSED_BYTES 446
++
++/* GUID Partition Table (GPT) header values */
++#define TEST_GPT_SIG_LEN 8
++#define TEST_GPT_SIG_INITIALISER {'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T'}
++#define TEST_GPT_REVISION 0x00010000
++#define TEST_GPT_HEADER_SIZE 92
++#define TEST_GPT_CRC32 42
++#define TEST_GPT_PRIMARY_LBA 1
++#define TEST_GPT_BACKUP_LBA (TEST_DISK_NUM_BLOCKS - 1)
++#define TEST_GPT_ARRAY_LBA (TEST_GPT_PRIMARY_LBA + 1)
++#define TEST_GPT_BACKUP_ARRAY_LBA (TEST_GPT_BACKUP_LBA - 1)
++#define TEST_GPT_FIRST_USABLE_LBA (TEST_GPT_ARRAY_LBA + 2)
++#define TEST_GPT_LAST_USABLE_LBA (TEST_GPT_BACKUP_LBA - 2)
++#define TEST_GPT_DISK_GUID MAKE_EFI_GUID(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
++#define TEST_GPT_ENTRY_SIZE 128
++
++/* Test data. These are defined to be not fragmented */
++#define TEST_GPT_FIRST_PARTITION_START TEST_GPT_FIRST_USABLE_LBA
++#define TEST_GPT_FIRST_PARTITION_END (TEST_GPT_FIRST_PARTITION_START + 3)
++#define TEST_GPT_SECOND_PARTITION_START (TEST_GPT_FIRST_PARTITION_END + 1)
++#define TEST_GPT_SECOND_PARTITION_END (TEST_GPT_SECOND_PARTITION_START + 50)
++#define TEST_GPT_THIRD_PARTITION_START (TEST_GPT_SECOND_PARTITION_END + 1)
++#define TEST_GPT_THIRD_PARTITION_END (TEST_GPT_THIRD_PARTITION_START + 1)
++
++/* Populates a backup header from a primary header and calculates the new CRC32 */
++#define MAKE_BACKUP_HEADER(backup, primary) \
++ do { \
++ memcpy(&backup, &primary, TEST_GPT_HEADER_SIZE); \
++ backup.header_crc = TEST_GPT_CRC32; \
++ backup.current_lba = primary.backup_lba; \
++ backup.backup_lba = primary.current_lba; \
++ backup.array_lba = TEST_GPT_BACKUP_ARRAY_LBA; \
++ } while (0)
++
++/* MBR partition entry */
++struct mbr_entry_t {
++ /* Indicates if bootable */
++ uint8_t status;
++ /* For legacy MBR, not used by UEFI firmware. For protective MBR, set to
++ * 0x000200
++ */
++ uint8_t first_sector[TEST_MBR_CHS_ADDRESS_LEN];
++ /* Type of partition */
++ uint8_t os_type;
++ /* For legacy MBR, not used by UEFI firmware. For protective MBR, last
++ * block on disk.
++ */
++ uint8_t last_sector[TEST_MBR_CHS_ADDRESS_LEN];
++ /* For legacy MBR, starting LBA of partition. For protective MBR, set to
++ * 0x00000001
++ */
++ uint32_t first_lba;
++ /* For legacy MBR, size of partition. For protective MBR, size of disk
++ * minus one
++ */
++ uint32_t size;
++} __attribute__((packed));
++
++/* Master Boot Record. */
++struct mbr_t {
++ /* Unused bytes */
++ uint8_t unused[TEST_MBR_UNUSED_BYTES];
++ /* Array of four MBR partition records. For protective MBR, only the first
++ * is valid
++ */
++ struct mbr_entry_t partitions[TEST_MBR_NUM_PARTITIONS];
++ /* 0xAA55 */
++ uint16_t sig;
++} __attribute__((packed));
++
++/* A gpt partition entry */
++struct gpt_entry_t {
++ struct efi_guid_t type; /* Partition type */
++ struct efi_guid_t guid; /* Unique GUID */
++ uint64_t start; /* Starting LBA for partition */
++ uint64_t end; /* Ending LBA for partition */
++ uint64_t attr; /* Attribute bits */
++ char name[GPT_ENTRY_NAME_LENGTH]; /* Human readable name for partition */
++} __attribute__((packed));
++
++/* The gpt header */
++struct gpt_header_t {
++ char signature[TEST_GPT_SIG_LEN]; /* "EFI PART" */
++ uint32_t revision; /* Revision number. */
++ uint32_t size; /* Size of this header */
++ uint32_t header_crc; /* CRC of this header */
++ uint32_t reserved; /* Reserved */
++ uint64_t current_lba; /* LBA of this header */
++ uint64_t backup_lba; /* LBA of backup GPT header */
++ uint64_t first_lba; /* First usable LBA */
++ uint64_t last_lba; /* Last usable LBA */
++ struct efi_guid_t disk_guid; /* Disk GUID */
++ uint64_t array_lba; /* First LBA of array of partition entries */
++ uint32_t num_partitions; /* Number of partition entries in array */
++ uint32_t entry_size; /* Size of a single partition entry */
++ uint32_t array_crc; /* CRC of partition entry array */
++} __attribute__((packed));
++
++static void register_mocked_read(void *buf, size_t num_bytes);
++static ssize_t test_driver_read(uint64_t lba, void *buf);
++
++/* LBA driver used in test module */
++static struct gpt_flash_driver_t mock_driver = {
++ .init = NULL,
++ .uninit = NULL,
++ .read = test_driver_read,
++};
++
++/* Valid MBR. Only signature is required to be valid */
++static struct mbr_t default_mbr = {
++ .unused = {0},
++ .sig = TEST_MBR_SIG,
++};
++static struct mbr_t test_mbr;
++
++/* Default GPT header. CRC values need to be populated to be valid. */
++static struct gpt_header_t default_header = {
++ .signature = TEST_GPT_SIG_INITIALISER,
++ .revision = TEST_GPT_REVISION,
++ .size = TEST_GPT_HEADER_SIZE,
++ .header_crc = TEST_GPT_CRC32,
++ .reserved = 0,
++ .current_lba = TEST_GPT_PRIMARY_LBA,
++ .backup_lba = TEST_GPT_BACKUP_LBA,
++ .first_lba = TEST_GPT_FIRST_USABLE_LBA,
++ .last_lba = TEST_GPT_LAST_USABLE_LBA,
++ .disk_guid = TEST_GPT_DISK_GUID,
++ .array_lba = TEST_GPT_ARRAY_LBA,
++ .num_partitions = TEST_MAX_PARTITIONS,
++ .entry_size = TEST_GPT_ENTRY_SIZE,
++ .array_crc = TEST_GPT_CRC32
++};
++static struct gpt_header_t test_header;
++
++/* Default entry array. This is valid, though fragmented. */
++static struct gpt_entry_t default_partition_array[TEST_DEFAULT_NUM_PARTITIONS] = {
++ {
++ .type = MAKE_EFI_GUID(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11),
++ .guid = MAKE_EFI_GUID(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11),
++ .start = TEST_GPT_FIRST_PARTITION_START,
++ .end = TEST_GPT_FIRST_PARTITION_END,
++ .attr = 0,
++ .name = "First partition"
++ },
++ {
++ .type = MAKE_EFI_GUID(2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11),
++ .guid = MAKE_EFI_GUID(2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11),
++ .start = TEST_GPT_SECOND_PARTITION_START,
++ .end = TEST_GPT_SECOND_PARTITION_END,
++ .attr = 0,
++ .name = "Second partition"
++ },
++ {
++ .type = MAKE_EFI_GUID(3, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11),
++ .guid = MAKE_EFI_GUID(3, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11),
++ .start = TEST_GPT_THIRD_PARTITION_START,
++ .end = TEST_GPT_THIRD_PARTITION_END,
++ .attr = 0,
++ .name = "Third partition"
++ }
++};
++static struct gpt_entry_t test_partition_array[TEST_MAX_PARTITIONS];
++
++/* Set to determine what is "read" by the flash driver. Allows for the mocking
++ * of multiple read calls
++ */
++static unsigned int num_mocked_reads = 0;
++static unsigned int registered_mocked_reads = 0;
++static uint8_t mock_read_buffer[TEST_MOCK_BUFFER_SIZE][TEST_BLOCK_SIZE] = {0};
++
++/* Turn ascii string to unicode */
++static void ascii_to_unicode(const char *ascii, char *unicode)
++{
++ for (int i = 0; i < strlen(ascii) + 1; ++i) {
++ unicode[i << 1] = ascii[i];
++ unicode[(i << 1) + 1] = '\0';
++ }
++}
++
++/* Tell the test what to return from the next read call. This is very much
++ * whitebox testing
++ */
++static void register_mocked_read(void *data, size_t num_bytes)
++{
++ memcpy(mock_read_buffer[registered_mocked_reads++], data, num_bytes);
++}
++
++/* Driver function that always succeeds in reading the data it has been given */
++static ssize_t test_driver_read(uint64_t lba, void *buf)
++{
++ memcpy(buf, mock_read_buffer[num_mocked_reads++], TEST_BLOCK_SIZE);
++ return TEST_BLOCK_SIZE;
++}
++
++/* Creates backup table from test table and registers a read for it */
++static void setup_backup_gpt(void)
++{
++ struct gpt_header_t backup_header;
++ MAKE_BACKUP_HEADER(backup_header, test_header);
++
++ register_mocked_read(&backup_header, sizeof(backup_header));
++}
++
++/* Uses the test MBR and GPT header to initialise for tests */
++static psa_status_t setup_test_gpt(void)
++{
++ /* Expect first a valid MBR read */
++ register_mocked_read(&test_mbr, sizeof(test_mbr));
++
++ /* Expect a GPT header read second */
++ register_mocked_read(&test_header, sizeof(test_header));
++
++ /* Expect third each partition is read to find the number in use (if the
++ * number in the header is non-zero)
++ */
++ if (test_header.num_partitions != 0) {
++ register_mocked_read(&test_partition_array, sizeof(test_partition_array));
++ }
++
++ /* Expect fourth the backup to be read */
++ setup_backup_gpt();
++
++ return gpt_init(&mock_driver, TEST_MAX_PARTITIONS);
++}
++
++/* Ensures a valid GPT populated with the default entries is initialised */
++static void setup_valid_gpt(void)
++{
++ TEST_ASSERT_EQUAL(PSA_SUCCESS, setup_test_gpt());
++}
++
++/* Ensures a valid but empty GPT is initialised */
++static void setup_empty_gpt(void)
++{
++ test_header.num_partitions = 0;
++ TEST_ASSERT_EQUAL(PSA_SUCCESS, setup_test_gpt());
++}
++
++void setUp(void)
++{
++ /* Default starting points */
++ test_mbr = default_mbr;
++ test_header = default_header;
++ memcpy(&test_partition_array, &default_partition_array, sizeof(default_partition_array));
++ for (size_t i = 0; i < TEST_DEFAULT_NUM_PARTITIONS; ++i) {
++ char unicode_name[GPT_ENTRY_NAME_LENGTH] = {'\0'};
++ ascii_to_unicode(test_partition_array[i].name, unicode_name);
++ memcpy(test_partition_array[i].name, unicode_name, GPT_ENTRY_NAME_LENGTH);
++ }
++
++ test_mbr.partitions[0].os_type = TEST_MBR_TYPE_GPT;
++
++ /* Ignore all logging calls */
++ tfm_log_Ignore();
++
++ num_mocked_reads = 0;
++ registered_mocked_reads = 0;
++ memset(mock_read_buffer, 0, sizeof(mock_read_buffer));
++}
++
++void tearDown(void)
++{
++ num_mocked_reads = 0;
++ registered_mocked_reads = 0;
++ memset(mock_read_buffer, 0, sizeof(mock_read_buffer));
++ memset(&test_partition_array, 0, sizeof(test_partition_array));
++ gpt_uninit();
++}
++
++void test_gpt_init_should_loadWhenGptGood(void)
++{
++ setup_valid_gpt();
++}
++
++void test_gpt_init_should_overwriteOldGpt(void)
++{
++ setup_valid_gpt();
++ gpt_uninit();
++
++ /* Use a different disk GUID */
++ const struct efi_guid_t new_guid = MAKE_EFI_GUID(1, 1, 3, 4, 5, 6 ,7 ,8, 9, 10, 11);
++ test_header.disk_guid = new_guid;
++
++ setup_valid_gpt();
++}
++
++void test_gpt_init_should_failWhenMbrSigBad(void)
++{
++ test_mbr.sig--;
++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, setup_test_gpt());
++}
++
++void test_gpt_init_should_failWhenMbrTypeInvalid(void)
++{
++ test_mbr.partitions[0].os_type--;
++ TEST_ASSERT_EQUAL(PSA_ERROR_NOT_SUPPORTED, setup_test_gpt());
++}
++
++void test_gpt_init_should_failWhenFlashDriverNotFullyDefined(void)
++{
++ gpt_flash_read_t read_fn = mock_driver.read;
++ mock_driver.read = NULL;
++ TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_ARGUMENT, gpt_init(&mock_driver, TEST_MAX_PARTITIONS));
++ mock_driver.read = read_fn;
++}
++
++void test_gpt_entry_read_should_populateEntry(void)
++{
++ /* Start with a populated GPT */
++ setup_valid_gpt();
++
++ /* Ensure an entry is found */
++ struct partition_entry_t entry;
++ struct gpt_entry_t *desired = &(test_partition_array[TEST_DEFAULT_NUM_PARTITIONS - 1]);
++ struct efi_guid_t test_guid = desired->guid;
++ struct efi_guid_t test_type = desired->type;
++ register_mocked_read(&test_partition_array, sizeof(test_partition_array));
++ TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_entry_read(&test_guid, &entry));
++
++ /* Ensure this is the correct entry */
++ TEST_ASSERT_EQUAL(0, efi_guid_cmp(&test_guid, &(entry.partition_guid)));
++ TEST_ASSERT_EQUAL(0, efi_guid_cmp(&test_type, &(entry.type_guid)));
++ TEST_ASSERT_EQUAL(desired->start, entry.start);
++
++ /* Size is number of blocks, so subtract one */
++ TEST_ASSERT_EQUAL(desired->end, entry.start + entry.size - 1);
++
++ /* Name is unicode */
++ TEST_ASSERT_EQUAL_MEMORY(desired->name, entry.name, GPT_ENTRY_NAME_LENGTH);
++}
++
++void test_gpt_entry_read_should_failWhenEntryNotExisting(void)
++{
++ /* Start with an empty GPT */
++ setup_empty_gpt();
++
++ /* Try to read something */
++ struct partition_entry_t entry;
++ struct efi_guid_t non_existing = NULL_GUID;
++ TEST_ASSERT_EQUAL(PSA_ERROR_DOES_NOT_EXIST, gpt_entry_read(&non_existing, &entry));
++
++ /* 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_read(&non_existing, &entry));
++}
+diff --git a/lib/gpt/unittests/gpt/utcfg.cmake b/lib/gpt/unittests/gpt/utcfg.cmake
+new file mode 100644
+index 000000000..37d72d138
+--- /dev/null
++++ b/lib/gpt/unittests/gpt/utcfg.cmake
+@@ -0,0 +1,28 @@
++#-------------------------------------------------------------------------------
++# SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
++#
++# SPDX-License-Identifier: BSD-3-Clause
++#
++#-------------------------------------------------------------------------------
++
++# Define what is being unit tested and by what
++set(UNIT_UNDER_TEST ${TFM_ROOT_DIR}/lib/gpt/src/gpt.c)
++set(UNIT_TEST_SUITE ${CMAKE_CURRENT_LIST_DIR}/test_gpt.c)
++
++# Dependencies for the UUT, that get linked into the executable
++
++# Include directories for compilation
++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/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)
++
++# Compile-time definitions
++list(APPEND UNIT_TEST_COMPILE_DEFS LOG_LEVEL=LOG_LEVEL_VERBOSE)
++list(APPEND UNIT_TEST_COMPILE_DEFS TFM_GPT_BLOCK_SIZE=512)
++
new file mode 100644
@@ -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));
++}
new file mode 100644
@@ -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)
new file mode 100644
@@ -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 */
new file mode 100644
@@ -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
new file mode 100644
@@ -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
new file mode 100644
@@ -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
new file mode 100644
@@ -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
+ */
new file mode 100644
@@ -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
new file mode 100644
@@ -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
new file mode 100644
@@ -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
+ )
new file mode 100644
@@ -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
new file mode 100644
@@ -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)
+
new file mode 100644
@@ -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>
+ )
+
new file mode 100644
@@ -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"
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> --- ...-lib-efi_guid-Added-EFI-GUID-library.patch | 217 +++ ...b-efi_soft_crc-Added-EFI-CRC-library.patch | 136 ++ ...emented-generic-GPT-parser-for-flash.patch | 1495 +++++++++++++++++ ...-how-GPT-partition-can-be-identified.patch | 308 ++++ ...dded-operations-to-modify-partitions.patch | 961 +++++++++++ ...ib-gpt-Added-operation-to-move-entry.patch | 374 +++++ ...ility-to-create-and-remove-partition.patch | 713 ++++++++ ...pt-Added-table-validation-operations.patch | 412 +++++ ...-gpt-Added-defragmentation-operation.patch | 280 +++ .../0026-lib-GPT-Fix-cppcheck-warnings.patch | 74 + ...uid-Remove-unecessary-include-folder.patch | 28 + ...lib-efi_guid-Correct-included-folder.patch | 28 + ...i_soft_crc-Correct-include-directory.patch | 25 + ...030-lib-gpt-Add-missing-link-library.patch | 27 + ...1-lib-gpt-Correct-variable-name-used.patch | 24 + ...32-lib-gpt-Correct-include-directory.patch | 27 + ...t-Move-contents-of-CMake-config-file.patch | 51 + .../trusted-firmware-m-corstone1000.inc | 17 + 18 files changed, 5197 insertions(+) create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0017-lib-efi_guid-Added-EFI-GUID-library.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0018-lib-efi_soft_crc-Added-EFI-CRC-library.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0019-lib-gpt-Implemented-generic-GPT-parser-for-flash.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0020-lib-gpt-Expanded-how-GPT-partition-can-be-identified.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0021-lib-gpt-Added-operations-to-modify-partitions.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0022-lib-gpt-Added-operation-to-move-entry.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0023-lib-gpt-Added-ability-to-create-and-remove-partition.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0024-lib-gpt-Added-table-validation-operations.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0025-lib-gpt-Added-defragmentation-operation.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0026-lib-GPT-Fix-cppcheck-warnings.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0027-lib-efi_guid-Remove-unecessary-include-folder.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0028-lib-efi_guid-Correct-included-folder.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0029-lib-efi_soft_crc-Correct-include-directory.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0030-lib-gpt-Add-missing-link-library.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0031-lib-gpt-Correct-variable-name-used.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0032-lib-gpt-Correct-include-directory.patch create mode 100644 meta-arm-bsp/recipes-bsp/trusted-firmware-m/files/corstone1000/0033-lib-gpt-Move-contents-of-CMake-config-file.patch