2658 lines
		
	
	
		
			69 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			2658 lines
		
	
	
		
			69 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  *
 | |
|  * (C) COPYRIGHT 2010-2017 ARM Limited. All rights reserved.
 | |
|  *
 | |
|  * This program is free software and is provided to you under the terms of the
 | |
|  * GNU General Public License version 2 as published by the Free Software
 | |
|  * Foundation, and any use by you of this program is subject to the terms
 | |
|  * of such GNU licence.
 | |
|  *
 | |
|  * A copy of the licence is included with the program, and can also be obtained
 | |
|  * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 | |
|  * Boston, MA  02110-1301, USA.
 | |
|  *
 | |
|  */
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * @file mali_kbase_mem.c
 | |
|  * Base kernel memory APIs
 | |
|  */
 | |
| #ifdef CONFIG_DMA_SHARED_BUFFER
 | |
| #include <linux/dma-buf.h>
 | |
| #endif				/* CONFIG_DMA_SHARED_BUFFER */
 | |
| #ifdef CONFIG_UMP
 | |
| #include <linux/ump.h>
 | |
| #endif				/* CONFIG_UMP */
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/bug.h>
 | |
| #include <linux/compat.h>
 | |
| #include <linux/version.h>
 | |
| #include <linux/sched/mm.h>
 | |
| 
 | |
| #include <mali_kbase_config.h>
 | |
| #include <mali_kbase.h>
 | |
| #include <mali_midg_regmap.h>
 | |
| #include <mali_kbase_cache_policy.h>
 | |
| #include <mali_kbase_hw.h>
 | |
| #include <mali_kbase_hwaccess_time.h>
 | |
| #include <mali_kbase_tlstream.h>
 | |
| 
 | |
| /* This function finds out which RB tree the given GPU VA region belongs to
 | |
|  * based on the region zone */
 | |
| static struct rb_root *kbase_reg_flags_to_rbtree(struct kbase_context *kctx,
 | |
| 						    struct kbase_va_region *reg)
 | |
