Skip to content

Commit 21ff1f8

Browse files
frazer-armadeaarm
authored andcommitted
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>
1 parent 62a038e commit 21ff1f8

3 files changed

Lines changed: 205 additions & 0 deletions

File tree

lib/gpt/inc/gpt.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,14 @@ psa_status_t gpt_validate(bool is_primary);
229229
*/
230230
psa_status_t gpt_restore(bool is_primary);
231231

232+
/**
233+
* \brief Defragments the GPT, ensuring free space becomes contiguous.
234+
*
235+
* \retval PSA_SUCCESS Success.
236+
* \retval PSA_ERROR_STORAGE_FAILURE I/O failure.
237+
*/
238+
psa_status_t gpt_defragment(void);
239+
232240
/**
233241
* \brief Reads the GPT header from the second block (LBA 1).
234242
*

lib/gpt/src/gpt.c

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include <stdbool.h>
88
#include <stdint.h>
9+
#include <stdlib.h>
910
#include <string.h>
1011
#include <inttypes.h>
1112

@@ -225,6 +226,7 @@ static bool gpt_entry_cmp_name(const struct gpt_entry_t *entry, const void *name
225226
static bool gpt_entry_cmp_type(const struct gpt_entry_t *entry, const void *type);
226227
static psa_status_t validate_table(struct gpt_t *table, bool is_primary);
227228
static psa_status_t restore_table(struct gpt_t *restore_from, bool is_primary);
229+
static psa_status_t sort_partition_array(struct gpt_t *table);
228230

229231
/* PUBLIC API FUNCTIONS */
230232

@@ -819,6 +821,66 @@ psa_status_t gpt_restore(bool is_primary)
819821
}
820822
}
821823

824+
psa_status_t gpt_defragment(void)
825+
{
826+
/* First, sort the partition array according to start LBA. This means that
827+
* moving partitions towards the start of the flash sequentially is safe
828+
* and will not result in lost data.
829+
*/
830+
psa_status_t ret = sort_partition_array(&primary_gpt);
831+
if (ret != PSA_SUCCESS) {
832+
WARN("Unable to defragment flash!\n");
833+
return ret;
834+
}
835+
836+
uint64_t prev_end = primary_gpt.header.first_lba;
837+
struct gpt_entry_t entry;
838+
839+
for (uint32_t i = 0; i < primary_gpt.num_used_partitions; ++i) {
840+
ret = read_entry_from_flash(&primary_gpt, i, &entry);
841+
if (ret != PSA_SUCCESS) {
842+
return ret;
843+
}
844+
845+
/* Move to be next to previous entry. Continue if already where it
846+
* needs to be.
847+
*/
848+
if (prev_end == entry.start) {
849+
prev_end = entry.end + 1;
850+
continue;
851+
}
852+
853+
const uint64_t num_blocks = entry.end - entry.start + 1;
854+
ret = move_partition(entry.start, prev_end, num_blocks);
855+
if (ret != PSA_SUCCESS) {
856+
return ret;
857+
}
858+
859+
/* Update header information */
860+
entry.start = prev_end;
861+
entry.end = entry.start + num_blocks - 1;
862+
prev_end = entry.end + 1;
863+
864+
/* Write the entry change, skipping header update until every entry
865+
* written
866+
*/
867+
ret = write_entry(i, &entry, true);
868+
if (ret != PSA_SUCCESS) {
869+
return ret;
870+
}
871+
}
872+
873+
/* Write everything to flash after defragmentation if not done so already.
874+
* The previous loop will write the last entry to the LBA buffer, which may
875+
* or not may not be flushed
876+
*/
877+
if (write_buffered) {
878+
return flush_lba_buf();
879+
}
880+
881+
return update_header(primary_gpt.num_used_partitions);
882+
}
883+
822884
/* Initialises GPT from first block. */
823885
psa_status_t gpt_init(struct gpt_flash_driver_t *flash_driver, uint64_t max_partitions)
824886
{
@@ -1533,6 +1595,134 @@ static psa_status_t restore_table(struct gpt_t *restore_from, bool is_primary)
15331595
return 0;
15341596
}
15351597

