mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-08-09 06:14:34 +02:00
When CONFIG_DRM_XE_USERPTR_INVAL_INJECT=y, xe_pt_svm_userptr_pre_commit()
runs vma_check_userptr() with the svm notifier_lock taken for read. The
test injection causes vma_check_userptr() to call
xe_vma_userptr_force_invalidate(), which feeds into
xe_vma_userptr_do_inval() with drm_gpusvm_ctx.in_notifier=true. That
flag tells drm_gpusvm_unmap_pages() the caller already holds
notifier_lock for write and only asserts the mode. Because the caller
actually holds it for read, the assertion fires:
WARNING: drivers/gpu/drm/drm_gpusvm.c:1669 at \
drm_gpusvm_unmap_pages+0xd4/0x130 [drm_gpusvm_helper]
Call Trace:
xe_vma_userptr_do_inval+0x40d/0xfd0 [xe]
xe_vma_userptr_invalidate_pass1+0x3e6/0x8d0 [xe]
xe_vma_userptr_force_invalidate+0xde/0x290 [xe]
vma_check_userptr.constprop.0+0x1c6/0x220 [xe]
xe_pt_svm_userptr_pre_commit+0x6a3/0xc60 [xe]
...
xe_vm_bind_ioctl+0x3a0a/0x4480 [xe]
Acquire notifier_lock for write in pre-commit when the inject Kconfig
is enabled, via new helpers xe_pt_svm_userptr_notifier_lock()/_unlock().
Rename xe_svm_assert_held_read() to
xe_svm_assert_held_read_or_inject_write() so it asserts the correct
mode under each build configuration. Production builds
(CONFIG_DRM_XE_USERPTR_INVAL_INJECT=n) keep the existing read-mode
behavior bit-for-bit.
Fixes: 9e97874148 ("drm/xe/userptr: replace xe_hmm with gpusvm")
Assisted-by: Claude:claude-opus-4.7
Cc: Matthew Auld <matthew.auld@intel.com>
Cc: Zongyao Bai <zongyao.bai@intel.com>
Reviewed-by: Matthew Brost <matthew.brost@intel.com>
Link: https://patch.msgid.link/20260625215615.3016892-1-shuicheng.lin@intel.com
Signed-off-by: Shuicheng Lin <shuicheng.lin@intel.com>
(cherry picked from commit 80ccbd97ffee8ad2e73167d826fe7be548364365)
Signed-off-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
442 lines
10 KiB
C
442 lines
10 KiB
C
/* SPDX-License-Identifier: MIT */
|
|
/*
|
|
* Copyright © 2024 Intel Corporation
|
|
*/
|
|
|
|
#ifndef _XE_SVM_H_
|
|
#define _XE_SVM_H_
|
|
|
|
#if IS_ENABLED(CONFIG_DRM_XE_GPUSVM)
|
|
|
|
#include <drm/drm_pagemap.h>
|
|
#include <drm/drm_gpusvm.h>
|
|
#include <drm/drm_pagemap_util.h>
|
|
|
|
#define XE_INTERCONNECT_VRAM DRM_INTERCONNECT_DRIVER
|
|
#define XE_INTERCONNECT_P2P (XE_INTERCONNECT_VRAM + 1)
|
|
|
|
struct drm_device;
|
|
struct drm_file;
|
|
|
|
struct xe_bo;
|
|
struct xe_gt;
|
|
struct xe_device;
|
|
struct xe_vram_region;
|
|
struct xe_tile;
|
|
struct xe_vm;
|
|
struct xe_vma;
|
|
struct xe_vram_region;
|
|
|
|
/** struct xe_svm_range - SVM range */
|
|
struct xe_svm_range {
|
|
/** @base: base drm_gpusvm_range */
|
|
struct drm_gpusvm_range base;
|
|
/**
|
|
* @garbage_collector_link: Link into VM's garbage collect SVM range
|
|
* list. Protected by VM's garbage collect lock.
|
|
*/
|
|
struct list_head garbage_collector_link;
|
|
/**
|
|
* @tile_present: Tile mask of binding is present for this range.
|
|
* Protected by GPU SVM notifier lock.
|
|
*/
|
|
u8 tile_present;
|
|
/**
|
|
* @tile_invalidated: Tile mask of binding is invalidated for this
|
|
* range. Protected by GPU SVM notifier lock.
|
|
*/
|
|
u8 tile_invalidated;
|
|
};
|
|
|
|
/**
|
|
* struct xe_pagemap - Manages xe device_private memory for SVM.
|
|
* @pagemap: The struct dev_pagemap providing the struct pages.
|
|
* @dpagemap: The drm_pagemap managing allocation and migration.
|
|
* @destroy_work: Handles asnynchronous destruction and caching.
|
|
* @peer: Used for pagemap owner computation.
|
|
* @hpa_base: The host physical address base for the managemd memory.
|
|
* @vr: Backpointer to the xe_vram region.
|
|
*/
|
|
struct xe_pagemap {
|
|
struct dev_pagemap pagemap;
|
|
struct drm_pagemap dpagemap;
|
|
struct work_struct destroy_work;
|
|
struct drm_pagemap_peer peer;
|
|
resource_size_t hpa_base;
|
|
struct xe_vram_region *vr;
|
|
};
|
|
|
|
/**
|
|
* xe_svm_range_pages_valid() - SVM range pages valid
|
|
* @range: SVM range
|
|
*
|
|
* Return: True if SVM range pages are valid, False otherwise
|
|
*/
|
|
static inline bool xe_svm_range_pages_valid(struct xe_svm_range *range)
|
|
{
|
|
return drm_gpusvm_range_pages_valid(range->base.gpusvm, &range->base);
|
|
}
|
|
|
|
int xe_devm_add(struct xe_tile *tile, struct xe_vram_region *vr);
|
|
|
|
int xe_svm_init(struct xe_vm *vm);
|
|
|
|
void xe_svm_fini(struct xe_vm *vm);
|
|
|
|
void xe_svm_close(struct xe_vm *vm);
|
|
|
|
int xe_svm_handle_pagefault(struct xe_vm *vm, struct xe_vma *vma,
|
|
struct xe_gt *gt, u64 fault_addr,
|
|
bool atomic);
|
|
|
|
bool xe_svm_has_mapping(struct xe_vm *vm, u64 start, u64 end);
|
|
|
|
int xe_svm_bo_evict(struct xe_bo *bo);
|
|
|
|
void xe_svm_range_debug(struct xe_svm_range *range, const char *operation);
|
|
|
|
int xe_svm_alloc_vram(struct xe_svm_range *range, const struct drm_gpusvm_ctx *ctx,
|
|
struct drm_pagemap *dpagemap);
|
|
|
|
struct xe_svm_range *xe_svm_range_find_or_insert(struct xe_vm *vm, u64 addr,
|
|
struct xe_vma *vma, struct drm_gpusvm_ctx *ctx);
|
|
|
|
int xe_svm_range_get_pages(struct xe_vm *vm, struct xe_svm_range *range,
|
|
struct drm_gpusvm_ctx *ctx);
|
|
|
|
bool xe_svm_range_needs_migrate_to_vram(struct xe_svm_range *range, struct xe_vma *vma,
|
|
const struct drm_pagemap *dpagemap);
|
|
|
|
void xe_svm_range_migrate_to_smem(struct xe_vm *vm, struct xe_svm_range *range);
|
|
|
|
bool xe_svm_range_validate(struct xe_vm *vm,
|
|
struct xe_svm_range *range,
|
|
u8 tile_mask, const struct drm_pagemap *dpagemap);
|
|
|
|
u64 xe_svm_find_vma_start(struct xe_vm *vm, u64 addr, u64 end, struct xe_vma *vma);
|
|
|
|
void xe_svm_unmap_address_range(struct xe_vm *vm, u64 start, u64 end);
|
|
|
|
u8 xe_svm_ranges_zap_ptes_in_range(struct xe_vm *vm, u64 start, u64 end);
|
|
|
|
struct drm_pagemap *xe_vma_resolve_pagemap(struct xe_vma *vma, struct xe_tile *tile);
|
|
|
|
void *xe_svm_private_page_owner(struct xe_vm *vm, bool force_smem);
|
|
|
|
/**
|
|
* xe_svm_range_has_dma_mapping() - SVM range has DMA mapping
|
|
* @range: SVM range
|
|
*
|
|
* Return: True if SVM range has a DMA mapping, False otherwise
|
|
*/
|
|
static inline bool xe_svm_range_has_dma_mapping(struct xe_svm_range *range)
|
|
{
|
|
lockdep_assert_held(&range->base.gpusvm->notifier_lock);
|
|
return range->base.pages.flags.has_dma_mapping;
|
|
}
|
|
|
|
/**
|
|
* to_xe_range - Convert a drm_gpusvm_range pointer to a xe_svm_range
|
|
* @r: Pointer to the drm_gpusvm_range structure
|
|
*
|
|
* This function takes a pointer to a drm_gpusvm_range structure and
|
|
* converts it to a pointer to the containing xe_svm_range structure.
|
|
*
|
|
* Return: Pointer to the xe_svm_range structure
|
|
*/
|
|
static inline struct xe_svm_range *to_xe_range(struct drm_gpusvm_range *r)
|
|
{
|
|
return container_of(r, struct xe_svm_range, base);
|
|
}
|
|
|
|
/**
|
|
* xe_svm_range_start() - SVM range start address
|
|
* @range: SVM range
|
|
*
|
|
* Return: start address of range.
|
|
*/
|
|
static inline unsigned long xe_svm_range_start(struct xe_svm_range *range)
|
|
{
|
|
return drm_gpusvm_range_start(&range->base);
|
|
}
|
|
|
|
/**
|
|
* xe_svm_range_end() - SVM range end address
|
|
* @range: SVM range
|
|
*
|
|
* Return: end address of range.
|
|
*/
|
|
static inline unsigned long xe_svm_range_end(struct xe_svm_range *range)
|
|
{
|
|
return drm_gpusvm_range_end(&range->base);
|
|
}
|
|
|
|
/**
|
|
* xe_svm_range_size() - SVM range size
|
|
* @range: SVM range
|
|
*
|
|
* Return: Size of range.
|
|
*/
|
|
static inline unsigned long xe_svm_range_size(struct xe_svm_range *range)
|
|
{
|
|
return drm_gpusvm_range_size(&range->base);
|
|
}
|
|
|
|
void xe_svm_flush(struct xe_vm *vm);
|
|
|
|
int xe_pagemap_shrinker_create(struct xe_device *xe);
|
|
|
|
int xe_pagemap_cache_create(struct xe_tile *tile);
|
|
|
|
struct drm_pagemap *xe_drm_pagemap_from_fd(int fd, u32 region_instance);
|
|
|
|
#else
|
|
#include <linux/interval_tree.h>
|
|
#include "xe_vm.h"
|
|
|
|
struct drm_pagemap_addr;
|
|
struct drm_gpusvm_ctx;
|
|
struct drm_gpusvm_range;
|
|
struct xe_bo;
|
|
struct xe_device;
|
|
struct xe_vm;
|
|
struct xe_vma;
|
|
struct xe_tile;
|
|
struct xe_vram_region;
|
|
|
|
#define XE_INTERCONNECT_VRAM 1
|
|
#define XE_INTERCONNECT_P2P (XE_INTERCONNECT_VRAM + 1)
|
|
|
|
struct xe_svm_range {
|
|
struct {
|
|
struct interval_tree_node itree;
|
|
struct {
|
|
const struct drm_pagemap_addr *dma_addr;
|
|
} pages;
|
|
} base;
|
|
u32 tile_present;
|
|
u32 tile_invalidated;
|
|
};
|
|
|
|
static inline bool xe_svm_range_pages_valid(struct xe_svm_range *range)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
static inline
|
|
int xe_devm_add(struct xe_tile *tile, struct xe_vram_region *vr)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline
|
|
int xe_svm_init(struct xe_vm *vm)
|
|
{
|
|
#if IS_ENABLED(CONFIG_DRM_GPUSVM)
|
|
return drm_gpusvm_init(&vm->svm.gpusvm, "Xe SVM (simple)", &vm->xe->drm,
|
|
NULL, 0, 0, 0, NULL, NULL, 0);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static inline
|
|
void xe_svm_fini(struct xe_vm *vm)
|
|
{
|
|
#if IS_ENABLED(CONFIG_DRM_GPUSVM)
|
|
xe_assert(vm->xe, xe_vm_is_closed(vm));
|
|
drm_gpusvm_fini(&vm->svm.gpusvm);
|
|
#endif
|
|
}
|
|
|
|
static inline
|
|
void xe_svm_close(struct xe_vm *vm)
|
|
{
|
|
}
|
|
|
|
static inline
|
|
int xe_svm_handle_pagefault(struct xe_vm *vm, struct xe_vma *vma,
|
|
struct xe_gt *gt, u64 fault_addr,
|
|
bool atomic)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline
|
|
bool xe_svm_has_mapping(struct xe_vm *vm, u64 start, u64 end)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
static inline
|
|
int xe_svm_bo_evict(struct xe_bo *bo)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline
|
|
void xe_svm_range_debug(struct xe_svm_range *range, const char *operation)
|
|
{
|
|
}
|
|
|
|
static inline int
|
|
xe_svm_alloc_vram(struct xe_svm_range *range, const struct drm_gpusvm_ctx *ctx,
|
|
struct drm_pagemap *dpagemap)
|
|
{
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
static inline
|
|
struct xe_svm_range *xe_svm_range_find_or_insert(struct xe_vm *vm, u64 addr,
|
|
struct xe_vma *vma, struct drm_gpusvm_ctx *ctx)
|
|
{
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
static inline
|
|
int xe_svm_range_get_pages(struct xe_vm *vm, struct xe_svm_range *range,
|
|
struct drm_gpusvm_ctx *ctx)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
static inline struct xe_svm_range *to_xe_range(struct drm_gpusvm_range *r)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static inline unsigned long xe_svm_range_start(struct xe_svm_range *range)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline unsigned long xe_svm_range_end(struct xe_svm_range *range)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline unsigned long xe_svm_range_size(struct xe_svm_range *range)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline
|
|
bool xe_svm_range_needs_migrate_to_vram(struct xe_svm_range *range, struct xe_vma *vma,
|
|
const struct drm_pagemap *dpagemap)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
static inline
|
|
void xe_svm_range_migrate_to_smem(struct xe_vm *vm, struct xe_svm_range *range)
|
|
{
|
|
}
|
|
|
|
static inline
|
|
bool xe_svm_range_validate(struct xe_vm *vm,
|
|
struct xe_svm_range *range,
|
|
u8 tile_mask, bool devmem_preferred)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
static inline
|
|
u64 xe_svm_find_vma_start(struct xe_vm *vm, u64 addr, u64 end, struct xe_vma *vma)
|
|
{
|
|
return ULONG_MAX;
|
|
}
|
|
|
|
static inline
|
|
void xe_svm_unmap_address_range(struct xe_vm *vm, u64 start, u64 end)
|
|
{
|
|
}
|
|
|
|
static inline
|
|
u8 xe_svm_ranges_zap_ptes_in_range(struct xe_vm *vm, u64 start, u64 end)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline
|
|
struct drm_pagemap *xe_vma_resolve_pagemap(struct xe_vma *vma, struct xe_tile *tile)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static inline void *xe_svm_private_page_owner(struct xe_vm *vm, bool force_smem)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static inline void xe_svm_flush(struct xe_vm *vm)
|
|
{
|
|
}
|
|
|
|
static inline int xe_pagemap_shrinker_create(struct xe_device *xe)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline int xe_pagemap_cache_create(struct xe_tile *tile)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline struct drm_pagemap *xe_drm_pagemap_from_fd(int fd, u32 region_instance)
|
|
{
|
|
return ERR_PTR(-ENOENT);
|
|
}
|
|
|
|
#define xe_svm_range_has_dma_mapping(...) false
|
|
#endif /* CONFIG_DRM_XE_GPUSVM */
|
|
|
|
#if IS_ENABLED(CONFIG_DRM_GPUSVM) /* Need to support userptr without XE_GPUSVM */
|
|
#define xe_svm_assert_in_notifier(vm__) \
|
|
lockdep_assert_held_write(&(vm__)->svm.gpusvm.notifier_lock)
|
|
|
|
/*
|
|
* Assert the svm notifier_lock is held. Read mode by default; write mode
|
|
* when CONFIG_DRM_XE_USERPTR_INVAL_INJECT is on, because that path forces
|
|
* a userptr invalidation that ends in drm_gpusvm_unmap_pages() with
|
|
* ctx->in_notifier=true, which requires the lock held for write.
|
|
*/
|
|
#if IS_ENABLED(CONFIG_DRM_XE_USERPTR_INVAL_INJECT)
|
|
#define xe_svm_assert_held_read_or_inject_write(vm__) \
|
|
lockdep_assert_held_write(&(vm__)->svm.gpusvm.notifier_lock)
|
|
#else
|
|
#define xe_svm_assert_held_read_or_inject_write(vm__) \
|
|
lockdep_assert_held_read(&(vm__)->svm.gpusvm.notifier_lock)
|
|
#endif
|
|
|
|
#define xe_svm_notifier_lock(vm__) \
|
|
drm_gpusvm_notifier_lock(&(vm__)->svm.gpusvm)
|
|
|
|
#define xe_svm_notifier_lock_interruptible(vm__) \
|
|
down_read_interruptible(&(vm__)->svm.gpusvm.notifier_lock)
|
|
|
|
#define xe_svm_notifier_unlock(vm__) \
|
|
drm_gpusvm_notifier_unlock(&(vm__)->svm.gpusvm)
|
|
|
|
#else
|
|
#define xe_svm_assert_in_notifier(...) do {} while (0)
|
|
|
|
static inline void xe_svm_assert_held_read_or_inject_write(struct xe_vm *vm)
|
|
{
|
|
}
|
|
|
|
static inline void xe_svm_notifier_lock(struct xe_vm *vm)
|
|
{
|
|
}
|
|
|
|
static inline int xe_svm_notifier_lock_interruptible(struct xe_vm *vm)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline void xe_svm_notifier_unlock(struct xe_vm *vm)
|
|
{
|
|
}
|
|
#endif /* CONFIG_DRM_GPUSVM */
|
|
|
|
#endif
|