iova: Add a simple IOVA allocator

Not well tested because I realized too late that NVMe doesn't
actually need any DART support...

Signed-off-by: Sven Peter <sven@svenpeter.dev>
This commit is contained in:
Sven Peter 2022-01-10 18:16:57 +01:00 committed by Hector Martin
parent 2b792ffc34
commit c0cc000ce3
3 changed files with 238 additions and 0 deletions

View file

@ -66,6 +66,7 @@ OBJECTS := \
hv.o hv_vm.o hv_exc.o hv_vuart.o hv_wdt.o hv_asm.o hv_aic.o \
i2c.o \
iodev.o \
iova.o \
kboot.o \
main.o \
mcc.o \

220
src/iova.c Normal file
View file

@ -0,0 +1,220 @@
/* 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 {
struct iova_block *free_list;
};
iova_domain_t *iovad_init(void)
{
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 = SZ_16K;
blk->sz = (1ULL << 32) - SZ_16K;
blk->next = NULL;
iovad->free_list = blk;
return iovad;
}
void iovad_shutdown(iova_domain_t *iovad)
{
struct iova_block *blk = iovad->free_list;
while (blk != NULL) {
struct iova_block *blk_free = blk;
blk = blk->next;
free(blk_free);
}
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");
}

17
src/iova.h Normal file
View file

@ -0,0 +1,17 @@
/* SPDX-License-Identifier: MIT */
#ifndef IOVA_H
#define IOVA_H
#include "types.h"
typedef struct iova_domain iova_domain_t;
iova_domain_t *iovad_init(void);
void iovad_shutdown(iova_domain_t *iovad);
bool iova_reserve(iova_domain_t *iovad, u64 iova, size_t sz);
u64 iova_alloc(iova_domain_t *iovad, size_t sz);
void iova_free(iova_domain_t *iovad, u64 iova, size_t sz);
#endif