summaryrefslogtreecommitdiff
path: root/tools/src/iova.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/src/iova.c')
-rw-r--r--tools/src/iova.c233
1 files changed, 233 insertions, 0 deletions
diff --git a/tools/src/iova.c b/tools/src/iova.c
new file mode 100644
index 0000000..c3146cd
--- /dev/null
+++ b/tools/src/iova.c
@@ -0,0 +1,233 @@
+/* SPDX-License-Identifier: MIT */
+
+#include "iova.h"
+#include "malloc.h"
+#include "string.h"
+#include "utils.h"
+
+struct iova_block {
+ u64 iova;
+ size_t sz;
+ struct iova_block *next;
+};
+
+struct iova_domain {
+ u64 base;
+ u64 limit;
+ struct iova_block *free_list;
+};
+
+iova_domain_t *iovad_init(u64 base, u64 limit)
+{
+ if (base != ALIGN_UP(base, SZ_32M)) {
+ printf("iovad_init: base it not is not aligned to SZ_32M\n");
+ return NULL;
+ }
+
+ iova_domain_t *iovad = malloc(sizeof(*iovad));
+ if (!iovad)
+ return NULL;
+
+ memset(iovad, 0, sizeof(*iovad));
+
+ struct iova_block *blk = malloc(sizeof(*blk));
+ if (!blk) {
+ free(iovad);
+ return NULL;
+ }
+
+ /* don't hand out NULL pointers */
+ blk->iova = base;
+ blk->sz = limit - SZ_16K;
+ blk->next = NULL;
+ iovad->base = base;
+ iovad->limit = limit;
+ iovad->free_list = blk;
+
+ return iovad;
+}
+
+void iovad_shutdown(iova_domain_t *iovad, dart_dev_t *dart)
+{
+ struct iova_block *blk = iovad->free_list;
+
+ while (blk != NULL) {
+ struct iova_block *blk_free = blk;
+ blk = blk->next;
+
+ free(blk_free);
+ }
+
+ if (dart)
+ for (u64 addr = iovad->base; addr < iovad->limit; addr += SZ_32M)
+ dart_free_l2(dart, addr);
+
+ free(iovad);
+}
+
+bool iova_reserve(iova_domain_t *iovad, u64 iova, size_t sz)
+{
+ iova = ALIGN_DOWN(iova, SZ_16K);
+ sz = ALIGN_UP(sz, SZ_16K);
+
+ if (iova == 0) {
+ iova += SZ_16K;
+ sz -= SZ_16K;
+ }
+ if (sz == 0)
+ return true;
+
+ if (!iovad->free_list) {
+ printf("iova_reserve: trying to reserve iova range but empty free list\n");
+ return false;
+ }
+
+ struct iova_block *blk = iovad->free_list;
+ struct iova_block *blk_prev = NULL;
+ while (blk != NULL) {
+ if (iova >= blk->iova && iova < (blk->iova + blk->sz)) {
+ if (iova + sz >= (blk->iova + blk->sz)) {
+ printf("iova_reserve: tried to reserve [%lx; +%lx] but block in free list has "
+ "range [%lx; +%lx]\n",
+ iova, sz, blk->iova, blk->sz);
+ return false;
+ }
+
+ if (iova == blk->iova && sz == blk->sz) {
+ /* if the to-be-reserved range is present as a single block in the free list we just
+ * need to remove it */
+ if (blk_prev)
+ blk_prev->next = blk->next;
+ else
+ iovad->free_list = NULL;
+
+ free(blk);
+ return true;
+ } else if (iova == blk->iova) {
+ /* cut off the reserved range from the beginning */
+ blk->iova += sz;
+ blk->sz -= sz;
+ return true;
+ } else if (iova + sz == blk->iova + blk->sz) {
+ /* cut off the reserved range from the end */
+ blk->sz -= sz;
+ return true;
+ } else {
+ /* the to-be-reserved range is in the middle and we'll have to split this block */
+ struct iova_block *blk_new = malloc(sizeof(*blk_new));
+ if (!blk_new) {
+ printf("iova_reserve: out of memory.\n");
+ return false;
+ }
+
+ blk_new->iova = iova + sz;
+ blk_new->sz = blk->iova + blk->sz - blk_new->iova;
+ blk_new->next = blk->next;
+ blk->next = blk_new;
+ blk->sz = iova - blk->iova;
+ return true;
+ }
+ }
+
+ blk_prev = blk;
+ blk = blk->next;
+ }
+
+ printf("iova_reserve: tried to reserve [%lx; +%lx] but range is already used.\n", iova, sz);
+ return false;
+}
+
+u64 iova_alloc(iova_domain_t *iovad, size_t sz)
+{
+ sz = ALIGN_UP(sz, SZ_16K);
+
+ struct iova_block *blk_prev = NULL;
+ struct iova_block *blk = iovad->free_list;
+ while (blk != NULL) {
+ if (blk->sz == sz) {
+ u64 iova = blk->iova;
+
+ if (blk_prev)
+ blk_prev->next = blk->next;
+ else
+ iovad->free_list = blk->next;
+
+ free(blk);
+ return iova;
+ } else if (blk->sz > sz) {
+ u64 iova = blk->iova;
+
+ blk->iova += sz;
+ blk->sz -= sz;
+
+ return iova;
+ }
+
+ blk_prev = blk;
+ blk = blk->next;
+ }
+
+ return 0;
+}
+
+void iova_free(iova_domain_t *iovad, u64 iova, size_t sz)
+{
+ sz = ALIGN_UP(sz, SZ_16K);
+
+ struct iova_block *blk_prev = NULL;
+ struct iova_block *blk = iovad->free_list;
+
+ /* create a new free list if it's empty */
+ if (!blk) {
+ blk = malloc(sizeof(*blk));
+ if (!blk)
+ panic("out of memory in iovad_free");
+ blk->iova = iova;
+ blk->sz = sz;
+ blk->next = NULL;
+ iovad->free_list = blk;
+ return;
+ }
+
+ while (blk != NULL) {
+ if ((iova + sz) == blk->iova) {
+ /* extend the block at the beginning */
+ blk->iova -= sz;
+ blk->sz += sz;
+
+ /* if we have just extended the start of the free list we're already done */
+ if (!blk_prev)
+ return;
+
+ /* check if we can merge two blocks otherwise */
+ if ((blk_prev->iova + blk_prev->sz) == blk->iova) {
+ blk_prev->sz += blk->sz;
+ blk_prev->next = blk->next;
+ free(blk);
+ }
+
+ return;
+ } else if ((iova + sz) < blk->iova) {
+ /* create a new block */
+ struct iova_block *blk_new = malloc(sizeof(*blk_new));
+ if (!blk_new)
+ panic("iova_free: out of memory\n");
+
+ blk_new->iova = iova;
+ blk_new->sz = sz;
+ blk_new->next = blk;
+
+ if (blk_prev)
+ blk_prev->next = blk_new;
+ else
+ iovad->free_list = blk_new;
+
+ return;
+ }
+
+ blk_prev = blk;
+ blk = blk->next;
+ }
+
+ panic("iovad_free: corruption detected, unable to insert freed range\n");
+}