diff --git a/common/bloblist.c b/common/bloblist.c index eab63e9ca51..bb49ecab92e 100644 --- a/common/bloblist.c +++ b/common/bloblist.c @@ -57,13 +57,22 @@ static struct bloblist_rec *bloblist_first_blob(struct bloblist_hdr *hdr) return (struct bloblist_rec *)((void *)hdr + hdr->hdr_size); } -static struct bloblist_rec *bloblist_next_blob(struct bloblist_hdr *hdr, - struct bloblist_rec *rec) +static ulong bloblist_blob_end_ofs(struct bloblist_hdr *hdr, + struct bloblist_rec *rec) { ulong offset; offset = (void *)rec - (void *)hdr; offset += rec->hdr_size + ALIGN(rec->size, BLOBLIST_ALIGN); + + return offset; +} + +static struct bloblist_rec *bloblist_next_blob(struct bloblist_hdr *hdr, + struct bloblist_rec *rec) +{ + ulong offset = bloblist_blob_end_ofs(hdr, rec); + if (offset >= hdr->alloced) return NULL; return (struct bloblist_rec *)((void *)hdr + offset); @@ -215,6 +224,64 @@ int bloblist_ensure_size_ret(uint tag, int *sizep, void **blobp) return 0; } +static int bloblist_resize_rec(struct bloblist_hdr *hdr, + struct bloblist_rec *rec, + int new_size) +{ + int expand_by; /* Number of bytes to expand by (-ve to contract) */ + int new_alloced; /* New value for @hdr->alloced */ + ulong next_ofs; /* Offset of the record after @rec */ + + expand_by = ALIGN(new_size - rec->size, BLOBLIST_ALIGN); + new_alloced = ALIGN(hdr->alloced + expand_by, BLOBLIST_ALIGN); + if (new_size < 0) { + log(LOGC_BLOBLIST, LOGL_DEBUG, + "Attempt to shrink blob size below 0 (%x)\n", new_size); + return log_msg_ret("size", -EINVAL); + } + if (new_alloced > hdr->size) { + log(LOGC_BLOBLIST, LOGL_ERR, + "Failed to allocate %x bytes size=%x, need size=%x\n", + new_size, hdr->size, new_alloced); + return log_msg_ret("alloc", -ENOSPC); + } + + /* Move the following blobs up or down, if this is not the last */ + next_ofs = bloblist_blob_end_ofs(hdr, rec); + if (next_ofs != hdr->alloced) { + memmove((void *)hdr + next_ofs + expand_by, + (void *)hdr + next_ofs, new_alloced - next_ofs); + } + hdr->alloced = new_alloced; + + /* Zero the new part of the blob */ + if (expand_by > 0) { + memset((void *)rec + rec->hdr_size + rec->size, '\0', + new_size - rec->size); + } + + /* Update the size of this blob */ + rec->size = new_size; + + return 0; +} + +int bloblist_resize(uint tag, int new_size) +{ + struct bloblist_hdr *hdr = gd->bloblist; + struct bloblist_rec *rec; + int ret; + + rec = bloblist_findrec(tag); + if (!rec) + return log_msg_ret("find", -ENOENT); + ret = bloblist_resize_rec(hdr, rec, new_size); + if (ret) + return log_msg_ret("resize", ret); + + return 0; +} + static u32 bloblist_calc_chksum(struct bloblist_hdr *hdr) { struct bloblist_rec *rec; diff --git a/include/bloblist.h b/include/bloblist.h index 964b974fdaf..b659d2bc93d 100644 --- a/include/bloblist.h +++ b/include/bloblist.h @@ -179,6 +179,19 @@ void *bloblist_ensure(uint tag, int size); */ int bloblist_ensure_size_ret(uint tag, int *sizep, void **blobp); +/** + * bloblist_resize() - resize a blob + * + * Any blobs above this one are relocated up or down. The resized blob remains + * in the same place. + * + * @tag: Tag to add (enum bloblist_tag_t) + * @new_size: New size of the blob (>0 to expand, <0 to contract) + * @return 0 if OK, -ENOSPC if the bloblist does not have enough space, -ENOENT + * if the tag is not found + */ +int bloblist_resize(uint tag, int new_size); + /** * bloblist_new() - Create a new, empty bloblist of a given size * diff --git a/test/bloblist.c b/test/bloblist.c index d876b639184..345eb181fff 100644 --- a/test/bloblist.c +++ b/test/bloblist.c @@ -33,6 +33,9 @@ enum { ERASE_BYTE = '\xff', }; +static const char test1_str[] = "the eyes are open"; +static const char test2_str[] = "the mouth moves"; + static struct bloblist_hdr *clear_bloblist(void) { struct bloblist_hdr *hdr; @@ -384,6 +387,195 @@ static int bloblist_test_reloc(struct unit_test_state *uts) } BLOBLIST_TEST(bloblist_test_reloc, 0); +/* Test expansion of a blob */ +static int bloblist_test_grow(struct unit_test_state *uts) +{ + const uint small_size = 0x20; + void *blob1, *blob2, *blob1_new; + struct bloblist_hdr *hdr; + void *ptr; + + ptr = map_sysmem(TEST_ADDR, TEST_BLOBLIST_SIZE); + hdr = ptr; + memset(hdr, ERASE_BYTE, TEST_BLOBLIST_SIZE); + + /* Create two blobs */ + ut_assertok(bloblist_new(TEST_ADDR, TEST_BLOBLIST_SIZE, 0)); + blob1 = bloblist_add(TEST_TAG, small_size, 0); + ut_assertnonnull(blob1); + ut_assertok(check_zero(blob1, small_size)); + strcpy(blob1, test1_str); + + blob2 = bloblist_add(TEST_TAG2, small_size, 0); + ut_assertnonnull(blob2); + strcpy(blob2, test2_str); + + ut_asserteq(sizeof(struct bloblist_hdr) + + sizeof(struct bloblist_rec) * 2 + small_size * 2, + hdr->alloced); + + /* Resize the first one */ + ut_assertok(bloblist_resize(TEST_TAG, small_size + 4)); + + /* The first one should not have moved, just got larger */ + blob1_new = bloblist_find(TEST_TAG, small_size + 4); + ut_asserteq_ptr(blob1, blob1_new); + + /* The new space should be zeroed */ + ut_assertok(check_zero(blob1 + small_size, 4)); + + /* The second one should have moved */ + blob2 = bloblist_find(TEST_TAG2, small_size); + ut_assertnonnull(blob2); + ut_asserteq_str(test2_str, blob2); + + /* The header should have more bytes in use */ + hdr = ptr; + ut_asserteq(sizeof(struct bloblist_hdr) + + sizeof(struct bloblist_rec) * 2 + small_size * 2 + + BLOBLIST_ALIGN, + hdr->alloced); + + return 0; +} +BLOBLIST_TEST(bloblist_test_grow, 0); + +/* Test shrinking of a blob */ +static int bloblist_test_shrink(struct unit_test_state *uts) +{ + const uint small_size = 0x20; + void *blob1, *blob2, *blob1_new; + struct bloblist_hdr *hdr; + int new_size; + void *ptr; + + ptr = map_sysmem(TEST_ADDR, TEST_BLOBLIST_SIZE); + + /* Create two blobs */ + ut_assertok(bloblist_new(TEST_ADDR, TEST_BLOBLIST_SIZE, 0)); + blob1 = bloblist_add(TEST_TAG, small_size, 0); + ut_assertnonnull(blob1); + strcpy(blob1, test1_str); + + blob2 = bloblist_add(TEST_TAG2, small_size, 0); + ut_assertnonnull(blob2); + strcpy(blob2, test2_str); + + hdr = ptr; + ut_asserteq(sizeof(struct bloblist_hdr) + + sizeof(struct bloblist_rec) * 2 + small_size * 2, + hdr->alloced); + + /* Resize the first one */ + new_size = small_size - BLOBLIST_ALIGN - 4; + ut_assertok(bloblist_resize(TEST_TAG, new_size)); + + /* The first one should not have moved, just got smaller */ + blob1_new = bloblist_find(TEST_TAG, new_size); + ut_asserteq_ptr(blob1, blob1_new); + + /* The second one should have moved */ + blob2 = bloblist_find(TEST_TAG2, small_size); + ut_assertnonnull(blob2); + ut_asserteq_str(test2_str, blob2); + + /* The header should have fewer bytes in use */ + hdr = ptr; + ut_asserteq(sizeof(struct bloblist_hdr) + + sizeof(struct bloblist_rec) * 2 + small_size * 2 - + BLOBLIST_ALIGN, + hdr->alloced); + + return 0; +} +BLOBLIST_TEST(bloblist_test_shrink, 0); + +/* Test failing to adjust a blob size */ +static int bloblist_test_resize_fail(struct unit_test_state *uts) +{ + const uint small_size = 0x20; + struct bloblist_hdr *hdr; + void *blob1, *blob2; + int new_size; + void *ptr; + + ptr = map_sysmem(TEST_ADDR, TEST_BLOBLIST_SIZE); + + /* Create two blobs */ + ut_assertok(bloblist_new(TEST_ADDR, TEST_BLOBLIST_SIZE, 0)); + blob1 = bloblist_add(TEST_TAG, small_size, 0); + ut_assertnonnull(blob1); + + blob2 = bloblist_add(TEST_TAG2, small_size, 0); + ut_assertnonnull(blob2); + + hdr = ptr; + ut_asserteq(sizeof(struct bloblist_hdr) + + sizeof(struct bloblist_rec) * 2 + small_size * 2, + hdr->alloced); + + /* Resize the first one, to check the boundary conditions */ + ut_asserteq(-EINVAL, bloblist_resize(TEST_TAG, -1)); + + new_size = small_size + (hdr->size - hdr->alloced); + ut_asserteq(-ENOSPC, bloblist_resize(TEST_TAG, new_size + 1)); + ut_assertok(bloblist_resize(TEST_TAG, new_size)); + + return 0; +} +BLOBLIST_TEST(bloblist_test_resize_fail, 0); + +/* Test expanding the last blob in a bloblist */ +static int bloblist_test_resize_last(struct unit_test_state *uts) +{ + const uint small_size = 0x20; + struct bloblist_hdr *hdr; + void *blob1, *blob2, *blob2_new; + int alloced_val; + void *ptr; + + ptr = map_sysmem(TEST_ADDR, TEST_BLOBLIST_SIZE); + memset(ptr, ERASE_BYTE, TEST_BLOBLIST_SIZE); + hdr = ptr; + + /* Create two blobs */ + ut_assertok(bloblist_new(TEST_ADDR, TEST_BLOBLIST_SIZE, 0)); + blob1 = bloblist_add(TEST_TAG, small_size, 0); + ut_assertnonnull(blob1); + + blob2 = bloblist_add(TEST_TAG2, small_size, 0); + ut_assertnonnull(blob2); + + /* Check the byte after the last blob */ + alloced_val = sizeof(struct bloblist_hdr) + + sizeof(struct bloblist_rec) * 2 + small_size * 2; + ut_asserteq(alloced_val, hdr->alloced); + ut_asserteq_ptr((void *)hdr + alloced_val, blob2 + small_size); + ut_asserteq((u8)ERASE_BYTE, *((u8 *)hdr + hdr->alloced)); + + /* Resize the second one, checking nothing changes */ + ut_asserteq(0, bloblist_resize(TEST_TAG2, small_size + 4)); + + blob2_new = bloblist_find(TEST_TAG2, small_size + 4); + ut_asserteq_ptr(blob2, blob2_new); + + /* + * the new blob should encompass the byte we checked now, so it should + * be zeroed. This zeroing should affect only the four new bytes added + * to the blob. + */ + ut_asserteq(0, *((u8 *)hdr + alloced_val)); + ut_asserteq((u8)ERASE_BYTE, *((u8 *)hdr + alloced_val + 4)); + + /* Check that the new top of the allocated blobs has not been touched */ + alloced_val += BLOBLIST_ALIGN; + ut_asserteq(alloced_val, hdr->alloced); + ut_asserteq((u8)ERASE_BYTE, *((u8 *)hdr + hdr->alloced)); + + return 0; +} +BLOBLIST_TEST(bloblist_test_resize_last, 0); + int do_ut_bloblist(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) {