On 9/16/26 17:10, SRINIVASAN SHANMUGAM wrote:
>
> On 9/16/2026 8:14 PM, Christian König wrote:
>> Hi Srini,
>>
>> please keep Timur, Natalie and Samuel on CC.
>>
>> On 9/16/26 07:37, Srinivasan Shanmugam wrote:
>>> Render-node user queues had no mechanism to install a userspace
>>> second-level trap handler equivalent to what KFD provides for compute.
>>>
>>> On dGPUs the CWSR TMA buffer is placed in VRAM and accessed via MMIO
>>> (PCI BAR). Raw CPU pointer dereferences are unsafe on platforms that
>>> lack full load/store support for MMIO. Replace void *tma_cpu_addr with
>>> struct iosys_map tma_map and use iosys_map_wr() for all TMA writes.
>>>
>>> Writing TBA and TMA as two separate stores creates a race where a
>>> trapping wave can observe a mismatched pair. For user queues, evict all
>>> queues and flush the GPU TLB before writing, then restore queues after.
>>> For kernel queues, wait for the VM to be idle via amdgpu_vm_wait_idle()
>>> before eviction and the descriptor write.
>>>
>>> Evict user queues in amdgpu_trap_free() at file close. Without eviction
>>> a freed TMA BO can be accessed via a stale TBA, causing a GPU page
>>> fault.
>>>
>>> Export amdgpu_userq_restore_all() for immediate queue restore after
>>> SET/CLEAR without waiting for the eviction fence.
>>>
>>> Add VM_ALWAYS_VALID validation in amdgpu_trap_validate_user_addr().
>>> SET_L2_TRAP returns -EINVAL if TBA or TMA BO does not have this flag.
>> Works for me, Timur/Natalie/Samuel any objections to that?
>>
>>> The old TRAP ioctl and its internal handler are removed. The new
>>> SET/CLEAR path is exposed via the VM ioctl in a later patch.
>>>
>>> SET_L2_TRAP is per-VM (per-process). Each process has its own TMA buffer
>>> allocated in amdgpu_trap_alloc(). There is no conflict between processes
>>> on user queue VMIDs.
>>>
>>> Cc: Christian König <[email protected]>
>>> Cc: Alex Deucher <[email protected]>
>>> Cc: Felix Kuehling <[email protected]>
>>> Cc: Lijo Lazar <[email protected]>
>>> Signed-off-by: Srinivasan Shanmugam <[email protected]>
>>> ---
>>> drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 1 -
>>> drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c | 2 +-
>>> drivers/gpu/drm/amd/amdgpu/amdgpu_trap.c | 284 ++++++++++++++++------
>>> drivers/gpu/drm/amd/amdgpu/amdgpu_trap.h | 64 ++++-
>>> drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 22 +-
>>> drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h | 1 +
>>> include/uapi/drm/amdgpu_drm.h | 2 -
>>> 7 files changed, 285 insertions(+), 91 deletions(-)
>>>
>>> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
>>> b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
>>> index a93ee106ab92..6473faf18d71 100644
>>> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
>>> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
>>> @@ -3079,7 +3079,6 @@ const struct drm_ioctl_desc amdgpu_ioctls_kms[] = {
>>> DRM_IOCTL_DEF_DRV(AMDGPU_SCHED, amdgpu_sched_ioctl, DRM_MASTER),
>>> DRM_IOCTL_DEF_DRV(AMDGPU_BO_LIST, amdgpu_bo_list_ioctl,
>>> DRM_AUTH|DRM_RENDER_ALLOW),
>>> DRM_IOCTL_DEF_DRV(AMDGPU_FENCE_TO_HANDLE,
>>> amdgpu_cs_fence_to_handle_ioctl, DRM_AUTH|DRM_RENDER_ALLOW),
>>> - DRM_IOCTL_DEF_DRV(AMDGPU_TRAP, amdgpu_trap_ioctl, DRM_AUTH |
>>> DRM_RENDER_ALLOW),
>>> /* KMS */
>>> DRM_IOCTL_DEF_DRV(AMDGPU_GEM_MMAP, amdgpu_gem_mmap_ioctl,
>>> DRM_AUTH|DRM_RENDER_ALLOW),
>>> DRM_IOCTL_DEF_DRV(AMDGPU_GEM_WAIT_IDLE, amdgpu_gem_wait_idle_ioctl,
>>> DRM_AUTH|DRM_RENDER_ALLOW),
>>> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
>>> b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
>>> index 47e4680db797..559c9222fc48 100644
>>> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
>>> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
>>> @@ -1716,7 +1716,7 @@ void amdgpu_driver_postclose_kms(struct drm_device
>>> *dev,
>>> }
>>> amdgpu_ctx_mgr_fini(&fpriv->ctx_mgr);
>>> - amdgpu_trap_free(adev, &fpriv->vm, &fpriv->trap_obj);
>>> + amdgpu_trap_free(adev, &fpriv->vm, &fpriv->userq_mgr,
>>> &fpriv->trap_obj);
>>> if (pasid)
>>> amdgpu_pasid_free_delayed(pd->tbo.base.resv, pasid);
>>> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_trap.c
>>> b/drivers/gpu/drm/amd/amdgpu/amdgpu_trap.c
>>> index 31f653ec3fb1..3313bc099cf4 100644
>>> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_trap.c
>>> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_trap.c
>>> @@ -19,12 +19,23 @@
>>> * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
>>> * OTHER DEALINGS IN THE SOFTWARE.
>>> */
>>> +#include <linux/iosys-map.h>
>>> +#include <drm/ttm/ttm_bo.h>
>>> +
>>> #include <drm/drm_exec.h>
>>> #include "amdgpu.h"
>>> +#include "amdgpu_gmc.h"
>>> +#include "amdgpu_userq.h"
>>> +#include "amdgpu_vm.h"
>>> #include "cwsr_trap_handler.h"
>>> #include "amdgpu_trap.h"
>>> +/* First-level TMA layout shared with second-level trap handler ABI */
>>> +#define AMDGPU_TRAP_L2_TBA_INDEX 0
>>> +#define AMDGPU_TRAP_L2_TMA_INDEX 1
>>> +#define AMDGPU_TRAP_DEBUG_FLAG_INDEX 2
>>> +
>>> extern int cwsr_enable;
>>> #define AMDGPU_TRAP_TBA_MAX_SIZE (2 * AMDGPU_GPU_PAGE_SIZE)
>>> @@ -380,6 +391,7 @@ int amdgpu_trap_alloc(struct amdgpu_device *adev,
>>> struct amdgpu_vm *vm,
>>> struct amdgpu_trap_obj *cwsr;
>>> struct amdgpu_bo *bo;
>>> struct drm_exec exec;
>>> + void *cpu_addr;
>>> int r;
>>> if (!amdgpu_trap_is_enabled(adev))
>>> @@ -407,10 +419,21 @@ int amdgpu_trap_alloc(struct amdgpu_device *adev,
>>> struct amdgpu_vm *vm,
>>> r = amdgpu_bo_create_kernel(adev, AMDGPU_TRAP_TMA_MAX_SIZE,
>>> PAGE_SIZE,
>>> AMDGPU_GEM_DOMAIN_GTT, &cwsr->tma_bo, NULL,
>>> - &cwsr->tma_cpu_addr);
>>> + &cpu_addr);
>>> if (r)
>>> goto err;
>>> + /*
>>> + * Build an iosys_map for the TMA CPU mapping. On dGPUs the TMA BO
>>> + * may be placed in VRAM and accessed via MMIO (PCI BAR). Check
>>> + * TTM_BO_MAP_IOMEM_MASK to select the correct write accessor.
>>> + */
>>> + if (cwsr->tma_bo->kmap.bo_kmap_type & TTM_BO_MAP_IOMEM_MASK)
>>> + iosys_map_set_vaddr_iomem(&cwsr->tma_map,
>>> + (void __iomem *)cpu_addr);
>>> + else
>>> + iosys_map_set_vaddr(&cwsr->tma_map, cpu_addr);
>>> +
>> Clear NAK to that hack. Please use ttm_bo_vmap() instead if you need an
>> iosys_map.
>>
>>
>>
>>> r = amdgpu_trap_map_region(adev, vm, cwsr, AMDGPU_TRAP_TMA);
>>> if (r)
>>> goto err;
>>> @@ -433,6 +456,29 @@ int amdgpu_trap_alloc(struct amdgpu_device *adev,
>>> struct amdgpu_vm *vm,
>>> return r;
>>> }
>>> +void amdgpu_trap_clear_l2_handler(struct amdgpu_vm *vm,
>>> + struct amdgpu_trap_obj *trap_obj)
>>> +{
>>> + if (!trap_obj || iosys_map_is_null(&trap_obj->tma_map))
>>> + return;
>>> +
>>> + dma_resv_assert_held(vm->root.bo->tbo.base.resv);
>>> +
>>> + /*
>>> + * Zero TBA first to disable dispatch before clearing TMA.
>>> + */
>>> + iosys_map_wr(&trap_obj->tma_map,
>>> + AMDGPU_TRAP_L2_TBA_INDEX * sizeof(u64), u64, 0);
>>> + dma_wmb();
>>> + iosys_map_wr(&trap_obj->tma_map,
>>> + AMDGPU_TRAP_L2_TMA_INDEX * sizeof(u64), u64, 0);
>> It's most likely a good idea to grab the reset lock around all
>> iosys_map_wr() as well.
>
>
> Thanks Christian. May I kno pls, do you mean taking the existing reset-domain
> lock around the complete TMA descriptor update (all iosys_map_wr() calls and
> the final barrier), or only around each individual write?
One read side lock around all iosys_map_wr() calls and the barrier should be
sufficient.
The problem is that during a reset you can't access VRAM for a moment without
crashing the system.
So we need to avoid writing to VRAM at the same time as a GPU reset is ongoing.
Regards,
Christian.
>
>
>>
>>> +
>>> + trap_obj->l2_tba_addr = 0;
>>> + trap_obj->l2_tba_size = 0;
>>> + trap_obj->l2_tma_addr = 0;
>>> + trap_obj->l2_tma_size = 0;
>>> +}
>>> +
>>> int amdgpu_trap_validate_params(struct amdgpu_device *adev,
>>> struct amdgpu_trap_params *trap_params,
>>> int num_xcc)
>>> @@ -464,6 +510,7 @@ int amdgpu_trap_validate_params(struct amdgpu_device
>>> *adev,
>>> }
>>> void amdgpu_trap_free(struct amdgpu_device *adev, struct amdgpu_vm *vm,
>>> + struct amdgpu_userq_mgr *uq_mgr,
>>> struct amdgpu_trap_obj **trap_obj)
>>> {
>>> struct amdgpu_bo *tba_bo;
>>> @@ -473,32 +520,41 @@ void amdgpu_trap_free(struct amdgpu_device *adev,
>>> struct amdgpu_vm *vm,
>>> if (!trap_obj || !*trap_obj || !(*trap_obj)->tma_bo)
>>> return;
>>> +
>>> + /*
>>> + * Evict all user queues before freeing. At file-close time waves
>>> + * may still be active; without eviction a wave can trap into the
>>> + * handler after the TMA BO is freed, causing a GPU page fault.
>>> + */
>>> + if (uq_mgr)
>>> + amdgpu_userq_evict(uq_mgr);
>>> +
>>> tba_bo = adev->trap_info->isa_bo;
>>> tma_bo = (*trap_obj)->tma_bo;
>>> if (!tba_bo || !tma_bo)
>>> return;
>>> - drm_exec_init(&exec, 0, 0);
>>> - drm_exec_until_all_locked(&exec)
>>> - {
>>> + drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT, 0);
>>> + drm_exec_until_all_locked(&exec) {
>>> r = amdgpu_vm_lock_pd(vm, &exec, 0);
>>> - if (likely(!r))
>>> - r = drm_exec_lock_obj(&exec, &tba_bo->tbo.base);
>>> - drm_exec_retry_on_contention(&exec);
>>> if (likely(!r))
>>> r = drm_exec_lock_obj(&exec, &tma_bo->tbo.base);
>>> + if (likely(!r))
>>> + r = drm_exec_lock_obj(&exec, &tba_bo->tbo.base);
>> Please don't do such hacks. drm_exec_retry_on_contention() should be called
>> after each drm_exec_lock_obj() call and if (r) goto error handling used here.
>>
>>> drm_exec_retry_on_contention(&exec);
>>> if (unlikely(r)) {
>>> dev_err(adev->dev,
>>> "failed to reserve CWSR BOs: err=%d\n", r);
>>> - goto err;
>>> + goto out;
>>> }
>>> }
>>> + amdgpu_trap_clear_l2_handler(vm, *trap_obj);
>>> +
>>> amdgpu_trap_unmap_region(adev, *trap_obj, AMDGPU_TRAP_TBA);
>>> amdgpu_trap_unmap_region(adev, *trap_obj, AMDGPU_TRAP_TMA);
>>> -err:
>>> +out:
>>> drm_exec_fini(&exec);
>>> amdgpu_bo_free_kernel(&(*trap_obj)->tma_bo, NULL, NULL);
>>> kfree(*trap_obj);
>>> @@ -509,120 +565,186 @@ static int amdgpu_trap_validate_user_addr(struct
>>> amdgpu_device *adev,
>>> struct amdgpu_vm *vm,
>>> struct amdgpu_trap_usr_addr *usr_addr)
>>> {
>>> + struct amdgpu_bo *bo;
>>> struct amdgpu_bo_va_mapping *va_map;
>>> - uint64_t addr;
>>> - uint32_t size;
>>> - int r;
>>> + u64 addr;
>>> + u32 size;
>>> +
>>> + /*
>>> + * Caller holds the VM root BO reservation; assert rather than
>>> + * taking it again.
>>> + */
>>> + dma_resv_assert_held(vm->root.bo->tbo.base.resv);
>>> addr = (usr_addr->addr & AMDGPU_GMC_HOLE_MASK) >>
>>> AMDGPU_GPU_PAGE_SHIFT;
>>> size = usr_addr->size >> AMDGPU_GPU_PAGE_SHIFT;
>>> - r = amdgpu_bo_reserve(vm->root.bo, false);
>>> - if (r)
>>> - return r;
>>> -
>>> va_map = amdgpu_vm_bo_lookup_mapping(vm, addr);
>>> - if (!va_map) {
>>> - r = -EINVAL;
>>> - goto err;
>>> - }
>>> - /* validate whether resident in the VM mapping range */
>>> - if (addr >= va_map->start && va_map->last - addr + 1 >= size) {
>>> - amdgpu_bo_unreserve(vm->root.bo);
>>> - return 0;
>>> + if (!va_map)
>>> + return -EINVAL;
>>> +
>>> + /*
>>> + * TBA/TMA BOs must be VM_ALWAYS_VALID. The GPU jumps to TBA on
>>> + * hardware exception without any software job submission, so the
>>> + * kernel cannot add TBA/TMA to a job's used-buffer list to keep
>>> + * page tables current. Without VM_ALWAYS_VALID the BO can be
>>> + * evicted and the page table entry becomes stale, causing a GPU
>>> + * page fault or memory corruption on the next shader exception.
>>> + */
>> That comment is not correct. This would work completely without
>> AMDGPU_GEM_CREATE_VM_ALWAYS_VALID.
>>
>> Please just completely drop it.
>>
>>> + bo = va_map->bo_va->base.bo;
>>> + if (!(bo->flags & AMDGPU_GEM_CREATE_VM_ALWAYS_VALID)) {
>>> + dev_dbg(adev->dev,
>>> + "TBA/TMA BO at VA 0x%llx must use VM_ALWAYS_VALID\n",
>>> + usr_addr->addr);
>>> + return -EINVAL;
>>> }
>>> - r = -EINVAL;
>>> -err:
>>> - amdgpu_bo_unreserve(vm->root.bo);
>>> + if (addr >= va_map->start && va_map->last - addr + 1 >= size)
>>> + return 0;
>>> - return r;
>>> + return -EINVAL;
>>> }
>>> -static int amdgpu_trap_set_l2_trap_handler(struct amdgpu_device *adev,
>>> struct amdgpu_vm *vm,
>>> - struct amdgpu_trap_obj *cwsr_obj,
>>> - struct amdgpu_trap_usr_addr *tma,
>>> - struct amdgpu_trap_usr_addr *tba)
>>> +int amdgpu_trap_vm_set_l2_trap(struct amdgpu_device *adev,
>>> + struct amdgpu_vm *vm,
>>> + struct amdgpu_userq_mgr *uq_mgr,
>>> + struct amdgpu_trap_obj *trap_obj,
>>> + u64 tba_va, uint32_t tba_sz,
>>> + u64 tma_va, uint32_t tma_sz)
>>> {
>>> - uint64_t *l1tma;
>>> + struct amdgpu_trap_usr_addr tba = { .addr = tba_va, .size = tba_sz };
>>> + struct amdgpu_trap_usr_addr tma = { .addr = tma_va, .size = tma_sz };
>>> + long timeout;
>>> int r;
>>> if (!amdgpu_trap_is_enabled(adev))
>>> return -EOPNOTSUPP;
>>> -
>>> - if (!cwsr_obj || !cwsr_obj->tma_cpu_addr || !tma || !tba)
>>> + if (!trap_obj || iosys_map_is_null(&trap_obj->tma_map))
>>> return -EINVAL;
>>> - r = amdgpu_trap_validate_user_addr(adev, vm, tma);
>>> +
>>> + /*
>>> + * Wait for any kernel-queue work using this VM to drain before
>>> + * evicting user queues and writing the new descriptor. Without
>>> + * this wait a kernel-queue wave can observe a partially-written
>>> + * TBA/TMA pair during the update window. SET fails if the wait
>>> + * is interrupted or times out so the caller can retry.
>>> + */
>>> + timeout = amdgpu_vm_wait_idle(vm, MAX_SCHEDULE_TIMEOUT);
>>> + if (timeout <= 0)
>>> + return timeout < 0 ? timeout : -ETIMEDOUT;
>>> +
>>> + /*
>>> + * Evict all user queues before updating TBA/TMA. Writing TBA and
>>> + * TMA as two separate stores creates a race: a wave that traps
>>> + * between them reads a mismatched pair. Evicting first ensures no
>>> + * wave is active during the update.
>>> + */
>>> + amdgpu_userq_evict(uq_mgr);
>>> +
>>> + /* Flush GPU TLB to remove stale cached TBA/TMA values */
>>> + amdgpu_gmc_flush_gpu_tlb_pasid(adev, vm->pasid, 2, true, 0);
>>> +
>>> + r = amdgpu_bo_reserve(vm->root.bo, false);
>>> if (r)
>>> return r;
>>> - r = amdgpu_trap_validate_user_addr(adev, vm, tba);
>>> +
>>> + r = amdgpu_trap_validate_user_addr(adev, vm, &tba);
>>> + if (r)
>>> + goto out_unreserve;
>>> + r = amdgpu_trap_validate_user_addr(adev, vm, &tma);
>>> + if (r)
>>> + goto out_unreserve;
>>> +
>>> + /* Zero TBA first to disable residual handler, then TMA, then TBA */
>> That doesn't seem to make sense. Why should that be necessary.
>
> I added the initial TBA=0 wrtie defensively to avoid exposing a stale
> descriptor during the update. Since execution is quiesced before updating the
> TMA, it isn't necessary, I'll remove it and place the barrier after the final
> TBA/TMA write.
>
> Thanks, Srini
>