/** * xe_bo_has_single_placement - check if BO is placed only in one memory location * @bo: The BO * * This function checks whether a given BO is placed in only one memory location. * * Returns: true if the BO is placed in a single memory location, false otherwise. *
*/ bool xe_bo_has_single_placement(struct xe_bo *bo)
{ return bo->placement.num_placement == 1;
}
/** * xe_bo_is_stolen_devmem - check if BO is of stolen type accessed via PCI BAR * @bo: The BO * * The stolen memory is accessed through the PCI BAR for both DGFX and some * integrated platforms that have a dedicated bit in the PTE for devmem (DM). * * Returns: true if it's stolen memory accessed via PCI BAR, false otherwise.
*/ bool xe_bo_is_stolen_devmem(struct xe_bo *bo)
{ return xe_bo_is_stolen(bo) &&
GRAPHICS_VERx100(xe_bo_device(bo)) >= 1270;
}
/** * xe_bo_is_vm_bound - check if BO has any mappings through VM_BIND * @bo: The BO * * Check if a given bo is bound through VM_BIND. This requires the * reservation lock for the BO to be held. * * Returns: boolean
*/ bool xe_bo_is_vm_bound(struct xe_bo *bo)
{
xe_bo_assert_held(bo);
/* * For eviction / restore on suspend / resume objects pinned in VRAM * must be contiguous, also only contiguous BOs support xe_bo_vmap.
*/ return bo_flags & XE_BO_FLAG_NEEDS_CPU_ACCESS &&
bo_flags & XE_BO_FLAG_PINNED;
}
bo = ttm_to_xe_bo(tbo); if (bo->flags & XE_BO_FLAG_CPU_ADDR_MIRROR) {
*placement = sys_placement; return;
}
if (device_unplugged && !tbo->base.dma_buf) {
*placement = purge_placement; return;
}
/* * For xe, sg bos that are evicted to system just triggers a * rebind of the sg list upon subsequent validation to XE_PL_TT.
*/ switch (tbo->resource->mem_type) { case XE_PL_VRAM0: case XE_PL_VRAM1: case XE_PL_STOLEN:
*placement = tt_placement; break; case XE_PL_TT: default:
*placement = sys_placement; break;
}
}
/* struct xe_ttm_tt - Subclassed ttm_tt for xe */ struct xe_ttm_tt { struct ttm_tt ttm; struct sg_table sgt; struct sg_table *sg; /** @purgeable: Whether the content of the pages of @ttm is purgeable. */ bool purgeable;
};
xe_tt = kzalloc(sizeof(*xe_tt), GFP_KERNEL); if (!xe_tt) return NULL;
tt = &xe_tt->ttm;
extra_pages = 0; if (xe_bo_needs_ccs_pages(bo))
extra_pages = DIV_ROUND_UP(xe_device_ccs_bytes(xe, xe_bo_size(bo)),
PAGE_SIZE);
/* * DGFX system memory is always WB / ttm_cached, since * other caching modes are only supported on x86. DGFX * GPU system memory accesses are always coherent with the * CPU.
*/ if (!IS_DGFX(xe)) { switch (bo->cpu_caching) { case DRM_XE_GEM_CPU_CACHING_WC:
caching = ttm_write_combined; break; default:
caching = ttm_cached; break;
}
/* * Display scanout is always non-coherent with the CPU cache. * * For Xe_LPG and beyond, PPGTT PTE lookups are also * non-coherent and require a CPU:WC mapping.
*/ if ((!bo->cpu_caching && bo->flags & XE_BO_FLAG_SCANOUT) ||
(xe->info.graphics_verx100 >= 1270 &&
bo->flags & XE_BO_FLAG_PAGETABLE))
caching = ttm_write_combined;
}
if (bo->flags & XE_BO_FLAG_NEEDS_UC) { /* * Valid only for internally-created buffers only, for * which cpu_caching is never initialized.
*/
xe_assert(xe, bo->cpu_caching == 0);
caching = ttm_uncached;
}
if (ttm_bo->type != ttm_bo_type_sg)
page_flags |= TTM_TT_FLAG_EXTERNAL | TTM_TT_FLAG_EXTERNAL_MAPPABLE;
/* * dma-bufs are not populated with pages, and the dma- * addresses are set up when moved to XE_PL_TT.
*/ if ((tt->page_flags & TTM_TT_FLAG_EXTERNAL) &&
!(tt->page_flags & TTM_TT_FLAG_EXTERNAL_MAPPABLE)) return 0;
switch (mem->mem_type) { case XE_PL_SYSTEM: case XE_PL_TT: return 0; case XE_PL_VRAM0: case XE_PL_VRAM1: { struct xe_vram_region *vram = res_to_mem_region(mem);
if (!xe_ttm_resource_visible(mem)) return -EINVAL;
trace_xe_vma_evict(vma);
ret = xe_vm_invalidate_vma(vma); if (XE_WARN_ON(ret)) return ret;
}
}
return ret;
}
/* * The dma-buf map_attachment() / unmap_attachment() is hooked up here. * Note that unmapping the attachment is deferred to the next * map_attachment time, or to bo destroy (after idling) whichever comes first. * This is to avoid syncing before unmap_attachment(), assuming that the * caller relies on idling the reservation object before moving the * backing store out. Should that assumption not hold, then we will be able * to unconditionally call unmap_attachment() when moving out to system.
*/ staticint xe_bo_move_dmabuf(struct ttm_buffer_object *ttm_bo, struct ttm_resource *new_res)
{ struct dma_buf_attachment *attach = ttm_bo->base.import_attach; struct xe_ttm_tt *xe_tt = container_of(ttm_bo->ttm, struct xe_ttm_tt,
ttm); struct xe_device *xe = ttm_to_xe_device(ttm_bo->bdev); bool device_unplugged = drm_dev_is_unplugged(&xe->drm); struct sg_table *sg;
if (ttm_bo->sg) {
dma_buf_unmap_attachment(attach, ttm_bo->sg, DMA_BIDIRECTIONAL);
ttm_bo->sg = NULL;
}
sg = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL); if (IS_ERR(sg)) return PTR_ERR(sg);
ttm_bo->sg = sg;
xe_tt->sg = sg;
out:
ttm_bo_move_null(ttm_bo, new_res);
return 0;
}
/** * xe_bo_move_notify - Notify subsystems of a pending move * @bo: The buffer object * @ctx: The struct ttm_operation_ctx controlling locking and waits. * * This function notifies subsystems of an upcoming buffer move. * Upon receiving such a notification, subsystems should schedule * halting access to the underlying pages and optionally add a fence * to the buffer object's dma_resv object, that signals when access is * stopped. The caller will wait on all dma_resv fences before * starting the move. * * A subsystem may commence access to the object after obtaining * bindings to the new backing memory under the object lock. * * Return: 0 on success, -EINTR or -ERESTARTSYS if interrupted in fault mode, * negative error code on error.
*/ staticint xe_bo_move_notify(struct xe_bo *bo, conststruct ttm_operation_ctx *ctx)
{ struct ttm_buffer_object *ttm_bo = &bo->ttm; struct xe_device *xe = ttm_to_xe_device(ttm_bo->bdev); struct ttm_resource *old_mem = ttm_bo->resource;
u32 old_mem_type = old_mem ? old_mem->mem_type : XE_PL_SYSTEM; int ret;
/* * If this starts to call into many components, consider * using a notification chain here.
*/
if (xe_bo_is_pinned(bo)) return -EINVAL;
xe_bo_vunmap(bo);
ret = xe_bo_trigger_rebind(xe, bo, ctx); if (ret) return ret;
/* Don't call move_notify() for imported dma-bufs. */ if (ttm_bo->base.dma_buf && !ttm_bo->base.import_attach)
dma_buf_move_notify(ttm_bo->base.dma_buf);
/* * TTM has already nuked the mmap for us (see ttm_bo_unmap_virtual), * so if we moved from VRAM make sure to unlink this from the userfault * tracking.
*/ if (mem_type_is_vram(old_mem_type)) {
mutex_lock(&xe->mem_access.vram_userfault.lock); if (!list_empty(&bo->vram_userfault_link))
list_del_init(&bo->vram_userfault_link);
mutex_unlock(&xe->mem_access.vram_userfault.lock);
}
/* Bo creation path, moving to system or TT. */ if ((!old_mem && ttm) && !handle_system_ccs) { if (new_mem->mem_type == XE_PL_TT)
ret = xe_tt_map_sg(xe, ttm); if (!ret)
ttm_bo_move_null(ttm_bo, new_mem); goto out;
}
if (ttm_bo->type == ttm_bo_type_sg) { if (new_mem->mem_type == XE_PL_SYSTEM)
ret = xe_bo_move_notify(bo, ctx); if (!ret)
ret = xe_bo_move_dmabuf(ttm_bo, new_mem); return ret;
}
/* * Failed multi-hop where the old_mem is still marked as * TTM_PL_FLAG_TEMPORARY, should just be a dummy move.
*/ if (old_mem_type == XE_PL_TT &&
new_mem->mem_type == XE_PL_TT) {
ttm_bo_move_null(ttm_bo, new_mem); goto out;
}
if (!move_lacks_source && !xe_bo_is_pinned(bo)) {
ret = xe_bo_move_notify(bo, ctx); if (ret) goto out;
}
if (old_mem_type == XE_PL_TT &&
new_mem->mem_type == XE_PL_SYSTEM) { long timeout = dma_resv_wait_timeout(ttm_bo->base.resv,
DMA_RESV_USAGE_BOOKKEEP, false,
MAX_SCHEDULE_TIMEOUT); if (timeout < 0) {
ret = timeout; goto out;
}
if (!handle_system_ccs) {
ttm_bo_move_null(ttm_bo, new_mem); goto out;
}
}
xe_assert(xe, migrate);
trace_xe_bo_move(bo, new_mem->mem_type, old_mem_type, move_lacks_source); if (xe_rpm_reclaim_safe(xe)) { /* * We might be called through swapout in the validation path of * another TTM device, so acquire rpm here.
*/
xe_pm_runtime_get(xe);
} else {
drm_WARN_ON(&xe->drm, handle_system_ccs);
xe_pm_runtime_get_noresume(xe);
}
if (move_lacks_source) {
u32 flags = 0;
if (mem_type_is_vram(new_mem->mem_type))
flags |= XE_MIGRATE_CLEAR_FLAG_FULL; elseif (handle_system_ccs)
flags |= XE_MIGRATE_CLEAR_FLAG_CCS_DATA;
fence = xe_migrate_clear(migrate, bo, new_mem, flags);
} else {
fence = xe_migrate_copy(migrate, bo, bo, old_mem, new_mem,
handle_system_ccs);
} if (IS_ERR(fence)) {
ret = PTR_ERR(fence);
xe_pm_runtime_put(xe); goto out;
} if (!move_lacks_source) {
ret = ttm_bo_move_accel_cleanup(ttm_bo, fence, evict, true,
new_mem); if (ret) {
dma_fence_wait(fence, false);
ttm_bo_move_null(ttm_bo, new_mem);
ret = 0;
}
} else { /* * ttm_bo_move_accel_cleanup() may blow up if * bo->resource == NULL, so just attach the * fence and set the new resource.
*/
dma_resv_add_fence(ttm_bo->base.resv, fence,
DMA_RESV_USAGE_KERNEL);
ttm_bo_move_null(ttm_bo, new_mem);
}
dma_fence_put(fence);
xe_pm_runtime_put(xe);
out: if ((!ttm_bo->resource || ttm_bo->resource->mem_type == XE_PL_SYSTEM) &&
ttm_bo->ttm) { long timeout = dma_resv_wait_timeout(ttm_bo->base.resv,
DMA_RESV_USAGE_KERNEL, false,
MAX_SCHEDULE_TIMEOUT); if (timeout < 0)
ret = timeout;
if (!ttm_bo_eviction_valuable(bo, place)) returnfalse;
if (!xe_bo_is_xe_bo(bo)) returntrue;
drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) { if (xe_vm_is_validating(gpuvm_to_vm(vm_bo->vm))) returnfalse;
}
returntrue;
}
/** * xe_bo_shrink() - Try to shrink an xe bo. * @ctx: The struct ttm_operation_ctx used for shrinking. * @bo: The TTM buffer object whose pages to shrink. * @flags: Flags governing the shrink behaviour. * @scanned: Pointer to a counter of the number of pages * attempted to shrink. * * Try to shrink- or purge a bo, and if it succeeds, unmap dma. * Note that we need to be able to handle also non xe bos * (ghost bos), but only if the struct ttm_tt is embedded in * a struct xe_ttm_tt. When the function attempts to shrink * the pages of a buffer object, The value pointed to by @scanned * is updated. * * Return: The number of pages shrunken or purged, or negative error * code on failure.
*/ long xe_bo_shrink(struct ttm_operation_ctx *ctx, struct ttm_buffer_object *bo, conststruct xe_bo_shrink_flags flags, unsignedlong *scanned)
{ struct ttm_tt *tt = bo->ttm; struct xe_ttm_tt *xe_tt = container_of(tt, struct xe_ttm_tt, ttm); struct ttm_place place = {.mem_type = bo->resource->mem_type}; struct xe_bo *xe_bo = ttm_to_xe_bo(bo); struct xe_device *xe = ttm_to_xe_device(bo->bdev); bool needs_rpm; long lret = 0L;
if (!(tt->page_flags & TTM_TT_FLAG_EXTERNAL_MAPPABLE) ||
(flags.purge && !xe_tt->purgeable)) return -EBUSY;
if (!xe_bo_eviction_valuable(bo, &place)) return -EBUSY;
if (!xe_bo_is_xe_bo(bo) || !xe_bo_get_unless_zero(xe_bo)) return xe_bo_shrink_purge(ctx, bo, scanned);
if (xe_tt->purgeable) { if (bo->resource->mem_type != XE_PL_SYSTEM)
lret = xe_bo_move_notify(xe_bo, ctx); if (!lret)
lret = xe_bo_shrink_purge(ctx, bo, scanned); goto out_unref;
}
/* System CCS needs gpu copy when moving PL_TT -> PL_SYSTEM */
needs_rpm = (!IS_DGFX(xe) && bo->resource->mem_type != XE_PL_SYSTEM &&
xe_bo_needs_ccs_pages(xe_bo)); if (needs_rpm && !xe_pm_runtime_get_if_active(xe)) goto out_unref;
/** * xe_bo_notifier_prepare_pinned() - Prepare a pinned VRAM object to be backed * up in system memory. * @bo: The buffer object to prepare. * * On successful completion, the object backup pages are allocated. Expectation * is that this is called from the PM notifier, prior to suspend/hibernation. * * Return: 0 on success. Negative error code on failure.
*/ int xe_bo_notifier_prepare_pinned(struct xe_bo *bo)
{ struct xe_device *xe = ttm_to_xe_device(bo->ttm.bdev); struct xe_bo *backup; int ret = 0;
xe_bo_lock(bo, false);
xe_assert(xe, !bo->backup_obj);
/* * Since this is called from the PM notifier we might have raced with * someone unpinning this after we dropped the pinned list lock and * grabbing the above bo lock.
*/ if (!xe_bo_is_pinned(bo)) goto out_unlock_bo;
if (!xe_bo_is_vram(bo)) goto out_unlock_bo;
if (bo->flags & XE_BO_FLAG_PINNED_NORESTORE) goto out_unlock_bo;
backup->parent_obj = xe_bo_get(bo); /* Released by bo_destroy */
ttm_bo_pin(&backup->ttm);
bo->backup_obj = backup;
out_unlock_bo:
xe_bo_unlock(bo); return ret;
}
/** * xe_bo_notifier_unprepare_pinned() - Undo the previous prepare operation. * @bo: The buffer object to undo the prepare for. * * Always returns 0. The backup object is removed, if still present. Expectation * it that this called from the PM notifier when undoing the prepare step. * * Return: Always returns 0.
*/ int xe_bo_notifier_unprepare_pinned(struct xe_bo *bo)
{
xe_bo_lock(bo, false); if (bo->backup_obj) {
ttm_bo_unpin(&bo->backup_obj->ttm);
xe_bo_put(bo->backup_obj);
bo->backup_obj = NULL;
}
xe_bo_unlock(bo);
return 0;
}
/** * xe_bo_evict_pinned() - Evict a pinned VRAM object to system memory * @bo: The buffer object to move. * * On successful completion, the object memory will be moved to system memory. * * This is needed to for special handling of pinned VRAM object during * suspend-resume. * * Return: 0 on success. Negative error code on failure.
*/ int xe_bo_evict_pinned(struct xe_bo *bo)
{ struct xe_device *xe = ttm_to_xe_device(bo->ttm.bdev); struct xe_bo *backup = bo->backup_obj; bool backup_created = false; bool unmap = false; int ret = 0;
xe_bo_lock(bo, false);
if (WARN_ON(!bo->ttm.resource)) {
ret = -EINVAL; goto out_unlock_bo;
}
if (WARN_ON(!xe_bo_is_pinned(bo))) {
ret = -EINVAL; goto out_unlock_bo;
}
if (!xe_bo_is_vram(bo)) goto out_unlock_bo;
if (bo->flags & XE_BO_FLAG_PINNED_NORESTORE) goto out_unlock_bo;
if (!backup) {
backup = ___xe_bo_create_locked(xe, NULL, NULL, bo->ttm.base.resv,
NULL, xe_bo_size(bo),
DRM_XE_GEM_CPU_CACHING_WB, ttm_bo_type_kernel,
XE_BO_FLAG_SYSTEM | XE_BO_FLAG_NEEDS_CPU_ACCESS |
XE_BO_FLAG_PINNED); if (IS_ERR(backup)) {
ret = PTR_ERR(backup); goto out_unlock_bo;
}
backup->parent_obj = xe_bo_get(bo); /* Released by bo_destroy */
backup_created = true;
}
out_backup:
xe_bo_vunmap(backup); if (ret && backup_created)
xe_bo_put(backup);
out_unlock_bo: if (unmap)
xe_bo_vunmap(bo);
xe_bo_unlock(bo); return ret;
}
/** * xe_bo_restore_pinned() - Restore a pinned VRAM object * @bo: The buffer object to move. * * On successful completion, the object memory will be moved back to VRAM. * * This is needed to for special handling of pinned VRAM object during * suspend-resume. * * Return: 0 on success. Negative error code on failure.
*/ int xe_bo_restore_pinned(struct xe_bo *bo)
{ struct ttm_operation_ctx ctx = {
.interruptible = false,
.gfp_retry_mayfail = false,
}; struct xe_device *xe = ttm_to_xe_device(bo->ttm.bdev); struct xe_bo *backup = bo->backup_obj; bool unmap = false; int ret;
if (!backup) return 0;
xe_bo_lock(bo, false);
if (!xe_bo_is_pinned(backup)) {
ret = ttm_bo_validate(&backup->ttm, &backup->placement, &ctx); if (ret) goto out_unlock_bo;
}
/* * TODO: Move this function to TTM so we don't rely on how TTM does its * locking, thereby abusing TTM internals.
*/ staticbool xe_ttm_bo_lock_in_destructor(struct ttm_buffer_object *ttm_bo)
{ struct xe_device *xe = ttm_to_xe_device(ttm_bo->bdev); bool locked;
xe_assert(xe, !kref_read(&ttm_bo->kref));
/* * We can typically only race with TTM trylocking under the * lru_lock, which will immediately be unlocked again since * the ttm_bo refcount is zero at this point. So trylocking *should* * always succeed here, as long as we hold the lru lock.
*/
spin_lock(&ttm_bo->bdev->lru_lock);
locked = dma_resv_trylock(ttm_bo->base.resv);
spin_unlock(&ttm_bo->bdev->lru_lock);
xe_assert(xe, locked);
bo = ttm_to_xe_bo(ttm_bo);
xe_assert(xe_bo_device(bo), !(bo->created && kref_read(&ttm_bo->base.refcount)));
/* * Corner case where TTM fails to allocate memory and this BOs resv * still points the VMs resv
*/ if (ttm_bo->base.resv != &ttm_bo->base._resv) return;
if (!xe_ttm_bo_lock_in_destructor(ttm_bo)) return;
/* * Scrub the preempt fences if any. The unbind fence is already * attached to the resv. * TODO: Don't do this for external bos once we scrub them after * unbind.
*/
dma_resv_for_each_fence(&cursor, ttm_bo->base.resv,
DMA_RESV_USAGE_BOOKKEEP, fence) { if (xe_fence_is_xe_preempt(fence) &&
!dma_fence_is_signaled(fence)) { if (!replacement)
replacement = dma_fence_get_stub();
staticvoid xe_ttm_bo_delete_mem_notify(struct ttm_buffer_object *ttm_bo)
{ if (!xe_bo_is_xe_bo(ttm_bo)) return;
/* * Object is idle and about to be destroyed. Release the * dma-buf attachment.
*/ if (ttm_bo->type == ttm_bo_type_sg && ttm_bo->sg) { struct xe_ttm_tt *xe_tt = container_of(ttm_bo->ttm, struct xe_ttm_tt, ttm);
for_each_tile(tile, xe, id) if (bo->ggtt_node[id] && bo->ggtt_node[id]->base.size)
xe_ggtt_remove_bo(tile->mem.ggtt, bo);
#ifdef CONFIG_PROC_FS if (bo->client)
xe_drm_client_remove_bo(bo); #endif
if (bo->vm && xe_bo_is_user(bo))
xe_vm_put(bo->vm);
if (bo->parent_obj)
xe_bo_put(bo->parent_obj);
mutex_lock(&xe->mem_access.vram_userfault.lock); if (!list_empty(&bo->vram_userfault_link))
list_del(&bo->vram_userfault_link);
mutex_unlock(&xe->mem_access.vram_userfault.lock);
kfree(bo);
}
staticvoid xe_gem_object_free(struct drm_gem_object *obj)
{ /* Our BO reference counting scheme works as follows: * * The gem object kref is typically used throughout the driver, * and the gem object holds a ttm_buffer_object refcount, so * that when the last gem object reference is put, which is when * we end up in this function, we put also that ttm_buffer_object * refcount. Anything using gem interfaces is then no longer * allowed to access the object in a way that requires a gem * refcount, including locking the object. * * driver ttm callbacks is allowed to use the ttm_buffer_object * refcount directly if needed.
*/
__xe_bo_vunmap(gem_to_xe_bo(obj));
ttm_bo_put(container_of(obj, struct ttm_buffer_object, base));
}
ret = ttm_bo_vm_reserve(tbo, vmf); if (ret) goto out;
if (drm_dev_enter(ddev, &idx)) {
trace_xe_bo_cpu_fault(bo);
ret = ttm_bo_vm_fault_reserved(vmf, vmf->vma->vm_page_prot,
TTM_BO_VM_NUM_PREFAULT);
drm_dev_exit(idx);
if (ret == VM_FAULT_RETRY &&
!(vmf->flags & FAULT_FLAG_RETRY_NOWAIT)) goto out;
/* * ttm_bo_vm_reserve() already has dma_resv_lock.
*/ if (ret == VM_FAULT_NOPAGE &&
mem_type_is_vram(tbo->resource->mem_type)) {
mutex_lock(&xe->mem_access.vram_userfault.lock); if (list_empty(&bo->vram_userfault_link))
list_add(&bo->vram_userfault_link,
&xe->mem_access.vram_userfault.list);
mutex_unlock(&xe->mem_access.vram_userfault.lock);
}
} else {
ret = ttm_bo_vm_dummy_page(vmf, vmf->vma->vm_page_prot);
}
dma_resv_unlock(tbo->base.resv);
out: if (needs_rpm)
xe_pm_runtime_put(xe);
return ret;
}
staticint xe_bo_vm_access(struct vm_area_struct *vma, unsignedlong addr, void *buf, int len, int write)
{ struct ttm_buffer_object *ttm_bo = vma->vm_private_data; struct xe_bo *bo = ttm_to_xe_bo(ttm_bo); struct xe_device *xe = xe_bo_device(bo); int ret;
xe_pm_runtime_get(xe);
ret = ttm_bo_vm_access(vma, addr, buf, len, write);
xe_pm_runtime_put(xe);
return ret;
}
/** * xe_bo_read() - Read from an xe_bo * @bo: The buffer object to read from. * @offset: The byte offset to start reading from. * @dst: Location to store the read. * @size: Size in bytes for the read. * * Read @size bytes from the @bo, starting from @offset, storing into @dst. * * Return: Zero on success, or negative error.
*/ int xe_bo_read(struct xe_bo *bo, u64 offset, void *dst, int size)
{ int ret;
ret = ttm_bo_access(&bo->ttm, offset, dst, size, 0); if (ret >= 0 && ret != size)
ret = -EIO; elseif (ret == size)
ret = 0;
/** * xe_bo_alloc - Allocate storage for a struct xe_bo * * This function is intended to allocate storage to be used for input * to __xe_bo_create_locked(), in the case a pointer to the bo to be * created is needed before the call to __xe_bo_create_locked(). * If __xe_bo_create_locked ends up never to be called, then the * storage allocated with this function needs to be freed using * xe_bo_free(). * * Return: A pointer to an uninitialized struct xe_bo on success, * ERR_PTR(-ENOMEM) on error.
*/ struct xe_bo *xe_bo_alloc(void)
{ struct xe_bo *bo = kzalloc(sizeof(*bo), GFP_KERNEL);
if (!bo) return ERR_PTR(-ENOMEM);
return bo;
}
/** * xe_bo_free - Free storage allocated using xe_bo_alloc() * @bo: The buffer object storage. * * Refer to xe_bo_alloc() documentation for valid use-cases.
*/ void xe_bo_free(struct xe_bo *bo)
{
kfree(bo);
}
/* * The VRAM pages underneath are potentially still being accessed by the * GPU, as per async GPU clearing and async evictions. However TTM makes * sure to add any corresponding move/clear fences into the objects * dma-resv using the DMA_RESV_USAGE_KERNEL slot. * * For KMD internal buffers we don't care about GPU clearing, however we * still need to handle async evictions, where the VRAM is still being * accessed by the GPU. Most internal callers are not expecting this, * since they are missing the required synchronisation before accessing * the memory. To keep things simple just sync wait any kernel fences * here, if the buffer is designated KMD internal. * * For normal userspace objects we should already have the required * pipelining or sync waiting elsewhere, since we already have to deal * with things like async GPU clearing.
*/ if (type == ttm_bo_type_kernel) { long timeout = dma_resv_wait_timeout(bo->ttm.base.resv,
DMA_RESV_USAGE_KERNEL,
ctx.interruptible,
MAX_SCHEDULE_TIMEOUT);
if (timeout < 0) { if (!resv)
dma_resv_unlock(bo->ttm.base.resv);
xe_bo_put(bo); return ERR_PTR(timeout);
}
}
bo->created = true; if (bulk)
ttm_bo_set_bulk_move(&bo->ttm, bulk); else
ttm_bo_move_to_lru_tail_unlocked(&bo->ttm);
bo = ___xe_bo_create_locked(xe, bo, tile, vm ? xe_vm_resv(vm) : NULL,
vm && !xe_vm_in_fault_mode(vm) &&
flags & XE_BO_FLAG_USER ?
&vm->lru_bulk_move : NULL, size,
cpu_caching, type, flags); if (IS_ERR(bo)) return bo;
bo->min_align = alignment;
/* * Note that instead of taking a reference no the drm_gpuvm_resv_bo(), * to ensure the shared resv doesn't disappear under the bo, the bo * will keep a reference to the vm, and avoid circular references * by having all the vm's bo refereferences released at vm close * time.
*/ if (vm && xe_bo_is_user(bo))
xe_vm_get(vm);
bo->vm = vm;
if (bo->flags & XE_BO_FLAG_GGTT) { struct xe_tile *t;
u8 id;
if (!(bo->flags & XE_BO_FLAG_GGTT_ALL)) { if (!tile && flags & XE_BO_FLAG_STOLEN)
tile = xe_device_get_root_tile(xe);
/** * xe_managed_bo_reinit_in_vram * @xe: xe device * @tile: Tile where the new buffer will be created * @src: Managed buffer object allocated in system memory * * Replace a managed src buffer object allocated in system memory with a new * one allocated in vram, copying the data between them. * Buffer object in VRAM is not going to have the same GGTT address, the caller * is responsible for making sure that any old references to it are updated. * * Returns 0 for success, negative error code otherwise.
*/ int xe_managed_bo_reinit_in_vram(struct xe_device *xe, struct xe_tile *tile, struct xe_bo **src)
{ struct xe_bo *bo;
u32 dst_flags = XE_BO_FLAG_VRAM_IF_DGFX(tile) | XE_BO_FLAG_GGTT;
/* * XXX: This is in the VM bind data path, likely should calculate this once and * store, with a recalculation if the BO is moved.
*/
uint64_t vram_region_gpu_offset(struct ttm_resource *res)
{ struct xe_device *xe = ttm_to_xe_device(res->bo->bdev);
switch (res->mem_type) { case XE_PL_STOLEN: return xe_ttm_stolen_gpu_offset(xe); case XE_PL_TT: case XE_PL_SYSTEM: return 0; default: return res_to_mem_region(res)->dpa_base;
} return 0;
}
/** * xe_bo_pin_external - pin an external BO * @bo: buffer object to be pinned * @in_place: Pin in current placement, don't attempt to migrate. * * Pin an external (not tied to a VM, can be exported via dma-buf / prime FD) * BO. Unique call compared to xe_bo_pin as this function has it own set of * asserts and code to ensure evict / restore on suspend / resume. * * Returns 0 for success, negative error code otherwise.
*/ int xe_bo_pin_external(struct xe_bo *bo, bool in_place)
{ struct xe_device *xe = xe_bo_device(bo); int err;
ttm_bo_pin(&bo->ttm); if (bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm))
xe_ttm_tt_account_subtract(xe, bo->ttm.ttm);
/* * FIXME: If we always use the reserve / unreserve functions for locking * we do not need this.
*/
ttm_bo_move_to_lru_tail_unlocked(&bo->ttm);
return 0;
}
int xe_bo_pin(struct xe_bo *bo)
{ struct ttm_place *place = &bo->placements[0]; struct xe_device *xe = xe_bo_device(bo); int err;
/* We currently don't expect user BO to be pinned */
xe_assert(xe, !xe_bo_is_user(bo));
/* Pinned object must be in GGTT or have pinned flag */
xe_assert(xe, bo->flags & (XE_BO_FLAG_PINNED |
XE_BO_FLAG_GGTT));
/* * No reason we can't support pinning imported dma-bufs we just don't * expect to pin an imported dma-buf.
*/
xe_assert(xe, !bo->ttm.base.import_attach);
/* We only expect at most 1 pin */
xe_assert(xe, !xe_bo_is_pinned(bo));
err = xe_bo_validate(bo, NULL, false); if (err) return err;
if (mem_type_is_vram(place->mem_type) || bo->flags & XE_BO_FLAG_GGTT) {
spin_lock(&xe->pinned.lock); if (bo->flags & XE_BO_FLAG_PINNED_LATE_RESTORE)
list_add_tail(&bo->pinned_link, &xe->pinned.late.kernel_bo_present); else
list_add_tail(&bo->pinned_link, &xe->pinned.early.kernel_bo_present);
spin_unlock(&xe->pinned.lock);
}
ttm_bo_pin(&bo->ttm); if (bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm))
xe_ttm_tt_account_subtract(xe, bo->ttm.ttm);
/* * FIXME: If we always use the reserve / unreserve functions for locking * we do not need this.
*/
ttm_bo_move_to_lru_tail_unlocked(&bo->ttm);
return 0;
}
/** * xe_bo_unpin_external - unpin an external BO * @bo: buffer object to be unpinned * * Unpin an external (not tied to a VM, can be exported via dma-buf / prime FD) * BO. Unique call compared to xe_bo_unpin as this function has it own set of * asserts and code to ensure evict / restore on suspend / resume. * * Returns 0 for success, negative error code otherwise.
*/ void xe_bo_unpin_external(struct xe_bo *bo)
{ struct xe_device *xe = xe_bo_device(bo);
if (bo->backup_obj) { if (xe_bo_is_pinned(bo->backup_obj))
ttm_bo_unpin(&bo->backup_obj->ttm);
xe_bo_put(bo->backup_obj);
bo->backup_obj = NULL;
}
}
ttm_bo_unpin(&bo->ttm); if (bo->ttm.ttm && ttm_tt_is_populated(bo->ttm.ttm))
xe_ttm_tt_account_add(xe, bo->ttm.ttm);
}
/** * xe_bo_validate() - Make sure the bo is in an allowed placement * @bo: The bo, * @vm: Pointer to a the vm the bo shares a locked dma_resv object with, or * NULL. Used together with @allow_res_evict. * @allow_res_evict: Whether it's allowed to evict bos sharing @vm's * reservation object. * * Make sure the bo is in allowed placement, migrating it if necessary. If * needed, other bos will be evicted. If bos selected for eviction shares * the @vm's reservation object, they can be evicted iff @allow_res_evict is * set to true, otherwise they will be bypassed. * * Return: 0 on success, negative error code on failure. May return * -EINTR or -ERESTARTSYS if internal waits are interrupted by a signal.
*/ int xe_bo_validate(struct xe_bo *bo, struct xe_vm *vm, bool allow_res_evict)
{ struct ttm_operation_ctx ctx = {
.interruptible = true,
.no_wait_gpu = false,
.gfp_retry_mayfail = true,
}; int ret;
if (xe_bo_is_pinned(bo)) return 0;
if (vm) {
lockdep_assert_held(&vm->lock);
xe_vm_assert_held(vm);
xe_vm_set_validating(vm, allow_res_evict);
trace_xe_bo_validate(bo);
ret = ttm_bo_validate(&bo->ttm, &bo->placement, &ctx);
xe_vm_clear_validating(vm, allow_res_evict);
return ret;
}
bool xe_bo_is_xe_bo(struct ttm_buffer_object *bo)
{ if (bo->destroy == &xe_ttm_bo_destroy) returntrue;
returnfalse;
}
/* * Resolve a BO address. There is no assert to check if the proper lock is held * so it should only be used in cases where it is not fatal to get the wrong * address, such as printing debug information, but not in cases where memory is * written based on this result.
*/
dma_addr_t __xe_bo_addr(struct xe_bo *bo, u64 offset, size_t page_size)
{ struct xe_device *xe = xe_bo_device(bo); struct xe_res_cursor cur;
u64 page;
int xe_bo_vmap(struct xe_bo *bo)
{ struct xe_device *xe = ttm_to_xe_device(bo->ttm.bdev); void *virtual; bool is_iomem; int ret;
xe_bo_assert_held(bo);
if (drm_WARN_ON(&xe->drm, !(bo->flags & XE_BO_FLAG_NEEDS_CPU_ACCESS) ||
!force_contiguous(bo->flags))) return -EINVAL;
if (!iosys_map_is_null(&bo->vmap)) return 0;
/* * We use this more or less deprecated interface for now since * ttm_bo_vmap() doesn't offer the optimization of kmapping * single page bos, which is done here. * TODO: Fix up ttm_bo_vmap to do that, or fix up ttm_bo_kmap * to use struct iosys_map.
*/
ret = ttm_bo_kmap(&bo->ttm, 0, xe_bo_size(bo) >> PAGE_SHIFT, &bo->kmap); if (ret) return ret;
virtual = ttm_kmap_obj_virtual(&bo->kmap, &is_iomem); if (is_iomem)
--> --------------------
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.