blob: 709ff3e770ff75ac4f46107160b32d2960f9834c [file] [log] [blame]
/*
*
* (C) COPYRIGHT 2010-2016 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 <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>
/**
* @brief Check the zone compatibility of two regions.
*/
static int kbase_region_tracker_match_zone(struct kbase_va_region *reg1,
struct kbase_va_region *reg2)
{
return ((reg1->flags & KBASE_REG_ZONE_MASK) ==
(reg2->flags & KBASE_REG_ZONE_MASK));
}
KBASE_EXPORT_TEST_API(kbase_region_tracker_match_zone);
/* 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 = &(kctx->reg_rbtree.rb_node);
struct rb_node *parent = NULL;
/* 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), &(kctx->reg_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;
struct kbase_va_region *reg;
u64 end_pfn = start_pfn + nr_pages;
rbnode = kctx->reg_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;
KBASE_DEBUG_ASSERT(NULL != kctx);
lockdep_assert_held(&kctx->reg_lock);
rbnode = kctx->reg_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;
struct kbase_va_region *reg;
KBASE_DEBUG_ASSERT(NULL != kctx);
lockdep_assert_held(&kctx->reg_lock);
rbnode = kctx->reg_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;
struct kbase_va_region *reg;
/* 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 */
rbnode = rb_first(&(kctx->reg_rbtree));
while (rbnode) {
reg = rb_entry(rbnode, struct kbase_va_region, rblink);
if ((reg->nr_pages >= nr_pages) &&
(reg->flags & KBASE_REG_FREE) &&
kbase_region_tracker_match_zone(reg, reg_reqs)) {
/* 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;
int merged_front = 0;
int merged_back = 0;
int err = 0;
/* 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) && kbase_region_tracker_match_zone(prev, reg)) {
/* We're compatible with the previous VMA, merge with it */
prev->nr_pages += reg->nr_pages;
rb_erase(&(reg->rblink), &kctx->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) && kbase_region_tracker_match_zone(next, reg)) {
next->start_pfn = reg->start_pfn;
next->nr_pages += reg->nr_pages;
rb_erase(&(reg->rblink), &kctx->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), &(kctx->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)
{
int err = 0;
/* 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), &(kctx->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 ((!kbase_region_tracker_match_zone(tmp, reg)) ||
(!(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 = RB_ROOT;
kbase_region_tracker_insert(kctx, same_va_reg);
/* exec and custom_va_reg doesn't always exist */
if (exec_reg && custom_va_reg) {
kbase_region_tracker_insert(kctx, exec_reg);
kbase_region_tracker_insert(kctx, custom_va_reg);
}
}
void kbase_region_tracker_term(struct kbase_context *kctx)
{
struct rb_node *rbnode;
struct kbase_va_region *reg;
do {
rbnode = rb_first(&(kctx->reg_rbtree));
if (rbnode) {
rb_erase(rbnode, &(kctx->reg_rbtree));
reg = rb_entry(rbnode, struct kbase_va_region, rblink);
kbase_free_alloced_region(reg);
}
} while (rbnode);
}
/**
* 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 (kctx->is_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 (kctx->is_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 (kctx->is_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(&reg->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);
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 (err)
return err;
err = kbase_remove_va_region(kctx, reg);
return err;
}
static struct kbase_cpu_mapping *kbasep_find_enclosing_cpu_mapping_of_region(const struct kbase_va_region *reg, unsigned long uaddr, size_t size)
{
struct kbase_cpu_mapping *map;
struct list_head *pos;
KBASE_DEBUG_ASSERT(NULL != reg);
KBASE_DEBUG_ASSERT(reg->cpu_alloc);
if ((uintptr_t) uaddr + size < (uintptr_t) uaddr) /* overflow check */
return NULL;
list_for_each(pos, &reg->cpu_alloc->mappings) {
map = list_entry(pos, struct kbase_cpu_mapping, mappings_list);
if (map->vm_start <= uaddr && map->vm_end >= uaddr + size)
return map;
}
return NULL;
}
KBASE_EXPORT_TEST_API(kbasep_find_enclosing_cpu_mapping_of_region);
int kbasep_find_enclosing_cpu_mapping_offset(
struct kbase_context *kctx, u64 gpu_addr,
unsigned long uaddr, size_t size, u64 * offset)
{
struct kbase_cpu_mapping *map = NULL;
const struct kbase_va_region *reg;
int err = -EINVAL;
KBASE_DEBUG_ASSERT(kctx != NULL);
kbase_gpu_vm_lock(kctx);
reg = kbase_region_tracker_find_region_enclosing_address(kctx, gpu_addr);
if (reg && !(reg->flags & KBASE_REG_FREE)) {
map = kbasep_find_enclosing_cpu_mapping_of_region(reg, uaddr,
size);
if (map) {
*offset = (uaddr - PTR_TO_U64(map->vm_start)) +
(map->page_off << PAGE_SHIFT);
err = 0;
}
}
kbase_gpu_vm_unlock(kctx);
return err;
}
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 base_syncset *set, enum kbase_sync_type sync_fn)
{
int err = 0;
struct basep_syncset *sset = &set->basep_sset;
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;
off_t 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_of_region(reg, start, size);
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;
}
offset = start & (PAGE_SIZE - 1);
page_off = map->page_off + ((start - map->vm_start) >> PAGE_SHIFT);
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);
/* 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 base_syncset *syncset)
{
int err = -EINVAL;
struct basep_syncset *sset;
KBASE_DEBUG_ASSERT(NULL != kctx);
KBASE_DEBUG_ASSERT(NULL != syncset);
sset = &syncset->basep_sset;
switch (sset->type) {
case BASE_SYNCSET_OP_MSYNC:
err = kbase_do_syncset(kctx, syncset, KBASE_SYNC_TO_DEVICE);
break;
case BASE_SYNCSET_OP_CSYNC:
err = kbase_do_syncset(kctx, syncset, 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 (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);
void 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 (flags & BASE_MEM_COHERENT_SYSTEM ||
flags & BASE_MEM_COHERENT_SYSTEM_REQUIRED)
reg->flags |= KBASE_REG_SHARE_BOTH;
else if (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);
}
}
KBASE_EXPORT_TEST_API(kbase_update_region_flags);
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;
/*
* Request a zone cache update, this scans only the new pages an
* appends their information to the zone cache. if the update
* fails then clear the cache so we fall-back to doing things
* page by page.
*/
if (kbase_zone_cache_update(alloc, old_page_count) != 0)
kbase_zone_cache_clear(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;
/*
* Clear the zone cache, we don't expect JIT allocations to be
* shrunk in parts so there is no point trying to optimize for that
* by scanning for the changes caused by freeing this memory and
* updating the existing cache entries.
*/
kbase_zone_cache_clear(alloc);
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_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_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_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_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_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_lock);
return 0;
}
KBASE_JIT_DEBUGFS_DECLARE(kbase_jit_debugfs_phys_fops,
kbase_jit_debugfs_phys_get);
void kbase_jit_debugfs_add(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_lock);
if (list_empty(&kctx->jit_destroy_head))
reg = NULL;
else
reg = list_first_entry(&kctx->jit_destroy_head,
struct kbase_va_region, jit_node);
if (reg) {
list_del(&reg->jit_node);
mutex_unlock(&kctx->jit_lock);
kbase_gpu_vm_lock(kctx);
kbase_mem_free_region(kctx, reg);
kbase_gpu_vm_unlock(kctx);
} else
mutex_unlock(&kctx->jit_lock);
} while (reg);
}
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);
mutex_init(&kctx->jit_lock);
INIT_WORK(&kctx->jit_work, kbase_jit_destroy_worker);
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_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_del_init(&reg->jit_node);
list_add(&reg->jit_node, &kctx->jit_active_head);
/* Release the jit lock before modifying the allocation */
mutex_unlock(&kctx->jit_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;
u16 alignment;
mutex_unlock(&kctx->jit_lock);
reg = kbase_mem_alloc(kctx, info->va_pages, info->commit_pages,
info->extent, &flags, &gpu_addr, &alignment);
if (!reg)
goto out_unlocked;
mutex_lock(&kctx->jit_lock);
list_add(&reg->jit_node, &kctx->jit_active_head);
mutex_unlock(&kctx->jit_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_lock);
list_del_init(&reg->jit_node);
list_add(&reg->jit_node, &kctx->jit_pool_head);
mutex_unlock(&kctx->jit_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 */
down_read(&kctx->process_mm->mmap_sem);
kbase_gpu_vm_lock(kctx);
kbase_mem_evictable_make(reg->gpu_alloc);
kbase_gpu_vm_unlock(kctx);
up_read(&kctx->process_mm->mmap_sem);
mutex_lock(&kctx->jit_lock);
list_del_init(&reg->jit_node);
list_add(&reg->jit_node, &kctx->jit_pool_head);
mutex_unlock(&kctx->jit_lock);
}
void kbase_jit_backing_lost(struct kbase_va_region *reg)
{
struct kbase_context *kctx = reg->kctx;
/*
* 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(&reg->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.
*/
mutex_lock(&kctx->jit_lock);
list_del_init(&reg->jit_node);
list_add(&reg->jit_node, &kctx->jit_destroy_head);
mutex_unlock(&kctx->jit_lock);
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_lock);
if (!list_empty(&kctx->jit_pool_head)) {
reg = list_entry(kctx->jit_pool_head.prev,
struct kbase_va_region, jit_node);
list_del(&reg->jit_node);
}
mutex_unlock(&kctx->jit_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);
/* 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);
kbase_mem_free_region(kctx, walker);
}
/* 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);
kbase_mem_free_region(kctx, walker);
}
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);
#else
pinned_pages = get_user_pages_remote(NULL, mm,
address,
alloc->imported.user_buf.nr_pages,
reg->flags & KBASE_REG_GPU_WR,
0, pages, 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;
}
#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(sg_dma_len(s));
WARN_ONCE(sg_dma_len(s) & (PAGE_SIZE-1),
"sg_dma_len(s)=%u is not a multiple of PAGE_SIZE\n",
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 (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 out;
}
/* Update nents as we now have pages to map */
alloc->nents = count;
err = kbase_mmu_insert_pages(kctx, reg->start_pfn,
kbase_get_gpu_phy_pages(reg),
kbase_reg_current_backed_size(reg),
reg->flags | KBASE_REG_GPU_WR | KBASE_REG_GPU_RD);
out:
if (err) {
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)
kbase_mmu_teardown_pages(
kctx,
reg->start_pfn,
kbase_reg_current_backed_size(reg));
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);
}
}