Currently the KMD is using enum i915_cache_level to set caching policy for buffer objects. This is flaky because the PAT index which really controls the caching behavior in PTE has far more levels than what's defined in the enum. In addition, the PAT index is platform dependent, having to translate between i915_cache_level and PAT index is not reliable, and makes the code more complicated. From UMD's perspective there is also a necessity to set caching policy for performance fine tuning. It's much easier for the UMD to directly use PAT index because the behavior of each PAT index is clearly defined in Bspec. Having the abstracted i915_cache_level sitting in between would only cause more ambiguity. PAT is expected to work much like MOCS already works today, and by design userspace is expected to select the index that exactly matches the desired behavior described in the hardware specification. For these reasons this patch replaces i915_cache_level with PAT index. Also note, the cache_level is not completely removed yet, because the KMD still has the need of creating buffer objects with simple cache settings such as cached, uncached, or writethrough. For kernel objects, cache_level is used for simplicity and backward compatibility. For Pre-gen12 platforms PAT can have 1:1 mapping to i915_cache_level, so these two are interchangeable. see the use of LEGACY_CACHELEVEL. One consequence of this change is that gen8_pte_encode is no longer working for gen12 platforms due to the fact that gen12 platforms has different PAT definitions. In the meantime the mtl_pte_encode introduced specfically for MTL becomes generic for all gen12 platforms. This patch renames the MTL PTE encode function into gen12_pte_encode and apply it to all gen12. Even though this change looks unrelated, but separating them would temporarily break gen12 PTE encoding, thus squash them in one patch. Special note: this patch changes the way caching behavior is controlled in the sense that some objects are left to be managed by userspace. For such objects we need to be careful not to change the userspace settings.There are kerneldoc and comments added around obj->cache_coherent, cache_dirty, and how to bypass the checkings by i915_gem_object_has_cache_level. For full understanding, these changes need to be looked at together with the two follow-up patches, one disables the {set|get}_caching ioctl's and the other adds set_pat extension to the GEM_CREATE uAPI. Bspec: 63019 Cc: Chris Wilson <chris.p.wilson@linux.intel.com> Signed-off-by: Fei Yang <fei.yang@intel.com> Reviewed-by: Andi Shyti <andi.shyti@linux.intel.com> Reviewed-by: Matt Roper <matthew.d.roper@intel.com> Signed-off-by: Andi Shyti <andi.shyti@linux.intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/20230509165200.1740-3-fei.yang@intel.com
758 lines
22 KiB
C
758 lines
22 KiB
C
// SPDX-License-Identifier: MIT
|
|
/*
|
|
* Copyright © 2021 Intel Corporation
|
|
*/
|
|
|
|
#include <drm/ttm/ttm_tt.h>
|
|
|
|
#include "i915_deps.h"
|
|
#include "i915_drv.h"
|
|
#include "intel_memory_region.h"
|
|
#include "intel_region_ttm.h"
|
|
|
|
#include "gem/i915_gem_object.h"
|
|
#include "gem/i915_gem_region.h"
|
|
#include "gem/i915_gem_ttm.h"
|
|
#include "gem/i915_gem_ttm_move.h"
|
|
|
|
#include "gt/intel_engine_pm.h"
|
|
#include "gt/intel_gt.h"
|
|
#include "gt/intel_migrate.h"
|
|
|
|
/**
|
|
* DOC: Selftest failure modes for failsafe migration:
|
|
*
|
|
* For fail_gpu_migration, the gpu blit scheduled is always a clear blit
|
|
* rather than a copy blit, and then we force the failure paths as if
|
|
* the blit fence returned an error.
|
|
*
|
|
* For fail_work_allocation we fail the kmalloc of the async worker, we
|
|
* sync the gpu blit. If it then fails, or fail_gpu_migration is set to
|
|
* true, then a memcpy operation is performed sync.
|
|
*/
|
|
#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST)
|
|
static bool fail_gpu_migration;
|
|
static bool fail_work_allocation;
|
|
static bool ban_memcpy;
|
|
|
|
void i915_ttm_migrate_set_failure_modes(bool gpu_migration,
|
|
bool work_allocation)
|
|
{
|
|
fail_gpu_migration = gpu_migration;
|
|
fail_work_allocation = work_allocation;
|
|
}
|
|
|
|
void i915_ttm_migrate_set_ban_memcpy(bool ban)
|
|
{
|
|
ban_memcpy = ban;
|
|
}
|
|
#endif
|
|
|
|
static enum i915_cache_level
|
|
i915_ttm_cache_level(struct drm_i915_private *i915, struct ttm_resource *res,
|
|
struct ttm_tt *ttm)
|
|
{
|
|
return ((HAS_LLC(i915) || HAS_SNOOP(i915)) &&
|
|
!i915_ttm_gtt_binds_lmem(res) &&
|
|
ttm->caching == ttm_cached) ? I915_CACHE_LLC :
|
|
I915_CACHE_NONE;
|
|
}
|
|
|
|
static struct intel_memory_region *
|
|
i915_ttm_region(struct ttm_device *bdev, int ttm_mem_type)
|
|
{
|
|
struct drm_i915_private *i915 = container_of(bdev, typeof(*i915), bdev);
|
|
|
|
/* There's some room for optimization here... */
|
|
GEM_BUG_ON(ttm_mem_type != I915_PL_SYSTEM &&
|
|
ttm_mem_type < I915_PL_LMEM0);
|
|
if (ttm_mem_type == I915_PL_SYSTEM)
|
|
return intel_memory_region_lookup(i915, INTEL_MEMORY_SYSTEM,
|
|
0);
|
|
|
|
return intel_memory_region_lookup(i915, INTEL_MEMORY_LOCAL,
|
|
ttm_mem_type - I915_PL_LMEM0);
|
|
}
|
|
|
|
/**
|
|
* i915_ttm_adjust_domains_after_move - Adjust the GEM domains after a
|
|
* TTM move
|
|
* @obj: The gem object
|
|
*/
|
|
void i915_ttm_adjust_domains_after_move(struct drm_i915_gem_object *obj)
|
|
{
|
|
struct ttm_buffer_object *bo = i915_gem_to_ttm(obj);
|
|
|
|
if (i915_ttm_cpu_maps_iomem(bo->resource) || bo->ttm->caching != ttm_cached) {
|
|
obj->write_domain = I915_GEM_DOMAIN_WC;
|
|
obj->read_domains = I915_GEM_DOMAIN_WC;
|
|
} else {
|
|
obj->write_domain = I915_GEM_DOMAIN_CPU;
|
|
obj->read_domains = I915_GEM_DOMAIN_CPU;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* i915_ttm_adjust_gem_after_move - Adjust the GEM state after a TTM move
|
|
* @obj: The gem object
|
|
*
|
|
* Adjusts the GEM object's region, mem_flags and cache coherency after a
|
|
* TTM move.
|
|
*/
|
|
void i915_ttm_adjust_gem_after_move(struct drm_i915_gem_object *obj)
|
|
{
|
|
struct ttm_buffer_object *bo = i915_gem_to_ttm(obj);
|
|
unsigned int cache_level;
|
|
unsigned int mem_flags;
|
|
unsigned int i;
|
|
int mem_type;
|
|
|
|
/*
|
|
* We might have been purged (or swapped out) if the resource is NULL,
|
|
* in which case the SYSTEM placement is the closest match to describe
|
|
* the current domain. If the object is ever used in this state then we
|
|
* will require moving it again.
|
|
*/
|
|
if (!bo->resource) {
|
|
mem_flags = I915_BO_FLAG_STRUCT_PAGE;
|
|
mem_type = I915_PL_SYSTEM;
|
|
cache_level = I915_CACHE_NONE;
|
|
} else {
|
|
mem_flags = i915_ttm_cpu_maps_iomem(bo->resource) ? I915_BO_FLAG_IOMEM :
|
|
I915_BO_FLAG_STRUCT_PAGE;
|
|
mem_type = bo->resource->mem_type;
|
|
cache_level = i915_ttm_cache_level(to_i915(bo->base.dev), bo->resource,
|
|
bo->ttm);
|
|
}
|
|
|
|
/*
|
|
* If object was moved to an allowable region, update the object
|
|
* region to consider it migrated. Note that if it's currently not
|
|
* in an allowable region, it's evicted and we don't update the
|
|
* object region.
|
|
*/
|
|
if (intel_region_to_ttm_type(obj->mm.region) != mem_type) {
|
|
for (i = 0; i < obj->mm.n_placements; ++i) {
|
|
struct intel_memory_region *mr = obj->mm.placements[i];
|
|
|
|
if (intel_region_to_ttm_type(mr) == mem_type &&
|
|
mr != obj->mm.region) {
|
|
i915_gem_object_release_memory_region(obj);
|
|
i915_gem_object_init_memory_region(obj, mr);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
obj->mem_flags &= ~(I915_BO_FLAG_STRUCT_PAGE | I915_BO_FLAG_IOMEM);
|
|
obj->mem_flags |= mem_flags;
|
|
|
|
i915_gem_object_set_cache_coherency(obj, cache_level);
|
|
}
|
|
|
|
/**
|
|
* i915_ttm_move_notify - Prepare an object for move
|
|
* @bo: The ttm buffer object.
|
|
*
|
|
* This function prepares an object for move by removing all GPU bindings,
|
|
* removing all CPU mapings and finally releasing the pages sg-table.
|
|
*
|
|
* Return: 0 if successful, negative error code on error.
|
|
*/
|
|
int i915_ttm_move_notify(struct ttm_buffer_object *bo)
|
|
{
|
|
struct drm_i915_gem_object *obj = i915_ttm_to_gem(bo);
|
|
int ret;
|
|
|
|
/*
|
|
* Note: The async unbinding here will actually transform the
|
|
* blocking wait for unbind into a wait before finally submitting
|
|
* evict / migration blit and thus stall the migration timeline
|
|
* which may not be good for overall throughput. We should make
|
|
* sure we await the unbind fences *after* the migration blit
|
|
* instead of *before* as we currently do.
|
|
*/
|
|
ret = i915_gem_object_unbind(obj, I915_GEM_OBJECT_UNBIND_ACTIVE |
|
|
I915_GEM_OBJECT_UNBIND_ASYNC);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = __i915_gem_object_put_pages(obj);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct dma_fence *i915_ttm_accel_move(struct ttm_buffer_object *bo,
|
|
bool clear,
|
|
struct ttm_resource *dst_mem,
|
|
struct ttm_tt *dst_ttm,
|
|
struct sg_table *dst_st,
|
|
const struct i915_deps *deps)
|
|
{
|
|
struct drm_i915_private *i915 = container_of(bo->bdev, typeof(*i915),
|
|
bdev);
|
|
struct drm_i915_gem_object *obj = i915_ttm_to_gem(bo);
|
|
struct i915_request *rq;
|
|
struct ttm_tt *src_ttm = bo->ttm;
|
|
enum i915_cache_level src_level, dst_level;
|
|
int ret;
|
|
|
|
if (!to_gt(i915)->migrate.context || intel_gt_is_wedged(to_gt(i915)))
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
/* With fail_gpu_migration, we always perform a GPU clear. */
|
|
if (I915_SELFTEST_ONLY(fail_gpu_migration))
|
|
clear = true;
|
|
|
|
dst_level = i915_ttm_cache_level(i915, dst_mem, dst_ttm);
|
|
if (clear) {
|
|
if (bo->type == ttm_bo_type_kernel &&
|
|
!I915_SELFTEST_ONLY(fail_gpu_migration))
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
intel_engine_pm_get(to_gt(i915)->migrate.context->engine);
|
|
ret = intel_context_migrate_clear(to_gt(i915)->migrate.context, deps,
|
|
dst_st->sgl,
|
|
i915_gem_get_pat_index(i915, dst_level),
|
|
i915_ttm_gtt_binds_lmem(dst_mem),
|
|
0, &rq);
|
|
} else {
|
|
struct i915_refct_sgt *src_rsgt =
|
|
i915_ttm_resource_get_st(obj, bo->resource);
|
|
|
|
if (IS_ERR(src_rsgt))
|
|
return ERR_CAST(src_rsgt);
|
|
|
|
src_level = i915_ttm_cache_level(i915, bo->resource, src_ttm);
|
|
intel_engine_pm_get(to_gt(i915)->migrate.context->engine);
|
|
ret = intel_context_migrate_copy(to_gt(i915)->migrate.context,
|
|
deps, src_rsgt->table.sgl,
|
|
i915_gem_get_pat_index(i915, src_level),
|
|
i915_ttm_gtt_binds_lmem(bo->resource),
|
|
dst_st->sgl,
|
|
i915_gem_get_pat_index(i915, dst_level),
|
|
i915_ttm_gtt_binds_lmem(dst_mem),
|
|
&rq);
|
|
|
|
i915_refct_sgt_put(src_rsgt);
|
|
}
|
|
|
|
intel_engine_pm_put(to_gt(i915)->migrate.context->engine);
|
|
|
|
if (ret && rq) {
|
|
i915_request_wait(rq, 0, MAX_SCHEDULE_TIMEOUT);
|
|
i915_request_put(rq);
|
|
}
|
|
|
|
return ret ? ERR_PTR(ret) : &rq->fence;
|
|
}
|
|
|
|
/**
|
|
* struct i915_ttm_memcpy_arg - argument for the bo memcpy functionality.
|
|
* @_dst_iter: Storage space for the destination kmap iterator.
|
|
* @_src_iter: Storage space for the source kmap iterator.
|
|
* @dst_iter: Pointer to the destination kmap iterator.
|
|
* @src_iter: Pointer to the source kmap iterator.
|
|
* @num_pages: Number of pages
|
|
* @clear: Whether to clear instead of copy.
|
|
* @src_rsgt: Refcounted scatter-gather list of source memory.
|
|
* @dst_rsgt: Refcounted scatter-gather list of destination memory.
|
|
*/
|
|
struct i915_ttm_memcpy_arg {
|
|
union {
|
|
struct ttm_kmap_iter_tt tt;
|
|
struct ttm_kmap_iter_iomap io;
|
|
} _dst_iter,
|
|
_src_iter;
|
|
struct ttm_kmap_iter *dst_iter;
|
|
struct ttm_kmap_iter *src_iter;
|
|
unsigned long num_pages;
|
|
bool clear;
|
|
struct i915_refct_sgt *src_rsgt;
|
|
struct i915_refct_sgt *dst_rsgt;
|
|
};
|
|
|
|
/**
|
|
* struct i915_ttm_memcpy_work - Async memcpy worker under a dma-fence.
|
|
* @fence: The dma-fence.
|
|
* @work: The work struct use for the memcpy work.
|
|
* @lock: The fence lock. Not used to protect anything else ATM.
|
|
* @irq_work: Low latency worker to signal the fence since it can't be done
|
|
* from the callback for lockdep reasons.
|
|
* @cb: Callback for the accelerated migration fence.
|
|
* @arg: The argument for the memcpy functionality.
|
|
* @i915: The i915 pointer.
|
|
* @obj: The GEM object.
|
|
* @memcpy_allowed: Instead of processing the @arg, and falling back to memcpy
|
|
* or memset, we wedge the device and set the @obj unknown_state, to prevent
|
|
* further access to the object with the CPU or GPU. On some devices we might
|
|
* only be permitted to use the blitter engine for such operations.
|
|
*/
|
|
struct i915_ttm_memcpy_work {
|
|
struct dma_fence fence;
|
|
struct work_struct work;
|
|
spinlock_t lock;
|
|
struct irq_work irq_work;
|
|
struct dma_fence_cb cb;
|
|
struct i915_ttm_memcpy_arg arg;
|
|
struct drm_i915_private *i915;
|
|
struct drm_i915_gem_object *obj;
|
|
bool memcpy_allowed;
|
|
};
|
|
|
|
static void i915_ttm_move_memcpy(struct i915_ttm_memcpy_arg *arg)
|
|
{
|
|
ttm_move_memcpy(arg->clear, arg->num_pages,
|
|
arg->dst_iter, arg->src_iter);
|
|
}
|
|
|
|
static void i915_ttm_memcpy_init(struct i915_ttm_memcpy_arg *arg,
|
|
struct ttm_buffer_object *bo, bool clear,
|
|
struct ttm_resource *dst_mem,
|
|
struct ttm_tt *dst_ttm,
|
|
struct i915_refct_sgt *dst_rsgt)
|
|
{
|
|
struct drm_i915_gem_object *obj = i915_ttm_to_gem(bo);
|
|
struct intel_memory_region *dst_reg, *src_reg;
|
|
|
|
dst_reg = i915_ttm_region(bo->bdev, dst_mem->mem_type);
|
|
src_reg = i915_ttm_region(bo->bdev, bo->resource->mem_type);
|
|
GEM_BUG_ON(!dst_reg || !src_reg);
|
|
|
|
arg->dst_iter = !i915_ttm_cpu_maps_iomem(dst_mem) ?
|
|
ttm_kmap_iter_tt_init(&arg->_dst_iter.tt, dst_ttm) :
|
|
ttm_kmap_iter_iomap_init(&arg->_dst_iter.io, &dst_reg->iomap,
|
|
&dst_rsgt->table, dst_reg->region.start);
|
|
|
|
arg->src_iter = !i915_ttm_cpu_maps_iomem(bo->resource) ?
|
|
ttm_kmap_iter_tt_init(&arg->_src_iter.tt, bo->ttm) :
|
|
ttm_kmap_iter_iomap_init(&arg->_src_iter.io, &src_reg->iomap,
|
|
&obj->ttm.cached_io_rsgt->table,
|
|
src_reg->region.start);
|
|
arg->clear = clear;
|
|
arg->num_pages = bo->base.size >> PAGE_SHIFT;
|
|
|
|
arg->dst_rsgt = i915_refct_sgt_get(dst_rsgt);
|
|
arg->src_rsgt = clear ? NULL :
|
|
i915_ttm_resource_get_st(obj, bo->resource);
|
|
}
|
|
|
|
static void i915_ttm_memcpy_release(struct i915_ttm_memcpy_arg *arg)
|
|
{
|
|
i915_refct_sgt_put(arg->src_rsgt);
|
|
i915_refct_sgt_put(arg->dst_rsgt);
|
|
}
|
|
|
|
static void __memcpy_work(struct work_struct *work)
|
|
{
|
|
struct i915_ttm_memcpy_work *copy_work =
|
|
container_of(work, typeof(*copy_work), work);
|
|
struct i915_ttm_memcpy_arg *arg = ©_work->arg;
|
|
bool cookie;
|
|
|
|
/*
|
|
* FIXME: We need to take a closer look here. We should be able to plonk
|
|
* this into the fence critical section.
|
|
*/
|
|
if (!copy_work->memcpy_allowed) {
|
|
struct intel_gt *gt;
|
|
unsigned int id;
|
|
|
|
for_each_gt(gt, copy_work->i915, id)
|
|
intel_gt_set_wedged(gt);
|
|
}
|
|
|
|
cookie = dma_fence_begin_signalling();
|
|
|
|
if (copy_work->memcpy_allowed) {
|
|
i915_ttm_move_memcpy(arg);
|
|
} else {
|
|
/*
|
|
* Prevent further use of the object. Any future GTT binding or
|
|
* CPU access is not allowed once we signal the fence. Outside
|
|
* of the fence critical section, we then also then wedge the gpu
|
|
* to indicate the device is not functional.
|
|
*
|
|
* The below dma_fence_signal() is our write-memory-barrier.
|
|
*/
|
|
copy_work->obj->mm.unknown_state = true;
|
|
}
|
|
|
|
dma_fence_end_signalling(cookie);
|
|
|
|
dma_fence_signal(©_work->fence);
|
|
|
|
i915_ttm_memcpy_release(arg);
|
|
i915_gem_object_put(copy_work->obj);
|
|
dma_fence_put(©_work->fence);
|
|
}
|
|
|
|
static void __memcpy_irq_work(struct irq_work *irq_work)
|
|
{
|
|
struct i915_ttm_memcpy_work *copy_work =
|
|
container_of(irq_work, typeof(*copy_work), irq_work);
|
|
struct i915_ttm_memcpy_arg *arg = ©_work->arg;
|
|
|
|
dma_fence_signal(©_work->fence);
|
|
i915_ttm_memcpy_release(arg);
|
|
i915_gem_object_put(copy_work->obj);
|
|
dma_fence_put(©_work->fence);
|
|
}
|
|
|
|
static void __memcpy_cb(struct dma_fence *fence, struct dma_fence_cb *cb)
|
|
{
|
|
struct i915_ttm_memcpy_work *copy_work =
|
|
container_of(cb, typeof(*copy_work), cb);
|
|
|
|
if (unlikely(fence->error || I915_SELFTEST_ONLY(fail_gpu_migration))) {
|
|
INIT_WORK(©_work->work, __memcpy_work);
|
|
queue_work(system_unbound_wq, ©_work->work);
|
|
} else {
|
|
init_irq_work(©_work->irq_work, __memcpy_irq_work);
|
|
irq_work_queue(©_work->irq_work);
|
|
}
|
|
}
|
|
|
|
static const char *get_driver_name(struct dma_fence *fence)
|
|
{
|
|
return "i915_ttm_memcpy_work";
|
|
}
|
|
|
|
static const char *get_timeline_name(struct dma_fence *fence)
|
|
{
|
|
return "unbound";
|
|
}
|
|
|
|
static const struct dma_fence_ops dma_fence_memcpy_ops = {
|
|
.get_driver_name = get_driver_name,
|
|
.get_timeline_name = get_timeline_name,
|
|
};
|
|
|
|
static struct dma_fence *
|
|
i915_ttm_memcpy_work_arm(struct i915_ttm_memcpy_work *work,
|
|
struct dma_fence *dep)
|
|
{
|
|
int ret;
|
|
|
|
spin_lock_init(&work->lock);
|
|
dma_fence_init(&work->fence, &dma_fence_memcpy_ops, &work->lock, 0, 0);
|
|
dma_fence_get(&work->fence);
|
|
ret = dma_fence_add_callback(dep, &work->cb, __memcpy_cb);
|
|
if (ret) {
|
|
if (ret != -ENOENT)
|
|
dma_fence_wait(dep, false);
|
|
|
|
return ERR_PTR(I915_SELFTEST_ONLY(fail_gpu_migration) ? -EINVAL :
|
|
dep->error);
|
|
}
|
|
|
|
return &work->fence;
|
|
}
|
|
|
|
static bool i915_ttm_memcpy_allowed(struct ttm_buffer_object *bo,
|
|
struct ttm_resource *dst_mem)
|
|
{
|
|
if (i915_gem_object_needs_ccs_pages(i915_ttm_to_gem(bo)))
|
|
return false;
|
|
|
|
if (!(i915_ttm_resource_mappable(bo->resource) &&
|
|
i915_ttm_resource_mappable(dst_mem)))
|
|
return false;
|
|
|
|
return I915_SELFTEST_ONLY(ban_memcpy) ? false : true;
|
|
}
|
|
|
|
static struct dma_fence *
|
|
__i915_ttm_move(struct ttm_buffer_object *bo,
|
|
const struct ttm_operation_ctx *ctx, bool clear,
|
|
struct ttm_resource *dst_mem, struct ttm_tt *dst_ttm,
|
|
struct i915_refct_sgt *dst_rsgt, bool allow_accel,
|
|
const struct i915_deps *move_deps)
|
|
{
|
|
const bool memcpy_allowed = i915_ttm_memcpy_allowed(bo, dst_mem);
|
|
struct drm_i915_gem_object *obj = i915_ttm_to_gem(bo);
|
|
struct drm_i915_private *i915 = to_i915(bo->base.dev);
|
|
struct i915_ttm_memcpy_work *copy_work = NULL;
|
|
struct i915_ttm_memcpy_arg _arg, *arg = &_arg;
|
|
struct dma_fence *fence = ERR_PTR(-EINVAL);
|
|
|
|
if (allow_accel) {
|
|
fence = i915_ttm_accel_move(bo, clear, dst_mem, dst_ttm,
|
|
&dst_rsgt->table, move_deps);
|
|
|
|
/*
|
|
* We only need to intercept the error when moving to lmem.
|
|
* When moving to system, TTM or shmem will provide us with
|
|
* cleared pages.
|
|
*/
|
|
if (!IS_ERR(fence) && !i915_ttm_gtt_binds_lmem(dst_mem) &&
|
|
!I915_SELFTEST_ONLY(fail_gpu_migration ||
|
|
fail_work_allocation))
|
|
goto out;
|
|
}
|
|
|
|
/* If we've scheduled gpu migration. Try to arm error intercept. */
|
|
if (!IS_ERR(fence)) {
|
|
struct dma_fence *dep = fence;
|
|
|
|
if (!I915_SELFTEST_ONLY(fail_work_allocation))
|
|
copy_work = kzalloc(sizeof(*copy_work), GFP_KERNEL);
|
|
|
|
if (copy_work) {
|
|
copy_work->i915 = i915;
|
|
copy_work->memcpy_allowed = memcpy_allowed;
|
|
copy_work->obj = i915_gem_object_get(obj);
|
|
arg = ©_work->arg;
|
|
if (memcpy_allowed)
|
|
i915_ttm_memcpy_init(arg, bo, clear, dst_mem,
|
|
dst_ttm, dst_rsgt);
|
|
|
|
fence = i915_ttm_memcpy_work_arm(copy_work, dep);
|
|
} else {
|
|
dma_fence_wait(dep, false);
|
|
fence = ERR_PTR(I915_SELFTEST_ONLY(fail_gpu_migration) ?
|
|
-EINVAL : fence->error);
|
|
}
|
|
dma_fence_put(dep);
|
|
|
|
if (!IS_ERR(fence))
|
|
goto out;
|
|
} else {
|
|
int err = PTR_ERR(fence);
|
|
|
|
if (err == -EINTR || err == -ERESTARTSYS || err == -EAGAIN)
|
|
return fence;
|
|
|
|
if (move_deps) {
|
|
err = i915_deps_sync(move_deps, ctx);
|
|
if (err)
|
|
return ERR_PTR(err);
|
|
}
|
|
}
|
|
|
|
/* Error intercept failed or no accelerated migration to start with */
|
|
|
|
if (memcpy_allowed) {
|
|
if (!copy_work)
|
|
i915_ttm_memcpy_init(arg, bo, clear, dst_mem, dst_ttm,
|
|
dst_rsgt);
|
|
i915_ttm_move_memcpy(arg);
|
|
i915_ttm_memcpy_release(arg);
|
|
}
|
|
if (copy_work)
|
|
i915_gem_object_put(copy_work->obj);
|
|
kfree(copy_work);
|
|
|
|
return memcpy_allowed ? NULL : ERR_PTR(-EIO);
|
|
out:
|
|
if (!fence && copy_work) {
|
|
i915_ttm_memcpy_release(arg);
|
|
i915_gem_object_put(copy_work->obj);
|
|
kfree(copy_work);
|
|
}
|
|
|
|
return fence;
|
|
}
|
|
|
|
/**
|
|
* i915_ttm_move - The TTM move callback used by i915.
|
|
* @bo: The buffer object.
|
|
* @evict: Whether this is an eviction.
|
|
* @ctx: Pointer to a struct ttm_operation_ctx indicating how the waits should be
|
|
* performed if waiting
|
|
* @dst_mem: The destination ttm resource.
|
|
* @hop: If we need multihop, what temporary memory type to move to.
|
|
*
|
|
* Return: 0 if successful, negative error code otherwise.
|
|
*/
|
|
int i915_ttm_move(struct ttm_buffer_object *bo, bool evict,
|
|
struct ttm_operation_ctx *ctx,
|
|
struct ttm_resource *dst_mem,
|
|
struct ttm_place *hop)
|
|
{
|
|
struct drm_i915_gem_object *obj = i915_ttm_to_gem(bo);
|
|
struct ttm_resource_manager *dst_man =
|
|
ttm_manager_type(bo->bdev, dst_mem->mem_type);
|
|
struct dma_fence *migration_fence = NULL;
|
|
struct ttm_tt *ttm = bo->ttm;
|
|
struct i915_refct_sgt *dst_rsgt;
|
|
bool clear, prealloc_bo;
|
|
int ret;
|
|
|
|
if (GEM_WARN_ON(i915_ttm_is_ghost_object(bo))) {
|
|
ttm_bo_move_null(bo, dst_mem);
|
|
return 0;
|
|
}
|
|
|
|
if (!bo->resource) {
|
|
if (dst_mem->mem_type != TTM_PL_SYSTEM) {
|
|
hop->mem_type = TTM_PL_SYSTEM;
|
|
hop->flags = TTM_PL_FLAG_TEMPORARY;
|
|
return -EMULTIHOP;
|
|
}
|
|
|
|
/*
|
|
* This is only reached when first creating the object, or if
|
|
* the object was purged or swapped out (pipeline-gutting). For
|
|
* the former we can safely skip all of the below since we are
|
|
* only using a dummy SYSTEM placement here. And with the latter
|
|
* we will always re-enter here with bo->resource set correctly
|
|
* (as per the above), since this is part of a multi-hop
|
|
* sequence, where at the end we can do the move for real.
|
|
*
|
|
* The special case here is when the dst_mem is TTM_PL_SYSTEM,
|
|
* which doens't require any kind of move, so it should be safe
|
|
* to skip all the below and call ttm_bo_move_null() here, where
|
|
* the caller in __i915_ttm_get_pages() will take care of the
|
|
* rest, since we should have a valid ttm_tt.
|
|
*/
|
|
ttm_bo_move_null(bo, dst_mem);
|
|
return 0;
|
|
}
|
|
|
|
ret = i915_ttm_move_notify(bo);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (obj->mm.madv != I915_MADV_WILLNEED) {
|
|
i915_ttm_purge(obj);
|
|
ttm_resource_free(bo, &dst_mem);
|
|
return 0;
|
|
}
|
|
|
|
/* Populate ttm with pages if needed. Typically system memory. */
|
|
if (ttm && (dst_man->use_tt || (ttm->page_flags & TTM_TT_FLAG_SWAPPED))) {
|
|
ret = ttm_tt_populate(bo->bdev, ttm, ctx);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
dst_rsgt = i915_ttm_resource_get_st(obj, dst_mem);
|
|
if (IS_ERR(dst_rsgt))
|
|
return PTR_ERR(dst_rsgt);
|
|
|
|
clear = !i915_ttm_cpu_maps_iomem(bo->resource) && (!ttm || !ttm_tt_is_populated(ttm));
|
|
prealloc_bo = obj->flags & I915_BO_PREALLOC;
|
|
if (!(clear && ttm && !((ttm->page_flags & TTM_TT_FLAG_ZERO_ALLOC) && !prealloc_bo))) {
|
|
struct i915_deps deps;
|
|
|
|
i915_deps_init(&deps, GFP_KERNEL | __GFP_NORETRY | __GFP_NOWARN);
|
|
ret = i915_deps_add_resv(&deps, bo->base.resv, ctx);
|
|
if (ret) {
|
|
i915_refct_sgt_put(dst_rsgt);
|
|
return ret;
|
|
}
|
|
|
|
migration_fence = __i915_ttm_move(bo, ctx, clear, dst_mem, ttm,
|
|
dst_rsgt, true, &deps);
|
|
i915_deps_fini(&deps);
|
|
}
|
|
|
|
/* We can possibly get an -ERESTARTSYS here */
|
|
if (IS_ERR(migration_fence)) {
|
|
i915_refct_sgt_put(dst_rsgt);
|
|
return PTR_ERR(migration_fence);
|
|
}
|
|
|
|
if (migration_fence) {
|
|
if (I915_SELFTEST_ONLY(evict && fail_gpu_migration))
|
|
ret = -EIO; /* never feed non-migrate fences into ttm */
|
|
else
|
|
ret = ttm_bo_move_accel_cleanup(bo, migration_fence, evict,
|
|
true, dst_mem);
|
|
if (ret) {
|
|
dma_fence_wait(migration_fence, false);
|
|
ttm_bo_move_sync_cleanup(bo, dst_mem);
|
|
}
|
|
dma_fence_put(migration_fence);
|
|
} else {
|
|
ttm_bo_move_sync_cleanup(bo, dst_mem);
|
|
}
|
|
|
|
i915_ttm_adjust_domains_after_move(obj);
|
|
i915_ttm_free_cached_io_rsgt(obj);
|
|
|
|
if (i915_ttm_gtt_binds_lmem(dst_mem) || i915_ttm_cpu_maps_iomem(dst_mem)) {
|
|
obj->ttm.cached_io_rsgt = dst_rsgt;
|
|
obj->ttm.get_io_page.sg_pos = dst_rsgt->table.sgl;
|
|
obj->ttm.get_io_page.sg_idx = 0;
|
|
} else {
|
|
i915_refct_sgt_put(dst_rsgt);
|
|
}
|
|
|
|
i915_ttm_adjust_lru(obj);
|
|
i915_ttm_adjust_gem_after_move(obj);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* i915_gem_obj_copy_ttm - Copy the contents of one ttm-based gem object to
|
|
* another
|
|
* @dst: The destination object
|
|
* @src: The source object
|
|
* @allow_accel: Allow using the blitter. Otherwise TTM memcpy is used.
|
|
* @intr: Whether to perform waits interruptible:
|
|
*
|
|
* Note: The caller is responsible for assuring that the underlying
|
|
* TTM objects are populated if needed and locked.
|
|
*
|
|
* Return: Zero on success. Negative error code on error. If @intr == true,
|
|
* then it may return -ERESTARTSYS or -EINTR.
|
|
*/
|
|
int i915_gem_obj_copy_ttm(struct drm_i915_gem_object *dst,
|
|
struct drm_i915_gem_object *src,
|
|
bool allow_accel, bool intr)
|
|
{
|
|
struct ttm_buffer_object *dst_bo = i915_gem_to_ttm(dst);
|
|
struct ttm_buffer_object *src_bo = i915_gem_to_ttm(src);
|
|
struct ttm_operation_ctx ctx = {
|
|
.interruptible = intr,
|
|
};
|
|
struct i915_refct_sgt *dst_rsgt;
|
|
struct dma_fence *copy_fence;
|
|
struct i915_deps deps;
|
|
int ret;
|
|
|
|
assert_object_held(dst);
|
|
assert_object_held(src);
|
|
|
|
if (GEM_WARN_ON(!src_bo->resource || !dst_bo->resource))
|
|
return -EINVAL;
|
|
|
|
i915_deps_init(&deps, GFP_KERNEL | __GFP_NORETRY | __GFP_NOWARN);
|
|
|
|
ret = dma_resv_reserve_fences(src_bo->base.resv, 1);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = dma_resv_reserve_fences(dst_bo->base.resv, 1);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = i915_deps_add_resv(&deps, dst_bo->base.resv, &ctx);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = i915_deps_add_resv(&deps, src_bo->base.resv, &ctx);
|
|
if (ret)
|
|
return ret;
|
|
|
|
dst_rsgt = i915_ttm_resource_get_st(dst, dst_bo->resource);
|
|
copy_fence = __i915_ttm_move(src_bo, &ctx, false, dst_bo->resource,
|
|
dst_bo->ttm, dst_rsgt, allow_accel,
|
|
&deps);
|
|
|
|
i915_deps_fini(&deps);
|
|
i915_refct_sgt_put(dst_rsgt);
|
|
if (IS_ERR_OR_NULL(copy_fence))
|
|
return PTR_ERR_OR_ZERO(copy_fence);
|
|
|
|
dma_resv_add_fence(dst_bo->base.resv, copy_fence, DMA_RESV_USAGE_WRITE);
|
|
dma_resv_add_fence(src_bo->base.resv, copy_fence, DMA_RESV_USAGE_READ);
|
|
dma_fence_put(copy_fence);
|
|
|
|
return 0;
|
|
}
|