Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5e25537a authored by Liam Mark's avatar Liam Mark Committed by Todd Kjos
Browse files

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: default avatarIsaac J. Manjarres <isaacm@codeaurora.org>
parent 741f92f5
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -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.
+72 −2
Original line number Diff line number Diff line
@@ -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 @@ static int __alloc_and_insert_iova_range(struct iova_domain *iovad,
	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;

	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);
+7 −0
Original line number Diff line number Diff line
@@ -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 */
+1 −0
Original line number Diff line number Diff line
@@ -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)