Commit a51973c5 authored by Dave Airlie's avatar Dave Airlie
Browse files

Merge tag 'drm-xe-next-2026-03-26-1' of https://gitlab.freedesktop.org/drm/xe/kernel into drm-next



Hi Dave and Sima,

Here goes our late, final drm-xe-next PR towards 7.1. We just purgeable
BO uAPI in today, hence the late pull.

In the big things we have:
- Add support for purgeable buffer objects

Thanks,
Matt

UAPI Changes:
- Add support for purgeable buffer objects (Arvind, Himal)

Driver Changes:
- Remove useless comment (Maarten)
- Issue GGTT invalidation under lock in ggtt_node_remove (Brost, Fixes)
- Fix mismatched include guards in header files (Shuicheng)

Signed-off-by: default avatarDave Airlie <airlied@redhat.com>
From: Matthew Brost <matthew.brost@intel.com>
Link: https://patch.msgid.link/acX4fWxPkZrrfwnT@gsse-cloud1.jf.intel.com
parents 0d270f0d 05c8b1cd
Loading
Loading
Loading
Loading
+181 −13
Original line number Diff line number Diff line
@@ -838,6 +838,122 @@ static int xe_bo_move_notify(struct xe_bo *bo,
	return 0;
}

/**
 * xe_bo_set_purgeable_shrinker() - Update shrinker accounting for purgeable state
 * @bo: Buffer object
 * @new_state: New purgeable state being set
 *
 * Transfers pages between shrinkable and purgeable buckets when the BO
 * purgeable state changes. Called automatically from xe_bo_set_purgeable_state().
 */
static void xe_bo_set_purgeable_shrinker(struct xe_bo *bo,
					 enum xe_madv_purgeable_state new_state)
{
	struct ttm_buffer_object *ttm_bo = &bo->ttm;
	struct ttm_tt *tt = ttm_bo->ttm;
	struct xe_device *xe = ttm_to_xe_device(ttm_bo->bdev);
	struct xe_ttm_tt *xe_tt;
	long tt_pages;

	xe_bo_assert_held(bo);

	if (!tt || !ttm_tt_is_populated(tt))
		return;

	xe_tt = container_of(tt, struct xe_ttm_tt, ttm);
	tt_pages = tt->num_pages;

	if (!xe_tt->purgeable && new_state == XE_MADV_PURGEABLE_DONTNEED) {
		xe_tt->purgeable = true;
		/* Transfer pages from shrinkable to purgeable count */
		xe_shrinker_mod_pages(xe->mem.shrinker, -tt_pages, tt_pages);
	} else if (xe_tt->purgeable && new_state == XE_MADV_PURGEABLE_WILLNEED) {
		xe_tt->purgeable = false;
		/* Transfer pages from purgeable to shrinkable count */
		xe_shrinker_mod_pages(xe->mem.shrinker, tt_pages, -tt_pages);
	}
}

/**
 * xe_bo_set_purgeable_state() - Set BO purgeable state with validation
 * @bo: Buffer object
 * @new_state: New purgeable state
 *
 * Sets the purgeable state with lockdep assertions and validates state
 * transitions. Once a BO is PURGED, it cannot transition to any other state.
 * Invalid transitions are caught with xe_assert(). Shrinker page accounting
 * is updated automatically.
 */
void xe_bo_set_purgeable_state(struct xe_bo *bo,
			       enum xe_madv_purgeable_state new_state)
{
	struct xe_device *xe = xe_bo_device(bo);

	xe_bo_assert_held(bo);

	/* Validate state is one of the known values */
	xe_assert(xe, new_state == XE_MADV_PURGEABLE_WILLNEED ||
		  new_state == XE_MADV_PURGEABLE_DONTNEED ||
		  new_state == XE_MADV_PURGEABLE_PURGED);

	/* Once purged, always purged - cannot transition out */
	xe_assert(xe, !(bo->madv_purgeable == XE_MADV_PURGEABLE_PURGED &&
			new_state != XE_MADV_PURGEABLE_PURGED));

