Newer
Older
OUT_RING (chan, handle);
BEGIN_NV04(chan, NvSubCopy, 0x0180, 1);
OUT_RING (chan, chan->drm->ntfy.handle);
}
return ret;
}
static inline uint32_t
nouveau_bo_mem_ctxdma(struct ttm_buffer_object *bo,
struct nouveau_channel *chan, struct ttm_mem_reg *reg)
if (reg->mem_type == TTM_PL_TT)
return chan->vram.handle;
static int
nv04_bo_move_m2mf(struct nouveau_channel *chan, struct ttm_buffer_object *bo,
struct ttm_mem_reg *old_reg, struct ttm_mem_reg *new_reg)
u32 src_offset = old_reg->start << PAGE_SHIFT;
u32 dst_offset = new_reg->start << PAGE_SHIFT;
u32 page_count = new_reg->num_pages;
int ret;
ret = RING_SPACE(chan, 3);
if (ret)
return ret;
BEGIN_NV04(chan, NvSubCopy, NV_MEMORY_TO_MEMORY_FORMAT_DMA_SOURCE, 2);
OUT_RING (chan, nouveau_bo_mem_ctxdma(bo, chan, old_reg));
OUT_RING (chan, nouveau_bo_mem_ctxdma(bo, chan, new_reg));
page_count = new_reg->num_pages;
while (page_count) {
int line_count = (page_count > 2047) ? 2047 : page_count;
ret = RING_SPACE(chan, 11);
if (ret)
return ret;
BEGIN_NV04(chan, NvSubCopy,
NV_MEMORY_TO_MEMORY_FORMAT_OFFSET_IN, 8);
OUT_RING (chan, src_offset);
OUT_RING (chan, dst_offset);
OUT_RING (chan, PAGE_SIZE); /* src_pitch */
OUT_RING (chan, PAGE_SIZE); /* dst_pitch */
OUT_RING (chan, PAGE_SIZE); /* line_length */
OUT_RING (chan, line_count);
OUT_RING (chan, 0x00000101);
OUT_RING (chan, 0x00000000);
BEGIN_NV04(chan, NvSubCopy, NV_MEMORY_TO_MEMORY_FORMAT_NOP, 1);
page_count -= line_count;
src_offset += (PAGE_SIZE * line_count);
dst_offset += (PAGE_SIZE * line_count);
}
nouveau_bo_move_prep(struct nouveau_drm *drm, struct ttm_buffer_object *bo,
struct ttm_mem_reg *reg)
struct nouveau_mem *old_mem = nouveau_mem(&bo->mem);
struct nouveau_mem *new_mem = nouveau_mem(reg);
struct nvif_vmm *vmm = &drm->client.vmm.vmm;
int ret;
ret = nvif_vmm_get(vmm, LAZY, false, old_mem->mem.page, 0,
old_mem->mem.size, &old_mem->vma[0]);
if (ret)
return ret;
ret = nvif_vmm_get(vmm, LAZY, false, new_mem->mem.page, 0,
new_mem->mem.size, &old_mem->vma[1]);
if (ret)
goto done;
ret = nouveau_mem_map(old_mem, vmm, &old_mem->vma[0]);
if (ret)
goto done;
ret = nouveau_mem_map(new_mem, vmm, &old_mem->vma[1]);
done:
if (ret) {
nvif_vmm_put(vmm, &old_mem->vma[1]);
nvif_vmm_put(vmm, &old_mem->vma[0]);
return 0;
}
static int
nouveau_bo_move_m2mf(struct ttm_buffer_object *bo, int evict, bool intr,
bool no_wait_gpu, struct ttm_mem_reg *new_reg)
struct nouveau_drm *drm = nouveau_bdev(bo->bdev);
struct nouveau_channel *chan = drm->ttm.chan;
struct nouveau_cli *cli = (void *)chan->user.client;
struct nouveau_fence *fence;
/* create temporary vmas for the transfer and attach them to the
* old nvkm_mem node, these will get cleaned up after ttm has
* destroyed the ttm_mem_reg
if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_TESLA) {
ret = nouveau_bo_move_prep(drm, bo, new_reg);
return ret;
mutex_lock_nested(&cli->mutex, SINGLE_DEPTH_NESTING);

Maarten Lankhorst
committed
ret = nouveau_fence_sync(nouveau_bo(bo), chan, true, intr);
if (ret == 0) {
ret = drm->ttm.move(chan, bo, &bo->mem, new_reg);
if (ret == 0) {
ret = nouveau_fence_new(chan, false, &fence);
if (ret == 0) {
ret = ttm_bo_move_accel_cleanup(bo,
&fence->base,
evict,
nouveau_fence_unref(&fence);
}
}
mutex_unlock(&cli->mutex);
return ret;
nouveau_bo_move_init(struct nouveau_drm *drm)
{
static const struct {
const char *name;
s32 oclass;
int (*exec)(struct nouveau_channel *,
struct ttm_buffer_object *,
struct ttm_mem_reg *, struct ttm_mem_reg *);
int (*init)(struct nouveau_channel *, u32 handle);
} _methods[] = {
{ "COPY", 4, 0xc1b5, nve0_bo_move_copy, nve0_bo_move_init },
{ "GRCE", 0, 0xc1b5, nve0_bo_move_copy, nvc0_bo_move_init },
{ "COPY", 4, 0xc0b5, nve0_bo_move_copy, nve0_bo_move_init },
{ "GRCE", 0, 0xc0b5, nve0_bo_move_copy, nvc0_bo_move_init },
{ "COPY", 4, 0xb0b5, nve0_bo_move_copy, nve0_bo_move_init },
{ "GRCE", 0, 0xb0b5, nve0_bo_move_copy, nvc0_bo_move_init },
{ "COPY", 4, 0xa0b5, nve0_bo_move_copy, nve0_bo_move_init },
{ "GRCE", 0, 0xa0b5, nve0_bo_move_copy, nvc0_bo_move_init },
{ "COPY1", 5, 0x90b8, nvc0_bo_move_copy, nvc0_bo_move_init },
{ "COPY0", 4, 0x90b5, nvc0_bo_move_copy, nvc0_bo_move_init },
{ "COPY", 0, 0x85b5, nva3_bo_move_copy, nv50_bo_move_init },
{ "CRYPT", 0, 0x74c1, nv84_bo_move_exec, nv50_bo_move_init },
{ "M2MF", 0, 0x9039, nvc0_bo_move_m2mf, nvc0_bo_move_init },
{ "M2MF", 0, 0x5039, nv50_bo_move_m2mf, nv50_bo_move_init },
{ "M2MF", 0, 0x0039, nv04_bo_move_m2mf, nv04_bo_move_init },
{ "CRYPT", 0, 0x88b4, nv98_bo_move_exec, nv50_bo_move_init },
}, *mthd = _methods;
const char *name = "CPU";
int ret;
do {
struct nouveau_channel *chan;
if (mthd->engine)
chan = drm->cechan;
else
chan = drm->channel;
if (chan == NULL)
continue;
ret = nvif_object_init(&chan->user,
mthd->oclass | (mthd->engine << 16),
mthd->oclass, NULL, 0,
&drm->ttm.copy);
if (ret == 0) {
ret = mthd->init(chan, drm->ttm.copy.handle);
nvif_object_fini(&drm->ttm.copy);
drm->ttm.move = mthd->exec;
drm->ttm.chan = chan;
name = mthd->name;
break;
}
} while ((++mthd)->exec);
NV_INFO(drm, "MM: using %s for buffer copies\n", name);
static int
nouveau_bo_move_flipd(struct ttm_buffer_object *bo, bool evict, bool intr,
bool no_wait_gpu, struct ttm_mem_reg *new_reg)
struct ttm_place placement_memtype = {
.fpfn = 0,
.lpfn = 0,
.flags = TTM_PL_FLAG_TT | TTM_PL_MASK_CACHING
};
struct ttm_placement placement;
struct ttm_mem_reg tmp_reg;
int ret;
placement.num_placement = placement.num_busy_placement = 1;

Francisco Jerez
committed
placement.placement = placement.busy_placement = &placement_memtype;
tmp_reg = *new_reg;
tmp_reg.mm_node = NULL;
ret = ttm_bo_mem_space(bo, &placement, &tmp_reg, intr, no_wait_gpu);
if (ret)
return ret;
ret = ttm_tt_bind(bo->ttm, &tmp_reg);
ret = nouveau_bo_move_m2mf(bo, true, intr, no_wait_gpu, &tmp_reg);
ret = ttm_bo_move_ttm(bo, intr, no_wait_gpu, new_reg);
ttm_bo_mem_put(bo, &tmp_reg);
return ret;
}
static int
nouveau_bo_move_flips(struct ttm_buffer_object *bo, bool evict, bool intr,
bool no_wait_gpu, struct ttm_mem_reg *new_reg)
struct ttm_place placement_memtype = {
.fpfn = 0,
.lpfn = 0,
.flags = TTM_PL_FLAG_TT | TTM_PL_MASK_CACHING
};
struct ttm_placement placement;
struct ttm_mem_reg tmp_reg;
int ret;
placement.num_placement = placement.num_busy_placement = 1;

Francisco Jerez
committed
placement.placement = placement.busy_placement = &placement_memtype;
tmp_reg = *new_reg;
tmp_reg.mm_node = NULL;
ret = ttm_bo_mem_space(bo, &placement, &tmp_reg, intr, no_wait_gpu);
if (ret)
return ret;
ret = ttm_bo_move_ttm(bo, intr, no_wait_gpu, &tmp_reg);
ret = nouveau_bo_move_m2mf(bo, true, intr, no_wait_gpu, new_reg);
if (ret)
goto out;
out:
ttm_bo_mem_put(bo, &tmp_reg);
nouveau_bo_move_ntfy(struct ttm_buffer_object *bo, bool evict,
struct ttm_mem_reg *new_reg)
struct nouveau_mem *mem = new_reg ? nouveau_mem(new_reg) : NULL;
struct nouveau_bo *nvbo = nouveau_bo(bo);
struct nouveau_vma *vma;
/* ttm can now (stupidly) pass the driver bos it didn't create... */
if (bo->destroy != nouveau_bo_del_ttm)
return;
if (mem && new_reg->mem_type != TTM_PL_SYSTEM &&
mem->mem.page == nvbo->page) {
list_for_each_entry(vma, &nvbo->vma_list, head) {
nouveau_vma_map(vma, mem);
}
} else {
list_for_each_entry(vma, &nvbo->vma_list, head) {
WARN_ON(ttm_bo_wait(bo, false, false));
nouveau_vma_unmap(vma);
nouveau_bo_vm_bind(struct ttm_buffer_object *bo, struct ttm_mem_reg *new_reg,
struct nouveau_drm_tile **new_tile)
struct nouveau_drm *drm = nouveau_bdev(bo->bdev);
struct drm_device *dev = drm->dev;
struct nouveau_bo *nvbo = nouveau_bo(bo);
u64 offset = new_reg->start << PAGE_SHIFT;
if (new_reg->mem_type != TTM_PL_VRAM)
if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_CELSIUS) {
*new_tile = nv10_bo_set_tiling(dev, offset, new_reg->size,
nvbo->mode, nvbo->zeta);
return 0;
}
static void
nouveau_bo_vm_cleanup(struct ttm_buffer_object *bo,
struct nouveau_drm_tile *new_tile,
struct nouveau_drm_tile **old_tile)
struct nouveau_drm *drm = nouveau_bdev(bo->bdev);
struct drm_device *dev = drm->dev;
struct dma_fence *fence = reservation_object_get_excl(bo->resv);
nv10_bo_put_tile_region(dev, *old_tile, fence);
*old_tile = new_tile;
}
static int
nouveau_bo_move(struct ttm_buffer_object *bo, bool evict, bool intr,
bool no_wait_gpu, struct ttm_mem_reg *new_reg)
struct nouveau_drm *drm = nouveau_bdev(bo->bdev);
struct nouveau_bo *nvbo = nouveau_bo(bo);
struct ttm_mem_reg *old_reg = &bo->mem;
struct nouveau_drm_tile *new_tile = NULL;
ret = ttm_bo_wait(bo, intr, no_wait_gpu);
if (ret)
return ret;
if (nvbo->pin_refcnt)
NV_WARN(drm, "Moving pinned object %p!\n", nvbo);
if (drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA) {
ret = nouveau_bo_vm_bind(bo, new_reg, &new_tile);
if (ret)
return ret;
}
/* Fake bo copy. */
if (old_reg->mem_type == TTM_PL_SYSTEM && !bo->ttm) {
BUG_ON(bo->mem.mm_node != NULL);
bo->mem = *new_reg;
new_reg->mm_node = NULL;
/* Hardware assisted copy. */
if (drm->ttm.move) {
if (new_reg->mem_type == TTM_PL_SYSTEM)
ret = nouveau_bo_move_flipd(bo, evict, intr,
no_wait_gpu, new_reg);
else if (old_reg->mem_type == TTM_PL_SYSTEM)
ret = nouveau_bo_move_flips(bo, evict, intr,
no_wait_gpu, new_reg);
else
ret = nouveau_bo_move_m2mf(bo, evict, intr,
no_wait_gpu, new_reg);
if (!ret)
goto out;
}
/* Fallback to software copy. */
ret = ttm_bo_wait(bo, intr, no_wait_gpu);
if (ret == 0)
ret = ttm_bo_move_memcpy(bo, intr, no_wait_gpu, new_reg);
if (drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA) {
if (ret)
nouveau_bo_vm_cleanup(bo, NULL, &new_tile);
else
nouveau_bo_vm_cleanup(bo, new_tile, &nvbo->tile);
}
}
static int
nouveau_bo_verify_access(struct ttm_buffer_object *bo, struct file *filp)
{
struct nouveau_bo *nvbo = nouveau_bo(bo);
return drm_vma_node_verify_access(&nvbo->gem.vma_node,
filp->private_data);
nouveau_ttm_io_mem_reserve(struct ttm_bo_device *bdev, struct ttm_mem_reg *reg)
struct ttm_mem_type_manager *man = &bdev->man[reg->mem_type];
struct nouveau_drm *drm = nouveau_bdev(bdev);
struct nvkm_device *device = nvxx_device(&drm->client.device);
struct nouveau_mem *mem = nouveau_mem(reg);
reg->bus.addr = NULL;
reg->bus.offset = 0;
reg->bus.size = reg->num_pages << PAGE_SHIFT;
reg->bus.base = 0;
reg->bus.is_iomem = false;
if (!(man->flags & TTM_MEMTYPE_FLAG_MAPPABLE))
return -EINVAL;
switch (reg->mem_type) {
case TTM_PL_SYSTEM:
/* System memory */
return 0;
case TTM_PL_TT:
if (drm->agp.bridge) {
reg->bus.offset = reg->start << PAGE_SHIFT;
reg->bus.base = drm->agp.base;
reg->bus.is_iomem = !drm->agp.cma;
}
#endif
if (drm->client.mem->oclass < NVIF_CLASS_MEM_NV50 || !mem->kind)
/* untiled */
break;
/* fallthrough, tiled memory */
case TTM_PL_VRAM:
reg->bus.offset = reg->start << PAGE_SHIFT;
reg->bus.base = device->func->resource_addr(device, 1);
reg->bus.is_iomem = true;
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
if (drm->client.mem->oclass >= NVIF_CLASS_MEM_NV50) {
union {
struct nv50_mem_map_v0 nv50;
struct gf100_mem_map_v0 gf100;
} args;
u64 handle, length;
u32 argc = 0;
int ret;
switch (mem->mem.object.oclass) {
case NVIF_CLASS_MEM_NV50:
args.nv50.version = 0;
args.nv50.ro = 0;
args.nv50.kind = mem->kind;
args.nv50.comp = mem->comp;
break;
case NVIF_CLASS_MEM_GF100:
args.gf100.version = 0;
args.gf100.ro = 0;
args.gf100.kind = mem->kind;
break;
default:
WARN_ON(1);
break;
}
ret = nvif_object_map_handle(&mem->mem.object,
&argc, argc,
&handle, &length);
if (ret != 1)
return ret ? ret : -EINVAL;
reg->bus.base = 0;
reg->bus.offset = handle;
break;
default:
return -EINVAL;
}
return 0;
}
static void
nouveau_ttm_io_mem_free(struct ttm_bo_device *bdev, struct ttm_mem_reg *reg)
struct nouveau_drm *drm = nouveau_bdev(bdev);
struct nouveau_mem *mem = nouveau_mem(reg);
if (drm->client.mem->oclass >= NVIF_CLASS_MEM_NV50) {
switch (reg->mem_type) {
case TTM_PL_TT:
if (mem->kind)
nvif_object_unmap_handle(&mem->mem.object);
break;
case TTM_PL_VRAM:
nvif_object_unmap_handle(&mem->mem.object);
break;
default:
break;
}
}
}
static int
nouveau_ttm_fault_reserve_notify(struct ttm_buffer_object *bo)
{
struct nouveau_drm *drm = nouveau_bdev(bo->bdev);
struct nouveau_bo *nvbo = nouveau_bo(bo);
struct nvkm_device *device = nvxx_device(&drm->client.device);
u32 mappable = device->func->resource_size(device, 1) >> PAGE_SHIFT;
/* as long as the bo isn't in vram, and isn't tiled, we've got
* nothing to do here.
*/
if (bo->mem.mem_type != TTM_PL_VRAM) {
if (drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA ||
!nvbo->kind)
if (bo->mem.mem_type == TTM_PL_SYSTEM) {
nouveau_bo_placement_set(nvbo, TTM_PL_TT, 0);
ret = nouveau_bo_validate(nvbo, false, false);
if (ret)
return ret;
}
return 0;
}
/* make sure bo is in mappable vram */
if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_TESLA ||
bo->mem.start + bo->mem.num_pages < mappable)
for (i = 0; i < nvbo->placement.num_placement; ++i) {
nvbo->placements[i].fpfn = 0;
nvbo->placements[i].lpfn = mappable;
}
for (i = 0; i < nvbo->placement.num_busy_placement; ++i) {
nvbo->busy_placements[i].fpfn = 0;
nvbo->busy_placements[i].lpfn = mappable;
}
nouveau_bo_placement_set(nvbo, TTM_PL_FLAG_VRAM, 0);
return nouveau_bo_validate(nvbo, false, false);
static int
nouveau_ttm_tt_populate(struct ttm_tt *ttm)
{
struct ttm_dma_tt *ttm_dma = (void *)ttm;
struct nouveau_drm *drm;
struct device *dev;
unsigned i;
int r;
bool slave = !!(ttm->page_flags & TTM_PAGE_FLAG_SG);
if (ttm->state != tt_unpopulated)
return 0;
if (slave && ttm->sg) {
/* make userspace faulting work */
drm_prime_sg_to_page_addr_arrays(ttm->sg, ttm->pages,
ttm_dma->dma_address, ttm->num_pages);
ttm->state = tt_unbound;
return 0;
}
drm = nouveau_bdev(ttm->bdev);
dev = drm->dev->dev;
if (drm->agp.bridge) {
return ttm_agp_tt_populate(ttm);
}
#endif
#if IS_ENABLED(CONFIG_SWIOTLB) && IS_ENABLED(CONFIG_X86)
if (swiotlb_nr_tbl()) {
return ttm_dma_populate((void *)ttm, dev);
}
#endif
r = ttm_pool_populate(ttm);
if (r) {
return r;
}
for (i = 0; i < ttm->num_pages; i++) {
addr = dma_map_page(dev, ttm->pages[i], 0, PAGE_SIZE,
if (dma_mapping_error(dev, addr)) {
dma_unmap_page(dev, ttm_dma->dma_address[i],
PAGE_SIZE, DMA_BIDIRECTIONAL);
}
ttm_pool_unpopulate(ttm);
return -EFAULT;
}
ttm_dma->dma_address[i] = addr;
}
return 0;
}
static void
nouveau_ttm_tt_unpopulate(struct ttm_tt *ttm)
{
struct ttm_dma_tt *ttm_dma = (void *)ttm;
struct nouveau_drm *drm;
struct device *dev;
unsigned i;
bool slave = !!(ttm->page_flags & TTM_PAGE_FLAG_SG);
if (slave)
return;
drm = nouveau_bdev(ttm->bdev);
dev = drm->dev->dev;
if (drm->agp.bridge) {
ttm_agp_tt_unpopulate(ttm);
return;
}
#endif
#if IS_ENABLED(CONFIG_SWIOTLB) && IS_ENABLED(CONFIG_X86)
if (swiotlb_nr_tbl()) {
ttm_dma_unpopulate((void *)ttm, dev);
return;
}
#endif
for (i = 0; i < ttm->num_pages; i++) {
if (ttm_dma->dma_address[i]) {
dma_unmap_page(dev, ttm_dma->dma_address[i], PAGE_SIZE,
}
}
ttm_pool_unpopulate(ttm);
}
void
nouveau_bo_fence(struct nouveau_bo *nvbo, struct nouveau_fence *fence, bool exclusive)
{
struct reservation_object *resv = nvbo->bo.resv;

Maarten Lankhorst
committed
if (exclusive)
reservation_object_add_excl_fence(resv, &fence->base);
else if (fence)
reservation_object_add_shared_fence(resv, &fence->base);
}
struct ttm_bo_driver nouveau_bo_driver = {
.ttm_tt_create = &nouveau_ttm_tt_create,
.ttm_tt_populate = &nouveau_ttm_tt_populate,
.ttm_tt_unpopulate = &nouveau_ttm_tt_unpopulate,
.invalidate_caches = nouveau_bo_invalidate_caches,
.init_mem_type = nouveau_bo_init_mem_type,
.eviction_valuable = ttm_bo_eviction_valuable,
.evict_flags = nouveau_bo_evict_flags,
.move_notify = nouveau_bo_move_ntfy,
.move = nouveau_bo_move,
.verify_access = nouveau_bo_verify_access,
.fault_reserve_notify = &nouveau_ttm_fault_reserve_notify,
.io_mem_reserve = &nouveau_ttm_io_mem_reserve,
.io_mem_free = &nouveau_ttm_io_mem_free,
.io_mem_pfn = ttm_bo_default_io_mem_pfn,