Files
linux-nvgpu/drivers/gpu/nvgpu/common/linux/ioctl_as.c
Alex Waterman 0c5d0c6a9e gpu: nvgpu: Begin reorganizing VM mapping/unmapping
Move vm_priv.h to <nvgpu/linux/vm.h> and rename nvgpu_vm_map()
to nvgpu_vm_map_linux(). Also remove a redundant unmap function
from the unmap path. These changes are the beginning of reworking
the nvgpu Linux mapping and unmapping code.

The rest of this patch is just the necessary changes to use the
new map function naming and the new path to the Linux vm header.

Patch Series Goal
-----------------

There's two major goals for this patch series. Note that these
goals are not achieved in this patch. There will be subsequent
patches.

  1.  Remove all last vestiges of Linux code from common/mm/vm.c
  2.  Implement map caching in the common/mm/vm.c code

To accomplish this firstly the VM mapping code needs to have the
struct nvgpu_mapped_buf data struct be completely Linux free. That
means implementing an abstraction for this to hold the Linux stuff
that mapped buffers carry about (SGT, dma_buf). This is why the
vm_priv.h code has been moved: it will need to be included by the
<nvgpu/vm.h> header so that the OS specific struct can be pulled
into struct nvgpu_mapped_buf.

Next renaming the nvgpu_vm_map() to nvgpu_vm_map_linux() is in
preparation for adding a new nvgpu_vm_map() that handles the
map caching with nvgpu_mapped_buf. The mapping code is fairly
straight forward: nvgpu_vm_map does OS generic stuff; each OS
then calls this function from an nvgpu_vm_map_<OS>() or the like
that does any OS specific adjustments/management.

Freeing buffers is much more tricky however. The maps are all
reference counted since userspace does not track buffers and
expects us to handle this instead. Ugh! Since there's ref-counts
the free code will require a callback into the OS specific code
since the OS specific code cannot free a buffer directly. THis
make's the path for freeing a buffer quite convoluted.

JIRA NVGPU-30
JIRA NVGPU-71

Change-Id: I5e0975f60663a0d6cf0a6bd90e099f51e02c2395
Signed-off-by: Alex Waterman <alexw@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/1578896
GVS: Gerrit_Virtual_Submit
Reviewed-by: David Martinez Nieto <dmartineznie@nvidia.com>
Reviewed-by: Terje Bergstrom <tbergstrom@nvidia.com>
2017-10-24 15:16:50 -07:00

388 lines
9.5 KiB
C