	bo->madv_purgeable = new_state;
	xe_bo_set_purgeable_shrinker(bo, new_state);
}

/**
 * xe_ttm_bo_purge() - Purge buffer object backing store
 * @ttm_bo: The TTM buffer object to purge
 * @ctx: TTM operation context
 *
 * This function purges the backing store of a BO marked as DONTNEED and
 * triggers rebind to invalidate stale GPU mappings. For fault-mode VMs,
 * this zaps the PTEs. The next GPU access will trigger a page fault and
 * perform NULL rebind (scratch pages or clear PTEs based on VM config).
 *
 * Return: 0 on success, negative error code on failure
 */
static int xe_ttm_bo_purge(struct ttm_buffer_object *ttm_bo, struct ttm_operation_ctx *ctx)
{
	struct xe_bo *bo = ttm_to_xe_bo(ttm_bo);
	struct ttm_placement place = {};
	int ret;

	xe_bo_assert_held(bo);

	if (!ttm_bo->ttm)
		return 0;

	if (!xe_bo_madv_is_dontneed(bo))
		return 0;

	/*
	 * Use the standard pre-move hook so we share the same cleanup/invalidate
	 * path as migrations: drop any CPU vmap and schedule the necessary GPU
	 * unbind/rebind work.
	 *
	 * This must be called before ttm_bo_validate() frees the pages.
	 * May fail in no-wait contexts (fault/shrinker) or if the BO is
	 * pinned. Keep state unchanged on failure so we don't end up "PURGED"
	 * with stale mappings.
	 */
	ret = xe_bo_move_notify(bo, ctx);
	if (ret)
		return ret;

	ret = ttm_bo_validate(ttm_bo, &place, ctx);
	if (ret)
		return ret;

	/* Commit the state transition only once invalidation was queued */
	xe_bo_set_purgeable_state(bo, XE_MADV_PURGEABLE_PURGED);

	return 0;
}

static int xe_bo_move(struct ttm_buffer_object *ttm_bo, bool evict,
		      struct ttm_operation_ctx *ctx,
		      struct ttm_resource *new_mem,
@@ -857,6 +973,20 @@ static int xe_bo_move(struct ttm_buffer_object *ttm_bo, bool evict,
				  ttm && ttm_tt_is_populated(ttm)) ? true : false;
	int ret = 0;

	/*
	 * Purge only non-shared BOs explicitly marked DONTNEED by userspace.
	 * The move_notify callback will handle invalidation asynchronously.
	 */
	if (evict && xe_bo_madv_is_dontneed(bo)) {
		ret = xe_ttm_bo_purge(ttm_bo, ctx);
		if (ret)
			return ret;

		/* Free the unused eviction destination resource */
		ttm_resource_free(ttm_bo, &new_mem);
		return 0;
	}

	/* Bo creation path, moving to system or TT. */
	if ((!old_mem && ttm) && !handle_system_ccs) {
		if (new_mem->mem_type == XE_PL_TT)
@@ -1154,6 +1284,9 @@ long xe_bo_shrink(struct ttm_operation_ctx *ctx, struct ttm_buffer_object *bo,
			lret = xe_bo_move_notify(xe_bo, ctx);
		if (!lret)
			lret = xe_bo_shrink_purge(ctx, bo, scanned);
		if (lret > 0 && xe_bo_madv_is_dontneed(xe_bo))
			xe_bo_set_purgeable_state(xe_bo,
						  XE_MADV_PURGEABLE_PURGED);
		goto out_unref;
	}

@@ -1606,18 +1739,6 @@ static void xe_ttm_bo_delete_mem_notify(struct ttm_buffer_object *ttm_bo)
	}
}

static void xe_ttm_bo_purge(struct ttm_buffer_object *ttm_bo, struct ttm_operation_ctx *ctx)
{
	struct xe_device *xe = ttm_to_xe_device(ttm_bo->bdev);

	if (ttm_bo->ttm) {
		struct ttm_placement place = {};
		int ret = ttm_bo_validate(ttm_bo, &place, ctx);

		drm_WARN_ON(&xe->drm, ret);
	}
}

