Newer
Older
/*
* Copyright 2007 Dave Airlied
* 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
* VA LINUX SYSTEMS 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.
*/
/*
* Authors: Dave Airlied <airlied@linux.ie>
* Ben Skeggs <darktama@iinet.net.au>
* Jeremy Kolb <jkolb@brandeis.edu>
*/
#include <linux/dma-mapping.h>
#include <linux/swiotlb.h>
#include "nouveau_drm.h"
#include "nouveau_fence.h"
#include "nouveau_bo.h"
#include "nouveau_ttm.h"
#include "nouveau_gem.h"
/*
* NV10-NV40 tiling helpers
*/
static void
nv10_bo_update_tile_region(struct drm_device *dev, struct nouveau_drm_tile *reg,
u32 addr, u32 size, u32 pitch, u32 flags)
struct nouveau_drm *drm = nouveau_drm(dev);
int i = reg - drm->tile.reg;
struct nouveau_fb *pfb = nvkm_fb(&drm->device);
struct nouveau_fb_tile *tile = &pfb->tile.region[i];
struct nouveau_engine *engine;
nouveau_fence_unref(®->fence);
if (tile->pitch)
pfb->tile.fini(pfb, i, tile);
if (pitch)
pfb->tile.init(pfb, i, addr, size, pitch, flags, tile);
pfb->tile.prog(pfb, i, tile);
if ((engine = nouveau_engine(pfb, NVDEV_ENGINE_GR)))
engine->tile_prog(engine, i);
if ((engine = nouveau_engine(pfb, NVDEV_ENGINE_MPEG)))
engine->tile_prog(engine, i);
static struct nouveau_drm_tile *
nv10_bo_get_tile_region(struct drm_device *dev, int i)
{
struct nouveau_drm *drm = nouveau_drm(dev);
struct nouveau_drm_tile *tile = &drm->tile.reg[i];
spin_lock(&drm->tile.lock);
if (!tile->used &&
(!tile->fence || nouveau_fence_done(tile->fence)))
tile->used = true;
else
tile = NULL;
spin_unlock(&drm->tile.lock);
return tile;
}
static void
nv10_bo_put_tile_region(struct drm_device *dev, struct nouveau_drm_tile *tile,
struct fence *fence)
struct nouveau_drm *drm = nouveau_drm(dev);
if (tile) {
spin_lock(&drm->tile.lock);
tile->fence = (struct nouveau_fence *)fence_get(fence);
tile->used = false;
spin_unlock(&drm->tile.lock);
}
}
static struct nouveau_drm_tile *
nv10_bo_set_tiling(struct drm_device *dev, u32 addr,
u32 size, u32 pitch, u32 flags)
struct nouveau_drm *drm = nouveau_drm(dev);
struct nouveau_fb *pfb = nvkm_fb(&drm->device);
struct nouveau_drm_tile *tile, *found = NULL;
int i;
for (i = 0; i < pfb->tile.regions; i++) {
tile = nv10_bo_get_tile_region(dev, i);
if (pitch && !found) {
found = tile;
continue;
} else if (tile && pfb->tile.region[i].pitch) {
/* Kill an unused tile region. */
nv10_bo_update_tile_region(dev, tile, 0, 0, 0, 0);
}
nv10_bo_put_tile_region(dev, tile, NULL);
}
if (found)
nv10_bo_update_tile_region(dev, found, addr, size,
pitch, flags);
return found;
}
static void
nouveau_bo_del_ttm(struct ttm_buffer_object *bo)
{
struct nouveau_drm *drm = nouveau_bdev(bo->bdev);
struct drm_device *dev = drm->dev;
struct nouveau_bo *nvbo = nouveau_bo(bo);
if (unlikely(nvbo->gem.filp))
DRM_ERROR("bo %p still attached to GEM object\n", bo);
WARN_ON(nvbo->pin_refcnt > 0);
nv10_bo_put_tile_region(dev, nvbo->tile, NULL);
nouveau_bo_fixup_align(struct nouveau_bo *nvbo, u32 flags,
struct nouveau_drm *drm = nouveau_bdev(nvbo->bo.bdev);
struct nvif_device *device = &drm->device;
if (device->info.family < NV_DEVICE_INFO_V0_TESLA) {
if (nvbo->tile_mode) {
if (device->info.chipset >= 0x40) {
*size = roundup(*size, 64 * nvbo->tile_mode);
} else if (device->info.chipset >= 0x30) {
*size = roundup(*size, 64 * nvbo->tile_mode);
} else if (device->info.chipset >= 0x20) {
*size = roundup(*size, 64 * nvbo->tile_mode);
} else if (device->info.chipset >= 0x10) {
*size = roundup(*size, 32 * nvbo->tile_mode);
*size = roundup(*size, (1 << nvbo->page_shift));
*align = max((1 << nvbo->page_shift), *align);

Maarten Maathuis
committed
*size = roundup(*size, PAGE_SIZE);
nouveau_bo_new(struct drm_device *dev, int size, int align,
uint32_t flags, uint32_t tile_mode, uint32_t tile_flags,
struct sg_table *sg, struct reservation_object *robj,
struct nouveau_bo **pnvbo)
struct nouveau_drm *drm = nouveau_drm(dev);
int lpg_shift = 12;
int max_size;
if (drm->client.vm)
lpg_shift = drm->client.vm->vmm->lpg_shift;
max_size = INT_MAX & ~((1 << lpg_shift) - 1);
if (size <= 0 || size > max_size) {
NV_WARN(drm, "skipped size %x\n", (u32)size);
return -EINVAL;
}
nvbo = kzalloc(sizeof(struct nouveau_bo), GFP_KERNEL);
if (!nvbo)
return -ENOMEM;
INIT_LIST_HEAD(&nvbo->head);
INIT_LIST_HEAD(&nvbo->entry);
INIT_LIST_HEAD(&nvbo->vma_list);
nvbo->tile_mode = tile_mode;
nvbo->tile_flags = tile_flags;
nvbo->bo.bdev = &drm->ttm.bdev;
if (!nv_device_is_cpu_coherent(nvkm_device(&drm->device)))
nvbo->force_coherent = flags & TTM_PL_FLAG_UNCACHED;
if (!(flags & TTM_PL_FLAG_TT) && size > 256 * 1024)
nvbo->page_shift = drm->client.vm->vmm->lpg_shift;
}
nouveau_bo_fixup_align(nvbo, flags, &align, &size);
nvbo->bo.mem.num_pages = size >> PAGE_SHIFT;
nouveau_bo_placement_set(nvbo, flags, 0);
acc_size = ttm_bo_dma_acc_size(&drm->ttm.bdev, size,
sizeof(struct nouveau_bo));
ret = ttm_bo_init(&drm->ttm.bdev, &nvbo->bo, size,
align >> PAGE_SHIFT, false, NULL, acc_size, sg,
robj, nouveau_bo_del_ttm);
if (ret) {
/* ttm will call nouveau_bo_del_ttm if it fails.. */
return ret;
}
*pnvbo = nvbo;
return 0;
}
set_placement_list(struct ttm_place *pl, unsigned *n, uint32_t type, uint32_t flags)
{
*n = 0;
if (type & TTM_PL_FLAG_VRAM)
pl[(*n)++].flags = TTM_PL_FLAG_VRAM | flags;
if (type & TTM_PL_FLAG_TT)
pl[(*n)++].flags = TTM_PL_FLAG_TT | flags;
if (type & TTM_PL_FLAG_SYSTEM)
pl[(*n)++].flags = TTM_PL_FLAG_SYSTEM | flags;

Francisco Jerez
committed
static void
set_placement_range(struct nouveau_bo *nvbo, uint32_t type)
{
struct nouveau_drm *drm = nouveau_bdev(nvbo->bo.bdev);
u32 vram_pages = drm->device.info.ram_size >> PAGE_SHIFT;
unsigned i, fpfn, lpfn;

Francisco Jerez
committed
if (drm->device.info.family == NV_DEVICE_INFO_V0_CELSIUS &&

Francisco Jerez
committed
nvbo->tile_mode && (type & TTM_PL_FLAG_VRAM) &&

Francisco Jerez
committed
nvbo->bo.mem.num_pages < vram_pages / 4) {

Francisco Jerez
committed
/*
* Make sure that the color and depth buffers are handled
* by independent memory controller units. Up to a 9x
* speed up when alpha-blending and depth-test are enabled
* at the same time.
*/
if (nvbo->tile_flags & NOUVEAU_GEM_TILE_ZETA) {
fpfn = vram_pages / 2;
lpfn = ~0;

Francisco Jerez
committed
} else {
fpfn = 0;
lpfn = vram_pages / 2;
}
for (i = 0; i < nvbo->placement.num_placement; ++i) {
nvbo->placements[i].fpfn = fpfn;
nvbo->placements[i].lpfn = lpfn;
}
for (i = 0; i < nvbo->placement.num_busy_placement; ++i) {
nvbo->busy_placements[i].fpfn = fpfn;
nvbo->busy_placements[i].lpfn = lpfn;

Francisco Jerez
committed
}
}
}
nouveau_bo_placement_set(struct nouveau_bo *nvbo, uint32_t type, uint32_t busy)
struct ttm_placement *pl = &nvbo->placement;
uint32_t flags = (nvbo->force_coherent ? TTM_PL_FLAG_UNCACHED :
TTM_PL_MASK_CACHING) |
(nvbo->pin_refcnt ? TTM_PL_FLAG_NO_EVICT : 0);
pl->placement = nvbo->placements;
set_placement_list(nvbo->placements, &pl->num_placement,
type, flags);
pl->busy_placement = nvbo->busy_placements;
set_placement_list(nvbo->busy_placements, &pl->num_busy_placement,
type | busy, flags);

Francisco Jerez
committed
set_placement_range(nvbo, type);
}
int
nouveau_bo_pin(struct nouveau_bo *nvbo, uint32_t memtype)
{
struct nouveau_drm *drm = nouveau_bdev(nvbo->bo.bdev);
struct ttm_buffer_object *bo = &nvbo->bo;
ret = ttm_bo_reserve(bo, false, false, false, NULL);
if (nvbo->pin_refcnt && !(memtype & (1 << bo->mem.mem_type))) {
NV_ERROR(drm, "bo %p pinned elsewhere: 0x%08x vs 0x%08x\n", bo,
1 << bo->mem.mem_type, memtype);
ret = -EINVAL;
goto out;
if (nvbo->pin_refcnt++)
goto out;
nouveau_bo_placement_set(nvbo, memtype, 0);
/* drop pin_refcnt temporarily, so we don't trip the assertion
* in nouveau_bo_move() that makes sure we're not trying to
* move a pinned buffer
*/
nvbo->pin_refcnt--;
ret = nouveau_bo_validate(nvbo, false, false);
nvbo->pin_refcnt++;
switch (bo->mem.mem_type) {
case TTM_PL_VRAM:
drm->gem.vram_available -= bo->mem.size;
break;
case TTM_PL_TT:
drm->gem.gart_available -= bo->mem.size;
break;
default:
break;
return ret;
}
int
nouveau_bo_unpin(struct nouveau_bo *nvbo)
{
struct nouveau_drm *drm = nouveau_bdev(nvbo->bo.bdev);
struct ttm_buffer_object *bo = &nvbo->bo;
ret = ttm_bo_reserve(bo, false, false, false, NULL);
ref = --nvbo->pin_refcnt;
WARN_ON_ONCE(ref < 0);
if (ref)
nouveau_bo_placement_set(nvbo, bo->mem.placement, 0);
ret = nouveau_bo_validate(nvbo, false, false);
if (ret == 0) {
switch (bo->mem.mem_type) {
case TTM_PL_VRAM:
drm->gem.vram_available += bo->mem.size;
drm->gem.gart_available += bo->mem.size;
break;
default:
break;
}
}
ttm_bo_unreserve(bo);
return ret;
}
int
nouveau_bo_map(struct nouveau_bo *nvbo)
{
int ret;
Loading
Loading full blame...