/*
* GK20A Address Spaces
*
* Copyright (c) 2011-2017, 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.
*/
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <trace/events/gk20a.h>
#include <uapi/linux/nvgpu.h>
#include <nvgpu/gmmu.h>
#include <nvgpu/vm_area.h>
#include <nvgpu/log2.h>
#include <nvgpu/linux/vm.h>
#include "gk20a/gk20a.h"
#include "gk20a/platform_gk20a.h"
#include "ioctl_as.h"
#include "os_linux.h"
static int gk20a_as_ioctl_bind_channel(
struct gk20a_as_share *as_share,
struct nvgpu_as_bind_channel_args *args)
{
int err = 0;
struct channel_gk20a *ch;
gk20a_dbg_fn("");
ch = gk20a_get_channel_from_file(args->channel_fd);
if (!ch)
return -EINVAL;
if (gk20a_channel_as_bound(ch)) {
err = -EINVAL;
goto out;
}
/* this will set channel_gk20a->vm */
err = ch->g->ops.mm.vm_bind_channel(as_share, ch);
out:
gk20a_channel_put(ch);
return err;
}
static int gk20a_as_ioctl_alloc_space(
struct gk20a_as_share *as_share,
struct nvgpu_as_alloc_space_args *args)
{
gk20a_dbg_fn("");
return nvgpu_vm_area_alloc(as_share->vm, args->pages, args->page_size,
&args->o_a.offset, args->flags);
}
static int gk20a_as_ioctl_free_space(
struct gk20a_as_share *as_share,
struct nvgpu_as_free_space_args *args)
{
gk20a_dbg_fn("");
return nvgpu_vm_area_free(as_share->vm, args->offset);
}
static int gk20a_as_ioctl_map_buffer_ex(
struct gk20a_as_share *as_share,
struct nvgpu_as_map_buffer_ex_args *args)
{
s16 compressible_kind;
s16 incompressible_kind;
gk20a_dbg_fn("");
if (args->flags & NVGPU_AS_MAP_BUFFER_FLAGS_DIRECT_KIND_CTRL) {
compressible_kind = args->compr_kind;
incompressible_kind = args->incompr_kind;
} else {
compressible_kind = args->kind;
incompressible_kind = NV_KIND_INVALID;
}
return nvgpu_vm_map_buffer(as_share->vm, args->dmabuf_fd,
&args->offset, args->flags,
compressible_kind, incompressible_kind,
args->buffer_offset,
args->mapping_size,
NULL);
}
static int gk20a_as_ioctl_map_buffer(
struct gk20a_as_share *as_share,
struct nvgpu_as_map_buffer_args *args)
{
gk20a_dbg_fn("");
return nvgpu_vm_map_buffer(as_share->vm, args->dmabuf_fd,
&args->o_a.offset,
args->flags, NV_KIND_DEFAULT,
NV_KIND_DEFAULT,
0, 0, NULL);
/* args->o_a.offset will be set if !err */
}
static int gk20a_as_ioctl_unmap_buffer(
struct gk20a_as_share *as_share,
struct nvgpu_as_unmap_buffer_args *args)
{
gk20a_dbg_fn("");
return nvgpu_vm_unmap_buffer(as_share->vm, args->offset, NULL);
}
static int gk20a_as_ioctl_map_buffer_batch(
struct gk20a_as_share *as_share,
struct nvgpu_as_map_buffer_batch_args *args)
{
struct gk20a *g = as_share->vm->mm->g;
u32 i;
int err = 0;
struct nvgpu_as_unmap_buffer_args __user *user_unmap_args =
(struct nvgpu_as_unmap_buffer_args __user *)(uintptr_t)
args->unmaps;
struct nvgpu_as_map_buffer_ex_args __user *user_map_args =
(struct nvgpu_as_map_buffer_ex_args __user *)(uintptr_t)
args->maps;
struct vm_gk20a_mapping_batch batch;
gk20a_dbg_fn("");
if (args->num_unmaps > g->gpu_characteristics.map_buffer_batch_limit ||
args->num_maps > g->gpu_characteristics.map_buffer_batch_limit)
return -EINVAL;
nvgpu_vm_mapping_batch_start(&batch);
for (i = 0; i < args->num_unmaps; ++i) {
struct nvgpu_as_unmap_buffer_args unmap_args;
if (copy_from_user(&unmap_args, &user_unmap_args[i],
sizeof(unmap_args))) {
err = -EFAULT;
break;
}
err = nvgpu_vm_unmap_buffer(as_share->vm, unmap_args.offset,
&batch);
if (err)
break;
}
if (err) {
nvgpu_vm_mapping_batch_finish(as_share->vm, &batch);
args->num_unmaps = i;
args->num_maps = 0;
return err;
}
for (i = 0; i < args->num_maps; ++i) {
s16 compressible_kind;
s16 incompressible_kind;
struct nvgpu_as_map_buffer_ex_args map_args;
memset(&map_args, 0, sizeof(map_args));
if (copy_from_user(&map_args, &user_map_args[i],
sizeof(map_args))) {
err = -EFAULT;
break;
}
if (map_args.flags &
NVGPU_AS_MAP_BUFFER_FLAGS_DIRECT_KIND_CTRL) {
compressible_kind = map_args.compr_kind;
incompressible_kind = map_args.incompr_kind;
} else {
compressible_kind = map_args.kind;
incompressible_kind = NV_KIND_INVALID;
}
err = nvgpu_vm_map_buffer(
as_share->vm, map_args.dmabuf_fd,
&map_args.offset, map_args.flags,
compressible_kind, incompressible_kind,
map_args.buffer_offset,
map_args.mapping_size,
&batch);
if (err)
break;
}
nvgpu_vm_mapping_batch_finish(as_share->vm, &batch);
if (err)
args->num_maps = i;
/* note: args->num_unmaps will be unmodified, which is ok
* since all unmaps are done */
return err;
}
static int gk20a_as_ioctl_get_va_regions(
struct gk20a_as_share *as_share,
struct nvgpu_as_get_va_regions_args *args)
{
unsigned int i;
unsigned int write_entries;
struct nvgpu_as_va_region __user *user_region_ptr;
struct vm_gk20a *vm = as_share->vm;
unsigned int page_sizes = gmmu_page_size_kernel;
gk20a_dbg_fn("");
if (!vm->big_pages)
page_sizes--;
write_entries = args->buf_size / sizeof(struct nvgpu_as_va_region);
if (write_entries > page_sizes)
write_entries = page_sizes;
user_region_ptr =
(struct nvgpu_as_va_region __user *)(uintptr_t)args->buf_addr;
for (i = 0; i < write_entries; ++i) {
struct nvgpu_as_va_region region;
struct nvgpu_allocator *vma = vm->vma[i];
memset(&region, 0, sizeof(struct nvgpu_as_va_region));
region.page_size = vm->gmmu_page_sizes[i];
region.offset = nvgpu_alloc_base(vma);
/* No __aeabi_uldivmod() on some platforms... */
region.pages = (nvgpu_alloc_end(vma) -
nvgpu_alloc_base(vma)) >> ilog2(region.page_size);
if (copy_to_user(user_region_ptr + i, &region, sizeof(region)))
return -EFAULT;
}
args->buf_size =
page_sizes * sizeof(struct nvgpu_as_va_region);
return 0;
}
int gk20a_as_dev_open(struct inode *inode, struct file *filp)
{
struct nvgpu_os_linux *l;
struct gk20a_as_share *as_share;
struct gk20a *g;
int err;
gk20a_dbg_fn("");
l = container_of(inode->i_cdev, struct nvgpu_os_linux, as_dev.cdev);
g = &l->g;
err = gk20a_as_alloc_share(g, 0, 0, &as_share);
if (err) {
gk20a_dbg_fn("failed to alloc share");
return err;
}
filp->private_data = as_share;
return 0;
}
int gk20a_as_dev_release(struct inode *inode, struct file *filp)
{
struct gk20a_as_share *as_share = filp->private_data;
gk20a_dbg_fn("");
if (!as_share)
return 0;
return gk20a_as_release_share(as_share);
}
long gk20a_as_dev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int err = 0;
struct gk20a_as_share *as_share = filp->private_data;
struct gk20a *g = gk20a_from_as(as_share->as);
u8 buf[NVGPU_AS_IOCTL_MAX_ARG_SIZE];
gk20a_dbg_fn("start %d", _IOC_NR(cmd));
if ((_IOC_TYPE(cmd) != NVGPU_AS_IOCTL_MAGIC) ||
(_IOC_NR(cmd) == 0) ||
(_IOC_NR(cmd) > NVGPU_AS_IOCTL_LAST) ||
(_IOC_SIZE(cmd) > NVGPU_AS_IOCTL_MAX_ARG_SIZE))
return -EINVAL;
memset(buf, 0, sizeof(buf));
if (_IOC_DIR(cmd) & _IOC_WRITE) {
if (copy_from_user(buf, (void __user *)arg, _IOC_SIZE(cmd)))
return -EFAULT;
}
err = gk20a_busy(g);
if (err)
return err;
switch (cmd) {
case NVGPU_AS_IOCTL_BIND_CHANNEL:
trace_gk20a_as_ioctl_bind_channel(g->name);
err = gk20a_as_ioctl_bind_channel(as_share,
(struct nvgpu_as_bind_channel_args *)buf);
break;
case NVGPU32_AS_IOCTL_ALLOC_SPACE:
{
struct nvgpu32_as_alloc_space_args *args32 =
(struct nvgpu32_as_alloc_space_args *)buf;
struct nvgpu_as_alloc_space_args args;
args.pages = args32->pages;
args.page_size = args32->page_size;
args.flags = args32->flags;
args.o_a.offset = args32->o_a.offset;
trace_gk20a_as_ioctl_alloc_space(g->name);
err = gk20a_as_ioctl_alloc_space(as_share, &args);
args32->o_a.offset = args.o_a.offset;
break;
}
case NVGPU_AS_IOCTL_ALLOC_SPACE:
trace_gk20a_as_ioctl_alloc_space(g->name);
err = gk20a_as_ioctl_alloc_space(as_share,
(struct nvgpu_as_alloc_space_args *)buf);
break;
case NVGPU_AS_IOCTL_FREE_SPACE:
trace_gk20a_as_ioctl_free_space(g->name);
err = gk20a_as_ioctl_free_space(as_share,
(struct nvgpu_as_free_space_args *)buf);
break;
case NVGPU_AS_IOCTL_MAP_BUFFER:
trace_gk20a_as_ioctl_map_buffer(g->name);
err = gk20a_as_ioctl_map_buffer(as_share,
(struct nvgpu_as_map_buffer_args *)buf);
break;
case NVGPU_AS_IOCTL_MAP_BUFFER_EX:
trace_gk20a_as_ioctl_map_buffer(g->name);
err = gk20a_as_ioctl_map_buffer_ex(as_share,
(struct nvgpu_as_map_buffer_ex_args *)buf);
break;
case NVGPU_AS_IOCTL_UNMAP_BUFFER:
trace_gk20a_as_ioctl_unmap_buffer(g->name);
err = gk20a_as_ioctl_unmap_buffer(as_share,
(struct nvgpu_as_unmap_buffer_args *)buf);
break;
case NVGPU_AS_IOCTL_GET_VA_REGIONS:
trace_gk20a_as_ioctl_get_va_regions(g->name);
err = gk20a_as_ioctl_get_va_regions(as_share,
(struct nvgpu_as_get_va_regions_args *)buf);
break;
case NVGPU_AS_IOCTL_MAP_BUFFER_BATCH:
err = gk20a_as_ioctl_map_buffer_batch(as_share,
(struct nvgpu_as_map_buffer_batch_args *)buf);
break;
default:
err = -ENOTTY;
break;
}
gk20a_idle(g);
if ((err == 0) && (_IOC_DIR(cmd) & _IOC_READ))
if (copy_to_user((void __user *)arg, buf, _IOC_SIZE(cmd)))
err = -EFAULT;
return err;
}