static void xe_ttm_bo_swap_notify(struct ttm_buffer_object *ttm_bo)
{
	struct ttm_operation_ctx ctx = {
@@ -1902,6 +2023,16 @@ static vm_fault_t xe_bo_cpu_fault_fastpath(struct vm_fault *vmf, struct xe_devic
	if (!dma_resv_trylock(tbo->base.resv))
		goto out_validation;

	/*
	 * Reject CPU faults to purgeable BOs. DONTNEED BOs can be purged
	 * at any time, and purged BOs have no backing store. Either case
	 * is undefined behavior for CPU access.
	 */
	if (xe_bo_madv_is_dontneed(bo) || xe_bo_is_purged(bo)) {
		ret = VM_FAULT_SIGBUS;
		goto out_unlock;
	}

	if (xe_ttm_bo_is_imported(tbo)) {
		ret = VM_FAULT_SIGBUS;
		drm_dbg(&xe->drm, "CPU trying to access an imported buffer object.\n");
@@ -1992,6 +2123,15 @@ static vm_fault_t xe_bo_cpu_fault(struct vm_fault *vmf)
		if (err)
			break;

		/*
		 * Reject CPU faults to purgeable BOs. DONTNEED BOs can be
		 * purged at any time, and purged BOs have no backing store.
		 */
		if (xe_bo_madv_is_dontneed(bo) || xe_bo_is_purged(bo)) {
			err = -EFAULT;
			break;
		}

		if (xe_ttm_bo_is_imported(tbo)) {
			err = -EFAULT;
			drm_dbg(&xe->drm, "CPU trying to access an imported buffer object.\n");
@@ -2069,10 +2209,35 @@ static const struct vm_operations_struct xe_gem_vm_ops = {
	.access = xe_bo_vm_access,
};

static int xe_gem_object_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma)
{
	struct xe_bo *bo = gem_to_xe_bo(obj);
	int err = 0;

	/*
	 * Reject mmap of purgeable BOs. DONTNEED BOs can be purged
	 * at any time, making CPU access undefined behavior. Purged BOs have
	 * no backing store and are permanently invalid.
	 */
	err = xe_bo_lock(bo, true);
	if (err)
		return err;

	if (xe_bo_madv_is_dontneed(bo))
		err = -EBUSY;
	else if (xe_bo_is_purged(bo))
		err = -EINVAL;
	xe_bo_unlock(bo);
	if (err)
		return err;

	return drm_gem_ttm_mmap(obj, vma);
}

static const struct drm_gem_object_funcs xe_gem_object_funcs = {
	.free = xe_gem_object_free,
	.close = xe_gem_object_close,
	.mmap = drm_gem_ttm_mmap,
	.mmap = xe_gem_object_mmap,
	.export = xe_gem_prime_export,
	.vm_ops = &xe_gem_vm_ops,
};
@@ -2198,6 +2363,9 @@ struct xe_bo *xe_bo_init_locked(struct xe_device *xe, struct xe_bo *bo,
#endif
	INIT_LIST_HEAD(&bo->vram_userfault_link);

	/* Initialize purge advisory state */
	bo->madv_purgeable = XE_MADV_PURGEABLE_WILLNEED;

	drm_gem_private_object_init(&xe->drm, &bo->ttm.base, size);

	if (resv) {
+58 −0
Original line number Diff line number Diff line
@@ -87,6 +87,28 @@

#define XE_PCI_BARRIER_MMAP_OFFSET	(0x50 << XE_PTE_SHIFT)

/**
 * enum xe_madv_purgeable_state - Buffer object purgeable state enumeration
 *
 * This enum defines the possible purgeable states for a buffer object,
 * allowing userspace to provide memory usage hints to the kernel for
 * better memory management under pressure.
 *
 * @XE_MADV_PURGEABLE_WILLNEED: The buffer object is needed and should not be purged.
 * This is the default state.
 * @XE_MADV_PURGEABLE_DONTNEED: The buffer object is not currently needed and can be
 * purged by the kernel under memory pressure.
 * @XE_MADV_PURGEABLE_PURGED: The buffer object has been purged by the kernel.
 *
 * Accessing a purged buffer will result in an error. Per i915 semantics,
 * once purged, a BO remains permanently invalid and must be destroyed and recreated.
 */
enum xe_madv_purgeable_state {
	XE_MADV_PURGEABLE_WILLNEED,
	XE_MADV_PURGEABLE_DONTNEED,
	XE_MADV_PURGEABLE_PURGED,
};

struct sg_table;

struct xe_bo *xe_bo_alloc(void);
@@ -215,6 +237,42 @@ static inline bool xe_bo_is_protected(const struct xe_bo *bo)
	return bo->pxp_key_instance;
}

/**
 * xe_bo_is_purged() - Check if buffer object has been purged
 * @bo: The buffer object to check
 *
 * Checks if the buffer object's backing store has been discarded by the
 * kernel due to memory pressure after being marked as purgeable (DONTNEED).
 * Once purged, the BO cannot be restored and any attempt to use it will fail.
 *
 * Context: Caller must hold the BO's dma-resv lock
 * Return: true if the BO has been purged, false otherwise
 */
static inline bool xe_bo_is_purged(struct xe_bo *bo)
{
	xe_bo_assert_held(bo);
	return bo->madv_purgeable == XE_MADV_PURGEABLE_PURGED;
}

/**
 * xe_bo_madv_is_dontneed() - Check if BO is marked as DONTNEED
 * @bo: The buffer object to check
 *
 * Checks if userspace has marked this BO as DONTNEED (i.e., its contents
 * are not currently needed and can be discarded under memory pressure).
 * This is used internally to decide whether a BO is eligible for purging.
 *
 * Context: Caller must hold the BO's dma-resv lock
 * Return: true if the BO is marked DONTNEED, false otherwise
 */
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;
}

void xe_bo_set_purgeable_state(struct xe_bo *bo, enum xe_madv_purgeable_state new_state);

static inline void xe_bo_unpin_map_no_vm(struct xe_bo *bo)
{
	if (likely(bo)) {
+6 −0
Original line number Diff line number Diff line
@@ -108,6 +108,12 @@ struct xe_bo {
	 * from default
	 */
	u64 min_align;

	/**
	 * @madv_purgeable: user space advise on BO purgeability, protected
	 * by BO's dma-resv lock.
	 */
	u32 madv_purgeable;
};

#endif
+0 −3
Original line number Diff line number Diff line
@@ -390,9 +390,6 @@ bool xe_is_xe_file(const struct file *file)
}

static struct drm_driver driver = {
	/* Don't use MTRRs here; the Xserver or userspace app should
	 * deal with them for Intel hardware.
	 */
	.driver_features =
	    DRIVER_GEM |
	    DRIVER_RENDER | DRIVER_SYNCOBJ |
+24 −0
Original line number Diff line number Diff line
@@ -223,6 +223,26 @@ struct dma_buf *xe_gem_prime_export(struct drm_gem_object *obj, int flags)
	if (bo->vm)
		return ERR_PTR(-EPERM);

	/*
	 * Reject exporting purgeable BOs. DONTNEED BOs can be purged
	 * at any time, making the exported dma-buf unusable. Purged BOs
	 * have no backing store and are permanently invalid.
	 */
	ret = xe_bo_lock(bo, true);
	if (ret)
		return ERR_PTR(ret);

	if (xe_bo_madv_is_dontneed(bo)) {
		ret = -EBUSY;
		goto out_unlock;
	}

	if (xe_bo_is_purged(bo)) {
		ret = -EINVAL;
		goto out_unlock;
	}
	xe_bo_unlock(bo);

	ret = ttm_bo_setup_export(&bo->ttm, &ctx);
	if (ret)
		return ERR_PTR(ret);
@@ -232,6 +252,10 @@ struct dma_buf *xe_gem_prime_export(struct drm_gem_object *obj, int flags)
		buf->ops = &xe_dmabuf_ops;

	return buf;

out_unlock:
	xe_bo_unlock(bo);
	return ERR_PTR(ret);
}

static struct drm_gem_object *
Loading