diff options
Diffstat (limited to 'drivers/iommu/iova.c')
| -rw-r--r-- | drivers/iommu/iova.c | 96 | 
1 files changed, 83 insertions, 13 deletions
| diff --git a/drivers/iommu/iova.c b/drivers/iommu/iova.c index e6e2fa85271c..b7ecd5b08039 100644 --- a/drivers/iommu/iova.c +++ b/drivers/iommu/iova.c @@ -22,11 +22,28 @@ static unsigned long iova_rcache_get(struct iova_domain *iovad,  				     unsigned long size,  				     unsigned long limit_pfn);  static void init_iova_rcaches(struct iova_domain *iovad); +static void free_cpu_cached_iovas(unsigned int cpu, struct iova_domain *iovad);  static void free_iova_rcaches(struct iova_domain *iovad);  static void fq_destroy_all_entries(struct iova_domain *iovad);  static void fq_flush_timeout(struct timer_list *t); + +static int iova_cpuhp_dead(unsigned int cpu, struct hlist_node *node) +{ +	struct iova_domain *iovad; + +	iovad = hlist_entry_safe(node, struct iova_domain, cpuhp_dead); + +	free_cpu_cached_iovas(cpu, iovad); +	return 0; +} +  static void free_global_cached_iovas(struct iova_domain *iovad); +static struct iova *to_iova(struct rb_node *node) +{ +	return rb_entry(node, struct iova, node); +} +  void  init_iova_domain(struct iova_domain *iovad, unsigned long granule,  	unsigned long start_pfn) @@ -51,6 +68,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); +	cpuhp_state_add_instance_nocalls(CPUHP_IOMMU_IOVA_DEAD, &iovad->cpuhp_dead);  	init_iova_rcaches(iovad);  }  EXPORT_SYMBOL_GPL(init_iova_domain); @@ -136,7 +154,7 @@ __cached_rbnode_delete_update(struct iova_domain *iovad, struct iova *free)  {  	struct iova *cached_iova; -	cached_iova = rb_entry(iovad->cached32_node, struct iova, node); +	cached_iova = to_iova(iovad->cached32_node);  	if (free == cached_iova ||  	    (free->pfn_hi < iovad->dma_32bit_pfn &&  	     free->pfn_lo >= cached_iova->pfn_lo)) { @@ -144,11 +162,48 @@ __cached_rbnode_delete_update(struct iova_domain *iovad, struct iova *free)  		iovad->max32_alloc_size = iovad->dma_32bit_pfn;  	} -	cached_iova = rb_entry(iovad->cached_node, struct iova, node); +	cached_iova = to_iova(iovad->cached_node);  	if (free->pfn_lo >= cached_iova->pfn_lo)  		iovad->cached_node = rb_next(&free->node);  } +static struct rb_node *iova_find_limit(struct iova_domain *iovad, unsigned long limit_pfn) +{ +	struct rb_node *node, *next; +	/* +	 * Ideally what we'd like to judge here is whether limit_pfn is close +	 * enough to the highest-allocated IOVA that starting the allocation +	 * walk from the anchor node will be quicker than this initial work to +	 * find an exact starting point (especially if that ends up being the +	 * anchor node anyway). This is an incredibly crude approximation which +	 * only really helps the most likely case, but is at least trivially easy. +	 */ +	if (limit_pfn > iovad->dma_32bit_pfn) +		return &iovad->anchor.node; + +	node = iovad->rbroot.rb_node; +	while (to_iova(node)->pfn_hi < limit_pfn) +		node = node->rb_right; + +search_left: +	while (node->rb_left && to_iova(node->rb_left)->pfn_lo >= limit_pfn) +		node = node->rb_left; + +	if (!node->rb_left) +		return node; + +	next = node->rb_left; +	while (next->rb_right) { +		next = next->rb_right; +		if (to_iova(next)->pfn_lo >= limit_pfn) { +			node = next; +			goto search_left; +		} +	} + +	return node; +} +  /* Insert the iova into domain rbtree by holding writer lock */  static void  iova_insert_rbtree(struct rb_root *root, struct iova *iova, @@ -159,7 +214,7 @@ iova_insert_rbtree(struct rb_root *root, struct iova *iova,  	new = (start) ? &start : &(root->rb_node);  	/* Figure out where to put new node */  	while (*new) { -		struct iova *this = rb_entry(*new, struct iova, node); +		struct iova *this = to_iova(*new);  		parent = *new; @@ -198,7 +253,7 @@ static int __alloc_and_insert_iova_range(struct iova_domain *iovad,  		goto iova32_full;  	curr = __get_cached_rbnode(iovad, limit_pfn); -	curr_iova = rb_entry(curr, struct iova, node); +	curr_iova = to_iova(curr);  	retry_pfn = curr_iova->pfn_hi + 1;  retry: @@ -207,15 +262,15 @@ retry:  		new_pfn = (high_pfn - size) & align_mask;  		prev = curr;  		curr = rb_prev(curr); -		curr_iova = rb_entry(curr, struct iova, node); +		curr_iova = to_iova(curr);  	} while (curr && new_pfn <= curr_iova->pfn_hi && new_pfn >= low_pfn);  	if (high_pfn < size || new_pfn < low_pfn) {  		if (low_pfn == iovad->start_pfn && retry_pfn < limit_pfn) {  			high_pfn = limit_pfn;  			low_pfn = retry_pfn; -			curr = &iovad->anchor.node; -			curr_iova = rb_entry(curr, struct iova, node); +			curr = iova_find_limit(iovad, limit_pfn); +			curr_iova = to_iova(curr);  			goto retry;  		}  		iovad->max32_alloc_size = size; @@ -257,10 +312,21 @@ int iova_cache_get(void)  {  	mutex_lock(&iova_cache_mutex);  	if (!iova_cache_users) { +		int ret; + +		ret = cpuhp_setup_state_multi(CPUHP_IOMMU_IOVA_DEAD, "iommu/iova:dead", NULL, +					iova_cpuhp_dead); +		if (ret) { +			mutex_unlock(&iova_cache_mutex); +			pr_err("Couldn't register cpuhp handler\n"); +			return ret; +		} +  		iova_cache = kmem_cache_create(  			"iommu_iova", sizeof(struct iova), 0,  			SLAB_HWCACHE_ALIGN, NULL);  		if (!iova_cache) { +			cpuhp_remove_multi_state(CPUHP_IOMMU_IOVA_DEAD);  			mutex_unlock(&iova_cache_mutex);  			pr_err("Couldn't create iova cache\n");  			return -ENOMEM; @@ -282,8 +348,10 @@ void iova_cache_put(void)  		return;  	}  	iova_cache_users--; -	if (!iova_cache_users) +	if (!iova_cache_users) { +		cpuhp_remove_multi_state(CPUHP_IOMMU_IOVA_DEAD);  		kmem_cache_destroy(iova_cache); +	}  	mutex_unlock(&iova_cache_mutex);  }  EXPORT_SYMBOL_GPL(iova_cache_put); @@ -331,7 +399,7 @@ private_find_iova(struct iova_domain *iovad, unsigned long pfn)  	assert_spin_locked(&iovad->iova_rbtree_lock);  	while (node) { -		struct iova *iova = rb_entry(node, struct iova, node); +		struct iova *iova = to_iova(node);  		if (pfn < iova->pfn_lo)  			node = node->rb_left; @@ -467,7 +535,6 @@ free_iova_fast(struct iova_domain *iovad, unsigned long pfn, unsigned long size)  	free_iova(iovad, pfn);  } -EXPORT_SYMBOL_GPL(free_iova_fast);  #define fq_ring_for_each(i, fq) \  	for ((i) = (fq)->head; (i) != (fq)->tail; (i) = ((i) + 1) % IOVA_FQ_SIZE) @@ -606,6 +673,9 @@ void put_iova_domain(struct iova_domain *iovad)  {  	struct iova *iova, *tmp; +	cpuhp_state_remove_instance_nocalls(CPUHP_IOMMU_IOVA_DEAD, +					    &iovad->cpuhp_dead); +  	free_iova_flush_queue(iovad);  	free_iova_rcaches(iovad);  	rbtree_postorder_for_each_entry_safe(iova, tmp, &iovad->rbroot, node) @@ -617,7 +687,7 @@ static int  __is_range_overlap(struct rb_node *node,  	unsigned long pfn_lo, unsigned long pfn_hi)  { -	struct iova *iova = rb_entry(node, struct iova, node); +	struct iova *iova = to_iova(node);  	if ((pfn_lo <= iova->pfn_hi) && (pfn_hi >= iova->pfn_lo))  		return 1; @@ -685,7 +755,7 @@ reserve_iova(struct iova_domain *iovad,  	spin_lock_irqsave(&iovad->iova_rbtree_lock, flags);  	for (node = rb_first(&iovad->rbroot); node; node = rb_next(node)) {  		if (__is_range_overlap(node, pfn_lo, pfn_hi)) { -			iova = rb_entry(node, struct iova, node); +			iova = to_iova(node);  			__adjust_overlap_range(iova, &pfn_lo, &pfn_hi);  			if ((pfn_lo >= iova->pfn_lo) &&  				(pfn_hi <= iova->pfn_hi)) @@ -970,7 +1040,7 @@ static void free_iova_rcaches(struct iova_domain *iovad)  /*   * free all the IOVA ranges cached by a cpu (used when cpu is unplugged)   */ -void free_cpu_cached_iovas(unsigned int cpu, struct iova_domain *iovad) +static void free_cpu_cached_iovas(unsigned int cpu, struct iova_domain *iovad)  {  	struct iova_cpu_rcache *cpu_rcache;  	struct iova_rcache *rcache; | 
