In various places it is useful to have an array of structures, but allow
it to grow. In some cases we work around it by setting maximum number of
entries, using a Kconfig option. In other places we use a linked list,
which does not provide for random access and can complicate the code.

Introduce a new data structure, which is a variable-sized list of structs
each of the same, pre-set size. It provides O(1) access and is reasonably
efficient at expanding linearly, since it doubles in size when it runs out
of space.

Signed-off-by: Simon Glass <s...@chromium.org>
---

Changes in v4:
- Avoid using realloc() unless specifically enabled by Kconfig

Changes in v3:
- Add prototype for alist_expand_by()

Changes in v2:
- Fix 'typeee' typo
- Make alist_get_ptr() take a const alist *
- Make alist_add() take a struct rather than a pointer
- Declare alist_expand_by() as static

 include/alist.h   | 214 ++++++++++++++++++++++++++++++++++++++++++++++
 lib/Kconfig       |  10 +++
 lib/Makefile      |   1 +
 lib/alist.c       | 157 ++++++++++++++++++++++++++++++++++
 test/lib/Makefile |   1 +
 test/lib/alist.c  | 197 ++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 580 insertions(+)
 create mode 100644 include/alist.h
 create mode 100644 lib/alist.c
 create mode 100644 test/lib/alist.c

diff --git a/include/alist.h b/include/alist.h
new file mode 100644
index 00000000000..6cc3161dcda
--- /dev/null
+++ b/include/alist.h
@@ -0,0 +1,214 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Handles a contiguous list of pointers which be allocated and freed
+ *
+ * Copyright 2023 Google LLC
+ * Written by Simon Glass <s...@chromium.org>
+ */
+
+#ifndef __ALIST_H
+#define __ALIST_H
+
+#include <stdbool.h>
+#include <linux/bitops.h>
+#include <linux/types.h>
+
+/**
+ * struct alist - object list that can be allocated and freed
+ *
+ * Holds a list of objects, each of the same size. The object is typically a
+ * C struct. The array is alloced in memory can change in size.
+ *
+ * The list rememebers the size of the list, but has a separate count of how
+ * much space is allocated, This allows it increase in size in steps as more
+ * elements are added, which is more efficient that reallocating the list every
+ * time a single item is added
+ *
+ * Two types of access are provided:
+ *
+ * alist_get...(index)
+ *     gets an existing element, if its index is less that size
+ *
+ * alist_ensure(index)
+ *     address an existing element, or creates a new one if not present
+ *
+ * @data: object data of size `@obj_size * @alloc`. The list can grow as
+ * needed but never shrinks
+ * @obj_size: Size of each object in bytes
+ * @count: number of objects in array
+ * @alloc: allocated length of array, to which @count can grow
+ * @flags: flags for the alist (ALISTF_...)
+ */
+struct alist {
+       void *data;
+       u16 obj_size;
+       u16 count;
+       u16 alloc;
+       u16 flags;
+};
+
+/**
+ * enum alist_flags - Flags for the alist
+ *
+ * @ALIST_FAIL: true if any allocation has failed. Once this has happened, the
+ * alist is dead and cannot grow further
+ */
+enum alist_flags {
+       ALISTF_FAIL     = BIT(0),
+};
+
+/**
+ * alist_has() - Check if an index is within the list range
+ *
+ * Checks if index is within the current alist count
+ *
+ * @lst: alist to check
+ * @index: Index to check
+ * Returns: true if value, else false
+ */
+static inline bool alist_has(struct alist *lst, uint index)
+{
+       return index < lst->count;
+}
+
+/**
+ * alist_err() - Check if the alist is still valid
+ *
+ * @lst: List to check
+ * Return: false if OK, true if any previous allocation failed
+ */
+static inline bool alist_err(struct alist *lst)
+{
+       return lst->flags & ALISTF_FAIL;
+}
+
+/**
+ * alist_get_ptr() - Get the value of a pointer
+ *
+ * @lst: alist to check
+ * @index: Index to read from
+ * Returns: pointer, if present, else NULL
+ */
+const void *alist_get_ptr(const struct alist *lst, uint index);
+
+/**
+ * alist_getd() - Get the value of a pointer directly, with no checking
+ *
+ * This must only be called on indexes for which alist_has() returns true
+ *
+ * @lst: alist to check
+ * @index: Index to read from
+ * Returns: pointer value (may be NULL)
+ */
+static inline const void *alist_getd(struct alist *lst, uint index)
+{
+       return lst->data + index * lst->obj_size;
+}
+
+#define alist_get(_lst, _index, _struct)       \
+       ((const _struct *)alist_get_ptr(_lst, _index))
+
+/**
+ * alist_ensure_ptr() - Ensure an object exists at a given index
+ *
+ * This provides read/write access to an array element. If it does not exist,
+ * it is allocated, reading for the caller to store the object into
+ *
+ * Allocates a object at the given index if needed
+ *
+ * @lst: alist to check
+ * @index: Index to address
+ * Returns: pointer where struct can be read/written, or NULL if out of memory
+ */
+void *alist_ensure_ptr(struct alist *lst, uint index);
+
+/**
+ * alist_ensure() - Address a struct, the correct object type
+ *
+ * Use as:
+ *     struct my_struct *ptr = alist_ensure(&lst, 4, struct my_struct);
+ */
+#define alist_ensure(_lst, _index, _struct)    \
+       ((_struct *)alist_ensure_ptr(_lst, _index))
+
+/**
+ * alist_add_ptr() - Ad a new object to the list
+ *
+ * @lst: alist to add to
+ * @obj: Pointer to object to copy in
+ * Returns: pointer to where the object was copied, or NULL if out of memory
+ */
+void *alist_add_ptr(struct alist *lst, void *obj);
+
+/**
+ * alist_expand_by() - Expand a list by the given amount
+ *
+ * @lst: alist to expand
+ * @inc_by: Amount to expand by
+ * Return: true if OK, false if out of memory
+ */
+bool alist_expand_by(struct alist *lst, uint inc_by);
+
+/**
+ * alist_add() - Used to add an object type with the correct type
+ *
+ * Use as:
+ *     struct my_struct obj;
+ *     struct my_struct *ptr = alist_add(&lst, &obj, struct my_struct);
+ */
+#define alist_add(_lst, _obj, _struct) \
+       ((_struct *)alist_add_ptr(_lst, (_struct *)&(_obj)))
+
+/**
+ * alist_init() - Set up a new object list
+ *
+ * Sets up a list of objects, initially empty
+ *
+ * @lst: alist to set up
+ * @obj_size: Size of each element in bytes
+ * @alloc_size: Number of items to allowed to start, before reallocation is
+ * needed (0 to start with no space)
+ * Return: true if OK, false if out of memory
+ */
+bool alist_init(struct alist *lst, uint obj_size, uint alloc_size);
+
+#define alist_init_struct(_lst, _struct)       \
+       alist_init(_lst, sizeof(_struct), 0)
+
+/**
+ * alist_uninit_move_ptr() - Return the allocated contents and uninit the alist
+ *
+ * This returns the alist data to the caller, so that the caller receives data
+ * that it can be sure will hang around. The caller is responsible for freeing
+ * the data.
+ *
+ * If the alist size is 0, this returns NULL
+ *
+ * The alist is uninited as part of this.
+ *
+ * The alist must be inited before this can be called.
+ *
+ * @alist: alist to uninit
+ * @countp: if non-NULL, returns the number of objects in the returned data
+ * (which is @alist->size)
+ * Return: data contents, allocated with malloc(), or NULL if the data could 
not
+ *     be allocated, or the data size is 0
+ */
+void *alist_uninit_move_ptr(struct alist *alist, size_t *countp);
+
+/**
+ * alist_uninit_move() - Typed version of alist_uninit_move_ptr()
+ */
+#define alist_uninit_move(_lst, _countp, _struct)      \
+       (_struct *)alist_uninit_move_ptr(_lst, _countp)
+
+/**
+ * alist_uninit() - Free any memory used by an alist
+ *
+ * The alist must be inited before this can be called.
+ *
+ * @alist: alist to uninit
+ */
+void alist_uninit(struct alist *alist);
+
+#endif /* __ALIST_H */
diff --git a/lib/Kconfig b/lib/Kconfig
index 2059219a120..40b59adbefc 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -8,6 +8,16 @@ config ADDR_MAP
 
          This library only works in the post-relocation phase.
 
