blob: 29a254982d1c4efd567c9ce5abf3f286c8b0ebe9 [file]
/* 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 = calloc(1, sizeof(*iovad));
if (!iovad)
return NULL;
struct iova_block *blk = calloc(1, 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 = calloc(1, 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 = calloc(1, 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 = calloc(1, 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");
}