FROMLIST: iommu/iova: Add a best-fit algorithm

Using the best-fit algorithm, instead of the first-fit
algorithm, may reduce fragmentation when allocating
IOVAs.

Bug: 149544392
Link: https://lore.kernel.org/lkml/7239ddd532e94a4371289f3be23c66a3@codeaurora.org/
Change-Id: Icfbac0cc7be972a092915335508cbc73c47471cf
Signed-off-by: Isaac J. Manjarres <isaacm@codeaurora.org>
This commit is contained in:
Liam Mark 2020-02-26 14:16:19 -08:00 committed by Todd Kjos
parent 741f92f5cb
commit 5e25537af7
4 changed files with 98 additions and 2 deletions

View File

@ -380,6 +380,24 @@ int iommu_dma_reserve_iova(struct device *dev, dma_addr_t base,
}
EXPORT_SYMBOL(iommu_dma_reserve_iova);
/*
* Should be called prior to using dma-apis.
*/
int iommu_dma_enable_best_fit_algo(struct device *dev)
{
struct iommu_domain *domain;
struct iova_domain *iovad;
domain = iommu_get_domain_for_dev(dev);
if (!domain || !domain->iova_cookie)
return -EINVAL;
iovad = &((struct iommu_dma_cookie *)domain->iova_cookie)->iovad;
iovad->best_fit = true;
return 0;
}
EXPORT_SYMBOL(iommu_dma_enable_best_fit_algo);
/**
* dma_info_to_prot - Translate DMA API directions and attributes to IOMMU API
* page flags.

View File

@ -50,6 +50,7 @@ init_iova_domain(struct iova_domain *iovad, unsigned long granule,
iovad->anchor.pfn_lo = iovad->anchor.pfn_hi = IOVA_ANCHOR;
rb_link_node(&iovad->anchor.node, NULL, &iovad->rbroot.rb_node);
rb_insert_color(&iovad->anchor.node, &iovad->rbroot);
iovad->best_fit = false;
init_iova_rcaches(iovad);
}
EXPORT_SYMBOL_GPL(init_iova_domain);
@ -227,6 +228,70 @@ iova32_full:
return -ENOMEM;
}
static int __alloc_and_insert_iova_best_fit(struct iova_domain *iovad,
unsigned long size,
unsigned long limit_pfn,
struct iova *new, bool size_aligned)
{
struct rb_node *curr, *prev;
struct iova *curr_iova, *prev_iova;
unsigned long flags;
unsigned long align_mask = ~0UL;
struct rb_node *candidate_rb_parent;
unsigned long new_pfn, candidate_pfn = ~0UL;
unsigned long gap, candidate_gap = ~0UL;
if (size_aligned)
align_mask <<= limit_align(iovad, fls_long(size - 1));
/* Walk the tree backwards */
spin_lock_irqsave(&iovad->iova_rbtree_lock, flags);
curr = &iovad->anchor.node;
prev = rb_prev(curr);
for (; prev; curr = prev, prev = rb_prev(curr)) {
curr_iova = rb_entry(curr, struct iova, node);
prev_iova = rb_entry(prev, struct iova, node);
limit_pfn = min(limit_pfn, curr_iova->pfn_lo);
new_pfn = (limit_pfn - size) & align_mask;
gap = curr_iova->pfn_lo - prev_iova->pfn_hi - 1;
if ((limit_pfn >= size) && (new_pfn > prev_iova->pfn_hi)
&& (gap < candidate_gap)) {
candidate_gap = gap;
candidate_pfn = new_pfn;
candidate_rb_parent = curr;
if (gap == size)
goto insert;
}
}
curr_iova = rb_entry(curr, struct iova, node);
limit_pfn = min(limit_pfn, curr_iova->pfn_lo);
new_pfn = (limit_pfn - size) & align_mask;
gap = curr_iova->pfn_lo - iovad->start_pfn;
if (limit_pfn >= size && new_pfn >= iovad->start_pfn &&
gap < candidate_gap) {
candidate_gap = gap;
candidate_pfn = new_pfn;
candidate_rb_parent = curr;
}
insert:
if (candidate_pfn == ~0UL) {
spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags);
return -ENOMEM;
}
/* pfn_lo will point to size aligned address if size_aligned is set */
new->pfn_lo = candidate_pfn;
new->pfn_hi = new->pfn_lo + size - 1;
/* If we have 'prev', it's a valid place to start the insertion. */
iova_insert_rbtree(&iovad->rbroot, new, candidate_rb_parent);
spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags);
return 0;
}
static struct kmem_cache *iova_cache;
static unsigned int iova_cache_users;
static DEFINE_MUTEX(iova_cache_mutex);
@ -302,8 +367,13 @@ alloc_iova(struct iova_domain *iovad, unsigned long size,
if (!new_iova)
return NULL;
ret = __alloc_and_insert_iova_range(iovad, size, limit_pfn + 1,
new_iova, size_aligned);
if (iovad->best_fit) {
ret = __alloc_and_insert_iova_range(iovad, size,
limit_pfn + 1, new_iova, size_aligned);
} else {
ret = __alloc_and_insert_iova_range(iovad, size, limit_pfn + 1,
new_iova, size_aligned);
}
if (ret) {
free_iova_mem(new_iova);

View File

@ -40,6 +40,8 @@ void iommu_dma_get_resv_regions(struct device *dev, struct list_head *list);
int iommu_dma_reserve_iova(struct device *dev, dma_addr_t base,
u64 size);
int iommu_dma_enable_best_fit_algo(struct device *dev);
#else /* CONFIG_IOMMU_DMA */
struct iommu_domain;
@ -87,5 +89,10 @@ static inline int iommu_dma_reserve_iova(struct device *dev, dma_addr_t base,
return -ENODEV;
}
static inline int iommu_dma_enable_best_fit_algo(struct device *dev)
{
return -ENODEV;
}
#endif /* CONFIG_IOMMU_DMA */
#endif /* __DMA_IOMMU_H */

View File

@ -95,6 +95,7 @@ struct iova_domain {
flush-queues */
atomic_t fq_timer_on; /* 1 when timer is active, 0
when not */
bool best_fit;
};
static inline unsigned long iova_size(struct iova *iova)