+config ALIST_REALLOC
+       bool "Use realloc() in the alist implementation"
+       help
+         An alist must expand when it runs out of space. It is more efficient
+         to use realloc() for this, but for boards which don't otherwise use
+         this function it can add 1KB of code space, so separate malloc() and
+         free are normally used instead.
+
+         Enable this to use realloc() in alist
+
 config SYS_NUM_ADDR_MAP
        int "Size of the address-map table"
        depends on ADDR_MAP
diff --git a/lib/Makefile b/lib/Makefile
index e389ad014f8..81b503ab526 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -147,6 +147,7 @@ endif
 obj-$(CONFIG_$(SPL_)OID_REGISTRY) += oid_registry.o
 
 obj-y += abuf.o
+obj-y += alist.o
 obj-y += date.o
 obj-y += rtc-lib.o
 obj-$(CONFIG_LIB_ELF) += elf.o
diff --git a/lib/alist.c b/lib/alist.c
new file mode 100644
index 00000000000..89339c02dfb
--- /dev/null
+++ b/lib/alist.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Handles a contiguous list of pointers which be allocated and freed
+ *
+ * Copyright 2023 Google LLC
+ * Written by Simon Glass <s...@chromium.org>
+ */
+
+#include <alist.h>
+#include <display_options.h>
+#include <malloc.h>
+#include <stdio.h>
+#include <string.h>
+
+enum {
+       ALIST_INITIAL_SIZE      = 4,    /* default size of unsized list */
+};
+
+bool alist_init(struct alist *lst, uint obj_size, uint start_size)
+{
+       /* Avoid realloc for the initial size to help malloc_simple */
+       memset(lst, '\0', sizeof(struct alist));
+       if (start_size) {
+               lst->data = calloc(obj_size, start_size);
+               if (!lst->data) {
+                       lst->flags = ALISTF_FAIL;
+                       return false;
+               }
+               lst->alloc = start_size;
+       }
+       lst->obj_size = obj_size;
+
+       return true;
+}
+
+void alist_uninit(struct alist *lst)
+{
+       free(lst->data);
+
+       /* Clear fields to avoid any confusion */
+       memset(lst, '\0', sizeof(struct alist));
+}
+
+/**
+ * alist_expand_to() - Expand a list to the given size
+ *
+ * @lst: List to modify
+ * @inc_by: Amount to expand to
+ * Return: true if OK, false if out of memory
+ */
+static bool alist_expand_to(struct alist *lst, uint new_alloc)
+{
+       void *new_data;
+
+       if (lst->flags & ALISTF_FAIL)
+               return false;
+
+       if (CONFIG_IS_ENABLED(ALIST_REALLOC))
+               new_data = realloc(lst->data, lst->obj_size * new_alloc);
+       else
+               new_data = malloc(lst->obj_size * new_alloc);
+       if (!new_data) {
+               lst->flags |= ALISTF_FAIL;
+               return false;
+       }
+
+       if (!CONFIG_IS_ENABLED(ALIST_REALLOC)) {
+               memcpy(new_data, lst->data, lst->obj_size * lst->alloc);
+               free(lst->data);
+       }
+
+       memset(new_data + lst->obj_size * lst->alloc, '\0',
+              lst->obj_size * (new_alloc - lst->alloc));
+       lst->alloc = new_alloc;
+       lst->data = new_data;
+
+       return true;
+}
+
+bool alist_expand_by(struct alist *lst, uint inc_by)
+{
+       return alist_expand_to(lst, lst->alloc + inc_by);
+}
+
+/**
+ * alist_expand_min() - Expand to at least the provided size
+ *
+ * Expands to the lowest power of two which can incorporate the new size
+ *
+ * @lst: alist to expand
+ * @min_alloc: Minimum new allocated size; if 0 then ALIST_INITIAL_SIZE is used
+ * Return: true if OK, false if out of memory
+ */
+static bool alist_expand_min(struct alist *lst, uint min_alloc)
+{
+       uint new_alloc;
+
+       for (new_alloc = lst->alloc ?: ALIST_INITIAL_SIZE;
+            new_alloc < min_alloc;)
+               new_alloc *= 2;
+
+       return alist_expand_to(lst, new_alloc);
+}
+
+const void *alist_get_ptr(const struct alist *lst, uint index)
+{
+       if (index >= lst->count)
+               return NULL;
+
+       return lst->data + index * lst->obj_size;
+}
+
+void *alist_ensure_ptr(struct alist *lst, uint index)
+{
+       uint minsize = index + 1;
+       void *ptr;
+
+       if (index >= lst->alloc && !alist_expand_min(lst, minsize))
+               return NULL;
+
+       ptr = lst->data + index * lst->obj_size;
+       if (minsize >= lst->count)
+               lst->count = minsize;
+
+       return ptr;
+}
+
+void *alist_add_ptr(struct alist *lst, void *obj)
+{
+       void *ptr;
+
+       ptr = alist_ensure_ptr(lst, lst->count);
+       if (!ptr)
+               return ptr;
+       memcpy(ptr, obj, lst->obj_size);
+
+       return ptr;
+}
+
+void *alist_uninit_move_ptr(struct alist *alist, size_t *countp)
+{
+       void *ptr;
+
+       if (countp)
+               *countp = alist->count;
+       if (!alist->count) {
+               alist_uninit(alist);
+               return NULL;
+       }
+
+       ptr = alist->data;
+
+       /* Clear everything out so there is no record of the data */
+       alist_init(alist, alist->obj_size, 0);
+
+       return ptr;
+}
diff --git a/test/lib/Makefile b/test/lib/Makefile
index e75a263e6a4..70f14c46b1e 100644
--- a/test/lib/Makefile
+++ b/test/lib/Makefile
@@ -5,6 +5,7 @@
 ifeq ($(CONFIG_SPL_BUILD),)
 obj-y += cmd_ut_lib.o
 obj-y += abuf.o
