diff options
Diffstat (limited to 'tools/src/iova.c')
| -rw-r--r-- | tools/src/iova.c | 233 |
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"); +} |
