mirror of
https://github.com/torvalds/linux.git
synced 2026-04-19 15:24:02 -04:00
Pull drm updates from Dave Airlie:
"There was a rather late merge of a new color pipeline feature, that
some userspace projects are blocked on, and has seen a lot of work in
amdgpu. This should have seen some time in -next. There is additional
support for this for Intel, that if it arrives in the next day or two
I'll pass it on in another pull request and you can decide if you want
to take it.
Highlights:
- Arm Ethos NPU accelerator driver
- new DRM color pipeline support
- amdgpu will now run discrete SI/CIK cards instead of radeon, which
enables vulkan support in userspace
- msm gets gen8 gpu support
- initial Xe3P support in xe
Full detail summary:
New driver:
- Arm Ethos-U65/U85 accel driver
Core:
- support the drm color pipeline in vkms/amdgfx
- add support for drm colorop pipeline
- add COLOR PIPELINE plane property
- add DRM_CLIENT_CAP_PLANE_COLOR_PIPELINE
- throttle dirty worker with vblank
- use drm_for_each_bridge_in_chain_scoped in drm's bridge code
- Ensure drm_client_modeset tests are enabled in UML
- add simulated vblank interrupt - use in drivers
- dumb buffer sizing helper
- move freeing of drm client memory to driver
- crtc sharpness strength property
- stop using system_wq in scheduler/drivers
- support emergency restore in drm-client
Rust:
- make slice::as_flattened usable on all supported rustc
- add FromBytes::from_bytes_prefix() method
- remove redundant device ptr from Rust GEM object
- Change how AlwaysRefCounted is implemented for GEM objects
gpuvm:
- Add deferred vm_bo cleanup to GPUVM (for rust)
atomic:
- cleanup and improve state handling interfaces
buddy:
- optimize block management
dma-buf:
- heaps: Create heap per CMA reserved location
- improve userspace documentation
dp:
- add POST_LT_ADJ_REQ training sequence
- DPCD dSC quirk for synaptics panamera devices
- helpers to query branch DSC max throughput
ttm:
- Rename ttm_bo_put to ttm_bo_fini
- allow page protection flags on risc-v
- rework pipelined eviction fence handling
amdgpu:
- enable amdgpu by default for SI/CI dGPUs
- enable DC by default on SI
- refactor CIK/SI enablement
- add ABM KMS property
- Re-enable DM idle optimizations
- DC Analog encoders support
- Powerplay fixes for fiji/iceland
- Enable DC on bonaire by default
- HMM cleanup
- Add new RAS framework
- DML2.1 updates
- YCbCr420 fixes
- DC FP fixes
- DMUB fixes
- LTTPR fixes
- DTBCLK fixes
- DMU cursor offload handling
- Userq validation improvements
- Unify shutdown callback handling
- Suspend improvements
- Power limit code cleanup
- SR-IOV fixes
- AUX backlight fixes
- DCN 3.5 fixes
- HDMI compliance fixes
- DCN 4.0.1 cursor updates
- DCN interrupt fix
- DC KMS full update improvements
- Add additional HDCP traces
- DCN 3.2 fixes
- DP MST fixes
- Add support for new SR-IOV mailbox interface
- UQ reset support
- HDP flush rework
- VCE1 support
amdkfd:
- HMM cleanups
- Relax checks on save area overallocations
- Fix GPU mappings after prefetch
radeon:
- refactor CIK/SI enablement
xe:
- Initial Xe3P support
- panic support on VRAM for display
- fix stolen size check
- Loosen used tracking restriction
- New SR-IOV debugfs structure and debugfs updates
- Hide the GPU madvise flag behind a VM_BIND flag
- Always expose VRAM provisioning data on discrete GPUs
- Allow VRAM mappings for userptr when used with SVM
- Allow pinning of p2p dma-buf
- Use per-tile debugfs where appropriate
- Add documentation for Execution Queues
- PF improvements
- VF migration recovery redesign work
- User / Kernel VRAM partitioning
- Update Tile-based messages
- Allow configfs to disable specific GT types
- VF provisioning and migration improvements
- use SVM range helpers in PT layer
- Initial CRI support
- access VF registers using dedicated MMIO view
- limit number of jobs per exec queue
- add sriov_admin sysfs tree
- more crescent island specific support
- debugfs residency counter
- SRIOV migration work
- runtime registers for GFX 35
i915:
- add initial Xe3p_LPD display version 35 support
- Enable LNL+ content adaptive sharpness filter
- Use optimized VRR guardband
- Enable Xe3p LT PHY
- enable FBC support for Xe3p_LPD display
- add display 30.02 firmware support
- refactor SKL+ watermark latency setup
- refactor fbdev handling
- call i915/xe runtime PM via function pointers
- refactor i915/xe stolen memory/display interfaces
- use display version instead of gfx version in display code
- extend i915_display_info with Type-C port details
- lots of display cleanups/refactorings
- set O_LARGEFILE in __create_shmem
- skuip guc communication warning on reset
- fix time conversions
- defeature DRRS on LNL+
- refactor intel_frontbuffer split between i915/xe/display
- convert inteL_rom interfaces to struct drm_device
- unify display register polling interfaces
- aovid lock inversion when pinning to GGTT on CHV/BXT+VTD
panel:
- Add KD116N3730A08/A12, chromebook mt8189
- JT101TM023, LQ079L1SX01,
- GLD070WX3-SL01 MIPI DSI
- Samsung LTL106AL0, Samsung LTL106AL01
- Raystar RFF500F-AWH-DNN
- Winstar WF70A8SYJHLNGA
- Wanchanglong w552946aaa
- Samsung SOFEF00
- Lenovo X13s panel
- ilitek-ili9881c - add rpi 5" support
- visionx-rm69299 - add backlight support
- edp - support AUI B116XAN02.0
bridge:
- improve ref counting
- ti-sn65dsi86 - add support for DP mode with HPD
- synopsis: support CEC, init timer with correct freq
- ASL CS5263 DP-to-HDMI bridge support
nova-core:
- introduce bitfield! macro
- introduce safe integer converters
- GSP inits to fully booted state on Ampere
- Use more future-proof register for GPU identification
nova-drm:
- select NOVA_CORE
- 64-bit only
nouveau:
- improve reclocking on tegra 186+
- add large page and compression support
msm:
- GPU:
- Gen8 support: A840 (Kaanapali) and X2-85 (Glymur)
- A612 support
- MDSS:
- Added support for Glymur and QCS8300 platforms
- DPU:
- Enabled Quad-Pipe support, unlocking higher resolutions support
- Added support for Glymur platform
- Documented DPU on QCS8300 platform as supported
- DisplayPort:
- Added support for Glymur platform
- Added support lame remapping inside DP block
- Documented DisplayPort controller on QCS8300 and SM6150/QCS615
as supported
tegra:
- NVJPG driver
panfrost:
- display JM contexts over debugfs
- export JM contexts to userspace
- improve error and job handling
panthor:
- support custom ASN_HASH for mt8196
- support mali-G1 GPU
- flush shmem write before mapping buffers uncached
- make timeout per-queue instead of per-job
mediatek:
- MT8195/88 HDMIv2/DDCv2 support
rockchip:
- dsi: add support for RK3368
amdxdna:
- enhance runtime PM
- last hardware error reading uapi
- support firmware debug output
- add resource and telemetry data uapi
- preemption support
imx:
- add driver for HDMI TX Parallel audio interface
ivpu:
- add support for user-managed preemption buffer
- add userptr support
- update JSM firware API to 3.33.0
- add better alloc/free warnings
- fix page fault in unbind all bos
- rework bind/unbind of imported buffers
- enable MCA ECC signalling
- split fw runtime and global memory buffers
- add fdinfo memory statistics
tidss:
- convert to drm logging
- logging cleanup
ast:
- refactor generation init paths
- add per chip generation detect_tx_chip
- set quirks for each chip model
atmel-hlcdc:
- set LCDC_ATTRE register in plane disable
- set correct values for plane scaler
solomon:
- use drm helper for get_modes and move_valid
sitronix:
- fix output position when clearing screens
qaic:
- support dma-buf exports
- support new firmware's READ_DATA implementation
- sahara AIC200 image table update
- add sysfs support
- add coredump support
- add uevents support
- PM support
sun4i:
- layer refactors to decouple plane from output
- improve DE33 support
vc4:
- switch to generic CEC helpers
komeda:
- use drm_ logging functions
vkms:
- configfs support for display configuration
vgem:
- fix fence timer deadlock
etnaviv:
- add HWDB entry for GC8000 Nano Ultra VIP r6205"
* tag 'drm-next-2025-12-03' of https://gitlab.freedesktop.org/drm/kernel: (1869 commits)
Revert "drm/amd: Skip power ungate during suspend for VPE"
drm/amdgpu: use common defines for HUB faults
drm/amdgpu/gmc12: add amdgpu_vm_handle_fault() handling
drm/amdgpu/gmc11: add amdgpu_vm_handle_fault() handling
drm/amdgpu: use static ids for ACP platform devs
drm/amdgpu/sdma6: Update SDMA 6.0.3 FW version to include UMQ protected-fence fix
drm/amdgpu: Forward VMID reservation errors
drm/amdgpu/gmc8: Delegate VM faults to soft IRQ handler ring
drm/amdgpu/gmc7: Delegate VM faults to soft IRQ handler ring
drm/amdgpu/gmc6: Delegate VM faults to soft IRQ handler ring
drm/amdgpu/gmc6: Cache VM fault info
drm/amdgpu/gmc6: Don't print MC client as it's unknown
drm/amdgpu/cz_ih: Enable soft IRQ handler ring
drm/amdgpu/tonga_ih: Enable soft IRQ handler ring
drm/amdgpu/iceland_ih: Enable soft IRQ handler ring
drm/amdgpu/cik_ih: Enable soft IRQ handler ring
drm/amdgpu/si_ih: Enable soft IRQ handler ring
drm/amd/display: fix typo in display_mode_core_structs.h
drm/amd/display: fix Smart Power OLED not working after S4
drm/amd/display: Move RGB-type check for audio sync to DCE HW sequence
...
4411 lines
105 KiB
C
4411 lines
105 KiB
C
// SPDX-License-Identifier: MIT
|
|
/*
|
|
* Copyright © 2021 Intel Corporation
|
|
*/
|
|
|
|
#include "xe_vm.h"
|
|
|
|
#include <linux/dma-fence-array.h>
|
|
#include <linux/nospec.h>
|
|
|
|
#include <drm/drm_drv.h>
|
|
#include <drm/drm_exec.h>
|
|
#include <drm/drm_print.h>
|
|
#include <drm/ttm/ttm_tt.h>
|
|
#include <uapi/drm/xe_drm.h>
|
|
#include <linux/ascii85.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/swap.h>
|
|
|
|
#include <generated/xe_wa_oob.h>
|
|
|
|
#include "regs/xe_gtt_defs.h"
|
|
#include "xe_assert.h"
|
|
#include "xe_bo.h"
|
|
#include "xe_device.h"
|
|
#include "xe_drm_client.h"
|
|
#include "xe_exec_queue.h"
|
|
#include "xe_migrate.h"
|
|
#include "xe_pat.h"
|
|
#include "xe_pm.h"
|
|
#include "xe_preempt_fence.h"
|
|
#include "xe_pt.h"
|
|
#include "xe_pxp.h"
|
|
#include "xe_res_cursor.h"
|
|
#include "xe_sriov_vf.h"
|
|
#include "xe_svm.h"
|
|
#include "xe_sync.h"
|
|
#include "xe_tile.h"
|
|
#include "xe_tlb_inval.h"
|
|
#include "xe_trace_bo.h"
|
|
#include "xe_wa.h"
|
|
|
|
static struct drm_gem_object *xe_vm_obj(struct xe_vm *vm)
|
|
{
|
|
return vm->gpuvm.r_obj;
|
|
}
|
|
|
|
/**
|
|
* xe_vm_drm_exec_lock() - Lock the vm's resv with a drm_exec transaction
|
|
* @vm: The vm whose resv is to be locked.
|
|
* @exec: The drm_exec transaction.
|
|
*
|
|
* Helper to lock the vm's resv as part of a drm_exec transaction.
|
|
*
|
|
* Return: %0 on success. See drm_exec_lock_obj() for error codes.
|
|
*/
|
|
int xe_vm_drm_exec_lock(struct xe_vm *vm, struct drm_exec *exec)
|
|
{
|
|
return drm_exec_lock_obj(exec, xe_vm_obj(vm));
|
|
}
|
|
|
|
static bool preempt_fences_waiting(struct xe_vm *vm)
|
|
{
|
|
struct xe_exec_queue *q;
|
|
|
|
lockdep_assert_held(&vm->lock);
|
|
xe_vm_assert_held(vm);
|
|
|
|
list_for_each_entry(q, &vm->preempt.exec_queues, lr.link) {
|
|
if (!q->lr.pfence ||
|
|
test_bit(DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT,
|
|
&q->lr.pfence->flags)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void free_preempt_fences(struct list_head *list)
|
|
{
|
|
struct list_head *link, *next;
|
|
|
|
list_for_each_safe(link, next, list)
|
|
xe_preempt_fence_free(to_preempt_fence_from_link(link));
|
|
}
|
|
|
|
static int alloc_preempt_fences(struct xe_vm *vm, struct list_head *list,
|
|
unsigned int *count)
|
|
{
|
|
lockdep_assert_held(&vm->lock);
|
|
xe_vm_assert_held(vm);
|
|
|
|
if (*count >= vm->preempt.num_exec_queues)
|
|
return 0;
|
|
|
|
for (; *count < vm->preempt.num_exec_queues; ++(*count)) {
|
|
struct xe_preempt_fence *pfence = xe_preempt_fence_alloc();
|
|
|
|
if (IS_ERR(pfence))
|
|
return PTR_ERR(pfence);
|
|
|
|
list_move_tail(xe_preempt_fence_link(pfence), list);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wait_for_existing_preempt_fences(struct xe_vm *vm)
|
|
{
|
|
struct xe_exec_queue *q;
|
|
bool vf_migration = IS_SRIOV_VF(vm->xe) &&
|
|
xe_sriov_vf_migration_supported(vm->xe);
|
|
signed long wait_time = vf_migration ? HZ / 5 : MAX_SCHEDULE_TIMEOUT;
|
|
|
|
xe_vm_assert_held(vm);
|
|
|
|
list_for_each_entry(q, &vm->preempt.exec_queues, lr.link) {
|
|
if (q->lr.pfence) {
|
|
long timeout;
|
|
|
|
timeout = dma_fence_wait_timeout(q->lr.pfence, false,
|
|
wait_time);
|
|
if (!timeout) {
|
|
xe_assert(vm->xe, vf_migration);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
/* Only -ETIME on fence indicates VM needs to be killed */
|
|
if (timeout < 0 || q->lr.pfence->error == -ETIME)
|
|
return -ETIME;
|
|
|
|
dma_fence_put(q->lr.pfence);
|
|
q->lr.pfence = NULL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool xe_vm_is_idle(struct xe_vm *vm)
|
|
{
|
|
struct xe_exec_queue *q;
|
|
|
|
xe_vm_assert_held(vm);
|
|
list_for_each_entry(q, &vm->preempt.exec_queues, lr.link) {
|
|
if (!xe_exec_queue_is_idle(q))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void arm_preempt_fences(struct xe_vm *vm, struct list_head *list)
|
|
{
|
|
struct list_head *link;
|
|
struct xe_exec_queue *q;
|
|
|
|
list_for_each_entry(q, &vm->preempt.exec_queues, lr.link) {
|
|
struct dma_fence *fence;
|
|
|
|
link = list->next;
|
|
xe_assert(vm->xe, link != list);
|
|
|
|
fence = xe_preempt_fence_arm(to_preempt_fence_from_link(link),
|
|
q, q->lr.context,
|
|
++q->lr.seqno);
|
|
dma_fence_put(q->lr.pfence);
|
|
q->lr.pfence = fence;
|
|
}
|
|
}
|
|
|
|
static int add_preempt_fences(struct xe_vm *vm, struct xe_bo *bo)
|
|
{
|
|
struct xe_exec_queue *q;
|
|
int err;
|
|
|
|
xe_bo_assert_held(bo);
|
|
|
|
if (!vm->preempt.num_exec_queues)
|
|
return 0;
|
|
|
|
err = dma_resv_reserve_fences(bo->ttm.base.resv, vm->preempt.num_exec_queues);
|
|
if (err)
|
|
return err;
|
|
|
|
list_for_each_entry(q, &vm->preempt.exec_queues, lr.link)
|
|
if (q->lr.pfence) {
|
|
dma_resv_add_fence(bo->ttm.base.resv,
|
|
q->lr.pfence,
|
|
DMA_RESV_USAGE_BOOKKEEP);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void resume_and_reinstall_preempt_fences(struct xe_vm *vm,
|
|
struct drm_exec *exec)
|
|
{
|
|
struct xe_exec_queue *q;
|
|
|
|
lockdep_assert_held(&vm->lock);
|
|
xe_vm_assert_held(vm);
|
|
|
|
list_for_each_entry(q, &vm->preempt.exec_queues, lr.link) {
|
|
q->ops->resume(q);
|
|
|
|
drm_gpuvm_resv_add_fence(&vm->gpuvm, exec, q->lr.pfence,
|
|
DMA_RESV_USAGE_BOOKKEEP, DMA_RESV_USAGE_BOOKKEEP);
|
|
}
|
|
}
|
|
|
|
int xe_vm_add_compute_exec_queue(struct xe_vm *vm, struct xe_exec_queue *q)
|
|
{
|
|
struct drm_gpuvm_exec vm_exec = {
|
|
.vm = &vm->gpuvm,
|
|
.flags = DRM_EXEC_INTERRUPTIBLE_WAIT,
|
|
.num_fences = 1,
|
|
};
|
|
struct drm_exec *exec = &vm_exec.exec;
|
|
struct xe_validation_ctx ctx;
|
|
struct dma_fence *pfence;
|
|
int err;
|
|
bool wait;
|
|
|
|
xe_assert(vm->xe, xe_vm_in_preempt_fence_mode(vm));
|
|
|
|
down_write(&vm->lock);
|
|
err = xe_validation_exec_lock(&ctx, &vm_exec, &vm->xe->val);
|
|
if (err)
|
|
goto out_up_write;
|
|
|
|
pfence = xe_preempt_fence_create(q, q->lr.context,
|
|
++q->lr.seqno);
|
|
if (IS_ERR(pfence)) {
|
|
err = PTR_ERR(pfence);
|
|
goto out_fini;
|
|
}
|
|
|
|
list_add(&q->lr.link, &vm->preempt.exec_queues);
|
|
++vm->preempt.num_exec_queues;
|
|
q->lr.pfence = pfence;
|
|
|
|
xe_svm_notifier_lock(vm);
|
|
|
|
drm_gpuvm_resv_add_fence(&vm->gpuvm, exec, pfence,
|
|
DMA_RESV_USAGE_BOOKKEEP, DMA_RESV_USAGE_BOOKKEEP);
|
|
|
|
/*
|
|
* Check to see if a preemption on VM is in flight or userptr
|
|
* invalidation, if so trigger this preempt fence to sync state with
|
|
* other preempt fences on the VM.
|
|
*/
|
|
wait = __xe_vm_userptr_needs_repin(vm) || preempt_fences_waiting(vm);
|
|
if (wait)
|
|
dma_fence_enable_sw_signaling(pfence);
|
|
|
|
xe_svm_notifier_unlock(vm);
|
|
|
|
out_fini:
|
|
xe_validation_ctx_fini(&ctx);
|
|
out_up_write:
|
|
up_write(&vm->lock);
|
|
|
|
return err;
|
|
}
|
|
ALLOW_ERROR_INJECTION(xe_vm_add_compute_exec_queue, ERRNO);
|
|
|
|
/**
|
|
* xe_vm_remove_compute_exec_queue() - Remove compute exec queue from VM
|
|
* @vm: The VM.
|
|
* @q: The exec_queue
|
|
*
|
|
* Note that this function might be called multiple times on the same queue.
|
|
*/
|
|
void xe_vm_remove_compute_exec_queue(struct xe_vm *vm, struct xe_exec_queue *q)
|
|
{
|
|
if (!xe_vm_in_preempt_fence_mode(vm))
|
|
return;
|
|
|
|
down_write(&vm->lock);
|
|
if (!list_empty(&q->lr.link)) {
|
|
list_del_init(&q->lr.link);
|
|
--vm->preempt.num_exec_queues;
|
|
}
|
|
if (q->lr.pfence) {
|
|
dma_fence_enable_sw_signaling(q->lr.pfence);
|
|
dma_fence_put(q->lr.pfence);
|
|
q->lr.pfence = NULL;
|
|
}
|
|
up_write(&vm->lock);
|
|
}
|
|
|
|
#define XE_VM_REBIND_RETRY_TIMEOUT_MS 1000
|
|
|
|
/**
|
|
* xe_vm_kill() - VM Kill
|
|
* @vm: The VM.
|
|
* @unlocked: Flag indicates the VM's dma-resv is not held
|
|
*
|
|
* Kill the VM by setting banned flag indicated VM is no longer available for
|
|
* use. If in preempt fence mode, also kill all exec queue attached to the VM.
|
|
*/
|
|
void xe_vm_kill(struct xe_vm *vm, bool unlocked)
|
|
{
|
|
struct xe_exec_queue *q;
|
|
|
|
lockdep_assert_held(&vm->lock);
|
|
|
|
if (unlocked)
|
|
xe_vm_lock(vm, false);
|
|
|
|
vm->flags |= XE_VM_FLAG_BANNED;
|
|
trace_xe_vm_kill(vm);
|
|
|
|
list_for_each_entry(q, &vm->preempt.exec_queues, lr.link)
|
|
q->ops->kill(q);
|
|
|
|
if (unlocked)
|
|
xe_vm_unlock(vm);
|
|
|
|
/* TODO: Inform user the VM is banned */
|
|
}
|
|
|
|
static int xe_gpuvm_validate(struct drm_gpuvm_bo *vm_bo, struct drm_exec *exec)
|
|
{
|
|
struct xe_vm *vm = gpuvm_to_vm(vm_bo->vm);
|
|
struct drm_gpuva *gpuva;
|
|
int ret;
|
|
|
|
lockdep_assert_held(&vm->lock);
|
|
drm_gpuvm_bo_for_each_va(gpuva, vm_bo)
|
|
list_move_tail(&gpuva_to_vma(gpuva)->combined_links.rebind,
|
|
&vm->rebind_list);
|
|
|
|
if (!try_wait_for_completion(&vm->xe->pm_block))
|
|
return -EAGAIN;
|
|
|
|
ret = xe_bo_validate(gem_to_xe_bo(vm_bo->obj), vm, false, exec);
|
|
if (ret)
|
|
return ret;
|
|
|
|
vm_bo->evicted = false;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* xe_vm_validate_rebind() - Validate buffer objects and rebind vmas
|
|
* @vm: The vm for which we are rebinding.
|
|
* @exec: The struct drm_exec with the locked GEM objects.
|
|
* @num_fences: The number of fences to reserve for the operation, not
|
|
* including rebinds and validations.
|
|
*
|
|
* Validates all evicted gem objects and rebinds their vmas. Note that
|
|
* rebindings may cause evictions and hence the validation-rebind
|
|
* sequence is rerun until there are no more objects to validate.
|
|
*
|
|
* Return: 0 on success, negative error code on error. In particular,
|
|
* may return -EINTR or -ERESTARTSYS if interrupted, and -EDEADLK if
|
|
* the drm_exec transaction needs to be restarted.
|
|
*/
|
|
int xe_vm_validate_rebind(struct xe_vm *vm, struct drm_exec *exec,
|
|
unsigned int num_fences)
|
|
{
|
|
struct drm_gem_object *obj;
|
|
unsigned long index;
|
|
int ret;
|
|
|
|
do {
|
|
ret = drm_gpuvm_validate(&vm->gpuvm, exec);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = xe_vm_rebind(vm, false);
|
|
if (ret)
|
|
return ret;
|
|
} while (!list_empty(&vm->gpuvm.evict.list));
|
|
|
|
drm_exec_for_each_locked_object(exec, index, obj) {
|
|
ret = dma_resv_reserve_fences(obj->resv, num_fences);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int xe_preempt_work_begin(struct drm_exec *exec, struct xe_vm *vm,
|
|
bool *done)
|
|
{
|
|
int err;
|
|
|
|
err = drm_gpuvm_prepare_vm(&vm->gpuvm, exec, 0);
|
|
if (err)
|
|
return err;
|
|
|
|
if (xe_vm_is_idle(vm)) {
|
|
vm->preempt.rebind_deactivated = true;
|
|
*done = true;
|
|
return 0;
|
|
}
|
|
|
|
if (!preempt_fences_waiting(vm)) {
|
|
*done = true;
|
|
return 0;
|
|
}
|
|
|
|
err = drm_gpuvm_prepare_objects(&vm->gpuvm, exec, 0);
|
|
if (err)
|
|
return err;
|
|
|
|
err = wait_for_existing_preempt_fences(vm);
|
|
if (err)
|
|
return err;
|
|
|
|
/*
|
|
* Add validation and rebinding to the locking loop since both can
|
|
* cause evictions which may require blocing dma_resv locks.
|
|
* The fence reservation here is intended for the new preempt fences
|
|
* we attach at the end of the rebind work.
|
|
*/
|
|
return xe_vm_validate_rebind(vm, exec, vm->preempt.num_exec_queues);
|
|
}
|
|
|
|
static bool vm_suspend_rebind_worker(struct xe_vm *vm)
|
|
{
|
|
struct xe_device *xe = vm->xe;
|
|
bool ret = false;
|
|
|
|
mutex_lock(&xe->rebind_resume_lock);
|
|
if (!try_wait_for_completion(&vm->xe->pm_block)) {
|
|
ret = true;
|
|
list_move_tail(&vm->preempt.pm_activate_link, &xe->rebind_resume_list);
|
|
}
|
|
mutex_unlock(&xe->rebind_resume_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* xe_vm_resume_rebind_worker() - Resume the rebind worker.
|
|
* @vm: The vm whose preempt worker to resume.
|
|
*
|
|
* Resume a preempt worker that was previously suspended by
|
|
* vm_suspend_rebind_worker().
|
|
*/
|
|
void xe_vm_resume_rebind_worker(struct xe_vm *vm)
|
|
{
|
|
queue_work(vm->xe->ordered_wq, &vm->preempt.rebind_work);
|
|
}
|
|
|
|
static void preempt_rebind_work_func(struct work_struct *w)
|
|
{
|
|
struct xe_vm *vm = container_of(w, struct xe_vm, preempt.rebind_work);
|
|
struct xe_validation_ctx ctx;
|
|
struct drm_exec exec;
|
|
unsigned int fence_count = 0;
|
|
LIST_HEAD(preempt_fences);
|
|
int err = 0;
|
|
long wait;
|
|
int __maybe_unused tries = 0;
|
|
|
|
xe_assert(vm->xe, xe_vm_in_preempt_fence_mode(vm));
|
|
trace_xe_vm_rebind_worker_enter(vm);
|
|
|
|
down_write(&vm->lock);
|
|
|
|
if (xe_vm_is_closed_or_banned(vm)) {
|
|
up_write(&vm->lock);
|
|
trace_xe_vm_rebind_worker_exit(vm);
|
|
return;
|
|
}
|
|
|
|
retry:
|
|
if (!try_wait_for_completion(&vm->xe->pm_block) && vm_suspend_rebind_worker(vm)) {
|
|
up_write(&vm->lock);
|
|
/* We don't actually block but don't make progress. */
|
|
xe_pm_might_block_on_suspend();
|
|
return;
|
|
}
|
|
|
|
if (xe_vm_userptr_check_repin(vm)) {
|
|
err = xe_vm_userptr_pin(vm);
|
|
if (err)
|
|
goto out_unlock_outer;
|
|
}
|
|
|
|
err = xe_validation_ctx_init(&ctx, &vm->xe->val, &exec,
|
|
(struct xe_val_flags) {.interruptible = true});
|
|
if (err)
|
|
goto out_unlock_outer;
|
|
|
|
drm_exec_until_all_locked(&exec) {
|
|
bool done = false;
|
|
|
|
err = xe_preempt_work_begin(&exec, vm, &done);
|
|
drm_exec_retry_on_contention(&exec);
|
|
xe_validation_retry_on_oom(&ctx, &err);
|
|
if (err || done) {
|
|
xe_validation_ctx_fini(&ctx);
|
|
goto out_unlock_outer;
|
|
}
|
|
}
|
|
|
|
err = alloc_preempt_fences(vm, &preempt_fences, &fence_count);
|
|
if (err)
|
|
goto out_unlock;
|
|
|
|
xe_vm_set_validation_exec(vm, &exec);
|
|
err = xe_vm_rebind(vm, true);
|
|
xe_vm_set_validation_exec(vm, NULL);
|
|
if (err)
|
|
goto out_unlock;
|
|
|
|
/* Wait on rebinds and munmap style VM unbinds */
|
|
wait = dma_resv_wait_timeout(xe_vm_resv(vm),
|
|
DMA_RESV_USAGE_KERNEL,
|
|
false, MAX_SCHEDULE_TIMEOUT);
|
|
if (wait <= 0) {
|
|
err = -ETIME;
|
|
goto out_unlock;
|
|
}
|
|
|
|
#define retry_required(__tries, __vm) \
|
|
(IS_ENABLED(CONFIG_DRM_XE_USERPTR_INVAL_INJECT) ? \
|
|
(!(__tries)++ || __xe_vm_userptr_needs_repin(__vm)) : \
|
|
__xe_vm_userptr_needs_repin(__vm))
|
|
|
|
xe_svm_notifier_lock(vm);
|
|
if (retry_required(tries, vm)) {
|
|
xe_svm_notifier_unlock(vm);
|
|
err = -EAGAIN;
|
|
goto out_unlock;
|
|
}
|
|
|
|
#undef retry_required
|
|
|
|
spin_lock(&vm->xe->ttm.lru_lock);
|
|
ttm_lru_bulk_move_tail(&vm->lru_bulk_move);
|
|
spin_unlock(&vm->xe->ttm.lru_lock);
|
|
|
|
/* Point of no return. */
|
|
arm_preempt_fences(vm, &preempt_fences);
|
|
resume_and_reinstall_preempt_fences(vm, &exec);
|
|
xe_svm_notifier_unlock(vm);
|
|
|
|
out_unlock:
|
|
xe_validation_ctx_fini(&ctx);
|
|
out_unlock_outer:
|
|
if (err == -EAGAIN) {
|
|
trace_xe_vm_rebind_worker_retry(vm);
|
|
|
|
/*
|
|
* We can't block in workers on a VF which supports migration
|
|
* given this can block the VF post-migration workers from
|
|
* getting scheduled.
|
|
*/
|
|
if (IS_SRIOV_VF(vm->xe) &&
|
|
xe_sriov_vf_migration_supported(vm->xe)) {
|
|
up_write(&vm->lock);
|
|
xe_vm_queue_rebind_worker(vm);
|
|
return;
|
|
}
|
|
|
|
goto retry;
|
|
}
|
|
|
|
if (err) {
|
|
drm_warn(&vm->xe->drm, "VM worker error: %d\n", err);
|
|
xe_vm_kill(vm, true);
|
|
}
|
|
up_write(&vm->lock);
|
|
|
|
free_preempt_fences(&preempt_fences);
|
|
|
|
trace_xe_vm_rebind_worker_exit(vm);
|
|
}
|
|
|
|
static int xe_vma_ops_alloc(struct xe_vma_ops *vops, bool array_of_binds)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < XE_MAX_TILES_PER_DEVICE; ++i) {
|
|
if (!vops->pt_update_ops[i].num_ops)
|
|
continue;
|
|
|
|
vops->pt_update_ops[i].ops =
|
|
kmalloc_array(vops->pt_update_ops[i].num_ops,
|
|
sizeof(*vops->pt_update_ops[i].ops),
|
|
GFP_KERNEL | __GFP_RETRY_MAYFAIL | __GFP_NOWARN);
|
|
if (!vops->pt_update_ops[i].ops)
|
|
return array_of_binds ? -ENOBUFS : -ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
ALLOW_ERROR_INJECTION(xe_vma_ops_alloc, ERRNO);
|
|
|
|
static void xe_vma_svm_prefetch_op_fini(struct xe_vma_op *op)
|
|
{
|
|
struct xe_vma *vma;
|
|
|
|
vma = gpuva_to_vma(op->base.prefetch.va);
|
|
|
|
if (op->base.op == DRM_GPUVA_OP_PREFETCH && xe_vma_is_cpu_addr_mirror(vma))
|
|
xa_destroy(&op->prefetch_range.range);
|
|
}
|
|
|
|
static void xe_vma_svm_prefetch_ops_fini(struct xe_vma_ops *vops)
|
|
{
|
|
struct xe_vma_op *op;
|
|
|
|
if (!(vops->flags & XE_VMA_OPS_FLAG_HAS_SVM_PREFETCH))
|
|
return;
|
|
|
|
list_for_each_entry(op, &vops->list, link)
|
|
xe_vma_svm_prefetch_op_fini(op);
|
|
}
|
|
|
|
static void xe_vma_ops_fini(struct xe_vma_ops *vops)
|
|
{
|
|
int i;
|
|
|
|
xe_vma_svm_prefetch_ops_fini(vops);
|
|
|
|
for (i = 0; i < XE_MAX_TILES_PER_DEVICE; ++i)
|
|
kfree(vops->pt_update_ops[i].ops);
|
|
}
|
|
|
|
static void xe_vma_ops_incr_pt_update_ops(struct xe_vma_ops *vops, u8 tile_mask, int inc_val)
|
|
{
|
|
int i;
|
|
|
|
if (!inc_val)
|
|
return;
|
|
|
|
for (i = 0; i < XE_MAX_TILES_PER_DEVICE; ++i)
|
|
if (BIT(i) & tile_mask)
|
|
vops->pt_update_ops[i].num_ops += inc_val;
|
|
}
|
|
|
|
#define XE_VMA_CREATE_MASK ( \
|
|
XE_VMA_READ_ONLY | \
|
|
XE_VMA_DUMPABLE | \
|
|
XE_VMA_SYSTEM_ALLOCATOR | \
|
|
DRM_GPUVA_SPARSE | \
|
|
XE_VMA_MADV_AUTORESET)
|
|
|
|
static void xe_vm_populate_rebind(struct xe_vma_op *op, struct xe_vma *vma,
|
|
u8 tile_mask)
|
|
{
|
|
INIT_LIST_HEAD(&op->link);
|
|
op->tile_mask = tile_mask;
|
|
op->base.op = DRM_GPUVA_OP_MAP;
|
|
op->base.map.va.addr = vma->gpuva.va.addr;
|
|
op->base.map.va.range = vma->gpuva.va.range;
|
|
op->base.map.gem.obj = vma->gpuva.gem.obj;
|
|
op->base.map.gem.offset = vma->gpuva.gem.offset;
|
|
op->map.vma = vma;
|
|
op->map.immediate = true;
|
|
op->map.vma_flags = vma->gpuva.flags & XE_VMA_CREATE_MASK;
|
|
}
|
|
|
|
static int xe_vm_ops_add_rebind(struct xe_vma_ops *vops, struct xe_vma *vma,
|
|
u8 tile_mask)
|
|
{
|
|
struct xe_vma_op *op;
|
|
|
|
op = kzalloc(sizeof(*op), GFP_KERNEL);
|
|
if (!op)
|
|
return -ENOMEM;
|
|
|
|
xe_vm_populate_rebind(op, vma, tile_mask);
|
|
list_add_tail(&op->link, &vops->list);
|
|
xe_vma_ops_incr_pt_update_ops(vops, tile_mask, 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct dma_fence *ops_execute(struct xe_vm *vm,
|
|
struct xe_vma_ops *vops);
|
|
static void xe_vma_ops_init(struct xe_vma_ops *vops, struct xe_vm *vm,
|
|
struct xe_exec_queue *q,
|
|
struct xe_sync_entry *syncs, u32 num_syncs);
|
|
|
|
int xe_vm_rebind(struct xe_vm *vm, bool rebind_worker)
|
|
{
|
|
struct dma_fence *fence;
|
|
struct xe_vma *vma, *next;
|
|
struct xe_vma_ops vops;
|
|
struct xe_vma_op *op, *next_op;
|
|
int err, i;
|
|
|
|
lockdep_assert_held(&vm->lock);
|
|
if ((xe_vm_in_lr_mode(vm) && !rebind_worker) ||
|
|
list_empty(&vm->rebind_list))
|
|
return 0;
|
|
|
|
xe_vma_ops_init(&vops, vm, NULL, NULL, 0);
|
|
for (i = 0; i < XE_MAX_TILES_PER_DEVICE; ++i)
|
|
vops.pt_update_ops[i].wait_vm_bookkeep = true;
|
|
|
|
xe_vm_assert_held(vm);
|
|
list_for_each_entry(vma, &vm->rebind_list, combined_links.rebind) {
|
|
xe_assert(vm->xe, vma->tile_present);
|
|
|
|
if (rebind_worker)
|
|
trace_xe_vma_rebind_worker(vma);
|
|
else
|
|
trace_xe_vma_rebind_exec(vma);
|
|
|
|
err = xe_vm_ops_add_rebind(&vops, vma,
|
|
vma->tile_present);
|
|
if (err)
|
|
goto free_ops;
|
|
}
|
|
|
|
err = xe_vma_ops_alloc(&vops, false);
|
|
if (err)
|
|
goto free_ops;
|
|
|
|
fence = ops_execute(vm, &vops);
|
|
if (IS_ERR(fence)) {
|
|
err = PTR_ERR(fence);
|
|
} else {
|
|
dma_fence_put(fence);
|
|
list_for_each_entry_safe(vma, next, &vm->rebind_list,
|
|
combined_links.rebind)
|
|
list_del_init(&vma->combined_links.rebind);
|
|
}
|
|
free_ops:
|
|
list_for_each_entry_safe(op, next_op, &vops.list, link) {
|
|
list_del(&op->link);
|
|
kfree(op);
|
|
}
|
|
xe_vma_ops_fini(&vops);
|
|
|
|
return err;
|
|
}
|
|
|
|
struct dma_fence *xe_vma_rebind(struct xe_vm *vm, struct xe_vma *vma, u8 tile_mask)
|
|
{
|
|
struct dma_fence *fence = NULL;
|
|
struct xe_vma_ops vops;
|
|
struct xe_vma_op *op, *next_op;
|
|
struct xe_tile *tile;
|
|
u8 id;
|
|
int err;
|
|
|
|
lockdep_assert_held(&vm->lock);
|
|
xe_vm_assert_held(vm);
|
|
xe_assert(vm->xe, xe_vm_in_fault_mode(vm));
|
|
|
|
xe_vma_ops_init(&vops, vm, NULL, NULL, 0);
|
|
vops.flags |= XE_VMA_OPS_FLAG_SKIP_TLB_WAIT;
|
|
for_each_tile(tile, vm->xe, id) {
|
|
vops.pt_update_ops[id].wait_vm_bookkeep = true;
|
|
vops.pt_update_ops[tile->id].q =
|
|
xe_migrate_exec_queue(tile->migrate);
|
|
}
|
|
|
|
err = xe_vm_ops_add_rebind(&vops, vma, tile_mask);
|
|
if (err)
|
|
return ERR_PTR(err);
|
|
|
|
err = xe_vma_ops_alloc(&vops, false);
|
|
if (err) {
|
|
fence = ERR_PTR(err);
|
|
goto free_ops;
|
|
}
|
|
|
|
fence = ops_execute(vm, &vops);
|
|
|
|
free_ops:
|
|
list_for_each_entry_safe(op, next_op, &vops.list, link) {
|
|
list_del(&op->link);
|
|
kfree(op);
|
|
}
|
|
xe_vma_ops_fini(&vops);
|
|
|
|
return fence;
|
|
}
|
|
|
|
static void xe_vm_populate_range_rebind(struct xe_vma_op *op,
|
|
struct xe_vma *vma,
|
|
struct xe_svm_range *range,
|
|
u8 tile_mask)
|
|
{
|
|
INIT_LIST_HEAD(&op->link);
|
|
op->tile_mask = tile_mask;
|
|
op->base.op = DRM_GPUVA_OP_DRIVER;
|
|
op->subop = XE_VMA_SUBOP_MAP_RANGE;
|
|
op->map_range.vma = vma;
|
|
op->map_range.range = range;
|
|
}
|
|
|
|
static int
|
|
xe_vm_ops_add_range_rebind(struct xe_vma_ops *vops,
|
|
struct xe_vma *vma,
|
|
struct xe_svm_range *range,
|
|
u8 tile_mask)
|
|
{
|
|
struct xe_vma_op *op;
|
|
|
|
op = kzalloc(sizeof(*op), GFP_KERNEL);
|
|
if (!op)
|
|
return -ENOMEM;
|
|
|
|
xe_vm_populate_range_rebind(op, vma, range, tile_mask);
|
|
list_add_tail(&op->link, &vops->list);
|
|
xe_vma_ops_incr_pt_update_ops(vops, tile_mask, 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* xe_vm_range_rebind() - VM range (re)bind
|
|
* @vm: The VM which the range belongs to.
|
|
* @vma: The VMA which the range belongs to.
|
|
* @range: SVM range to rebind.
|
|
* @tile_mask: Tile mask to bind the range to.
|
|
*
|
|
* (re)bind SVM range setting up GPU page tables for the range.
|
|
*
|
|
* Return: dma fence for rebind to signal completion on success, ERR_PTR on
|
|
* failure
|
|
*/
|
|
struct dma_fence *xe_vm_range_rebind(struct xe_vm *vm,
|
|
struct xe_vma *vma,
|
|
struct xe_svm_range *range,
|
|
u8 tile_mask)
|
|
{
|
|
struct dma_fence *fence = NULL;
|
|
struct xe_vma_ops vops;
|
|
struct xe_vma_op *op, *next_op;
|
|
struct xe_tile *tile;
|
|
u8 id;
|
|
int err;
|
|
|
|
lockdep_assert_held(&vm->lock);
|
|
xe_vm_assert_held(vm);
|
|
xe_assert(vm->xe, xe_vm_in_fault_mode(vm));
|
|
xe_assert(vm->xe, xe_vma_is_cpu_addr_mirror(vma));
|
|
|
|
xe_vma_ops_init(&vops, vm, NULL, NULL, 0);
|
|
vops.flags |= XE_VMA_OPS_FLAG_SKIP_TLB_WAIT;
|
|
for_each_tile(tile, vm->xe, id) {
|
|
vops.pt_update_ops[id].wait_vm_bookkeep = true;
|
|
vops.pt_update_ops[tile->id].q =
|
|
xe_migrate_exec_queue(tile->migrate);
|
|
}
|
|
|
|
err = xe_vm_ops_add_range_rebind(&vops, vma, range, tile_mask);
|
|
if (err)
|
|
return ERR_PTR(err);
|
|
|
|
err = xe_vma_ops_alloc(&vops, false);
|
|
if (err) {
|
|
fence = ERR_PTR(err);
|
|
goto free_ops;
|
|
}
|
|
|
|
fence = ops_execute(vm, &vops);
|
|
|
|
free_ops:
|
|
list_for_each_entry_safe(op, next_op, &vops.list, link) {
|
|
list_del(&op->link);
|
|
kfree(op);
|
|
}
|
|
xe_vma_ops_fini(&vops);
|
|
|
|
return fence;
|
|
}
|
|
|
|
static void xe_vm_populate_range_unbind(struct xe_vma_op *op,
|
|
struct xe_svm_range *range)
|
|
{
|
|
INIT_LIST_HEAD(&op->link);
|
|
op->tile_mask = range->tile_present;
|
|
op->base.op = DRM_GPUVA_OP_DRIVER;
|
|
op->subop = XE_VMA_SUBOP_UNMAP_RANGE;
|
|
op->unmap_range.range = range;
|
|
}
|
|
|
|
static int
|
|
xe_vm_ops_add_range_unbind(struct xe_vma_ops *vops,
|
|
struct xe_svm_range *range)
|
|
{
|
|
struct xe_vma_op *op;
|
|
|
|
op = kzalloc(sizeof(*op), GFP_KERNEL);
|
|
if (!op)
|
|
return -ENOMEM;
|
|
|
|
xe_vm_populate_range_unbind(op, range);
|
|
list_add_tail(&op->link, &vops->list);
|
|
xe_vma_ops_incr_pt_update_ops(vops, range->tile_present, 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* xe_vm_range_unbind() - VM range unbind
|
|
* @vm: The VM which the range belongs to.
|
|
* @range: SVM range to rebind.
|
|
*
|
|
* Unbind SVM range removing the GPU page tables for the range.
|
|
*
|
|
* Return: dma fence for unbind to signal completion on success, ERR_PTR on
|
|
* failure
|
|
*/
|
|
struct dma_fence *xe_vm_range_unbind(struct xe_vm *vm,
|
|
struct xe_svm_range *range)
|
|
{
|
|
struct dma_fence *fence = NULL;
|
|
struct xe_vma_ops vops;
|
|
struct xe_vma_op *op, *next_op;
|
|
struct xe_tile *tile;
|
|
u8 id;
|
|
int err;
|
|
|
|
lockdep_assert_held(&vm->lock);
|
|
xe_vm_assert_held(vm);
|
|
xe_assert(vm->xe, xe_vm_in_fault_mode(vm));
|
|
|
|
if (!range->tile_present)
|
|
return dma_fence_get_stub();
|
|
|
|
xe_vma_ops_init(&vops, vm, NULL, NULL, 0);
|
|
for_each_tile(tile, vm->xe, id) {
|
|
vops.pt_update_ops[id].wait_vm_bookkeep = true;
|
|
vops.pt_update_ops[tile->id].q =
|
|
xe_migrate_exec_queue(tile->migrate);
|
|
}
|
|
|
|
err = xe_vm_ops_add_range_unbind(&vops, range);
|
|
if (err)
|
|
return ERR_PTR(err);
|
|
|
|
err = xe_vma_ops_alloc(&vops, false);
|
|
if (err) {
|
|
fence = ERR_PTR(err);
|
|
goto free_ops;
|
|
}
|
|
|
|
fence = ops_execute(vm, &vops);
|
|
|
|
free_ops:
|
|
list_for_each_entry_safe(op, next_op, &vops.list, link) {
|
|
list_del(&op->link);
|
|
kfree(op);
|
|
}
|
|
xe_vma_ops_fini(&vops);
|
|
|
|
return fence;
|
|
}
|
|
|
|
static void xe_vma_free(struct xe_vma *vma)
|
|
{
|
|
if (xe_vma_is_userptr(vma))
|
|
kfree(to_userptr_vma(vma));
|
|
else
|
|
kfree(vma);
|
|
}
|
|
|
|
static struct xe_vma *xe_vma_create(struct xe_vm *vm,
|
|
struct xe_bo *bo,
|
|
u64 bo_offset_or_userptr,
|
|
u64 start, u64 end,
|
|
struct xe_vma_mem_attr *attr,
|
|
unsigned int flags)
|
|
{
|
|
struct xe_vma *vma;
|
|
struct xe_tile *tile;
|
|
u8 id;
|
|
bool is_null = (flags & DRM_GPUVA_SPARSE);
|
|
bool is_cpu_addr_mirror = (flags & XE_VMA_SYSTEM_ALLOCATOR);
|
|
|
|
xe_assert(vm->xe, start < end);
|
|
xe_assert(vm->xe, end < vm->size);
|
|
|
|
/*
|
|
* Allocate and ensure that the xe_vma_is_userptr() return
|
|
* matches what was allocated.
|
|
*/
|
|
if (!bo && !is_null && !is_cpu_addr_mirror) {
|
|
struct xe_userptr_vma *uvma = kzalloc(sizeof(*uvma), GFP_KERNEL);
|
|
|
|
if (!uvma)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
vma = &uvma->vma;
|
|
} else {
|
|
vma = kzalloc(sizeof(*vma), GFP_KERNEL);
|
|
if (!vma)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
if (bo)
|
|
vma->gpuva.gem.obj = &bo->ttm.base;
|
|
}
|
|
|
|
INIT_LIST_HEAD(&vma->combined_links.rebind);
|
|
|
|
INIT_LIST_HEAD(&vma->gpuva.gem.entry);
|
|
vma->gpuva.vm = &vm->gpuvm;
|
|
vma->gpuva.va.addr = start;
|
|
vma->gpuva.va.range = end - start + 1;
|
|
vma->gpuva.flags = flags;
|
|
|
|
for_each_tile(tile, vm->xe, id)
|
|
vma->tile_mask |= 0x1 << id;
|
|
|
|
if (vm->xe->info.has_atomic_enable_pte_bit)
|
|
vma->gpuva.flags |= XE_VMA_ATOMIC_PTE_BIT;
|
|
|
|
vma->attr = *attr;
|
|
|
|
if (bo) {
|
|
struct drm_gpuvm_bo *vm_bo;
|
|
|
|
xe_bo_assert_held(bo);
|
|
|
|
vm_bo = drm_gpuvm_bo_obtain(vma->gpuva.vm, &bo->ttm.base);
|
|
if (IS_ERR(vm_bo)) {
|
|
xe_vma_free(vma);
|
|
return ERR_CAST(vm_bo);
|
|
}
|
|
|
|
drm_gpuvm_bo_extobj_add(vm_bo);
|
|
drm_gem_object_get(&bo->ttm.base);
|
|
vma->gpuva.gem.offset = bo_offset_or_userptr;
|
|
drm_gpuva_link(&vma->gpuva, vm_bo);
|
|
drm_gpuvm_bo_put(vm_bo);
|
|
} else /* userptr or null */ {
|
|
if (!is_null && !is_cpu_addr_mirror) {
|
|
struct xe_userptr_vma *uvma = to_userptr_vma(vma);
|
|
u64 size = end - start + 1;
|
|
int err;
|
|
|
|
vma->gpuva.gem.offset = bo_offset_or_userptr;
|
|
|
|
err = xe_userptr_setup(uvma, xe_vma_userptr(vma), size);
|
|
if (err) {
|
|
xe_vma_free(vma);
|
|
return ERR_PTR(err);
|
|
}
|
|
}
|
|
|
|
xe_vm_get(vm);
|
|
}
|
|
|
|
return vma;
|
|
}
|
|
|
|
static void xe_vma_destroy_late(struct xe_vma *vma)
|
|
{
|
|
struct xe_vm *vm = xe_vma_vm(vma);
|
|
|
|
if (vma->ufence) {
|
|
xe_sync_ufence_put(vma->ufence);
|
|
vma->ufence = NULL;
|
|
}
|
|
|
|
if (xe_vma_is_userptr(vma)) {
|
|
struct xe_userptr_vma *uvma = to_userptr_vma(vma);
|
|
|
|
xe_userptr_remove(uvma);
|
|
xe_vm_put(vm);
|
|
} else if (xe_vma_is_null(vma) || xe_vma_is_cpu_addr_mirror(vma)) {
|
|
xe_vm_put(vm);
|
|
} else {
|
|
xe_bo_put(xe_vma_bo(vma));
|
|
}
|
|
|
|
xe_vma_free(vma);
|
|
}
|
|
|
|
static void vma_destroy_work_func(struct work_struct *w)
|
|
{
|
|
struct xe_vma *vma =
|
|
container_of(w, struct xe_vma, destroy_work);
|
|
|
|
xe_vma_destroy_late(vma);
|
|
}
|
|
|
|
static void vma_destroy_cb(struct dma_fence *fence,
|
|
struct dma_fence_cb *cb)
|
|
{
|
|
struct xe_vma *vma = container_of(cb, struct xe_vma, destroy_cb);
|
|
|
|
INIT_WORK(&vma->destroy_work, vma_destroy_work_func);
|
|
queue_work(system_unbound_wq, &vma->destroy_work);
|
|
}
|
|
|
|
static void xe_vma_destroy(struct xe_vma *vma, struct dma_fence *fence)
|
|
{
|
|
struct xe_vm *vm = xe_vma_vm(vma);
|
|
|
|
lockdep_assert_held_write(&vm->lock);
|
|
xe_assert(vm->xe, list_empty(&vma->combined_links.destroy));
|
|
|
|
if (xe_vma_is_userptr(vma)) {
|
|
xe_assert(vm->xe, vma->gpuva.flags & XE_VMA_DESTROYED);
|
|
xe_userptr_destroy(to_userptr_vma(vma));
|
|
} else if (!xe_vma_is_null(vma) && !xe_vma_is_cpu_addr_mirror(vma)) {
|
|
xe_bo_assert_held(xe_vma_bo(vma));
|
|
|
|
drm_gpuva_unlink(&vma->gpuva);
|
|
}
|
|
|
|
xe_vm_assert_held(vm);
|
|
if (fence) {
|
|
int ret = dma_fence_add_callback(fence, &vma->destroy_cb,
|
|
vma_destroy_cb);
|
|
|
|
if (ret) {
|
|
XE_WARN_ON(ret != -ENOENT);
|
|
xe_vma_destroy_late(vma);
|
|
}
|
|
} else {
|
|
xe_vma_destroy_late(vma);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* xe_vm_lock_vma() - drm_exec utility to lock a vma
|
|
* @exec: The drm_exec object we're currently locking for.
|
|
* @vma: The vma for witch we want to lock the vm resv and any attached
|
|
* object's resv.
|
|
*
|
|
* Return: 0 on success, negative error code on error. In particular
|
|
* may return -EDEADLK on WW transaction contention and -EINTR if
|
|
* an interruptible wait is terminated by a signal.
|
|
*/
|
|
int xe_vm_lock_vma(struct drm_exec *exec, struct xe_vma *vma)
|
|
{
|
|
struct xe_vm *vm = xe_vma_vm(vma);
|
|
struct xe_bo *bo = xe_vma_bo(vma);
|
|
int err;
|
|
|
|
XE_WARN_ON(!vm);
|
|
|
|
err = drm_exec_lock_obj(exec, xe_vm_obj(vm));
|
|
if (!err && bo && !bo->vm)
|
|
err = drm_exec_lock_obj(exec, &bo->ttm.base);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void xe_vma_destroy_unlocked(struct xe_vma *vma)
|
|
{
|
|
struct xe_device *xe = xe_vma_vm(vma)->xe;
|
|
struct xe_validation_ctx ctx;
|
|
struct drm_exec exec;
|
|
int err = 0;
|
|
|
|
xe_validation_guard(&ctx, &xe->val, &exec, (struct xe_val_flags) {}, err) {
|
|
err = xe_vm_lock_vma(&exec, vma);
|
|
drm_exec_retry_on_contention(&exec);
|
|
if (XE_WARN_ON(err))
|
|
break;
|
|
xe_vma_destroy(vma, NULL);
|
|
}
|
|
xe_assert(xe, !err);
|
|
}
|
|
|
|
struct xe_vma *
|
|
xe_vm_find_overlapping_vma(struct xe_vm *vm, u64 start, u64 range)
|
|
{
|
|
struct drm_gpuva *gpuva;
|
|
|
|
lockdep_assert_held(&vm->lock);
|
|
|
|
if (xe_vm_is_closed_or_banned(vm))
|
|
return NULL;
|
|
|
|
xe_assert(vm->xe, start + range <= vm->size);
|
|
|
|
gpuva = drm_gpuva_find_first(&vm->gpuvm, start, range);
|
|
|
|
return gpuva ? gpuva_to_vma(gpuva) : NULL;
|
|
}
|
|
|
|
static int xe_vm_insert_vma(struct xe_vm *vm, struct xe_vma *vma)
|
|
{
|
|
int err;
|
|
|
|
xe_assert(vm->xe, xe_vma_vm(vma) == vm);
|
|
lockdep_assert_held(&vm->lock);
|
|
|
|
mutex_lock(&vm->snap_mutex);
|
|
err = drm_gpuva_insert(&vm->gpuvm, &vma->gpuva);
|
|
mutex_unlock(&vm->snap_mutex);
|
|
XE_WARN_ON(err); /* Shouldn't be possible */
|
|
|
|
return err;
|
|
}
|
|
|
|
static void xe_vm_remove_vma(struct xe_vm *vm, struct xe_vma *vma)
|
|
{
|
|
xe_assert(vm->xe, xe_vma_vm(vma) == vm);
|
|
lockdep_assert_held(&vm->lock);
|
|
|
|
mutex_lock(&vm->snap_mutex);
|
|
drm_gpuva_remove(&vma->gpuva);
|
|
mutex_unlock(&vm->snap_mutex);
|
|
if (vm->usm.last_fault_vma == vma)
|
|
vm->usm.last_fault_vma = NULL;
|
|
}
|
|
|
|
static struct drm_gpuva_op *xe_vm_op_alloc(void)
|
|
{
|
|
struct xe_vma_op *op;
|
|
|
|
op = kzalloc(sizeof(*op), GFP_KERNEL);
|
|
|
|
if (unlikely(!op))
|
|
return NULL;
|
|
|
|
return &op->base;
|
|
}
|
|
|
|
static void xe_vm_free(struct drm_gpuvm *gpuvm);
|
|
|
|
static const struct drm_gpuvm_ops gpuvm_ops = {
|
|
.op_alloc = xe_vm_op_alloc,
|
|
.vm_bo_validate = xe_gpuvm_validate,
|
|
.vm_free = xe_vm_free,
|
|
};
|
|
|
|
static u64 pde_encode_pat_index(u16 pat_index)
|
|
{
|
|
u64 pte = 0;
|
|
|
|
if (pat_index & BIT(0))
|
|
pte |= XE_PPGTT_PTE_PAT0;
|
|
|
|
if (pat_index & BIT(1))
|
|
pte |= XE_PPGTT_PTE_PAT1;
|
|
|
|
return pte;
|
|
}
|
|
|
|
static u64 pte_encode_pat_index(u16 pat_index, u32 pt_level)
|
|
{
|
|
u64 pte = 0;
|
|
|
|
if (pat_index & BIT(0))
|
|
pte |= XE_PPGTT_PTE_PAT0;
|
|
|
|
if (pat_index & BIT(1))
|
|
pte |= XE_PPGTT_PTE_PAT1;
|
|
|
|
if (pat_index & BIT(2)) {
|
|
if (pt_level)
|
|
pte |= XE_PPGTT_PDE_PDPE_PAT2;
|
|
else
|
|
pte |= XE_PPGTT_PTE_PAT2;
|
|
}
|
|
|
|
if (pat_index & BIT(3))
|
|
pte |= XELPG_PPGTT_PTE_PAT3;
|
|
|
|
if (pat_index & (BIT(4)))
|
|
pte |= XE2_PPGTT_PTE_PAT4;
|
|
|
|
return pte;
|
|
}
|
|
|
|
static u64 pte_encode_ps(u32 pt_level)
|
|
{
|
|
XE_WARN_ON(pt_level > MAX_HUGEPTE_LEVEL);
|
|
|
|
if (pt_level == 1)
|
|
return XE_PDE_PS_2M;
|
|
else if (pt_level == 2)
|
|
return XE_PDPE_PS_1G;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static u16 pde_pat_index(struct xe_bo *bo)
|
|
{
|
|
struct xe_device *xe = xe_bo_device(bo);
|
|
u16 pat_index;
|
|
|
|
/*
|
|
* We only have two bits to encode the PAT index in non-leaf nodes, but
|
|
* these only point to other paging structures so we only need a minimal
|
|
* selection of options. The user PAT index is only for encoding leaf
|
|
* nodes, where we have use of more bits to do the encoding. The
|
|
* non-leaf nodes are instead under driver control so the chosen index
|
|
* here should be distinct from the user PAT index. Also the
|
|
* corresponding coherency of the PAT index should be tied to the
|
|
* allocation type of the page table (or at least we should pick
|
|
* something which is always safe).
|
|
*/
|
|
if (!xe_bo_is_vram(bo) && bo->ttm.ttm->caching == ttm_cached)
|
|
pat_index = xe->pat.idx[XE_CACHE_WB];
|
|
else
|
|
pat_index = xe->pat.idx[XE_CACHE_NONE];
|
|
|
|
xe_assert(xe, pat_index <= 3);
|
|
|
|
return pat_index;
|
|
}
|
|
|
|
static u64 xelp_pde_encode_bo(struct xe_bo *bo, u64 bo_offset)
|
|
{
|
|
u64 pde;
|
|
|
|
pde = xe_bo_addr(bo, bo_offset, XE_PAGE_SIZE);
|
|
pde |= XE_PAGE_PRESENT | XE_PAGE_RW;
|
|
pde |= pde_encode_pat_index(pde_pat_index(bo));
|
|
|
|
return pde;
|
|
}
|
|
|
|
static u64 xelp_pte_encode_bo(struct xe_bo *bo, u64 bo_offset,
|
|
u16 pat_index, u32 pt_level)
|
|
{
|
|
u64 pte;
|
|
|
|
pte = xe_bo_addr(bo, bo_offset, XE_PAGE_SIZE);
|
|
pte |= XE_PAGE_PRESENT | XE_PAGE_RW;
|
|
pte |= pte_encode_pat_index(pat_index, pt_level);
|
|
pte |= pte_encode_ps(pt_level);
|
|
|
|
if (xe_bo_is_vram(bo) || xe_bo_is_stolen_devmem(bo))
|
|
pte |= XE_PPGTT_PTE_DM;
|
|
|
|
return pte;
|
|
}
|
|
|
|
static u64 xelp_pte_encode_vma(u64 pte, struct xe_vma *vma,
|
|
u16 pat_index, u32 pt_level)
|
|
{
|
|
pte |= XE_PAGE_PRESENT;
|
|
|
|
if (likely(!xe_vma_read_only(vma)))
|
|
pte |= XE_PAGE_RW;
|
|
|
|
pte |= pte_encode_pat_index(pat_index, pt_level);
|
|
pte |= pte_encode_ps(pt_level);
|
|
|
|
if (unlikely(xe_vma_is_null(vma)))
|
|
pte |= XE_PTE_NULL;
|
|
|
|
return pte;
|
|
}
|
|
|
|
static u64 xelp_pte_encode_addr(struct xe_device *xe, u64 addr,
|
|
u16 pat_index,
|
|
u32 pt_level, bool devmem, u64 flags)
|
|
{
|
|
u64 pte;
|
|
|
|
/* Avoid passing random bits directly as flags */
|
|
xe_assert(xe, !(flags & ~XE_PTE_PS64));
|
|
|
|
pte = addr;
|
|
pte |= XE_PAGE_PRESENT | XE_PAGE_RW;
|
|
pte |= pte_encode_pat_index(pat_index, pt_level);
|
|
pte |= pte_encode_ps(pt_level);
|
|
|
|
if (devmem)
|
|
pte |= XE_PPGTT_PTE_DM;
|
|
|
|
pte |= flags;
|
|
|
|
return pte;
|
|
}
|
|
|
|
static const struct xe_pt_ops xelp_pt_ops = {
|
|
.pte_encode_bo = xelp_pte_encode_bo,
|
|
.pte_encode_vma = xelp_pte_encode_vma,
|
|
.pte_encode_addr = xelp_pte_encode_addr,
|
|
.pde_encode_bo = xelp_pde_encode_bo,
|
|
};
|
|
|
|
static void vm_destroy_work_func(struct work_struct *w);
|
|
|
|
/**
|
|
* xe_vm_create_scratch() - Setup a scratch memory pagetable tree for the
|
|
* given tile and vm.
|
|
* @xe: xe device.
|
|
* @tile: tile to set up for.
|
|
* @vm: vm to set up for.
|
|
* @exec: The struct drm_exec object used to lock the vm resv.
|
|
*
|
|
* Sets up a pagetable tree with one page-table per level and a single
|
|
* leaf PTE. All pagetable entries point to the single page-table or,
|
|
* for MAX_HUGEPTE_LEVEL, a NULL huge PTE returning 0 on read and
|
|
* writes become NOPs.
|
|
*
|
|
* Return: 0 on success, negative error code on error.
|
|
*/
|
|
static int xe_vm_create_scratch(struct xe_device *xe, struct xe_tile *tile,
|
|
struct xe_vm *vm, struct drm_exec *exec)
|
|
{
|
|
u8 id = tile->id;
|
|
int i;
|
|
|
|
for (i = MAX_HUGEPTE_LEVEL; i < vm->pt_root[id]->level; i++) {
|
|
vm->scratch_pt[id][i] = xe_pt_create(vm, tile, i, exec);
|
|
if (IS_ERR(vm->scratch_pt[id][i])) {
|
|
int err = PTR_ERR(vm->scratch_pt[id][i]);
|
|
|
|
vm->scratch_pt[id][i] = NULL;
|
|
return err;
|
|
}
|
|
xe_pt_populate_empty(tile, vm, vm->scratch_pt[id][i]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
ALLOW_ERROR_INJECTION(xe_vm_create_scratch, ERRNO);
|
|
|
|
static void xe_vm_free_scratch(struct xe_vm *vm)
|
|
{
|
|
struct xe_tile *tile;
|
|
u8 id;
|
|
|
|
if (!xe_vm_has_scratch(vm))
|
|
return;
|
|
|
|
for_each_tile(tile, vm->xe, id) {
|
|
u32 i;
|
|
|
|
if (!vm->pt_root[id])
|
|
continue;
|
|
|
|
for (i = MAX_HUGEPTE_LEVEL; i < vm->pt_root[id]->level; ++i)
|
|
if (vm->scratch_pt[id][i])
|
|
xe_pt_destroy(vm->scratch_pt[id][i], vm->flags, NULL);
|
|
}
|
|
}
|
|
|
|
static void xe_vm_pt_destroy(struct xe_vm *vm)
|
|
{
|
|
struct xe_tile *tile;
|
|
u8 id;
|
|
|
|
xe_vm_assert_held(vm);
|
|
|
|
for_each_tile(tile, vm->xe, id) {
|
|
if (vm->pt_root[id]) {
|
|
xe_pt_destroy(vm->pt_root[id], vm->flags, NULL);
|
|
vm->pt_root[id] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
struct xe_vm *xe_vm_create(struct xe_device *xe, u32 flags, struct xe_file *xef)
|
|
{
|
|
struct drm_gem_object *vm_resv_obj;
|
|
struct xe_validation_ctx ctx;
|
|
struct drm_exec exec;
|
|
struct xe_vm *vm;
|
|
int err;
|
|
struct xe_tile *tile;
|
|
u8 id;
|
|
|
|
/*
|
|
* Since the GSCCS is not user-accessible, we don't expect a GSC VM to
|
|
* ever be in faulting mode.
|
|
*/
|
|
xe_assert(xe, !((flags & XE_VM_FLAG_GSC) && (flags & XE_VM_FLAG_FAULT_MODE)));
|
|
|
|
vm = kzalloc(sizeof(*vm), GFP_KERNEL);
|
|
if (!vm)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
vm->xe = xe;
|
|
|
|
vm->size = 1ull << xe->info.va_bits;
|
|
vm->flags = flags;
|
|
|
|
if (xef)
|
|
vm->xef = xe_file_get(xef);
|
|
/**
|
|
* GSC VMs are kernel-owned, only used for PXP ops and can sometimes be
|
|
* manipulated under the PXP mutex. However, the PXP mutex can be taken
|
|
* under a user-VM lock when the PXP session is started at exec_queue
|
|
* creation time. Those are different VMs and therefore there is no risk
|
|
* of deadlock, but we need to tell lockdep that this is the case or it
|
|
* will print a warning.
|
|
*/
|
|
if (flags & XE_VM_FLAG_GSC) {
|
|
static struct lock_class_key gsc_vm_key;
|
|
|
|
__init_rwsem(&vm->lock, "gsc_vm", &gsc_vm_key);
|
|
} else {
|
|
init_rwsem(&vm->lock);
|
|
}
|
|
mutex_init(&vm->snap_mutex);
|
|
|
|
INIT_LIST_HEAD(&vm->rebind_list);
|
|
|
|
INIT_LIST_HEAD(&vm->userptr.repin_list);
|
|
INIT_LIST_HEAD(&vm->userptr.invalidated);
|
|
spin_lock_init(&vm->userptr.invalidated_lock);
|
|
|
|
ttm_lru_bulk_move_init(&vm->lru_bulk_move);
|
|
|
|
INIT_WORK(&vm->destroy_work, vm_destroy_work_func);
|
|
|
|
INIT_LIST_HEAD(&vm->preempt.exec_queues);
|
|
vm->preempt.min_run_period_ms = 10; /* FIXME: Wire up to uAPI */
|
|
|
|
for_each_tile(tile, xe, id)
|
|
xe_range_fence_tree_init(&vm->rftree[id]);
|
|
|
|
vm->pt_ops = &xelp_pt_ops;
|
|
|
|
/*
|
|
* Long-running workloads are not protected by the scheduler references.
|
|
* By design, run_job for long-running workloads returns NULL and the
|
|
* scheduler drops all the references of it, hence protecting the VM
|
|
* for this case is necessary.
|
|
*/
|
|
if (flags & XE_VM_FLAG_LR_MODE) {
|
|
INIT_WORK(&vm->preempt.rebind_work, preempt_rebind_work_func);
|
|
xe_pm_runtime_get_noresume(xe);
|
|
INIT_LIST_HEAD(&vm->preempt.pm_activate_link);
|
|
}
|
|
|
|
err = xe_svm_init(vm);
|
|
if (err)
|
|
goto err_no_resv;
|
|
|
|
vm_resv_obj = drm_gpuvm_resv_object_alloc(&xe->drm);
|
|
if (!vm_resv_obj) {
|
|
err = -ENOMEM;
|
|
goto err_svm_fini;
|
|
}
|
|
|
|
drm_gpuvm_init(&vm->gpuvm, "Xe VM", DRM_GPUVM_RESV_PROTECTED, &xe->drm,
|
|
vm_resv_obj, 0, vm->size, 0, 0, &gpuvm_ops);
|
|
|
|
drm_gem_object_put(vm_resv_obj);
|
|
|
|
err = 0;
|
|
xe_validation_guard(&ctx, &xe->val, &exec, (struct xe_val_flags) {.interruptible = true},
|
|
err) {
|
|
err = xe_vm_drm_exec_lock(vm, &exec);
|
|
drm_exec_retry_on_contention(&exec);
|
|
|
|
if (IS_DGFX(xe) && xe->info.vram_flags & XE_VRAM_FLAGS_NEED64K)
|
|
vm->flags |= XE_VM_FLAG_64K;
|
|
|
|
for_each_tile(tile, xe, id) {
|
|
if (flags & XE_VM_FLAG_MIGRATION &&
|
|
tile->id != XE_VM_FLAG_TILE_ID(flags))
|
|
continue;
|
|
|
|
vm->pt_root[id] = xe_pt_create(vm, tile, xe->info.vm_max_level,
|
|
&exec);
|
|
if (IS_ERR(vm->pt_root[id])) {
|
|
err = PTR_ERR(vm->pt_root[id]);
|
|
vm->pt_root[id] = NULL;
|
|
xe_vm_pt_destroy(vm);
|
|
drm_exec_retry_on_contention(&exec);
|
|
xe_validation_retry_on_oom(&ctx, &err);
|
|
break;
|
|
}
|
|
}
|
|
if (err)
|
|
break;
|
|
|
|
if (xe_vm_has_scratch(vm)) {
|
|
for_each_tile(tile, xe, id) {
|
|
if (!vm->pt_root[id])
|
|
continue;
|
|
|
|
err = xe_vm_create_scratch(xe, tile, vm, &exec);
|
|
if (err) {
|
|
xe_vm_free_scratch(vm);
|
|
xe_vm_pt_destroy(vm);
|
|
drm_exec_retry_on_contention(&exec);
|
|
xe_validation_retry_on_oom(&ctx, &err);
|
|
break;
|
|
}
|
|
}
|
|
if (err)
|
|
break;
|
|
vm->batch_invalidate_tlb = true;
|
|
}
|
|
|
|
if (vm->flags & XE_VM_FLAG_LR_MODE) {
|
|
INIT_WORK(&vm->preempt.rebind_work, preempt_rebind_work_func);
|
|
vm->batch_invalidate_tlb = false;
|
|
}
|
|
|
|
/* Fill pt_root after allocating scratch tables */
|
|
for_each_tile(tile, xe, id) {
|
|
if (!vm->pt_root[id])
|
|
continue;
|
|
|
|
xe_pt_populate_empty(tile, vm, vm->pt_root[id]);
|
|
}
|
|
}
|
|
if (err)
|
|
goto err_close;
|
|
|
|
/* Kernel migration VM shouldn't have a circular loop.. */
|
|
if (!(flags & XE_VM_FLAG_MIGRATION)) {
|
|
for_each_tile(tile, xe, id) {
|
|
struct xe_exec_queue *q;
|
|
u32 create_flags = EXEC_QUEUE_FLAG_VM;
|
|
|
|
if (!vm->pt_root[id])
|
|
continue;
|
|
|
|
q = xe_exec_queue_create_bind(xe, tile, create_flags, 0);
|
|
if (IS_ERR(q)) {
|
|
err = PTR_ERR(q);
|
|
goto err_close;
|
|
}
|
|
vm->q[id] = q;
|
|
}
|
|
}
|
|
|
|
if (xef && xe->info.has_asid) {
|
|
u32 asid;
|
|
|
|
down_write(&xe->usm.lock);
|
|
err = xa_alloc_cyclic(&xe->usm.asid_to_vm, &asid, vm,
|
|
XA_LIMIT(1, XE_MAX_ASID - 1),
|
|
&xe->usm.next_asid, GFP_KERNEL);
|
|
up_write(&xe->usm.lock);
|
|
if (err < 0)
|
|
goto err_close;
|
|
|
|
vm->usm.asid = asid;
|
|
}
|
|
|
|
trace_xe_vm_create(vm);
|
|
|
|
return vm;
|
|
|
|
err_close:
|
|
xe_vm_close_and_put(vm);
|
|
return ERR_PTR(err);
|
|
|
|
err_svm_fini:
|
|
if (flags & XE_VM_FLAG_FAULT_MODE) {
|
|
vm->size = 0; /* close the vm */
|
|
xe_svm_fini(vm);
|
|
}
|
|
err_no_resv:
|
|
mutex_destroy(&vm->snap_mutex);
|
|
for_each_tile(tile, xe, id)
|
|
xe_range_fence_tree_fini(&vm->rftree[id]);
|
|
ttm_lru_bulk_move_fini(&xe->ttm, &vm->lru_bulk_move);
|
|
if (vm->xef)
|
|
xe_file_put(vm->xef);
|
|
kfree(vm);
|
|
if (flags & XE_VM_FLAG_LR_MODE)
|
|
xe_pm_runtime_put(xe);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
static void xe_vm_close(struct xe_vm *vm)
|
|
{
|
|
struct xe_device *xe = vm->xe;
|
|
bool bound;
|
|
int idx;
|
|
|
|
bound = drm_dev_enter(&xe->drm, &idx);
|
|
|
|
down_write(&vm->lock);
|
|
if (xe_vm_in_fault_mode(vm))
|
|
xe_svm_notifier_lock(vm);
|
|
|
|
vm->size = 0;
|
|
|
|
if (!((vm->flags & XE_VM_FLAG_MIGRATION))) {
|
|
struct xe_tile *tile;
|
|
struct xe_gt *gt;
|
|
u8 id;
|
|
|
|
/* Wait for pending binds */
|
|
dma_resv_wait_timeout(xe_vm_resv(vm),
|
|
DMA_RESV_USAGE_BOOKKEEP,
|
|
false, MAX_SCHEDULE_TIMEOUT);
|
|
|
|
if (bound) {
|
|
for_each_tile(tile, xe, id)
|
|
if (vm->pt_root[id])
|
|
xe_pt_clear(xe, vm->pt_root[id]);
|
|
|
|
for_each_gt(gt, xe, id)
|
|
xe_tlb_inval_vm(>->tlb_inval, vm);
|
|
}
|
|
}
|
|
|
|
if (xe_vm_in_fault_mode(vm))
|
|
xe_svm_notifier_unlock(vm);
|
|
up_write(&vm->lock);
|
|
|
|
if (bound)
|
|
drm_dev_exit(idx);
|
|
}
|
|
|
|
void xe_vm_close_and_put(struct xe_vm *vm)
|
|
{
|
|
LIST_HEAD(contested);
|
|
struct xe_device *xe = vm->xe;
|
|
struct xe_tile *tile;
|
|
struct xe_vma *vma, *next_vma;
|
|
struct drm_gpuva *gpuva, *next;
|
|
u8 id;
|
|
|
|
xe_assert(xe, !vm->preempt.num_exec_queues);
|
|
|
|
xe_vm_close(vm);
|
|
if (xe_vm_in_preempt_fence_mode(vm)) {
|
|
mutex_lock(&xe->rebind_resume_lock);
|
|
list_del_init(&vm->preempt.pm_activate_link);
|
|
mutex_unlock(&xe->rebind_resume_lock);
|
|
flush_work(&vm->preempt.rebind_work);
|
|
}
|
|
if (xe_vm_in_fault_mode(vm))
|
|
xe_svm_close(vm);
|
|
|
|
down_write(&vm->lock);
|
|
for_each_tile(tile, xe, id) {
|
|
if (vm->q[id]) {
|
|
int i;
|
|
|
|
xe_exec_queue_last_fence_put(vm->q[id], vm);
|
|
for_each_tlb_inval(i)
|
|
xe_exec_queue_tlb_inval_last_fence_put(vm->q[id], vm, i);
|
|
}
|
|
}
|
|
up_write(&vm->lock);
|
|
|
|
for_each_tile(tile, xe, id) {
|
|
if (vm->q[id]) {
|
|
xe_exec_queue_kill(vm->q[id]);
|
|
xe_exec_queue_put(vm->q[id]);
|
|
vm->q[id] = NULL;
|
|
}
|
|
}
|
|
|
|
down_write(&vm->lock);
|
|
xe_vm_lock(vm, false);
|
|
drm_gpuvm_for_each_va_safe(gpuva, next, &vm->gpuvm) {
|
|
vma = gpuva_to_vma(gpuva);
|
|
|
|
if (xe_vma_has_no_bo(vma)) {
|
|
xe_svm_notifier_lock(vm);
|
|
vma->gpuva.flags |= XE_VMA_DESTROYED;
|
|
xe_svm_notifier_unlock(vm);
|
|
}
|
|
|
|
xe_vm_remove_vma(vm, vma);
|
|
|
|
/* easy case, remove from VMA? */
|
|
if (xe_vma_has_no_bo(vma) || xe_vma_bo(vma)->vm) {
|
|
list_del_init(&vma->combined_links.rebind);
|
|
xe_vma_destroy(vma, NULL);
|
|
continue;
|
|
}
|
|
|
|
list_move_tail(&vma->combined_links.destroy, &contested);
|
|
vma->gpuva.flags |= XE_VMA_DESTROYED;
|
|
}
|
|
|
|
/*
|
|
* All vm operations will add shared fences to resv.
|
|
* The only exception is eviction for a shared object,
|
|
* but even so, the unbind when evicted would still
|
|
* install a fence to resv. Hence it's safe to
|
|
* destroy the pagetables immediately.
|
|
*/
|
|
xe_vm_free_scratch(vm);
|
|
xe_vm_pt_destroy(vm);
|
|
xe_vm_unlock(vm);
|
|
|
|
/*
|
|
* VM is now dead, cannot re-add nodes to vm->vmas if it's NULL
|
|
* Since we hold a refcount to the bo, we can remove and free
|
|
* the members safely without locking.
|
|
*/
|
|
list_for_each_entry_safe(vma, next_vma, &contested,
|
|
combined_links.destroy) {
|
|
list_del_init(&vma->combined_links.destroy);
|
|
xe_vma_destroy_unlocked(vma);
|
|
}
|
|
|
|
xe_svm_fini(vm);
|
|
|
|
up_write(&vm->lock);
|
|
|
|
down_write(&xe->usm.lock);
|
|
if (vm->usm.asid) {
|
|
void *lookup;
|
|
|
|
xe_assert(xe, xe->info.has_asid);
|
|
xe_assert(xe, !(vm->flags & XE_VM_FLAG_MIGRATION));
|
|
|
|
lookup = xa_erase(&xe->usm.asid_to_vm, vm->usm.asid);
|
|
xe_assert(xe, lookup == vm);
|
|
}
|
|
up_write(&xe->usm.lock);
|
|
|
|
for_each_tile(tile, xe, id)
|
|
xe_range_fence_tree_fini(&vm->rftree[id]);
|
|
|
|
xe_vm_put(vm);
|
|
}
|
|
|
|
static void vm_destroy_work_func(struct work_struct *w)
|
|
{
|
|
struct xe_vm *vm =
|
|
container_of(w, struct xe_vm, destroy_work);
|
|
struct xe_device *xe = vm->xe;
|
|
struct xe_tile *tile;
|
|
u8 id;
|
|
|
|
/* xe_vm_close_and_put was not called? */
|
|
xe_assert(xe, !vm->size);
|
|
|
|
if (xe_vm_in_preempt_fence_mode(vm))
|
|
flush_work(&vm->preempt.rebind_work);
|
|
|
|
mutex_destroy(&vm->snap_mutex);
|
|
|
|
if (vm->flags & XE_VM_FLAG_LR_MODE)
|
|
xe_pm_runtime_put(xe);
|
|
|
|
for_each_tile(tile, xe, id)
|
|
XE_WARN_ON(vm->pt_root[id]);
|
|
|
|
trace_xe_vm_free(vm);
|
|
|
|
ttm_lru_bulk_move_fini(&xe->ttm, &vm->lru_bulk_move);
|
|
|
|
if (vm->xef)
|
|
xe_file_put(vm->xef);
|
|
|
|
kfree(vm);
|
|
}
|
|
|
|
static void xe_vm_free(struct drm_gpuvm *gpuvm)
|
|
{
|
|
struct xe_vm *vm = container_of(gpuvm, struct xe_vm, gpuvm);
|
|
|
|
/* To destroy the VM we need to be able to sleep */
|
|
queue_work(system_unbound_wq, &vm->destroy_work);
|
|
}
|
|
|
|
struct xe_vm *xe_vm_lookup(struct xe_file *xef, u32 id)
|
|
{
|
|
struct xe_vm *vm;
|
|
|
|
mutex_lock(&xef->vm.lock);
|
|
vm = xa_load(&xef->vm.xa, id);
|
|
if (vm)
|
|
xe_vm_get(vm);
|
|
mutex_unlock(&xef->vm.lock);
|
|
|
|
return vm;
|
|
}
|
|
|
|
u64 xe_vm_pdp4_descriptor(struct xe_vm *vm, struct xe_tile *tile)
|
|
{
|
|
return vm->pt_ops->pde_encode_bo(vm->pt_root[tile->id]->bo, 0);
|
|
}
|
|
|
|
static struct xe_exec_queue *
|
|
to_wait_exec_queue(struct xe_vm *vm, struct xe_exec_queue *q)
|
|
{
|
|
return q ? q : vm->q[0];
|
|
}
|
|
|
|
static struct xe_user_fence *
|
|
find_ufence_get(struct xe_sync_entry *syncs, u32 num_syncs)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < num_syncs; i++) {
|
|
struct xe_sync_entry *e = &syncs[i];
|
|
|
|
if (xe_sync_is_ufence(e))
|
|
return xe_sync_ufence_get(e);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#define ALL_DRM_XE_VM_CREATE_FLAGS (DRM_XE_VM_CREATE_FLAG_SCRATCH_PAGE | \
|
|
DRM_XE_VM_CREATE_FLAG_LR_MODE | \
|
|
DRM_XE_VM_CREATE_FLAG_FAULT_MODE)
|
|
|
|
int xe_vm_create_ioctl(struct drm_device *dev, void *data,
|
|
struct drm_file *file)
|
|
{
|
|
struct xe_device *xe = to_xe_device(dev);
|
|
struct xe_file *xef = to_xe_file(file);
|
|
struct drm_xe_vm_create *args = data;
|
|
struct xe_gt *wa_gt = xe_root_mmio_gt(xe);
|
|
struct xe_vm *vm;
|
|
u32 id;
|
|
int err;
|
|
u32 flags = 0;
|
|
|
|
if (XE_IOCTL_DBG(xe, args->extensions))
|
|
return -EINVAL;
|
|
|
|
if (wa_gt && XE_GT_WA(wa_gt, 22014953428))
|
|
args->flags |= DRM_XE_VM_CREATE_FLAG_SCRATCH_PAGE;
|
|
|
|
if (XE_IOCTL_DBG(xe, args->flags & DRM_XE_VM_CREATE_FLAG_FAULT_MODE &&
|
|
!xe->info.has_usm))
|
|
return -EINVAL;
|
|
|
|
if (XE_IOCTL_DBG(xe, args->reserved[0] || args->reserved[1]))
|
|
return -EINVAL;
|
|
|
|
if (XE_IOCTL_DBG(xe, args->flags & ~ALL_DRM_XE_VM_CREATE_FLAGS))
|
|
return -EINVAL;
|
|
|
|
if (XE_IOCTL_DBG(xe, args->flags & DRM_XE_VM_CREATE_FLAG_SCRATCH_PAGE &&
|
|
args->flags & DRM_XE_VM_CREATE_FLAG_FAULT_MODE &&
|
|
!xe->info.needs_scratch))
|
|
return -EINVAL;
|
|
|
|
if (XE_IOCTL_DBG(xe, !(args->flags & DRM_XE_VM_CREATE_FLAG_LR_MODE) &&
|
|
args->flags & DRM_XE_VM_CREATE_FLAG_FAULT_MODE))
|
|
return -EINVAL;
|
|
|
|
if (args->flags & DRM_XE_VM_CREATE_FLAG_SCRATCH_PAGE)
|
|
flags |= XE_VM_FLAG_SCRATCH_PAGE;
|
|
if (args->flags & DRM_XE_VM_CREATE_FLAG_LR_MODE)
|
|
flags |= XE_VM_FLAG_LR_MODE;
|
|
if (args->flags & DRM_XE_VM_CREATE_FLAG_FAULT_MODE)
|
|
flags |= XE_VM_FLAG_FAULT_MODE;
|
|
|
|
vm = xe_vm_create(xe, flags, xef);
|
|
if (IS_ERR(vm))
|
|
return PTR_ERR(vm);
|
|
|
|
#if IS_ENABLED(CONFIG_DRM_XE_DEBUG_MEM)
|
|
/* Warning: Security issue - never enable by default */
|
|
args->reserved[0] = xe_bo_main_addr(vm->pt_root[0]->bo, XE_PAGE_SIZE);
|
|
#endif
|
|
|
|
/* user id alloc must always be last in ioctl to prevent UAF */
|
|
err = xa_alloc(&xef->vm.xa, &id, vm, xa_limit_32b, GFP_KERNEL);
|
|
if (err)
|
|
goto err_close_and_put;
|
|
|
|
args->vm_id = id;
|
|
|
|
return 0;
|
|
|
|
err_close_and_put:
|
|
xe_vm_close_and_put(vm);
|
|
|
|
return err;
|
|
}
|
|
|
|
int xe_vm_destroy_ioctl(struct drm_device *dev, void *data,
|
|
struct drm_file *file)
|
|
{
|
|
struct xe_device *xe = to_xe_device(dev);
|
|
struct xe_file *xef = to_xe_file(file);
|
|
struct drm_xe_vm_destroy *args = data;
|
|
struct xe_vm *vm;
|
|
int err = 0;
|
|
|
|
if (XE_IOCTL_DBG(xe, args->pad) ||
|
|
XE_IOCTL_DBG(xe, args->reserved[0] || args->reserved[1]))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&xef->vm.lock);
|
|
vm = xa_load(&xef->vm.xa, args->vm_id);
|
|
if (XE_IOCTL_DBG(xe, !vm))
|
|
err = -ENOENT;
|
|
else if (XE_IOCTL_DBG(xe, vm->preempt.num_exec_queues))
|
|
err = -EBUSY;
|
|
else
|
|
xa_erase(&xef->vm.xa, args->vm_id);
|
|
mutex_unlock(&xef->vm.lock);
|
|
|
|
if (!err)
|
|
xe_vm_close_and_put(vm);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int xe_vm_query_vmas(struct xe_vm *vm, u64 start, u64 end)
|
|
{
|
|
struct drm_gpuva *gpuva;
|
|
u32 num_vmas = 0;
|
|
|
|
lockdep_assert_held(&vm->lock);
|
|
drm_gpuvm_for_each_va_range(gpuva, &vm->gpuvm, start, end)
|
|
num_vmas++;
|
|
|
|
return num_vmas;
|
|
}
|
|
|
|
static int get_mem_attrs(struct xe_vm *vm, u32 *num_vmas, u64 start,
|
|
u64 end, struct drm_xe_mem_range_attr *attrs)
|
|
{
|
|
struct drm_gpuva *gpuva;
|
|
int i = 0;
|
|
|
|
lockdep_assert_held(&vm->lock);
|
|
|
|
drm_gpuvm_for_each_va_range(gpuva, &vm->gpuvm, start, end) {
|
|
struct xe_vma *vma = gpuva_to_vma(gpuva);
|
|
|
|
if (i == *num_vmas)
|
|
return -ENOSPC;
|
|
|
|
attrs[i].start = xe_vma_start(vma);
|
|
attrs[i].end = xe_vma_end(vma);
|
|
attrs[i].atomic.val = vma->attr.atomic_access;
|
|
attrs[i].pat_index.val = vma->attr.pat_index;
|
|
attrs[i].preferred_mem_loc.devmem_fd = vma->attr.preferred_loc.devmem_fd;
|
|
attrs[i].preferred_mem_loc.migration_policy =
|
|
vma->attr.preferred_loc.migration_policy;
|
|
|
|
i++;
|
|
}
|
|
|
|
*num_vmas = i;
|
|
return 0;
|
|
}
|
|
|
|
int xe_vm_query_vmas_attrs_ioctl(struct drm_device *dev, void *data, struct drm_file *file)
|
|
{
|
|
struct xe_device *xe = to_xe_device(dev);
|
|
struct xe_file *xef = to_xe_file(file);
|
|
struct drm_xe_mem_range_attr *mem_attrs;
|
|
struct drm_xe_vm_query_mem_range_attr *args = data;
|
|
u64 __user *attrs_user = u64_to_user_ptr(args->vector_of_mem_attr);
|
|
struct xe_vm *vm;
|
|
int err = 0;
|
|
|
|
if (XE_IOCTL_DBG(xe,
|
|
((args->num_mem_ranges == 0 &&
|
|
(attrs_user || args->sizeof_mem_range_attr != 0)) ||
|
|
(args->num_mem_ranges > 0 &&
|
|
(!attrs_user ||
|
|
args->sizeof_mem_range_attr !=
|
|
sizeof(struct drm_xe_mem_range_attr))))))
|
|
return -EINVAL;
|
|
|
|
vm = xe_vm_lookup(xef, args->vm_id);
|
|
if (XE_IOCTL_DBG(xe, !vm))
|
|
return -EINVAL;
|
|
|
|
err = down_read_interruptible(&vm->lock);
|
|
if (err)
|
|
goto put_vm;
|
|
|
|
attrs_user = u64_to_user_ptr(args->vector_of_mem_attr);
|
|
|
|
if (args->num_mem_ranges == 0 && !attrs_user) {
|
|
args->num_mem_ranges = xe_vm_query_vmas(vm, args->start, args->start + args->range);
|
|
args->sizeof_mem_range_attr = sizeof(struct drm_xe_mem_range_attr);
|
|
goto unlock_vm;
|
|
}
|
|
|
|
mem_attrs = kvmalloc_array(args->num_mem_ranges, args->sizeof_mem_range_attr,
|
|
GFP_KERNEL | __GFP_ACCOUNT |
|
|
__GFP_RETRY_MAYFAIL | __GFP_NOWARN);
|
|
if (!mem_attrs) {
|
|
err = args->num_mem_ranges > 1 ? -ENOBUFS : -ENOMEM;
|
|
goto unlock_vm;
|
|
}
|
|
|
|
memset(mem_attrs, 0, args->num_mem_ranges * args->sizeof_mem_range_attr);
|
|
err = get_mem_attrs(vm, &args->num_mem_ranges, args->start,
|
|
args->start + args->range, mem_attrs);
|
|
if (err)
|
|
goto free_mem_attrs;
|
|
|
|
err = copy_to_user(attrs_user, mem_attrs,
|
|
args->sizeof_mem_range_attr * args->num_mem_ranges);
|
|
if (err)
|
|
err = -EFAULT;
|
|
|
|
free_mem_attrs:
|
|
kvfree(mem_attrs);
|
|
unlock_vm:
|
|
up_read(&vm->lock);
|
|
put_vm:
|
|
xe_vm_put(vm);
|
|
return err;
|
|
}
|
|
|
|
static bool vma_matches(struct xe_vma *vma, u64 page_addr)
|
|
{
|
|
if (page_addr > xe_vma_end(vma) - 1 ||
|
|
page_addr + SZ_4K - 1 < xe_vma_start(vma))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* xe_vm_find_vma_by_addr() - Find a VMA by its address
|
|
*
|
|
* @vm: the xe_vm the vma belongs to
|
|
* @page_addr: address to look up
|
|
*/
|
|
struct xe_vma *xe_vm_find_vma_by_addr(struct xe_vm *vm, u64 page_addr)
|
|
{
|
|
struct xe_vma *vma = NULL;
|
|
|
|
if (vm->usm.last_fault_vma) { /* Fast lookup */
|
|
if (vma_matches(vm->usm.last_fault_vma, page_addr))
|
|
vma = vm->usm.last_fault_vma;
|
|
}
|
|
if (!vma)
|
|
vma = xe_vm_find_overlapping_vma(vm, page_addr, SZ_4K);
|
|
|
|
return vma;
|
|
}
|
|
|
|
static const u32 region_to_mem_type[] = {
|
|
XE_PL_TT,
|
|
XE_PL_VRAM0,
|
|
XE_PL_VRAM1,
|
|
};
|
|
|
|
static void prep_vma_destroy(struct xe_vm *vm, struct xe_vma *vma,
|
|
bool post_commit)
|
|
{
|
|
xe_svm_notifier_lock(vm);
|
|
vma->gpuva.flags |= XE_VMA_DESTROYED;
|
|
xe_svm_notifier_unlock(vm);
|
|
if (post_commit)
|
|
xe_vm_remove_vma(vm, vma);
|
|
}
|
|
|
|
#undef ULL
|
|
#define ULL unsigned long long
|
|
|
|
#if IS_ENABLED(CONFIG_DRM_XE_DEBUG_VM)
|
|
static void print_op(struct xe_device *xe, struct drm_gpuva_op *op)
|
|
{
|
|
struct xe_vma *vma;
|
|
|
|
switch (op->op) {
|
|
case DRM_GPUVA_OP_MAP:
|
|
vm_dbg(&xe->drm, "MAP: addr=0x%016llx, range=0x%016llx",
|
|
(ULL)op->map.va.addr, (ULL)op->map.va.range);
|
|
break;
|
|
case DRM_GPUVA_OP_REMAP:
|
|
vma = gpuva_to_vma(op->remap.unmap->va);
|
|
vm_dbg(&xe->drm, "REMAP:UNMAP: addr=0x%016llx, range=0x%016llx, keep=%d",
|
|
(ULL)xe_vma_start(vma), (ULL)xe_vma_size(vma),
|
|
op->remap.unmap->keep ? 1 : 0);
|
|
if (op->remap.prev)
|
|
vm_dbg(&xe->drm,
|
|
"REMAP:PREV: addr=0x%016llx, range=0x%016llx",
|
|
(ULL)op->remap.prev->va.addr,
|
|
(ULL)op->remap.prev->va.range);
|
|
if (op->remap.next)
|
|
vm_dbg(&xe->drm,
|
|
"REMAP:NEXT: addr=0x%016llx, range=0x%016llx",
|
|
(ULL)op->remap.next->va.addr,
|
|
(ULL)op->remap.next->va.range);
|
|
break;
|
|
case DRM_GPUVA_OP_UNMAP:
|
|
vma = gpuva_to_vma(op->unmap.va);
|
|
vm_dbg(&xe->drm, "UNMAP: addr=0x%016llx, range=0x%016llx, keep=%d",
|
|
(ULL)xe_vma_start(vma), (ULL)xe_vma_size(vma),
|
|
op->unmap.keep ? 1 : 0);
|
|
break;
|
|
case DRM_GPUVA_OP_PREFETCH:
|
|
vma = gpuva_to_vma(op->prefetch.va);
|
|
vm_dbg(&xe->drm, "PREFETCH: addr=0x%016llx, range=0x%016llx",
|
|
(ULL)xe_vma_start(vma), (ULL)xe_vma_size(vma));
|
|
break;
|
|
default:
|
|
drm_warn(&xe->drm, "NOT POSSIBLE");
|
|
}
|
|
}
|
|
#else
|
|
static void print_op(struct xe_device *xe, struct drm_gpuva_op *op)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
static bool __xe_vm_needs_clear_scratch_pages(struct xe_vm *vm, u32 bind_flags)
|
|
{
|
|
if (!xe_vm_in_fault_mode(vm))
|
|
return false;
|
|
|
|
if (!xe_vm_has_scratch(vm))
|
|
return false;
|
|
|
|
if (bind_flags & DRM_XE_VM_BIND_FLAG_IMMEDIATE)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void xe_svm_prefetch_gpuva_ops_fini(struct drm_gpuva_ops *ops)
|
|
{
|
|
struct drm_gpuva_op *__op;
|
|
|
|
drm_gpuva_for_each_op(__op, ops) {
|
|
struct xe_vma_op *op = gpuva_op_to_vma_op(__op);
|
|
|
|
xe_vma_svm_prefetch_op_fini(op);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Create operations list from IOCTL arguments, setup operations fields so parse
|
|
* and commit steps are decoupled from IOCTL arguments. This step can fail.
|
|
*/
|
|
static struct drm_gpuva_ops *
|
|
vm_bind_ioctl_ops_create(struct xe_vm *vm, struct xe_vma_ops *vops,
|
|
struct xe_bo *bo, u64 bo_offset_or_userptr,
|
|
u64 addr, u64 range,
|
|
u32 operation, u32 flags,
|
|
u32 prefetch_region, u16 pat_index)
|
|
{
|
|
struct drm_gem_object *obj = bo ? &bo->ttm.base : NULL;
|
|
struct drm_gpuva_ops *ops;
|
|
struct drm_gpuva_op *__op;
|
|
struct drm_gpuvm_bo *vm_bo;
|
|
u64 range_end = addr + range;
|
|
int err;
|
|
|
|
lockdep_assert_held_write(&vm->lock);
|
|
|
|
vm_dbg(&vm->xe->drm,
|
|
"op=%d, addr=0x%016llx, range=0x%016llx, bo_offset_or_userptr=0x%016llx",
|
|
operation, (ULL)addr, (ULL)range,
|
|
(ULL)bo_offset_or_userptr);
|
|
|
|
switch (operation) {
|
|
case DRM_XE_VM_BIND_OP_MAP:
|
|
case DRM_XE_VM_BIND_OP_MAP_USERPTR: {
|
|
struct drm_gpuvm_map_req map_req = {
|
|
.map.va.addr = addr,
|
|
.map.va.range = range,
|
|
.map.gem.obj = obj,
|
|
.map.gem.offset = bo_offset_or_userptr,
|
|
};
|
|
|
|
ops = drm_gpuvm_sm_map_ops_create(&vm->gpuvm, &map_req);
|
|
break;
|
|
}
|
|
case DRM_XE_VM_BIND_OP_UNMAP:
|
|
ops = drm_gpuvm_sm_unmap_ops_create(&vm->gpuvm, addr, range);
|
|
break;
|
|
case DRM_XE_VM_BIND_OP_PREFETCH:
|
|
ops = drm_gpuvm_prefetch_ops_create(&vm->gpuvm, addr, range);
|
|
break;
|
|
case DRM_XE_VM_BIND_OP_UNMAP_ALL:
|
|
xe_assert(vm->xe, bo);
|
|
|
|
err = xe_bo_lock(bo, true);
|
|
if (err)
|
|
return ERR_PTR(err);
|
|
|
|
vm_bo = drm_gpuvm_bo_obtain(&vm->gpuvm, obj);
|
|
if (IS_ERR(vm_bo)) {
|
|
xe_bo_unlock(bo);
|
|
return ERR_CAST(vm_bo);
|
|
}
|
|
|
|
ops = drm_gpuvm_bo_unmap_ops_create(vm_bo);
|
|
drm_gpuvm_bo_put(vm_bo);
|
|
xe_bo_unlock(bo);
|
|
break;
|
|
default:
|
|
drm_warn(&vm->xe->drm, "NOT POSSIBLE");
|
|
ops = ERR_PTR(-EINVAL);
|
|
}
|
|
if (IS_ERR(ops))
|
|
return ops;
|
|
|
|
drm_gpuva_for_each_op(__op, ops) {
|
|
struct xe_vma_op *op = gpuva_op_to_vma_op(__op);
|
|
|
|
if (__op->op == DRM_GPUVA_OP_MAP) {
|
|
op->map.immediate =
|
|
flags & DRM_XE_VM_BIND_FLAG_IMMEDIATE;
|
|
if (flags & DRM_XE_VM_BIND_FLAG_READONLY)
|
|
op->map.vma_flags |= XE_VMA_READ_ONLY;
|
|
if (flags & DRM_XE_VM_BIND_FLAG_NULL)
|
|
op->map.vma_flags |= DRM_GPUVA_SPARSE;
|
|
if (flags & DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR)
|
|
op->map.vma_flags |= XE_VMA_SYSTEM_ALLOCATOR;
|
|
if (flags & DRM_XE_VM_BIND_FLAG_DUMPABLE)
|
|
op->map.vma_flags |= XE_VMA_DUMPABLE;
|
|
if (flags & DRM_XE_VM_BIND_FLAG_MADVISE_AUTORESET)
|
|
op->map.vma_flags |= XE_VMA_MADV_AUTORESET;
|
|
op->map.pat_index = pat_index;
|
|
op->map.invalidate_on_bind =
|
|
__xe_vm_needs_clear_scratch_pages(vm, flags);
|
|
} else if (__op->op == DRM_GPUVA_OP_PREFETCH) {
|
|
struct xe_vma *vma = gpuva_to_vma(op->base.prefetch.va);
|
|
struct xe_tile *tile;
|
|
struct xe_svm_range *svm_range;
|
|
struct drm_gpusvm_ctx ctx = {};
|
|
struct drm_pagemap *dpagemap;
|
|
u8 id, tile_mask = 0;
|
|
u32 i;
|
|
|
|
if (!xe_vma_is_cpu_addr_mirror(vma)) {
|
|
op->prefetch.region = prefetch_region;
|
|
break;
|
|
}
|
|
|
|
ctx.read_only = xe_vma_read_only(vma);
|
|
ctx.devmem_possible = IS_DGFX(vm->xe) &&
|
|
IS_ENABLED(CONFIG_DRM_XE_PAGEMAP);
|
|
|
|
for_each_tile(tile, vm->xe, id)
|
|
tile_mask |= 0x1 << id;
|
|
|
|
xa_init_flags(&op->prefetch_range.range, XA_FLAGS_ALLOC);
|
|
op->prefetch_range.ranges_count = 0;
|
|
tile = NULL;
|
|
|
|
if (prefetch_region == DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC) {
|
|
dpagemap = xe_vma_resolve_pagemap(vma,
|
|
xe_device_get_root_tile(vm->xe));
|
|
/*
|
|
* TODO: Once multigpu support is enabled will need
|
|
* something to dereference tile from dpagemap.
|
|
*/
|
|
if (dpagemap)
|
|
tile = xe_device_get_root_tile(vm->xe);
|
|
} else if (prefetch_region) {
|
|
tile = &vm->xe->tiles[region_to_mem_type[prefetch_region] -
|
|
XE_PL_VRAM0];
|
|
}
|
|
|
|
op->prefetch_range.tile = tile;
|
|
alloc_next_range:
|
|
svm_range = xe_svm_range_find_or_insert(vm, addr, vma, &ctx);
|
|
|
|
if (PTR_ERR(svm_range) == -ENOENT) {
|
|
u64 ret = xe_svm_find_vma_start(vm, addr, range_end, vma);
|
|
|
|
addr = ret == ULONG_MAX ? 0 : ret;
|
|
if (addr)
|
|
goto alloc_next_range;
|
|
else
|
|
goto print_op_label;
|
|
}
|
|
|
|
if (IS_ERR(svm_range)) {
|
|
err = PTR_ERR(svm_range);
|
|
goto unwind_prefetch_ops;
|
|
}
|
|
|
|
if (xe_svm_range_validate(vm, svm_range, tile_mask, !!tile)) {
|
|
xe_svm_range_debug(svm_range, "PREFETCH - RANGE IS VALID");
|
|
goto check_next_range;
|
|
}
|
|
|
|
err = xa_alloc(&op->prefetch_range.range,
|
|
&i, svm_range, xa_limit_32b,
|
|
GFP_KERNEL);
|
|
|
|
if (err)
|
|
goto unwind_prefetch_ops;
|
|
|
|
op->prefetch_range.ranges_count++;
|
|
vops->flags |= XE_VMA_OPS_FLAG_HAS_SVM_PREFETCH;
|
|
xe_svm_range_debug(svm_range, "PREFETCH - RANGE CREATED");
|
|
check_next_range:
|
|
if (range_end > xe_svm_range_end(svm_range) &&
|
|
xe_svm_range_end(svm_range) < xe_vma_end(vma)) {
|
|
addr = xe_svm_range_end(svm_range);
|
|
goto alloc_next_range;
|
|
}
|
|
}
|
|
print_op_label:
|
|
print_op(vm->xe, __op);
|
|
}
|
|
|
|
return ops;
|
|
|
|
unwind_prefetch_ops:
|
|
xe_svm_prefetch_gpuva_ops_fini(ops);
|
|
drm_gpuva_ops_free(&vm->gpuvm, ops);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
ALLOW_ERROR_INJECTION(vm_bind_ioctl_ops_create, ERRNO);
|
|
|
|
static struct xe_vma *new_vma(struct xe_vm *vm, struct drm_gpuva_op_map *op,
|
|
struct xe_vma_mem_attr *attr, unsigned int flags)
|
|
{
|
|
struct xe_bo *bo = op->gem.obj ? gem_to_xe_bo(op->gem.obj) : NULL;
|
|
struct xe_validation_ctx ctx;
|
|
struct drm_exec exec;
|
|
struct xe_vma *vma;
|
|
int err = 0;
|
|
|
|
lockdep_assert_held_write(&vm->lock);
|
|
|
|
if (bo) {
|
|
err = 0;
|
|
xe_validation_guard(&ctx, &vm->xe->val, &exec,
|
|
(struct xe_val_flags) {.interruptible = true}, err) {
|
|
if (!bo->vm) {
|
|
err = drm_exec_lock_obj(&exec, xe_vm_obj(vm));
|
|
drm_exec_retry_on_contention(&exec);
|
|
}
|
|
if (!err) {
|
|
err = drm_exec_lock_obj(&exec, &bo->ttm.base);
|
|
drm_exec_retry_on_contention(&exec);
|
|
}
|
|
if (err)
|
|
return ERR_PTR(err);
|
|
|
|
vma = xe_vma_create(vm, bo, op->gem.offset,
|
|
op->va.addr, op->va.addr +
|
|
op->va.range - 1, attr, flags);
|
|
if (IS_ERR(vma))
|
|
return vma;
|
|
|
|
if (!bo->vm) {
|
|
err = add_preempt_fences(vm, bo);
|
|
if (err) {
|
|
prep_vma_destroy(vm, vma, false);
|
|
xe_vma_destroy(vma, NULL);
|
|
}
|
|
}
|
|
}
|
|
if (err)
|
|
return ERR_PTR(err);
|
|
} else {
|
|
vma = xe_vma_create(vm, NULL, op->gem.offset,
|
|
op->va.addr, op->va.addr +
|
|
op->va.range - 1, attr, flags);
|
|
if (IS_ERR(vma))
|
|
return vma;
|
|
|
|
if (xe_vma_is_userptr(vma))
|
|
err = xe_vma_userptr_pin_pages(to_userptr_vma(vma));
|
|
}
|
|
if (err) {
|
|
prep_vma_destroy(vm, vma, false);
|
|
xe_vma_destroy_unlocked(vma);
|
|
vma = ERR_PTR(err);
|
|
}
|
|
|
|
return vma;
|
|
}
|
|
|
|
static u64 xe_vma_max_pte_size(struct xe_vma *vma)
|
|
{
|
|
if (vma->gpuva.flags & XE_VMA_PTE_1G)
|
|
return SZ_1G;
|
|
else if (vma->gpuva.flags & (XE_VMA_PTE_2M | XE_VMA_PTE_COMPACT))
|
|
return SZ_2M;
|
|
else if (vma->gpuva.flags & XE_VMA_PTE_64K)
|
|
return SZ_64K;
|
|
else if (vma->gpuva.flags & XE_VMA_PTE_4K)
|
|
return SZ_4K;
|
|
|
|
return SZ_1G; /* Uninitialized, used max size */
|
|
}
|
|
|
|
static void xe_vma_set_pte_size(struct xe_vma *vma, u64 size)
|
|
{
|
|
switch (size) {
|
|
case SZ_1G:
|
|
vma->gpuva.flags |= XE_VMA_PTE_1G;
|
|
break;
|
|
case SZ_2M:
|
|
vma->gpuva.flags |= XE_VMA_PTE_2M;
|
|
break;
|
|
case SZ_64K:
|
|
vma->gpuva.flags |= XE_VMA_PTE_64K;
|
|
break;
|
|
case SZ_4K:
|
|
vma->gpuva.flags |= XE_VMA_PTE_4K;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int xe_vma_op_commit(struct xe_vm *vm, struct xe_vma_op *op)
|
|
{
|
|
int err = 0;
|
|
|
|
lockdep_assert_held_write(&vm->lock);
|
|
|
|
switch (op->base.op) {
|
|
case DRM_GPUVA_OP_MAP:
|
|
err |= xe_vm_insert_vma(vm, op->map.vma);
|
|
if (!err)
|
|
op->flags |= XE_VMA_OP_COMMITTED;
|
|
break;
|
|
case DRM_GPUVA_OP_REMAP:
|
|
{
|
|
u8 tile_present =
|
|
gpuva_to_vma(op->base.remap.unmap->va)->tile_present;
|
|
|
|
prep_vma_destroy(vm, gpuva_to_vma(op->base.remap.unmap->va),
|
|
true);
|
|
op->flags |= XE_VMA_OP_COMMITTED;
|
|
|
|
if (op->remap.prev) {
|
|
err |= xe_vm_insert_vma(vm, op->remap.prev);
|
|
if (!err)
|
|
op->flags |= XE_VMA_OP_PREV_COMMITTED;
|
|
if (!err && op->remap.skip_prev) {
|
|
op->remap.prev->tile_present =
|
|
tile_present;
|
|
op->remap.prev = NULL;
|
|
}
|
|
}
|
|
if (op->remap.next) {
|
|
err |= xe_vm_insert_vma(vm, op->remap.next);
|
|
if (!err)
|
|
op->flags |= XE_VMA_OP_NEXT_COMMITTED;
|
|
if (!err && op->remap.skip_next) {
|
|
op->remap.next->tile_present =
|
|
tile_present;
|
|
op->remap.next = NULL;
|
|
}
|
|
}
|
|
|
|
/* Adjust for partial unbind after removing VMA from VM */
|
|
if (!err) {
|
|
op->base.remap.unmap->va->va.addr = op->remap.start;
|
|
op->base.remap.unmap->va->va.range = op->remap.range;
|
|
}
|
|
break;
|
|
}
|
|
case DRM_GPUVA_OP_UNMAP:
|
|
prep_vma_destroy(vm, gpuva_to_vma(op->base.unmap.va), true);
|
|
op->flags |= XE_VMA_OP_COMMITTED;
|
|
break;
|
|
case DRM_GPUVA_OP_PREFETCH:
|
|
op->flags |= XE_VMA_OP_COMMITTED;
|
|
break;
|
|
default:
|
|
drm_warn(&vm->xe->drm, "NOT POSSIBLE");
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* xe_vma_has_default_mem_attrs - Check if a VMA has default memory attributes
|
|
* @vma: Pointer to the xe_vma structure to check
|
|
*
|
|
* This function determines whether the given VMA (Virtual Memory Area)
|
|
* has its memory attributes set to their default values. Specifically,
|
|
* it checks the following conditions:
|
|
*
|
|
* - `atomic_access` is `DRM_XE_VMA_ATOMIC_UNDEFINED`
|
|
* - `pat_index` is equal to `default_pat_index`
|
|
* - `preferred_loc.devmem_fd` is `DRM_XE_PREFERRED_LOC_DEFAULT_DEVICE`
|
|
* - `preferred_loc.migration_policy` is `DRM_XE_MIGRATE_ALL_PAGES`
|
|
*
|
|
* Return: true if all attributes are at their default values, false otherwise.
|
|
*/
|
|
bool xe_vma_has_default_mem_attrs(struct xe_vma *vma)
|
|
{
|
|
return (vma->attr.atomic_access == DRM_XE_ATOMIC_UNDEFINED &&
|
|
vma->attr.pat_index == vma->attr.default_pat_index &&
|
|
vma->attr.preferred_loc.devmem_fd == DRM_XE_PREFERRED_LOC_DEFAULT_DEVICE &&
|
|
vma->attr.preferred_loc.migration_policy == DRM_XE_MIGRATE_ALL_PAGES);
|
|
}
|
|
|
|
static int vm_bind_ioctl_ops_parse(struct xe_vm *vm, struct drm_gpuva_ops *ops,
|
|
struct xe_vma_ops *vops)
|
|
{
|
|
struct xe_device *xe = vm->xe;
|
|
struct drm_gpuva_op *__op;
|
|
struct xe_tile *tile;
|
|
u8 id, tile_mask = 0;
|
|
int err = 0;
|
|
|
|
lockdep_assert_held_write(&vm->lock);
|
|
|
|
for_each_tile(tile, vm->xe, id)
|
|
tile_mask |= 0x1 << id;
|
|
|
|
drm_gpuva_for_each_op(__op, ops) {
|
|
struct xe_vma_op *op = gpuva_op_to_vma_op(__op);
|
|
struct xe_vma *vma;
|
|
unsigned int flags = 0;
|
|
|
|
INIT_LIST_HEAD(&op->link);
|
|
list_add_tail(&op->link, &vops->list);
|
|
op->tile_mask = tile_mask;
|
|
|
|
switch (op->base.op) {
|
|
case DRM_GPUVA_OP_MAP:
|
|
{
|
|
struct xe_vma_mem_attr default_attr = {
|
|
.preferred_loc = {
|
|
.devmem_fd = DRM_XE_PREFERRED_LOC_DEFAULT_DEVICE,
|
|
.migration_policy = DRM_XE_MIGRATE_ALL_PAGES,
|
|
},
|
|
.atomic_access = DRM_XE_ATOMIC_UNDEFINED,
|
|
.default_pat_index = op->map.pat_index,
|
|
.pat_index = op->map.pat_index,
|
|
};
|
|
|
|
flags |= op->map.vma_flags & XE_VMA_CREATE_MASK;
|
|
|
|
vma = new_vma(vm, &op->base.map, &default_attr,
|
|
flags);
|
|
if (IS_ERR(vma))
|
|
return PTR_ERR(vma);
|
|
|
|
op->map.vma = vma;
|
|
if (((op->map.immediate || !xe_vm_in_fault_mode(vm)) &&
|
|
!(op->map.vma_flags & XE_VMA_SYSTEM_ALLOCATOR)) ||
|
|
op->map.invalidate_on_bind)
|
|
xe_vma_ops_incr_pt_update_ops(vops,
|
|
op->tile_mask, 1);
|
|
break;
|
|
}
|
|
case DRM_GPUVA_OP_REMAP:
|
|
{
|
|
struct xe_vma *old =
|
|
gpuva_to_vma(op->base.remap.unmap->va);
|
|
bool skip = xe_vma_is_cpu_addr_mirror(old);
|
|
u64 start = xe_vma_start(old), end = xe_vma_end(old);
|
|
int num_remap_ops = 0;
|
|
|
|
if (op->base.remap.prev)
|
|
start = op->base.remap.prev->va.addr +
|
|
op->base.remap.prev->va.range;
|
|
if (op->base.remap.next)
|
|
end = op->base.remap.next->va.addr;
|
|
|
|
if (xe_vma_is_cpu_addr_mirror(old) &&
|
|
xe_svm_has_mapping(vm, start, end)) {
|
|
if (vops->flags & XE_VMA_OPS_FLAG_MADVISE)
|
|
xe_svm_unmap_address_range(vm, start, end);
|
|
else
|
|
return -EBUSY;
|
|
}
|
|
|
|
op->remap.start = xe_vma_start(old);
|
|
op->remap.range = xe_vma_size(old);
|
|
|
|
flags |= op->base.remap.unmap->va->flags & XE_VMA_CREATE_MASK;
|
|
if (op->base.remap.prev) {
|
|
vma = new_vma(vm, op->base.remap.prev,
|
|
&old->attr, flags);
|
|
if (IS_ERR(vma))
|
|
return PTR_ERR(vma);
|
|
|
|
op->remap.prev = vma;
|
|
|
|
/*
|
|
* Userptr creates a new SG mapping so
|
|
* we must also rebind.
|
|
*/
|
|
op->remap.skip_prev = skip ||
|
|
(!xe_vma_is_userptr(old) &&
|
|
IS_ALIGNED(xe_vma_end(vma),
|
|
xe_vma_max_pte_size(old)));
|
|
if (op->remap.skip_prev) {
|
|
xe_vma_set_pte_size(vma, xe_vma_max_pte_size(old));
|
|
op->remap.range -=
|
|
xe_vma_end(vma) -
|
|
xe_vma_start(old);
|
|
op->remap.start = xe_vma_end(vma);
|
|
vm_dbg(&xe->drm, "REMAP:SKIP_PREV: addr=0x%016llx, range=0x%016llx",
|
|
(ULL)op->remap.start,
|
|
(ULL)op->remap.range);
|
|
} else {
|
|
num_remap_ops++;
|
|
}
|
|
}
|
|
|
|
if (op->base.remap.next) {
|
|
vma = new_vma(vm, op->base.remap.next,
|
|
&old->attr, flags);
|
|
if (IS_ERR(vma))
|
|
return PTR_ERR(vma);
|
|
|
|
op->remap.next = vma;
|
|
|
|
/*
|
|
* Userptr creates a new SG mapping so
|
|
* we must also rebind.
|
|
*/
|
|
op->remap.skip_next = skip ||
|
|
(!xe_vma_is_userptr(old) &&
|
|
IS_ALIGNED(xe_vma_start(vma),
|
|
xe_vma_max_pte_size(old)));
|
|
if (op->remap.skip_next) {
|
|
xe_vma_set_pte_size(vma, xe_vma_max_pte_size(old));
|
|
op->remap.range -=
|
|
xe_vma_end(old) -
|
|
xe_vma_start(vma);
|
|
vm_dbg(&xe->drm, "REMAP:SKIP_NEXT: addr=0x%016llx, range=0x%016llx",
|
|
(ULL)op->remap.start,
|
|
(ULL)op->remap.range);
|
|
} else {
|
|
num_remap_ops++;
|
|
}
|
|
}
|
|
if (!skip)
|
|
num_remap_ops++;
|
|
|
|
xe_vma_ops_incr_pt_update_ops(vops, op->tile_mask, num_remap_ops);
|
|
break;
|
|
}
|
|
case DRM_GPUVA_OP_UNMAP:
|
|
vma = gpuva_to_vma(op->base.unmap.va);
|
|
|
|
if (xe_vma_is_cpu_addr_mirror(vma) &&
|
|
xe_svm_has_mapping(vm, xe_vma_start(vma),
|
|
xe_vma_end(vma)))
|
|
return -EBUSY;
|
|
|
|
if (!xe_vma_is_cpu_addr_mirror(vma))
|
|
xe_vma_ops_incr_pt_update_ops(vops, op->tile_mask, 1);
|
|
break;
|
|
case DRM_GPUVA_OP_PREFETCH:
|
|
vma = gpuva_to_vma(op->base.prefetch.va);
|
|
|
|
if (xe_vma_is_userptr(vma)) {
|
|
err = xe_vma_userptr_pin_pages(to_userptr_vma(vma));
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
if (xe_vma_is_cpu_addr_mirror(vma))
|
|
xe_vma_ops_incr_pt_update_ops(vops, op->tile_mask,
|
|
op->prefetch_range.ranges_count);
|
|
else
|
|
xe_vma_ops_incr_pt_update_ops(vops, op->tile_mask, 1);
|
|
|
|
break;
|
|
default:
|
|
drm_warn(&vm->xe->drm, "NOT POSSIBLE");
|
|
}
|
|
|
|
err = xe_vma_op_commit(vm, op);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void xe_vma_op_unwind(struct xe_vm *vm, struct xe_vma_op *op,
|
|
bool post_commit, bool prev_post_commit,
|
|
bool next_post_commit)
|
|
{
|
|
lockdep_assert_held_write(&vm->lock);
|
|
|
|
switch (op->base.op) {
|
|
case DRM_GPUVA_OP_MAP:
|
|
if (op->map.vma) {
|
|
prep_vma_destroy(vm, op->map.vma, post_commit);
|
|
xe_vma_destroy_unlocked(op->map.vma);
|
|
}
|
|
break;
|
|
case DRM_GPUVA_OP_UNMAP:
|
|
{
|
|
struct xe_vma *vma = gpuva_to_vma(op->base.unmap.va);
|
|
|
|
if (vma) {
|
|
xe_svm_notifier_lock(vm);
|
|
vma->gpuva.flags &= ~XE_VMA_DESTROYED;
|
|
xe_svm_notifier_unlock(vm);
|
|
if (post_commit)
|
|
xe_vm_insert_vma(vm, vma);
|
|
}
|
|
break;
|
|
}
|
|
case DRM_GPUVA_OP_REMAP:
|
|
{
|
|
struct xe_vma *vma = gpuva_to_vma(op->base.remap.unmap->va);
|
|
|
|
if (op->remap.prev) {
|
|
prep_vma_destroy(vm, op->remap.prev, prev_post_commit);
|
|
xe_vma_destroy_unlocked(op->remap.prev);
|
|
}
|
|
if (op->remap.next) {
|
|
prep_vma_destroy(vm, op->remap.next, next_post_commit);
|
|
xe_vma_destroy_unlocked(op->remap.next);
|
|
}
|
|
if (vma) {
|
|
xe_svm_notifier_lock(vm);
|
|
vma->gpuva.flags &= ~XE_VMA_DESTROYED;
|
|
xe_svm_notifier_unlock(vm);
|
|
if (post_commit)
|
|
xe_vm_insert_vma(vm, vma);
|
|
}
|
|
break;
|
|
}
|
|
case DRM_GPUVA_OP_PREFETCH:
|
|
/* Nothing to do */
|
|
break;
|
|
default:
|
|
drm_warn(&vm->xe->drm, "NOT POSSIBLE");
|
|
}
|
|
}
|
|
|
|
static void vm_bind_ioctl_ops_unwind(struct xe_vm *vm,
|
|
struct drm_gpuva_ops **ops,
|
|
int num_ops_list)
|
|
{
|
|
int i;
|
|
|
|
for (i = num_ops_list - 1; i >= 0; --i) {
|
|
struct drm_gpuva_ops *__ops = ops[i];
|
|
struct drm_gpuva_op *__op;
|
|
|
|
if (!__ops)
|
|
continue;
|
|
|
|
drm_gpuva_for_each_op_reverse(__op, __ops) {
|
|
struct xe_vma_op *op = gpuva_op_to_vma_op(__op);
|
|
|
|
xe_vma_op_unwind(vm, op,
|
|
op->flags & XE_VMA_OP_COMMITTED,
|
|
op->flags & XE_VMA_OP_PREV_COMMITTED,
|
|
op->flags & XE_VMA_OP_NEXT_COMMITTED);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int vma_lock_and_validate(struct drm_exec *exec, struct xe_vma *vma,
|
|
bool res_evict, bool validate)
|
|
{
|
|
struct xe_bo *bo = xe_vma_bo(vma);
|
|
struct xe_vm *vm = xe_vma_vm(vma);
|
|
int err = 0;
|
|
|
|
if (bo) {
|
|
if (!bo->vm)
|
|
err = drm_exec_lock_obj(exec, &bo->ttm.base);
|
|
if (!err && validate)
|
|
err = xe_bo_validate(bo, vm,
|
|
!xe_vm_in_preempt_fence_mode(vm) &&
|
|
res_evict, exec);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int check_ufence(struct xe_vma *vma)
|
|
{
|
|
if (vma->ufence) {
|
|
struct xe_user_fence * const f = vma->ufence;
|
|
|
|
if (!xe_sync_ufence_get_status(f))
|
|
return -EBUSY;
|
|
|
|
vma->ufence = NULL;
|
|
xe_sync_ufence_put(f);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int prefetch_ranges(struct xe_vm *vm, struct xe_vma_op *op)
|
|
{
|
|
bool devmem_possible = IS_DGFX(vm->xe) && IS_ENABLED(CONFIG_DRM_XE_PAGEMAP);
|
|
struct xe_vma *vma = gpuva_to_vma(op->base.prefetch.va);
|
|
struct xe_tile *tile = op->prefetch_range.tile;
|
|
int err = 0;
|
|
|
|
struct xe_svm_range *svm_range;
|
|
struct drm_gpusvm_ctx ctx = {};
|
|
unsigned long i;
|
|
|
|
if (!xe_vma_is_cpu_addr_mirror(vma))
|
|
return 0;
|
|
|
|
ctx.read_only = xe_vma_read_only(vma);
|
|
ctx.devmem_possible = devmem_possible;
|
|
ctx.check_pages_threshold = devmem_possible ? SZ_64K : 0;
|
|
ctx.device_private_page_owner = xe_svm_devm_owner(vm->xe);
|
|
|
|
/* TODO: Threading the migration */
|
|
xa_for_each(&op->prefetch_range.range, i, svm_range) {
|
|
if (!tile)
|
|
xe_svm_range_migrate_to_smem(vm, svm_range);
|
|
|
|
if (xe_svm_range_needs_migrate_to_vram(svm_range, vma, !!tile)) {
|
|
err = xe_svm_alloc_vram(tile, svm_range, &ctx);
|
|
if (err) {
|
|
drm_dbg(&vm->xe->drm, "VRAM allocation failed, retry from userspace, asid=%u, gpusvm=%p, errno=%pe\n",
|
|
vm->usm.asid, &vm->svm.gpusvm, ERR_PTR(err));
|
|
return -ENODATA;
|
|
}
|
|
xe_svm_range_debug(svm_range, "PREFETCH - RANGE MIGRATED TO VRAM");
|
|
}
|
|
|
|
err = xe_svm_range_get_pages(vm, svm_range, &ctx);
|
|
if (err) {
|
|
drm_dbg(&vm->xe->drm, "Get pages failed, asid=%u, gpusvm=%p, errno=%pe\n",
|
|
vm->usm.asid, &vm->svm.gpusvm, ERR_PTR(err));
|
|
if (err == -EOPNOTSUPP || err == -EFAULT || err == -EPERM)
|
|
err = -ENODATA;
|
|
return err;
|
|
}
|
|
xe_svm_range_debug(svm_range, "PREFETCH - RANGE GET PAGES DONE");
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int op_lock_and_prep(struct drm_exec *exec, struct xe_vm *vm,
|
|
struct xe_vma_ops *vops, struct xe_vma_op *op)
|
|
{
|
|
int err = 0;
|
|
bool res_evict;
|
|
|
|
/*
|
|
* We only allow evicting a BO within the VM if it is not part of an
|
|
* array of binds, as an array of binds can evict another BO within the
|
|
* bind.
|
|
*/
|
|
res_evict = !(vops->flags & XE_VMA_OPS_ARRAY_OF_BINDS);
|
|
|
|
switch (op->base.op) {
|
|
case DRM_GPUVA_OP_MAP:
|
|
if (!op->map.invalidate_on_bind)
|
|
err = vma_lock_and_validate(exec, op->map.vma,
|
|
res_evict,
|
|
!xe_vm_in_fault_mode(vm) ||
|
|
op->map.immediate);
|
|
break;
|
|
case DRM_GPUVA_OP_REMAP:
|
|
err = check_ufence(gpuva_to_vma(op->base.remap.unmap->va));
|
|
if (err)
|
|
break;
|
|
|
|
err = vma_lock_and_validate(exec,
|
|
gpuva_to_vma(op->base.remap.unmap->va),
|
|
res_evict, false);
|
|
if (!err && op->remap.prev)
|
|
err = vma_lock_and_validate(exec, op->remap.prev,
|
|
res_evict, true);
|
|
if (!err && op->remap.next)
|
|
err = vma_lock_and_validate(exec, op->remap.next,
|
|
res_evict, true);
|
|
break;
|
|
case DRM_GPUVA_OP_UNMAP:
|
|
err = check_ufence(gpuva_to_vma(op->base.unmap.va));
|
|
if (err)
|
|
break;
|
|
|
|
err = vma_lock_and_validate(exec,
|
|
gpuva_to_vma(op->base.unmap.va),
|
|
res_evict, false);
|
|
break;
|
|
case DRM_GPUVA_OP_PREFETCH:
|
|
{
|
|
struct xe_vma *vma = gpuva_to_vma(op->base.prefetch.va);
|
|
u32 region;
|
|
|
|
if (!xe_vma_is_cpu_addr_mirror(vma)) {
|
|
region = op->prefetch.region;
|
|
xe_assert(vm->xe, region == DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC ||
|
|
region <= ARRAY_SIZE(region_to_mem_type));
|
|
}
|
|
|
|
err = vma_lock_and_validate(exec,
|
|
gpuva_to_vma(op->base.prefetch.va),
|
|
res_evict, false);
|
|
if (!err && !xe_vma_has_no_bo(vma))
|
|
err = xe_bo_migrate(xe_vma_bo(vma),
|
|
region_to_mem_type[region],
|
|
NULL,
|
|
exec);
|
|
break;
|
|
}
|
|
default:
|
|
drm_warn(&vm->xe->drm, "NOT POSSIBLE");
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int vm_bind_ioctl_ops_prefetch_ranges(struct xe_vm *vm, struct xe_vma_ops *vops)
|
|
{
|
|
struct xe_vma_op *op;
|
|
int err;
|
|
|
|
if (!(vops->flags & XE_VMA_OPS_FLAG_HAS_SVM_PREFETCH))
|
|
return 0;
|
|
|
|
list_for_each_entry(op, &vops->list, link) {
|
|
if (op->base.op == DRM_GPUVA_OP_PREFETCH) {
|
|
err = prefetch_ranges(vm, op);
|
|
if (err)
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vm_bind_ioctl_ops_lock_and_prep(struct drm_exec *exec,
|
|
struct xe_vm *vm,
|
|
struct xe_vma_ops *vops)
|
|
{
|
|
struct xe_vma_op *op;
|
|
int err;
|
|
|
|
err = drm_exec_lock_obj(exec, xe_vm_obj(vm));
|
|
if (err)
|
|
return err;
|
|
|
|
list_for_each_entry(op, &vops->list, link) {
|
|
err = op_lock_and_prep(exec, vm, vops, op);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
#ifdef TEST_VM_OPS_ERROR
|
|
if (vops->inject_error &&
|
|
vm->xe->vm_inject_error_position == FORCE_OP_ERROR_LOCK)
|
|
return -ENOSPC;
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void op_trace(struct xe_vma_op *op)
|
|
{
|
|
switch (op->base.op) {
|
|
case DRM_GPUVA_OP_MAP:
|
|
trace_xe_vma_bind(op->map.vma);
|
|
break;
|
|
case DRM_GPUVA_OP_REMAP:
|
|
trace_xe_vma_unbind(gpuva_to_vma(op->base.remap.unmap->va));
|
|
if (op->remap.prev)
|
|
trace_xe_vma_bind(op->remap.prev);
|
|
if (op->remap.next)
|
|
trace_xe_vma_bind(op->remap.next);
|
|
break;
|
|
case DRM_GPUVA_OP_UNMAP:
|
|
trace_xe_vma_unbind(gpuva_to_vma(op->base.unmap.va));
|
|
break;
|
|
case DRM_GPUVA_OP_PREFETCH:
|
|
trace_xe_vma_bind(gpuva_to_vma(op->base.prefetch.va));
|
|
break;
|
|
case DRM_GPUVA_OP_DRIVER:
|
|
break;
|
|
default:
|
|
XE_WARN_ON("NOT POSSIBLE");
|
|
}
|
|
}
|
|
|
|
static void trace_xe_vm_ops_execute(struct xe_vma_ops *vops)
|
|
{
|
|
struct xe_vma_op *op;
|
|
|
|
list_for_each_entry(op, &vops->list, link)
|
|
op_trace(op);
|
|
}
|
|
|
|
static int vm_ops_setup_tile_args(struct xe_vm *vm, struct xe_vma_ops *vops)
|
|
{
|
|
struct xe_exec_queue *q = vops->q;
|
|
struct xe_tile *tile;
|
|
int number_tiles = 0;
|
|
u8 id;
|
|
|
|
for_each_tile(tile, vm->xe, id) {
|
|
if (vops->pt_update_ops[id].num_ops)
|
|
++number_tiles;
|
|
|
|
if (vops->pt_update_ops[id].q)
|
|
continue;
|
|
|
|
if (q) {
|
|
vops->pt_update_ops[id].q = q;
|
|
if (vm->pt_root[id] && !list_empty(&q->multi_gt_list))
|
|
q = list_next_entry(q, multi_gt_list);
|
|
} else {
|
|
vops->pt_update_ops[id].q = vm->q[id];
|
|
}
|
|
}
|
|
|
|
return number_tiles;
|
|
}
|
|
|
|
static struct dma_fence *ops_execute(struct xe_vm *vm,
|
|
struct xe_vma_ops *vops)
|
|
{
|
|
struct xe_tile *tile;
|
|
struct dma_fence *fence = NULL;
|
|
struct dma_fence **fences = NULL;
|
|
struct dma_fence_array *cf = NULL;
|
|
int number_tiles = 0, current_fence = 0, n_fence = 0, err;
|
|
u8 id;
|
|
|
|
number_tiles = vm_ops_setup_tile_args(vm, vops);
|
|
if (number_tiles == 0)
|
|
return ERR_PTR(-ENODATA);
|
|
|
|
if (vops->flags & XE_VMA_OPS_FLAG_SKIP_TLB_WAIT) {
|
|
for_each_tile(tile, vm->xe, id)
|
|
++n_fence;
|
|
} else {
|
|
for_each_tile(tile, vm->xe, id)
|
|
n_fence += (1 + XE_MAX_GT_PER_TILE);
|
|
}
|
|
|
|
fences = kmalloc_array(n_fence, sizeof(*fences), GFP_KERNEL);
|
|
if (!fences) {
|
|
fence = ERR_PTR(-ENOMEM);
|
|
goto err_trace;
|
|
}
|
|
|
|
cf = dma_fence_array_alloc(n_fence);
|
|
if (!cf) {
|
|
fence = ERR_PTR(-ENOMEM);
|
|
goto err_out;
|
|
}
|
|
|
|
for_each_tile(tile, vm->xe, id) {
|
|
if (!vops->pt_update_ops[id].num_ops)
|
|
continue;
|
|
|
|
err = xe_pt_update_ops_prepare(tile, vops);
|
|
if (err) {
|
|
fence = ERR_PTR(err);
|
|
goto err_out;
|
|
}
|
|
}
|
|
|
|
trace_xe_vm_ops_execute(vops);
|
|
|
|
for_each_tile(tile, vm->xe, id) {
|
|
struct xe_exec_queue *q = vops->pt_update_ops[tile->id].q;
|
|
int i;
|
|
|
|
fence = NULL;
|
|
if (!vops->pt_update_ops[id].num_ops)
|
|
goto collect_fences;
|
|
|
|
fence = xe_pt_update_ops_run(tile, vops);
|
|
if (IS_ERR(fence))
|
|
goto err_out;
|
|
|
|
collect_fences:
|
|
fences[current_fence++] = fence ?: dma_fence_get_stub();
|
|
if (vops->flags & XE_VMA_OPS_FLAG_SKIP_TLB_WAIT)
|
|
continue;
|
|
|
|
xe_migrate_job_lock(tile->migrate, q);
|
|
for_each_tlb_inval(i)
|
|
fences[current_fence++] =
|
|
xe_exec_queue_tlb_inval_last_fence_get(q, vm, i);
|
|
xe_migrate_job_unlock(tile->migrate, q);
|
|
}
|
|
|
|
xe_assert(vm->xe, current_fence == n_fence);
|
|
dma_fence_array_init(cf, n_fence, fences, dma_fence_context_alloc(1),
|
|
1, false);
|
|
fence = &cf->base;
|
|
|
|
for_each_tile(tile, vm->xe, id) {
|
|
if (!vops->pt_update_ops[id].num_ops)
|
|
continue;
|
|
|
|
xe_pt_update_ops_fini(tile, vops);
|
|
}
|
|
|
|
return fence;
|
|
|
|
err_out:
|
|
for_each_tile(tile, vm->xe, id) {
|
|
if (!vops->pt_update_ops[id].num_ops)
|
|
continue;
|
|
|
|
xe_pt_update_ops_abort(tile, vops);
|
|
}
|
|
while (current_fence)
|
|
dma_fence_put(fences[--current_fence]);
|
|
kfree(fences);
|
|
kfree(cf);
|
|
|
|
err_trace:
|
|
trace_xe_vm_ops_fail(vm);
|
|
return fence;
|
|
}
|
|
|
|
static void vma_add_ufence(struct xe_vma *vma, struct xe_user_fence *ufence)
|
|
{
|
|
if (vma->ufence)
|
|
xe_sync_ufence_put(vma->ufence);
|
|
vma->ufence = __xe_sync_ufence_get(ufence);
|
|
}
|
|
|
|
static void op_add_ufence(struct xe_vm *vm, struct xe_vma_op *op,
|
|
struct xe_user_fence *ufence)
|
|
{
|
|
switch (op->base.op) {
|
|
case DRM_GPUVA_OP_MAP:
|
|
vma_add_ufence(op->map.vma, ufence);
|
|
break;
|
|
case DRM_GPUVA_OP_REMAP:
|
|
if (op->remap.prev)
|
|
vma_add_ufence(op->remap.prev, ufence);
|
|
if (op->remap.next)
|
|
vma_add_ufence(op->remap.next, ufence);
|
|
break;
|
|
case DRM_GPUVA_OP_UNMAP:
|
|
break;
|
|
case DRM_GPUVA_OP_PREFETCH:
|
|
vma_add_ufence(gpuva_to_vma(op->base.prefetch.va), ufence);
|
|
break;
|
|
default:
|
|
drm_warn(&vm->xe->drm, "NOT POSSIBLE");
|
|
}
|
|
}
|
|
|
|
static void vm_bind_ioctl_ops_fini(struct xe_vm *vm, struct xe_vma_ops *vops,
|
|
struct dma_fence *fence)
|
|
{
|
|
struct xe_user_fence *ufence;
|
|
struct xe_vma_op *op;
|
|
int i;
|
|
|
|
ufence = find_ufence_get(vops->syncs, vops->num_syncs);
|
|
list_for_each_entry(op, &vops->list, link) {
|
|
if (ufence)
|
|
op_add_ufence(vm, op, ufence);
|
|
|
|
if (op->base.op == DRM_GPUVA_OP_UNMAP)
|
|
xe_vma_destroy(gpuva_to_vma(op->base.unmap.va), fence);
|
|
else if (op->base.op == DRM_GPUVA_OP_REMAP)
|
|
xe_vma_destroy(gpuva_to_vma(op->base.remap.unmap->va),
|
|
fence);
|
|
}
|
|
if (ufence)
|
|
xe_sync_ufence_put(ufence);
|
|
if (fence) {
|
|
for (i = 0; i < vops->num_syncs; i++)
|
|
xe_sync_entry_signal(vops->syncs + i, fence);
|
|
}
|
|
}
|
|
|
|
static struct dma_fence *vm_bind_ioctl_ops_execute(struct xe_vm *vm,
|
|
struct xe_vma_ops *vops)
|
|
{
|
|
struct xe_validation_ctx ctx;
|
|
struct drm_exec exec;
|
|
struct dma_fence *fence;
|
|
int err = 0;
|
|
|
|
lockdep_assert_held_write(&vm->lock);
|
|
|
|
xe_validation_guard(&ctx, &vm->xe->val, &exec,
|
|
((struct xe_val_flags) {
|
|
.interruptible = true,
|
|
.exec_ignore_duplicates = true,
|
|
}), err) {
|
|
err = vm_bind_ioctl_ops_lock_and_prep(&exec, vm, vops);
|
|
drm_exec_retry_on_contention(&exec);
|
|
xe_validation_retry_on_oom(&ctx, &err);
|
|
if (err)
|
|
return ERR_PTR(err);
|
|
|
|
xe_vm_set_validation_exec(vm, &exec);
|
|
fence = ops_execute(vm, vops);
|
|
xe_vm_set_validation_exec(vm, NULL);
|
|
if (IS_ERR(fence)) {
|
|
if (PTR_ERR(fence) == -ENODATA)
|
|
vm_bind_ioctl_ops_fini(vm, vops, NULL);
|
|
return fence;
|
|
}
|
|
|
|
vm_bind_ioctl_ops_fini(vm, vops, fence);
|
|
}
|
|
|
|
return err ? ERR_PTR(err) : fence;
|
|
}
|
|
ALLOW_ERROR_INJECTION(vm_bind_ioctl_ops_execute, ERRNO);
|
|
|
|
#define SUPPORTED_FLAGS_STUB \
|
|
(DRM_XE_VM_BIND_FLAG_READONLY | \
|
|
DRM_XE_VM_BIND_FLAG_IMMEDIATE | \
|
|
DRM_XE_VM_BIND_FLAG_NULL | \
|
|
DRM_XE_VM_BIND_FLAG_DUMPABLE | \
|
|
DRM_XE_VM_BIND_FLAG_CHECK_PXP | \
|
|
DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR | \
|
|
DRM_XE_VM_BIND_FLAG_MADVISE_AUTORESET)
|
|
|
|
#ifdef TEST_VM_OPS_ERROR
|
|
#define SUPPORTED_FLAGS (SUPPORTED_FLAGS_STUB | FORCE_OP_ERROR)
|
|
#else
|
|
#define SUPPORTED_FLAGS SUPPORTED_FLAGS_STUB
|
|
#endif
|
|
|
|
#define XE_64K_PAGE_MASK 0xffffull
|
|
#define ALL_DRM_XE_SYNCS_FLAGS (DRM_XE_SYNCS_FLAG_WAIT_FOR_OP)
|
|
|
|
static int vm_bind_ioctl_check_args(struct xe_device *xe, struct xe_vm *vm,
|
|
struct drm_xe_vm_bind *args,
|
|
struct drm_xe_vm_bind_op **bind_ops)
|
|
{
|
|
int err;
|
|
int i;
|
|
|
|
if (XE_IOCTL_DBG(xe, args->pad || args->pad2) ||
|
|
XE_IOCTL_DBG(xe, args->reserved[0] || args->reserved[1]))
|
|
return -EINVAL;
|
|
|
|
if (XE_IOCTL_DBG(xe, args->extensions))
|
|
return -EINVAL;
|
|
|
|
if (args->num_binds > 1) {
|
|
u64 __user *bind_user =
|
|
u64_to_user_ptr(args->vector_of_binds);
|
|
|
|
*bind_ops = kvmalloc_array(args->num_binds,
|
|
sizeof(struct drm_xe_vm_bind_op),
|
|
GFP_KERNEL | __GFP_ACCOUNT |
|
|
__GFP_RETRY_MAYFAIL | __GFP_NOWARN);
|
|
if (!*bind_ops)
|
|
return args->num_binds > 1 ? -ENOBUFS : -ENOMEM;
|
|
|
|
err = copy_from_user(*bind_ops, bind_user,
|
|
sizeof(struct drm_xe_vm_bind_op) *
|
|
args->num_binds);
|
|
if (XE_IOCTL_DBG(xe, err)) {
|
|
err = -EFAULT;
|
|
goto free_bind_ops;
|
|
}
|
|
} else {
|
|
*bind_ops = &args->bind;
|
|
}
|
|
|
|
for (i = 0; i < args->num_binds; ++i) {
|
|
u64 range = (*bind_ops)[i].range;
|
|
u64 addr = (*bind_ops)[i].addr;
|
|
u32 op = (*bind_ops)[i].op;
|
|
u32 flags = (*bind_ops)[i].flags;
|
|
u32 obj = (*bind_ops)[i].obj;
|
|
u64 obj_offset = (*bind_ops)[i].obj_offset;
|
|
u32 prefetch_region = (*bind_ops)[i].prefetch_mem_region_instance;
|
|
bool is_null = flags & DRM_XE_VM_BIND_FLAG_NULL;
|
|
bool is_cpu_addr_mirror = flags &
|
|
DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR;
|
|
u16 pat_index = (*bind_ops)[i].pat_index;
|
|
u16 coh_mode;
|
|
|
|
if (XE_IOCTL_DBG(xe, is_cpu_addr_mirror &&
|
|
(!xe_vm_in_fault_mode(vm) ||
|
|
!IS_ENABLED(CONFIG_DRM_XE_GPUSVM)))) {
|
|
err = -EINVAL;
|
|
goto free_bind_ops;
|
|
}
|
|
|
|
if (XE_IOCTL_DBG(xe, pat_index >= xe->pat.n_entries)) {
|
|
err = -EINVAL;
|
|
goto free_bind_ops;
|
|
}
|
|
|
|
pat_index = array_index_nospec(pat_index, xe->pat.n_entries);
|
|
(*bind_ops)[i].pat_index = pat_index;
|
|
coh_mode = xe_pat_index_get_coh_mode(xe, pat_index);
|
|
if (XE_IOCTL_DBG(xe, !coh_mode)) { /* hw reserved */
|
|
err = -EINVAL;
|
|
goto free_bind_ops;
|
|
}
|
|
|
|
if (XE_WARN_ON(coh_mode > XE_COH_AT_LEAST_1WAY)) {
|
|
err = -EINVAL;
|
|
goto free_bind_ops;
|
|
}
|
|
|
|
if (XE_IOCTL_DBG(xe, op > DRM_XE_VM_BIND_OP_PREFETCH) ||
|
|
XE_IOCTL_DBG(xe, flags & ~SUPPORTED_FLAGS) ||
|
|
XE_IOCTL_DBG(xe, obj && (is_null || is_cpu_addr_mirror)) ||
|
|
XE_IOCTL_DBG(xe, obj_offset && (is_null ||
|
|
is_cpu_addr_mirror)) ||
|
|
XE_IOCTL_DBG(xe, op != DRM_XE_VM_BIND_OP_MAP &&
|
|
(is_null || is_cpu_addr_mirror)) ||
|
|
XE_IOCTL_DBG(xe, !obj &&
|
|
op == DRM_XE_VM_BIND_OP_MAP &&
|
|
!is_null && !is_cpu_addr_mirror) ||
|
|
XE_IOCTL_DBG(xe, !obj &&
|
|
op == DRM_XE_VM_BIND_OP_UNMAP_ALL) ||
|
|
XE_IOCTL_DBG(xe, addr &&
|
|
op == DRM_XE_VM_BIND_OP_UNMAP_ALL) ||
|
|
XE_IOCTL_DBG(xe, range &&
|
|
op == DRM_XE_VM_BIND_OP_UNMAP_ALL) ||
|
|
XE_IOCTL_DBG(xe, obj &&
|
|
op == DRM_XE_VM_BIND_OP_MAP_USERPTR) ||
|
|
XE_IOCTL_DBG(xe, coh_mode == XE_COH_NONE &&
|
|
op == DRM_XE_VM_BIND_OP_MAP_USERPTR) ||
|
|
XE_IOCTL_DBG(xe, op == DRM_XE_VM_BIND_OP_MAP_USERPTR &&
|
|
!IS_ENABLED(CONFIG_DRM_GPUSVM)) ||
|
|
XE_IOCTL_DBG(xe, obj &&
|
|
op == DRM_XE_VM_BIND_OP_PREFETCH) ||
|
|
XE_IOCTL_DBG(xe, prefetch_region &&
|
|
op != DRM_XE_VM_BIND_OP_PREFETCH) ||
|
|
XE_IOCTL_DBG(xe, (prefetch_region != DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC &&
|
|
/* Guard against undefined shift in BIT(prefetch_region) */
|
|
(prefetch_region >= (sizeof(xe->info.mem_region_mask) * 8) ||
|
|
!(BIT(prefetch_region) & xe->info.mem_region_mask)))) ||
|
|
XE_IOCTL_DBG(xe, obj &&
|
|
op == DRM_XE_VM_BIND_OP_UNMAP) ||
|
|
XE_IOCTL_DBG(xe, (flags & DRM_XE_VM_BIND_FLAG_MADVISE_AUTORESET) &&
|
|
(!is_cpu_addr_mirror || op != DRM_XE_VM_BIND_OP_MAP))) {
|
|
err = -EINVAL;
|
|
goto free_bind_ops;
|
|
}
|
|
|
|
if (XE_IOCTL_DBG(xe, obj_offset & ~PAGE_MASK) ||
|
|
XE_IOCTL_DBG(xe, addr & ~PAGE_MASK) ||
|
|
XE_IOCTL_DBG(xe, range & ~PAGE_MASK) ||
|
|
XE_IOCTL_DBG(xe, !range &&
|
|
op != DRM_XE_VM_BIND_OP_UNMAP_ALL)) {
|
|
err = -EINVAL;
|
|
goto free_bind_ops;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
free_bind_ops:
|
|
if (args->num_binds > 1)
|
|
kvfree(*bind_ops);
|
|
*bind_ops = NULL;
|
|
return err;
|
|
}
|
|
|
|
static int vm_bind_ioctl_signal_fences(struct xe_vm *vm,
|
|
struct xe_exec_queue *q,
|
|
struct xe_sync_entry *syncs,
|
|
int num_syncs)
|
|
{
|
|
struct dma_fence *fence = NULL;
|
|
int i, err = 0;
|
|
|
|
if (num_syncs) {
|
|
fence = xe_sync_in_fence_get(syncs, num_syncs,
|
|
to_wait_exec_queue(vm, q), vm);
|
|
if (IS_ERR(fence))
|
|
return PTR_ERR(fence);
|
|
|
|
for (i = 0; i < num_syncs; i++)
|
|
xe_sync_entry_signal(&syncs[i], fence);
|
|
}
|
|
|
|
dma_fence_put(fence);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void xe_vma_ops_init(struct xe_vma_ops *vops, struct xe_vm *vm,
|
|
struct xe_exec_queue *q,
|
|
struct xe_sync_entry *syncs, u32 num_syncs)
|
|
{
|
|
memset(vops, 0, sizeof(*vops));
|
|
INIT_LIST_HEAD(&vops->list);
|
|
vops->vm = vm;
|
|
vops->q = q;
|
|
vops->syncs = syncs;
|
|
vops->num_syncs = num_syncs;
|
|
vops->flags = 0;
|
|
}
|
|
|
|
static int xe_vm_bind_ioctl_validate_bo(struct xe_device *xe, struct xe_bo *bo,
|
|
u64 addr, u64 range, u64 obj_offset,
|
|
u16 pat_index, u32 op, u32 bind_flags)
|
|
{
|
|
u16 coh_mode;
|
|
|
|
if (XE_IOCTL_DBG(xe, range > xe_bo_size(bo)) ||
|
|
XE_IOCTL_DBG(xe, obj_offset >
|
|
xe_bo_size(bo) - range)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Some platforms require 64k VM_BIND alignment,
|
|
* specifically those with XE_VRAM_FLAGS_NEED64K.
|
|
*
|
|
* Other platforms may have BO's set to 64k physical placement,
|
|
* but can be mapped at 4k offsets anyway. This check is only
|
|
* there for the former case.
|
|
*/
|
|
if ((bo->flags & XE_BO_FLAG_INTERNAL_64K) &&
|
|
(xe->info.vram_flags & XE_VRAM_FLAGS_NEED64K)) {
|
|
if (XE_IOCTL_DBG(xe, obj_offset &
|
|
XE_64K_PAGE_MASK) ||
|
|
XE_IOCTL_DBG(xe, addr & XE_64K_PAGE_MASK) ||
|
|
XE_IOCTL_DBG(xe, range & XE_64K_PAGE_MASK)) {
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
coh_mode = xe_pat_index_get_coh_mode(xe, pat_index);
|
|
if (bo->cpu_caching) {
|
|
if (XE_IOCTL_DBG(xe, coh_mode == XE_COH_NONE &&
|
|
bo->cpu_caching == DRM_XE_GEM_CPU_CACHING_WB)) {
|
|
return -EINVAL;
|
|
}
|
|
} else if (XE_IOCTL_DBG(xe, coh_mode == XE_COH_NONE)) {
|
|
/*
|
|
* Imported dma-buf from a different device should
|
|
* require 1way or 2way coherency since we don't know
|
|
* how it was mapped on the CPU. Just assume is it
|
|
* potentially cached on CPU side.
|
|
*/
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* If a BO is protected it can only be mapped if the key is still valid */
|
|
if ((bind_flags & DRM_XE_VM_BIND_FLAG_CHECK_PXP) && xe_bo_is_protected(bo) &&
|
|
op != DRM_XE_VM_BIND_OP_UNMAP && op != DRM_XE_VM_BIND_OP_UNMAP_ALL)
|
|
if (XE_IOCTL_DBG(xe, xe_pxp_bo_key_check(xe->pxp, bo) != 0))
|
|
return -ENOEXEC;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int xe_vm_bind_ioctl(struct drm_device *dev, void *data, struct drm_file *file)
|
|
{
|
|
struct xe_device *xe = to_xe_device(dev);
|
|
struct xe_file *xef = to_xe_file(file);
|
|
struct drm_xe_vm_bind *args = data;
|
|
struct drm_xe_sync __user *syncs_user;
|
|
struct xe_bo **bos = NULL;
|
|
struct drm_gpuva_ops **ops = NULL;
|
|
struct xe_vm *vm;
|
|
struct xe_exec_queue *q = NULL;
|
|
u32 num_syncs, num_ufence = 0;
|
|
struct xe_sync_entry *syncs = NULL;
|
|
struct drm_xe_vm_bind_op *bind_ops = NULL;
|
|
struct xe_vma_ops vops;
|
|
struct dma_fence *fence;
|
|
int err;
|
|
int i;
|
|
|
|
vm = xe_vm_lookup(xef, args->vm_id);
|
|
if (XE_IOCTL_DBG(xe, !vm))
|
|
return -EINVAL;
|
|
|
|
err = vm_bind_ioctl_check_args(xe, vm, args, &bind_ops);
|
|
if (err)
|
|
goto put_vm;
|
|
|
|
if (args->exec_queue_id) {
|
|
q = xe_exec_queue_lookup(xef, args->exec_queue_id);
|
|
if (XE_IOCTL_DBG(xe, !q)) {
|
|
err = -ENOENT;
|
|
goto free_bind_ops;
|
|
}
|
|
|
|
if (XE_IOCTL_DBG(xe, !(q->flags & EXEC_QUEUE_FLAG_VM))) {
|
|
err = -EINVAL;
|
|
goto put_exec_queue;
|
|
}
|
|
}
|
|
|
|
/* Ensure all UNMAPs visible */
|
|
xe_svm_flush(vm);
|
|
|
|
err = down_write_killable(&vm->lock);
|
|
if (err)
|
|
goto put_exec_queue;
|
|
|
|
if (XE_IOCTL_DBG(xe, xe_vm_is_closed_or_banned(vm))) {
|
|
err = -ENOENT;
|
|
goto release_vm_lock;
|
|
}
|
|
|
|
for (i = 0; i < args->num_binds; ++i) {
|
|
u64 range = bind_ops[i].range;
|
|
u64 addr = bind_ops[i].addr;
|
|
|
|
if (XE_IOCTL_DBG(xe, range > vm->size) ||
|
|
XE_IOCTL_DBG(xe, addr > vm->size - range)) {
|
|
err = -EINVAL;
|
|
goto release_vm_lock;
|
|
}
|
|
}
|
|
|
|
if (args->num_binds) {
|
|
bos = kvcalloc(args->num_binds, sizeof(*bos),
|
|
GFP_KERNEL | __GFP_ACCOUNT |
|
|
__GFP_RETRY_MAYFAIL | __GFP_NOWARN);
|
|
if (!bos) {
|
|
err = -ENOMEM;
|
|
goto release_vm_lock;
|
|
}
|
|
|
|
ops = kvcalloc(args->num_binds, sizeof(*ops),
|
|
GFP_KERNEL | __GFP_ACCOUNT |
|
|
__GFP_RETRY_MAYFAIL | __GFP_NOWARN);
|
|
if (!ops) {
|
|
err = -ENOMEM;
|
|
goto free_bos;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < args->num_binds; ++i) {
|
|
struct drm_gem_object *gem_obj;
|
|
u64 range = bind_ops[i].range;
|
|
u64 addr = bind_ops[i].addr;
|
|
u32 obj = bind_ops[i].obj;
|
|
u64 obj_offset = bind_ops[i].obj_offset;
|
|
u16 pat_index = bind_ops[i].pat_index;
|
|
u32 op = bind_ops[i].op;
|
|
u32 bind_flags = bind_ops[i].flags;
|
|
|
|
if (!obj)
|
|
continue;
|
|
|
|
gem_obj = drm_gem_object_lookup(file, obj);
|
|
if (XE_IOCTL_DBG(xe, !gem_obj)) {
|
|
err = -ENOENT;
|
|
goto put_obj;
|
|
}
|
|
bos[i] = gem_to_xe_bo(gem_obj);
|
|
|
|
err = xe_vm_bind_ioctl_validate_bo(xe, bos[i], addr, range,
|
|
obj_offset, pat_index, op,
|
|
bind_flags);
|
|
if (err)
|
|
goto put_obj;
|
|
}
|
|
|
|
if (args->num_syncs) {
|
|
syncs = kcalloc(args->num_syncs, sizeof(*syncs), GFP_KERNEL);
|
|
if (!syncs) {
|
|
err = -ENOMEM;
|
|
goto put_obj;
|
|
}
|
|
}
|
|
|
|
syncs_user = u64_to_user_ptr(args->syncs);
|
|
for (num_syncs = 0; num_syncs < args->num_syncs; num_syncs++) {
|
|
struct xe_exec_queue *__q = q ?: vm->q[0];
|
|
|
|
err = xe_sync_entry_parse(xe, xef, &syncs[num_syncs],
|
|
&syncs_user[num_syncs],
|
|
__q->ufence_syncobj,
|
|
++__q->ufence_timeline_value,
|
|
(xe_vm_in_lr_mode(vm) ?
|
|
SYNC_PARSE_FLAG_LR_MODE : 0) |
|
|
(!args->num_binds ?
|
|
SYNC_PARSE_FLAG_DISALLOW_USER_FENCE : 0));
|
|
if (err)
|
|
goto free_syncs;
|
|
|
|
if (xe_sync_is_ufence(&syncs[num_syncs]))
|
|
num_ufence++;
|
|
}
|
|
|
|
if (XE_IOCTL_DBG(xe, num_ufence > 1)) {
|
|
err = -EINVAL;
|
|
goto free_syncs;
|
|
}
|
|
|
|
if (!args->num_binds) {
|
|
err = -ENODATA;
|
|
goto free_syncs;
|
|
}
|
|
|
|
xe_vma_ops_init(&vops, vm, q, syncs, num_syncs);
|
|
if (args->num_binds > 1)
|
|
vops.flags |= XE_VMA_OPS_ARRAY_OF_BINDS;
|
|
for (i = 0; i < args->num_binds; ++i) {
|
|
u64 range = bind_ops[i].range;
|
|
u64 addr = bind_ops[i].addr;
|
|
u32 op = bind_ops[i].op;
|
|
u32 flags = bind_ops[i].flags;
|
|
u64 obj_offset = bind_ops[i].obj_offset;
|
|
u32 prefetch_region = bind_ops[i].prefetch_mem_region_instance;
|
|
u16 pat_index = bind_ops[i].pat_index;
|
|
|
|
ops[i] = vm_bind_ioctl_ops_create(vm, &vops, bos[i], obj_offset,
|
|
addr, range, op, flags,
|
|
prefetch_region, pat_index);
|
|
if (IS_ERR(ops[i])) {
|
|
err = PTR_ERR(ops[i]);
|
|
ops[i] = NULL;
|
|
goto unwind_ops;
|
|
}
|
|
|
|
err = vm_bind_ioctl_ops_parse(vm, ops[i], &vops);
|
|
if (err)
|
|
goto unwind_ops;
|
|
|
|
#ifdef TEST_VM_OPS_ERROR
|
|
if (flags & FORCE_OP_ERROR) {
|
|
vops.inject_error = true;
|
|
vm->xe->vm_inject_error_position =
|
|
(vm->xe->vm_inject_error_position + 1) %
|
|
FORCE_OP_ERROR_COUNT;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Nothing to do */
|
|
if (list_empty(&vops.list)) {
|
|
err = -ENODATA;
|
|
goto unwind_ops;
|
|
}
|
|
|
|
err = xe_vma_ops_alloc(&vops, args->num_binds > 1);
|
|
if (err)
|
|
goto unwind_ops;
|
|
|
|
err = vm_bind_ioctl_ops_prefetch_ranges(vm, &vops);
|
|
if (err)
|
|
goto unwind_ops;
|
|
|
|
fence = vm_bind_ioctl_ops_execute(vm, &vops);
|
|
if (IS_ERR(fence))
|
|
err = PTR_ERR(fence);
|
|
else
|
|
dma_fence_put(fence);
|
|
|
|
unwind_ops:
|
|
if (err && err != -ENODATA)
|
|
vm_bind_ioctl_ops_unwind(vm, ops, args->num_binds);
|
|
xe_vma_ops_fini(&vops);
|
|
for (i = args->num_binds - 1; i >= 0; --i)
|
|
if (ops[i])
|
|
drm_gpuva_ops_free(&vm->gpuvm, ops[i]);
|
|
free_syncs:
|
|
if (err == -ENODATA)
|
|
err = vm_bind_ioctl_signal_fences(vm, q, syncs, num_syncs);
|
|
while (num_syncs--)
|
|
xe_sync_entry_cleanup(&syncs[num_syncs]);
|
|
|
|
kfree(syncs);
|
|
put_obj:
|
|
for (i = 0; i < args->num_binds; ++i)
|
|
xe_bo_put(bos[i]);
|
|
|
|
kvfree(ops);
|
|
free_bos:
|
|
kvfree(bos);
|
|
release_vm_lock:
|
|
up_write(&vm->lock);
|
|
put_exec_queue:
|
|
if (q)
|
|
xe_exec_queue_put(q);
|
|
free_bind_ops:
|
|
if (args->num_binds > 1)
|
|
kvfree(bind_ops);
|
|
put_vm:
|
|
xe_vm_put(vm);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* xe_vm_bind_kernel_bo - bind a kernel BO to a VM
|
|
* @vm: VM to bind the BO to
|
|
* @bo: BO to bind
|
|
* @q: exec queue to use for the bind (optional)
|
|
* @addr: address at which to bind the BO
|
|
* @cache_lvl: PAT cache level to use
|
|
*
|
|
* Execute a VM bind map operation on a kernel-owned BO to bind it into a
|
|
* kernel-owned VM.
|
|
*
|
|
* Returns a dma_fence to track the binding completion if the job to do so was
|
|
* successfully submitted, an error pointer otherwise.
|
|
*/
|
|
struct dma_fence *xe_vm_bind_kernel_bo(struct xe_vm *vm, struct xe_bo *bo,
|
|
struct xe_exec_queue *q, u64 addr,
|
|
enum xe_cache_level cache_lvl)
|
|
{
|
|
struct xe_vma_ops vops;
|
|
struct drm_gpuva_ops *ops = NULL;
|
|
struct dma_fence *fence;
|
|
int err;
|
|
|
|
xe_bo_get(bo);
|
|
xe_vm_get(vm);
|
|
if (q)
|
|
xe_exec_queue_get(q);
|
|
|
|
down_write(&vm->lock);
|
|
|
|
xe_vma_ops_init(&vops, vm, q, NULL, 0);
|
|
|
|
ops = vm_bind_ioctl_ops_create(vm, &vops, bo, 0, addr, xe_bo_size(bo),
|
|
DRM_XE_VM_BIND_OP_MAP, 0, 0,
|
|
vm->xe->pat.idx[cache_lvl]);
|
|
if (IS_ERR(ops)) {
|
|
err = PTR_ERR(ops);
|
|
goto release_vm_lock;
|
|
}
|
|
|
|
err = vm_bind_ioctl_ops_parse(vm, ops, &vops);
|
|
if (err)
|
|
goto release_vm_lock;
|
|
|
|
xe_assert(vm->xe, !list_empty(&vops.list));
|
|
|
|
err = xe_vma_ops_alloc(&vops, false);
|
|
if (err)
|
|
goto unwind_ops;
|
|
|
|
fence = vm_bind_ioctl_ops_execute(vm, &vops);
|
|
if (IS_ERR(fence))
|
|
err = PTR_ERR(fence);
|
|
|
|
unwind_ops:
|
|
if (err && err != -ENODATA)
|
|
vm_bind_ioctl_ops_unwind(vm, &ops, 1);
|
|
|
|
xe_vma_ops_fini(&vops);
|
|
drm_gpuva_ops_free(&vm->gpuvm, ops);
|
|
|
|
release_vm_lock:
|
|
up_write(&vm->lock);
|
|
|
|
if (q)
|
|
xe_exec_queue_put(q);
|
|
xe_vm_put(vm);
|
|
xe_bo_put(bo);
|
|
|
|
if (err)
|
|
fence = ERR_PTR(err);
|
|
|
|
return fence;
|
|
}
|
|
|
|
/**
|
|
* xe_vm_lock() - Lock the vm's dma_resv object
|
|
* @vm: The struct xe_vm whose lock is to be locked
|
|
* @intr: Whether to perform any wait interruptible
|
|
*
|
|
* Return: 0 on success, -EINTR if @intr is true and the wait for a
|
|
* contended lock was interrupted. If @intr is false, the function
|
|
* always returns 0.
|
|
*/
|
|
int xe_vm_lock(struct xe_vm *vm, bool intr)
|
|
{
|
|
int ret;
|
|
|
|
if (intr)
|
|
ret = dma_resv_lock_interruptible(xe_vm_resv(vm), NULL);
|
|
else
|
|
ret = dma_resv_lock(xe_vm_resv(vm), NULL);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* xe_vm_unlock() - Unlock the vm's dma_resv object
|
|
* @vm: The struct xe_vm whose lock is to be released.
|
|
*
|
|
* Unlock a buffer object lock that was locked by xe_vm_lock().
|
|
*/
|
|
void xe_vm_unlock(struct xe_vm *vm)
|
|
{
|
|
dma_resv_unlock(xe_vm_resv(vm));
|
|
}
|
|
|
|
/**
|
|
* xe_vm_range_tilemask_tlb_inval - Issue a TLB invalidation on this tilemask for an
|
|
* address range
|
|
* @vm: The VM
|
|
* @start: start address
|
|
* @end: end address
|
|
* @tile_mask: mask for which gt's issue tlb invalidation
|
|
*
|
|
* Issue a range based TLB invalidation for gt's in tilemask
|
|
*
|
|
* Returns 0 for success, negative error code otherwise.
|
|
*/
|
|
int xe_vm_range_tilemask_tlb_inval(struct xe_vm *vm, u64 start,
|
|
u64 end, u8 tile_mask)
|
|
{
|
|
struct xe_tlb_inval_fence
|
|
fence[XE_MAX_TILES_PER_DEVICE * XE_MAX_GT_PER_TILE];
|
|
struct xe_tile *tile;
|
|
u32 fence_id = 0;
|
|
u8 id;
|
|
int err;
|
|
|
|
if (!tile_mask)
|
|
return 0;
|
|
|
|
for_each_tile(tile, vm->xe, id) {
|
|
if (!(tile_mask & BIT(id)))
|
|
continue;
|
|
|
|
xe_tlb_inval_fence_init(&tile->primary_gt->tlb_inval,
|
|
&fence[fence_id], true);
|
|
|
|
err = xe_tlb_inval_range(&tile->primary_gt->tlb_inval,
|
|
&fence[fence_id], start, end,
|
|
vm->usm.asid);
|
|
if (err)
|
|
goto wait;
|
|
++fence_id;
|
|
|
|
if (!tile->media_gt)
|
|
continue;
|
|
|
|
xe_tlb_inval_fence_init(&tile->media_gt->tlb_inval,
|
|
&fence[fence_id], true);
|
|
|
|
err = xe_tlb_inval_range(&tile->media_gt->tlb_inval,
|
|
&fence[fence_id], start, end,
|
|
vm->usm.asid);
|
|
if (err)
|
|
goto wait;
|
|
++fence_id;
|
|
}
|
|
|
|
wait:
|
|
for (id = 0; id < fence_id; ++id)
|
|
xe_tlb_inval_fence_wait(&fence[id]);
|
|
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* xe_vm_invalidate_vma - invalidate GPU mappings for VMA without a lock
|
|
* @vma: VMA to invalidate
|
|
*
|
|
* Walks a list of page tables leaves which it memset the entries owned by this
|
|
* VMA to zero, invalidates the TLBs, and block until TLBs invalidation is
|
|
* complete.
|
|
*
|
|
* Returns 0 for success, negative error code otherwise.
|
|
*/
|
|
int xe_vm_invalidate_vma(struct xe_vma *vma)
|
|
{
|
|
struct xe_device *xe = xe_vma_vm(vma)->xe;
|
|
struct xe_vm *vm = xe_vma_vm(vma);
|
|
struct xe_tile *tile;
|
|
u8 tile_mask = 0;
|
|
int ret = 0;
|
|
u8 id;
|
|
|
|
xe_assert(xe, !xe_vma_is_null(vma));
|
|
xe_assert(xe, !xe_vma_is_cpu_addr_mirror(vma));
|
|
trace_xe_vma_invalidate(vma);
|
|
|
|
vm_dbg(&vm->xe->drm,
|
|
"INVALIDATE: addr=0x%016llx, range=0x%016llx",
|
|
xe_vma_start(vma), xe_vma_size(vma));
|
|
|
|
/*
|
|
* Check that we don't race with page-table updates, tile_invalidated
|
|
* update is safe
|
|
*/
|
|
if (IS_ENABLED(CONFIG_PROVE_LOCKING)) {
|
|
if (xe_vma_is_userptr(vma)) {
|
|
lockdep_assert(lockdep_is_held_type(&vm->svm.gpusvm.notifier_lock, 0) ||
|
|
(lockdep_is_held_type(&vm->svm.gpusvm.notifier_lock, 1) &&
|
|
lockdep_is_held(&xe_vm_resv(vm)->lock.base)));
|
|
|
|
WARN_ON_ONCE(!mmu_interval_check_retry
|
|
(&to_userptr_vma(vma)->userptr.notifier,
|
|
to_userptr_vma(vma)->userptr.pages.notifier_seq));
|
|
WARN_ON_ONCE(!dma_resv_test_signaled(xe_vm_resv(vm),
|
|
DMA_RESV_USAGE_BOOKKEEP));
|
|
|
|
} else {
|
|
xe_bo_assert_held(xe_vma_bo(vma));
|
|
}
|
|
}
|
|
|
|
for_each_tile(tile, xe, id)
|
|
if (xe_pt_zap_ptes(tile, vma))
|
|
tile_mask |= BIT(id);
|
|
|
|
xe_device_wmb(xe);
|
|
|
|
ret = xe_vm_range_tilemask_tlb_inval(xe_vma_vm(vma), xe_vma_start(vma),
|
|
xe_vma_end(vma), tile_mask);
|
|
|
|
/* WRITE_ONCE pairs with READ_ONCE in xe_vm_has_valid_gpu_mapping() */
|
|
WRITE_ONCE(vma->tile_invalidated, vma->tile_mask);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int xe_vm_validate_protected(struct xe_vm *vm)
|
|
{
|
|
struct drm_gpuva *gpuva;
|
|
int err = 0;
|
|
|
|
if (!vm)
|
|
return -ENODEV;
|
|
|
|
mutex_lock(&vm->snap_mutex);
|
|
|
|
drm_gpuvm_for_each_va(gpuva, &vm->gpuvm) {
|
|
struct xe_vma *vma = gpuva_to_vma(gpuva);
|
|
struct xe_bo *bo = vma->gpuva.gem.obj ?
|
|
gem_to_xe_bo(vma->gpuva.gem.obj) : NULL;
|
|
|
|
if (!bo)
|
|
continue;
|
|
|
|
if (xe_bo_is_protected(bo)) {
|
|
err = xe_pxp_bo_key_check(vm->xe->pxp, bo);
|
|
if (err)
|
|
break;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&vm->snap_mutex);
|
|
return err;
|
|
}
|
|
|
|
struct xe_vm_snapshot {
|
|
unsigned long num_snaps;
|
|
struct {
|
|
u64 ofs, bo_ofs;
|
|
unsigned long len;
|
|
struct xe_bo *bo;
|
|
void *data;
|
|
struct mm_struct *mm;
|
|
} snap[];
|
|
};
|
|
|
|
struct xe_vm_snapshot *xe_vm_snapshot_capture(struct xe_vm *vm)
|
|
{
|
|
unsigned long num_snaps = 0, i;
|
|
struct xe_vm_snapshot *snap = NULL;
|
|
struct drm_gpuva *gpuva;
|
|
|
|
if (!vm)
|
|
return NULL;
|
|
|
|
mutex_lock(&vm->snap_mutex);
|
|
drm_gpuvm_for_each_va(gpuva, &vm->gpuvm) {
|
|
if (gpuva->flags & XE_VMA_DUMPABLE)
|
|
num_snaps++;
|
|
}
|
|
|
|
if (num_snaps)
|
|
snap = kvzalloc(offsetof(struct xe_vm_snapshot, snap[num_snaps]), GFP_NOWAIT);
|
|
if (!snap) {
|
|
snap = num_snaps ? ERR_PTR(-ENOMEM) : ERR_PTR(-ENODEV);
|
|
goto out_unlock;
|
|
}
|
|
|
|
snap->num_snaps = num_snaps;
|
|
i = 0;
|
|
drm_gpuvm_for_each_va(gpuva, &vm->gpuvm) {
|
|
struct xe_vma *vma = gpuva_to_vma(gpuva);
|
|
struct xe_bo *bo = vma->gpuva.gem.obj ?
|
|
gem_to_xe_bo(vma->gpuva.gem.obj) : NULL;
|
|
|
|
if (!(gpuva->flags & XE_VMA_DUMPABLE))
|
|
continue;
|
|
|
|
snap->snap[i].ofs = xe_vma_start(vma);
|
|
snap->snap[i].len = xe_vma_size(vma);
|
|
if (bo) {
|
|
snap->snap[i].bo = xe_bo_get(bo);
|
|
snap->snap[i].bo_ofs = xe_vma_bo_offset(vma);
|
|
} else if (xe_vma_is_userptr(vma)) {
|
|
struct mm_struct *mm =
|
|
to_userptr_vma(vma)->userptr.notifier.mm;
|
|
|
|
if (mmget_not_zero(mm))
|
|
snap->snap[i].mm = mm;
|
|
else
|
|
snap->snap[i].data = ERR_PTR(-EFAULT);
|
|
|
|
snap->snap[i].bo_ofs = xe_vma_userptr(vma);
|
|
} else {
|
|
snap->snap[i].data = ERR_PTR(-ENOENT);
|
|
}
|
|
i++;
|
|
}
|
|
|
|
out_unlock:
|
|
mutex_unlock(&vm->snap_mutex);
|
|
return snap;
|
|
}
|
|
|
|
void xe_vm_snapshot_capture_delayed(struct xe_vm_snapshot *snap)
|
|
{
|
|
if (IS_ERR_OR_NULL(snap))
|
|
return;
|
|
|
|
for (int i = 0; i < snap->num_snaps; i++) {
|
|
struct xe_bo *bo = snap->snap[i].bo;
|
|
int err;
|
|
|
|
if (IS_ERR(snap->snap[i].data))
|
|
continue;
|
|
|
|
snap->snap[i].data = kvmalloc(snap->snap[i].len, GFP_USER);
|
|
if (!snap->snap[i].data) {
|
|
snap->snap[i].data = ERR_PTR(-ENOMEM);
|
|
goto cleanup_bo;
|
|
}
|
|
|
|
if (bo) {
|
|
err = xe_bo_read(bo, snap->snap[i].bo_ofs,
|
|
snap->snap[i].data, snap->snap[i].len);
|
|
} else {
|
|
void __user *userptr = (void __user *)(size_t)snap->snap[i].bo_ofs;
|
|
|
|
kthread_use_mm(snap->snap[i].mm);
|
|
if (!copy_from_user(snap->snap[i].data, userptr, snap->snap[i].len))
|
|
err = 0;
|
|
else
|
|
err = -EFAULT;
|
|
kthread_unuse_mm(snap->snap[i].mm);
|
|
|
|
mmput(snap->snap[i].mm);
|
|
snap->snap[i].mm = NULL;
|
|
}
|
|
|
|
if (err) {
|
|
kvfree(snap->snap[i].data);
|
|
snap->snap[i].data = ERR_PTR(err);
|
|
}
|
|
|
|
cleanup_bo:
|
|
xe_bo_put(bo);
|
|
snap->snap[i].bo = NULL;
|
|
}
|
|
}
|
|
|
|
void xe_vm_snapshot_print(struct xe_vm_snapshot *snap, struct drm_printer *p)
|
|
{
|
|
unsigned long i, j;
|
|
|
|
if (IS_ERR_OR_NULL(snap)) {
|
|
drm_printf(p, "[0].error: %li\n", PTR_ERR(snap));
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < snap->num_snaps; i++) {
|
|
drm_printf(p, "[%llx].length: 0x%lx\n", snap->snap[i].ofs, snap->snap[i].len);
|
|
|
|
if (IS_ERR(snap->snap[i].data)) {
|
|
drm_printf(p, "[%llx].error: %li\n", snap->snap[i].ofs,
|
|
PTR_ERR(snap->snap[i].data));
|
|
continue;
|
|
}
|
|
|
|
drm_printf(p, "[%llx].data: ", snap->snap[i].ofs);
|
|
|
|
for (j = 0; j < snap->snap[i].len; j += sizeof(u32)) {
|
|
u32 *val = snap->snap[i].data + j;
|
|
char dumped[ASCII85_BUFSZ];
|
|
|
|
drm_puts(p, ascii85_encode(*val, dumped));
|
|
}
|
|
|
|
drm_puts(p, "\n");
|
|
|
|
if (drm_coredump_printer_is_full(p))
|
|
return;
|
|
}
|
|
}
|
|
|
|
void xe_vm_snapshot_free(struct xe_vm_snapshot *snap)
|
|
{
|
|
unsigned long i;
|
|
|
|
if (IS_ERR_OR_NULL(snap))
|
|
return;
|
|
|
|
for (i = 0; i < snap->num_snaps; i++) {
|
|
if (!IS_ERR(snap->snap[i].data))
|
|
kvfree(snap->snap[i].data);
|
|
xe_bo_put(snap->snap[i].bo);
|
|
if (snap->snap[i].mm)
|
|
mmput(snap->snap[i].mm);
|
|
}
|
|
kvfree(snap);
|
|
}
|
|
|
|
/**
|
|
* xe_vma_need_vram_for_atomic - Check if VMA needs VRAM migration for atomic operations
|
|
* @xe: Pointer to the Xe device structure
|
|
* @vma: Pointer to the virtual memory area (VMA) structure
|
|
* @is_atomic: In pagefault path and atomic operation
|
|
*
|
|
* This function determines whether the given VMA needs to be migrated to
|
|
* VRAM in order to do atomic GPU operation.
|
|
*
|
|
* Return:
|
|
* 1 - Migration to VRAM is required
|
|
* 0 - Migration is not required
|
|
* -EACCES - Invalid access for atomic memory attr
|
|
*
|
|
*/
|
|
int xe_vma_need_vram_for_atomic(struct xe_device *xe, struct xe_vma *vma, bool is_atomic)
|
|
{
|
|
u32 atomic_access = xe_vma_bo(vma) ? xe_vma_bo(vma)->attr.atomic_access :
|
|
vma->attr.atomic_access;
|
|
|
|
if (!IS_DGFX(xe) || !is_atomic)
|
|
return false;
|
|
|
|
/*
|
|
* NOTE: The checks implemented here are platform-specific. For
|
|
* instance, on a device supporting CXL atomics, these would ideally
|
|
* work universally without additional handling.
|
|
*/
|
|
switch (atomic_access) {
|
|
case DRM_XE_ATOMIC_DEVICE:
|
|
return !xe->info.has_device_atomics_on_smem;
|
|
|
|
case DRM_XE_ATOMIC_CPU:
|
|
return -EACCES;
|
|
|
|
case DRM_XE_ATOMIC_UNDEFINED:
|
|
case DRM_XE_ATOMIC_GLOBAL:
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static int xe_vm_alloc_vma(struct xe_vm *vm,
|
|
struct drm_gpuvm_map_req *map_req,
|
|
bool is_madvise)
|
|
{
|
|
struct xe_vma_ops vops;
|
|
struct drm_gpuva_ops *ops = NULL;
|
|
struct drm_gpuva_op *__op;
|
|
unsigned int vma_flags = 0;
|
|
bool remap_op = false;
|
|
struct xe_vma_mem_attr tmp_attr;
|
|
u16 default_pat;
|
|
int err;
|
|
|
|
lockdep_assert_held_write(&vm->lock);
|
|
|
|
if (is_madvise)
|
|
ops = drm_gpuvm_madvise_ops_create(&vm->gpuvm, map_req);
|
|
else
|
|
ops = drm_gpuvm_sm_map_ops_create(&vm->gpuvm, map_req);
|
|
|
|
if (IS_ERR(ops))
|
|
return PTR_ERR(ops);
|
|
|
|
if (list_empty(&ops->list)) {
|
|
err = 0;
|
|
goto free_ops;
|
|
}
|
|
|
|
drm_gpuva_for_each_op(__op, ops) {
|
|
struct xe_vma_op *op = gpuva_op_to_vma_op(__op);
|
|
struct xe_vma *vma = NULL;
|
|
|
|
if (!is_madvise) {
|
|
if (__op->op == DRM_GPUVA_OP_UNMAP) {
|
|
vma = gpuva_to_vma(op->base.unmap.va);
|
|
XE_WARN_ON(!xe_vma_has_default_mem_attrs(vma));
|
|
default_pat = vma->attr.default_pat_index;
|
|
vma_flags = vma->gpuva.flags;
|
|
}
|
|
|
|
if (__op->op == DRM_GPUVA_OP_REMAP) {
|
|
vma = gpuva_to_vma(op->base.remap.unmap->va);
|
|
default_pat = vma->attr.default_pat_index;
|
|
vma_flags = vma->gpuva.flags;
|
|
}
|
|
|
|
if (__op->op == DRM_GPUVA_OP_MAP) {
|
|
op->map.vma_flags |= vma_flags & XE_VMA_CREATE_MASK;
|
|
op->map.pat_index = default_pat;
|
|
}
|
|
} else {
|
|
if (__op->op == DRM_GPUVA_OP_REMAP) {
|
|
vma = gpuva_to_vma(op->base.remap.unmap->va);
|
|
xe_assert(vm->xe, !remap_op);
|
|
xe_assert(vm->xe, xe_vma_has_no_bo(vma));
|
|
remap_op = true;
|
|
vma_flags = vma->gpuva.flags;
|
|
}
|
|
|
|
if (__op->op == DRM_GPUVA_OP_MAP) {
|
|
xe_assert(vm->xe, remap_op);
|
|
remap_op = false;
|
|
/*
|
|
* In case of madvise ops DRM_GPUVA_OP_MAP is
|
|
* always after DRM_GPUVA_OP_REMAP, so ensure
|
|
* to propagate the flags from the vma we're
|
|
* unmapping.
|
|
*/
|
|
op->map.vma_flags |= vma_flags & XE_VMA_CREATE_MASK;
|
|
}
|
|
}
|
|
print_op(vm->xe, __op);
|
|
}
|
|
|
|
xe_vma_ops_init(&vops, vm, NULL, NULL, 0);
|
|
|
|
if (is_madvise)
|
|
vops.flags |= XE_VMA_OPS_FLAG_MADVISE;
|
|
|
|
err = vm_bind_ioctl_ops_parse(vm, ops, &vops);
|
|
if (err)
|
|
goto unwind_ops;
|
|
|
|
xe_vm_lock(vm, false);
|
|
|
|
drm_gpuva_for_each_op(__op, ops) {
|
|
struct xe_vma_op *op = gpuva_op_to_vma_op(__op);
|
|
struct xe_vma *vma;
|
|
|
|
if (__op->op == DRM_GPUVA_OP_UNMAP) {
|
|
vma = gpuva_to_vma(op->base.unmap.va);
|
|
/* There should be no unmap for madvise */
|
|
if (is_madvise)
|
|
XE_WARN_ON("UNEXPECTED UNMAP");
|
|
|
|
xe_vma_destroy(vma, NULL);
|
|
} else if (__op->op == DRM_GPUVA_OP_REMAP) {
|
|
vma = gpuva_to_vma(op->base.remap.unmap->va);
|
|
/* In case of madvise ops Store attributes for REMAP UNMAPPED
|
|
* VMA, so they can be assigned to newly MAP created vma.
|
|
*/
|
|
if (is_madvise)
|
|
tmp_attr = vma->attr;
|
|
|
|
xe_vma_destroy(gpuva_to_vma(op->base.remap.unmap->va), NULL);
|
|
} else if (__op->op == DRM_GPUVA_OP_MAP) {
|
|
vma = op->map.vma;
|
|
/* In case of madvise call, MAP will always be followed by REMAP.
|
|
* Therefore temp_attr will always have sane values, making it safe to
|
|
* copy them to new vma.
|
|
*/
|
|
if (is_madvise)
|
|
vma->attr = tmp_attr;
|
|
}
|
|
}
|
|
|
|
xe_vm_unlock(vm);
|
|
drm_gpuva_ops_free(&vm->gpuvm, ops);
|
|
return 0;
|
|
|
|
unwind_ops:
|
|
vm_bind_ioctl_ops_unwind(vm, &ops, 1);
|
|
free_ops:
|
|
drm_gpuva_ops_free(&vm->gpuvm, ops);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* xe_vm_alloc_madvise_vma - Allocate VMA's with madvise ops
|
|
* @vm: Pointer to the xe_vm structure
|
|
* @start: Starting input address
|
|
* @range: Size of the input range
|
|
*
|
|
* This function splits existing vma to create new vma for user provided input range
|
|
*
|
|
* Return: 0 if success
|
|
*/
|
|
int xe_vm_alloc_madvise_vma(struct xe_vm *vm, uint64_t start, uint64_t range)
|
|
{
|
|
struct drm_gpuvm_map_req map_req = {
|
|
.map.va.addr = start,
|
|
.map.va.range = range,
|
|
};
|
|
|
|
lockdep_assert_held_write(&vm->lock);
|
|
|
|
vm_dbg(&vm->xe->drm, "MADVISE_OPS_CREATE: addr=0x%016llx, size=0x%016llx", start, range);
|
|
|
|
return xe_vm_alloc_vma(vm, &map_req, true);
|
|
}
|
|
|
|
/**
|
|
* xe_vm_alloc_cpu_addr_mirror_vma - Allocate CPU addr mirror vma
|
|
* @vm: Pointer to the xe_vm structure
|
|
* @start: Starting input address
|
|
* @range: Size of the input range
|
|
*
|
|
* This function splits/merges existing vma to create new vma for user provided input range
|
|
*
|
|
* Return: 0 if success
|
|
*/
|
|
int xe_vm_alloc_cpu_addr_mirror_vma(struct xe_vm *vm, uint64_t start, uint64_t range)
|
|
{
|
|
struct drm_gpuvm_map_req map_req = {
|
|
.map.va.addr = start,
|
|
.map.va.range = range,
|
|
};
|
|
|
|
lockdep_assert_held_write(&vm->lock);
|
|
|
|
vm_dbg(&vm->xe->drm, "CPU_ADDR_MIRROR_VMA_OPS_CREATE: addr=0x%016llx, size=0x%016llx",
|
|
start, range);
|
|
|
|
return xe_vm_alloc_vma(vm, &map_req, false);
|
|
}
|