// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
|
/*
|
*
|
* (C) COPYRIGHT 2019-2021 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 license.
|
*
|
* This program is distributed in the hope that it will be useful,
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* GNU General Public License for more details.
|
*
|
* You should have received a copy of the GNU General Public License
|
* along with this program; if not, you can access it online at
|
* http://www.gnu.org/licenses/gpl-2.0.html.
|
*
|
*/
|
|
#include <tl/mali_kbase_tracepoints.h>
|
|
#include "mali_kbase_csf_tiler_heap.h"
|
#include "mali_kbase_csf_tiler_heap_def.h"
|
#include "mali_kbase_csf_heap_context_alloc.h"
|
|
/**
|
* encode_chunk_ptr - Encode the address and size of a chunk as an integer.
|
*
|
* The size and address of the next chunk in a list are packed into a single
|
* 64-bit value for storage in a chunk's header. This function returns that
|
* value.
|
*
|
* @chunk_size: Size of a tiler heap chunk, in bytes.
|
* @chunk_addr: GPU virtual address of the same tiler heap chunk.
|
*
|
* Return: Next chunk pointer suitable for writing into a chunk header.
|
*/
|
static u64 encode_chunk_ptr(u32 const chunk_size, u64 const chunk_addr)
|
{
|
u64 encoded_size, encoded_addr;
|
|
WARN_ON(chunk_size & ~CHUNK_SIZE_MASK);
|
WARN_ON(chunk_addr & ~CHUNK_ADDR_MASK);
|
|
encoded_size =
|
(u64)(chunk_size >> CHUNK_HDR_NEXT_SIZE_ENCODE_SHIFT) <<
|
CHUNK_HDR_NEXT_SIZE_POS;
|
|
encoded_addr =
|
(chunk_addr >> CHUNK_HDR_NEXT_ADDR_ENCODE_SHIFT) <<
|
CHUNK_HDR_NEXT_ADDR_POS;
|
|
return (encoded_size & CHUNK_HDR_NEXT_SIZE_MASK) |
|
(encoded_addr & CHUNK_HDR_NEXT_ADDR_MASK);
|
}
|
|
/**
|
* get_last_chunk - Get the last chunk of a tiler heap
|
*
|
* @heap: Pointer to the tiler heap.
|
*
|
* Return: The address of the most recently-linked chunk, or NULL if none.
|
*/
|
static struct kbase_csf_tiler_heap_chunk *get_last_chunk(
|
struct kbase_csf_tiler_heap *const heap)
|
{
|
lockdep_assert_held(&heap->kctx->csf.tiler_heaps.lock);
|
|
if (list_empty(&heap->chunks_list))
|
return NULL;
|
|
return list_last_entry(&heap->chunks_list,
|
struct kbase_csf_tiler_heap_chunk, link);
|
}
|
|
/**
|
* link_chunk - Link a chunk into a tiler heap
|
*
|
* Unless the @chunk is the first in the kernel's list of chunks belonging to
|
* a given tiler heap, this function stores the size and address of the @chunk
|
* in the header of the preceding chunk. This requires the GPU memory region
|
* containing the header to be be mapped temporarily, which can fail.
|
*
|
* @heap: Pointer to the tiler heap.
|
* @chunk: Pointer to the heap chunk to be linked.
|
*
|
* Return: 0 if successful or a negative error code on failure.
|
*/
|
static int link_chunk(struct kbase_csf_tiler_heap *const heap,
|
struct kbase_csf_tiler_heap_chunk *const chunk)
|
{
|
struct kbase_csf_tiler_heap_chunk *const prev = get_last_chunk(heap);
|
|
if (prev) {
|
struct kbase_context *const kctx = heap->kctx;
|
struct kbase_vmap_struct map;
|
u64 *const prev_hdr = kbase_vmap_prot(kctx, prev->gpu_va,
|
sizeof(*prev_hdr), KBASE_REG_CPU_WR, &map);
|
|
if (unlikely(!prev_hdr)) {
|
dev_err(kctx->kbdev->dev,
|
"Failed to map tiler heap chunk 0x%llX\n",
|
prev->gpu_va);
|
return -ENOMEM;
|
}
|
|
*prev_hdr = encode_chunk_ptr(heap->chunk_size, chunk->gpu_va);
|
kbase_vunmap(kctx, &map);
|
|
dev_dbg(kctx->kbdev->dev,
|
"Linked tiler heap chunks, 0x%llX -> 0x%llX\n",
|
prev->gpu_va, chunk->gpu_va);
|
}
|
|
return 0;
|
}
|
|
/**
|
* init_chunk - Initialize and link a tiler heap chunk
|
*
|
* Zero-initialize a new chunk's header (including its pointer to the next
|
* chunk, which doesn't exist yet) and then update the previous chunk's
|
* header to link the new chunk into the chunk list.
|
*
|
* @heap: Pointer to the tiler heap.
|
* @chunk: Pointer to the heap chunk to be initialized and linked.
|
* @link_with_prev: Flag to indicate if the new chunk needs to be linked with
|
* the previously allocated chunk.
|
*
|
* Return: 0 if successful or a negative error code on failure.
|
*/
|
static int init_chunk(struct kbase_csf_tiler_heap *const heap,
|
struct kbase_csf_tiler_heap_chunk *const chunk, bool link_with_prev)
|
{
|
struct kbase_vmap_struct map;
|
struct u64 *chunk_hdr = NULL;
|
struct kbase_context *const kctx = heap->kctx;
|
|
if (unlikely(chunk->gpu_va & ~CHUNK_ADDR_MASK)) {
|
dev_err(kctx->kbdev->dev,
|
"Tiler heap chunk address is unusable\n");
|
return -EINVAL;
|
}
|
|
chunk_hdr = kbase_vmap_prot(kctx,
|
chunk->gpu_va, CHUNK_HDR_SIZE, KBASE_REG_CPU_WR, &map);
|
|
if (unlikely(!chunk_hdr)) {
|
dev_err(kctx->kbdev->dev,
|
"Failed to map a tiler heap chunk header\n");
|
return -ENOMEM;
|
}
|
|
memset(chunk_hdr, 0, CHUNK_HDR_SIZE);
|
kbase_vunmap(kctx, &map);
|
|
if (link_with_prev)
|
return link_chunk(heap, chunk);
|
else
|
return 0;
|
}
|
|
/**
|
* create_chunk - Create a tiler heap chunk
|
*
|
* This function allocates a chunk of memory for a tiler heap and adds it to
|
* the end of the list of chunks associated with that heap. The size of the
|
* chunk is not a parameter because it is configured per-heap not per-chunk.
|
*
|
* @heap: Pointer to the tiler heap for which to allocate memory.
|
* @link_with_prev: Flag to indicate if the chunk to be allocated needs to be
|
* linked with the previously allocated chunk.
|
*
|
* Return: 0 if successful or a negative error code on failure.
|
*/
|
static int create_chunk(struct kbase_csf_tiler_heap *const heap,
|
bool link_with_prev)
|
{
|
int err = 0;
|
struct kbase_context *const kctx = heap->kctx;
|
u64 nr_pages = PFN_UP(heap->chunk_size);
|
u64 flags = BASE_MEM_PROT_GPU_RD | BASE_MEM_PROT_GPU_WR |
|
BASE_MEM_PROT_CPU_WR | BASEP_MEM_NO_USER_FREE |
|
BASE_MEM_COHERENT_LOCAL;
|
struct kbase_csf_tiler_heap_chunk *chunk = NULL;
|
|
flags |= base_mem_group_id_set(kctx->jit_group_id);
|
|
#if defined(CONFIG_MALI_BIFROST_DEBUG) || defined(CONFIG_MALI_VECTOR_DUMP)
|
flags |= BASE_MEM_PROT_CPU_RD;
|
#endif
|
|
lockdep_assert_held(&kctx->csf.tiler_heaps.lock);
|
|
chunk = kzalloc(sizeof(*chunk), GFP_KERNEL);
|
if (unlikely(!chunk)) {
|
dev_err(kctx->kbdev->dev,
|
"No kernel memory for a new tiler heap chunk\n");
|
return -ENOMEM;
|
}
|
|
/* Allocate GPU memory for the new chunk. */
|
INIT_LIST_HEAD(&chunk->link);
|
chunk->region = kbase_mem_alloc(kctx, nr_pages, nr_pages, 0,
|
&flags, &chunk->gpu_va);
|
|
if (unlikely(!chunk->region)) {
|
dev_err(kctx->kbdev->dev,
|
"Failed to allocate a tiler heap chunk\n");
|
err = -ENOMEM;
|
} else {
|
err = init_chunk(heap, chunk, link_with_prev);
|
if (unlikely(err)) {
|
kbase_gpu_vm_lock(kctx);
|
chunk->region->flags &= ~KBASE_REG_NO_USER_FREE;
|
kbase_mem_free_region(kctx, chunk->region);
|
kbase_gpu_vm_unlock(kctx);
|
}
|
}
|
|
if (unlikely(err)) {
|
kfree(chunk);
|
} else {
|
list_add_tail(&chunk->link, &heap->chunks_list);
|
heap->chunk_count++;
|
|
dev_dbg(kctx->kbdev->dev, "Created tiler heap chunk 0x%llX\n",
|
chunk->gpu_va);
|
}
|
|
return err;
|
}
|
|
/**
|
* delete_chunk - Delete a tiler heap chunk
|
*
|
* This function frees a tiler heap chunk previously allocated by @create_chunk
|
* and removes it from the list of chunks associated with the heap.
|
*
|
* WARNING: The deleted chunk is not unlinked from the list of chunks used by
|
* the GPU, therefore it is only safe to use this function when
|
* deleting a heap.
|
*
|
* @heap: Pointer to the tiler heap for which @chunk was allocated.
|
* @chunk: Pointer to a chunk to be deleted.
|
*/
|
static void delete_chunk(struct kbase_csf_tiler_heap *const heap,
|
struct kbase_csf_tiler_heap_chunk *const chunk)
|
{
|
struct kbase_context *const kctx = heap->kctx;
|
|
lockdep_assert_held(&kctx->csf.tiler_heaps.lock);
|
|
kbase_gpu_vm_lock(kctx);
|
chunk->region->flags &= ~KBASE_REG_NO_USER_FREE;
|
kbase_mem_free_region(kctx, chunk->region);
|
kbase_gpu_vm_unlock(kctx);
|
list_del(&chunk->link);
|
heap->chunk_count--;
|
kfree(chunk);
|
}
|
|
/**
|
* delete_all_chunks - Delete all chunks belonging to a tiler heap
|
*
|
* This function empties the list of chunks associated with a tiler heap by
|
* freeing all chunks previously allocated by @create_chunk.
|
*
|
* @heap: Pointer to a tiler heap.
|
*/
|
static void delete_all_chunks(struct kbase_csf_tiler_heap *heap)
|
{
|
struct list_head *entry = NULL, *tmp = NULL;
|
struct kbase_context *const kctx = heap->kctx;
|
|
lockdep_assert_held(&kctx->csf.tiler_heaps.lock);
|
|
list_for_each_safe(entry, tmp, &heap->chunks_list) {
|
struct kbase_csf_tiler_heap_chunk *chunk = list_entry(
|
entry, struct kbase_csf_tiler_heap_chunk, link);
|
|
delete_chunk(heap, chunk);
|
}
|
}
|
|
/**
|
* create_initial_chunks - Create the initial list of chunks for a tiler heap
|
*
|
* This function allocates a given number of chunks for a tiler heap and
|
* adds them to the list of chunks associated with that heap.
|
*
|
* @heap: Pointer to the tiler heap for which to allocate memory.
|
* @nchunks: Number of chunks to create.
|
*
|
* Return: 0 if successful or a negative error code on failure.
|
*/
|
static int create_initial_chunks(struct kbase_csf_tiler_heap *const heap,
|
u32 const nchunks)
|
{
|
int err = 0;
|
u32 i;
|
|
for (i = 0; (i < nchunks) && likely(!err); i++)
|
err = create_chunk(heap, true);
|
|
if (unlikely(err))
|
delete_all_chunks(heap);
|
|
return err;
|
}
|
|
/**
|
* delete_heap - Delete a tiler heap
|
*
|
* This function frees any chunks allocated for a tiler heap previously
|
* initialized by @kbase_csf_tiler_heap_init and removes it from the list of
|
* heaps associated with the kbase context. The heap context structure used by
|
* the firmware is also freed.
|
*
|
* @heap: Pointer to a tiler heap to be deleted.
|
*/
|
static void delete_heap(struct kbase_csf_tiler_heap *heap)
|
{
|
struct kbase_context *const kctx = heap->kctx;
|
|
dev_dbg(kctx->kbdev->dev, "Deleting tiler heap 0x%llX\n", heap->gpu_va);
|
|
lockdep_assert_held(&kctx->csf.tiler_heaps.lock);
|
|
delete_all_chunks(heap);
|
|
/* We could optimize context destruction by not freeing leaked heap
|
* contexts but it doesn't seem worth the extra complexity.
|
*/
|
kbase_csf_heap_context_allocator_free(&kctx->csf.tiler_heaps.ctx_alloc,
|
heap->gpu_va);
|
|
list_del(&heap->link);
|
|
WARN_ON(heap->chunk_count);
|
KBASE_TLSTREAM_AUX_TILER_HEAP_STATS(kctx->kbdev, kctx->id,
|
heap->heap_id, 0, 0, heap->max_chunks, heap->chunk_size, 0,
|
heap->target_in_flight, 0);
|
|
kfree(heap);
|
}
|
|
/**
|
* find_tiler_heap - Find a tiler heap from the address of its heap context
|
*
|
* Each tiler heap managed by the kernel has an associated heap context
|
* structure used by the firmware. This function finds a tiler heap object from
|
* the GPU virtual address of its associated heap context. The heap context
|
* should have been allocated by @kbase_csf_heap_context_allocator_alloc in the
|
* same @kctx.
|
*
|
* @kctx: Pointer to the kbase context to search for a tiler heap.
|
* @heap_gpu_va: GPU virtual address of a heap context structure.
|
*
|
* Return: pointer to the tiler heap object, or NULL if not found.
|
*/
|
static struct kbase_csf_tiler_heap *find_tiler_heap(
|
struct kbase_context *const kctx, u64 const heap_gpu_va)
|
{
|
struct kbase_csf_tiler_heap *heap = NULL;
|
|
lockdep_assert_held(&kctx->csf.tiler_heaps.lock);
|
|
list_for_each_entry(heap, &kctx->csf.tiler_heaps.list, link) {
|
if (heap_gpu_va == heap->gpu_va)
|
return heap;
|
}
|
|
dev_dbg(kctx->kbdev->dev, "Tiler heap 0x%llX was not found\n",
|
heap_gpu_va);
|
|
return NULL;
|
}
|
|
int kbase_csf_tiler_heap_context_init(struct kbase_context *const kctx)
|
{
|
int err = kbase_csf_heap_context_allocator_init(
|
&kctx->csf.tiler_heaps.ctx_alloc, kctx);
|
|
if (unlikely(err))
|
return err;
|
|
INIT_LIST_HEAD(&kctx->csf.tiler_heaps.list);
|
mutex_init(&kctx->csf.tiler_heaps.lock);
|
|
dev_dbg(kctx->kbdev->dev, "Initialized a context for tiler heaps\n");
|
|
return 0;
|
}
|
|
void kbase_csf_tiler_heap_context_term(struct kbase_context *const kctx)
|
{
|
struct list_head *entry = NULL, *tmp = NULL;
|
|
dev_dbg(kctx->kbdev->dev, "Terminating a context for tiler heaps\n");
|
|
mutex_lock(&kctx->csf.tiler_heaps.lock);
|
|
list_for_each_safe(entry, tmp, &kctx->csf.tiler_heaps.list) {
|
struct kbase_csf_tiler_heap *heap = list_entry(
|
entry, struct kbase_csf_tiler_heap, link);
|
delete_heap(heap);
|
}
|
|
mutex_unlock(&kctx->csf.tiler_heaps.lock);
|
mutex_destroy(&kctx->csf.tiler_heaps.lock);
|
|
kbase_csf_heap_context_allocator_term(&kctx->csf.tiler_heaps.ctx_alloc);
|
}
|
|
int kbase_csf_tiler_heap_init(struct kbase_context *const kctx,
|
u32 const chunk_size, u32 const initial_chunks, u32 const max_chunks,
|
u16 const target_in_flight, u64 *const heap_gpu_va,
|
u64 *const first_chunk_va)
|
{
|
int err = 0;
|
struct kbase_csf_tiler_heap *heap = NULL;
|
struct kbase_csf_heap_context_allocator *const ctx_alloc =
|
&kctx->csf.tiler_heaps.ctx_alloc;
|
|
dev_dbg(kctx->kbdev->dev,
|
"Creating a tiler heap with %u chunks (limit: %u) of size %u\n",
|
initial_chunks, max_chunks, chunk_size);
|
|
if (chunk_size == 0)
|
return -EINVAL;
|
|
if (chunk_size & ~CHUNK_SIZE_MASK)
|
return -EINVAL;
|
|
if (initial_chunks == 0)
|
return -EINVAL;
|
|
if (initial_chunks > max_chunks)
|
return -EINVAL;
|
|
if (target_in_flight == 0)
|
return -EINVAL;
|
|
heap = kzalloc(sizeof(*heap), GFP_KERNEL);
|
if (unlikely(!heap)) {
|
dev_err(kctx->kbdev->dev,
|
"No kernel memory for a new tiler heap\n");
|
return -ENOMEM;
|
}
|
|
heap->kctx = kctx;
|
heap->chunk_size = chunk_size;
|
heap->max_chunks = max_chunks;
|
heap->target_in_flight = target_in_flight;
|
INIT_LIST_HEAD(&heap->chunks_list);
|
|
heap->gpu_va = kbase_csf_heap_context_allocator_alloc(ctx_alloc);
|
|
mutex_lock(&kctx->csf.tiler_heaps.lock);
|
|
if (unlikely(!heap->gpu_va)) {
|
dev_err(kctx->kbdev->dev,
|
"Failed to allocate a tiler heap context\n");
|
err = -ENOMEM;
|
} else {
|
err = create_initial_chunks(heap, initial_chunks);
|
if (unlikely(err)) {
|
kbase_csf_heap_context_allocator_free(ctx_alloc,
|
heap->gpu_va);
|
}
|
}
|
|
if (unlikely(err)) {
|
kfree(heap);
|
} else {
|
struct kbase_csf_tiler_heap_chunk const *first_chunk =
|
list_first_entry(&heap->chunks_list,
|
struct kbase_csf_tiler_heap_chunk, link);
|
|
kctx->csf.tiler_heaps.nr_of_heaps++;
|
heap->heap_id = kctx->csf.tiler_heaps.nr_of_heaps;
|
list_add(&heap->link, &kctx->csf.tiler_heaps.list);
|
|
*heap_gpu_va = heap->gpu_va;
|
*first_chunk_va = first_chunk->gpu_va;
|
|
KBASE_TLSTREAM_AUX_TILER_HEAP_STATS(
|
kctx->kbdev, kctx->id, heap->heap_id,
|
PFN_UP(heap->chunk_size * heap->max_chunks),
|
PFN_UP(heap->chunk_size * heap->chunk_count),
|
heap->max_chunks, heap->chunk_size, heap->chunk_count,
|
heap->target_in_flight, 0);
|
|
dev_dbg(kctx->kbdev->dev, "Created tiler heap 0x%llX\n",
|
heap->gpu_va);
|
}
|
|
mutex_unlock(&kctx->csf.tiler_heaps.lock);
|
|
return err;
|
}
|
|
int kbase_csf_tiler_heap_term(struct kbase_context *const kctx,
|
u64 const heap_gpu_va)
|
{
|
int err = 0;
|
struct kbase_csf_tiler_heap *heap = NULL;
|
|
mutex_lock(&kctx->csf.tiler_heaps.lock);
|
|
heap = find_tiler_heap(kctx, heap_gpu_va);
|
if (likely(heap))
|
delete_heap(heap);
|
else
|
err = -EINVAL;
|
|
mutex_unlock(&kctx->csf.tiler_heaps.lock);
|
|
return err;
|
}
|
|
/**
|
* alloc_new_chunk - Allocate a new chunk for the tiler heap.
|
*
|
* This function will allocate a new chunk for the chunked tiler heap depending
|
* on the settings provided by userspace when the heap was created and the
|
* heap's statistics (like number of render passes in-flight).
|
*
|
* @heap: Pointer to the tiler heap.
|
* @nr_in_flight: Number of render passes that are in-flight, must not be zero.
|
* @pending_frag_count: Number of render passes in-flight with completed vertex/tiler stage.
|
* The minimum value is zero but it must be less or equal to
|
* the total number of render passes in flight
|
* @new_chunk_ptr: Where to store the GPU virtual address & size of the new
|
* chunk allocated for the heap.
|
*
|
* Return: 0 if a new chunk was allocated otherwise an appropriate negative
|
* error code.
|
*/
|
static int alloc_new_chunk(struct kbase_csf_tiler_heap *heap,
|
u32 nr_in_flight, u32 pending_frag_count, u64 *new_chunk_ptr)
|
{
|
int err = -ENOMEM;
|
|
lockdep_assert_held(&heap->kctx->csf.tiler_heaps.lock);
|
|
if (WARN_ON(!nr_in_flight) ||
|
WARN_ON(pending_frag_count > nr_in_flight))
|
return -EINVAL;
|
|
if (nr_in_flight <= heap->target_in_flight) {
|
if (heap->chunk_count < heap->max_chunks) {
|
/* Not exceeded the target number of render passes yet so be
|
* generous with memory.
|
*/
|
err = create_chunk(heap, false);
|
|
if (likely(!err)) {
|
struct kbase_csf_tiler_heap_chunk *new_chunk =
|
get_last_chunk(heap);
|
if (!WARN_ON(!new_chunk)) {
|
*new_chunk_ptr =
|
encode_chunk_ptr(heap->chunk_size,
|
new_chunk->gpu_va);
|
return 0;
|
}
|
}
|
} else if (pending_frag_count > 0) {
|
err = -EBUSY;
|
} else {
|
err = -ENOMEM;
|
}
|
} else {
|
/* Reached target number of render passes in flight.
|
* Wait for some of them to finish
|
*/
|
err = -EBUSY;
|
}
|
|
return err;
|
}
|
|
int kbase_csf_tiler_heap_alloc_new_chunk(struct kbase_context *kctx,
|
u64 gpu_heap_va, u32 nr_in_flight, u32 pending_frag_count, u64 *new_chunk_ptr)
|
{
|
struct kbase_csf_tiler_heap *heap;
|
int err = -EINVAL;
|
|
mutex_lock(&kctx->csf.tiler_heaps.lock);
|
|
heap = find_tiler_heap(kctx, gpu_heap_va);
|
|
if (likely(heap)) {
|
err = alloc_new_chunk(heap, nr_in_flight, pending_frag_count,
|
new_chunk_ptr);
|
|
KBASE_TLSTREAM_AUX_TILER_HEAP_STATS(
|
kctx->kbdev, kctx->id, heap->heap_id,
|
PFN_UP(heap->chunk_size * heap->max_chunks),
|
PFN_UP(heap->chunk_size * heap->chunk_count),
|
heap->max_chunks, heap->chunk_size, heap->chunk_count,
|
heap->target_in_flight, nr_in_flight);
|
}
|
|
mutex_unlock(&kctx->csf.tiler_heaps.lock);
|
|
return err;
|
}
|