- Madvise fix around purgeability tracking (Arvind)
- Restore engine mask for specific blitter style (Roper) - Couple UAF fixes (Auld) - Drop unused ggtt_balloon field (Wajdeczko) -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEEbSBwaO7dZQkcLOKj+mJfZA7rE8oFAmoF1nAACgkQ+mJfZA7r E8oZ1gf8Cr2J4s+umcMurjta9zSQ+b/cLhz2+aniGV3+zmrI/55Xq9vR3xJ6Bfqx Ou0pgzsvLs1N3v3NYUrIy+ru8eZfyyQXzIBAjw7dZz44CP5btHygm+cCOAK0ZM7c f1g/xPYqMtOybYllaQN17rmeGcy179E5vndpu6zLVE3PcjCYZEI9y51F7ADTJcx5 uer1bNt0dGsvy4T7JNyRwCQHgjGmMFF7OAAvDiiJqB7/ow6nGjbEpUxrDn3og/v4 HVQuaq8LtBrCOvoVC+MjokHyYLg4PsrLV/4EB2cLuMn6N2mDpCkwrdbhRyWdEiD4 JbxStIhFJ8fX8SDGv/P6OwX6Uz0JUw== =haC2 -----END PGP SIGNATURE----- Merge tag 'drm-xe-fixes-2026-05-14' of https://gitlab.freedesktop.org/drm/xe/kernel into drm-fixes - Madvise fix around purgeability tracking (Arvind) - Restore engine mask for specific blitter style (Roper) - Couple UAF fixes (Auld) - Drop unused ggtt_balloon field (Wajdeczko) Signed-off-by: Dave Airlie <airlied@redhat.com> From: Rodrigo Vivi <rodrigo.vivi@intel.com> Link: https://patch.msgid.link/agXWkM3Y98bqt6TG@intel.commaster
commit
96f34d185c
|
|
@ -897,10 +897,10 @@ void xe_bo_set_purgeable_state(struct xe_bo *bo,
|
|||
new_state == XE_MADV_PURGEABLE_PURGED);
|
||||
|
||||
/* Once purged, always purged - cannot transition out */
|
||||
xe_assert(xe, !(bo->madv_purgeable == XE_MADV_PURGEABLE_PURGED &&
|
||||
xe_assert(xe, !(bo->purgeable.state == XE_MADV_PURGEABLE_PURGED &&
|
||||
new_state != XE_MADV_PURGEABLE_PURGED));
|
||||
|
||||
bo->madv_purgeable = new_state;
|
||||
bo->purgeable.state = new_state;
|
||||
xe_bo_set_purgeable_shrinker(bo, new_state);
|
||||
}
|
||||
|
||||
|
|
@ -2368,7 +2368,7 @@ struct xe_bo *xe_bo_init_locked(struct xe_device *xe, struct xe_bo *bo,
|
|||
INIT_LIST_HEAD(&bo->vram_userfault_link);
|
||||
|
||||
/* Initialize purge advisory state */
|
||||
bo->madv_purgeable = XE_MADV_PURGEABLE_WILLNEED;
|
||||
bo->purgeable.state = XE_MADV_PURGEABLE_WILLNEED;
|
||||
|
||||
drm_gem_private_object_init(&xe->drm, &bo->ttm.base, size);
|
||||
|
||||
|
|
|
|||
|
|
@ -251,7 +251,7 @@ static inline bool xe_bo_is_protected(const struct xe_bo *bo)
|
|||
static inline bool xe_bo_is_purged(struct xe_bo *bo)
|
||||
{
|
||||
xe_bo_assert_held(bo);
|
||||
return bo->madv_purgeable == XE_MADV_PURGEABLE_PURGED;
|
||||
return bo->purgeable.state == XE_MADV_PURGEABLE_PURGED;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -268,11 +268,95 @@ static inline bool xe_bo_is_purged(struct xe_bo *bo)
|
|||
static inline bool xe_bo_madv_is_dontneed(struct xe_bo *bo)
|
||||
{
|
||||
xe_bo_assert_held(bo);
|
||||
return bo->madv_purgeable == XE_MADV_PURGEABLE_DONTNEED;
|
||||
return bo->purgeable.state == XE_MADV_PURGEABLE_DONTNEED;
|
||||
}
|
||||
|
||||
void xe_bo_set_purgeable_state(struct xe_bo *bo, enum xe_madv_purgeable_state new_state);
|
||||
|
||||
/**
|
||||
* xe_bo_willneed_get_locked() - Acquire a WILLNEED holder on a BO
|
||||
* @bo: Buffer object
|
||||
*
|
||||
* Increments willneed_count and, on a 0->1 transition, promotes the BO
|
||||
* from DONTNEED to WILLNEED. PURGED is terminal and is never modified.
|
||||
*
|
||||
* Caller must hold the BO's dma-resv lock.
|
||||
*/
|
||||
static inline void xe_bo_willneed_get_locked(struct xe_bo *bo)
|
||||
{
|
||||
xe_bo_assert_held(bo);
|
||||
|
||||
/* Imported BOs are owned externally; do not track purgeability. */
|
||||
if (drm_gem_is_imported(&bo->ttm.base))
|
||||
return;
|
||||
|
||||
if (bo->purgeable.willneed_count++ == 0 && xe_bo_madv_is_dontneed(bo))
|
||||
xe_bo_set_purgeable_state(bo, XE_MADV_PURGEABLE_WILLNEED);
|
||||
}
|
||||
|
||||
/**
|
||||
* xe_bo_willneed_put_locked() - Release a WILLNEED holder on a BO
|
||||
* @bo: Buffer object
|
||||
*
|
||||
* Decrements willneed_count and, on a 1->0 transition, marks the BO
|
||||
* DONTNEED only if it still has VMAs (implying all active VMAs are
|
||||
* DONTNEED). If the last VMA is being removed, preserve the current BO
|
||||
* state to match the previous VMA-walk semantics.
|
||||
*
|
||||
* PURGED is terminal and the BO state is never modified.
|
||||
*
|
||||
* Caller must hold the BO's dma-resv lock.
|
||||
*/
|
||||
static inline void xe_bo_willneed_put_locked(struct xe_bo *bo)
|
||||
{
|
||||
xe_bo_assert_held(bo);
|
||||
|
||||
if (drm_gem_is_imported(&bo->ttm.base))
|
||||
return;
|
||||
|
||||
xe_assert(xe_bo_device(bo), bo->purgeable.willneed_count > 0);
|
||||
if (--bo->purgeable.willneed_count == 0 && bo->purgeable.vma_count > 0 &&
|
||||
!xe_bo_is_purged(bo))
|
||||
xe_bo_set_purgeable_state(bo, XE_MADV_PURGEABLE_DONTNEED);
|
||||
}
|
||||
|
||||
/**
|
||||
* xe_bo_vma_count_inc_locked() - Account a new VMA on a BO
|
||||
* @bo: Buffer object
|
||||
*
|
||||
* Increments vma_count.
|
||||
*
|
||||
* Caller must hold the BO's dma-resv lock.
|
||||
*/
|
||||
static inline void xe_bo_vma_count_inc_locked(struct xe_bo *bo)
|
||||
{
|
||||
xe_bo_assert_held(bo);
|
||||
|
||||
if (drm_gem_is_imported(&bo->ttm.base))
|
||||
return;
|
||||
|
||||
bo->purgeable.vma_count++;
|
||||
}
|
||||
|
||||
/**
|
||||
* xe_bo_vma_count_dec_locked() - Account a VMA removal on a BO
|
||||
* @bo: Buffer object
|
||||
*
|
||||
* Decrements vma_count.
|
||||
*
|
||||
* Caller must hold the BO's dma-resv lock.
|
||||
*/
|
||||
static inline void xe_bo_vma_count_dec_locked(struct xe_bo *bo)
|
||||
{
|
||||
xe_bo_assert_held(bo);
|
||||
|
||||
if (drm_gem_is_imported(&bo->ttm.base))
|
||||
return;
|
||||
|
||||
xe_assert(xe_bo_device(bo), bo->purgeable.vma_count > 0);
|
||||
bo->purgeable.vma_count--;
|
||||
}
|
||||
|
||||
static inline void xe_bo_unpin_map_no_vm(struct xe_bo *bo)
|
||||
{
|
||||
if (likely(bo)) {
|
||||
|
|
|
|||
|
|
@ -111,10 +111,32 @@ struct xe_bo {
|
|||
u64 min_align;
|
||||
|
||||
/**
|
||||
* @madv_purgeable: user space advise on BO purgeability, protected
|
||||
* by BO's dma-resv lock.
|
||||
* @purgeable: Purgeability state and accounting.
|
||||
*
|
||||
* All fields are protected by the BO's dma-resv lock.
|
||||
*/
|
||||
u32 madv_purgeable;
|
||||
struct {
|
||||
/**
|
||||
* @purgeable.state: BO purgeability state
|
||||
* (WILLNEED/DONTNEED/PURGED).
|
||||
*/
|
||||
u32 state;
|
||||
|
||||
/**
|
||||
* @purgeable.vma_count: Number of VMAs currently mapping this BO.
|
||||
*/
|
||||
u32 vma_count;
|
||||
|
||||
/**
|
||||
* @purgeable.willneed_count: Number of active WILLNEED holders.
|
||||
*
|
||||
* Counts WILLNEED VMAs plus active dma-buf exports for
|
||||
* non-imported BOs. The BO flips to DONTNEED on a 1->0
|
||||
* transition only when VMAs still exist; if the last VMA is
|
||||
* removed, the previous BO state is preserved.
|
||||
*/
|
||||
u32 willneed_count;
|
||||
} purgeable;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -193,6 +193,18 @@ static int xe_dma_buf_begin_cpu_access(struct dma_buf *dma_buf,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void xe_dma_buf_release(struct dma_buf *dmabuf)
|
||||
{
|
||||
struct drm_gem_object *obj = dmabuf->priv;
|
||||
struct xe_bo *bo = gem_to_xe_bo(obj);
|
||||
|
||||
xe_bo_lock(bo, false);
|
||||
xe_bo_willneed_put_locked(bo);
|
||||
xe_bo_unlock(bo);
|
||||
|
||||
drm_gem_dmabuf_release(dmabuf);
|
||||
}
|
||||
|
||||
static const struct dma_buf_ops xe_dmabuf_ops = {
|
||||
.attach = xe_dma_buf_attach,
|
||||
.detach = xe_dma_buf_detach,
|
||||
|
|
@ -200,7 +212,7 @@ static const struct dma_buf_ops xe_dmabuf_ops = {
|
|||
.unpin = xe_dma_buf_unpin,
|
||||
.map_dma_buf = xe_dma_buf_map,
|
||||
.unmap_dma_buf = xe_dma_buf_unmap,
|
||||
.release = drm_gem_dmabuf_release,
|
||||
.release = xe_dma_buf_release,
|
||||
.begin_cpu_access = xe_dma_buf_begin_cpu_access,
|
||||
.mmap = drm_gem_dmabuf_mmap,
|
||||
.vmap = drm_gem_dmabuf_vmap,
|
||||
|
|
@ -241,33 +253,33 @@ struct dma_buf *xe_gem_prime_export(struct drm_gem_object *obj, int flags)
|
|||
ret = -EINVAL;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
xe_bo_willneed_get_locked(bo);
|
||||
xe_bo_unlock(bo);
|
||||
|
||||
ret = ttm_bo_setup_export(&bo->ttm, &ctx);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
goto out_put;
|
||||
|
||||
buf = drm_gem_prime_export(obj, flags);
|
||||
if (!IS_ERR(buf))
|
||||
buf->ops = &xe_dmabuf_ops;
|
||||
if (IS_ERR(buf)) {
|
||||
ret = PTR_ERR(buf);
|
||||
goto out_put;
|
||||
}
|
||||
|
||||
buf->ops = &xe_dmabuf_ops;
|
||||
return buf;
|
||||
|
||||
out_put:
|
||||
xe_bo_lock(bo, false);
|
||||
xe_bo_willneed_put_locked(bo);
|
||||
out_unlock:
|
||||
xe_bo_unlock(bo);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* Takes ownership of @storage: on success it is transferred to the returned
|
||||
* drm_gem_object; on failure it is freed before returning the error.
|
||||
* This matches the contract of xe_bo_init_locked() which frees @storage on
|
||||
* its error paths, so callers need not (and must not) free @storage after
|
||||
* this call.
|
||||
*/
|
||||
static struct drm_gem_object *
|
||||
xe_dma_buf_init_obj(struct drm_device *dev, struct xe_bo *storage,
|
||||
struct dma_buf *dma_buf)
|
||||
xe_dma_buf_create_obj(struct drm_device *dev, struct dma_buf *dma_buf)
|
||||
{
|
||||
struct dma_resv *resv = dma_buf->resv;
|
||||
struct xe_device *xe = to_xe_device(dev);
|
||||
|
|
@ -278,10 +290,8 @@ xe_dma_buf_init_obj(struct drm_device *dev, struct xe_bo *storage,
|
|||
int ret = 0;
|
||||
|
||||
dummy_obj = drm_gpuvm_resv_object_alloc(&xe->drm);
|
||||
if (!dummy_obj) {
|
||||
xe_bo_free(storage);
|
||||
if (!dummy_obj)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
dummy_obj->resv = resv;
|
||||
xe_validation_guard(&ctx, &xe->val, &exec, (struct xe_val_flags) {}, ret) {
|
||||
|
|
@ -290,8 +300,7 @@ xe_dma_buf_init_obj(struct drm_device *dev, struct xe_bo *storage,
|
|||
if (ret)
|
||||
break;
|
||||
|
||||
/* xe_bo_init_locked() frees storage on error */
|
||||
bo = xe_bo_init_locked(xe, storage, NULL, resv, NULL, dma_buf->size,
|
||||
bo = xe_bo_init_locked(xe, NULL, NULL, resv, NULL, dma_buf->size,
|
||||
0, /* Will require 1way or 2way for vm_bind */
|
||||
ttm_bo_type_sg, XE_BO_FLAG_SYSTEM, &exec);
|
||||
drm_exec_retry_on_contention(&exec);
|
||||
|
|
@ -342,7 +351,6 @@ struct drm_gem_object *xe_gem_prime_import(struct drm_device *dev,
|
|||
const struct dma_buf_attach_ops *attach_ops;
|
||||
struct dma_buf_attachment *attach;
|
||||
struct drm_gem_object *obj;
|
||||
struct xe_bo *bo;
|
||||
|
||||
if (dma_buf->ops == &xe_dmabuf_ops) {
|
||||
obj = dma_buf->priv;
|
||||
|
|
@ -358,13 +366,15 @@ struct drm_gem_object *xe_gem_prime_import(struct drm_device *dev,
|
|||
}
|
||||
|
||||
/*
|
||||
* Don't publish the bo until we have a valid attachment, and a
|
||||
* valid attachment needs the bo address. So pre-create a bo before
|
||||
* creating the attachment and publish.
|
||||
* This needs to happen before the attach, since it will create a new
|
||||
* attachment for this, and add it to the list of attachments, at which
|
||||
* point it is globally visible, and at any point the export side can
|
||||
* call into on invalidate_mappings callback, which require a working
|
||||
* object.
|
||||
*/
|
||||
bo = xe_bo_alloc();
|
||||
if (IS_ERR(bo))
|
||||
return ERR_CAST(bo);
|
||||
obj = xe_dma_buf_create_obj(dev, dma_buf);
|
||||
if (IS_ERR(obj))
|
||||
return obj;
|
||||
|
||||
attach_ops = &xe_dma_buf_attach_ops;
|
||||
#if IS_ENABLED(CONFIG_DRM_XE_KUNIT_TEST)
|
||||
|
|
@ -372,29 +382,15 @@ struct drm_gem_object *xe_gem_prime_import(struct drm_device *dev,
|
|||
attach_ops = test->attach_ops;
|
||||
#endif
|
||||
|
||||
attach = dma_buf_dynamic_attach(dma_buf, dev->dev, attach_ops, &bo->ttm.base);
|
||||
attach = dma_buf_dynamic_attach(dma_buf, dev->dev, attach_ops, obj);
|
||||
if (IS_ERR(attach)) {
|
||||
obj = ERR_CAST(attach);
|
||||
goto out_err;
|
||||
xe_bo_put(gem_to_xe_bo(obj));
|
||||
return ERR_CAST(attach);
|
||||
}
|
||||
|
||||
/*
|
||||
* xe_dma_buf_init_obj() takes ownership of bo on both success
|
||||
* and failure, so we must not touch bo after this call.
|
||||
*/
|
||||
obj = xe_dma_buf_init_obj(dev, bo, dma_buf);
|
||||
if (IS_ERR(obj)) {
|
||||
dma_buf_detach(dma_buf, attach);
|
||||
return obj;
|
||||
}
|
||||
get_dma_buf(dma_buf);
|
||||
obj->import_attach = attach;
|
||||
return obj;
|
||||
|
||||
out_err:
|
||||
xe_bo_free(bo);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_DRM_XE_KUNIT_TEST)
|
||||
|
|
|
|||
|
|
@ -144,6 +144,13 @@ struct xe_gt {
|
|||
u8 id;
|
||||
/** @info.has_indirect_ring_state: GT has indirect ring state support */
|
||||
u8 has_indirect_ring_state:1;
|
||||
/**
|
||||
* @info.has_xe2_blt_instructions: GT supports Xe2-style MEM_SET
|
||||
* and MEM_COPY blitter functionality. Note that despite the
|
||||
* name, some Xe1 platforms may also support this "Xe2-style"
|
||||
* feature.
|
||||
*/
|
||||
u8 has_xe2_blt_instructions:1;
|
||||
/**
|
||||
* @info.num_geometry_xecore_fuse_regs: Number of 32b-bit fuse
|
||||
* registers the geometry XeCore mask spans.
|
||||
|
|
|
|||
|
|
@ -1524,23 +1524,9 @@ static void emit_clear_main_copy(struct xe_gt *gt, struct xe_bb *bb,
|
|||
bb->len += len;
|
||||
}
|
||||
|
||||
static bool has_service_copy_support(struct xe_gt *gt)
|
||||
{
|
||||
/*
|
||||
* What we care about is whether the architecture was designed with
|
||||
* service copy functionality (specifically the new MEM_SET / MEM_COPY
|
||||
* instructions) so check the architectural engine list rather than the
|
||||
* actual list since these instructions are usable on BCS0 even if
|
||||
* all of the actual service copy engines (BCS1-BCS8) have been fused
|
||||
* off.
|
||||
*/
|
||||
return gt->info.engine_mask & GENMASK(XE_HW_ENGINE_BCS8,
|
||||
XE_HW_ENGINE_BCS1);
|
||||
}
|
||||
|
||||
static u32 emit_clear_cmd_len(struct xe_gt *gt)
|
||||
{
|
||||
if (has_service_copy_support(gt))
|
||||
if (gt->info.has_xe2_blt_instructions)
|
||||
return PVC_MEM_SET_CMD_LEN_DW;
|
||||
else
|
||||
return XY_FAST_COLOR_BLT_DW;
|
||||
|
|
@ -1549,7 +1535,7 @@ static u32 emit_clear_cmd_len(struct xe_gt *gt)
|
|||
static void emit_clear(struct xe_gt *gt, struct xe_bb *bb, u64 src_ofs,
|
||||
u32 size, u32 pitch, bool is_vram)
|
||||
{
|
||||
if (has_service_copy_support(gt))
|
||||
if (gt->info.has_xe2_blt_instructions)
|
||||
emit_clear_link_copy(gt, bb, src_ofs, size, pitch);
|
||||
else
|
||||
emit_clear_main_copy(gt, bb, src_ofs, size, pitch,
|
||||
|
|
|
|||
|
|
@ -851,6 +851,15 @@ static struct xe_gt *alloc_primary_gt(struct xe_tile *tile,
|
|||
gt->info.num_geometry_xecore_fuse_regs = graphics_desc->num_geometry_xecore_fuse_regs;
|
||||
gt->info.num_compute_xecore_fuse_regs = graphics_desc->num_compute_xecore_fuse_regs;
|
||||
|
||||
/*
|
||||
* Even if the service copy engines wind up being fused off, their
|
||||
* presence in the IP descriptor indicates that the platform supports
|
||||
* Xe2-style MEM_SET and MEM_COPY functionality.
|
||||
*/
|
||||
if (graphics_desc->hw_engine_mask & GENMASK(XE_HW_ENGINE_BCS8,
|
||||
XE_HW_ENGINE_BCS1))
|
||||
gt->info.has_xe2_blt_instructions = true;
|
||||
|
||||
/*
|
||||
* Before media version 13, the media IP was part of the primary GT
|
||||
* so we need to add the media engines to the primary GT's engine list.
|
||||
|
|
|
|||
|
|
@ -106,8 +106,6 @@ struct xe_tile {
|
|||
struct xe_lmtt lmtt;
|
||||
} pf;
|
||||
struct {
|
||||
/** @sriov.vf.ggtt_balloon: GGTT regions excluded from use. */
|
||||
struct xe_ggtt_node *ggtt_balloon[2];
|
||||
/** @sriov.vf.self_config: VF configuration data */
|
||||
struct xe_tile_sriov_vf_selfconfig self_config;
|
||||
} vf;
|
||||
|
|
|
|||
|
|
@ -1120,6 +1120,25 @@ static struct xe_vma *xe_vma_create(struct xe_vm *vm,
|
|||
|
||||
xe_bo_assert_held(bo);
|
||||
|
||||
/*
|
||||
* Reject only WILLNEED mappings on DONTNEED/PURGED BOs. This
|
||||
* gates new vm_bind ioctls (user supplies WILLNEED) while
|
||||
* still allowing partial-unbind / remap splits whose new VMAs
|
||||
* inherit the parent's DONTNEED attr. It must also run before
|
||||
* xe_bo_willneed_get_locked() below so a 0->1 holder bump
|
||||
* cannot silently promote DONTNEED back to WILLNEED.
|
||||
*/
|
||||
if (vma->attr.purgeable_state == XE_MADV_PURGEABLE_WILLNEED) {
|
||||
if (xe_bo_madv_is_dontneed(bo)) {
|
||||
xe_vma_free(vma);
|
||||
return ERR_PTR(-EBUSY);
|
||||
}
|
||||
if (xe_bo_is_purged(bo)) {
|
||||
xe_vma_free(vma);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
vm_bo = drm_gpuvm_bo_obtain_locked(vma->gpuva.vm, &bo->ttm.base);
|
||||
if (IS_ERR(vm_bo)) {
|
||||
xe_vma_free(vma);
|
||||
|
|
@ -1131,6 +1150,10 @@ static struct xe_vma *xe_vma_create(struct xe_vm *vm,
|
|||
vma->gpuva.gem.offset = bo_offset_or_userptr;
|
||||
drm_gpuva_link(&vma->gpuva, vm_bo);
|
||||
drm_gpuvm_bo_put(vm_bo);
|
||||
|
||||
xe_bo_vma_count_inc_locked(bo);
|
||||
if (vma->attr.purgeable_state == XE_MADV_PURGEABLE_WILLNEED)
|
||||
xe_bo_willneed_get_locked(bo);
|
||||
} else /* userptr or null */ {
|
||||
if (!is_null && !is_cpu_addr_mirror) {
|
||||
struct xe_userptr_vma *uvma = to_userptr_vma(vma);
|
||||
|
|
@ -1208,7 +1231,10 @@ static void xe_vma_destroy(struct xe_vma *vma, struct dma_fence *fence)
|
|||
xe_bo_assert_held(bo);
|
||||
|
||||
drm_gpuva_unlink(&vma->gpuva);
|
||||
xe_bo_recompute_purgeable_state(bo);
|
||||
|
||||
xe_bo_vma_count_dec_locked(bo);
|
||||
if (vma->attr.purgeable_state == XE_MADV_PURGEABLE_WILLNEED)
|
||||
xe_bo_willneed_put_locked(bo);
|
||||
}
|
||||
|
||||
xe_vm_assert_held(vm);
|
||||
|
|
@ -3016,7 +3042,7 @@ static void vm_bind_ioctl_ops_unwind(struct xe_vm *vm,
|
|||
* @res_evict: Allow evicting resources during validation
|
||||
* @validate: Perform BO validation
|
||||
* @request_decompress: Request BO decompression
|
||||
* @check_purged: Reject operation if BO is purged
|
||||
* @check_purged: Reject operation if BO is DONTNEED or PURGED
|
||||
*/
|
||||
struct xe_vma_lock_and_validate_flags {
|
||||
u32 res_evict : 1;
|
||||
|
|
@ -3030,6 +3056,7 @@ static int vma_lock_and_validate(struct drm_exec *exec, struct xe_vma *vma,
|
|||
{
|
||||
struct xe_bo *bo = xe_vma_bo(vma);
|
||||
struct xe_vm *vm = xe_vma_vm(vma);
|
||||
bool validate_bo = flags.validate;
|
||||
int err = 0;
|
||||
|
||||
if (bo) {
|
||||
|
|
@ -3044,7 +3071,11 @@ static int vma_lock_and_validate(struct drm_exec *exec, struct xe_vma *vma,
|
|||
err = -EINVAL; /* BO already purged */
|
||||
}
|
||||
|
||||
if (!err && flags.validate)
|
||||
/* Don't validate the BO for DONTNEED/PURGED remap remnants. */
|
||||
if (vma->attr.purgeable_state != XE_MADV_PURGEABLE_WILLNEED)
|
||||
validate_bo = false;
|
||||
|
||||
if (!err && validate_bo)
|
||||
err = xe_bo_validate(bo, vm,
|
||||
xe_vm_allow_vm_eviction(vm) &&
|
||||
flags.res_evict, exec);
|
||||
|
|
@ -3152,7 +3183,7 @@ static int op_lock_and_prep(struct drm_exec *exec, struct xe_vm *vm,
|
|||
op->map.immediate,
|
||||
.request_decompress =
|
||||
op->map.request_decompress,
|
||||
.check_purged = true,
|
||||
.check_purged = false,
|
||||
});
|
||||
break;
|
||||
case DRM_GPUVA_OP_REMAP:
|
||||
|
|
@ -3174,7 +3205,7 @@ static int op_lock_and_prep(struct drm_exec *exec, struct xe_vm *vm,
|
|||
.res_evict = res_evict,
|
||||
.validate = true,
|
||||
.request_decompress = false,
|
||||
.check_purged = true,
|
||||
.check_purged = false,
|
||||
});
|
||||
if (!err && op->remap.next)
|
||||
err = vma_lock_and_validate(exec, op->remap.next,
|
||||
|
|
@ -3182,7 +3213,7 @@ static int op_lock_and_prep(struct drm_exec *exec, struct xe_vm *vm,
|
|||
.res_evict = res_evict,
|
||||
.validate = true,
|
||||
.request_decompress = false,
|
||||
.check_purged = true,
|
||||
.check_purged = false,
|
||||
});
|
||||
break;
|
||||
case DRM_GPUVA_OP_UNMAP:
|
||||
|
|
@ -3211,9 +3242,11 @@ static int op_lock_and_prep(struct drm_exec *exec, struct xe_vm *vm,
|
|||
}
|
||||
|
||||
/*
|
||||
* Prefetch attempts to migrate BO's backing store without
|
||||
* repopulating it first. Purged BOs have no backing store
|
||||
* to migrate, so reject the operation.
|
||||
* PREFETCH is the only op that still gates on BO purge state.
|
||||
* MAP/REMAP handle this inside xe_vma_create() so partial
|
||||
* unbind on a DONTNEED BO still works. PREFETCH skips
|
||||
* xe_vma_create() and would migrate a BO with no backing
|
||||
* store, so reject DONTNEED/PURGED here.
|
||||
*/
|
||||
err = vma_lock_and_validate(exec,
|
||||
gpuva_to_vma(op->base.prefetch.va),
|
||||
|
|
|
|||
|
|
@ -185,147 +185,6 @@ static void madvise_pat_index(struct xe_device *xe, struct xe_vm *vm,
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* xe_bo_is_dmabuf_shared() - Check if BO is shared via dma-buf
|
||||
* @bo: Buffer object
|
||||
*
|
||||
* Prevent marking imported or exported dma-bufs as purgeable.
|
||||
* For imported BOs, Xe doesn't own the backing store and cannot
|
||||
* safely reclaim pages (exporter or other devices may still be
|
||||
* using them). For exported BOs, external devices may have active
|
||||
* mappings we cannot track.
|
||||
*
|
||||
* Return: true if BO is imported or exported, false otherwise
|
||||
*/
|
||||
static bool xe_bo_is_dmabuf_shared(struct xe_bo *bo)
|
||||
{
|
||||
struct drm_gem_object *obj = &bo->ttm.base;
|
||||
|
||||
/* Imported: exporter owns backing store */
|
||||
if (drm_gem_is_imported(obj))
|
||||
return true;
|
||||
|
||||
/* Exported: external devices may be accessing */
|
||||
if (obj->dma_buf)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* enum xe_bo_vmas_purge_state - VMA purgeable state aggregation
|
||||
*
|
||||
* Distinguishes whether a BO's VMAs are all DONTNEED, have at least
|
||||
* one WILLNEED, or have no VMAs at all.
|
||||
*
|
||||
* Enum values align with XE_MADV_PURGEABLE_* states for consistency.
|
||||
*/
|
||||
enum xe_bo_vmas_purge_state {
|
||||
/** @XE_BO_VMAS_STATE_WILLNEED: At least one VMA is WILLNEED */
|
||||
XE_BO_VMAS_STATE_WILLNEED = 0,
|
||||
/** @XE_BO_VMAS_STATE_DONTNEED: All VMAs are DONTNEED */
|
||||
XE_BO_VMAS_STATE_DONTNEED = 1,
|
||||
/** @XE_BO_VMAS_STATE_NO_VMAS: BO has no VMAs */
|
||||
XE_BO_VMAS_STATE_NO_VMAS = 2,
|
||||
};
|
||||
|
||||
/*
|
||||
* xe_bo_recompute_purgeable_state() casts between xe_bo_vmas_purge_state and
|
||||
* xe_madv_purgeable_state. Enforce that WILLNEED=0 and DONTNEED=1 match across
|
||||
* both enums so the single-line cast is always valid.
|
||||
*/
|
||||
static_assert(XE_BO_VMAS_STATE_WILLNEED == (int)XE_MADV_PURGEABLE_WILLNEED,
|
||||
"VMA purge state WILLNEED must equal madv purgeable WILLNEED");
|
||||
static_assert(XE_BO_VMAS_STATE_DONTNEED == (int)XE_MADV_PURGEABLE_DONTNEED,
|
||||
"VMA purge state DONTNEED must equal madv purgeable DONTNEED");
|
||||
|
||||
/**
|
||||
* xe_bo_all_vmas_dontneed() - Determine BO VMA purgeable state
|
||||
* @bo: Buffer object
|
||||
*
|
||||
* Check all VMAs across all VMs to determine aggregate purgeable state.
|
||||
* Shared BOs require unanimous DONTNEED state from all mappings.
|
||||
*
|
||||
* Caller must hold BO dma-resv lock.
|
||||
*
|
||||
* Return: XE_BO_VMAS_STATE_DONTNEED if all VMAs are DONTNEED,
|
||||
* XE_BO_VMAS_STATE_WILLNEED if at least one VMA is not DONTNEED,
|
||||
* XE_BO_VMAS_STATE_NO_VMAS if BO has no VMAs
|
||||
*/
|
||||
static enum xe_bo_vmas_purge_state xe_bo_all_vmas_dontneed(struct xe_bo *bo)
|
||||
{
|
||||
struct drm_gpuvm_bo *vm_bo;
|
||||
struct drm_gpuva *gpuva;
|
||||
struct drm_gem_object *obj = &bo->ttm.base;
|
||||
bool has_vmas = false;
|
||||
|
||||
xe_bo_assert_held(bo);
|
||||
|
||||
/* Shared dma-bufs cannot be purgeable */
|
||||
if (xe_bo_is_dmabuf_shared(bo))
|
||||
return XE_BO_VMAS_STATE_WILLNEED;
|
||||
|
||||
drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
|
||||
drm_gpuvm_bo_for_each_va(gpuva, vm_bo) {
|
||||
struct xe_vma *vma = gpuva_to_vma(gpuva);
|
||||
|
||||
has_vmas = true;
|
||||
|
||||
/* Any non-DONTNEED VMA prevents purging */
|
||||
if (vma->attr.purgeable_state != XE_MADV_PURGEABLE_DONTNEED)
|
||||
return XE_BO_VMAS_STATE_WILLNEED;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* No VMAs => preserve existing BO purgeable state.
|
||||
* Avoids incorrectly flipping DONTNEED -> WILLNEED when last VMA unmapped.
|
||||
*/
|
||||
if (!has_vmas)
|
||||
return XE_BO_VMAS_STATE_NO_VMAS;
|
||||
|
||||
return XE_BO_VMAS_STATE_DONTNEED;
|
||||
}
|
||||
|
||||
/**
|
||||
* xe_bo_recompute_purgeable_state() - Recompute BO purgeable state from VMAs
|
||||
* @bo: Buffer object
|
||||
*
|
||||
* Walk all VMAs to determine if BO should be purgeable or not.
|
||||
* Shared BOs require unanimous DONTNEED state from all mappings.
|
||||
* If the BO has no VMAs the existing state is preserved.
|
||||
*
|
||||
* Locking: Caller must hold BO dma-resv lock. When iterating GPUVM lists,
|
||||
* VM lock must also be held (write) to prevent concurrent VMA modifications.
|
||||
* This is satisfied at both call sites:
|
||||
* - xe_vma_destroy(): holds vm->lock write
|
||||
* - madvise_purgeable(): holds vm->lock write (from madvise ioctl path)
|
||||
*
|
||||
* Return: nothing
|
||||
*/
|
||||
void xe_bo_recompute_purgeable_state(struct xe_bo *bo)
|
||||
{
|
||||
enum xe_bo_vmas_purge_state vma_state;
|
||||
|
||||
if (!bo)
|
||||
return;
|
||||
|
||||
xe_bo_assert_held(bo);
|
||||
|
||||
/*
|
||||
* Once purged, always purged. Cannot transition back to WILLNEED.
|
||||
* This matches i915 semantics where purged BOs are permanently invalid.
|
||||
*/
|
||||
if (bo->madv_purgeable == XE_MADV_PURGEABLE_PURGED)
|
||||
return;
|
||||
|
||||
vma_state = xe_bo_all_vmas_dontneed(bo);
|
||||
|
||||
if (vma_state != (enum xe_bo_vmas_purge_state)bo->madv_purgeable &&
|
||||
vma_state != XE_BO_VMAS_STATE_NO_VMAS)
|
||||
xe_bo_set_purgeable_state(bo, (enum xe_madv_purgeable_state)vma_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* madvise_purgeable - Handle purgeable buffer object advice
|
||||
* @xe: XE device
|
||||
|
|
@ -359,12 +218,6 @@ static void madvise_purgeable(struct xe_device *xe, struct xe_vm *vm,
|
|||
/* BO must be locked before modifying madv state */
|
||||
xe_bo_assert_held(bo);
|
||||
|
||||
/* Skip shared dma-bufs - no PTEs to zap */
|
||||
if (xe_bo_is_dmabuf_shared(bo)) {
|
||||
vmas[i]->skip_invalidation = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Once purged, always purged. Cannot transition back to WILLNEED.
|
||||
* This matches i915 semantics where purged BOs are permanently invalid.
|
||||
|
|
@ -377,13 +230,14 @@ static void madvise_purgeable(struct xe_device *xe, struct xe_vm *vm,
|
|||
|
||||
switch (op->purge_state_val.val) {
|
||||
case DRM_XE_VMA_PURGEABLE_STATE_WILLNEED:
|
||||
vmas[i]->attr.purgeable_state = XE_MADV_PURGEABLE_WILLNEED;
|
||||
vmas[i]->skip_invalidation = true;
|
||||
|
||||
xe_bo_recompute_purgeable_state(bo);
|
||||
/* Only act on a real DONTNEED -> WILLNEED transition. */
|
||||
if (vmas[i]->attr.purgeable_state == XE_MADV_PURGEABLE_DONTNEED) {
|
||||
vmas[i]->attr.purgeable_state = XE_MADV_PURGEABLE_WILLNEED;
|
||||
xe_bo_willneed_get_locked(bo);
|
||||
}
|
||||
break;
|
||||
case DRM_XE_VMA_PURGEABLE_STATE_DONTNEED:
|
||||
vmas[i]->attr.purgeable_state = XE_MADV_PURGEABLE_DONTNEED;
|
||||
/*
|
||||
* Don't zap PTEs at DONTNEED time -- pages are still
|
||||
* alive. The zap happens in xe_bo_move_notify() right
|
||||
|
|
@ -391,7 +245,11 @@ static void madvise_purgeable(struct xe_device *xe, struct xe_vm *vm,
|
|||
*/
|
||||
vmas[i]->skip_invalidation = true;
|
||||
|
||||
xe_bo_recompute_purgeable_state(bo);
|
||||
/* Only act on a real WILLNEED -> DONTNEED transition. */
|
||||
if (vmas[i]->attr.purgeable_state == XE_MADV_PURGEABLE_WILLNEED) {
|
||||
vmas[i]->attr.purgeable_state = XE_MADV_PURGEABLE_DONTNEED;
|
||||
xe_bo_willneed_put_locked(bo);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* Should never hit - values validated in madvise_args_are_sane() */
|
||||
|
|
|
|||
|
|
@ -13,6 +13,4 @@ struct xe_bo;
|
|||
int xe_vm_madvise_ioctl(struct drm_device *dev, void *data,
|
||||
struct drm_file *file);
|
||||
|
||||
void xe_bo_recompute_purgeable_state(struct xe_bo *bo);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Reference in New Issue