+obj-y += alist.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/alist.c b/test/lib/alist.c
new file mode 100644
index 00000000000..f9050a963e0
--- /dev/null
+++ b/test/lib/alist.c
@@ -0,0 +1,197 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2023 Google LLC
+ * Written by Simon Glass <s...@chromium.org>
+ */
+
+#include <alist.h>
+#include <string.h>
+#include <test/lib.h>
+#include <test/test.h>
+#include <test/ut.h>
+
+struct my_struct {
+       uint val;
+       uint other_val;
+};
+
+enum {
+       obj_size        = sizeof(struct my_struct),
+};
+
+/* Test alist_init() */
+static int lib_test_alist_init(struct unit_test_state *uts)
+{
+       struct alist lst;
+       ulong start;
+
+       start = ut_check_free();
+
+       /* with a size of 0, the fields should be inited, with no memory used */
+       memset(&lst, '\xff', sizeof(lst));
+       ut_assert(alist_init_struct(&lst, struct my_struct));
+       ut_asserteq_ptr(NULL, lst.data);
+       ut_asserteq(0, lst.count);
+       ut_asserteq(0, lst.alloc);
+       ut_assertok(ut_check_delta(start));
+       alist_uninit(&lst);
+       ut_asserteq_ptr(NULL, lst.data);
+       ut_asserteq(0, lst.count);
+       ut_asserteq(0, lst.alloc);
+
+       /* use an impossible size */
+       ut_asserteq(false, alist_init(&lst, obj_size,
+                                     CONFIG_SYS_MALLOC_LEN));
+       ut_assertnull(lst.data);
+       ut_asserteq(0, lst.count);
+       ut_asserteq(0, lst.alloc);
+
+       /* use a small size */
+       ut_assert(alist_init(&lst, obj_size, 4));
+       ut_assertnonnull(lst.data);
+       ut_asserteq(0, lst.count);
+       ut_asserteq(4, lst.alloc);
+
+       /* free it */
+       alist_uninit(&lst);
+       ut_asserteq_ptr(NULL, lst.data);
+       ut_asserteq(0, lst.count);
+       ut_asserteq(0, lst.alloc);
+       ut_assertok(ut_check_delta(start));
+
+       /* Check for memory leaks */
+       ut_assertok(ut_check_delta(start));
+
+       return 0;
+}
+LIB_TEST(lib_test_alist_init, 0);
+
+/* Test alist_get() and alist_getd() */
+static int lib_test_alist_get(struct unit_test_state *uts)
+{
+       struct alist lst;
+       ulong start;
+       void *ptr;
+
+       start = ut_check_free();
+
+       ut_assert(alist_init(&lst, obj_size, 3));
+       ut_asserteq(0, lst.count);
+       ut_asserteq(3, lst.alloc);
+
+       ut_assertnull(alist_get_ptr(&lst, 2));
+       ut_assertnull(alist_get_ptr(&lst, 3));
+
+       ptr = alist_ensure_ptr(&lst, 1);
+       ut_assertnonnull(ptr);
+       ut_asserteq(2, lst.count);
+       ptr = alist_ensure_ptr(&lst, 2);
+       ut_asserteq(3, lst.count);
+       ut_assertnonnull(ptr);
+
+       ptr = alist_ensure_ptr(&lst, 3);
+       ut_assertnonnull(ptr);
+       ut_asserteq(4, lst.count);
+       ut_asserteq(6, lst.alloc);
+
+       ut_assertnull(alist_get_ptr(&lst, 4));
+
+       alist_uninit(&lst);
+
+       /* Check for memory leaks */
+       ut_assertok(ut_check_delta(start));
+
+       return 0;
+}
+LIB_TEST(lib_test_alist_get, 0);
+
+/* Test alist_has() */
+static int lib_test_alist_has(struct unit_test_state *uts)
+{
+       struct alist lst;
+       ulong start;
+       void *ptr;
+
+       start = ut_check_free();
+
+       ut_assert(alist_init(&lst, obj_size, 3));
+
+       ut_assert(!alist_has(&lst, 0));
+       ut_assert(!alist_has(&lst, 1));
+       ut_assert(!alist_has(&lst, 2));
+       ut_assert(!alist_has(&lst, 3));
+
+       /* create a new one to force expansion */
+       ptr = alist_ensure_ptr(&lst, 4);
+       ut_assertnonnull(ptr);
+
+       ut_assert(alist_has(&lst, 0));
+       ut_assert(alist_has(&lst, 1));
+       ut_assert(alist_has(&lst, 2));
+       ut_assert(alist_has(&lst, 3));
+       ut_assert(alist_has(&lst, 4));
+       ut_assert(!alist_has(&lst, 5));
+
+       alist_uninit(&lst);
+
+       /* Check for memory leaks */
+       ut_assertok(ut_check_delta(start));
+
+       return 0;
+}
+LIB_TEST(lib_test_alist_has, 0);
+
+/* Test alist_ensure() */
+static int lib_test_alist_ensure(struct unit_test_state *uts)
+{
+       struct my_struct *ptr3, *ptr4;
+       struct alist lst;
+       ulong start;
+
+       start = ut_check_free();
+
+       ut_assert(alist_init_struct(&lst, struct my_struct));
+       ut_asserteq(obj_size, lst.obj_size);
+       ut_asserteq(0, lst.count);
+       ut_asserteq(0, lst.alloc);
+       ptr3 = alist_ensure_ptr(&lst, 3);
+       ut_asserteq(4, lst.count);
+       ut_asserteq(4, lst.alloc);
+       ut_assertnonnull(ptr3);
+       ptr3->val = 3;
+
+       ptr4 = alist_ensure_ptr(&lst, 4);
+       ut_asserteq(8, lst.alloc);
+       ut_asserteq(5, lst.count);
+       ut_assertnonnull(ptr4);
+       ptr4->val = 4;
+       ut_asserteq(4, alist_get(&lst, 4, struct my_struct)->val);
+
+       ut_asserteq_ptr(ptr4, alist_ensure(&lst, 4, struct my_struct));
+
+       alist_ensure(&lst, 4, struct my_struct)->val = 44;
+       ut_asserteq(44, alist_get(&lst, 4, struct my_struct)->val);
+       ut_asserteq(3, alist_get(&lst, 3, struct my_struct)->val);
+       ut_assertnull(alist_get(&lst, 7, struct my_struct));
+       ut_asserteq(8, lst.alloc);
+       ut_asserteq(5, lst.count);
+
+       /* add some more, checking handling of malloc() failure */
+       malloc_enable_testing(0);
+       ut_assertnonnull(alist_ensure(&lst, 7, struct my_struct));
+       ut_assertnull(alist_ensure(&lst, 8, struct my_struct));
+       malloc_disable_testing();
+
+       lst.flags &= ~ALISTF_FAIL;
+       ut_assertnonnull(alist_ensure(&lst, 8, struct my_struct));
+       ut_asserteq(16, lst.alloc);
+       ut_asserteq(9, lst.count);
+
+       alist_uninit(&lst);
+
+       /* Check for memory leaks */
+       ut_assertok(ut_check_delta(start));
+
+       return 0;
+}
+LIB_TEST(lib_test_alist_ensure, 0);
-- 
2.34.1

Reply via email to