/*
* Tegra Graphics Host Virtual Memory
*
* Copyright (c) 2014, 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 .
*/
#include
#include
#include
#include
#include "nvhost_vm.h"
#include "dev.h"
struct nvhost_vm {
struct platform_device *pdev;
struct kref kref; /* reference to this VM */
struct mutex mutex;
/* rb-tree of buffers mapped into this VM */
struct rb_root buffer_list;
/* count of application viewed buffers mapped into this VM */
unsigned int num_user_mapped_buffers;
};
struct nvhost_vm_buffer {
struct nvhost_vm *vm;
/* buffer attachment */
struct dma_buf *dmabuf;
struct dma_buf_attachment *attach;
struct sg_table *sgt;
/* context specific view to the buffer */
dma_addr_t addr;
size_t size;
struct kref kref; /* reference to this buffer */
/* bookkeeping */
unsigned int user_map_count; /* application view to the buffer */
unsigned int submit_map_count; /* hw view to this buffer */
struct rb_node node;
};
struct nvhost_vm_pin {
/* list of pinned buffers */
struct nvhost_vm_buffer **buffers;
unsigned int num_buffers;
};
static struct nvhost_vm_buffer *nvhost_vm_find_buffer(struct rb_root *root,
struct dma_buf *dmabuf)
{
struct rb_node *node = root->rb_node;
while (node) {
struct nvhost_vm_buffer *buffer =
container_of(node, struct nvhost_vm_buffer, node);
if (buffer->dmabuf == dmabuf)
return buffer;
else if (buffer->dmabuf > dmabuf)
node = node->rb_left;
else
node = node->rb_right;
}
return NULL;
}
static int insert_mapped_buffer(struct rb_root *root,
struct nvhost_vm_buffer *buffer)
{
struct rb_node **new_node = &(root->rb_node), *parent = NULL;
/* Figure out where to put new node */
while (*new_node) {
struct nvhost_vm_buffer *cmp_with =
container_of(*new_node, struct nvhost_vm_buffer,
node);
parent = *new_node;
if (cmp_with->dmabuf > buffer->dmabuf)
new_node = &((*new_node)->rb_left);
else if (cmp_with->dmabuf != buffer->dmabuf)
new_node = &((*new_node)->rb_right);
else
return -EINVAL; /* duplicate buffer not allowed */
}
/* Add new node and rebalance tree. */
rb_link_node(&buffer->node, parent, new_node);
rb_insert_color(&buffer->node, root);
return 0;
}
static void nvhost_vm_destroy_buffer_locked(struct nvhost_vm_buffer *buffer)
{
struct nvhost_vm *vm = buffer->vm;
dma_buf_unmap_attachment(buffer->attach, buffer->sgt,
DMA_BIDIRECTIONAL);
dma_buf_detach(buffer->dmabuf, buffer->attach);
dma_buf_put(buffer->dmabuf);
rb_erase(&buffer->node, &vm->buffer_list);
kfree(buffer);
buffer = NULL;
}
static void nvhost_vm_buffer_deinit_locked(struct kref *kref)
{
struct nvhost_vm_buffer *buffer =
container_of(kref, struct nvhost_vm_buffer, kref);
nvhost_vm_destroy_buffer_locked(buffer);
}
void nvhost_vm_buffer_put_locked(struct nvhost_vm_buffer *buffer)
{
kref_put(&buffer->kref, nvhost_vm_buffer_deinit_locked);
}
void nvhost_vm_buffer_get(struct nvhost_vm_buffer *buffer)
{
kref_get(&buffer->kref);
}
void nvhost_vm_unmap_dmabuf(struct nvhost_vm *vm, struct dma_buf *dmabuf)
{
struct nvhost_vm_buffer *buffer;
mutex_lock(&vm->mutex);
/* find the buffer */
buffer = nvhost_vm_find_buffer(&vm->buffer_list, dmabuf);
if (!buffer)
goto err_find_buffer;
/* check that the buffer is mapped by user */
if (!buffer->user_map_count)
goto err_dec_refcount;
/* handle bookkeeping */
buffer->user_map_count--;
if (!buffer->user_map_count)
vm->num_user_mapped_buffers--;
nvhost_vm_buffer_put_locked(buffer);
mutex_unlock(&vm->mutex);
return;
err_dec_refcount:
err_find_buffer:
mutex_unlock(&vm->mutex);
WARN(1, "dmabuf %p already unmapped", dmabuf);
}
int nvhost_vm_map_dmabuf(struct nvhost_vm *vm, struct dma_buf *dmabuf,
dma_addr_t *addr)
{
struct nvhost_vm_buffer *buffer;
int err;
mutex_lock(&vm->mutex);
/* avoid duplicate mappings */
buffer = nvhost_vm_find_buffer(&vm->buffer_list, dmabuf);
if (buffer) {
buffer->user_map_count++;
if (buffer->user_map_count == 1)
vm->num_user_mapped_buffers++;
nvhost_vm_buffer_get(buffer);
mutex_unlock(&vm->mutex);
*addr = buffer->addr;
return 0;
}
/* allocate room to hold buffer data */
buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
if (!buffer)
goto err_alloc_buffer;
get_dma_buf(dmabuf);
buffer->dmabuf = dmabuf;
buffer->vm = vm;
buffer->size = dmabuf->size;
buffer->user_map_count = 1;
kref_init(&buffer->kref);
/* attach buffer to host1x device */
buffer->attach = dma_buf_attach(dmabuf, &vm->pdev->dev);
if (IS_ERR(buffer->attach)) {
err = PTR_ERR(buffer->attach);
goto err_attach;
}
/* ..and map the attachment */
buffer->sgt = dma_buf_map_attachment(buffer->attach,
DMA_BIDIRECTIONAL);
if (IS_ERR(buffer->sgt)) {
err = PTR_ERR(buffer->sgt);
goto err_map_attachment;
}
/* get dma address */
buffer->addr = sg_dma_address(buffer->sgt->sgl);
/* handle physical addresses */
if (!buffer->addr)
buffer->addr = sg_phys(buffer->sgt->sgl);
/* add buffer to the buffer list to avoid duplicate mappings */
err = insert_mapped_buffer(&vm->buffer_list, buffer);
if (err) {
nvhost_err(&vm->pdev->dev, "failed to insert mapped buffer\n");
goto err_insert_buffer;
}
vm->num_user_mapped_buffers++;
mutex_unlock(&vm->mutex);
*addr = buffer->addr;
return 0;
err_insert_buffer:
dma_buf_unmap_attachment(buffer->attach,
buffer->sgt, DMA_BIDIRECTIONAL);
err_map_attachment:
dma_buf_detach(dmabuf, buffer->attach);
err_attach:
kfree(buffer);
err_alloc_buffer:
mutex_unlock(&vm->mutex);
return -ENOMEM;
}
void nvhost_vm_unpin_buffers(struct nvhost_vm *vm, struct nvhost_vm_pin *pin)
{
int i;
mutex_lock(&vm->mutex);
/* for each pinned buffer: */
for (i = 0; i < pin->num_buffers; i++) {
struct nvhost_vm_buffer *buffer = pin->buffers[i];
/* check the buffer state */
if (!buffer->submit_map_count) {
WARN(1, "inconsistent state while unpinning. leaking memory to avoid crash\n");
mutex_unlock(&vm->mutex);
return;
}
/* reduce number of submit maps */
buffer->submit_map_count--;
nvhost_vm_buffer_put_locked(buffer);
}
mutex_unlock(&vm->mutex);
kfree(pin->buffers);
kfree(pin);
}
struct nvhost_vm_pin *nvhost_vm_pin_buffers(struct nvhost_vm *vm)
{
struct nvhost_vm_pin *pin;
struct nvhost_vm_buffer **buffers, *buffer;
struct rb_node *node;
int i = 0;
/* allocate space for pin.. */
pin = kzalloc(sizeof(*pin), GFP_KERNEL);
if (!pin)
goto err_alloc_pin;
/* ..and buffers */
buffers = kzalloc(sizeof(*buffers) * vm->num_user_mapped_buffers,
GFP_KERNEL);
if (!buffers)
goto err_alloc_buffers;
mutex_lock(&vm->mutex);
/* go through all mapped buffers */
node = rb_first(&vm->buffer_list);
while (node) {
buffer = container_of(node, struct nvhost_vm_buffer, node);
/* ...that are visible in application view */
if (!buffer->user_map_count) {
node = rb_next(&buffer->node);
continue;
}
/* and add them to the list of submit buffers */
buffer->submit_map_count++;
nvhost_vm_buffer_get(buffer);
buffers[i] = buffer;
i++;
node = rb_next(&buffer->node);
}
/* store this data into pin */
pin->num_buffers = vm->num_user_mapped_buffers;
pin->buffers = buffers;
mutex_unlock(&vm->mutex);
return pin;
err_alloc_buffers:
kfree(pin);
err_alloc_pin:
return NULL;
}
static void nvhost_vm_deinit(struct kref *kref)
{
struct nvhost_vm *vm = container_of(kref, struct nvhost_vm, kref);
struct nvhost_vm_buffer *buffer;
struct rb_node *node;
mutex_lock(&vm->mutex);
/* go through all remaining buffers (if any) and free them here */
node = rb_first(&vm->buffer_list);
while (node) {
buffer = container_of(node, struct nvhost_vm_buffer, node);
nvhost_vm_destroy_buffer_locked(buffer);
node = rb_first(&vm->buffer_list);
}
mutex_unlock(&vm->mutex);
kfree(vm);
vm = NULL;
}
void nvhost_vm_put(struct nvhost_vm *vm)
{
kref_put(&vm->kref, nvhost_vm_deinit);
}
void nvhost_vm_get(struct nvhost_vm *vm)
{
kref_get(&vm->kref);
}
struct nvhost_vm *nvhost_vm_allocate(struct platform_device *pdev)
{
struct nvhost_vm *vm;
/* get room to keep vm */
vm = kzalloc(sizeof(*vm), GFP_KERNEL);
if (!vm)
goto err_alloc_vm;
vm->buffer_list = RB_ROOT;
mutex_init(&vm->mutex);
kref_init(&vm->kref);
vm->pdev = pdev;
return vm;
err_alloc_vm:
return NULL;
}