| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171 |
- /* SPDX-License-Identifier: GPL-2.0 OR MIT */
- /**************************************************************************
- *
- * Copyright (c) 2007-2009 VMware, Inc., Palo Alto, CA., USA
- * 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, sub license, 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 NON-INFRINGEMENT. IN NO EVENT SHALL
- * THE COPYRIGHT HOLDERS, AUTHORS 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: Thomas Hellstrom <thellstrom-at-vmware-dot-com>
- */
- #include <linux/export.h>
- #include <linux/swap.h>
- #include <linux/vmalloc.h>
- #include <drm/ttm/ttm_bo.h>
- #include <drm/ttm/ttm_placement.h>
- #include <drm/ttm/ttm_tt.h>
- #include <drm/drm_cache.h>
- #include "ttm_bo_internal.h"
- struct ttm_transfer_obj {
- struct ttm_buffer_object base;
- struct ttm_buffer_object *bo;
- };
- int ttm_mem_io_reserve(struct ttm_device *bdev,
- struct ttm_resource *mem)
- {
- if (mem->bus.offset || mem->bus.addr)
- return 0;
- mem->bus.is_iomem = false;
- if (!bdev->funcs->io_mem_reserve)
- return 0;
- return bdev->funcs->io_mem_reserve(bdev, mem);
- }
- void ttm_mem_io_free(struct ttm_device *bdev,
- struct ttm_resource *mem)
- {
- if (!mem)
- return;
- if (!mem->bus.offset && !mem->bus.addr)
- return;
- if (bdev->funcs->io_mem_free)
- bdev->funcs->io_mem_free(bdev, mem);
- mem->bus.offset = 0;
- mem->bus.addr = NULL;
- }
- /**
- * ttm_move_memcpy - Helper to perform a memcpy ttm move operation.
- * @clear: Whether to clear rather than copy.
- * @num_pages: Number of pages of the operation.
- * @dst_iter: A struct ttm_kmap_iter representing the destination resource.
- * @src_iter: A struct ttm_kmap_iter representing the source resource.
- *
- * This function is intended to be able to move out async under a
- * dma-fence if desired.
- */
- void ttm_move_memcpy(bool clear,
- u32 num_pages,
- struct ttm_kmap_iter *dst_iter,
- struct ttm_kmap_iter *src_iter)
- {
- const struct ttm_kmap_iter_ops *dst_ops = dst_iter->ops;
- const struct ttm_kmap_iter_ops *src_ops = src_iter->ops;
- struct iosys_map src_map, dst_map;
- pgoff_t i;
- /* Single TTM move. NOP */
- if (dst_ops->maps_tt && src_ops->maps_tt)
- return;
- /* Don't move nonexistent data. Clear destination instead. */
- if (clear) {
- for (i = 0; i < num_pages; ++i) {
- dst_ops->map_local(dst_iter, &dst_map, i);
- if (dst_map.is_iomem)
- memset_io(dst_map.vaddr_iomem, 0, PAGE_SIZE);
- else
- memset(dst_map.vaddr, 0, PAGE_SIZE);
- if (dst_ops->unmap_local)
- dst_ops->unmap_local(dst_iter, &dst_map);
- }
- return;
- }
- for (i = 0; i < num_pages; ++i) {
- dst_ops->map_local(dst_iter, &dst_map, i);
- src_ops->map_local(src_iter, &src_map, i);
- drm_memcpy_from_wc(&dst_map, &src_map, PAGE_SIZE);
- if (src_ops->unmap_local)
- src_ops->unmap_local(src_iter, &src_map);
- if (dst_ops->unmap_local)
- dst_ops->unmap_local(dst_iter, &dst_map);
- }
- }
- EXPORT_SYMBOL(ttm_move_memcpy);
- /**
- * ttm_bo_move_memcpy
- *
- * @bo: A pointer to a struct ttm_buffer_object.
- * @ctx: operation context
- * @dst_mem: struct ttm_resource indicating where to move.
- *
- * Fallback move function for a mappable buffer object in mappable memory.
- * The function will, if successful,
- * free any old aperture space, and set (@new_mem)->mm_node to NULL,
- * and update the (@bo)->mem placement flags. If unsuccessful, the old
- * data remains untouched, and it's up to the caller to free the
- * memory space indicated by @new_mem.
- * Returns:
- * !0: Failure.
- */
- int ttm_bo_move_memcpy(struct ttm_buffer_object *bo,
- struct ttm_operation_ctx *ctx,
- struct ttm_resource *dst_mem)
- {
- struct ttm_device *bdev = bo->bdev;
- struct ttm_resource_manager *dst_man =
- ttm_manager_type(bo->bdev, dst_mem->mem_type);
- struct ttm_tt *ttm = bo->ttm;
- struct ttm_resource *src_mem = bo->resource;
- struct ttm_resource_manager *src_man;
- union {
- struct ttm_kmap_iter_tt tt;
- struct ttm_kmap_iter_linear_io io;
- } _dst_iter, _src_iter;
- struct ttm_kmap_iter *dst_iter, *src_iter;
- bool clear;
- int ret = 0;
- if (WARN_ON(!src_mem))
- return -EINVAL;
- src_man = ttm_manager_type(bdev, src_mem->mem_type);
- if (ttm && ((ttm->page_flags & TTM_TT_FLAG_SWAPPED) ||
- dst_man->use_tt)) {
- ret = ttm_bo_populate(bo, ctx);
- if (ret)
- return ret;
- }
- dst_iter = ttm_kmap_iter_linear_io_init(&_dst_iter.io, bdev, dst_mem);
- if (PTR_ERR(dst_iter) == -EINVAL && dst_man->use_tt)
- dst_iter = ttm_kmap_iter_tt_init(&_dst_iter.tt, ttm);
- if (IS_ERR(dst_iter))
- return PTR_ERR(dst_iter);
- src_iter = ttm_kmap_iter_linear_io_init(&_src_iter.io, bdev, src_mem);
- if (PTR_ERR(src_iter) == -EINVAL && src_man->use_tt)
- src_iter = ttm_kmap_iter_tt_init(&_src_iter.tt, ttm);
- if (IS_ERR(src_iter)) {
- ret = PTR_ERR(src_iter);
- goto out_src_iter;
- }
- clear = src_iter->ops->maps_tt && (!ttm || !ttm_tt_is_populated(ttm));
- if (!(clear && ttm && !(ttm->page_flags & TTM_TT_FLAG_ZERO_ALLOC)))
- ttm_move_memcpy(clear, PFN_UP(dst_mem->size), dst_iter, src_iter);
- if (!src_iter->ops->maps_tt)
- ttm_kmap_iter_linear_io_fini(&_src_iter.io, bdev, src_mem);
- ttm_bo_move_sync_cleanup(bo, dst_mem);
- out_src_iter:
- if (!dst_iter->ops->maps_tt)
- ttm_kmap_iter_linear_io_fini(&_dst_iter.io, bdev, dst_mem);
- return ret;
- }
- EXPORT_SYMBOL(ttm_bo_move_memcpy);
- static void ttm_transfered_destroy(struct ttm_buffer_object *bo)
- {
- struct ttm_transfer_obj *fbo;
- fbo = container_of(bo, struct ttm_transfer_obj, base);
- dma_resv_fini(&fbo->base.base._resv);
- ttm_bo_put(fbo->bo);
- kfree(fbo);
- }
- /**
- * ttm_buffer_object_transfer
- *
- * @bo: A pointer to a struct ttm_buffer_object.
- * @new_obj: A pointer to a pointer to a newly created ttm_buffer_object,
- * holding the data of @bo with the old placement.
- *
- * This is a utility function that may be called after an accelerated move
- * has been scheduled. A new buffer object is created as a placeholder for
- * the old data while it's being copied. When that buffer object is idle,
- * it can be destroyed, releasing the space of the old placement.
- * Returns:
- * !0: Failure.
- */
- static int ttm_buffer_object_transfer(struct ttm_buffer_object *bo,
- struct ttm_buffer_object **new_obj)
- {
- struct ttm_transfer_obj *fbo;
- int ret;
- fbo = kmalloc_obj(*fbo);
- if (!fbo)
- return -ENOMEM;
- fbo->base = *bo;
- /**
- * Fix up members that we shouldn't copy directly:
- * TODO: Explicit member copy would probably be better here.
- */
- atomic_inc(&ttm_glob.bo_count);
- drm_vma_node_reset(&fbo->base.base.vma_node);
- kref_init(&fbo->base.kref);
- fbo->base.destroy = &ttm_transfered_destroy;
- fbo->base.pin_count = 0;
- if (bo->type != ttm_bo_type_sg)
- fbo->base.base.resv = &fbo->base.base._resv;
- dma_resv_init(&fbo->base.base._resv);
- fbo->base.base.dev = NULL;
- ret = dma_resv_trylock(&fbo->base.base._resv);
- WARN_ON(!ret);
- ret = dma_resv_reserve_fences(&fbo->base.base._resv, TTM_NUM_MOVE_FENCES);
- if (ret) {
- dma_resv_unlock(&fbo->base.base._resv);
- kfree(fbo);
- return ret;
- }
- if (fbo->base.resource) {
- ttm_resource_set_bo(fbo->base.resource, &fbo->base);
- bo->resource = NULL;
- ttm_bo_set_bulk_move(&fbo->base, NULL);
- } else {
- fbo->base.bulk_move = NULL;
- }
- ttm_bo_get(bo);
- fbo->bo = bo;
- ttm_bo_move_to_lru_tail_unlocked(&fbo->base);
- *new_obj = &fbo->base;
- return 0;
- }
- /**
- * ttm_io_prot
- *
- * @bo: ttm buffer object
- * @res: ttm resource object
- * @tmp: Page protection flag for a normal, cached mapping.
- *
- * Utility function that returns the pgprot_t that should be used for
- * setting up a PTE with the caching model indicated by @c_state.
- */
- pgprot_t ttm_io_prot(struct ttm_buffer_object *bo, struct ttm_resource *res,
- pgprot_t tmp)
- {
- struct ttm_resource_manager *man;
- enum ttm_caching caching;
- man = ttm_manager_type(bo->bdev, res->mem_type);
- if (man->use_tt) {
- caching = bo->ttm->caching;
- if (bo->ttm->page_flags & TTM_TT_FLAG_DECRYPTED)
- tmp = pgprot_decrypted(tmp);
- } else {
- caching = res->bus.caching;
- }
- return ttm_prot_from_caching(caching, tmp);
- }
- EXPORT_SYMBOL(ttm_io_prot);
- static int ttm_bo_ioremap(struct ttm_buffer_object *bo,
- unsigned long offset,
- unsigned long size,
- struct ttm_bo_kmap_obj *map)
- {
- struct ttm_resource *mem = bo->resource;
- if (mem->bus.addr) {
- map->bo_kmap_type = ttm_bo_map_premapped;
- map->virtual = ((u8 *)mem->bus.addr) + offset;
- } else {
- resource_size_t res = mem->bus.offset + offset;
- map->bo_kmap_type = ttm_bo_map_iomap;
- if (mem->bus.caching == ttm_write_combined)
- map->virtual = ioremap_wc(res, size);
- #ifdef CONFIG_X86
- else if (mem->bus.caching == ttm_cached)
- map->virtual = ioremap_cache(res, size);
- #endif
- else
- map->virtual = ioremap(res, size);
- }
- return (!map->virtual) ? -ENOMEM : 0;
- }
- static int ttm_bo_kmap_ttm(struct ttm_buffer_object *bo,
- unsigned long start_page,
- unsigned long num_pages,
- struct ttm_bo_kmap_obj *map)
- {
- struct ttm_resource *mem = bo->resource;
- struct ttm_operation_ctx ctx = { };
- struct ttm_tt *ttm = bo->ttm;
- struct ttm_resource_manager *man =
- ttm_manager_type(bo->bdev, mem->mem_type);
- pgprot_t prot;
- int ret;
- BUG_ON(!ttm);
- ret = ttm_bo_populate(bo, &ctx);
- if (ret)
- return ret;
- if (num_pages == 1 && ttm->caching == ttm_cached &&
- !(man->use_tt && (ttm->page_flags & TTM_TT_FLAG_DECRYPTED))) {
- /*
- * We're mapping a single page, and the desired
- * page protection is consistent with the bo.
- */
- map->bo_kmap_type = ttm_bo_map_kmap;
- map->page = ttm->pages[start_page];
- map->virtual = kmap(map->page);
- } else {
- /*
- * We need to use vmap to get the desired page protection
- * or to make the buffer object look contiguous.
- */
- prot = ttm_io_prot(bo, mem, PAGE_KERNEL);
- map->bo_kmap_type = ttm_bo_map_vmap;
- map->virtual = vmap(ttm->pages + start_page, num_pages,
- 0, prot);
- }
- return (!map->virtual) ? -ENOMEM : 0;
- }
- /**
- * ttm_bo_kmap_try_from_panic
- *
- * @bo: The buffer object
- * @page: The page to map
- *
- * Sets up a kernel virtual mapping using kmap_local_page_try_from_panic().
- * This should only be called from the panic handler, if you make sure the bo
- * is the one being displayed, so is properly allocated, and protected.
- *
- * Returns the vaddr, that you can use to write to the bo, and that you should
- * pass to kunmap_local() when you're done with this page, or NULL if the bo
- * is in iomem.
- */
- void *ttm_bo_kmap_try_from_panic(struct ttm_buffer_object *bo, unsigned long page)
- {
- if (page + 1 > PFN_UP(bo->resource->size))
- return NULL;
- if (!bo->resource->bus.is_iomem && bo->ttm->pages && bo->ttm->pages[page])
- return kmap_local_page_try_from_panic(bo->ttm->pages[page]);
- return NULL;
- }
- EXPORT_SYMBOL(ttm_bo_kmap_try_from_panic);
- /**
- * ttm_bo_kmap
- *
- * @bo: The buffer object.
- * @start_page: The first page to map.
- * @num_pages: Number of pages to map.
- * @map: pointer to a struct ttm_bo_kmap_obj representing the map.
- *
- * Sets up a kernel virtual mapping, using ioremap, vmap or kmap to the
- * data in the buffer object. The ttm_kmap_obj_virtual function can then be
- * used to obtain a virtual address to the data.
- *
- * Returns
- * -ENOMEM: Out of memory.
- * -EINVAL: Invalid range.
- */
- int ttm_bo_kmap(struct ttm_buffer_object *bo,
- unsigned long start_page, unsigned long num_pages,
- struct ttm_bo_kmap_obj *map)
- {
- struct ttm_resource *res = bo->resource;
- unsigned long offset, size;
- int ret;
- map->virtual = NULL;
- map->bo = bo;
- if (num_pages > PFN_UP(res->size))
- return -EINVAL;
- if ((start_page + num_pages) > PFN_UP(res->size))
- return -EINVAL;
- ret = ttm_mem_io_reserve(bo->bdev, res);
- if (ret)
- return ret;
- if (!res->bus.is_iomem) {
- return ttm_bo_kmap_ttm(bo, start_page, num_pages, map);
- } else {
- offset = start_page << PAGE_SHIFT;
- size = num_pages << PAGE_SHIFT;
- return ttm_bo_ioremap(bo, offset, size, map);
- }
- }
- EXPORT_SYMBOL(ttm_bo_kmap);
- /**
- * ttm_bo_kunmap
- *
- * @map: Object describing the map to unmap.
- *
- * Unmaps a kernel map set up by ttm_bo_kmap.
- */
- void ttm_bo_kunmap(struct ttm_bo_kmap_obj *map)
- {
- if (!map->virtual)
- return;
- switch (map->bo_kmap_type) {
- case ttm_bo_map_iomap:
- iounmap(map->virtual);
- break;
- case ttm_bo_map_vmap:
- vunmap(map->virtual);
- break;
- case ttm_bo_map_kmap:
- kunmap(map->page);
- break;
- case ttm_bo_map_premapped:
- break;
- default:
- BUG();
- }
- ttm_mem_io_free(map->bo->bdev, map->bo->resource);
- map->virtual = NULL;
- map->page = NULL;
- }
- EXPORT_SYMBOL(ttm_bo_kunmap);
- /**
- * ttm_bo_vmap
- *
- * @bo: The buffer object.
- * @map: pointer to a struct iosys_map representing the map.
- *
- * Sets up a kernel virtual mapping, using ioremap or vmap to the
- * data in the buffer object. The parameter @map returns the virtual
- * address as struct iosys_map. Unmap the buffer with ttm_bo_vunmap().
- *
- * Returns
- * -ENOMEM: Out of memory.
- * -EINVAL: Invalid range.
- */
- int ttm_bo_vmap(struct ttm_buffer_object *bo, struct iosys_map *map)
- {
- struct ttm_resource *mem = bo->resource;
- int ret;
- dma_resv_assert_held(bo->base.resv);
- ret = ttm_mem_io_reserve(bo->bdev, mem);
- if (ret)
- return ret;
- if (mem->bus.is_iomem) {
- void __iomem *vaddr_iomem;
- if (mem->bus.addr)
- vaddr_iomem = (void __iomem *)mem->bus.addr;
- else if (mem->bus.caching == ttm_write_combined)
- vaddr_iomem = ioremap_wc(mem->bus.offset,
- bo->base.size);
- #ifdef CONFIG_X86
- else if (mem->bus.caching == ttm_cached)
- vaddr_iomem = ioremap_cache(mem->bus.offset,
- bo->base.size);
- #endif
- else
- vaddr_iomem = ioremap(mem->bus.offset, bo->base.size);
- if (!vaddr_iomem)
- return -ENOMEM;
- iosys_map_set_vaddr_iomem(map, vaddr_iomem);
- } else {
- struct ttm_operation_ctx ctx = { };
- struct ttm_tt *ttm = bo->ttm;
- pgprot_t prot;
- void *vaddr;
- ret = ttm_bo_populate(bo, &ctx);
- if (ret)
- return ret;
- /*
- * We need to use vmap to get the desired page protection
- * or to make the buffer object look contiguous.
- */
- prot = ttm_io_prot(bo, mem, PAGE_KERNEL);
- vaddr = vmap(ttm->pages, ttm->num_pages, 0, prot);
- if (!vaddr)
- return -ENOMEM;
- iosys_map_set_vaddr(map, vaddr);
- }
- return 0;
- }
- EXPORT_SYMBOL(ttm_bo_vmap);
- /**
- * ttm_bo_vunmap
- *
- * @bo: The buffer object.
- * @map: Object describing the map to unmap.
- *
- * Unmaps a kernel map set up by ttm_bo_vmap().
- */
- void ttm_bo_vunmap(struct ttm_buffer_object *bo, struct iosys_map *map)
- {
- struct ttm_resource *mem = bo->resource;
- dma_resv_assert_held(bo->base.resv);
- if (iosys_map_is_null(map))
- return;
- if (!map->is_iomem)
- vunmap(map->vaddr);
- else if (!mem->bus.addr)
- iounmap(map->vaddr_iomem);
- iosys_map_clear(map);
- ttm_mem_io_free(bo->bdev, mem);
- }
- EXPORT_SYMBOL(ttm_bo_vunmap);
- static int ttm_bo_wait_free_node(struct ttm_buffer_object *bo,
- bool dst_use_tt)
- {
- long ret;
- ret = dma_resv_wait_timeout(bo->base.resv, DMA_RESV_USAGE_BOOKKEEP,
- false, 15 * HZ);
- if (ret == 0)
- return -EBUSY;
- if (ret < 0)
- return ret;
- if (!dst_use_tt)
- ttm_bo_tt_destroy(bo);
- ttm_resource_free(bo, &bo->resource);
- return 0;
- }
- static int ttm_bo_move_to_ghost(struct ttm_buffer_object *bo,
- struct dma_fence *fence,
- bool dst_use_tt)
- {
- struct ttm_buffer_object *ghost_obj;
- int ret;
- /**
- * This should help pipeline ordinary buffer moves.
- *
- * Hang old buffer memory on a new buffer object,
- * and leave it to be released when the GPU
- * operation has completed.
- */
- ret = ttm_buffer_object_transfer(bo, &ghost_obj);
- if (ret)
- return ret;
- dma_resv_add_fence(&ghost_obj->base._resv, fence,
- DMA_RESV_USAGE_KERNEL);
- /**
- * If we're not moving to fixed memory, the TTM object
- * needs to stay alive. Otherwhise hang it on the ghost
- * bo to be unbound and destroyed.
- */
- if (dst_use_tt)
- ghost_obj->ttm = NULL;
- else
- bo->ttm = NULL;
- dma_resv_unlock(&ghost_obj->base._resv);
- ttm_bo_put(ghost_obj);
- return 0;
- }
- static void ttm_bo_move_pipeline_evict(struct ttm_buffer_object *bo,
- struct dma_fence *fence)
- {
- struct ttm_resource_manager *from;
- struct dma_fence *tmp;
- int i;
- from = ttm_manager_type(bo->bdev, bo->resource->mem_type);
- /**
- * BO doesn't have a TTM we need to bind/unbind. Just remember
- * this eviction and free up the allocation.
- * The fence will be saved in the first free slot or in the slot
- * already used to store a fence from the same context. Since
- * drivers can't use more than TTM_NUM_MOVE_FENCES contexts for
- * evictions we should always find a slot to use.
- */
- spin_lock(&from->eviction_lock);
- for (i = 0; i < TTM_NUM_MOVE_FENCES; i++) {
- tmp = from->eviction_fences[i];
- if (!tmp)
- break;
- if (fence->context != tmp->context)
- continue;
- if (dma_fence_is_later(fence, tmp)) {
- dma_fence_put(tmp);
- break;
- }
- goto unlock;
- }
- if (i < TTM_NUM_MOVE_FENCES) {
- from->eviction_fences[i] = dma_fence_get(fence);
- } else {
- WARN(1, "not enough fence slots for all fence contexts");
- spin_unlock(&from->eviction_lock);
- dma_fence_wait(fence, false);
- goto end;
- }
- unlock:
- spin_unlock(&from->eviction_lock);
- end:
- ttm_resource_free(bo, &bo->resource);
- }
- /**
- * ttm_bo_move_accel_cleanup - cleanup helper for hw copies
- *
- * @bo: A pointer to a struct ttm_buffer_object.
- * @fence: A fence object that signals when moving is complete.
- * @evict: This is an evict move. Don't return until the buffer is idle.
- * @pipeline: evictions are to be pipelined.
- * @new_mem: struct ttm_resource indicating where to move.
- *
- * Accelerated move function to be called when an accelerated move
- * has been scheduled. The function will create a new temporary buffer object
- * representing the old placement, and put the sync object on both buffer
- * objects. After that the newly created buffer object is unref'd to be
- * destroyed when the move is complete. This will help pipeline
- * buffer moves.
- */
- int ttm_bo_move_accel_cleanup(struct ttm_buffer_object *bo,
- struct dma_fence *fence,
- bool evict,
- bool pipeline,
- struct ttm_resource *new_mem)
- {
- struct ttm_device *bdev = bo->bdev;
- struct ttm_resource_manager *from = ttm_manager_type(bdev, bo->resource->mem_type);
- struct ttm_resource_manager *man = ttm_manager_type(bdev, new_mem->mem_type);
- int ret = 0;
- dma_resv_add_fence(bo->base.resv, fence, DMA_RESV_USAGE_KERNEL);
- if (!evict)
- ret = ttm_bo_move_to_ghost(bo, fence, man->use_tt);
- else if (!from->use_tt && pipeline)
- ttm_bo_move_pipeline_evict(bo, fence);
- else
- ret = ttm_bo_wait_free_node(bo, man->use_tt);
- if (ret)
- return ret;
- ttm_bo_assign_mem(bo, new_mem);
- return 0;
- }
- EXPORT_SYMBOL(ttm_bo_move_accel_cleanup);
- /**
- * ttm_bo_move_sync_cleanup - cleanup by waiting for the move to finish
- *
- * @bo: A pointer to a struct ttm_buffer_object.
- * @new_mem: struct ttm_resource indicating where to move.
- *
- * Special case of ttm_bo_move_accel_cleanup where the bo is guaranteed
- * by the caller to be idle. Typically used after memcpy buffer moves.
- */
- void ttm_bo_move_sync_cleanup(struct ttm_buffer_object *bo,
- struct ttm_resource *new_mem)
- {
- struct ttm_resource_manager *man =
- ttm_manager_type(bo->bdev, new_mem->mem_type);
- int ret;
- ret = ttm_bo_wait_free_node(bo, man->use_tt);
- if (WARN_ON(ret))
- return;
- ttm_bo_assign_mem(bo, new_mem);
- }
- EXPORT_SYMBOL(ttm_bo_move_sync_cleanup);
- /**
- * ttm_bo_pipeline_gutting - purge the contents of a bo
- * @bo: The buffer object
- *
- * Purge the contents of a bo, async if the bo is not idle.
- * After a successful call, the bo is left unpopulated in
- * system placement. The function may wait uninterruptible
- * for idle on OOM.
- *
- * Return: 0 if successful, negative error code on failure.
- */
- int ttm_bo_pipeline_gutting(struct ttm_buffer_object *bo)
- {
- struct ttm_buffer_object *ghost;
- struct ttm_tt *ttm;
- int ret;
- /* If already idle, no need for ghost object dance. */
- if (dma_resv_test_signaled(bo->base.resv, DMA_RESV_USAGE_BOOKKEEP)) {
- if (!bo->ttm) {
- /* See comment below about clearing. */
- ret = ttm_tt_create(bo, true);
- if (ret)
- return ret;
- } else {
- ttm_tt_unpopulate(bo->bdev, bo->ttm);
- if (bo->type == ttm_bo_type_device)
- ttm_tt_mark_for_clear(bo->ttm);
- }
- ttm_resource_free(bo, &bo->resource);
- return 0;
- }
- /*
- * We need an unpopulated ttm_tt after giving our current one,
- * if any, to the ghost object. And we can't afford to fail
- * creating one *after* the operation. If the bo subsequently gets
- * resurrected, make sure it's cleared (if ttm_bo_type_device)
- * to avoid leaking sensitive information to user-space.
- */
- ttm = bo->ttm;
- bo->ttm = NULL;
- ret = ttm_tt_create(bo, true);
- swap(bo->ttm, ttm);
- if (ret)
- return ret;
- ret = ttm_buffer_object_transfer(bo, &ghost);
- if (ret)
- goto error_destroy_tt;
- ret = dma_resv_copy_fences(&ghost->base._resv, bo->base.resv);
- /* Last resort, wait for the BO to be idle when we are OOM */
- if (ret) {
- dma_resv_wait_timeout(bo->base.resv, DMA_RESV_USAGE_BOOKKEEP,
- false, MAX_SCHEDULE_TIMEOUT);
- }
- dma_resv_unlock(&ghost->base._resv);
- ttm_bo_put(ghost);
- bo->ttm = ttm;
- return 0;
- error_destroy_tt:
- ttm_tt_destroy(bo->bdev, ttm);
- return ret;
- }
- static bool ttm_lru_walk_trylock(struct ttm_bo_lru_cursor *curs,
- struct ttm_buffer_object *bo)
- {
- struct ttm_operation_ctx *ctx = curs->arg->ctx;
- curs->needs_unlock = false;
- if (dma_resv_trylock(bo->base.resv)) {
- curs->needs_unlock = true;
- return true;
- }
- if (bo->base.resv == ctx->resv && ctx->allow_res_evict) {
- dma_resv_assert_held(bo->base.resv);
- return true;
- }
- return false;
- }
- static int ttm_lru_walk_ticketlock(struct ttm_bo_lru_cursor *curs,
- struct ttm_buffer_object *bo)
- {
- struct ttm_lru_walk_arg *arg = curs->arg;
- int ret;
- if (arg->ctx->interruptible)
- ret = dma_resv_lock_interruptible(bo->base.resv, arg->ticket);
- else
- ret = dma_resv_lock(bo->base.resv, arg->ticket);
- if (!ret) {
- curs->needs_unlock = true;
- /*
- * Only a single ticketlock per loop. Ticketlocks are prone
- * to return -EDEADLK causing the eviction to fail, so
- * after waiting for the ticketlock, revert back to
- * trylocking for this walk.
- */
- arg->ticket = NULL;
- } else if (ret == -EDEADLK) {
- /* Caller needs to exit the ww transaction. */
- ret = -ENOSPC;
- }
- return ret;
- }
- /**
- * ttm_lru_walk_for_evict() - Perform a LRU list walk, with actions taken on
- * valid items.
- * @walk: describe the walks and actions taken
- * @bdev: The TTM device.
- * @man: The struct ttm_resource manager whose LRU lists we're walking.
- * @target: The end condition for the walk.
- *
- * The LRU lists of @man are walk, and for each struct ttm_resource encountered,
- * the corresponding ttm_buffer_object is locked and taken a reference on, and
- * the LRU lock is dropped. the LRU lock may be dropped before locking and, in
- * that case, it's verified that the item actually remains on the LRU list after
- * the lock, and that the buffer object didn't switch resource in between.
- *
- * With a locked object, the actions indicated by @walk->process_bo are
- * performed, and after that, the bo is unlocked, the refcount dropped and the
- * next struct ttm_resource is processed. Here, the walker relies on
- * TTM's restartable LRU list implementation.
- *
- * Typically @walk->process_bo() would return the number of pages evicted,
- * swapped or shrunken, so that when the total exceeds @target, or when the
- * LRU list has been walked in full, iteration is terminated. It's also terminated
- * on error. Note that the definition of @target is done by the caller, it
- * could have a different meaning than the number of pages.
- *
- * Note that the way dma_resv individualization is done, locking needs to be done
- * either with the LRU lock held (trylocking only) or with a reference on the
- * object.
- *
- * Return: The progress made towards target or negative error code on error.
- */
- s64 ttm_lru_walk_for_evict(struct ttm_lru_walk *walk, struct ttm_device *bdev,
- struct ttm_resource_manager *man, s64 target)
- {
- struct ttm_bo_lru_cursor cursor;
- struct ttm_buffer_object *bo;
- s64 progress = 0;
- s64 lret;
- ttm_bo_lru_for_each_reserved_guarded(&cursor, man, &walk->arg, bo) {
- lret = walk->ops->process_bo(walk, bo);
- if (lret == -EBUSY || lret == -EALREADY)
- lret = 0;
- progress = (lret < 0) ? lret : progress + lret;
- if (progress < 0 || progress >= target)
- break;
- }
- if (IS_ERR(bo))
- return PTR_ERR(bo);
- return progress;
- }
- EXPORT_SYMBOL(ttm_lru_walk_for_evict);
- static void ttm_bo_lru_cursor_cleanup_bo(struct ttm_bo_lru_cursor *curs)
- {
- struct ttm_buffer_object *bo = curs->bo;
- if (bo) {
- if (curs->needs_unlock)
- dma_resv_unlock(bo->base.resv);
- ttm_bo_put(bo);
- curs->bo = NULL;
- }
- }
- /**
- * ttm_bo_lru_cursor_fini() - Stop using a struct ttm_bo_lru_cursor
- * and clean up any iteration it was used for.
- * @curs: The cursor.
- */
- void ttm_bo_lru_cursor_fini(struct ttm_bo_lru_cursor *curs)
- {
- spinlock_t *lru_lock = &curs->res_curs.man->bdev->lru_lock;
- ttm_bo_lru_cursor_cleanup_bo(curs);
- spin_lock(lru_lock);
- ttm_resource_cursor_fini(&curs->res_curs);
- spin_unlock(lru_lock);
- }
- EXPORT_SYMBOL(ttm_bo_lru_cursor_fini);
- /**
- * ttm_bo_lru_cursor_init() - Initialize a struct ttm_bo_lru_cursor
- * @curs: The ttm_bo_lru_cursor to initialize.
- * @man: The ttm resource_manager whose LRU lists to iterate over.
- * @arg: The ttm_lru_walk_arg to govern the walk.
- *
- * Initialize a struct ttm_bo_lru_cursor.
- *
- * Return: Pointer to @curs. The function does not fail.
- */
- struct ttm_bo_lru_cursor *
- ttm_bo_lru_cursor_init(struct ttm_bo_lru_cursor *curs,
- struct ttm_resource_manager *man,
- struct ttm_lru_walk_arg *arg)
- {
- memset(curs, 0, sizeof(*curs));
- ttm_resource_cursor_init(&curs->res_curs, man);
- curs->arg = arg;
- return curs;
- }
- EXPORT_SYMBOL(ttm_bo_lru_cursor_init);
- static struct ttm_buffer_object *
- __ttm_bo_lru_cursor_next(struct ttm_bo_lru_cursor *curs)
- {
- spinlock_t *lru_lock = &curs->res_curs.man->bdev->lru_lock;
- struct ttm_resource *res = NULL;
- struct ttm_buffer_object *bo;
- struct ttm_lru_walk_arg *arg = curs->arg;
- bool first = !curs->bo;
- ttm_bo_lru_cursor_cleanup_bo(curs);
- spin_lock(lru_lock);
- for (;;) {
- int mem_type, ret = 0;
- bool bo_locked = false;
- if (first) {
- res = ttm_resource_manager_first(&curs->res_curs);
- first = false;
- } else {
- res = ttm_resource_manager_next(&curs->res_curs);
- }
- if (!res)
- break;
- bo = res->bo;
- if (ttm_lru_walk_trylock(curs, bo))
- bo_locked = true;
- else if (!arg->ticket || arg->ctx->no_wait_gpu || arg->trylock_only)
- continue;
- if (!ttm_bo_get_unless_zero(bo)) {
- if (curs->needs_unlock)
- dma_resv_unlock(bo->base.resv);
- continue;
- }
- mem_type = res->mem_type;
- spin_unlock(lru_lock);
- if (!bo_locked)
- ret = ttm_lru_walk_ticketlock(curs, bo);
- /*
- * Note that in between the release of the lru lock and the
- * ticketlock, the bo may have switched resource,
- * and also memory type, since the resource may have been
- * freed and allocated again with a different memory type.
- * In that case, just skip it.
- */
- curs->bo = bo;
- if (!ret && bo->resource && bo->resource->mem_type == mem_type)
- return bo;
- ttm_bo_lru_cursor_cleanup_bo(curs);
- if (ret && ret != -EALREADY)
- return ERR_PTR(ret);
- spin_lock(lru_lock);
- }
- spin_unlock(lru_lock);
- return res ? bo : NULL;
- }
- /**
- * ttm_bo_lru_cursor_next() - Continue iterating a manager's LRU lists
- * to find and lock buffer object.
- * @curs: The cursor initialized using ttm_bo_lru_cursor_init() and
- * ttm_bo_lru_cursor_first().
- *
- * Return: A pointer to a locked and reference-counted buffer object,
- * or NULL if none could be found and looping should be terminated.
- */
- struct ttm_buffer_object *ttm_bo_lru_cursor_next(struct ttm_bo_lru_cursor *curs)
- {
- return __ttm_bo_lru_cursor_next(curs);
- }
- EXPORT_SYMBOL(ttm_bo_lru_cursor_next);
- /**
- * ttm_bo_lru_cursor_first() - Start iterating a manager's LRU lists
- * to find and lock buffer object.
- * @curs: The cursor initialized using ttm_bo_lru_cursor_init().
- *
- * Return: A pointer to a locked and reference-counted buffer object,
- * or NULL if none could be found and looping should be terminated.
- */
- struct ttm_buffer_object *ttm_bo_lru_cursor_first(struct ttm_bo_lru_cursor *curs)
- {
- ttm_bo_lru_cursor_cleanup_bo(curs);
- return __ttm_bo_lru_cursor_next(curs);
- }
- EXPORT_SYMBOL(ttm_bo_lru_cursor_first);
- /**
- * ttm_bo_shrink() - Helper to shrink a ttm buffer object.
- * @ctx: The struct ttm_operation_ctx used for the shrinking operation.
- * @bo: The buffer object.
- * @flags: Flags governing the shrinking behaviour.
- *
- * The function uses the ttm_tt_back_up functionality to back up or
- * purge a struct ttm_tt. If the bo is not in system, it's first
- * moved there.
- *
- * Return: The number of pages shrunken or purged, or
- * negative error code on failure.
- */
- long ttm_bo_shrink(struct ttm_operation_ctx *ctx, struct ttm_buffer_object *bo,
- const struct ttm_bo_shrink_flags flags)
- {
- static const struct ttm_place sys_placement_flags = {
- .fpfn = 0,
- .lpfn = 0,
- .mem_type = TTM_PL_SYSTEM,
- .flags = 0,
- };
- static struct ttm_placement sys_placement = {
- .num_placement = 1,
- .placement = &sys_placement_flags,
- };
- struct ttm_device *bdev = bo->bdev;
- long lret;
- dma_resv_assert_held(bo->base.resv);
- if (flags.allow_move && bo->resource->mem_type != TTM_PL_SYSTEM) {
- int ret = ttm_bo_validate(bo, &sys_placement, ctx);
- /* Consider -ENOMEM and -ENOSPC non-fatal. */
- if (ret) {
- if (ret == -ENOMEM || ret == -ENOSPC)
- ret = -EBUSY;
- return ret;
- }
- }
- ttm_bo_unmap_virtual(bo);
- lret = ttm_bo_wait_ctx(bo, ctx);
- if (lret < 0)
- return lret;
- if (bo->bulk_move) {
- spin_lock(&bdev->lru_lock);
- ttm_resource_del_bulk_move(bo->resource, bo);
- spin_unlock(&bdev->lru_lock);
- }
- lret = ttm_tt_backup(bdev, bo->ttm, (struct ttm_backup_flags)
- {.purge = flags.purge,
- .writeback = flags.writeback});
- if (lret <= 0 && bo->bulk_move) {
- spin_lock(&bdev->lru_lock);
- ttm_resource_add_bulk_move(bo->resource, bo);
- spin_unlock(&bdev->lru_lock);
- }
- if (lret < 0 && lret != -EINTR)
- return -EBUSY;
- return lret;
- }
- EXPORT_SYMBOL(ttm_bo_shrink);
- /**
- * ttm_bo_shrink_suitable() - Whether a bo is suitable for shinking
- * @ctx: The struct ttm_operation_ctx governing the shrinking.
- * @bo: The candidate for shrinking.
- *
- * Check whether the object, given the information available to TTM,
- * is suitable for shinking, This function can and should be used
- * before attempting to shrink an object.
- *
- * Return: true if suitable. false if not.
- */
- bool ttm_bo_shrink_suitable(struct ttm_buffer_object *bo, struct ttm_operation_ctx *ctx)
- {
- return bo->ttm && ttm_tt_is_populated(bo->ttm) && !bo->pin_count &&
- (!ctx->no_wait_gpu ||
- dma_resv_test_signaled(bo->base.resv, DMA_RESV_USAGE_BOOKKEEP));
- }
- EXPORT_SYMBOL(ttm_bo_shrink_suitable);
- /**
- * ttm_bo_shrink_avoid_wait() - Whether to avoid waiting for GPU
- * during shrinking
- *
- * In some situations, like direct reclaim, waiting (in particular gpu waiting)
- * should be avoided since it may stall a system that could otherwise make progress
- * shrinking something else less time consuming.
- *
- * Return: true if gpu waiting should be avoided, false if not.
- */
- bool ttm_bo_shrink_avoid_wait(void)
- {
- return !current_is_kswapd();
- }
- EXPORT_SYMBOL(ttm_bo_shrink_avoid_wait);
|