summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/abuf.h159
-rw-r--r--lib/Makefile1
-rw-r--r--lib/abuf.c109
-rw-r--r--test/lib/Makefile1
-rw-r--r--test/lib/abuf.c344
5 files changed, 614 insertions, 0 deletions
diff --git a/include/abuf.h b/include/abuf.h
new file mode 100644
index 00000000000..d230f72806d
--- /dev/null
+++ b/include/abuf.h
@@ -0,0 +1,159 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Handles a buffer that can be allocated and freed
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <[email protected]>
+ */
+
+#ifndef __ABUF_H
+#define __ABUF_H
+
+#include <linux/types.h>
+
+/**
+ * struct abuf - buffer that can be allocated and freed
+ *
+ * This is useful for a block of data which may be allocated with malloc(), or
+ * not, so that it needs to be freed correctly when finished with.
+ *
+ * For now it has a very simple purpose.
+ *
+ * Using memset() to zero all fields is guaranteed to be equivalent to
+ * abuf_init().
+ *
+ * @data: Pointer to data
+ * @size: Size of data in bytes
+ * @alloced: true if allocated with malloc(), so must be freed after use
+ */
+struct abuf {
+ void *data;
+ size_t size;
+ bool alloced;
+};
+
+static inline void *abuf_data(const struct abuf *abuf)
+{
+ return abuf->data;
+}
+
+static inline size_t abuf_size(const struct abuf *abuf)
+{
+ return abuf->size;
+}
+
+/**
+ * abuf_set() - set the (unallocated) data in a buffer
+ *
+ * This simply makes the abuf point to the supplied data, which must be live
+ * for the lifetime of the abuf. It is not alloced.
+ *
+ * Any existing data in the abuf is freed and the alloced member is set to
+ * false.
+ *
+ * @abuf: abuf to adjust
+ * @data: New contents of abuf
+ * @size: New size of abuf
+ */
+void abuf_set(struct abuf *abuf, void *data, size_t size);
+
+/**
+ * abuf_map_sysmem() - calls map_sysmem() to set up an abuf
+ *
+ * This is equivalent to abuf_set(abuf, map_sysmem(addr, size), size)
+ *
+ * Any existing data in the abuf is freed and the alloced member is set to
+ * false.
+ *
+ * @abuf: abuf to adjust
+ * @addr: Address to set the abuf to
+ * @size: New size of abuf
+ */
+void abuf_map_sysmem(struct abuf *abuf, ulong addr, size_t size);
+
+/**
+ * abuf_realloc() - Change the size of a buffer
+ *
+ * This uses realloc() to change the size of the buffer, with the same semantics
+ * as that function. If the abuf is not currently alloced, then it will alloc
+ * it if the size needs to increase (i.e. set the alloced member to true)
+ *
+ * @abuf: abuf to adjust
+ * @new_size: new size in bytes.
+ * if 0, the abuf is freed
+ * if greater than the current size, the abuf is extended and the new
+ * space is not inited. The alloced member is set to true
+ * if less than the current size, the abuf is contracted and the data at
+ * the end is lost. If @new_size is 0, this sets the alloced member to
+ * false
+ * @return true if OK, false if out of memory
+ */
+bool abuf_realloc(struct abuf *abuf, size_t new_size);
+
+/**
+ * abuf_uninit_move() - Return the allocated contents and uninit the abuf
+ *
+ * This returns the abuf data to the caller, allocating it if necessary, so that
+ * the caller receives data that it can be sure will hang around. The caller is
+ * responsible for freeing the data.
+ *
+ * If the abuf has allocated data, it is returned. If the abuf has data but it
+ * is not allocated, then it is first allocated, then returned.
+ *
+ * If the abuf size is 0, this returns NULL
+ *
+ * The abuf is uninited as part of this, except if the allocation fails, in
+ * which NULL is returned and the abuf remains untouched.
+ *
+ * The abuf must be inited before this can be called.
+ *
+ * @abuf: abuf to uninit
+ * @sizep: if non-NULL, returns the size of the returned data
+ * @return data contents, allocated with malloc(), or NULL if the data could not
+ * be allocated, or the data size is 0
+ */
+void *abuf_uninit_move(struct abuf *abuf, size_t *sizep);
+
+/**
+ * abuf_init_move() - Make abuf take over the management of an allocated region
+ *
+ * After this, @data must not be used. All access must be via the abuf.
+ *
+ * @abuf: abuf to init
+ * @data: Existing allocated buffer to place in the abuf
+ * @size: Size of allocated buffer
+ */
+void abuf_init_move(struct abuf *abuf, void *data, size_t size);
+
+/**
+ * abuf_init_set() - Set up a new abuf
+ *
+ * Inits a new abuf and sets up its (unallocated) data
+ *
+ * @abuf: abuf to set up
+ * @data: New contents of abuf
+ * @size: New size of abuf
+ */
+void abuf_init_set(struct abuf *abuf, void *data, size_t size);
+
+/**
+ * abuf_uninit() - Free any memory used by an abuf
+ *
+ * The buffer must be inited before this can be called.
+ *
+ * @abuf: abuf to uninit
+ */
+void abuf_uninit(struct abuf *abuf);
+
+/**
+ * abuf_init() - Set up a new abuf
+ *
+ * This initially has no data and alloced is set to false. This is equivalent to
+ * setting all fields to 0, e.g. with memset(), so callers can do that instead
+ * if desired.
+ *
+ * @abuf: abuf to set up
+ */
+void abuf_init(struct abuf *abuf);
+
+#endif
diff --git a/lib/Makefile b/lib/Makefile
index 962470f4962..454396f101b 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -134,6 +134,7 @@ obj-$(CONFIG_OID_REGISTRY) += oid_registry.o
obj-$(CONFIG_SSCANF) += sscanf.o
endif
+obj-y += abuf.o
obj-y += date.o
obj-y += rtc-lib.o
obj-$(CONFIG_LIB_ELF) += elf.o
diff --git a/lib/abuf.c b/lib/abuf.c
new file mode 100644
index 00000000000..4b17e0b8c0d
--- /dev/null
+++ b/lib/abuf.c
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Handles a buffer that can be allocated and freed
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <[email protected]>
+ */
+
+#include <common.h>
+#include <abuf.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <string.h>
+
+void abuf_set(struct abuf *abuf, void *data, size_t size)
+{
+ abuf_uninit(abuf);
+ abuf->data = data;
+ abuf->size = size;
+}
+
+void abuf_map_sysmem(struct abuf *abuf, ulong addr, size_t size)
+{
+ abuf_set(abuf, map_sysmem(addr, size), size);
+}
+
+bool abuf_realloc(struct abuf *abuf, size_t new_size)
+{
+ void *ptr;
+
+ if (!new_size) {
+ /* easy case, just need to uninit, freeing any allocation */
+ abuf_uninit(abuf);
+ return true;
+ } else if (abuf->alloced) {
+ /* currently allocated, so need to reallocate */
+ ptr = realloc(abuf->data, new_size);
+ if (!ptr)
+ return false;
+ abuf->data = ptr;
+ abuf->size = new_size;
+ return true;
+ } else if (new_size <= abuf->size) {
+ /*
+ * not currently alloced and new size is no larger. Just update
+ * it. Data is lost off the end if new_size < abuf->size
+ */
+ abuf->size = new_size;
+ return true;
+ } else {
+ /* not currently allocated and new size is larger. Alloc and
+ * copy in data. The new space is not inited.
+ */
+ ptr = memdup(abuf->data, new_size);
+ if (!ptr)
+ return false;
+ abuf->data = ptr;
+ abuf->size = new_size;
+ abuf->alloced = true;
+ return true;
+ }
+}
+
+void *abuf_uninit_move(struct abuf *abuf, size_t *sizep)
+{
+ void *ptr;
+
+ if (sizep)
+ *sizep = abuf->size;
+ if (!abuf->size)
+ return NULL;
+ if (abuf->alloced) {
+ ptr = abuf->data;
+ } else {
+ ptr = memdup(abuf->data, abuf->size);
+ if (!ptr)
+ return NULL;
+ }
+ /* Clear everything out so there is no record of the data */
+ abuf_init(abuf);
+
+ return ptr;
+}
+
+void abuf_init_set(struct abuf *abuf, void *data, size_t size)
+{
+ abuf_init(abuf);
+ abuf_set(abuf, data, size);
+}
+
+void abuf_init_move(struct abuf *abuf, void *data, size_t size)
+{
+ abuf_init_set(abuf, data, size);
+ abuf->alloced = true;
+}
+
+void abuf_uninit(struct abuf *abuf)
+{
+ if (abuf->alloced)
+ free(abuf->data);
+ abuf_init(abuf);
+}
+
+void abuf_init(struct abuf *abuf)
+{
+ abuf->data = NULL;
+ abuf->size = 0;
+ abuf->alloced = false;
+}
diff --git a/test/lib/Makefile b/test/lib/Makefile
index 6fd05142510..d244bb431d4 100644
--- a/test/lib/Makefile
+++ b/test/lib/Makefile
@@ -3,6 +3,7 @@
# (C) Copyright 2018
# Mario Six, Guntermann & Drunck GmbH, [email protected]
obj-y += cmd_ut_lib.o
+obj-y += abuf.o
obj-$(CONFIG_EFI_LOADER) += efi_device_path.o
obj-$(CONFIG_EFI_SECURE_BOOT) += efi_image_region.o
obj-y += hexdump.o
diff --git a/test/lib/abuf.c b/test/lib/abuf.c
new file mode 100644
index 00000000000..086c9b22821
--- /dev/null
+++ b/test/lib/abuf.c
@@ -0,0 +1,344 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <[email protected]>
+ */
+
+#include <common.h>
+#include <abuf.h>
+#include <mapmem.h>
+#include <test/lib.h>
+#include <test/test.h>
+#include <test/ut.h>
+
+static char test_data[] = "1234";
+#define TEST_DATA_LEN sizeof(test_data)
+
+/* Test abuf_set() */
+static int lib_test_abuf_set(struct unit_test_state *uts)
+{
+ struct abuf buf;
+ ulong start;
+
+ start = ut_check_free();
+
+ abuf_init(&buf);
+ abuf_set(&buf, test_data, TEST_DATA_LEN);
+ ut_asserteq_ptr(test_data, buf.data);
+ ut_asserteq(TEST_DATA_LEN, buf.size);
+ ut_asserteq(false, buf.alloced);
+
+ /* Force it to allocate */
+ ut_asserteq(true, abuf_realloc(&buf, TEST_DATA_LEN + 1));
+ ut_assertnonnull(buf.data);
+ ut_asserteq(TEST_DATA_LEN + 1, buf.size);
+ ut_asserteq(true, buf.alloced);
+
+ /* Now set it again, to force it to free */
+ abuf_set(&buf, test_data, TEST_DATA_LEN);
+ ut_asserteq_ptr(test_data, buf.data);
+ ut_asserteq(TEST_DATA_LEN, buf.size);
+ ut_asserteq(false, buf.alloced);
+
+ /* Check for memory leaks */
+ ut_assertok(ut_check_delta(start));
+
+ return 0;
+}
+LIB_TEST(lib_test_abuf_set, 0);
+
+/* Test abuf_map_sysmem() */
+static int lib_test_abuf_map_sysmem(struct unit_test_state *uts)
+{
+ struct abuf buf;
+ ulong addr;
+
+ abuf_init(&buf);
+ addr = 0x100;
+ abuf_map_sysmem(&buf, addr, TEST_DATA_LEN);
+
+ ut_asserteq_ptr(map_sysmem(0x100, 0), buf.data);
+ ut_asserteq(TEST_DATA_LEN, buf.size);
+ ut_asserteq(false, buf.alloced);
+
+ return 0;
+}
+LIB_TEST(lib_test_abuf_map_sysmem, 0);
+
+/* Test abuf_realloc() */
+static int lib_test_abuf_realloc(struct unit_test_state *uts)
+{
+ struct abuf buf;
+ ulong start;
+ void *ptr;
+
+ /*
+ * TODO: crashes on sandbox sometimes due to an apparent bug in
+ * realloc().
+ */
+ return 0;
+
+ start = ut_check_free();
+
+ abuf_init(&buf);
+
+ /* Allocate an empty buffer */
+ ut_asserteq(true, abuf_realloc(&buf, 0));
+ ut_assertnull(buf.data);
+ ut_asserteq(0, buf.size);
+ ut_asserteq(false, buf.alloced);
+
+ /* Allocate a non-empty abuf */
+ ut_asserteq(true, abuf_realloc(&buf, TEST_DATA_LEN));
+ ut_assertnonnull(buf.data);
+ ut_asserteq(TEST_DATA_LEN, buf.size);
+ ut_asserteq(true, buf.alloced);
+ ptr = buf.data;
+
+ /*
+ * Make it smaller; the pointer should remain the same. Note this relies
+ * on knowledge of how U-Boot's realloc() works
+ */
+ ut_asserteq(true, abuf_realloc(&buf, TEST_DATA_LEN - 1));
+ ut_asserteq(TEST_DATA_LEN - 1, buf.size);
+ ut_asserteq(true, buf.alloced);
+ ut_asserteq_ptr(ptr, buf.data);
+
+ /*
+ * Make it larger, forcing reallocation. Note this relies on knowledge
+ * of how U-Boot's realloc() works
+ */
+ ut_asserteq(true, abuf_realloc(&buf, 0x1000));
+ ut_assert(buf.data != ptr);
+ ut_asserteq(0x1000, buf.size);
+ ut_asserteq(true, buf.alloced);
+
+ /* Free it */
+ ut_asserteq(true, abuf_realloc(&buf, 0));
+ ut_assertnull(buf.data);
+ ut_asserteq(0, buf.size);
+ ut_asserteq(false, buf.alloced);
+
+ /* Check for memory leaks */
+ ut_assertok(ut_check_delta(start));
+
+ return 0;
+}
+LIB_TEST(lib_test_abuf_realloc, 0);
+
+/* Test handling of buffers that are too large */
+static int lib_test_abuf_large(struct unit_test_state *uts)
+{
+ struct abuf buf;
+ ulong start;
+ size_t size;
+ int delta;
+ void *ptr;
+
+ /*
+ * This crashes at present due to trying to allocate more memory than
+ * available, which breaks something on sandbox.
+ */
+ return 0;
+
+ start = ut_check_free();
+
+ /* Try an impossible size */
+ abuf_init(&buf);
+ ut_asserteq(false, abuf_realloc(&buf, CONFIG_SYS_MALLOC_LEN));
+ ut_assertnull(buf.data);
+ ut_asserteq(0, buf.size);
+ ut_asserteq(false, buf.alloced);
+
+ abuf_uninit(&buf);
+ ut_assertnull(buf.data);
+ ut_asserteq(0, buf.size);
+ ut_asserteq(false, buf.alloced);
+
+ /* Start with a normal size then try to increase it, to check realloc */
+ ut_asserteq(true, abuf_realloc(&buf, TEST_DATA_LEN));
+ ut_assertnonnull(buf.data);
+ ut_asserteq(TEST_DATA_LEN, buf.size);
+ ut_asserteq(true, buf.alloced);
+ ptr = buf.data;
+ delta = ut_check_delta(start);
+ ut_assert(delta > 0);
+
+ /* try to increase it */
+ ut_asserteq(false, abuf_realloc(&buf, CONFIG_SYS_MALLOC_LEN));
+ ut_asserteq_ptr(ptr, buf.data);
+ ut_asserteq(TEST_DATA_LEN, buf.size);
+ ut_asserteq(true, buf.alloced);
+ ut_asserteq(delta, ut_check_delta(start));
+
+ /* Check for memory leaks */
+ abuf_uninit(&buf);
+ ut_assertok(ut_check_delta(start));
+
+ /* Start with a huge unallocated buf and try to move it */
+ abuf_init(&buf);
+ abuf_map_sysmem(&buf, 0, CONFIG_SYS_MALLOC_LEN);
+ ut_asserteq(CONFIG_SYS_MALLOC_LEN, buf.size);
+ ut_asserteq(false, buf.alloced);
+ ut_assertnull(abuf_uninit_move(&buf, &size));
+
+ /* Check for memory leaks */
+ abuf_uninit(&buf);
+ ut_assertok(ut_check_delta(start));
+
+ return 0;
+}
+LIB_TEST(lib_test_abuf_large, 0);
+
+/* Test abuf_uninit_move() */
+static int lib_test_abuf_uninit_move(struct unit_test_state *uts)
+{
+ void *ptr, *orig_ptr;
+ struct abuf buf;
+ size_t size;
+ ulong start;
+ int delta;
+
+ start = ut_check_free();
+
+ /*
+ * TODO: crashes on sandbox sometimes due to an apparent bug in
+ * realloc().
+ */
+ return 0;
+
+ /* Move an empty buffer */
+ abuf_init(&buf);
+ ut_assertnull(abuf_uninit_move(&buf, &size));
+ ut_asserteq(0, size);
+ ut_assertnull(abuf_uninit_move(&buf, NULL));
+
+ /* Move an unallocated buffer */
+ abuf_set(&buf, test_data, TEST_DATA_LEN);
+ ut_assertok(ut_check_delta(start));
+ ptr = abuf_uninit_move(&buf, &size);
+ ut_asserteq(TEST_DATA_LEN, size);
+ ut_asserteq_str(ptr, test_data);
+ ut_assertnonnull(ptr);
+ ut_assertnull(buf.data);
+ ut_asserteq(0, buf.size);
+ ut_asserteq(false, buf.alloced);
+
+ /* Check that freeing it frees the only allocation */
+ delta = ut_check_delta(start);
+ ut_assert(delta > 0);
+ free(ptr);
+ ut_assertok(ut_check_delta(start));
+
+ /* Move an allocated buffer */
+ ut_asserteq(true, abuf_realloc(&buf, TEST_DATA_LEN));
+ orig_ptr = buf.data;
+ strcpy(orig_ptr, test_data);
+
+ delta = ut_check_delta(start);
+ ut_assert(delta > 0);
+ ptr = abuf_uninit_move(&buf, &size);
+ ut_asserteq(TEST_DATA_LEN, size);
+ ut_assertnonnull(ptr);
+ ut_asserteq_ptr(ptr, orig_ptr);
+ ut_asserteq_str(ptr, test_data);
+ ut_assertnull(buf.data);
+ ut_asserteq(0, buf.size);
+ ut_asserteq(false, buf.alloced);
+
+ /* Check there was no new allocation */
+ ut_asserteq(delta, ut_check_delta(start));
+
+ /* Check that freeing it frees the only allocation */
+ free(ptr);
+ ut_assertok(ut_check_delta(start));
+
+ /* Move an unallocated buffer, without the size */
+ abuf_set(&buf, test_data, TEST_DATA_LEN);
+ ut_assertok(ut_check_delta(start));
+ ptr = abuf_uninit_move(&buf, NULL);
+ ut_asserteq_str(ptr, test_data);
+
+ return 0;
+}
+LIB_TEST(lib_test_abuf_uninit_move, 0);
+
+/* Test abuf_uninit() */
+static int lib_test_abuf_uninit(struct unit_test_state *uts)
+{
+ struct abuf buf;
+
+ /* Nothing in the buffer */
+ abuf_init(&buf);
+ abuf_uninit(&buf);
+ ut_assertnull(buf.data);
+ ut_asserteq(0, buf.size);
+ ut_asserteq(false, buf.alloced);
+
+ /* Not allocated */
+ abuf_set(&buf, test_data, TEST_DATA_LEN);
+ abuf_uninit(&buf);
+ ut_assertnull(buf.data);
+ ut_asserteq(0, buf.size);
+ ut_asserteq(false, buf.alloced);
+
+ return 0;
+}
+LIB_TEST(lib_test_abuf_uninit, 0);
+
+/* Test abuf_init_set() */
+static int lib_test_abuf_init_set(struct unit_test_state *uts)
+{
+ struct abuf buf;
+
+ abuf_init_set(&buf, test_data, TEST_DATA_LEN);
+ ut_asserteq_ptr(test_data, buf.data);
+ ut_asserteq(TEST_DATA_LEN, buf.size);
+ ut_asserteq(false, buf.alloced);
+
+ return 0;
+}
+LIB_TEST(lib_test_abuf_init_set, 0);
+
+/* Test abuf_init_move() */
+static int lib_test_abuf_init_move(struct unit_test_state *uts)
+{
+ struct abuf buf;
+ void *ptr;
+
+ /*
+ * TODO: crashes on sandbox sometimes due to an apparent bug in
+ * realloc().
+ */
+ return 0;
+
+ ptr = strdup(test_data);
+ ut_assertnonnull(ptr);
+
+ free(ptr);
+
+ abuf_init_move(&buf, ptr, TEST_DATA_LEN);
+ ut_asserteq_ptr(ptr, abuf_data(&buf));
+ ut_asserteq(TEST_DATA_LEN, abuf_size(&buf));
+ ut_asserteq(true, buf.alloced);
+
+ return 0;
+}
+LIB_TEST(lib_test_abuf_init_move, 0);
+
+/* Test abuf_init() */
+static int lib_test_abuf_init(struct unit_test_state *uts)
+{
+ struct abuf buf;
+
+ buf.data = &buf;
+ buf.size = 123;
+ buf.alloced = true;
+ abuf_init(&buf);
+ ut_assertnull(buf.data);
+ ut_asserteq(0, buf.size);
+ ut_asserteq(false, buf.alloced);
+
+ return 0;
+}
+LIB_TEST(lib_test_abuf_init, 0);