384 lines
8.8 KiB
C
384 lines
8.8 KiB
C
/*
|
|
* NVHOST buffer management for T194
|
|
*
|
|
* Copyright (c) 2016-2018, NVIDIA Corporation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <linux/platform_device.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/dma-buf.h>
|
|
#include <linux/cvnas.h>
|
|
|
|
#include "dev.h"
|
|
#include "nvhost_buffer.h"
|
|
|
|
/**
|
|
* nvhost_vm_buffer - Virtual mapping information for a buffer
|
|
*
|
|
* @attach: Pointer to dma_buf_attachment struct
|
|
* @dmabuf: Pointer to dma_buf struct
|
|
* @sgt: Pointer to sg_table struct
|
|
* @addr: Physical address of the buffer
|
|
* @size: Size of the buffer
|
|
* @user_map_count: Buffer reference count from user space
|
|
* @submit_map_count: Buffer reference count from task submit
|
|
* @rb_node: pinned buffer node
|
|
* @list_head: List entry
|
|
*
|
|
*/
|
|
struct nvhost_vm_buffer {
|
|
struct dma_buf_attachment *attach;
|
|
struct dma_buf *dmabuf;
|
|
struct sg_table *sgt;
|
|
|
|
dma_addr_t addr;
|
|
size_t size;
|
|
enum nvhost_buffers_heap heap;
|
|
|
|
s32 user_map_count;
|
|
s32 submit_map_count;
|
|
|
|
struct rb_node rb_node;
|
|
struct list_head list_head;
|
|
};
|
|
|
|
static struct nvhost_vm_buffer *nvhost_find_map_buffer(
|
|
struct nvhost_buffers *nvhost_buffers, struct dma_buf *dmabuf)
|
|
{
|
|
struct rb_root *root = &nvhost_buffers->rb_root;
|
|
struct rb_node *node = root->rb_node;
|
|
struct nvhost_vm_buffer *vm;
|
|
|
|
/* check in a sorted tree */
|
|
while (node) {
|
|
vm = rb_entry(node, struct nvhost_vm_buffer,
|
|
rb_node);
|
|
|
|
if (vm->dmabuf > dmabuf)
|
|
node = node->rb_left;
|
|
else if (vm->dmabuf != dmabuf)
|
|
node = node->rb_right;
|
|
else
|
|
return vm;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void nvhost_buffer_insert_map_buffer(
|
|
struct nvhost_buffers *nvhost_buffers,
|
|
struct nvhost_vm_buffer *new_vm)
|
|
{
|
|
struct rb_node **new_node = &(nvhost_buffers->rb_root.rb_node);
|
|
struct rb_node *parent = NULL;
|
|
|
|
/* Figure out where to put the new node */
|
|
while (*new_node) {
|
|
struct nvhost_vm_buffer *vm =
|
|
rb_entry(*new_node, struct nvhost_vm_buffer,
|
|
rb_node);
|
|
parent = *new_node;
|
|
|
|
if (vm->dmabuf > new_vm->dmabuf)
|
|
new_node = &((*new_node)->rb_left);
|
|
else
|
|
new_node = &((*new_node)->rb_right);
|
|
}
|
|
|
|
/* Add new node and rebalance tree */
|
|
rb_link_node(&new_vm->rb_node, parent, new_node);
|
|
rb_insert_color(&new_vm->rb_node, &nvhost_buffers->rb_root);
|
|
|
|
/* Add the node into a list */
|
|
list_add_tail(&new_vm->list_head, &nvhost_buffers->list_head);
|
|
}
|
|
|
|
int nvhost_get_iova_addr(struct nvhost_buffers *nvhost_buffers,
|
|
struct dma_buf *dmabuf, dma_addr_t *addr)
|
|
{
|
|
struct nvhost_vm_buffer *vm;
|
|
int err = -EINVAL;
|
|
|
|
mutex_lock(&nvhost_buffers->mutex);
|
|
|
|
vm = nvhost_find_map_buffer(nvhost_buffers, dmabuf);
|
|
if (vm) {
|
|
*addr = vm->addr;
|
|
err = 0;
|
|
}
|
|
|
|
mutex_unlock(&nvhost_buffers->mutex);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int nvhost_buffer_map(struct platform_device *pdev,
|
|
struct dma_buf *dmabuf,
|
|
struct nvhost_vm_buffer *vm)
|
|
{
|
|
|
|
const dma_addr_t cvnas_begin = nvcvnas_get_cvsram_base();
|
|
const dma_addr_t cvnas_end = cvnas_begin + nvcvnas_get_cvsram_size();
|
|
struct dma_buf_attachment *attach;
|
|
struct sg_table *sgt;
|
|
dma_addr_t dma_addr;
|
|
dma_addr_t phys_addr;
|
|
int err = 0;
|
|
|
|
get_dma_buf(dmabuf);
|
|
|
|
attach = dma_buf_attach(dmabuf, &pdev->dev);
|
|
if (IS_ERR_OR_NULL(attach)) {
|
|
err = PTR_ERR(dmabuf);
|
|
dev_err(&pdev->dev, "dma_attach failed: %d\n", err);
|
|
goto buf_attach_err;
|
|
}
|
|
|
|
sgt = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL);
|
|
if (IS_ERR_OR_NULL(sgt)) {
|
|
err = PTR_ERR(sgt);
|
|
dev_err(&pdev->dev, "dma mapping failed: %d\n", err);
|
|
goto buf_map_err;
|
|
}
|
|
|
|
phys_addr = sg_phys(sgt->sgl);
|
|
dma_addr = sg_dma_address(sgt->sgl);
|
|
|
|
/* Determine the heap */
|
|
if (phys_addr >= cvnas_begin && phys_addr < cvnas_end)
|
|
vm->heap = NVHOST_BUFFERS_HEAP_CVNAS;
|
|
else
|
|
vm->heap = NVHOST_BUFFERS_HEAP_DRAM;
|
|
|
|
/*
|
|
* If dma address is not available or heap is in CVNAS, use the
|
|
* physical address.
|
|
*/
|
|
if (!dma_addr || vm->heap == NVHOST_BUFFERS_HEAP_CVNAS)
|
|
dma_addr = phys_addr;
|
|
|
|
vm->sgt = sgt;
|
|
vm->attach = attach;
|
|
vm->dmabuf = dmabuf;
|
|
vm->size = dmabuf->size;
|
|
vm->addr = dma_addr;
|
|
vm->user_map_count = 1;
|
|
|
|
return err;
|
|
|
|
buf_map_err:
|
|
dma_buf_detach(dmabuf, attach);
|
|
buf_attach_err:
|
|
dma_buf_put(dmabuf);
|
|
return err;
|
|
}
|
|
|
|
static void nvhost_free_buffers(struct kref *kref)
|
|
{
|
|
struct nvhost_buffers *nvhost_buffers =
|
|
container_of(kref, struct nvhost_buffers, kref);
|
|
|
|
kfree(nvhost_buffers);
|
|
}
|
|
|
|
static void nvhost_buffer_unmap(struct nvhost_buffers *nvhost_buffers,
|
|
struct nvhost_vm_buffer *vm)
|
|
{
|
|
nvhost_dbg_fn("");
|
|
|
|
if ((vm->user_map_count != 0) || (vm->submit_map_count != 0))
|
|
return;
|
|
|
|
dma_buf_unmap_attachment(vm->attach, vm->sgt, DMA_BIDIRECTIONAL);
|
|
dma_buf_detach(vm->dmabuf, vm->attach);
|
|
dma_buf_put(vm->dmabuf);
|
|
|
|
rb_erase(&vm->rb_node, &nvhost_buffers->rb_root);
|
|
list_del(&vm->list_head);
|
|
|
|
kfree(vm);
|
|
}
|
|
|
|
struct nvhost_buffers *nvhost_buffer_init(struct platform_device *pdev)
|
|
{
|
|
struct nvhost_buffers *nvhost_buffers;
|
|
int err = 0;
|
|
|
|
nvhost_buffers = kzalloc(sizeof(struct nvhost_buffers), GFP_KERNEL);
|
|
if (!nvhost_buffers) {
|
|
err = -ENOMEM;
|
|
goto nvhost_buffer_init_err;
|
|
}
|
|
|
|
nvhost_buffers->pdev = pdev;
|
|
mutex_init(&nvhost_buffers->mutex);
|
|
nvhost_buffers->rb_root = RB_ROOT;
|
|
INIT_LIST_HEAD(&nvhost_buffers->list_head);
|
|
kref_init(&nvhost_buffers->kref);
|
|
|
|
return nvhost_buffers;
|
|
|
|
nvhost_buffer_init_err:
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
int nvhost_buffer_submit_pin(struct nvhost_buffers *nvhost_buffers,
|
|
struct dma_buf **dmabufs, u32 count,
|
|
dma_addr_t *paddr, size_t *psize,
|
|
enum nvhost_buffers_heap *heap)
|
|
{
|
|
struct nvhost_vm_buffer *vm;
|
|
int i = 0;
|
|
|
|
kref_get(&nvhost_buffers->kref);
|
|
|
|
mutex_lock(&nvhost_buffers->mutex);
|
|
|
|
for (i = 0; i < count; i++) {
|
|
vm = nvhost_find_map_buffer(nvhost_buffers, dmabufs[i]);
|
|
if (vm == NULL)
|
|
goto submit_err;
|
|
|
|
vm->submit_map_count++;
|
|
paddr[i] = vm->addr;
|
|
psize[i] = vm->size;
|
|
|
|
/* Return heap only if requested */
|
|
if (heap != NULL)
|
|
heap[i] = vm->heap;
|
|
}
|
|
|
|
mutex_unlock(&nvhost_buffers->mutex);
|
|
return 0;
|
|
|
|
submit_err:
|
|
mutex_unlock(&nvhost_buffers->mutex);
|
|
|
|
count = i;
|
|
|
|
nvhost_buffer_submit_unpin(nvhost_buffers, dmabufs, count);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
int nvhost_buffer_pin(struct nvhost_buffers *nvhost_buffers,
|
|
struct dma_buf **dmabufs,
|
|
u32 count)
|
|
{
|
|
struct nvhost_vm_buffer *vm;
|
|
int i = 0;
|
|
int err = 0;
|
|
|
|
mutex_lock(&nvhost_buffers->mutex);
|
|
|
|
for (i = 0; i < count; i++) {
|
|
vm = nvhost_find_map_buffer(nvhost_buffers, dmabufs[i]);
|
|
if (vm) {
|
|
vm->user_map_count++;
|
|
continue;
|
|
}
|
|
|
|
vm = kzalloc(sizeof(struct nvhost_vm_buffer), GFP_KERNEL);
|
|
if (!vm) {
|
|
nvhost_err(NULL, "could not allocate vm_buffer");
|
|
goto unpin;
|
|
}
|
|
|
|
err = nvhost_buffer_map(nvhost_buffers->pdev, dmabufs[i], vm);
|
|
if (err)
|
|
goto free_vm;
|
|
|
|
nvhost_buffer_insert_map_buffer(nvhost_buffers, vm);
|
|
}
|
|
|
|
mutex_unlock(&nvhost_buffers->mutex);
|
|
return err;
|
|
|
|
free_vm:
|
|
kfree(vm);
|
|
unpin:
|
|
mutex_unlock(&nvhost_buffers->mutex);
|
|
|
|
/* free pinned buffers */
|
|
count = i;
|
|
nvhost_buffer_unpin(nvhost_buffers, dmabufs, count);
|
|
|
|
return err;
|
|
}
|
|
|
|
void nvhost_buffer_submit_unpin(struct nvhost_buffers *nvhost_buffers,
|
|
struct dma_buf **dmabufs, u32 count)
|
|
{
|
|
struct nvhost_vm_buffer *vm;
|
|
int i = 0;
|
|
|
|
mutex_lock(&nvhost_buffers->mutex);
|
|
|
|
for (i = 0; i < count; i++) {
|
|
|
|
vm = nvhost_find_map_buffer(nvhost_buffers, dmabufs[i]);
|
|
if (vm == NULL)
|
|
continue;
|
|
|
|
if (vm->submit_map_count-- < 0)
|
|
vm->submit_map_count = 0;
|
|
nvhost_buffer_unmap(nvhost_buffers, vm);
|
|
}
|
|
|
|
mutex_unlock(&nvhost_buffers->mutex);
|
|
|
|
kref_put(&nvhost_buffers->kref, nvhost_free_buffers);
|
|
}
|
|
|
|
void nvhost_buffer_unpin(struct nvhost_buffers *nvhost_buffers,
|
|
struct dma_buf **dmabufs, u32 count)
|
|
{
|
|
int i = 0;
|
|
|
|
mutex_lock(&nvhost_buffers->mutex);
|
|
|
|
for (i = 0; i < count; i++) {
|
|
struct nvhost_vm_buffer *vm = NULL;
|
|
|
|
vm = nvhost_find_map_buffer(nvhost_buffers, dmabufs[i]);
|
|
if (vm == NULL)
|
|
continue;
|
|
|
|
if (vm->user_map_count-- < 0)
|
|
vm->user_map_count = 0;
|
|
nvhost_buffer_unmap(nvhost_buffers, vm);
|
|
}
|
|
|
|
mutex_unlock(&nvhost_buffers->mutex);
|
|
}
|
|
|
|
void nvhost_buffer_release(struct nvhost_buffers *nvhost_buffers)
|
|
{
|
|
struct nvhost_vm_buffer *vm, *n;
|
|
|
|
/* Go through each entry and remove it safely */
|
|
mutex_lock(&nvhost_buffers->mutex);
|
|
list_for_each_entry_safe(vm, n, &nvhost_buffers->list_head,
|
|
list_head) {
|
|
vm->user_map_count = 0;
|
|
nvhost_buffer_unmap(nvhost_buffers, vm);
|
|
}
|
|
mutex_unlock(&nvhost_buffers->mutex);
|
|
|
|
kref_put(&nvhost_buffers->kref, nvhost_free_buffers);
|
|
}
|