mirror of https://gitlab.freedesktop.org/mesa/mesa
429 lines
11 KiB
C
429 lines
11 KiB
C
#include "nouveau_bo.h"
|
|
|
|
#include "drm-uapi/nouveau_drm.h"
|
|
#include "util/hash_table.h"
|
|
#include "util/u_math.h"
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stddef.h>
|
|
#include <sys/mman.h>
|
|
#include <xf86drm.h>
|
|
|
|
#include "nvidia/classes/cl9097.h"
|
|
#include "nvidia/classes/clc597.h"
|
|
|
|
static void
|
|
bo_bind(struct nouveau_ws_device *dev,
|
|
uint32_t handle, uint64_t addr,
|
|
uint64_t range, uint64_t bo_offset,
|
|
uint32_t flags)
|
|
{
|
|
int ret;
|
|
|
|
struct drm_nouveau_vm_bind_op newbindop = {
|
|
.op = DRM_NOUVEAU_VM_BIND_OP_MAP,
|
|
.handle = handle,
|
|
.addr = addr,
|
|
.range = range,
|
|
.bo_offset = bo_offset,
|
|
.flags = flags,
|
|
};
|
|
struct drm_nouveau_vm_bind vmbind = {
|
|
.op_count = 1,
|
|
.op_ptr = (uint64_t)(uintptr_t)(void *)&newbindop,
|
|
};
|
|
ret = drmCommandWriteRead(dev->fd, DRM_NOUVEAU_VM_BIND, &vmbind, sizeof(vmbind));
|
|
if (ret)
|
|
fprintf(stderr, "vm bind failed %d\n", errno);
|
|
assert(ret == 0);
|
|
}
|
|
|
|
static void
|
|
bo_unbind(struct nouveau_ws_device *dev,
|
|
uint64_t offset, uint64_t range,
|
|
uint32_t flags)
|
|
{
|
|
struct drm_nouveau_vm_bind_op newbindop = {
|
|
.op = DRM_NOUVEAU_VM_BIND_OP_UNMAP,
|
|
.addr = offset,
|
|
.range = range,
|
|
.flags = flags,
|
|
};
|
|
struct drm_nouveau_vm_bind vmbind = {
|
|
.op_count = 1,
|
|
.op_ptr = (uint64_t)(uintptr_t)(void *)&newbindop,
|
|
};
|
|
ASSERTED int ret = drmCommandWriteRead(dev->fd, DRM_NOUVEAU_VM_BIND, &vmbind, sizeof(vmbind));
|
|
assert(ret == 0);
|
|
}
|
|
|
|
uint64_t
|
|
nouveau_ws_alloc_vma(struct nouveau_ws_device *dev,
|
|
uint64_t req_addr, uint64_t size, uint64_t align,
|
|
bool bda_capture_replay,
|
|
bool sparse_resident)
|
|
{
|
|
assert(dev->has_vm_bind);
|
|
|
|
/* if the caller doesn't care, use the GPU page size */
|
|
if (align == 0)
|
|
align = 0x1000;
|
|
|
|
uint64_t offset;
|
|
simple_mtx_lock(&dev->vma_mutex);
|
|
if (bda_capture_replay) {
|
|
if (req_addr != 0) {
|
|
bool found = util_vma_heap_alloc_addr(&dev->bda_heap, req_addr, size);
|
|
offset = found ? req_addr : 0;
|
|
} else {
|
|
offset = util_vma_heap_alloc(&dev->bda_heap, size, align);
|
|
}
|
|
} else {
|
|
offset = util_vma_heap_alloc(&dev->vma_heap, size, align);
|
|
}
|
|
simple_mtx_unlock(&dev->vma_mutex);
|
|
|
|
if (offset == 0) {
|
|
if (dev->debug_flags & NVK_DEBUG_VM) {
|
|
fprintf(stderr, "alloc vma FAILED: %" PRIx64 " sparse: %d\n",
|
|
size, sparse_resident);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (dev->debug_flags & NVK_DEBUG_VM)
|
|
fprintf(stderr, "alloc vma %" PRIx64 " %" PRIx64 " sparse: %d\n",
|
|
offset, size, sparse_resident);
|
|
|
|
if (sparse_resident)
|
|
bo_bind(dev, 0, offset, size, 0, DRM_NOUVEAU_VM_BIND_SPARSE);
|
|
|
|
return offset;
|
|
}
|
|
|
|
void
|
|
nouveau_ws_free_vma(struct nouveau_ws_device *dev,
|
|
uint64_t offset, uint64_t size,
|
|
bool bda_capture_replay,
|
|
bool sparse_resident)
|
|
{
|
|
assert(dev->has_vm_bind);
|
|
|
|
if (dev->debug_flags & NVK_DEBUG_VM)
|
|
fprintf(stderr, "free vma %" PRIx64 " %" PRIx64 "\n",
|
|
offset, size);
|
|
|
|
if (sparse_resident)
|
|
bo_unbind(dev, offset, size, DRM_NOUVEAU_VM_BIND_SPARSE);
|
|
|
|
simple_mtx_lock(&dev->vma_mutex);
|
|
if (bda_capture_replay) {
|
|
util_vma_heap_free(&dev->bda_heap, offset, size);
|
|
} else {
|
|
util_vma_heap_free(&dev->vma_heap, offset, size);
|
|
}
|
|
simple_mtx_unlock(&dev->vma_mutex);
|
|
}
|
|
|
|
void
|
|
nouveau_ws_bo_unbind_vma(struct nouveau_ws_device *dev,
|
|
uint64_t offset, uint64_t range)
|
|
{
|
|
assert(dev->has_vm_bind);
|
|
|
|
if (dev->debug_flags & NVK_DEBUG_VM)
|
|
fprintf(stderr, "unbind vma %" PRIx64 " %" PRIx64 "\n",
|
|
offset, range);
|
|
bo_unbind(dev, offset, range, 0);
|
|
}
|
|
|
|
void
|
|
nouveau_ws_bo_bind_vma(struct nouveau_ws_device *dev,
|
|
struct nouveau_ws_bo *bo,
|
|
uint64_t addr,
|
|
uint64_t range,
|
|
uint64_t bo_offset,
|
|
uint32_t pte_kind)
|
|
{
|
|
assert(dev->has_vm_bind);
|
|
|
|
if (dev->debug_flags & NVK_DEBUG_VM)
|
|
fprintf(stderr, "bind vma %x %" PRIx64 " %" PRIx64 " %" PRIx64 " %d\n",
|
|
bo->handle, addr, range, bo_offset, pte_kind);
|
|
bo_bind(dev, bo->handle, addr, range, bo_offset, pte_kind);
|
|
}
|
|
|
|
struct nouveau_ws_bo *
|
|
nouveau_ws_bo_new_mapped(struct nouveau_ws_device *dev,
|
|
uint64_t size, uint64_t align,
|
|
enum nouveau_ws_bo_flags flags,
|
|
enum nouveau_ws_bo_map_flags map_flags,
|
|
void **map_out)
|
|
{
|
|
struct nouveau_ws_bo *bo = nouveau_ws_bo_new(dev, size, align,
|
|
flags | NOUVEAU_WS_BO_MAP);
|
|
if (!bo)
|
|
return NULL;
|
|
|
|
void *map = nouveau_ws_bo_map(bo, map_flags, NULL);
|
|
if (map == NULL) {
|
|
nouveau_ws_bo_destroy(bo);
|
|
return NULL;
|
|
}
|
|
|
|
*map_out = map;
|
|
return bo;
|
|
}
|
|
|
|
static struct nouveau_ws_bo *
|
|
nouveau_ws_bo_new_tiled_locked(struct nouveau_ws_device *dev,
|
|
uint64_t size, uint64_t align,
|
|
uint8_t pte_kind, uint16_t tile_mode,
|
|
enum nouveau_ws_bo_flags flags)
|
|
{
|
|
struct drm_nouveau_gem_new req = {};
|
|
|
|
/* if the caller doesn't care, use the GPU page size */
|
|
if (align == 0)
|
|
align = 0x1000;
|
|
|
|
/* Align the size */
|
|
size = align64(size, align);
|
|
|
|
req.info.domain = 0;
|
|
|
|
/* It needs to live somewhere */
|
|
assert((flags & NOUVEAU_WS_BO_LOCAL) || (flags & NOUVEAU_WS_BO_GART));
|
|
|
|
if (flags & NOUVEAU_WS_BO_LOCAL)
|
|
req.info.domain |= dev->local_mem_domain;
|
|
|
|
if (flags & NOUVEAU_WS_BO_GART)
|
|
req.info.domain |= NOUVEAU_GEM_DOMAIN_GART;
|
|
|
|
/* TODO:
|
|
*
|
|
* VRAM maps on Kepler appear to be broken and we don't really know why.
|
|
* My NVIDIA contact doesn't remember them not working so they probably
|
|
* should but they don't today. Force everything that may be mapped to
|
|
* use GART for now.
|
|
*/
|
|
else if (dev->info.chipset < 0x110 && (flags & NOUVEAU_WS_BO_MAP))
|
|
req.info.domain |= NOUVEAU_GEM_DOMAIN_GART;
|
|
|
|
if (flags & NOUVEAU_WS_BO_MAP)
|
|
req.info.domain |= NOUVEAU_GEM_DOMAIN_MAPPABLE;
|
|
|
|
if (flags & NOUVEAU_WS_BO_NO_SHARE)
|
|
req.info.domain |= NOUVEAU_GEM_DOMAIN_NO_SHARE;
|
|
|
|
req.info.tile_flags = (uint32_t)pte_kind << 8;
|
|
req.info.tile_mode = tile_mode;
|
|
|
|
req.info.size = size;
|
|
req.align = align;
|
|
|
|
int ret = drmCommandWriteRead(dev->fd, DRM_NOUVEAU_GEM_NEW, &req, sizeof(req));
|
|
if (ret != 0)
|
|
return NULL;
|
|
|
|
struct nouveau_ws_bo *bo = CALLOC_STRUCT(nouveau_ws_bo);
|
|
bo->size = size;
|
|
bo->align = align;
|
|
bo->offset = -1ULL;
|
|
bo->handle = req.info.handle;
|
|
bo->map_handle = req.info.map_handle;
|
|
bo->dev = dev;
|
|
bo->flags = flags;
|
|
bo->refcnt = 1;
|
|
|
|
if (dev->has_vm_bind) {
|
|
bo->offset = nouveau_ws_alloc_vma(dev, 0, bo->size, align, false, false);
|
|
if (bo->offset == 0)
|
|
goto fail_gem_new;
|
|
|
|
nouveau_ws_bo_bind_vma(dev, bo, bo->offset, bo->size, 0, 0);
|
|
}
|
|
|
|
_mesa_hash_table_insert(dev->bos, (void *)(uintptr_t)bo->handle, bo);
|
|
|
|
return bo;
|
|
|
|
fail_gem_new:
|
|
drmCloseBufferHandle(dev->fd, req.info.handle);
|
|
FREE(bo);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct nouveau_ws_bo *
|
|
nouveau_ws_bo_new_tiled(struct nouveau_ws_device *dev,
|
|
uint64_t size, uint64_t align,
|
|
uint8_t pte_kind, uint16_t tile_mode,
|
|
enum nouveau_ws_bo_flags flags)
|
|
{
|
|
struct nouveau_ws_bo *bo;
|
|
|
|
simple_mtx_lock(&dev->bos_lock);
|
|
bo = nouveau_ws_bo_new_tiled_locked(dev, size, align,
|
|
pte_kind, tile_mode, flags);
|
|
simple_mtx_unlock(&dev->bos_lock);
|
|
|
|
return bo;
|
|
}
|
|
|
|
struct nouveau_ws_bo *
|
|
nouveau_ws_bo_new(struct nouveau_ws_device *dev,
|
|
uint64_t size, uint64_t align,
|
|
enum nouveau_ws_bo_flags flags)
|
|
{
|
|
return nouveau_ws_bo_new_tiled(dev, size, align, 0, 0, flags);
|
|
}
|
|
|
|
static struct nouveau_ws_bo *
|
|
nouveau_ws_bo_from_dma_buf_locked(struct nouveau_ws_device *dev, int fd)
|
|
{
|
|
uint32_t handle;
|
|
int ret = drmPrimeFDToHandle(dev->fd, fd, &handle);
|
|
if (ret != 0)
|
|
return NULL;
|
|
|
|
struct hash_entry *entry =
|
|
_mesa_hash_table_search(dev->bos, (void *)(uintptr_t)handle);
|
|
if (entry != NULL) {
|
|
struct nouveau_ws_bo *bo = entry->data;
|
|
nouveau_ws_bo_ref(bo);
|
|
return bo;
|
|
}
|
|
|
|
/*
|
|
* If we got here, no BO exists for the retrieved handle. If we error
|
|
* after this point, we need to close the handle.
|
|
*/
|
|
|
|
struct drm_nouveau_gem_info info = {
|
|
.handle = handle
|
|
};
|
|
ret = drmCommandWriteRead(dev->fd, DRM_NOUVEAU_GEM_INFO,
|
|
&info, sizeof(info));
|
|
if (ret != 0)
|
|
goto fail_fd_to_handle;
|
|
|
|
enum nouveau_ws_bo_flags flags = 0;
|
|
if (info.domain & dev->local_mem_domain)
|
|
flags |= NOUVEAU_WS_BO_LOCAL;
|
|
if (info.domain & NOUVEAU_GEM_DOMAIN_GART)
|
|
flags |= NOUVEAU_WS_BO_GART;
|
|
if (info.map_handle)
|
|
flags |= NOUVEAU_WS_BO_MAP;
|
|
|
|
struct nouveau_ws_bo *bo = CALLOC_STRUCT(nouveau_ws_bo);
|
|
bo->size = info.size;
|
|
bo->offset = info.offset;
|
|
bo->handle = info.handle;
|
|
bo->map_handle = info.map_handle;
|
|
bo->dev = dev;
|
|
bo->flags = flags;
|
|
bo->refcnt = 1;
|
|
|
|
uint64_t align = (1ULL << 12);
|
|
if (info.domain & NOUVEAU_GEM_DOMAIN_VRAM)
|
|
align = (1ULL << 16);
|
|
|
|
assert(bo->size == align64(bo->size, align));
|
|
|
|
bo->offset = nouveau_ws_alloc_vma(dev, 0, bo->size, align, false, false);
|
|
if (bo->offset == 0)
|
|
goto fail_calloc;
|
|
|
|
nouveau_ws_bo_bind_vma(dev, bo, bo->offset, bo->size, 0, 0);
|
|
_mesa_hash_table_insert(dev->bos, (void *)(uintptr_t)handle, bo);
|
|
|
|
return bo;
|
|
|
|
fail_calloc:
|
|
FREE(bo);
|
|
fail_fd_to_handle:
|
|
drmCloseBufferHandle(dev->fd, handle);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct nouveau_ws_bo *
|
|
nouveau_ws_bo_from_dma_buf(struct nouveau_ws_device *dev, int fd)
|
|
{
|
|
struct nouveau_ws_bo *bo;
|
|
|
|
simple_mtx_lock(&dev->bos_lock);
|
|
bo = nouveau_ws_bo_from_dma_buf_locked(dev, fd);
|
|
simple_mtx_unlock(&dev->bos_lock);
|
|
|
|
return bo;
|
|
}
|
|
|
|
void
|
|
nouveau_ws_bo_destroy(struct nouveau_ws_bo *bo)
|
|
{
|
|
if (--bo->refcnt)
|
|
return;
|
|
|
|
struct nouveau_ws_device *dev = bo->dev;
|
|
|
|
simple_mtx_lock(&dev->bos_lock);
|
|
|
|
_mesa_hash_table_remove_key(dev->bos, (void *)(uintptr_t)bo->handle);
|
|
|
|
if (dev->has_vm_bind) {
|
|
nouveau_ws_bo_unbind_vma(bo->dev, bo->offset, bo->size);
|
|
nouveau_ws_free_vma(bo->dev, bo->offset, bo->size, false, false);
|
|
}
|
|
|
|
drmCloseBufferHandle(bo->dev->fd, bo->handle);
|
|
FREE(bo);
|
|
|
|
simple_mtx_unlock(&dev->bos_lock);
|
|
}
|
|
|
|
void *
|
|
nouveau_ws_bo_map(struct nouveau_ws_bo *bo,
|
|
enum nouveau_ws_bo_map_flags flags,
|
|
void *fixed_addr)
|
|
{
|
|
int prot = 0, map_flags = 0;
|
|
|
|
if (flags & NOUVEAU_WS_BO_RD)
|
|
prot |= PROT_READ;
|
|
if (flags & NOUVEAU_WS_BO_WR)
|
|
prot |= PROT_WRITE;
|
|
|
|
map_flags = MAP_SHARED;
|
|
if (fixed_addr != NULL)
|
|
map_flags |= MAP_FIXED;
|
|
|
|
void *res = mmap(fixed_addr, bo->size, prot, map_flags,
|
|
bo->dev->fd, bo->map_handle);
|
|
if (res == MAP_FAILED)
|
|
return NULL;
|
|
|
|
return res;
|
|
}
|
|
|
|
bool
|
|
nouveau_ws_bo_wait(struct nouveau_ws_bo *bo, enum nouveau_ws_bo_map_flags flags)
|
|
{
|
|
struct drm_nouveau_gem_cpu_prep req = {};
|
|
|
|
req.handle = bo->handle;
|
|
if (flags & NOUVEAU_WS_BO_WR)
|
|
req.flags |= NOUVEAU_GEM_CPU_PREP_WRITE;
|
|
|
|
return !drmCommandWrite(bo->dev->fd, DRM_NOUVEAU_GEM_CPU_PREP, &req, sizeof(req));
|
|
}
|
|
|
|
int
|
|
nouveau_ws_bo_dma_buf(struct nouveau_ws_bo *bo, int *fd)
|
|
{
|
|
return drmPrimeHandleToFD(bo->dev->fd, bo->handle, DRM_CLOEXEC, fd);
|
|
}
|