1598+
/* Comparison function to pass to qsort */
1599+
static int cmp_u64(const void *a, const void *b)
1600+
{
1601+
const uint64_t *a_u64 = (const uint64_t *)a;
1602+
const uint64_t *b_u64 = (const uint64_t *)b;
1603+
return (*a_u64 > *b_u64) - (*a_u64 < *b_u64);
1604+
}
1605+
1606+
/* bsearch but returns the index rather than the item */
1607+
static int64_t bsearch_index(uint64_t arr[], uint32_t len, uint64_t key)
1608+
{
1609+
uint32_t l = 0;
1610+
uint32_t r = len;
1611+
1612+
while (l < r) {
1613+
uint32_t m = l + (r - l) / 2;
1614+
uint64_t item = arr[m];
1615+
1616+
if (item < key) {
1617+
l = m + 1;
1618+
} else if (item > key) {
1619+
r = m;
1620+
} else {
1621+
return (int64_t)m;
1622+
}
1623+
}
1624+
1625+
return -1;
1626+
}
1627+
1628+
/* Sorts the partition array for the given table by the start LBA for each
1629+
* partition. This makes defragmentation easier.
1630+
*/
1631+
static psa_status_t sort_partition_array(struct gpt_t *table)
1632+
{
1633+
/* To avoid as much I/O as possible, the LBA's for each entry are sorted in
1634+
* memory and then the entries rearranged on flash after
1635+
*/
1636+
uint64_t lba_arr[table->num_used_partitions];
1637+
psa_status_t ret;
1638+
for (uint32_t i = 0; i < table->num_used_partitions; ++i) {
1639+
struct gpt_entry_t entry;
1640+
ret = read_entry_from_flash(table, i, &entry);
1641+
if (ret != PSA_SUCCESS) {
1642+
return ret;
1643+
}
1644+
lba_arr[i] = entry.start;
1645+
}
1646+
1647+
qsort(lba_arr, table->num_used_partitions, sizeof(uint64_t), cmp_u64);
1648+
1649+
/* Now read and place the entries in the correct spot, starting with the
1650+
* first. Each entry is dealt with as it is encountered. When an entry is
1651+
* found and already in the correct spot, the next smallest index not yet
1652+
* handled becomes the next.
1653+
*/
1654+
struct gpt_entry_t saved_entry = {0};
1655+
struct gpt_entry_t curr_entry;
1656+
uint8_t handled_indices[table->num_used_partitions];
1657+
memset(handled_indices, 0, table->num_used_partitions);
1658+
1659+
ret = read_entry_from_flash(table, 0, &curr_entry);
1660+
if (ret != PSA_SUCCESS) {
1661+
return ret;
1662+
}
1663+
1664+
for (uint32_t i = 0; i < table->num_used_partitions; ++i) {
1665+
const int64_t new_index = bsearch_index(
1666+
lba_arr,
1667+
table->num_used_partitions,
1668+
curr_entry.start);
1669+
if (new_index < 0) {
1670+
ERROR("Encountered unknown partition entry!\n");
1671+
return PSA_ERROR_STORAGE_FAILURE;
1672+
}
1673+
1674+
/* For final entry, just write it out */
1675+
if (i == table->num_used_partitions - 1) {
1676+
ret = write_entry((uint32_t)new_index, &curr_entry, false);
1677+
if (ret != PSA_SUCCESS) {
1678+
return ret;
1679+
}
1680+
break;
1681+
}
1682+
1683+
/* Replace the entry in the new_index place with the current entry */
1684+
ret = read_entry_from_flash(table, (uint32_t)new_index, &saved_entry);
1685+
if (ret != PSA_SUCCESS) {
1686+
return ret;
1687+
}
1688+
1689+
struct efi_guid_t saved_guid = saved_entry.unique_guid;
1690+
struct efi_guid_t curr_guid = curr_entry.unique_guid;
1691+
if (efi_guid_cmp(&saved_guid, &curr_guid) == 0) {
1692+
/* This entry is already where it needs to be, so try the smallest
1693+
* index not yet handled next
1694+
*/
1695+
handled_indices[new_index] = 1;
1696+
uint32_t next_entry = 0;
1697+
while(next_entry < table->num_used_partitions && handled_indices[next_entry]) {
1698+
++next_entry;
1699+
}
1700+
1701+
if (next_entry == table->num_used_partitions) {
1702+
/* Done everything */
1703+
break;
1704+
}
1705+
1706+
ret = read_entry_from_flash(table, next_entry, &saved_entry);
1707+
if (ret != PSA_SUCCESS) {
1708+
return ret;
1709+
}
1710+
} else {
1711+
/* Write, skipping header update until very end */
1712+
ret = write_entry((uint32_t)new_index, &curr_entry, true);
1713+
if (ret != PSA_SUCCESS) {
1714+
return ret;
1715+
}
1716+
}
1717+
1718+
/* Ready up for the next loop */
1719+
curr_entry = saved_entry;
1720+
handled_indices[new_index] = 1;
1721+
}
1722+
1723+
return PSA_SUCCESS;
1724+
}
1725+
15361726
/* Converts unicode string to valid ascii */
15371727
static psa_status_t unicode_to_ascii(const char *unicode, char *ascii)
15381728
{

lib/gpt/unittests/gpt/test_gpt.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,13 @@ void test_gpt_restore_should_failToRestoreWhenBackupIsBad(void)
504504
TEST_ASSERT_EQUAL(PSA_ERROR_INVALID_SIGNATURE, gpt_restore(true));
505505
}
506506

507+
void test_gpt_defragment_should_succeedWhenNoIOFailure(void)
508+
{
509+
setup_valid_gpt();
510+
511+
TEST_ASSERT_EQUAL(PSA_SUCCESS, gpt_defragment());
512+
}
513+
507514
void test_gpt_entry_create_should_createNewEntry(void)
508515
{
509516
/* Add an entry. It must not overlap with an existing entry and must also

0 commit comments

Comments
 (0)