| {
 | |
| 	struct rb_root *rbtree = NULL;
 | |
| 
 | |
| 	switch (reg->flags & KBASE_REG_ZONE_MASK) {
 | |
| 	case KBASE_REG_ZONE_CUSTOM_VA:
 | |
| 		rbtree = &kctx->reg_rbtree_custom;
 | |
| 		break;
 | |
| 	case KBASE_REG_ZONE_EXEC:
 | |
| 		rbtree = &kctx->reg_rbtree_exec;
 | |
| 		break;
 | |
| 	case KBASE_REG_ZONE_SAME_VA:
 | |
| 		rbtree = &kctx->reg_rbtree_same;
 | |
| 		fallthrough;
 | |
| 	default:
 | |
| 		rbtree = &kctx->reg_rbtree_same;
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	return rbtree;
 | |
| }
 | |
| 
 | |
| /* This function finds out which RB tree the given pfn from the GPU VA belongs
 | |
|  * to based on the memory zone the pfn refers to */
 | |
| static struct rb_root *kbase_gpu_va_to_rbtree(struct kbase_context *kctx,
 | |
| 								    u64 gpu_pfn)
 | |
| {
 | |
| 	struct rb_root *rbtree = NULL;
 | |
| 
 | |
| #ifdef CONFIG_64BIT
 | |
| 	if (kbase_ctx_flag(kctx, KCTX_COMPAT)) {
 | |
| #endif /* CONFIG_64BIT */
 | |
| 		if (gpu_pfn >= KBASE_REG_ZONE_CUSTOM_VA_BASE)
 | |
| 			rbtree = &kctx->reg_rbtree_custom;
 | |
| 		else if (gpu_pfn >= KBASE_REG_ZONE_EXEC_BASE)
 | |
| 			rbtree = &kctx->reg_rbtree_exec;
 | |
| 		else
 | |
| 			rbtree = &kctx->reg_rbtree_same;
 | |
| #ifdef CONFIG_64BIT
 | |
| 	} else {
 | |
| 		if (gpu_pfn >= kctx->same_va_end)
 | |
| 			rbtree = &kctx->reg_rbtree_custom;
 | |
| 		else
 | |
| 			rbtree = &kctx->reg_rbtree_same;
 | |
| 	}
 | |
| #endif /* CONFIG_64BIT */
 | |
| 
 | |
| 	return rbtree;
 | |
| }
 | |
| 
 | |
| /* This function inserts a region into the tree. */
 | |
| static void kbase_region_tracker_insert(struct kbase_context *kctx,
 | |
| 						struct kbase_va_region *new_reg)
 | |
| {
 | |
| 	u64 start_pfn = new_reg->start_pfn;
 | |
| 	struct rb_node **link = NULL;
 | |
| 	struct rb_node *parent = NULL;
 | |
| 	struct rb_root *rbtree = NULL;
 | |
| 
 | |
| 	rbtree = kbase_reg_flags_to_rbtree(kctx, new_reg);
 | |
| 
 | |
| 	link = &(rbtree->rb_node);
 | |
| 	/* Find the right place in the tree using tree search */
 | |
| 	while (*link) {
 | |
| 		struct kbase_va_region *old_reg;
 | |
| 
 | |
| 		parent = *link;
 | |
| 		old_reg = rb_entry(parent, struct kbase_va_region, rblink);
 | |
| 
 | |
| 		/* RBTree requires no duplicate entries. */
 | |
| 		KBASE_DEBUG_ASSERT(old_reg->start_pfn != start_pfn);
 | |
| 
 | |
| 		if (old_reg->start_pfn > start_pfn)
 | |
| 			link = &(*link)->rb_left;
 | |
| 		else
 | |
| 			link = &(*link)->rb_right;
 | |
| 	}
 | |
| 
 | |
| 	/* Put the new node there, and rebalance tree */
 | |
| 	rb_link_node(&(new_reg->rblink), parent, link);
 | |
| 
 | |
| 	rb_insert_color(&(new_reg->rblink), rbtree);
 | |
| }
 | |
| 
 | |
| /* Find allocated region enclosing free range. */
 | |
| static struct kbase_va_region *kbase_region_tracker_find_region_enclosing_range_free(
 | |
| 		struct kbase_context *kctx, u64 start_pfn, size_t nr_pages)
 | |
| {
 | |
| 	struct rb_node *rbnode = NULL;
 | |
| 	struct kbase_va_region *reg = NULL;
 | |
| 	struct rb_root *rbtree = NULL;
 | |
| 
 | |
| 	u64 end_pfn = start_pfn + nr_pages;
 | |
| 
 | |
| 	rbtree = kbase_gpu_va_to_rbtree(kctx, start_pfn);
 | |
| 
 | |
| 	rbnode = rbtree->rb_node;
 | |
| 
 | |
| 	while (rbnode) {
 | |
| 		u64 tmp_start_pfn, tmp_end_pfn;
 | |
| 
 | |
| 		reg = rb_entry(rbnode, struct kbase_va_region, rblink);
 | |
| 		tmp_start_pfn = reg->start_pfn;
 | |
| 		tmp_end_pfn = reg->start_pfn + reg->nr_pages;
 | |
| 
 | |
| 		/* If start is lower than this, go left. */
 | |
| 		if (start_pfn < tmp_start_pfn)
 | |
| 			rbnode = rbnode->rb_left;
 | |
| 		/* If end is higher than this, then go right. */
 | |
| 		else if (end_pfn > tmp_end_pfn)
 | |
| 			rbnode = rbnode->rb_right;
 | |
| 		else	/* Enclosing */
 | |
| 			return reg;
 | |
| 	}
 | |
| 
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| /* Find region enclosing given address. */
 | |
| struct kbase_va_region *kbase_region_tracker_find_region_enclosing_address(struct kbase_context *kctx, u64 gpu_addr)
 | |
| {
 | |
| 	struct rb_node *rbnode;
 | |
| 	struct kbase_va_region *reg;
 | |
| 	u64 gpu_pfn = gpu_addr >> PAGE_SHIFT;
 | |
| 	struct rb_root *rbtree = NULL;
 | |
| 
 | |
| 	KBASE_DEBUG_ASSERT(NULL != kctx);
 | |
| 
 | |
| 	lockdep_assert_held(&kctx->reg_lock);
 | |
| 
 | |
| 	rbtree = kbase_gpu_va_to_rbtree(kctx, gpu_pfn);
 | |
| 
 | |
| 	rbnode = rbtree->rb_node;
 | |
| 
 | |
| 	while (rbnode) {
 | |
| 		u64 tmp_start_pfn, tmp_end_pfn;
 | |
| 
 | |
| 		reg = rb_entry(rbnode, struct kbase_va_region, rblink);
 | |
| 		tmp_start_pfn = reg->start_pfn;
 | |
| 		tmp_end_pfn = reg->start_pfn + reg->nr_pages;
 | |
| 
 | |
| 		/* If start is lower than this, go left. */
 | |
| 		if (gpu_pfn < tmp_start_pfn)
 | |
| 			rbnode = rbnode->rb_left;
 | |
| 		/* If end is higher than this, then go right. */
 | |
| 		else if (gpu_pfn >= tmp_end_pfn)
 | |
| 			rbnode = rbnode->rb_right;
 | |
| 		else	/* Enclosing */
 | |
| 			return reg;
 | |
| 	}
 | |
| 
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| KBASE_EXPORT_TEST_API(kbase_region_tracker_find_region_enclosing_address);
 | |
| 
 | |
| /* Find region with given base address */
 | |
| struct kbase_va_region *kbase_region_tracker_find_region_base_address(struct kbase_context *kctx, u64 gpu_addr)
 | |
| {
 | |
| 	u64 gpu_pfn = gpu_addr >> PAGE_SHIFT;
 | |
| 	struct rb_node *rbnode = NULL;
 | |
| 	struct kbase_va_region *reg = NULL;
 | |
| 	struct rb_root *rbtree = NULL;
 | |
| 
 | |
| 	KBASE_DEBUG_ASSERT(NULL != kctx);
 | |
| 
 | |
| 	lockdep_assert_held(&kctx->reg_lock);
 | |
| 
 | |
| 	rbtree = kbase_gpu_va_to_rbtree(kctx, gpu_pfn);
 | |
| 
 | |
| 	rbnode = rbtree->rb_node;
 | |
| 
 | |
| 	while (rbnode) {
 | |
| 		reg = rb_entry(rbnode, struct kbase_va_region, rblink);
 | |
| 		if (reg->start_pfn > gpu_pfn)
 | |
| 			rbnode = rbnode->rb_left;
 | |
| 		else if (reg->start_pfn < gpu_pfn)
 | |
| 			rbnode = rbnode->rb_right;
 | |
| 		else
 | |
| 			return reg;
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| KBASE_EXPORT_TEST_API(kbase_region_tracker_find_region_base_address);
 | |
| 
 | |
| /* Find region meeting given requirements */
 | |
| static struct kbase_va_region *kbase_region_tracker_find_region_meeting_reqs(struct kbase_context *kctx, struct kbase_va_region *reg_reqs, size_t nr_pages, size_t align)
 | |
| {
 | |
| 	struct rb_node *rbnode = NULL;
 | |
| 	struct kbase_va_region *reg = NULL;
 | |
| 	struct rb_root *rbtree = NULL;
 | |
| 
 | |
| 	/* Note that this search is a linear search, as we do not have a target
 | |
| 	   address in mind, so does not benefit from the rbtree search */
 | |
| 
 | |
| 	rbtree = kbase_reg_flags_to_rbtree(kctx, reg_reqs);
 | |
| 
 | |
| 	rbnode = rb_first(rbtree);
 | |
| 
 | |
| 	while (rbnode) {
 | |
| 		reg = rb_entry(rbnode, struct kbase_va_region, rblink);
 | |
| 		if ((reg->nr_pages >= nr_pages) &&
 | |
| 				(reg->flags & KBASE_REG_FREE)) {
 | |
| 			/* Check alignment */
 | |
| 			u64 start_pfn = (reg->start_pfn + align - 1) & ~(align - 1);
 | |
| 
 | |
| 			if ((start_pfn >= reg->start_pfn) &&
 | |
| 					(start_pfn <= (reg->start_pfn + reg->nr_pages - 1)) &&
 | |
| 					((start_pfn + nr_pages - 1) <= (reg->start_pfn + reg->nr_pages - 1)))
 | |
| 				return reg;
 | |
| 		}
 | |
| 		rbnode = rb_next(rbnode);
 | |
| 	}
 | |
| 
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Remove a region object from the global list.
 | |
|  *
 | |
|  * The region reg is removed, possibly by merging with other free and
 | |
|  * compatible adjacent regions.  It must be called with the context
 | |
|  * region lock held. The associated memory is not released (see
 | |
|  * kbase_free_alloced_region). Internal use only.
 | |
|  */
 | |
| static int kbase_remove_va_region(struct kbase_context *kctx, struct kbase_va_region *reg)
 | |
| {
 | |
| 	struct rb_node *rbprev;
 | |
| 	struct kbase_va_region *prev = NULL;
 | |
| 	struct rb_node *rbnext;
 | |
| 	struct kbase_va_region *next = NULL;
 | |
| 	struct rb_root *reg_rbtree = NULL;
 | |
| 
 | |
| 	int merged_front = 0;
 | |
| 	int merged_back = 0;
 | |
| 	int err = 0;
 | |
| 
 | |
| 	reg_rbtree = kbase_reg_flags_to_rbtree(kctx, reg);
 | |
| 
 | |
| 	/* Try to merge with the previous block first */
 | |
| 	rbprev = rb_prev(&(reg->rblink));
 | |
| 	if (rbprev) {
 | |
| 		prev = rb_entry(rbprev, struct kbase_va_region, rblink);
 | |
| 		if (prev->flags & KBASE_REG_FREE) {
 | |
| 			/* We're compatible with the previous VMA,
 | |
| 			 * merge with it */
 | |
| 			WARN_ON((prev->flags & KBASE_REG_ZONE_MASK) !=
 | |
| 					    (reg->flags & KBASE_REG_ZONE_MASK));
 | |
| 			prev->nr_pages += reg->nr_pages;
 | |
| 			rb_erase(&(reg->rblink), reg_rbtree);
 | |
| 			reg = prev;
 | |
| 			merged_front = 1;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* Try to merge with the next block second */
 | |
| 	/* Note we do the lookup here as the tree may have been rebalanced. */
 | |
| 	rbnext = rb_next(&(reg->rblink));
 | |
| 	if (rbnext) {
 | |
| 		/* We're compatible with the next VMA, merge with it */
 | |
| 		next = rb_entry(rbnext, struct kbase_va_region, rblink);
 | |
| 		if (next->flags & KBASE_REG_FREE) {
 | |
| 			WARN_ON((next->flags & KBASE_REG_ZONE_MASK) !=
 | |
| 					    (reg->flags & KBASE_REG_ZONE_MASK));
 | |
| 			next->start_pfn = reg->start_pfn;
 | |
| 			next->nr_pages += reg->nr_pages;
 | |
| 			rb_erase(&(reg->rblink), reg_rbtree);
 | |
| 			merged_back = 1;
 | |
| 			if (merged_front) {
 | |
| 				/* We already merged with prev, free it */
 | |
| 				kbase_free_alloced_region(reg);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* If we failed to merge then we need to add a new block */
 | |
| 	if (!(merged_front || merged_back)) {
 | |
| 		/*
 | |
| 		 * We didn't merge anything. Add a new free
 | |
| 		 * placeholder and remove the original one.
 | |
| 		 */
 | |
| 		struct kbase_va_region *free_reg;
 | |
| 
 | |
| 		free_reg = kbase_alloc_free_region(kctx, reg->start_pfn, reg->nr_pages, reg->flags & KBASE_REG_ZONE_MASK);
 | |
| 		if (!free_reg) {
 | |
| 			err = -ENOMEM;
 | |
| 			goto out;
 | |
| 		}
 | |
| 		rb_replace_node(&(reg->rblink), &(free_reg->rblink), reg_rbtree);
 | |
| 	}
 | |
| 
 | |
|  out:
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| KBASE_EXPORT_TEST_API(kbase_remove_va_region);
 | |
| 
 | |
| /**
 | |
|  * @brief Insert a VA region to the list, replacing the current at_reg.
 | |
|  */
 | |
| static int kbase_insert_va_region_nolock(struct kbase_context *kctx, struct kbase_va_region *new_reg, struct kbase_va_region *at_reg, u64 start_pfn, size_t nr_pages)
 | |
| {
 | |
| 	struct rb_root *reg_rbtree = NULL;
 | |
| 	int err = 0;
 | |
| 
 | |
| 	reg_rbtree = kbase_reg_flags_to_rbtree(kctx, at_reg);
 | |
| 
 | |
| 	/* Must be a free region */
 | |
| 	KBASE_DEBUG_ASSERT((at_reg->flags & KBASE_REG_FREE) != 0);
 | |
| 	/* start_pfn should be contained within at_reg */
 | |
| 	KBASE_DEBUG_ASSERT((start_pfn >= at_reg->start_pfn) && (start_pfn < at_reg->start_pfn + at_reg->nr_pages));
 | |
| 	/* at least nr_pages from start_pfn should be contained within at_reg */
 | |
| 	KBASE_DEBUG_ASSERT(start_pfn + nr_pages <= at_reg->start_pfn + at_reg->nr_pages);
 | |
| 
 | |
| 	new_reg->start_pfn = start_pfn;
 | |
| 	new_reg->nr_pages = nr_pages;
 | |
| 
 | |
| 	/* Regions are a whole use, so swap and delete old one. */
 | |
| 	if (at_reg->start_pfn == start_pfn && at_reg->nr_pages == nr_pages) {
 | |
| 		rb_replace_node(&(at_reg->rblink), &(new_reg->rblink),
 | |
| 								reg_rbtree);
 | |
| 		kbase_free_alloced_region(at_reg);
 | |
| 	}
 | |
| 	/* New region replaces the start of the old one, so insert before. */
 | |
| 	else if (at_reg->start_pfn == start_pfn) {
 | |
| 		at_reg->start_pfn += nr_pages;
 | |
| 		KBASE_DEBUG_ASSERT(at_reg->nr_pages >= nr_pages);
 | |
| 		at_reg->nr_pages -= nr_pages;
 | |
| 
 | |
| 		kbase_region_tracker_insert(kctx, new_reg);
 | |
| 	}
 | |
| 	/* New region replaces the end of the old one, so insert after. */
 | |
| 	else if ((at_reg->start_pfn + at_reg->nr_pages) == (start_pfn + nr_pages)) {
 | |
| 		at_reg->nr_pages -= nr_pages;
 | |
| 
 | |
| 		kbase_region_tracker_insert(kctx, new_reg);
 | |
| 	}
 | |
| 	/* New region splits the old one, so insert and create new */
 | |
| 	else {
 | |
| 		struct kbase_va_region *new_front_reg;
 | |
| 
 | |
| 		new_front_reg = kbase_alloc_free_region(kctx,
 | |
| 				at_reg->start_pfn,
 | |
| 				start_pfn - at_reg->start_pfn,
 | |
| 				at_reg->flags & KBASE_REG_ZONE_MASK);
 | |
| 
 | |
| 		if (new_front_reg) {
 | |
| 			at_reg->nr_pages -= nr_pages + new_front_reg->nr_pages;
 | |
| 			at_reg->start_pfn = start_pfn + nr_pages;
 | |
| 
 | |
| 			kbase_region_tracker_insert(kctx, new_front_reg);
 | |
| 			kbase_region_tracker_insert(kctx, new_reg);
 | |
| 		} else {
 | |
| 			err = -ENOMEM;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Add a VA region to the list.
 | |
|  */
 | |
| int kbase_add_va_region(struct kbase_context *kctx,
 | |
| 		struct kbase_va_region *reg, u64 addr,
 | |
| 		size_t nr_pages, size_t align)
 | |
| {
 | |
| 	struct kbase_va_region *tmp;
 | |
| 	u64 gpu_pfn = addr >> PAGE_SHIFT;
 | |
| 	int err = 0;
 | |
| 
 | |
| 	KBASE_DEBUG_ASSERT(NULL != kctx);
 | |
| 	KBASE_DEBUG_ASSERT(NULL != reg);
 | |
| 
 | |
| 	lockdep_assert_held(&kctx->reg_lock);
 | |
| 
 | |
| 	if (!align)
 | |
| 		align = 1;
 | |
| 
 | |
| 	/* must be a power of 2 */
 | |
| 	KBASE_DEBUG_ASSERT((align & (align - 1)) == 0);
 | |
| 	KBASE_DEBUG_ASSERT(nr_pages > 0);
 | |
| 
 | |
| 	/* Path 1: Map a specific address. Find the enclosing region, which *must* be free. */
 | |
| 	if (gpu_pfn) {
 | |
| 		struct device *dev = kctx->kbdev->dev;
 | |
| 
 | |
| 		KBASE_DEBUG_ASSERT(!(gpu_pfn & (align - 1)));
 | |
| 
 | |
| 		tmp = kbase_region_tracker_find_region_enclosing_range_free(kctx, gpu_pfn, nr_pages);
 | |
| 		if (!tmp) {
 | |
| 			dev_warn(dev, "Enclosing region not found: 0x%08llx gpu_pfn, %zu nr_pages", gpu_pfn, nr_pages);
 | |
| 			err = -ENOMEM;
 | |
| 			goto exit;
 | |
| 		}
 | |
| 		if (!(tmp->flags & KBASE_REG_FREE)) {
 | |
| 			dev_warn(dev, "Zone mismatch: %lu != %lu", tmp->flags & KBASE_REG_ZONE_MASK, reg->flags & KBASE_REG_ZONE_MASK);
 | |
| 			dev_warn(dev, "!(tmp->flags & KBASE_REG_FREE): tmp->start_pfn=0x%llx tmp->flags=0x%lx tmp->nr_pages=0x%zx gpu_pfn=0x%llx nr_pages=0x%zx\n", tmp->start_pfn, tmp->flags, tmp->nr_pages, gpu_pfn, nr_pages);
 | |
| 			dev_warn(dev, "in function %s (%p, %p, 0x%llx, 0x%zx, 0x%zx)\n", __func__, kctx, reg, addr, nr_pages, align);
 | |
| 			err = -ENOMEM;
 | |
| 			goto exit;
 | |
| 		}
 | |
| 
 | |
| 		err = kbase_insert_va_region_nolock(kctx, reg, tmp, gpu_pfn, nr_pages);
 | |
| 		if (err) {
 | |
| 			dev_warn(dev, "Failed to insert va region");
 | |
| 			err = -ENOMEM;
 | |
| 			goto exit;
 | |
| 		}
 | |
| 
 | |
| 		goto exit;
 | |
| 	}
 | |
| 
 | |
| 	/* Path 2: Map any free address which meets the requirements.  */
 | |
| 	{
 | |
| 		u64 start_pfn;
 | |
| 
 | |
| 		/*
 | |
| 		 * Depending on the zone the allocation request is for
 | |
| 		 * we might need to retry it.
 | |
| 		 */
 | |
| 		do {
 | |
| 			tmp = kbase_region_tracker_find_region_meeting_reqs(
 | |
| 					kctx, reg, nr_pages, align);
 | |
| 			if (tmp) {
 | |
| 				start_pfn = (tmp->start_pfn + align - 1) &
 | |
| 						~(align - 1);
 | |
| 				err = kbase_insert_va_region_nolock(kctx, reg,
 | |
| 						tmp, start_pfn, nr_pages);
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			/*
 | |
| 			 * If the allocation is not from the same zone as JIT
 | |
| 			 * then don't retry, we're out of VA and there is
 | |
| 			 * nothing which can be done about it.
 | |
| 			 */
 | |
| 			if ((reg->flags & KBASE_REG_ZONE_MASK) !=
 | |
| 					KBASE_REG_ZONE_CUSTOM_VA)
 | |
| 				break;
 | |
| 		} while (kbase_jit_evict(kctx));
 | |
| 
 | |
| 		if (!tmp)
 | |
| 			err = -ENOMEM;
 | |
| 	}
 | |
| 
 | |
|  exit:
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| KBASE_EXPORT_TEST_API(kbase_add_va_region);
 | |
| 
 | |
| /**
 | |
|  * @brief Initialize the internal region tracker data structure.
 | |
|  */
 | |
| static void kbase_region_tracker_ds_init(struct kbase_context *kctx,
 | |
| 		struct kbase_va_region *same_va_reg,
 | |
| 		struct kbase_va_region *exec_reg,
 | |
| 		struct kbase_va_region *custom_va_reg)
 | |
| {
 | |
| 	kctx->reg_rbtree_same = RB_ROOT;
 | |
| 	kbase_region_tracker_insert(kctx, same_va_reg);
 | |
| 
 | |
| 	/* Although exec and custom_va_reg don't always exist,
 | |
| 	 * initialize unconditionally because of the mem_view debugfs
 | |
| 	 * implementation which relies on these being empty */
 | |
| 	kctx->reg_rbtree_exec = RB_ROOT;
 | |
| 	kctx->reg_rbtree_custom = RB_ROOT;
 | |
| 
 | |
| 	if (exec_reg)
 | |
| 		kbase_region_tracker_insert(kctx, exec_reg);
 | |
| 	if (custom_va_reg)
 | |
| 		kbase_region_tracker_insert(kctx, custom_va_reg);
 | |
| }
 | |
| 
 | |
| static void kbase_region_tracker_erase_rbtree(struct rb_root *rbtree)
 | |
| {
 | |
| 	struct rb_node *rbnode;
 | |
| 	struct kbase_va_region *reg;
 | |
| 
 | |
| 	do {
 | |
| 		rbnode = rb_first(rbtree);
 | |
| 		if (rbnode) {
 | |
| 			rb_erase(rbnode, rbtree);
 | |
| 			reg = rb_entry(rbnode, struct kbase_va_region, rblink);
 | |
| 			kbase_free_alloced_region(reg);
 | |
| 		}
 | |
| 	} while (rbnode);
 | |
| }
 | |
| 
 | |
| void kbase_region_tracker_term(struct kbase_context *kctx)
 | |
| {
 | |
| 	kbase_region_tracker_erase_rbtree(&kctx->reg_rbtree_same);
 | |
| 	kbase_region_tracker_erase_rbtree(&kctx->reg_rbtree_exec);
 | |
| 	kbase_region_tracker_erase_rbtree(&kctx->reg_rbtree_custom);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Initialize the region tracker data structure.
 | |
|  */
 | |
| int kbase_region_tracker_init(struct kbase_context *kctx)
 | |
| {
 | |
| 	struct kbase_va_region *same_va_reg;
 | |
| 	struct kbase_va_region *exec_reg = NULL;
 | |
| 	struct kbase_va_region *custom_va_reg = NULL;
 | |
| 	size_t same_va_bits = sizeof(void *) * BITS_PER_BYTE;
 | |
| 	u64 custom_va_size = KBASE_REG_ZONE_CUSTOM_VA_SIZE;
 | |
| 	u64 gpu_va_limit = (1ULL << kctx->kbdev->gpu_props.mmu.va_bits) >> PAGE_SHIFT;
 | |
| 	u64 same_va_pages;
 | |
| 	int err;
 | |
| 
 | |
| 	/* Take the lock as kbase_free_alloced_region requires it */
 | |
| 	kbase_gpu_vm_lock(kctx);
 | |
| 
 | |
| #if defined(CONFIG_ARM64)
 | |
| 	same_va_bits = VA_BITS;
 | |
| #elif defined(CONFIG_X86_64)
 | |
| 	same_va_bits = 47;
 | |
| #elif defined(CONFIG_64BIT)
 | |
| #error Unsupported 64-bit architecture
 | |
| #endif
 | |
| 
 | |
| #ifdef CONFIG_64BIT
 | |
| 	if (kbase_ctx_flag(kctx, KCTX_COMPAT))
 | |
| 		same_va_bits = 32;
 | |
| 	else if (kbase_hw_has_feature(kctx->kbdev, BASE_HW_FEATURE_33BIT_VA))
 | |
| 		same_va_bits = 33;
 | |
| #endif
 | |
| 
 | |
| 	if (kctx->kbdev->gpu_props.mmu.va_bits < same_va_bits) {
 | |
| 		err = -EINVAL;
 | |
| 		goto fail_unlock;
 | |
| 	}
 | |
| 
 | |
| 	same_va_pages = (1ULL << (same_va_bits - PAGE_SHIFT)) - 1;
 | |
| 	/* all have SAME_VA */
 | |
| 	same_va_reg = kbase_alloc_free_region(kctx, 1,
 | |
| 			same_va_pages,
 | |
| 			KBASE_REG_ZONE_SAME_VA);
 | |
| 
 | |
| 	if (!same_va_reg) {
 | |
| 		err = -ENOMEM;
 | |
| 		goto fail_unlock;
 | |
| 	}
 | |
| 
 | |
| #ifdef CONFIG_64BIT
 | |
| 	/* 32-bit clients have exec and custom VA zones */
 | |
| 	if (kbase_ctx_flag(kctx, KCTX_COMPAT)) {
 | |
| #endif
 | |
| 		if (gpu_va_limit <= KBASE_REG_ZONE_CUSTOM_VA_BASE) {
 | |
| 			err = -EINVAL;
 | |
| 			goto fail_free_same_va;
 | |
| 		}
 | |
| 		/* If the current size of TMEM is out of range of the
 | |
| 		 * virtual address space addressable by the MMU then
 | |
| 		 * we should shrink it to fit
 | |
| 		 */
 | |
| 		if ((KBASE_REG_ZONE_CUSTOM_VA_BASE + KBASE_REG_ZONE_CUSTOM_VA_SIZE) >= gpu_va_limit)
 | |
| 			custom_va_size = gpu_va_limit - KBASE_REG_ZONE_CUSTOM_VA_BASE;
 | |
| 
 | |
| 		exec_reg = kbase_alloc_free_region(kctx,
 | |
| 				KBASE_REG_ZONE_EXEC_BASE,
 | |
| 				KBASE_REG_ZONE_EXEC_SIZE,
 | |
| 				KBASE_REG_ZONE_EXEC);
 | |
| 
 | |
| 		if (!exec_reg) {
 | |
| 			err = -ENOMEM;
 | |
| 			goto fail_free_same_va;
 | |
| 		}
 | |
| 
 | |
| 		custom_va_reg = kbase_alloc_free_region(kctx,
 | |
| 				KBASE_REG_ZONE_CUSTOM_VA_BASE,
 | |
| 				custom_va_size, KBASE_REG_ZONE_CUSTOM_VA);
 | |
| 
 | |
| 		if (!custom_va_reg) {
 | |
| 			err = -ENOMEM;
 | |
| 			goto fail_free_exec;
 | |
| 		}
 | |
| #ifdef CONFIG_64BIT
 | |
| 	}
 | |
| #endif
 | |
| 
 | |
| 	kbase_region_tracker_ds_init(kctx, same_va_reg, exec_reg, custom_va_reg);
 | |
| 
 | |
| 	kctx->same_va_end = same_va_pages + 1;
 | |
| 
 | |
| 	kbase_gpu_vm_unlock(kctx);
 | |
| 	return 0;
 | |
| 
 | |
| fail_free_exec:
 | |
| 	kbase_free_alloced_region(exec_reg);
 | |
| fail_free_same_va:
 | |
| 	kbase_free_alloced_region(same_va_reg);
 | |
| fail_unlock:
 | |
| 	kbase_gpu_vm_unlock(kctx);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| int kbase_region_tracker_init_jit(struct kbase_context *kctx, u64 jit_va_pages)
 | |
| {
 | |
| #ifdef CONFIG_64BIT
 | |
| 	struct kbase_va_region *same_va;
 | |
| 	struct kbase_va_region *custom_va_reg;
 | |
| 	u64 same_va_bits;
 | |
| 	u64 total_va_size;
 | |
| 	int err;
 | |
| 
 | |
| 	/*
 | |
| 	 * Nothing to do for 32-bit clients, JIT uses the existing
 | |
| 	 * custom VA zone.
 | |
| 	 */
 | |
| 	if (kbase_ctx_flag(kctx, KCTX_COMPAT))
 | |
| 		return 0;
 | |
| 
 | |
| #if defined(CONFIG_ARM64)
 | |
| 	same_va_bits = VA_BITS;
 | |
| #elif defined(CONFIG_X86_64)
 | |
| 	same_va_bits = 47;
 | |
| #elif defined(CONFIG_64BIT)
 | |
| #error Unsupported 64-bit architecture
 | |
| #endif
 | |
| 
 | |
| 	if (kbase_hw_has_feature(kctx->kbdev, BASE_HW_FEATURE_33BIT_VA))
 | |
| 		same_va_bits = 33;
 | |
| 
 | |
| 	total_va_size = (1ULL << (same_va_bits - PAGE_SHIFT)) - 1;
 | |
| 
 | |
| 	kbase_gpu_vm_lock(kctx);
 | |
| 
 | |
| 	/*
 | |
| 	 * Modify the same VA free region after creation. Be careful to ensure
 | |
| 	 * that allocations haven't been made as they could cause an overlap
 | |
| 	 * to happen with existing same VA allocations and the custom VA zone.
 | |
| 	 */
 | |
| 	same_va = kbase_region_tracker_find_region_base_address(kctx,
 | |
| 			PAGE_SIZE);
 | |
| 	if (!same_va) {
 | |
| 		err = -ENOMEM;
 | |
| 		goto fail_unlock;
 | |
| 	}
 | |
| 
 | |
| 	/* The region flag or region size has changed since creation so bail. */
 | |
| 	if ((!(same_va->flags & KBASE_REG_FREE)) ||
 | |
| 			(same_va->nr_pages != total_va_size)) {
 | |
| 		err = -ENOMEM;
 | |
| 		goto fail_unlock;
 | |
| 	}
 | |
| 
 | |
| 	if (same_va->nr_pages < jit_va_pages ||
 | |
| 			kctx->same_va_end < jit_va_pages) {
 | |
| 		err = -ENOMEM;
 | |
| 		goto fail_unlock;
 | |
| 	}
 | |
| 
 | |
| 	/* It's safe to adjust the same VA zone now */
 | |
| 	same_va->nr_pages -= jit_va_pages;
 | |
| 	kctx->same_va_end -= jit_va_pages;
 | |
| 
 | |
| 	/*
 | |
| 	 * Create a custom VA zone at the end of the VA for allocations which
 | |
| 	 * JIT can use so it doesn't have to allocate VA from the kernel.
 | |
| 	 */
 | |
| 	custom_va_reg = kbase_alloc_free_region(kctx,
 | |
| 				kctx->same_va_end,
 | |
| 				jit_va_pages,
 | |
| 				KBASE_REG_ZONE_CUSTOM_VA);
 | |
| 
 | |
| 	if (!custom_va_reg) {
 | |
| 		/*
 | |
| 		 * The context will be destroyed if we fail here so no point
 | |
| 		 * reverting the change we made to same_va.
 | |
| 		 */
 | |
| 		err = -ENOMEM;
 | |
| 		goto fail_unlock;
 | |
| 	}
 | |
| 
 | |
| 	kbase_region_tracker_insert(kctx, custom_va_reg);
 | |
| 
 | |
| 	kbase_gpu_vm_unlock(kctx);
 | |
| 	return 0;
 | |
| 
 | |
| fail_unlock:
 | |
| 	kbase_gpu_vm_unlock(kctx);
 | |
| 	return err;
 | |
| #else
 | |
| 	return 0;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| int kbase_mem_init(struct kbase_device *kbdev)
 | |
| {
 | |
| 	struct kbasep_mem_device *memdev;
 | |
| 
 | |
| 	KBASE_DEBUG_ASSERT(kbdev);
 | |
| 
 | |
| 	memdev = &kbdev->memdev;
 | |
| 	kbdev->mem_pool_max_size_default = KBASE_MEM_POOL_MAX_SIZE_KCTX;
 | |
| 
 | |
| 	/* Initialize memory usage */
 | |
| 	atomic_set(&memdev->used_pages, 0);
 | |
| 
 | |
| 	return kbase_mem_pool_init(&kbdev->mem_pool,
 | |
| 			KBASE_MEM_POOL_MAX_SIZE_KBDEV, kbdev, NULL);
 | |
| }
 | |
| 
 | |
| void kbase_mem_halt(struct kbase_device *kbdev)
 | |
| {
 | |
| 	CSTD_UNUSED(kbdev);
 | |
| }
 | |
| 
 | |
| void kbase_mem_term(struct kbase_device *kbdev)
 | |
| {
 | |
| 	struct kbasep_mem_device *memdev;
 | |
| 	int pages;
 | |
| 
 | |
| 	KBASE_DEBUG_ASSERT(kbdev);
 | |
| 
 | |
| 	memdev = &kbdev->memdev;
 | |
| 
 | |
| 	pages = atomic_read(&memdev->used_pages);
 | |
| 	if (pages != 0)
 | |
| 		dev_warn(kbdev->dev, "%s: %d pages in use!\n", __func__, pages);
 | |
| 
 | |
| 	kbase_mem_pool_term(&kbdev->mem_pool);
 | |
| }
 | |
| 
 | |
| KBASE_EXPORT_TEST_API(kbase_mem_term);
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * @brief Allocate a free region object.
 | |
|  *
 | |
|  * The allocated object is not part of any list yet, and is flagged as
 | |
|  * KBASE_REG_FREE. No mapping is allocated yet.
 | |
|  *
 | |
|  * zone is KBASE_REG_ZONE_CUSTOM_VA, KBASE_REG_ZONE_SAME_VA, or KBASE_REG_ZONE_EXEC
 | |
|  *
 | |
|  */
 | |
| struct kbase_va_region *kbase_alloc_free_region(struct kbase_context *kctx, u64 start_pfn, size_t nr_pages, int zone)
 | |
| {
 | |
| 	struct kbase_va_region *new_reg;
 | |
| 
 | |
| 	KBASE_DEBUG_ASSERT(kctx != NULL);
 | |
| 
 | |
| 	/* zone argument should only contain zone related region flags */
 | |
| 	KBASE_DEBUG_ASSERT((zone & ~KBASE_REG_ZONE_MASK) == 0);
 | |
| 	KBASE_DEBUG_ASSERT(nr_pages > 0);
 | |
| 	/* 64-bit address range is the max */
 | |
| 	KBASE_DEBUG_ASSERT(start_pfn + nr_pages <= (U64_MAX / PAGE_SIZE));
 | |
| 
 | |
| 	new_reg = kzalloc(sizeof(*new_reg), GFP_KERNEL);
 | |
| 
 | |
| 	if (!new_reg)
 | |
| 		return NULL;
 | |
| 
 | |
| 	new_reg->cpu_alloc = NULL; /* no alloc bound yet */
 | |
| 	new_reg->gpu_alloc = NULL; /* no alloc bound yet */
 | |
| 	new_reg->kctx = kctx;
 | |
| 	new_reg->flags = zone | KBASE_REG_FREE;
 | |
| 
 | |
| 	new_reg->flags |= KBASE_REG_GROWABLE;
 | |
| 
 | |
| 	new_reg->start_pfn = start_pfn;
 | |
| 	new_reg->nr_pages = nr_pages;
 | |
| 
 | |
| 	return new_reg;
 | |
| }
 | |
| 
 | |
| KBASE_EXPORT_TEST_API(kbase_alloc_free_region);
 | |
| 
 | |
| /**
 | |
|  * @brief Free a region object.
 | |
|  *
 | |
|  * The described region must be freed of any mapping.
 | |
|  *
 | |
|  * If the region is not flagged as KBASE_REG_FREE, the region's
 | |
|  * alloc object will be released.
 | |
|  * It is a bug if no alloc object exists for non-free regions.
 | |
|  *
 | |
|  */
 | |
| void kbase_free_alloced_region(struct kbase_va_region *reg)
 | |
| {
 | |
| 	if (!(reg->flags & KBASE_REG_FREE)) {
 | |
| 		/*
 | |
| 		 * The physical allocation should have been removed from the
 | |
| 		 * eviction list before this function is called. However, in the
 | |
| 		 * case of abnormal process termination or the app leaking the
 | |
| 		 * memory kbase_mem_free_region is not called so it can still be
 | |
| 		 * on the list at termination time of the region tracker.
 | |
| 		 */
 | |
| 		if (!list_empty(®->gpu_alloc->evict_node)) {
 | |
| 			/*
 | |
| 			 * Unlink the physical allocation before unmaking it
 | |
| 			 * evictable so that the allocation isn't grown back to
 | |
| 			 * its last backed size as we're going to unmap it
 | |
| 			 * anyway.
 | |
| 			 */
 | |
| 			reg->cpu_alloc->reg = NULL;
 | |
| 			if (reg->cpu_alloc != reg->gpu_alloc)
 | |
| 				reg->gpu_alloc->reg = NULL;
 | |
| 
 | |
| 			/*
 | |
| 			 * If a region has been made evictable then we must
 | |
| 			 * unmake it before trying to free it.
 | |
| 			 * If the memory hasn't been reclaimed it will be
 | |
| 			 * unmapped and freed below, if it has been reclaimed
 | |
| 			 * then the operations below are no-ops.
 | |
| 			 */
 | |
| 			if (reg->flags & KBASE_REG_DONT_NEED) {
 | |
| 				KBASE_DEBUG_ASSERT(reg->cpu_alloc->type ==
 | |
| 						   KBASE_MEM_TYPE_NATIVE);
 | |
| 				kbase_mem_evictable_unmake(reg->gpu_alloc);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		/*
 | |
| 		 * Remove the region from the sticky resource metadata
 | |
| 		 * list should it be there.
 | |
| 		 */
 | |
| 		kbase_sticky_resource_release(reg->kctx, NULL,
 | |
| 				reg->start_pfn << PAGE_SHIFT);
 | |
| 
 | |
| 		kbase_mem_phy_alloc_put(reg->cpu_alloc);
 | |
| 		kbase_mem_phy_alloc_put(reg->gpu_alloc);
 | |
| 		/* To detect use-after-free in debug builds */
 | |
| 		KBASE_DEBUG_CODE(reg->flags |= KBASE_REG_FREE);
 | |
| 	}
 | |
| 	kfree(reg);
 | |
| }
 | |
| 
 | |
| KBASE_EXPORT_TEST_API(kbase_free_alloced_region);
 | |
| 
 | |
| int kbase_gpu_mmap(struct kbase_context *kctx, struct kbase_va_region *reg, u64 addr, size_t nr_pages, size_t align)
 | |
| {
 | |
| 	int err;
 | |
| 	size_t i = 0;
 | |
| 	unsigned long attr;
 | |
| 	unsigned long mask = ~KBASE_REG_MEMATTR_MASK;
 | |
| 
 | |
| 	if ((kctx->kbdev->system_coherency == COHERENCY_ACE) &&
 | |
| 		(reg->flags & KBASE_REG_SHARE_BOTH))
 | |
| 		attr = KBASE_REG_MEMATTR_INDEX(AS_MEMATTR_INDEX_OUTER_WA);
 | |
| 	else
 | |
| 		attr = KBASE_REG_MEMATTR_INDEX(AS_MEMATTR_INDEX_WRITE_ALLOC);
 | |
| 
 | |
| 	KBASE_DEBUG_ASSERT(NULL != kctx);
 | |
| 	KBASE_DEBUG_ASSERT(NULL != reg);
 | |
| 
 | |
| 	err = kbase_add_va_region(kctx, reg, addr, nr_pages, align);
 | |
| 	if (err)
 | |
| 		return err;
 | |
| 
 | |
| 	if (reg->gpu_alloc->type == KBASE_MEM_TYPE_ALIAS) {
 | |
| 		u64 stride;
 | |
| 		struct kbase_mem_phy_alloc *alloc;
 | |
| 
 | |
| 		alloc = reg->gpu_alloc;
 | |
| 		stride = alloc->imported.alias.stride;
 | |
| 		KBASE_DEBUG_ASSERT(alloc->imported.alias.aliased);
 | |
| 		for (i = 0; i < alloc->imported.alias.nents; i++) {
 | |
| 			if (alloc->imported.alias.aliased[i].alloc) {
 | |
| 				err = kbase_mmu_insert_pages(kctx,
 | |
| 						reg->start_pfn + (i * stride),
 | |
| 						alloc->imported.alias.aliased[i].alloc->pages + alloc->imported.alias.aliased[i].offset,
 | |
| 						alloc->imported.alias.aliased[i].length,
 | |
| 						reg->flags);
 | |
| 				if (err)
 | |
| 					goto bad_insert;
 | |
| 
 | |
| 				kbase_mem_phy_alloc_gpu_mapped(alloc->imported.alias.aliased[i].alloc);
 | |
| 			} else {
 | |
| 				err = kbase_mmu_insert_single_page(kctx,
 | |
| 					reg->start_pfn + i * stride,
 | |
| 					page_to_phys(kctx->aliasing_sink_page),
 | |
| 					alloc->imported.alias.aliased[i].length,
 | |
| 					(reg->flags & mask) | attr);
 | |
| 
 | |
| 				if (err)
 | |
| 					goto bad_insert;
 | |
| 			}
 | |
| 		}
 | |
| 	} else {
 | |
| 		err = kbase_mmu_insert_pages(kctx, reg->start_pfn,
 | |
| 				kbase_get_gpu_phy_pages(reg),
 | |
| 				kbase_reg_current_backed_size(reg),
 | |
| 				reg->flags);
 | |
| 		if (err)
 | |
| 			goto bad_insert;
 | |
| 		kbase_mem_phy_alloc_gpu_mapped(reg->gpu_alloc);
 | |
| 	}
 | |
| 
 | |
| 	return err;
 | |
| 
 | |
| bad_insert:
 | |
| 	if (reg->gpu_alloc->type == KBASE_MEM_TYPE_ALIAS) {
 | |
| 		u64 stride;
 | |
| 
 | |
| 		stride = reg->gpu_alloc->imported.alias.stride;
 | |
| 		KBASE_DEBUG_ASSERT(reg->gpu_alloc->imported.alias.aliased);
 | |
| 		while (i--)
 | |
| 			if (reg->gpu_alloc->imported.alias.aliased[i].alloc) {
 | |
| 				kbase_mmu_teardown_pages(kctx, reg->start_pfn + (i * stride), reg->gpu_alloc->imported.alias.aliased[i].length);
 | |
| 				kbase_mem_phy_alloc_gpu_unmapped(reg->gpu_alloc->imported.alias.aliased[i].alloc);
 | |
| 			}
 | |
| 	}
 | |
| 
 | |
| 	kbase_remove_va_region(kctx, reg);
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| KBASE_EXPORT_TEST_API(kbase_gpu_mmap);
 | |
| 
 | |
| static void kbase_jd_user_buf_unmap(struct kbase_context *kctx,
 | |
| 		struct kbase_mem_phy_alloc *alloc, bool writeable);
 | |
| 
 | |
| int kbase_gpu_munmap(struct kbase_context *kctx, struct kbase_va_region *reg)
 | |
| {
 | |
| 	int err;
 | |
| 
 | |
| 	if (reg->start_pfn == 0)
 | |
| 		return 0;
 | |
| 
 | |
| 	if (reg->gpu_alloc && reg->gpu_alloc->type == KBASE_MEM_TYPE_ALIAS) {
 | |
| 		size_t i;
 | |
| 
 | |
| 		err = kbase_mmu_teardown_pages(kctx, reg->start_pfn, reg->nr_pages);
 | |
| 		KBASE_DEBUG_ASSERT(reg->gpu_alloc->imported.alias.aliased);
 | |
| 		for (i = 0; i < reg->gpu_alloc->imported.alias.nents; i++)
 | |
| 			if (reg->gpu_alloc->imported.alias.aliased[i].alloc)
 | |
| 				kbase_mem_phy_alloc_gpu_unmapped(reg->gpu_alloc->imported.alias.aliased[i].alloc);
 | |
| 	} else {
 | |
| 		err = kbase_mmu_teardown_pages(kctx, reg->start_pfn, kbase_reg_current_backed_size(reg));
 | |
| 		kbase_mem_phy_alloc_gpu_unmapped(reg->gpu_alloc);
 | |
| 	}
 | |
| 
 | |
| 	if (reg->gpu_alloc && reg->gpu_alloc->type ==
 | |
| 			KBASE_MEM_TYPE_IMPORTED_USER_BUF) {
 | |
| 		struct kbase_alloc_import_user_buf *user_buf =
 | |
| 			®->gpu_alloc->imported.user_buf;
 | |
| 
 | |
| 		if (user_buf->current_mapping_usage_count & PINNED_ON_IMPORT) {
 | |
| 			user_buf->current_mapping_usage_count &=
 | |
| 				~PINNED_ON_IMPORT;
 | |
| 
 | |
| 			kbase_jd_user_buf_unmap(kctx, reg->gpu_alloc,
 | |
| 					(reg->flags & KBASE_REG_GPU_WR));
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (err)
 | |
| 		return err;
 | |
| 
 | |
| 	err = kbase_remove_va_region(kctx, reg);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static struct kbase_cpu_mapping *kbasep_find_enclosing_cpu_mapping(
 | |
| 		struct kbase_context *kctx,
 | |
| 		unsigned long uaddr, size_t size, u64 *offset)
 | |
| {
 | |
| 	struct vm_area_struct *vma;
 | |
| 	struct kbase_cpu_mapping *map;
 | |
| 	unsigned long vm_pgoff_in_region;
 | |
| 	unsigned long vm_off_in_region;
 | |
| 	unsigned long map_start;
 | |
| 	size_t map_size;
 | |
| 
 | |
| 	lockdep_assert_held(¤t->mm->mmap_lock);
 | |
| 
 | |
| 	if ((uintptr_t) uaddr + size < (uintptr_t) uaddr) /* overflow check */
 | |
| 		return NULL;
 | |
| 
 | |
| 	vma = find_vma_intersection(current->mm, uaddr, uaddr+size);
 | |
| 
 | |
| 	if (!vma || vma->vm_start > uaddr)
 | |
| 		return NULL;
 | |
| 	if (vma->vm_ops != &kbase_vm_ops)
 | |
| 		/* Not ours! */
 | |
| 		return NULL;
 | |
| 
 | |
| 	map = vma->vm_private_data;
 | |
| 
 | |
| 	if (map->kctx != kctx)
 | |
| 		/* Not from this context! */
 | |
| 		return NULL;
 | |
| 
 | |
| 	vm_pgoff_in_region = vma->vm_pgoff - map->region->start_pfn;
 | |
| 	vm_off_in_region = vm_pgoff_in_region << PAGE_SHIFT;
 | |
| 	map_start = vma->vm_start - vm_off_in_region;
 | |
| 	map_size = map->region->nr_pages << PAGE_SHIFT;
 | |
| 
 | |
| 	if ((uaddr + size) > (map_start + map_size))
 | |
| 		/* Not within the CPU mapping */
 | |
| 		return NULL;
 | |
| 
 | |
| 	*offset = (uaddr - vma->vm_start) + vm_off_in_region;
 | |
| 
 | |
| 	return map;
 | |
| }
 | |
| 
 | |
| int kbasep_find_enclosing_cpu_mapping_offset(
 | |
| 		struct kbase_context *kctx,
 | |
| 		unsigned long uaddr, size_t size, u64 *offset)
 | |
| {
 | |
| 	struct kbase_cpu_mapping *map;
 | |
| 
 | |
| 	kbase_os_mem_map_lock(kctx);
 | |
| 
 | |
| 	map = kbasep_find_enclosing_cpu_mapping(kctx, uaddr, size, offset);
 | |
| 
 | |
| 	kbase_os_mem_map_unlock(kctx);
 | |
| 
 | |
| 	if (!map)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| KBASE_EXPORT_TEST_API(kbasep_find_enclosing_cpu_mapping_offset);
 | |
| 
 | |
| void kbase_sync_single(struct kbase_context *kctx,
 | |
| 		phys_addr_t cpu_pa, phys_addr_t gpu_pa,
 | |
| 		off_t offset, size_t size, enum kbase_sync_type sync_fn)
 | |
| {
 | |
| 	struct page *cpu_page;
 | |
| 
 | |
| 	cpu_page = pfn_to_page(PFN_DOWN(cpu_pa));
 | |
| 
 | |
| 	if (likely(cpu_pa == gpu_pa)) {
 | |
| 		dma_addr_t dma_addr;
 | |
| 
 | |
| 		BUG_ON(!cpu_page);
 | |
| 		BUG_ON(offset + size > PAGE_SIZE);
 | |
| 
 | |
| 		dma_addr = kbase_dma_addr(cpu_page) + offset;
 | |
| 		if (sync_fn == KBASE_SYNC_TO_CPU)
 | |
| 			dma_sync_single_for_cpu(kctx->kbdev->dev, dma_addr,
 | |
| 					size, DMA_BIDIRECTIONAL);
 | |
| 		else if (sync_fn == KBASE_SYNC_TO_DEVICE)
 | |
| 			dma_sync_single_for_device(kctx->kbdev->dev, dma_addr,
 | |
| 					size, DMA_BIDIRECTIONAL);
 | |
| 	} else {
 | |
| 		void *src = NULL;
 | |
| 		void *dst = NULL;
 | |
| 		struct page *gpu_page;
 | |
| 
 | |
| 		if (WARN(!gpu_pa, "No GPU PA found for infinite cache op"))
 | |
| 			return;
 | |
| 
 | |
| 		gpu_page = pfn_to_page(PFN_DOWN(gpu_pa));
 | |
| 
 | |
| 		if (sync_fn == KBASE_SYNC_TO_DEVICE) {
 | |
| 			src = ((unsigned char *)kmap(cpu_page)) + offset;
 | |
| 			dst = ((unsigned char *)kmap(gpu_page)) + offset;
 | |
| 		} else if (sync_fn == KBASE_SYNC_TO_CPU) {
 | |
| 			dma_sync_single_for_cpu(kctx->kbdev->dev,
 | |
| 					kbase_dma_addr(gpu_page) + offset,
 | |
| 					size, DMA_BIDIRECTIONAL);
 | |
| 			src = ((unsigned char *)kmap(gpu_page)) + offset;
 | |
| 			dst = ((unsigned char *)kmap(cpu_page)) + offset;
 | |
| 		}
 | |
| 		memcpy(dst, src, size);
 | |
| 		kunmap(gpu_page);
 | |
| 		kunmap(cpu_page);
 | |
| 		if (sync_fn == KBASE_SYNC_TO_DEVICE)
 | |
| 			dma_sync_single_for_device(kctx->kbdev->dev,
 | |
| 					kbase_dma_addr(gpu_page) + offset,
 | |
| 					size, DMA_BIDIRECTIONAL);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int kbase_do_syncset(struct kbase_context *kctx,
 | |
| 		struct basep_syncset *sset, enum kbase_sync_type sync_fn)
 | |
| {
 | |
| 	int err = 0;
 | |
| 	struct kbase_va_region *reg;
 | |
| 	struct kbase_cpu_mapping *map;
 | |
| 	unsigned long start;
 | |
| 	size_t size;
 | |
| 	phys_addr_t *cpu_pa;
 | |
| 	phys_addr_t *gpu_pa;
 | |
| 	u64 page_off, page_count;
 | |
| 	u64 i;
 | |
| 	u64 offset;
 | |
| 
 | |
| 	kbase_os_mem_map_lock(kctx);
 | |
| 	kbase_gpu_vm_lock(kctx);
 | |
| 
 | |
| 	/* find the region where the virtual address is contained */
 | |
| 	reg = kbase_region_tracker_find_region_enclosing_address(kctx,
 | |
| 			sset->mem_handle.basep.handle);
 | |
| 	if (!reg) {
 | |
| 		dev_warn(kctx->kbdev->dev, "Can't find region at VA 0x%016llX",
 | |
| 				sset->mem_handle.basep.handle);
 | |
| 		err = -EINVAL;
 | |
| 		goto out_unlock;
 | |
| 	}
 | |
| 
 | |
| 	if (!(reg->flags & KBASE_REG_CPU_CACHED))
 | |
| 		goto out_unlock;
 | |
| 
 | |
| 	start = (uintptr_t)sset->user_addr;
 | |
| 	size = (size_t)sset->size;
 | |
| 
 | |
| 	map = kbasep_find_enclosing_cpu_mapping(kctx, start, size, &offset);
 | |
| 	if (!map) {
 | |
| 		dev_warn(kctx->kbdev->dev, "Can't find CPU mapping 0x%016lX for VA 0x%016llX",
 | |
| 				start, sset->mem_handle.basep.handle);
 | |
| 		err = -EINVAL;
 | |
| 		goto out_unlock;
 | |
| 	}
 | |
| 
 | |
| 	page_off = offset >> PAGE_SHIFT;
 | |
| 	offset &= ~PAGE_MASK;
 | |
| 	page_count = (size + offset + (PAGE_SIZE - 1)) >> PAGE_SHIFT;
 | |
| 	cpu_pa = kbase_get_cpu_phy_pages(reg);
 | |
| 	gpu_pa = kbase_get_gpu_phy_pages(reg);
 | |
| 
 | |
| 	if (page_off > reg->nr_pages ||
 | |
| 			page_off + page_count > reg->nr_pages) {
 | |
| 		/* Sync overflows the region */
 | |
| 		err = -EINVAL;
 | |
| 		goto out_unlock;
 | |
| 	}
 | |
| 
 | |
| 	/* Sync first page */
 | |
| 	if (cpu_pa[page_off]) {
 | |
| 		size_t sz = MIN(((size_t) PAGE_SIZE - offset), size);
 | |
| 
 | |
| 		kbase_sync_single(kctx, cpu_pa[page_off], gpu_pa[page_off],
 | |
| 				offset, sz, sync_fn);
 | |
| 	}
 | |
| 
 | |
| 	/* Sync middle pages (if any) */
 | |
| 	for (i = 1; page_count > 2 && i < page_count - 1; i++) {
 | |
| 		/* we grow upwards, so bail on first non-present page */
 | |
| 		if (!cpu_pa[page_off + i])
 | |
| 			break;
 | |
| 
 | |
| 		kbase_sync_single(kctx, cpu_pa[page_off + i],
 | |
| 				gpu_pa[page_off + i], 0, PAGE_SIZE, sync_fn);
 | |
| 	}
 | |
| 
 | |
| 	/* Sync last page (if any) */
 | |
| 	if (page_count > 1 && cpu_pa[page_off + page_count - 1]) {
 | |
| 		size_t sz = ((start + size - 1) & ~PAGE_MASK) + 1;
 | |
| 
 | |
| 		kbase_sync_single(kctx, cpu_pa[page_off + page_count - 1],
 | |
| 				gpu_pa[page_off + page_count - 1], 0, sz,
 | |
| 				sync_fn);
 | |
| 	}
 | |
| 
 | |
| out_unlock:
 | |
| 	kbase_gpu_vm_unlock(kctx);
 | |
| 	kbase_os_mem_map_unlock(kctx);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| int kbase_sync_now(struct kbase_context *kctx, struct basep_syncset *sset)
 | |
| {
 | |
| 	int err = -EINVAL;
 | |
| 
 | |
| 	KBASE_DEBUG_ASSERT(kctx != NULL);
 | |
| 	KBASE_DEBUG_ASSERT(sset != NULL);
 | |
| 
 | |
| 	if (sset->mem_handle.basep.handle & ~PAGE_MASK) {
 | |
| 		dev_warn(kctx->kbdev->dev,
 | |
| 				"mem_handle: passed parameter is invalid");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	switch (sset->type) {
 | |
| 	case BASE_SYNCSET_OP_MSYNC:
 | |
| 		err = kbase_do_syncset(kctx, sset, KBASE_SYNC_TO_DEVICE);
 | |
| 		break;
 | |
| 
 | |
| 	case BASE_SYNCSET_OP_CSYNC:
 | |
| 		err = kbase_do_syncset(kctx, sset, KBASE_SYNC_TO_CPU);
 | |
| 		break;
 | |
| 
 | |
| 	default:
 | |
| 		dev_warn(kctx->kbdev->dev, "Unknown msync op %d\n", sset->type);
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| KBASE_EXPORT_TEST_API(kbase_sync_now);
 | |
| 
 | |
| /* vm lock must be held */
 | |
| int kbase_mem_free_region(struct kbase_context *kctx, struct kbase_va_region *reg)
 | |
| {
 | |
| 	int err;
 | |
| 
 | |
| 	KBASE_DEBUG_ASSERT(NULL != kctx);
 | |
| 	KBASE_DEBUG_ASSERT(NULL != reg);
 | |
| 	lockdep_assert_held(&kctx->reg_lock);
 | |
| 
 | |
| 	/*
 | |
| 	 * Unlink the physical allocation before unmaking it evictable so
 | |
| 	 * that the allocation isn't grown back to its last backed size
 | |
| 	 * as we're going to unmap it anyway.
 | |
| 	 */
 | |
| 	reg->cpu_alloc->reg = NULL;
 | |
| 	if (reg->cpu_alloc != reg->gpu_alloc)
 | |
| 		reg->gpu_alloc->reg = NULL;
 | |
| 
 | |
| 	/*
 | |
| 	 * If a region has been made evictable then we must unmake it
 | |
| 	 * before trying to free it.
 | |
| 	 * If the memory hasn't been reclaimed it will be unmapped and freed
 | |
| 	 * below, if it has been reclaimed then the operations below are no-ops.
 | |
| 	 */
 | |
| 	if (reg->flags & KBASE_REG_DONT_NEED) {
 | |
| 		KBASE_DEBUG_ASSERT(reg->cpu_alloc->type ==
 | |
| 				   KBASE_MEM_TYPE_NATIVE);
 | |
| 		kbase_mem_evictable_unmake(reg->gpu_alloc);
 | |
| 	}
 | |
| 
 | |
| 	err = kbase_gpu_munmap(kctx, reg);
 | |
| 	if (err) {
 | |
| 		dev_warn(reg->kctx->kbdev->dev, "Could not unmap from the GPU...\n");
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	/* This will also free the physical pages */
 | |
| 	kbase_free_alloced_region(reg);
 | |
| 
 | |
|  out:
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| KBASE_EXPORT_TEST_API(kbase_mem_free_region);
 | |
| 
 | |
| /**
 | |
|  * @brief Free the region from the GPU and unregister it.
 | |
|  *
 | |
|  * This function implements the free operation on a memory segment.
 | |
|  * It will loudly fail if called with outstanding mappings.
 | |
|  */
 | |
| int kbase_mem_free(struct kbase_context *kctx, u64 gpu_addr)
 | |
| {
 | |
| 	int err = 0;
 | |
| 	struct kbase_va_region *reg;
 | |
| 
 | |
| 	KBASE_DEBUG_ASSERT(kctx != NULL);
 | |
| 
 | |
| 	if ((gpu_addr & ~PAGE_MASK) && (gpu_addr >= PAGE_SIZE)) {
 | |
| 		dev_warn(kctx->kbdev->dev, "kbase_mem_free: gpu_addr parameter is invalid");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	if (0 == gpu_addr) {
 | |
| 		dev_warn(kctx->kbdev->dev, "gpu_addr 0 is reserved for the ringbuffer and it's an error to try to free it using kbase_mem_free\n");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 	kbase_gpu_vm_lock(kctx);
 | |
| 
 | |
| 	if (gpu_addr >= BASE_MEM_COOKIE_BASE &&
 | |
| 	    gpu_addr < BASE_MEM_FIRST_FREE_ADDRESS) {
 | |
| 		int cookie = PFN_DOWN(gpu_addr - BASE_MEM_COOKIE_BASE);
 | |
| 
 | |
| 		reg = kctx->pending_regions[cookie];
 | |
| 		if (!reg) {
 | |
| 			err = -EINVAL;
 | |
| 			goto out_unlock;
 | |
| 		}
 | |
| 
 | |
| 		/* ask to unlink the cookie as we'll free it */
 | |
| 
 | |
| 		kctx->pending_regions[cookie] = NULL;
 | |
| 		kctx->cookies |= (1UL << cookie);
 | |
| 
 | |
| 		kbase_free_alloced_region(reg);
 | |
| 	} else {
 | |
| 		/* A real GPU va */
 | |
| 		/* Validate the region */
 | |
| 		reg = kbase_region_tracker_find_region_base_address(kctx, gpu_addr);
 | |
| 		if (!reg || (reg->flags & KBASE_REG_FREE)) {
 | |
| 			dev_warn(kctx->kbdev->dev, "kbase_mem_free called with nonexistent gpu_addr 0x%llX",
 | |
| 					gpu_addr);
 | |
| 			err = -EINVAL;
 | |
| 			goto out_unlock;
 | |
| 		}
 | |
| 
 | |
| 		if ((reg->flags & KBASE_REG_ZONE_MASK) == KBASE_REG_ZONE_SAME_VA) {
 | |
| 			/* SAME_VA must be freed through munmap */
 | |
| 			dev_warn(kctx->kbdev->dev, "%s called on SAME_VA memory 0x%llX", __func__,
 | |
| 					gpu_addr);
 | |
| 			err = -EINVAL;
 | |
| 			goto out_unlock;
 | |
| 		}
 | |
| 		err = kbase_mem_free_region(kctx, reg);
 | |
| 	}
 | |
| 
 | |
|  out_unlock:
 | |
| 	kbase_gpu_vm_unlock(kctx);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| KBASE_EXPORT_TEST_API(kbase_mem_free);
 | |
| 
 | |
| int kbase_update_region_flags(struct kbase_context *kctx,
 | |
| 		struct kbase_va_region *reg, unsigned long flags)
 | |
| {
 | |
| 	KBASE_DEBUG_ASSERT(NULL != reg);
 | |
| 	KBASE_DEBUG_ASSERT((flags & ~((1ul << BASE_MEM_FLAGS_NR_BITS) - 1)) == 0);
 | |
| 
 | |
| 	reg->flags |= kbase_cache_enabled(flags, reg->nr_pages);
 | |
| 	/* all memory is now growable */
 | |
| 	reg->flags |= KBASE_REG_GROWABLE;
 | |
| 
 | |
| 	if (flags & BASE_MEM_GROW_ON_GPF)
 | |
| 		reg->flags |= KBASE_REG_PF_GROW;
 | |
| 
 | |
| 	if (flags & BASE_MEM_PROT_CPU_WR)
 | |
| 		reg->flags |= KBASE_REG_CPU_WR;
 | |
| 
 | |
| 	if (flags & BASE_MEM_PROT_CPU_RD)
 | |
| 		reg->flags |= KBASE_REG_CPU_RD;
 | |
| 
 | |
| 	if (flags & BASE_MEM_PROT_GPU_WR)
 | |
| 		reg->flags |= KBASE_REG_GPU_WR;
 | |
| 
 | |
| 	if (flags & BASE_MEM_PROT_GPU_RD)
 | |
| 		reg->flags |= KBASE_REG_GPU_RD;
 | |
| 
 | |
| 	if (0 == (flags & BASE_MEM_PROT_GPU_EX))
 | |
| 		reg->flags |= KBASE_REG_GPU_NX;
 | |
| 
 | |
| 	if (!kbase_device_is_cpu_coherent(kctx->kbdev)) {
 | |
| 		if (flags & BASE_MEM_COHERENT_SYSTEM_REQUIRED)
 | |
| 			return -EINVAL;
 | |
| 	} else if (flags & (BASE_MEM_COHERENT_SYSTEM |
 | |
| 			BASE_MEM_COHERENT_SYSTEM_REQUIRED)) {
 | |
| 		reg->flags |= KBASE_REG_SHARE_BOTH;
 | |
| 	}
 | |
| 
 | |
| 	if (!(reg->flags & KBASE_REG_SHARE_BOTH) &&
 | |
| 			flags & BASE_MEM_COHERENT_LOCAL) {
 | |
| 		reg->flags |= KBASE_REG_SHARE_IN;
 | |
| 	}
 | |
| 
 | |
| 	/* Set up default MEMATTR usage */
 | |
| 	if (kctx->kbdev->system_coherency == COHERENCY_ACE &&
 | |
| 		(reg->flags & KBASE_REG_SHARE_BOTH)) {
 | |
| 		reg->flags |=
 | |
| 			KBASE_REG_MEMATTR_INDEX(AS_MEMATTR_INDEX_DEFAULT_ACE);
 | |
| 	} else {
 | |
| 		reg->flags |=
 | |
| 			KBASE_REG_MEMATTR_INDEX(AS_MEMATTR_INDEX_DEFAULT);
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int kbase_alloc_phy_pages_helper(
 | |
| 	struct kbase_mem_phy_alloc *alloc,
 | |
| 	size_t nr_pages_requested)
 | |
| {
 | |
| 	int new_page_count __maybe_unused;
 | |
| 	size_t old_page_count = alloc->nents;
 | |
| 
 | |
| 	KBASE_DEBUG_ASSERT(alloc->type == KBASE_MEM_TYPE_NATIVE);
 | |
| 	KBASE_DEBUG_ASSERT(alloc->imported.kctx);
 | |
| 
 | |
| 	if (nr_pages_requested == 0)
 | |
| 		goto done; /*nothing to do*/
 | |
| 
 | |
| 	new_page_count = kbase_atomic_add_pages(
 | |
| 			nr_pages_requested, &alloc->imported.kctx->used_pages);
 | |
| 	kbase_atomic_add_pages(nr_pages_requested, &alloc->imported.kctx->kbdev->memdev.used_pages);
 | |
| 
 | |
| 	/* Increase mm counters before we allocate pages so that this
 | |
| 	 * allocation is visible to the OOM killer */
 | |
| 	kbase_process_page_usage_inc(alloc->imported.kctx, nr_pages_requested);
 | |
| 
 | |
| 	if (kbase_mem_pool_alloc_pages(&alloc->imported.kctx->mem_pool,
 | |
| 			nr_pages_requested, alloc->pages + old_page_count) != 0)
 | |
| 		goto no_alloc;
 | |
| 
 | |
| 	KBASE_TLSTREAM_AUX_PAGESALLOC(
 | |
| 			(u32)alloc->imported.kctx->id,
 | |
| 			(u64)new_page_count);
 | |
| 
 | |
| 	alloc->nents += nr_pages_requested;
 | |
| done:
 | |
| 	return 0;
 | |
| 
 | |
| no_alloc:
 | |
| 	kbase_process_page_usage_dec(alloc->imported.kctx, nr_pages_requested);
 | |
| 	kbase_atomic_sub_pages(nr_pages_requested, &alloc->imported.kctx->used_pages);
 | |
| 	kbase_atomic_sub_pages(nr_pages_requested, &alloc->imported.kctx->kbdev->memdev.used_pages);
 | |
| 
 | |
| 	return -ENOMEM;
 | |
| }
 | |
| 
 | |
| int kbase_free_phy_pages_helper(
 | |
| 	struct kbase_mem_phy_alloc *alloc,
 | |
| 	size_t nr_pages_to_free)
 | |
| {
 | |
| 	struct kbase_context *kctx = alloc->imported.kctx;
 | |
| 	bool syncback;
 | |
| 	bool reclaimed = (alloc->evicted != 0);
 | |
| 	phys_addr_t *start_free;
 | |
| 	int new_page_count __maybe_unused;
 | |
| 
 | |
| 	KBASE_DEBUG_ASSERT(alloc->type == KBASE_MEM_TYPE_NATIVE);
 | |
| 	KBASE_DEBUG_ASSERT(alloc->imported.kctx);
 | |
| 	KBASE_DEBUG_ASSERT(alloc->nents >= nr_pages_to_free);
 | |
| 
 | |
| 	/* early out if nothing to do */
 | |
| 	if (0 == nr_pages_to_free)
 | |
| 		return 0;
 | |
| 
 | |
| 	start_free = alloc->pages + alloc->nents - nr_pages_to_free;
 | |
| 
 | |
| 	syncback = alloc->properties & KBASE_MEM_PHY_ALLOC_ACCESSED_CACHED;
 | |
| 
 | |
| 	kbase_mem_pool_free_pages(&kctx->mem_pool,
 | |
| 				  nr_pages_to_free,
 | |
| 				  start_free,
 | |
| 				  syncback,
 | |
| 				  reclaimed);
 | |
| 
 | |
| 	alloc->nents -= nr_pages_to_free;
 | |
| 
 | |
| 	/*
 | |
| 	 * If the allocation was not evicted (i.e. evicted == 0) then
 | |
| 	 * the page accounting needs to be done.
 | |
| 	 */
 | |
| 	if (!reclaimed) {
 | |
| 		kbase_process_page_usage_dec(kctx, nr_pages_to_free);
 | |
| 		new_page_count = kbase_atomic_sub_pages(nr_pages_to_free,
 | |
| 							&kctx->used_pages);
 | |
| 		kbase_atomic_sub_pages(nr_pages_to_free,
 | |
| 				       &kctx->kbdev->memdev.used_pages);
 | |
| 
 | |
| 		KBASE_TLSTREAM_AUX_PAGESALLOC(
 | |
| 				(u32)kctx->id,
 | |
| 				(u64)new_page_count);
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void kbase_mem_kref_free(struct kref *kref)
 | |
| {
 | |
| 	struct kbase_mem_phy_alloc *alloc;
 | |
| 
 | |
| 	alloc = container_of(kref, struct kbase_mem_phy_alloc, kref);
 | |
| 
 | |
| 	switch (alloc->type) {
 | |
| 	case KBASE_MEM_TYPE_NATIVE: {
 | |
| 		WARN_ON(!alloc->imported.kctx);
 | |
| 		/*
 | |
| 		 * The physical allocation must have been removed from the
 | |
| 		 * eviction list before trying to free it.
 | |
| 		 */
 | |
| 		WARN_ON(!list_empty(&alloc->evict_node));
 | |
| 		kbase_free_phy_pages_helper(alloc, alloc->nents);
 | |
| 		break;
 | |
| 	}
 | |
| 	case KBASE_MEM_TYPE_ALIAS: {
 | |
| 		/* just call put on the underlying phy allocs */
 | |
| 		size_t i;
 | |
| 		struct kbase_aliased *aliased;
 | |
| 
 | |
| 		aliased = alloc->imported.alias.aliased;
 | |
| 		if (aliased) {
 | |
| 			for (i = 0; i < alloc->imported.alias.nents; i++)
 | |
| 				if (aliased[i].alloc)
 | |
| 					kbase_mem_phy_alloc_put(aliased[i].alloc);
 | |
| 			vfree(aliased);
 | |
| 		}
 | |
| 		break;
 | |
| 	}
 | |
| 	case KBASE_MEM_TYPE_RAW:
 | |
| 		/* raw pages, external cleanup */
 | |
| 		break;
 | |
|  #ifdef CONFIG_UMP
 | |
| 	case KBASE_MEM_TYPE_IMPORTED_UMP:
 | |
| 		ump_dd_release(alloc->imported.ump_handle);
 | |
| 		break;
 | |
| #endif
 | |
| #ifdef CONFIG_DMA_SHARED_BUFFER
 | |
| 	case KBASE_MEM_TYPE_IMPORTED_UMM:
 | |
| 		dma_buf_detach(alloc->imported.umm.dma_buf,
 | |
| 			       alloc->imported.umm.dma_attachment);
 | |
| 		dma_buf_put(alloc->imported.umm.dma_buf);
 | |
| 		break;
 | |
| #endif
 | |
| 	case KBASE_MEM_TYPE_IMPORTED_USER_BUF:
 | |
| 		if (alloc->imported.user_buf.mm)
 | |
| 			mmdrop(alloc->imported.user_buf.mm);
 | |
| 		kfree(alloc->imported.user_buf.pages);
 | |
| 		break;
 | |
| 	case KBASE_MEM_TYPE_TB:{
 | |
| 		void *tb;
 | |
| 
 | |
| 		tb = alloc->imported.kctx->jctx.tb;
 | |
| 		kbase_device_trace_buffer_uninstall(alloc->imported.kctx);
 | |
| 		vfree(tb);
 | |
| 		break;
 | |
| 	}
 | |
| 	default:
 | |
| 		WARN(1, "Unexecpted free of type %d\n", alloc->type);
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	/* Free based on allocation type */
 | |
| 	if (alloc->properties & KBASE_MEM_PHY_ALLOC_LARGE)
 | |
| 		vfree(alloc);
 | |
| 	else
 | |
| 		kfree(alloc);
 | |
| }
 | |
| 
 | |
| KBASE_EXPORT_TEST_API(kbase_mem_kref_free);
 | |
| 
 | |
| int kbase_alloc_phy_pages(struct kbase_va_region *reg, size_t vsize, size_t size)
 | |
| {
 | |
| 	KBASE_DEBUG_ASSERT(NULL != reg);
 | |
| 	KBASE_DEBUG_ASSERT(vsize > 0);
 | |
| 
 | |
| 	/* validate user provided arguments */
 | |
| 	if (size > vsize || vsize > reg->nr_pages)
 | |
| 		goto out_term;
 | |
| 
 | |
| 	/* Prevent vsize*sizeof from wrapping around.
 | |
| 	 * For instance, if vsize is 2**29+1, we'll allocate 1 byte and the alloc won't fail.
 | |
| 	 */
 | |
| 	if ((size_t) vsize > ((size_t) -1 / sizeof(*reg->cpu_alloc->pages)))
 | |
| 		goto out_term;
 | |
| 
 | |
| 	KBASE_DEBUG_ASSERT(0 != vsize);
 | |
| 
 | |
| 	if (kbase_alloc_phy_pages_helper(reg->cpu_alloc, size) != 0)
 | |
| 		goto out_term;
 | |
| 
 | |
| 	reg->cpu_alloc->reg = reg;
 | |
| 	if (reg->cpu_alloc != reg->gpu_alloc) {
 | |
| 		if (kbase_alloc_phy_pages_helper(reg->gpu_alloc, size) != 0)
 | |
| 			goto out_rollback;
 | |
| 		reg->gpu_alloc->reg = reg;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| out_rollback:
 | |
| 	kbase_free_phy_pages_helper(reg->cpu_alloc, size);
 | |
| out_term:
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| KBASE_EXPORT_TEST_API(kbase_alloc_phy_pages);
 | |
| 
 | |
| bool kbase_check_alloc_flags(unsigned long flags)
 | |
| {
 | |
| 	/* Only known input flags should be set. */
 | |
| 	if (flags & ~BASE_MEM_FLAGS_INPUT_MASK)
 | |
| 		return false;
 | |
| 
 | |
| 	/* At least one flag should be set */
 | |
| 	if (flags == 0)
 | |
| 		return false;
 | |
| 
 | |
| 	/* Either the GPU or CPU must be reading from the allocated memory */
 | |
| 	if ((flags & (BASE_MEM_PROT_CPU_RD | BASE_MEM_PROT_GPU_RD)) == 0)
 | |
| 		return false;
 | |
| 
 | |
| 	/* Either the GPU or CPU must be writing to the allocated memory */
 | |
| 	if ((flags & (BASE_MEM_PROT_CPU_WR | BASE_MEM_PROT_GPU_WR)) == 0)
 | |
| 		return false;
 | |
| 
 | |
| 	/* GPU cannot be writing to GPU executable memory and cannot grow the memory on page fault. */
 | |
| 	if ((flags & BASE_MEM_PROT_GPU_EX) && (flags & (BASE_MEM_PROT_GPU_WR | BASE_MEM_GROW_ON_GPF)))
 | |
| 		return false;
 | |
| 
 | |
| 	/* GPU should have at least read or write access otherwise there is no
 | |
| 	   reason for allocating. */
 | |
| 	if ((flags & (BASE_MEM_PROT_GPU_RD | BASE_MEM_PROT_GPU_WR)) == 0)
 | |
| 		return false;
 | |
| 
 | |
| 	/* BASE_MEM_IMPORT_SHARED is only valid for imported memory */
 | |
| 	if ((flags & BASE_MEM_IMPORT_SHARED) == BASE_MEM_IMPORT_SHARED)
 | |
| 		return false;
 | |
| 
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| bool kbase_check_import_flags(unsigned long flags)
 | |
| {
 | |
| 	/* Only known input flags should be set. */
 | |
| 	if (flags & ~BASE_MEM_FLAGS_INPUT_MASK)
 | |
| 		return false;
 | |
| 
 | |
| 	/* At least one flag should be set */
 | |
| 	if (flags == 0)
 | |
| 		return false;
 | |
| 
 | |
| 	/* Imported memory cannot be GPU executable */
 | |
| 	if (flags & BASE_MEM_PROT_GPU_EX)
 | |
| 		return false;
 | |
| 
 | |
| 	/* Imported memory cannot grow on page fault */
 | |
| 	if (flags & BASE_MEM_GROW_ON_GPF)
 | |
| 		return false;
 | |
| 
 | |
| 	/* GPU should have at least read or write access otherwise there is no
 | |
| 	   reason for importing. */
 | |
| 	if ((flags & (BASE_MEM_PROT_GPU_RD | BASE_MEM_PROT_GPU_WR)) == 0)
 | |
| 		return false;
 | |
| 
 | |
| 	/* Secure memory cannot be read by the CPU */
 | |
| 	if ((flags & BASE_MEM_SECURE) && (flags & BASE_MEM_PROT_CPU_RD))
 | |
| 		return false;
 | |
| 
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Acquire the per-context region list lock
 | |
|  */
 | |
| void kbase_gpu_vm_lock(struct kbase_context *kctx)
 | |
| {
 | |
| 	KBASE_DEBUG_ASSERT(kctx != NULL);
 | |
| 	mutex_lock(&kctx->reg_lock);
 | |
| }
 | |
| 
 | |
| KBASE_EXPORT_TEST_API(kbase_gpu_vm_lock);
 | |
| 
 | |
| /**
 | |
|  * @brief Release the per-context region list lock
 | |
|  */
 | |
| void kbase_gpu_vm_unlock(struct kbase_context *kctx)
 | |
| {
 | |
| 	KBASE_DEBUG_ASSERT(kctx != NULL);
 | |
| 	mutex_unlock(&kctx->reg_lock);
 | |
| }
 | |
| 
 | |
| KBASE_EXPORT_TEST_API(kbase_gpu_vm_unlock);
 | |
| 
 | |
| #ifdef CONFIG_DEBUG_FS
 | |
| struct kbase_jit_debugfs_data {
 | |
| 	int (*func)(struct kbase_jit_debugfs_data *);
 | |
| 	struct mutex lock;
 | |
| 	struct kbase_context *kctx;
 | |
| 	u64 active_value;
 | |
| 	u64 pool_value;
 | |
| 	u64 destroy_value;
 | |
| 	char buffer[50];
 | |
| };
 | |
| 
 | |
| static int kbase_jit_debugfs_common_open(struct inode *inode,
 | |
| 		struct file *file, int (*func)(struct kbase_jit_debugfs_data *))
 | |
| {
 | |
| 	struct kbase_jit_debugfs_data *data;
 | |
| 
 | |
| 	data = kzalloc(sizeof(*data), GFP_KERNEL);
 | |
| 	if (!data)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	data->func = func;
 | |
| 	mutex_init(&data->lock);
 | |
| 	data->kctx = (struct kbase_context *) inode->i_private;
 | |
| 
 | |
| 	file->private_data = data;
 | |
| 
 | |
| 	return nonseekable_open(inode, file);
 | |
| }
 | |
| 
 | |
| static ssize_t kbase_jit_debugfs_common_read(struct file *file,
 | |
| 		char __user *buf, size_t len, loff_t *ppos)
 | |
| {
 | |
| 	struct kbase_jit_debugfs_data *data;
 | |
| 	size_t size;
 | |
| 	int ret;
 | |
| 
 | |
| 	data = (struct kbase_jit_debugfs_data *) file->private_data;
 | |
| 	mutex_lock(&data->lock);
 | |
| 
 | |
| 	if (*ppos) {
 | |
| 		size = strnlen(data->buffer, sizeof(data->buffer));
 | |
| 	} else {
 | |
| 		if (!data->func) {
 | |
| 			ret = -EACCES;
 | |
| 			goto out_unlock;
 | |
| 		}
 | |
| 
 | |
| 		if (data->func(data)) {
 | |
| 			ret = -EACCES;
 | |
| 			goto out_unlock;
 | |
| 		}
 | |
| 
 | |
| 		size = scnprintf(data->buffer, sizeof(data->buffer),
 | |
| 				"%llu,%llu,%llu", data->active_value,
 | |
| 				data->pool_value, data->destroy_value);
 | |
| 	}
 | |
| 
 | |
| 	ret = simple_read_from_buffer(buf, len, ppos, data->buffer, size);
 | |
| 
 | |
| out_unlock:
 | |
| 	mutex_unlock(&data->lock);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int kbase_jit_debugfs_common_release(struct inode *inode,
 | |
| 		struct file *file)
 | |
| {
 | |
| 	kfree(file->private_data);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| #define KBASE_JIT_DEBUGFS_DECLARE(__fops, __func) \
 | |
| static int __fops ## _open(struct inode *inode, struct file *file) \
 | |
| { \
 | |
| 	return kbase_jit_debugfs_common_open(inode, file, __func); \
 | |
| } \
 | |
| static const struct file_operations __fops = { \
 | |
| 	.owner = THIS_MODULE, \
 | |
| 	.open = __fops ## _open, \
 | |
| 	.release = kbase_jit_debugfs_common_release, \
 | |
| 	.read = kbase_jit_debugfs_common_read, \
 | |
| 	.write = NULL, \
 | |
| 	.llseek = generic_file_llseek, \
 | |
| }
 | |
| 
 | |
| static int kbase_jit_debugfs_count_get(struct kbase_jit_debugfs_data *data)
 | |
| {
 | |
| 	struct kbase_context *kctx = data->kctx;
 | |
| 	struct list_head *tmp;
 | |
| 
 | |
| 	mutex_lock(&kctx->jit_evict_lock);
 | |
| 	list_for_each(tmp, &kctx->jit_active_head) {
 | |
| 		data->active_value++;
 | |
| 	}
 | |
| 
 | |
| 	list_for_each(tmp, &kctx->jit_pool_head) {
 | |
| 		data->pool_value++;
 | |
| 	}
 | |
| 
 | |
| 	list_for_each(tmp, &kctx->jit_destroy_head) {
 | |
| 		data->destroy_value++;
 | |
| 	}
 | |
| 	mutex_unlock(&kctx->jit_evict_lock);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| KBASE_JIT_DEBUGFS_DECLARE(kbase_jit_debugfs_count_fops,
 | |
| 		kbase_jit_debugfs_count_get);
 | |
| 
 | |
| static int kbase_jit_debugfs_vm_get(struct kbase_jit_debugfs_data *data)
 | |
| {
 | |
| 	struct kbase_context *kctx = data->kctx;
 | |
| 	struct kbase_va_region *reg;
 | |
| 
 | |
| 	mutex_lock(&kctx->jit_evict_lock);
 | |
| 	list_for_each_entry(reg, &kctx->jit_active_head, jit_node) {
 | |
| 		data->active_value += reg->nr_pages;
 | |
| 	}
 | |
| 
 | |
| 	list_for_each_entry(reg, &kctx->jit_pool_head, jit_node) {
 | |
| 		data->pool_value += reg->nr_pages;
 | |
| 	}
 | |
| 
 | |
| 	list_for_each_entry(reg, &kctx->jit_destroy_head, jit_node) {
 | |
| 		data->destroy_value += reg->nr_pages;
 | |
| 	}
 | |
| 	mutex_unlock(&kctx->jit_evict_lock);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| KBASE_JIT_DEBUGFS_DECLARE(kbase_jit_debugfs_vm_fops,
 | |
| 		kbase_jit_debugfs_vm_get);
 | |
| 
 | |
| static int kbase_jit_debugfs_phys_get(struct kbase_jit_debugfs_data *data)
 | |
| {
 | |
| 	struct kbase_context *kctx = data->kctx;
 | |
| 	struct kbase_va_region *reg;
 | |
| 
 | |
| 	mutex_lock(&kctx->jit_evict_lock);
 | |
| 	list_for_each_entry(reg, &kctx->jit_active_head, jit_node) {
 | |
| 		data->active_value += reg->gpu_alloc->nents;
 | |
| 	}
 | |
| 
 | |
| 	list_for_each_entry(reg, &kctx->jit_pool_head, jit_node) {
 | |
| 		data->pool_value += reg->gpu_alloc->nents;
 | |
| 	}
 | |
| 
 | |
| 	list_for_each_entry(reg, &kctx->jit_destroy_head, jit_node) {
 | |
| 		data->destroy_value += reg->gpu_alloc->nents;
 | |
| 	}
 | |
| 	mutex_unlock(&kctx->jit_evict_lock);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| KBASE_JIT_DEBUGFS_DECLARE(kbase_jit_debugfs_phys_fops,
 | |
| 		kbase_jit_debugfs_phys_get);
 | |
| 
 | |
| void kbase_jit_debugfs_init(struct kbase_context *kctx)
 | |
| {
 | |
| 	/* Debugfs entry for getting the number of JIT allocations. */
 | |
| 	debugfs_create_file("mem_jit_count", S_IRUGO, kctx->kctx_dentry,
 | |
| 			kctx, &kbase_jit_debugfs_count_fops);
 | |
| 
 | |
| 	/*
 | |
| 	 * Debugfs entry for getting the total number of virtual pages
 | |
| 	 * used by JIT allocations.
 | |
| 	 */
 | |
| 	debugfs_create_file("mem_jit_vm", S_IRUGO, kctx->kctx_dentry,
 | |
| 			kctx, &kbase_jit_debugfs_vm_fops);
 | |
| 
 | |
| 	/*
 | |
| 	 * Debugfs entry for getting the number of physical pages used
 | |
| 	 * by JIT allocations.
 | |
| 	 */
 | |
| 	debugfs_create_file("mem_jit_phys", S_IRUGO, kctx->kctx_dentry,
 | |
| 			kctx, &kbase_jit_debugfs_phys_fops);
 | |
| }
 | |
| #endif /* CONFIG_DEBUG_FS */
 | |
| 
 | |
| /**
 | |
|  * kbase_jit_destroy_worker - Deferred worker which frees JIT allocations
 | |
|  * @work: Work item
 | |
|  *
 | |
|  * This function does the work of freeing JIT allocations whose physical
 | |
|  * backing has been released.
 | |
|  */
 | |
| static void kbase_jit_destroy_worker(struct work_struct *work)
 | |
| {
 | |
| 	struct kbase_context *kctx;
 | |
| 	struct kbase_va_region *reg;
 | |
| 
 | |
| 	kctx = container_of(work, struct kbase_context, jit_work);
 | |
| 	do {
 | |
| 		mutex_lock(&kctx->jit_evict_lock);
 | |
| 		if (list_empty(&kctx->jit_destroy_head)) {
 | |
| 			mutex_unlock(&kctx->jit_evict_lock);
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		reg = list_first_entry(&kctx->jit_destroy_head,
 | |
| 				struct kbase_va_region, jit_node);
 | |
| 
 | |
| 		list_del(®->jit_node);
 | |
| 		mutex_unlock(&kctx->jit_evict_lock);
 | |
| 
 | |
| 		kbase_gpu_vm_lock(kctx);
 | |
| 		kbase_mem_free_region(kctx, reg);
 | |
| 		kbase_gpu_vm_unlock(kctx);
 | |
| 	} while (1);
 | |
| }
 | |
| 
 | |
| int kbase_jit_init(struct kbase_context *kctx)
 | |
| {
 | |
| 	INIT_LIST_HEAD(&kctx->jit_active_head);
 | |
| 	INIT_LIST_HEAD(&kctx->jit_pool_head);
 | |
| 	INIT_LIST_HEAD(&kctx->jit_destroy_head);
 | |
| 	INIT_WORK(&kctx->jit_work, kbase_jit_destroy_worker);
 | |
| 
 | |
| 	INIT_LIST_HEAD(&kctx->jit_pending_alloc);
 | |
| 	INIT_LIST_HEAD(&kctx->jit_atoms_head);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| struct kbase_va_region *kbase_jit_allocate(struct kbase_context *kctx,
 | |
| 		struct base_jit_alloc_info *info)
 | |
| {
 | |
| 	struct kbase_va_region *reg = NULL;
 | |
| 	struct kbase_va_region *walker;
 | |
| 	struct kbase_va_region *temp;
 | |
| 	size_t current_diff = SIZE_MAX;
 | |
| 
 | |
| 	int ret;
 | |
| 
 | |
| 	mutex_lock(&kctx->jit_evict_lock);
 | |
| 	/*
 | |
| 	 * Scan the pool for an existing allocation which meets our
 | |
| 	 * requirements and remove it.
 | |
| 	 */
 | |
| 	list_for_each_entry_safe(walker, temp, &kctx->jit_pool_head, jit_node) {
 | |
| 
 | |
| 		if (walker->nr_pages >= info->va_pages) {
 | |
| 			size_t min_size, max_size, diff;
 | |
| 
 | |
| 			/*
 | |
| 			 * The JIT allocations VA requirements have been
 | |
| 			 * meet, it's suitable but other allocations
 | |
| 			 * might be a better fit.
 | |
| 			 */
 | |
| 			min_size = min_t(size_t, walker->gpu_alloc->nents,
 | |
| 					info->commit_pages);
 | |
| 			max_size = max_t(size_t, walker->gpu_alloc->nents,
 | |
| 					info->commit_pages);
 | |
| 			diff = max_size - min_size;
 | |
| 
 | |
| 			if (current_diff > diff) {
 | |
| 				current_diff = diff;
 | |
| 				reg = walker;
 | |
| 			}
 | |
| 
 | |
| 			/* The allocation is an exact match, stop looking */
 | |
| 			if (current_diff == 0)
 | |
| 				break;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (reg) {
 | |
| 		/*
 | |
| 		 * Remove the found region from the pool and add it to the
 | |
| 		 * active list.
 | |
| 		 */
 | |
| 		list_move(®->jit_node, &kctx->jit_active_head);
 | |
| 
 | |
| 		/*
 | |
| 		 * Remove the allocation from the eviction list as it's no
 | |
| 		 * longer eligible for eviction. This must be done before
 | |
| 		 * dropping the jit_evict_lock
 | |
| 		 */
 | |
| 		list_del_init(®->gpu_alloc->evict_node);
 | |
| 		mutex_unlock(&kctx->jit_evict_lock);
 | |
| 
 | |
| 		kbase_gpu_vm_lock(kctx);
 | |
| 
 | |
| 		/* Make the physical backing no longer reclaimable */
 | |
| 		if (!kbase_mem_evictable_unmake(reg->gpu_alloc))
 | |
| 			goto update_failed;
 | |
| 
 | |
| 		/* Grow the backing if required */
 | |
| 		if (reg->gpu_alloc->nents < info->commit_pages) {
 | |
| 			size_t delta;
 | |
| 			size_t old_size = reg->gpu_alloc->nents;
 | |
| 
 | |
| 			/* Allocate some more pages */
 | |
| 			delta = info->commit_pages - reg->gpu_alloc->nents;
 | |
| 			if (kbase_alloc_phy_pages_helper(reg->gpu_alloc, delta)
 | |
| 					!= 0)
 | |
| 				goto update_failed;
 | |
| 
 | |
| 			if (reg->cpu_alloc != reg->gpu_alloc) {
 | |
| 				if (kbase_alloc_phy_pages_helper(
 | |
| 						reg->cpu_alloc, delta) != 0) {
 | |
| 					kbase_free_phy_pages_helper(
 | |
| 							reg->gpu_alloc, delta);
 | |
| 					goto update_failed;
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			ret = kbase_mem_grow_gpu_mapping(kctx, reg,
 | |
| 					info->commit_pages, old_size);
 | |
| 			/*
 | |
| 			 * The grow failed so put the allocation back in the
 | |
| 			 * pool and return failure.
 | |
| 			 */
 | |
| 			if (ret)
 | |
| 				goto update_failed;
 | |
| 		}
 | |
| 		kbase_gpu_vm_unlock(kctx);
 | |
| 	} else {
 | |
| 		/* No suitable JIT allocation was found so create a new one */
 | |
| 		u64 flags = BASE_MEM_PROT_CPU_RD | BASE_MEM_PROT_GPU_RD |
 | |
| 				BASE_MEM_PROT_GPU_WR | BASE_MEM_GROW_ON_GPF |
 | |
| 				BASE_MEM_COHERENT_LOCAL;
 | |
| 		u64 gpu_addr;
 | |
| 
 | |
| 		mutex_unlock(&kctx->jit_evict_lock);
 | |
| 
 | |
| 		reg = kbase_mem_alloc(kctx, info->va_pages, info->commit_pages,
 | |
| 				info->extent, &flags, &gpu_addr);
 | |
| 		if (!reg)
 | |
| 			goto out_unlocked;
 | |
| 
 | |
| 		mutex_lock(&kctx->jit_evict_lock);
 | |
| 		list_add(®->jit_node, &kctx->jit_active_head);
 | |
| 		mutex_unlock(&kctx->jit_evict_lock);
 | |
| 	}
 | |
| 
 | |
| 	return reg;
 | |
| 
 | |
| update_failed:
 | |
| 	/*
 | |
| 	 * An update to an allocation from the pool failed, chances
 | |
| 	 * are slim a new allocation would fair any better so return
 | |
| 	 * the allocation to the pool and return the function with failure.
 | |
| 	 */
 | |
| 	kbase_gpu_vm_unlock(kctx);
 | |
| 	mutex_lock(&kctx->jit_evict_lock);
 | |
| 	list_move(®->jit_node, &kctx->jit_pool_head);
 | |
| 	mutex_unlock(&kctx->jit_evict_lock);
 | |
| out_unlocked:
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| void kbase_jit_free(struct kbase_context *kctx, struct kbase_va_region *reg)
 | |
| {
 | |
| 	/* The physical backing of memory in the pool is always reclaimable */
 | |
| 	kbase_gpu_vm_lock(kctx);
 | |
| 	kbase_mem_evictable_make(reg->gpu_alloc);
 | |
| 	kbase_gpu_vm_unlock(kctx);
 | |
| 
 | |
| 	mutex_lock(&kctx->jit_evict_lock);
 | |
| 	list_move(®->jit_node, &kctx->jit_pool_head);
 | |
| 	mutex_unlock(&kctx->jit_evict_lock);
 | |
| }
 | |
| 
 | |
| void kbase_jit_backing_lost(struct kbase_va_region *reg)
 | |
| {
 | |
| 	struct kbase_context *kctx = reg->kctx;
 | |
| 
 | |
| 	lockdep_assert_held(&kctx->jit_evict_lock);
 | |
| 
 | |
| 	/*
 | |
| 	 * JIT allocations will always be on a list, if the region
 | |
| 	 * is not on a list then it's not a JIT allocation.
 | |
| 	 */
 | |
| 	if (list_empty(®->jit_node))
 | |
| 		return;
 | |
| 
 | |
| 	/*
 | |
| 	 * Freeing the allocation requires locks we might not be able
 | |
| 	 * to take now, so move the allocation to the free list and kick
 | |
| 	 * the worker which will do the freeing.
 | |
| 	 */
 | |
| 	list_move(®->jit_node, &kctx->jit_destroy_head);
 | |
| 
 | |
| 	schedule_work(&kctx->jit_work);
 | |
| }
 | |
| 
 | |
| bool kbase_jit_evict(struct kbase_context *kctx)
 | |
| {
 | |
| 	struct kbase_va_region *reg = NULL;
 | |
| 
 | |
| 	lockdep_assert_held(&kctx->reg_lock);
 | |
| 
 | |
| 	/* Free the oldest allocation from the pool */
 | |
| 	mutex_lock(&kctx->jit_evict_lock);
 | |
| 	if (!list_empty(&kctx->jit_pool_head)) {
 | |
| 		reg = list_entry(kctx->jit_pool_head.prev,
 | |
| 				struct kbase_va_region, jit_node);
 | |
| 		list_del(®->jit_node);
 | |
| 	}
 | |
| 	mutex_unlock(&kctx->jit_evict_lock);
 | |
| 
 | |
| 	if (reg)
 | |
| 		kbase_mem_free_region(kctx, reg);
 | |
| 
 | |
| 	return (reg != NULL);
 | |
| }
 | |
| 
 | |
| void kbase_jit_term(struct kbase_context *kctx)
 | |
| {
 | |
| 	struct kbase_va_region *walker;
 | |
| 
 | |
| 	/* Free all allocations for this context */
 | |
| 
 | |
| 	/*
 | |
| 	 * Flush the freeing of allocations whose backing has been freed
 | |
| 	 * (i.e. everything in jit_destroy_head).
 | |
| 	 */
 | |
| 	cancel_work_sync(&kctx->jit_work);
 | |
| 
 | |
| 	kbase_gpu_vm_lock(kctx);
 | |
| 	mutex_lock(&kctx->jit_evict_lock);
 | |
| 	/* Free all allocations from the pool */
 | |
| 	while (!list_empty(&kctx->jit_pool_head)) {
 | |
| 		walker = list_first_entry(&kctx->jit_pool_head,
 | |
| 				struct kbase_va_region, jit_node);
 | |
| 		list_del(&walker->jit_node);
 | |
| 		mutex_unlock(&kctx->jit_evict_lock);
 | |
| 		kbase_mem_free_region(kctx, walker);
 | |
| 		mutex_lock(&kctx->jit_evict_lock);
 | |
| 	}
 | |
| 
 | |
| 	/* Free all allocations from active list */
 | |
| 	while (!list_empty(&kctx->jit_active_head)) {
 | |
| 		walker = list_first_entry(&kctx->jit_active_head,
 | |
| 				struct kbase_va_region, jit_node);
 | |
| 		list_del(&walker->jit_node);
 | |
| 		mutex_unlock(&kctx->jit_evict_lock);
 | |
| 		kbase_mem_free_region(kctx, walker);
 | |
| 		mutex_lock(&kctx->jit_evict_lock);
 | |
| 	}
 | |
| 	mutex_unlock(&kctx->jit_evict_lock);
 | |
| 	kbase_gpu_vm_unlock(kctx);
 | |
| }
 | |
| 
 | |
| static int kbase_jd_user_buf_map(struct kbase_context *kctx,
 | |
| 		struct kbase_va_region *reg)
 | |
| {
 | |
| 	long pinned_pages;
 | |
| 	struct kbase_mem_phy_alloc *alloc;
 | |
| 	struct page **pages;
 | |
| 	phys_addr_t *pa;
 | |
| 	long i;
 | |
| 	int err = -ENOMEM;
 | |
| 	unsigned long address;
 | |
| 	struct mm_struct *mm;
 | |
| 	struct device *dev;
 | |
| 	unsigned long offset;
 | |
| 	unsigned long local_size;
 | |
| 
 | |
| 	alloc = reg->gpu_alloc;
 | |
| 	pa = kbase_get_gpu_phy_pages(reg);
 | |
| 	address = alloc->imported.user_buf.address;
 | |
| 	mm = alloc->imported.user_buf.mm;
 | |
| 
 | |
| 	KBASE_DEBUG_ASSERT(alloc->type == KBASE_MEM_TYPE_IMPORTED_USER_BUF);
 | |
| 
 | |
| 	pages = alloc->imported.user_buf.pages;
 | |
| 
 | |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0)
 | |
| 	pinned_pages = get_user_pages(NULL, mm,
 | |
| 			address,
 | |
| 			alloc->imported.user_buf.nr_pages,
 | |
| 			reg->flags & KBASE_REG_GPU_WR,
 | |
| 			0, pages, NULL);
 | |
| #elif LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0)
 | |
| 	pinned_pages = get_user_pages_remote(NULL, mm,
 | |
| 			address,
 | |
| 			alloc->imported.user_buf.nr_pages,
 | |
| 			reg->flags & KBASE_REG_GPU_WR,
 | |
| 			0, pages, NULL);
 | |
| #elif LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
 | |
| 	pinned_pages = get_user_pages_remote(NULL, mm,
 | |
| 			address,
 | |
| 			alloc->imported.user_buf.nr_pages,
 | |
| 			reg->flags & KBASE_REG_GPU_WR ? FOLL_WRITE : 0,
 | |
| 			pages, NULL);
 | |
| #elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0)
 | |
| 	pinned_pages = get_user_pages_remote(NULL, mm,
 | |
| 			address,
 | |
| 			alloc->imported.user_buf.nr_pages,
 | |
| 			reg->flags & KBASE_REG_GPU_WR ? FOLL_WRITE : 0,
 | |
| 			pages, NULL, NULL);
 | |
| #else
 | |
| 	pinned_pages = get_user_pages_remote(mm,
 | |
| 			address,
 | |
| 			alloc->imported.user_buf.nr_pages,
 | |
| 			reg->flags & KBASE_REG_GPU_WR ? FOLL_WRITE : 0,
 | |
| 			pages, NULL, NULL);
 | |
| #endif
 | |
| 
 | |
| 	if (pinned_pages <= 0)
 | |
| 		return pinned_pages;
 | |
| 
 | |
| 	if (pinned_pages != alloc->imported.user_buf.nr_pages) {
 | |
| 		for (i = 0; i < pinned_pages; i++)
 | |
| 			put_page(pages[i]);
 | |
| 		return -ENOMEM;
 | |
| 	}
 | |
| 
 | |
| 	dev = kctx->kbdev->dev;
 | |
| 	offset = address & ~PAGE_MASK;
 | |
| 	local_size = alloc->imported.user_buf.size;
 | |
| 
 | |
| 	for (i = 0; i < pinned_pages; i++) {
 | |
| 		dma_addr_t dma_addr;
 | |
| 		unsigned long min;
 | |
| 
 | |
| 		min = MIN(PAGE_SIZE - offset, local_size);
 | |
| 		dma_addr = dma_map_page(dev, pages[i],
 | |
| 				offset, min,
 | |
| 				DMA_BIDIRECTIONAL);
 | |
| 		if (dma_mapping_error(dev, dma_addr))
 | |
| 			goto unwind;
 | |
| 
 | |
| 		alloc->imported.user_buf.dma_addrs[i] = dma_addr;
 | |
| 		pa[i] = page_to_phys(pages[i]);
 | |
| 
 | |
| 		local_size -= min;
 | |
| 		offset = 0;
 | |
| 	}
 | |
| 
 | |
| 	alloc->nents = pinned_pages;
 | |
| 
 | |
| 	err = kbase_mmu_insert_pages(kctx, reg->start_pfn, pa,
 | |
| 			kbase_reg_current_backed_size(reg),
 | |
| 			reg->flags);
 | |
| 	if (err == 0)
 | |
| 		return 0;
 | |
| 
 | |
| 	alloc->nents = 0;
 | |
| 	/* fall down */
 | |
| unwind:
 | |
| 	while (i--) {
 | |
| 		dma_unmap_page(kctx->kbdev->dev,
 | |
| 				alloc->imported.user_buf.dma_addrs[i],
 | |
| 				PAGE_SIZE, DMA_BIDIRECTIONAL);
 | |
| 		put_page(pages[i]);
 | |
| 		pages[i] = NULL;
 | |
| 	}
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static void kbase_jd_user_buf_unmap(struct kbase_context *kctx,
 | |
| 		struct kbase_mem_phy_alloc *alloc, bool writeable)
 | |
| {
 | |
| 	long i;
 | |
| 	struct page **pages;
 | |
| 	unsigned long size = alloc->imported.user_buf.size;
 | |
| 
 | |
| 	KBASE_DEBUG_ASSERT(alloc->type == KBASE_MEM_TYPE_IMPORTED_USER_BUF);
 | |
| 	pages = alloc->imported.user_buf.pages;
 | |
| 	for (i = 0; i < alloc->imported.user_buf.nr_pages; i++) {
 | |
| 		unsigned long local_size;
 | |
| 		dma_addr_t dma_addr = alloc->imported.user_buf.dma_addrs[i];
 | |
| 
 | |
| 		local_size = MIN(size, PAGE_SIZE - (dma_addr & ~PAGE_MASK));
 | |
| 		dma_unmap_page(kctx->kbdev->dev, dma_addr, local_size,
 | |
| 				DMA_BIDIRECTIONAL);
 | |
| 		if (writeable)
 | |
| 			set_page_dirty_lock(pages[i]);
 | |
| 		put_page(pages[i]);
 | |
| 		pages[i] = NULL;
 | |
| 
 | |
| 		size -= local_size;
 | |
| 	}
 | |
| 	alloc->nents = 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* to replace sg_dma_len. */
 | |
| #define MALI_SG_DMA_LEN(sg)        ((sg)->length)
 | |
| 
 | |
| #ifdef CONFIG_DMA_SHARED_BUFFER
 | |
| static int kbase_jd_umm_map(struct kbase_context *kctx,
 | |
| 		struct kbase_va_region *reg)
 | |
| {
 | |
| 	struct sg_table *sgt;
 | |
| 	struct scatterlist *s;
 | |
| 	int i;
 | |
| 	phys_addr_t *pa;
 | |
| 	int err;
 | |
| 	size_t count = 0;
 | |
| 	struct kbase_mem_phy_alloc *alloc;
 | |
| 
 | |
| 	alloc = reg->gpu_alloc;
 | |
| 
 | |
| 	KBASE_DEBUG_ASSERT(alloc->type == KBASE_MEM_TYPE_IMPORTED_UMM);
 | |
| 	KBASE_DEBUG_ASSERT(NULL == alloc->imported.umm.sgt);
 | |
| 	sgt = dma_buf_map_attachment(alloc->imported.umm.dma_attachment,
 | |
| 			DMA_BIDIRECTIONAL);
 | |
| 
 | |
| 	if (IS_ERR_OR_NULL(sgt))
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	/* save for later */
 | |
| 	alloc->imported.umm.sgt = sgt;
 | |
| 
 | |
| 	pa = kbase_get_gpu_phy_pages(reg);
 | |
| 	KBASE_DEBUG_ASSERT(pa);
 | |
| 
 | |
| 	for_each_sg(sgt->sgl, s, sgt->nents, i) {
 | |
| 		int j;
 | |
| 		size_t pages = PFN_UP(MALI_SG_DMA_LEN(s));
 | |
| 
 | |
| 		WARN_ONCE(MALI_SG_DMA_LEN(s) & (PAGE_SIZE-1),
 | |
| 		"MALI_SG_DMA_LEN(s)=%u is not a multiple of PAGE_SIZE\n",
 | |
| 		MALI_SG_DMA_LEN(s));
 | |
| 
 | |
| 		WARN_ONCE(sg_dma_address(s) & (PAGE_SIZE-1),
 | |
| 		"sg_dma_address(s)=%llx is not aligned to PAGE_SIZE\n",
 | |
| 		(unsigned long long) sg_dma_address(s));
 | |
| 
 | |
| 		for (j = 0; (j < pages) && (count < reg->nr_pages); j++,
 | |
| 				count++)
 | |
| 			*pa++ = sg_dma_address(s) + (j << PAGE_SHIFT);
 | |
| 		WARN_ONCE(j < pages,
 | |
| 		"sg list from dma_buf_map_attachment > dma_buf->size=%zu\n",
 | |
| 		alloc->imported.umm.dma_buf->size);
 | |
| 	}
 | |
| 
 | |
| 	if (!(reg->flags & KBASE_REG_IMPORT_PAD) &&
 | |
| 			WARN_ONCE(count < reg->nr_pages,
 | |
| 			"sg list from dma_buf_map_attachment < dma_buf->size=%zu\n",
 | |
| 			alloc->imported.umm.dma_buf->size)) {
 | |
| 		err = -EINVAL;
 | |
| 		goto err_unmap_attachment;
 | |
| 	}
 | |
| 
 | |
| 	/* Update nents as we now have pages to map */
 | |
| 	alloc->nents = reg->nr_pages;
 | |
| 
 | |
| 	err = kbase_mmu_insert_pages(kctx, reg->start_pfn,
 | |
| 			kbase_get_gpu_phy_pages(reg),
 | |
| 			count,
 | |
| 			reg->flags | KBASE_REG_GPU_WR | KBASE_REG_GPU_RD);
 | |
| 	if (err)
 | |
| 		goto err_unmap_attachment;
 | |
| 
 | |
| 	if (reg->flags & KBASE_REG_IMPORT_PAD) {
 | |
| 		err = kbase_mmu_insert_single_page(kctx,
 | |
| 				reg->start_pfn + count,
 | |
| 				page_to_phys(kctx->aliasing_sink_page),
 | |
| 				reg->nr_pages - count,
 | |
| 				(reg->flags | KBASE_REG_GPU_RD) &
 | |
| 				~KBASE_REG_GPU_WR);
 | |
| 		if (err)
 | |
| 			goto err_teardown_orig_pages;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| err_teardown_orig_pages:
 | |
| 	kbase_mmu_teardown_pages(kctx, reg->start_pfn, count);
 | |
| err_unmap_attachment:
 | |
| 	dma_buf_unmap_attachment(alloc->imported.umm.dma_attachment,
 | |
| 			alloc->imported.umm.sgt, DMA_BIDIRECTIONAL);
 | |
| 	alloc->imported.umm.sgt = NULL;
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static void kbase_jd_umm_unmap(struct kbase_context *kctx,
 | |
| 		struct kbase_mem_phy_alloc *alloc)
 | |
| {
 | |
| 	KBASE_DEBUG_ASSERT(kctx);
 | |
| 	KBASE_DEBUG_ASSERT(alloc);
 | |
| 	KBASE_DEBUG_ASSERT(alloc->imported.umm.dma_attachment);
 | |
| 	KBASE_DEBUG_ASSERT(alloc->imported.umm.sgt);
 | |
| 	dma_buf_unmap_attachment(alloc->imported.umm.dma_attachment,
 | |
| 	    alloc->imported.umm.sgt, DMA_BIDIRECTIONAL);
 | |
| 	alloc->imported.umm.sgt = NULL;
 | |
| 	alloc->nents = 0;
 | |
| }
 | |
| #endif				/* CONFIG_DMA_SHARED_BUFFER */
 | |
| 
 | |
| #if (defined(CONFIG_KDS) && defined(CONFIG_UMP)) \
 | |
| 		|| defined(CONFIG_DMA_SHARED_BUFFER_USES_KDS)
 | |
| static void add_kds_resource(struct kds_resource *kds_res,
 | |
| 		struct kds_resource **kds_resources, u32 *kds_res_count,
 | |
| 		unsigned long *kds_access_bitmap, bool exclusive)
 | |
| {
 | |
| 	u32 i;
 | |
| 
 | |
| 	for (i = 0; i < *kds_res_count; i++) {
 | |
| 		/* Duplicate resource, ignore */
 | |
| 		if (kds_resources[i] == kds_res)
 | |
| 			return;
 | |
| 	}
 | |
| 
 | |
| 	kds_resources[*kds_res_count] = kds_res;
 | |
| 	if (exclusive)
 | |
| 		set_bit(*kds_res_count, kds_access_bitmap);
 | |
| 	(*kds_res_count)++;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| struct kbase_mem_phy_alloc *kbase_map_external_resource(
 | |
| 		struct kbase_context *kctx, struct kbase_va_region *reg,
 | |
| 		struct mm_struct *locked_mm
 | |
| #ifdef CONFIG_KDS
 | |
| 		, u32 *kds_res_count, struct kds_resource **kds_resources,
 | |
| 		unsigned long *kds_access_bitmap, bool exclusive
 | |
| #endif
 | |
| 		)
 | |
| {
 | |
| 	int err;
 | |
| 
 | |
| 	/* decide what needs to happen for this resource */
 | |
| 	switch (reg->gpu_alloc->type) {
 | |
| 	case KBASE_MEM_TYPE_IMPORTED_USER_BUF: {
 | |
| 		if (reg->gpu_alloc->imported.user_buf.mm != locked_mm)
 | |
| 			goto exit;
 | |
| 
 | |
| 		reg->gpu_alloc->imported.user_buf.current_mapping_usage_count++;
 | |
| 		if (1 == reg->gpu_alloc->imported.user_buf.current_mapping_usage_count) {
 | |
| 			err = kbase_jd_user_buf_map(kctx, reg);
 | |
| 			if (err) {
 | |
| 				reg->gpu_alloc->imported.user_buf.current_mapping_usage_count--;
 | |
| 				goto exit;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	break;
 | |
| 	case KBASE_MEM_TYPE_IMPORTED_UMP: {
 | |
| #if defined(CONFIG_KDS) && defined(CONFIG_UMP)
 | |
| 		if (kds_res_count) {
 | |
| 			struct kds_resource *kds_res;
 | |
| 
 | |
| 			kds_res = ump_dd_kds_resource_get(
 | |
| 					reg->gpu_alloc->imported.ump_handle);
 | |
| 			if (kds_res)
 | |
| 				add_kds_resource(kds_res, kds_resources,
 | |
| 						kds_res_count,
 | |
| 						kds_access_bitmap, exclusive);
 | |
| 		}
 | |
| #endif				/*defined(CONFIG_KDS) && defined(CONFIG_UMP) */
 | |
| 		break;
 | |
| 	}
 | |
| #ifdef CONFIG_DMA_SHARED_BUFFER
 | |
| 	case KBASE_MEM_TYPE_IMPORTED_UMM: {
 | |
| #ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
 | |
| 		if (kds_res_count) {
 | |
| 			struct kds_resource *kds_res;
 | |
| 
 | |
| 			kds_res = get_dma_buf_kds_resource(
 | |
| 					reg->gpu_alloc->imported.umm.dma_buf);
 | |
| 			if (kds_res)
 | |
| 				add_kds_resource(kds_res, kds_resources,
 | |
| 						kds_res_count,
 | |
| 						kds_access_bitmap, exclusive);
 | |
| 		}
 | |
| #endif
 | |
| 		reg->gpu_alloc->imported.umm.current_mapping_usage_count++;
 | |
| 		if (1 == reg->gpu_alloc->imported.umm.current_mapping_usage_count) {
 | |
| 			err = kbase_jd_umm_map(kctx, reg);
 | |
| 			if (err) {
 | |
| 				reg->gpu_alloc->imported.umm.current_mapping_usage_count--;
 | |
| 				goto exit;
 | |
| 			}
 | |
| 		}
 | |
| 		break;
 | |
| 	}
 | |
| #endif
 | |
| 	default:
 | |
| 		goto exit;
 | |
| 	}
 | |
| 
 | |
| 	return kbase_mem_phy_alloc_get(reg->gpu_alloc);
 | |
| exit:
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| void kbase_unmap_external_resource(struct kbase_context *kctx,
 | |
| 		struct kbase_va_region *reg, struct kbase_mem_phy_alloc *alloc)
 | |
| {
 | |
| 	switch (alloc->type) {
 | |
| #ifdef CONFIG_DMA_SHARED_BUFFER
 | |
| 	case KBASE_MEM_TYPE_IMPORTED_UMM: {
 | |
| 		alloc->imported.umm.current_mapping_usage_count--;
 | |
| 
 | |
| 		if (0 == alloc->imported.umm.current_mapping_usage_count) {
 | |
| 			if (reg && reg->gpu_alloc == alloc) {
 | |
| 				int err;
 | |
| 
 | |
| 				err = kbase_mmu_teardown_pages(
 | |
| 						kctx,
 | |
| 						reg->start_pfn,
 | |
| 						alloc->nents);
 | |
| 				WARN_ON(err);
 | |
| 			}
 | |
| 
 | |
| 			kbase_jd_umm_unmap(kctx, alloc);
 | |
| 		}
 | |
| 	}
 | |
| 	break;
 | |
| #endif /* CONFIG_DMA_SHARED_BUFFER */
 | |
| 	case KBASE_MEM_TYPE_IMPORTED_USER_BUF: {
 | |
| 		alloc->imported.user_buf.current_mapping_usage_count--;
 | |
| 
 | |
| 		if (0 == alloc->imported.user_buf.current_mapping_usage_count) {
 | |
| 			bool writeable = true;
 | |
| 
 | |
| 			if (reg && reg->gpu_alloc == alloc)
 | |
| 				kbase_mmu_teardown_pages(
 | |
| 						kctx,
 | |
| 						reg->start_pfn,
 | |
| 						kbase_reg_current_backed_size(reg));
 | |
| 
 | |
| 			if (reg && ((reg->flags & KBASE_REG_GPU_WR) == 0))
 | |
| 				writeable = false;
 | |
| 
 | |
| 			kbase_jd_user_buf_unmap(kctx, alloc, writeable);
 | |
| 		}
 | |
| 	}
 | |
| 	break;
 | |
| 	default:
 | |
| 	break;
 | |
| 	}
 | |
| 	kbase_mem_phy_alloc_put(alloc);
 | |
| }
 | |
| 
 | |
| struct kbase_ctx_ext_res_meta *kbase_sticky_resource_acquire(
 | |
| 		struct kbase_context *kctx, u64 gpu_addr)
 | |
| {
 | |
| 	struct kbase_ctx_ext_res_meta *meta = NULL;
 | |
| 	struct kbase_ctx_ext_res_meta *walker;
 | |
| 
 | |
| 	lockdep_assert_held(&kctx->reg_lock);
 | |
| 
 | |
| 	/*
 | |
| 	 * Walk the per context external resource metadata list for the
 | |
| 	 * metadata which matches the region which is being acquired.
 | |
| 	 */
 | |
| 	list_for_each_entry(walker, &kctx->ext_res_meta_head, ext_res_node) {
 | |
| 		if (walker->gpu_addr == gpu_addr) {
 | |
| 			meta = walker;
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* No metadata exists so create one. */
 | |
| 	if (!meta) {
 | |
| 		struct kbase_va_region *reg;
 | |
| 
 | |
| 		/* Find the region */
 | |
| 		reg = kbase_region_tracker_find_region_enclosing_address(
 | |
| 				kctx, gpu_addr);
 | |
| 		if (NULL == reg || (reg->flags & KBASE_REG_FREE))
 | |
| 			goto failed;
 | |
| 
 | |
| 		/* Allocate the metadata object */
 | |
| 		meta = kzalloc(sizeof(*meta), GFP_KERNEL);
 | |
| 		if (!meta)
 | |
| 			goto failed;
 | |
| 
 | |
| 		/*
 | |
| 		 * Fill in the metadata object and acquire a reference
 | |
| 		 * for the physical resource.
 | |
| 		 */
 | |
| 		meta->alloc = kbase_map_external_resource(kctx, reg, NULL
 | |
| #ifdef CONFIG_KDS
 | |
| 				, NULL, NULL,
 | |
| 				NULL, false
 | |
| #endif
 | |
| 				);
 | |
| 
 | |
| 		if (!meta->alloc)
 | |
| 			goto fail_map;
 | |
| 
 | |
| 		meta->gpu_addr = reg->start_pfn << PAGE_SHIFT;
 | |
| 
 | |
| 		list_add(&meta->ext_res_node, &kctx->ext_res_meta_head);
 | |
| 	}
 | |
| 
 | |
| 	return meta;
 | |
| 
 | |
| fail_map:
 | |
| 	kfree(meta);
 | |
| failed:
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| bool kbase_sticky_resource_release(struct kbase_context *kctx,
 | |
| 		struct kbase_ctx_ext_res_meta *meta, u64 gpu_addr)
 | |
| {
 | |
| 	struct kbase_ctx_ext_res_meta *walker;
 | |
| 	struct kbase_va_region *reg;
 | |
| 
 | |
| 	lockdep_assert_held(&kctx->reg_lock);
 | |
| 
 | |
| 	/* Search of the metadata if one isn't provided. */
 | |
| 	if (!meta) {
 | |
| 		/*
 | |
| 		 * Walk the per context external resource metadata list for the
 | |
| 		 * metadata which matches the region which is being released.
 | |
| 		 */
 | |
| 		list_for_each_entry(walker, &kctx->ext_res_meta_head,
 | |
| 				ext_res_node) {
 | |
| 			if (walker->gpu_addr == gpu_addr) {
 | |
| 				meta = walker;
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* No metadata so just return. */
 | |
| 	if (!meta)
 | |
| 		return false;
 | |
| 
 | |
| 	/* Drop the physical memory reference and free the metadata. */
 | |
| 	reg = kbase_region_tracker_find_region_enclosing_address(
 | |
| 			kctx,
 | |
| 			meta->gpu_addr);
 | |
| 
 | |
| 	kbase_unmap_external_resource(kctx, reg, meta->alloc);
 | |
| 	list_del(&meta->ext_res_node);
 | |
| 	kfree(meta);
 | |
| 
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| int kbase_sticky_resource_init(struct kbase_context *kctx)
 | |
| {
 | |
| 	INIT_LIST_HEAD(&kctx->ext_res_meta_head);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void kbase_sticky_resource_term(struct kbase_context *kctx)
 | |
| {
 | |
| 	struct kbase_ctx_ext_res_meta *walker;
 | |
| 
 | |
| 	lockdep_assert_held(&kctx->reg_lock);
 | |
| 
 | |
| 	/*
 | |
| 	 * Free any sticky resources which haven't been unmapped.
 | |
| 	 *
 | |
| 	 * Note:
 | |
| 	 * We don't care about refcounts at this point as no future
 | |
| 	 * references to the meta data will be made.
 | |
| 	 * Region termination would find these if we didn't free them
 | |
| 	 * here, but it's more efficient if we do the clean up here.
 | |
| 	 */
 | |
| 	while (!list_empty(&kctx->ext_res_meta_head)) {
 | |
| 		walker = list_first_entry(&kctx->ext_res_meta_head,
 | |
| 				struct kbase_ctx_ext_res_meta, ext_res_node);
 | |
| 
 | |
| 		kbase_sticky_resource_release(kctx, walker, 0);
 | |
| 	}
 | |
| }
 |