mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-01-15 17:43:59 +00:00
ff60c8da0a
Avoids use-after-free situations when panthor_fw_unplug() is called and the kernel BO was mapped to the FW VM. Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com> Reviewed-by: Steven Price <steven.price@arm.com> Reviewed-by: Liviu Dudau <liviu.dudau@arm.com> Link: https://patchwork.freedesktop.org/patch/msgid/20240502183813.1612017-3-boris.brezillon@collabora.com
606 lines
15 KiB
C
606 lines
15 KiB
C
// SPDX-License-Identifier: GPL-2.0 or MIT
|
|
/* Copyright 2023 Collabora ltd. */
|
|
|
|
#include <linux/iosys-map.h>
|
|
#include <linux/rwsem.h>
|
|
|
|
#include <drm/panthor_drm.h>
|
|
|
|
#include "panthor_device.h"
|
|
#include "panthor_gem.h"
|
|
#include "panthor_heap.h"
|
|
#include "panthor_mmu.h"
|
|
#include "panthor_regs.h"
|
|
|
|
/*
|
|
* The GPU heap context is an opaque structure used by the GPU to track the
|
|
* heap allocations. The driver should only touch it to initialize it (zero all
|
|
* fields). Because the CPU and GPU can both access this structure it is
|
|
* required to be GPU cache line aligned.
|
|
*/
|
|
#define HEAP_CONTEXT_SIZE 32
|
|
|
|
/**
|
|
* struct panthor_heap_chunk_header - Heap chunk header
|
|
*/
|
|
struct panthor_heap_chunk_header {
|
|
/**
|
|
* @next: Next heap chunk in the list.
|
|
*
|
|
* This is a GPU VA.
|
|
*/
|
|
u64 next;
|
|
|
|
/** @unknown: MBZ. */
|
|
u32 unknown[14];
|
|
};
|
|
|
|
/**
|
|
* struct panthor_heap_chunk - Structure used to keep track of allocated heap chunks.
|
|
*/
|
|
struct panthor_heap_chunk {
|
|
/** @node: Used to insert the heap chunk in panthor_heap::chunks. */
|
|
struct list_head node;
|
|
|
|
/** @bo: Buffer object backing the heap chunk. */
|
|
struct panthor_kernel_bo *bo;
|
|
};
|
|
|
|
/**
|
|
* struct panthor_heap - Structure used to manage tiler heap contexts.
|
|
*/
|
|
struct panthor_heap {
|
|
/** @chunks: List containing all heap chunks allocated so far. */
|
|
struct list_head chunks;
|
|
|
|
/** @lock: Lock protecting insertion in the chunks list. */
|
|
struct mutex lock;
|
|
|
|
/** @chunk_size: Size of each chunk. */
|
|
u32 chunk_size;
|
|
|
|
/** @max_chunks: Maximum number of chunks. */
|
|
u32 max_chunks;
|
|
|
|
/**
|
|
* @target_in_flight: Number of in-flight render passes after which
|
|
* we'd let the FW wait for fragment job to finish instead of allocating new chunks.
|
|
*/
|
|
u32 target_in_flight;
|
|
|
|
/** @chunk_count: Number of heap chunks currently allocated. */
|
|
u32 chunk_count;
|
|
};
|
|
|
|
#define MAX_HEAPS_PER_POOL 128
|
|
|
|
/**
|
|
* struct panthor_heap_pool - Pool of heap contexts
|
|
*
|
|
* The pool is attached to a panthor_file and can't be shared across processes.
|
|
*/
|
|
struct panthor_heap_pool {
|
|
/** @refcount: Reference count. */
|
|
struct kref refcount;
|
|
|
|
/** @ptdev: Device. */
|
|
struct panthor_device *ptdev;
|
|
|
|
/** @vm: VM this pool is bound to. */
|
|
struct panthor_vm *vm;
|
|
|
|
/** @lock: Lock protecting access to @xa. */
|
|
struct rw_semaphore lock;
|
|
|
|
/** @xa: Array storing panthor_heap objects. */
|
|
struct xarray xa;
|
|
|
|
/** @gpu_contexts: Buffer object containing the GPU heap contexts. */
|
|
struct panthor_kernel_bo *gpu_contexts;
|
|
};
|
|
|
|
static int panthor_heap_ctx_stride(struct panthor_device *ptdev)
|
|
{
|
|
u32 l2_features = ptdev->gpu_info.l2_features;
|
|
u32 gpu_cache_line_size = GPU_L2_FEATURES_LINE_SIZE(l2_features);
|
|
|
|
return ALIGN(HEAP_CONTEXT_SIZE, gpu_cache_line_size);
|
|
}
|
|
|
|
static int panthor_get_heap_ctx_offset(struct panthor_heap_pool *pool, int id)
|
|
{
|
|
return panthor_heap_ctx_stride(pool->ptdev) * id;
|
|
}
|
|
|
|
static void *panthor_get_heap_ctx(struct panthor_heap_pool *pool, int id)
|
|
{
|
|
return pool->gpu_contexts->kmap +
|
|
panthor_get_heap_ctx_offset(pool, id);
|
|
}
|
|
|
|
static void panthor_free_heap_chunk(struct panthor_vm *vm,
|
|
struct panthor_heap *heap,
|
|
struct panthor_heap_chunk *chunk)
|
|
{
|
|
mutex_lock(&heap->lock);
|
|
list_del(&chunk->node);
|
|
heap->chunk_count--;
|
|
mutex_unlock(&heap->lock);
|
|
|
|
panthor_kernel_bo_destroy(chunk->bo);
|
|
kfree(chunk);
|
|
}
|
|
|
|
static int panthor_alloc_heap_chunk(struct panthor_device *ptdev,
|
|
struct panthor_vm *vm,
|
|
struct panthor_heap *heap,
|
|
bool initial_chunk)
|
|
{
|
|
struct panthor_heap_chunk *chunk;
|
|
struct panthor_heap_chunk_header *hdr;
|
|
int ret;
|
|
|
|
chunk = kmalloc(sizeof(*chunk), GFP_KERNEL);
|
|
if (!chunk)
|
|
return -ENOMEM;
|
|
|
|
chunk->bo = panthor_kernel_bo_create(ptdev, vm, heap->chunk_size,
|
|
DRM_PANTHOR_BO_NO_MMAP,
|
|
DRM_PANTHOR_VM_BIND_OP_MAP_NOEXEC,
|
|
PANTHOR_VM_KERNEL_AUTO_VA);
|
|
if (IS_ERR(chunk->bo)) {
|
|
ret = PTR_ERR(chunk->bo);
|
|
goto err_free_chunk;
|
|
}
|
|
|
|
ret = panthor_kernel_bo_vmap(chunk->bo);
|
|
if (ret)
|
|
goto err_destroy_bo;
|
|
|
|
hdr = chunk->bo->kmap;
|
|
memset(hdr, 0, sizeof(*hdr));
|
|
|
|
if (initial_chunk && !list_empty(&heap->chunks)) {
|
|
struct panthor_heap_chunk *prev_chunk;
|
|
u64 prev_gpuva;
|
|
|
|
prev_chunk = list_first_entry(&heap->chunks,
|
|
struct panthor_heap_chunk,
|
|
node);
|
|
|
|
prev_gpuva = panthor_kernel_bo_gpuva(prev_chunk->bo);
|
|
hdr->next = (prev_gpuva & GENMASK_ULL(63, 12)) |
|
|
(heap->chunk_size >> 12);
|
|
}
|
|
|
|
panthor_kernel_bo_vunmap(chunk->bo);
|
|
|
|
mutex_lock(&heap->lock);
|
|
list_add(&chunk->node, &heap->chunks);
|
|
heap->chunk_count++;
|
|
mutex_unlock(&heap->lock);
|
|
|
|
return 0;
|
|
|
|
err_destroy_bo:
|
|
panthor_kernel_bo_destroy(chunk->bo);
|
|
|
|
err_free_chunk:
|
|
kfree(chunk);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void panthor_free_heap_chunks(struct panthor_vm *vm,
|
|
struct panthor_heap *heap)
|
|
{
|
|
struct panthor_heap_chunk *chunk, *tmp;
|
|
|
|
list_for_each_entry_safe(chunk, tmp, &heap->chunks, node)
|
|
panthor_free_heap_chunk(vm, heap, chunk);
|
|
}
|
|
|
|
static int panthor_alloc_heap_chunks(struct panthor_device *ptdev,
|
|
struct panthor_vm *vm,
|
|
struct panthor_heap *heap,
|
|
u32 chunk_count)
|
|
{
|
|
int ret;
|
|
u32 i;
|
|
|
|
for (i = 0; i < chunk_count; i++) {
|
|
ret = panthor_alloc_heap_chunk(ptdev, vm, heap, true);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
panthor_heap_destroy_locked(struct panthor_heap_pool *pool, u32 handle)
|
|
{
|
|
struct panthor_heap *heap;
|
|
|
|
heap = xa_erase(&pool->xa, handle);
|
|
if (!heap)
|
|
return -EINVAL;
|
|
|
|
panthor_free_heap_chunks(pool->vm, heap);
|
|
mutex_destroy(&heap->lock);
|
|
kfree(heap);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* panthor_heap_destroy() - Destroy a heap context
|
|
* @pool: Pool this context belongs to.
|
|
* @handle: Handle returned by panthor_heap_create().
|
|
*/
|
|
int panthor_heap_destroy(struct panthor_heap_pool *pool, u32 handle)
|
|
{
|
|
int ret;
|
|
|
|
down_write(&pool->lock);
|
|
ret = panthor_heap_destroy_locked(pool, handle);
|
|
up_write(&pool->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* panthor_heap_create() - Create a heap context
|
|
* @pool: Pool to instantiate the heap context from.
|
|
* @initial_chunk_count: Number of chunk allocated at initialization time.
|
|
* Must be at least 1.
|
|
* @chunk_size: The size of each chunk. Must be page-aligned and lie in the
|
|
* [128k:8M] range.
|
|
* @max_chunks: Maximum number of chunks that can be allocated.
|
|
* @target_in_flight: Maximum number of in-flight render passes.
|
|
* @heap_ctx_gpu_va: Pointer holding the GPU address of the allocated heap
|
|
* context.
|
|
* @first_chunk_gpu_va: Pointer holding the GPU address of the first chunk
|
|
* assigned to the heap context.
|
|
*
|
|
* Return: a positive handle on success, a negative error otherwise.
|
|
*/
|
|
int panthor_heap_create(struct panthor_heap_pool *pool,
|
|
u32 initial_chunk_count,
|
|
u32 chunk_size,
|
|
u32 max_chunks,
|
|
u32 target_in_flight,
|
|
u64 *heap_ctx_gpu_va,
|
|
u64 *first_chunk_gpu_va)
|
|
{
|
|
struct panthor_heap *heap;
|
|
struct panthor_heap_chunk *first_chunk;
|
|
struct panthor_vm *vm;
|
|
int ret = 0;
|
|
u32 id;
|
|
|
|
if (initial_chunk_count == 0)
|
|
return -EINVAL;
|
|
|
|
if (initial_chunk_count > max_chunks)
|
|
return -EINVAL;
|
|
|
|
if (!IS_ALIGNED(chunk_size, PAGE_SIZE) ||
|
|
chunk_size < SZ_128K || chunk_size > SZ_8M)
|
|
return -EINVAL;
|
|
|
|
down_read(&pool->lock);
|
|
vm = panthor_vm_get(pool->vm);
|
|
up_read(&pool->lock);
|
|
|
|
/* The pool has been destroyed, we can't create a new heap. */
|
|
if (!vm)
|
|
return -EINVAL;
|
|
|
|
heap = kzalloc(sizeof(*heap), GFP_KERNEL);
|
|
if (!heap) {
|
|
ret = -ENOMEM;
|
|
goto err_put_vm;
|
|
}
|
|
|
|
mutex_init(&heap->lock);
|
|
INIT_LIST_HEAD(&heap->chunks);
|
|
heap->chunk_size = chunk_size;
|
|
heap->max_chunks = max_chunks;
|
|
heap->target_in_flight = target_in_flight;
|
|
|
|
ret = panthor_alloc_heap_chunks(pool->ptdev, vm, heap,
|
|
initial_chunk_count);
|
|
if (ret)
|
|
goto err_free_heap;
|
|
|
|
first_chunk = list_first_entry(&heap->chunks,
|
|
struct panthor_heap_chunk,
|
|
node);
|
|
*first_chunk_gpu_va = panthor_kernel_bo_gpuva(first_chunk->bo);
|
|
|
|
down_write(&pool->lock);
|
|
/* The pool has been destroyed, we can't create a new heap. */
|
|
if (!pool->vm) {
|
|
ret = -EINVAL;
|
|
} else {
|
|
ret = xa_alloc(&pool->xa, &id, heap,
|
|
XA_LIMIT(0, MAX_HEAPS_PER_POOL - 1), GFP_KERNEL);
|
|
if (!ret) {
|
|
void *gpu_ctx = panthor_get_heap_ctx(pool, id);
|
|
|
|
memset(gpu_ctx, 0, panthor_heap_ctx_stride(pool->ptdev));
|
|
*heap_ctx_gpu_va = panthor_kernel_bo_gpuva(pool->gpu_contexts) +
|
|
panthor_get_heap_ctx_offset(pool, id);
|
|
}
|
|
}
|
|
up_write(&pool->lock);
|
|
|
|
if (ret)
|
|
goto err_free_heap;
|
|
|
|
panthor_vm_put(vm);
|
|
return id;
|
|
|
|
err_free_heap:
|
|
panthor_free_heap_chunks(pool->vm, heap);
|
|
mutex_destroy(&heap->lock);
|
|
kfree(heap);
|
|
|
|
err_put_vm:
|
|
panthor_vm_put(vm);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* panthor_heap_return_chunk() - Return an unused heap chunk
|
|
* @pool: The pool this heap belongs to.
|
|
* @heap_gpu_va: The GPU address of the heap context.
|
|
* @chunk_gpu_va: The chunk VA to return.
|
|
*
|
|
* This function is used when a chunk allocated with panthor_heap_grow()
|
|
* couldn't be linked to the heap context through the FW interface because
|
|
* the group requesting the allocation was scheduled out in the meantime.
|
|
*/
|
|
int panthor_heap_return_chunk(struct panthor_heap_pool *pool,
|
|
u64 heap_gpu_va,
|
|
u64 chunk_gpu_va)
|
|
{
|
|
u64 offset = heap_gpu_va - panthor_kernel_bo_gpuva(pool->gpu_contexts);
|
|
u32 heap_id = (u32)offset / panthor_heap_ctx_stride(pool->ptdev);
|
|
struct panthor_heap_chunk *chunk, *tmp, *removed = NULL;
|
|
struct panthor_heap *heap;
|
|
int ret;
|
|
|
|
if (offset > U32_MAX || heap_id >= MAX_HEAPS_PER_POOL)
|
|
return -EINVAL;
|
|
|
|
down_read(&pool->lock);
|
|
heap = xa_load(&pool->xa, heap_id);
|
|
if (!heap) {
|
|
ret = -EINVAL;
|
|
goto out_unlock;
|
|
}
|
|
|
|
chunk_gpu_va &= GENMASK_ULL(63, 12);
|
|
|
|
mutex_lock(&heap->lock);
|
|
list_for_each_entry_safe(chunk, tmp, &heap->chunks, node) {
|
|
if (panthor_kernel_bo_gpuva(chunk->bo) == chunk_gpu_va) {
|
|
removed = chunk;
|
|
list_del(&chunk->node);
|
|
heap->chunk_count--;
|
|
break;
|
|
}
|
|
}
|
|
mutex_unlock(&heap->lock);
|
|
|
|
if (removed) {
|
|
panthor_kernel_bo_destroy(chunk->bo);
|
|
kfree(chunk);
|
|
ret = 0;
|
|
} else {
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
out_unlock:
|
|
up_read(&pool->lock);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* panthor_heap_grow() - Make a heap context grow.
|
|
* @pool: The pool this heap belongs to.
|
|
* @heap_gpu_va: The GPU address of the heap context.
|
|
* @renderpasses_in_flight: Number of render passes currently in-flight.
|
|
* @pending_frag_count: Number of fragment jobs waiting for execution/completion.
|
|
* @new_chunk_gpu_va: Pointer used to return the chunk VA.
|
|
*
|
|
* Return:
|
|
* - 0 if a new heap was allocated
|
|
* - -ENOMEM if the tiler context reached the maximum number of chunks
|
|
* or if too many render passes are in-flight
|
|
* or if the allocation failed
|
|
* - -EINVAL if any of the arguments passed to panthor_heap_grow() is invalid
|
|
*/
|
|
int panthor_heap_grow(struct panthor_heap_pool *pool,
|
|
u64 heap_gpu_va,
|
|
u32 renderpasses_in_flight,
|
|
u32 pending_frag_count,
|
|
u64 *new_chunk_gpu_va)
|
|
{
|
|
u64 offset = heap_gpu_va - panthor_kernel_bo_gpuva(pool->gpu_contexts);
|
|
u32 heap_id = (u32)offset / panthor_heap_ctx_stride(pool->ptdev);
|
|
struct panthor_heap_chunk *chunk;
|
|
struct panthor_heap *heap;
|
|
int ret;
|
|
|
|
if (offset > U32_MAX || heap_id >= MAX_HEAPS_PER_POOL)
|
|
return -EINVAL;
|
|
|
|
down_read(&pool->lock);
|
|
heap = xa_load(&pool->xa, heap_id);
|
|
if (!heap) {
|
|
ret = -EINVAL;
|
|
goto out_unlock;
|
|
}
|
|
|
|
/* If we reached the target in-flight render passes, or if we
|
|
* reached the maximum number of chunks, let the FW figure another way to
|
|
* find some memory (wait for render passes to finish, or call the exception
|
|
* handler provided by the userspace driver, if any).
|
|
*/
|
|
if (renderpasses_in_flight > heap->target_in_flight ||
|
|
heap->chunk_count >= heap->max_chunks) {
|
|
ret = -ENOMEM;
|
|
goto out_unlock;
|
|
}
|
|
|
|
/* FIXME: panthor_alloc_heap_chunk() triggers a kernel BO creation,
|
|
* which goes through the blocking allocation path. Ultimately, we
|
|
* want a non-blocking allocation, so we can immediately report to the
|
|
* FW when the system is running out of memory. In that case, the FW
|
|
* can call a user-provided exception handler, which might try to free
|
|
* some tiler memory by issuing an intermediate fragment job. If the
|
|
* exception handler can't do anything, it will flag the queue as
|
|
* faulty so the job that triggered this tiler chunk allocation and all
|
|
* further jobs in this queue fail immediately instead of having to
|
|
* wait for the job timeout.
|
|
*/
|
|
ret = panthor_alloc_heap_chunk(pool->ptdev, pool->vm, heap, false);
|
|
if (ret)
|
|
goto out_unlock;
|
|
|
|
chunk = list_first_entry(&heap->chunks,
|
|
struct panthor_heap_chunk,
|
|
node);
|
|
*new_chunk_gpu_va = (panthor_kernel_bo_gpuva(chunk->bo) & GENMASK_ULL(63, 12)) |
|
|
(heap->chunk_size >> 12);
|
|
ret = 0;
|
|
|
|
out_unlock:
|
|
up_read(&pool->lock);
|
|
return ret;
|
|
}
|
|
|
|
static void panthor_heap_pool_release(struct kref *refcount)
|
|
{
|
|
struct panthor_heap_pool *pool =
|
|
container_of(refcount, struct panthor_heap_pool, refcount);
|
|
|
|
xa_destroy(&pool->xa);
|
|
kfree(pool);
|
|
}
|
|
|
|
/**
|
|
* panthor_heap_pool_put() - Release a heap pool reference
|
|
* @pool: Pool to release the reference on. Can be NULL.
|
|
*/
|
|
void panthor_heap_pool_put(struct panthor_heap_pool *pool)
|
|
{
|
|
if (pool)
|
|
kref_put(&pool->refcount, panthor_heap_pool_release);
|
|
}
|
|
|
|
/**
|
|
* panthor_heap_pool_get() - Get a heap pool reference
|
|
* @pool: Pool to get the reference on. Can be NULL.
|
|
*
|
|
* Return: @pool.
|
|
*/
|
|
struct panthor_heap_pool *
|
|
panthor_heap_pool_get(struct panthor_heap_pool *pool)
|
|
{
|
|
if (pool)
|
|
kref_get(&pool->refcount);
|
|
|
|
return pool;
|
|
}
|
|
|
|
/**
|
|
* panthor_heap_pool_create() - Create a heap pool
|
|
* @ptdev: Device.
|
|
* @vm: The VM this heap pool will be attached to.
|
|
*
|
|
* Heap pools might contain up to 128 heap contexts, and are per-VM.
|
|
*
|
|
* Return: A valid pointer on success, a negative error code otherwise.
|
|
*/
|
|
struct panthor_heap_pool *
|
|
panthor_heap_pool_create(struct panthor_device *ptdev, struct panthor_vm *vm)
|
|
{
|
|
size_t bosize = ALIGN(MAX_HEAPS_PER_POOL *
|
|
panthor_heap_ctx_stride(ptdev),
|
|
4096);
|
|
struct panthor_heap_pool *pool;
|
|
int ret = 0;
|
|
|
|
pool = kzalloc(sizeof(*pool), GFP_KERNEL);
|
|
if (!pool)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
/* We want a weak ref here: the heap pool belongs to the VM, so we're
|
|
* sure that, as long as the heap pool exists, the VM exists too.
|
|
*/
|
|
pool->vm = vm;
|
|
pool->ptdev = ptdev;
|
|
init_rwsem(&pool->lock);
|
|
xa_init_flags(&pool->xa, XA_FLAGS_ALLOC);
|
|
kref_init(&pool->refcount);
|
|
|
|
pool->gpu_contexts = panthor_kernel_bo_create(ptdev, vm, bosize,
|
|
DRM_PANTHOR_BO_NO_MMAP,
|
|
DRM_PANTHOR_VM_BIND_OP_MAP_NOEXEC,
|
|
PANTHOR_VM_KERNEL_AUTO_VA);
|
|
if (IS_ERR(pool->gpu_contexts)) {
|
|
ret = PTR_ERR(pool->gpu_contexts);
|
|
goto err_destroy_pool;
|
|
}
|
|
|
|
ret = panthor_kernel_bo_vmap(pool->gpu_contexts);
|
|
if (ret)
|
|
goto err_destroy_pool;
|
|
|
|
return pool;
|
|
|
|
err_destroy_pool:
|
|
panthor_heap_pool_destroy(pool);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
/**
|
|
* panthor_heap_pool_destroy() - Destroy a heap pool.
|
|
* @pool: Pool to destroy.
|
|
*
|
|
* This function destroys all heap contexts and their resources. Thus
|
|
* preventing any use of the heap context or the chunk attached to them
|
|
* after that point.
|
|
*
|
|
* If the GPU still has access to some heap contexts, a fault should be
|
|
* triggered, which should flag the command stream groups using these
|
|
* context as faulty.
|
|
*
|
|
* The heap pool object is only released when all references to this pool
|
|
* are released.
|
|
*/
|
|
void panthor_heap_pool_destroy(struct panthor_heap_pool *pool)
|
|
{
|
|
struct panthor_heap *heap;
|
|
unsigned long i;
|
|
|
|
if (!pool)
|
|
return;
|
|
|
|
down_write(&pool->lock);
|
|
xa_for_each(&pool->xa, i, heap)
|
|
drm_WARN_ON(&pool->ptdev->base, panthor_heap_destroy_locked(pool, i));
|
|
|
|
if (!IS_ERR_OR_NULL(pool->gpu_contexts))
|
|
panthor_kernel_bo_destroy(pool->gpu_contexts);
|
|
|
|
/* Reflects the fact the pool has been destroyed. */
|
|
pool->vm = NULL;
|
|
up_write(&pool->lock);
|
|
|
|
panthor_heap_pool_put(pool);
|
|
}
|