| /* 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"); |
| } |