/*
* Copyright (C) 2008 Ben Skeggs.
* All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
#include <subdev/fb.h>
#include "nouveau_drm.h"
#include "nouveau_dma.h"
#include "nouveau_fence.h"
#include "nouveau_abi16.h"
#include "nouveau_ttm.h"
#include "nouveau_gem.h"
void
nouveau_gem_object_del(struct drm_gem_object *gem)
{
struct nouveau_bo *nvbo = nouveau_gem_object(gem);
struct ttm_buffer_object *bo = &nvbo->bo;
if (gem->import_attach)
drm_prime_gem_destroy(gem, nvbo->bo.sg);
drm_gem_object_release(gem);
/* reset filp so nouveau_bo_del_ttm() can test for it */
gem->filp = NULL;
ttm_bo_unref(&bo);
}
int
nouveau_gem_object_open(struct drm_gem_object *gem, struct drm_file *file_priv)
{
struct nouveau_cli *cli = nouveau_cli(file_priv);
struct nouveau_bo *nvbo = nouveau_gem_object(gem);
struct nouveau_vma *vma;
int ret;
if (!cli->base.vm)
return 0;
ret = ttm_bo_reserve(&nvbo->bo, false, false, false, 0);
if (ret)
return ret;
vma = nouveau_bo_vma_find(nvbo, cli->base.vm);
if (!vma) {
vma = kzalloc(sizeof(*vma), GFP_KERNEL);
if (!vma) {
ret = -ENOMEM;
goto out;
}
ret = nouveau_bo_vma_add(nvbo, cli->base.vm, vma);
if (ret) {
kfree(vma);
goto out;
}
} else {
vma->refcount++;
}
out:
ttm_bo_unreserve(&nvbo->bo);
return ret;
}
static void
nouveau_gem_object_delete(void *data)
{
struct nouveau_vma *vma = data;
nouveau_vm_unmap(vma);
nouveau_vm_put(vma);
kfree(vma);
}
static void
nouveau_gem_object_unmap(struct nouveau_bo *nvbo, struct nouveau_vma *vma)
{
const bool mapped = nvbo->bo.mem.mem_type != TTM_PL_SYSTEM;
struct nouveau_fence *fence = NULL;
list_del(&vma->head);
if (mapped) {
spin_lock(&nvbo->bo.bdev->fence_lock);
fence = nouveau_fence_ref(nvbo->bo.sync_obj);
spin_unlock(&nvbo->bo.bdev->fence_lock);
}
if (fence) {
nouveau_fence_work(fence, nouveau_gem_object_delete, vma);
} else {
if (mapped)
nouveau_vm_unmap(vma);
nouveau_vm_put(vma);
kfree(vma);
}
nouveau_fence_unref(&fence);
}
void
nouveau_gem_object_close(struct drm_gem_object *gem, struct drm_file *file_priv)
{
struct nouveau_cli *cli = nouveau_cli(file_priv);
struct nouveau_bo *nvbo = nouveau_gem_object(gem);
struct nouveau_vma *vma;
int ret;
if (!cli->base.vm)
return;
ret = ttm_bo_reserve(&nvbo->bo, false, false, false, 0);
if (ret)
return;
vma = nouveau_bo_vma_find(nvbo, cli->base.vm);
if (vma) {
if (--vma->refcount == 0)
nouveau_gem_object_unmap(nvbo, vma);
}
ttm_bo_unreserve(&nvbo->bo);
}
int
nouveau_gem_new(struct drm_device *dev, int size, int align, uint32_t domain,
uint32_t tile_mode, uint32_t tile_flags,
struct nouveau_bo **pnvbo)
{
struct nouveau_drm *drm = nouveau_drm(dev);
struct nouveau_bo *nvbo;
u32 flags = 0;
int ret;
if (domain & NOUVEAU_GEM_DOMAIN_VRAM)
flags |= TTM_PL_FLAG_VRAM;
if (domain & NOUVEAU_GEM_DOMAIN_GART)
flags |= TTM_PL_FLAG_TT;
if (!flags || domain & NOUVEAU_GEM_DOMAIN_CPU)
flags |= TTM_PL_FLAG_SYSTEM;
ret = nouveau_bo_new(dev, size, align, flags, tile_mode,
tile_flags, NULL, pnvbo);
if (ret)
return ret;
nvbo = *pnvbo;
/* we restrict allowed domains on nv50+ to only the types
* that were requested at creation time. not possibly on
* earlier chips without busting the ABI.
*/
nvbo->valid_domains = NOUVEAU_GEM_DOMAIN_VRAM |
NOUVEAU_GEM_DOMAIN_GART;
if (nv_device(drm->device)->card_type >= NV_50)
nvbo->valid_domains &= domain;
/* Initialize the embedded gem-object. We return a single gem-reference
* to the caller, instead of a normal nouveau_bo ttm reference. */
ret = drm_gem_object_init(dev, &nvbo->gem, nvbo->bo.mem.size);
if (ret) {
nouveau_bo_ref(NULL, pnvbo);
return -ENOMEM;
}
nvbo->bo.persistent_swap_storage = nvbo->gem.filp;
return 0;
}
static int
nouveau_gem_info(struct drm_file *file_priv, struct drm_gem_object *gem,
struct drm_nouveau_gem_info *rep)
{
struct nouveau_cli *cli = nouveau_cli(file_priv);
struct nouveau_bo *nvbo = nouveau_gem_object(gem);
struct nouveau_vma *vma;
if (nvbo->bo.mem.mem_type == TTM_PL_TT)
rep->domain = NOUVEAU_GEM_DOMAIN_GART;
else
rep->domain = NOUVEAU_GEM_DOMAIN_VRAM;
rep->offset = nvbo->bo.offset;
if (cli->base.vm) {
vma = nouveau_bo_vma_find(nvbo, cli->base.vm);
if (!vma)
return -EINVAL;
rep->offset = vma->offset;
}
rep->size = nvbo->bo.mem.num_pages << PAGE_SHIFT;
rep->map_handle = drm_vma_node_offset_addr(&nvbo->bo.vma_node);
rep->tile_mode = nvbo->tile_mode;
rep->tile_flags = nvbo->tile_flags;
return 0;
}
int
nouveau_gem_ioctl_new(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct nouveau_drm *drm = nouveau_drm(dev);
struct nouveau_cli *cli = nouveau_cli(file_priv);
struct nouveau_fb *pfb = nouveau_fb(drm->device);
struct drm_nouveau_gem_new *req = data;
struct nouveau_bo *nvbo = NULL;
int ret = 0;
drm->ttm.bdev.dev_mapping = drm->dev->dev_mapping;
if (!pfb->memtype_valid(pfb, req->info.tile_flags)) {
NV_ERROR(cli, "bad page flags: 0x%08x\n", req->info.tile_flags);
return -EINVAL;
}
ret = nouveau_gem_new(dev, req->info.size, req->align,
req->info.domain, req->info.tile_mode,
req->info.tile_flags, &nvbo);
if (ret)
return ret;
ret = drm_gem_handle_create(file_priv, &nvbo->gem, &req->info.handle);
if (ret == 0) {
ret = nouveau_gem_info(file_priv, &nvbo->gem, &req->info);
if (ret)
drm_gem_handle_delete(file_priv, req->info.handle);
}
/* drop reference from allocate - handle holds it now */
drm_gem_object_unreference_unlocked(&nvbo->gem);
return ret;
}
static int
nouveau_gem_set_domain(struct drm_gem_object *gem, uint32_t read_domains,
uint32_t write_domains, uint32_t valid_domains)
{
struct nouveau_bo *nvbo = nouveau_gem_object(gem);
struct ttm_buffer_object *bo = &nvbo->bo;
uint32_t domains = valid_domains & nvbo->valid_domains &
(write_domains ? write_domains : read_domains);
uint32_t pref_flags = 0, valid_flags = 0;
if (!domains)
return -EINVAL;
if (valid_domains & NOUVEAU_GEM_DOMAIN_VRAM)
valid_flags |= TTM_PL_FLAG_VRAM;
if (valid_domains & NOUVEAU_GEM_DOMAIN_GART)
valid_flags |= TTM_PL_FLAG_TT;
if ((domains & NOUVEAU_GEM_DOMAIN_VRAM) &&
bo->mem.mem_type == TTM_PL_VRAM)
pref_flags |= TTM_PL_FLAG_VRAM;
else if ((domains & NOUVEAU_GEM_DOMAIN_GART) &&
bo->mem.mem_type == TTM_PL_TT)
pref_flags |= TTM_PL_FLAG_TT;
else if (domains & NOUVEAU_GEM_DOMAIN_VRAM)
pref_flags |= TTM_PL_FLAG_VRAM;
else
pref_flags |= TTM_PL_FLAG_TT;
nouveau_bo_placement_set(nvbo, pref_flags, valid_flags);
return 0;
}
struct validate_op {
struct list_head vram_list;
struct list_head gart_list;
struct list_head both_list;
struct ww_acquire_ctx ticket;
};
static void
validate_fini_list(struct list_head *list, struct nouveau_fence *fence,
struct ww_acquire_ctx *ticket)
{
struct list_head *entry, *tmp;
struct nouv