On 7/1/26 13:33, Natalie Vock wrote:
> We claimed vm_bo->moved was protected by the BO being reserved, but then
> accessed it in a bunch of places without a reservation to the BO anyway.

Can you point out where? That clearly doesn't sound correct to me.

We have some questionable uses for userptrs, but those should be irrelevant.

> 
> It's not sensible to protect this by BO reservation in any case - it's a
> property relating to VM state, just like vm_status. Let's protect this
> by vm->status_lock as well. We usually grab the lock at some point when
> we access the field anyway, so it fits well with the current usage of
> the field.

That won't work. You can't hold the status lock while the moved flag can't be 
modified.

> We also need to remove some unprotected accesses of the field when
> removing a mapping. Checking for the field there was a microoptimization
> anyway.
> 
> Lastly, and most critically, we also need to handle buffer
> moves/invalidations racing with amdgpu_vm_bo_update. Otherwise we might
> accidentally undo the invalidation without the PTs actually being
> properly updated.

That's not correct as far as I can see the moves/invalidations can perfectly 
happen in paralell and are handled before the next CS.

Regards,
Christian.

> 
> Fixes: d38ceaf99ed0 ("drm/amdgpu: add core driver (v4)")
> Signed-off-by: Natalie Vock <[email protected]>
> ---
> FWIW, I'm not 100% positive on whether the Fixes tag is correct - I'm
> fairly certain the VM update <-> invalidation race existed ever since
> the driver's inception, but it may have been hidden in the initial
> revision. I suspect backporting it all the way to wherever the race
> started manifesting is roughly similarly painful, though.
> 
> Also, this patchset is based on my previous one to rename the "moved" VM
> state to "needs_update". I think it got reviewed and should've been
> picked up, but I'm not sure I see it in amd-staging-drm-next?
> ---
>  drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 108 +++++++++++++++++--------
>  drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h |   3 +-
>  2 files changed, 75 insertions(+), 36 deletions(-)
> 
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c 
> b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c
> index 32719f31b6c9e..3451dca7de194 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c
> @@ -214,11 +214,12 @@ static void amdgpu_vm_bo_evicted(struct 
> amdgpu_vm_bo_base *vm_bo)
>   * amdgpu_vm_bo_needs_update - vm_bo needs pagetable update
>   *
>   * @vm_bo: vm_bo which is out of date
> + * @moved: whether the vm_bo was moved
>   *
>   * State for vm_bo objects meaning the underlying BO had mapping changes 
> (move, PRT bind/unbind)
>   * but the new location is not yet reflected in the page tables.
>   */
> -static void amdgpu_vm_bo_needs_update(struct amdgpu_vm_bo_base *vm_bo)
> +static void amdgpu_vm_bo_needs_update(struct amdgpu_vm_bo_base *vm_bo, bool 
> moved)
>  {
>       struct amdgpu_vm_bo_status *lists;
>       struct amdgpu_bo *bo = vm_bo->bo;
> @@ -232,11 +233,28 @@ static void amdgpu_vm_bo_needs_update(struct 
> amdgpu_vm_bo_base *vm_bo)
>               vm_bo->moved = false;
>               list_move(&vm_bo->vm_status, &lists->idle);
>       } else {
> +             if (moved)
> +                     vm_bo->moved = true;
>               list_move(&vm_bo->vm_status, &lists->needs_update);
>       }
>       amdgpu_vm_bo_unlock_lists(vm_bo);
>  }
>  
> +/**
> + * amdgpu_vm_bo_idle_locked - vm_bo is idle, already-locked version
> + *
> + * @vm_bo: vm_bo which is now idle
> + *
> + * State for vm_bo objects meaning we are done with the state machine and no
> + * further action is necessary. Needs to supply a locked status list.
> + */
> +static void amdgpu_vm_bo_idle_locked(struct amdgpu_vm_bo_base *vm_bo,
> +                           struct amdgpu_vm_bo_status *lists)
> +{
> +     if (!amdgpu_vm_is_bo_always_valid(vm_bo->vm, vm_bo->bo))
> +             vm_bo->moved = false;
> +     list_move(&vm_bo->vm_status, &lists->idle);
> +}
>  /**
>   * amdgpu_vm_bo_idle - vm_bo is idle
>   *
> @@ -250,9 +268,7 @@ static void amdgpu_vm_bo_idle(struct amdgpu_vm_bo_base 
> *vm_bo)
>       struct amdgpu_vm_bo_status *lists;
>  
>       lists = amdgpu_vm_bo_lock_lists(vm_bo);
> -     if (!amdgpu_vm_is_bo_always_valid(vm_bo->vm, vm_bo->bo))
> -             vm_bo->moved = false;
> -     list_move(&vm_bo->vm_status, &lists->idle);
> +     amdgpu_vm_bo_idle_locked(vm_bo, lists);
>       amdgpu_vm_bo_unlock_lists(vm_bo);
>  }
>  
> @@ -273,9 +289,9 @@ static void amdgpu_vm_bo_reset_state_machine(struct 
> amdgpu_vm *vm)
>        */
>       amdgpu_vm_assert_locked(vm);
>       list_for_each_entry_safe(vm_bo, tmp, &vm->kernel.idle, vm_status)
> -             amdgpu_vm_bo_needs_update(vm_bo);
> +             amdgpu_vm_bo_needs_update(vm_bo, false);
>       list_for_each_entry_safe(vm_bo, tmp, &vm->always_valid.idle, vm_status)
> -             amdgpu_vm_bo_needs_update(vm_bo);
> +             amdgpu_vm_bo_needs_update(vm_bo, false);
>  
>       spin_lock(&vm->individual_lock);
>       list_for_each_entry_safe(vm_bo, tmp, &vm->individual.idle, vm_status) {
> @@ -435,7 +451,7 @@ void amdgpu_vm_bo_base_init(struct amdgpu_vm_bo_base 
> *base,
>        */
>       if (bo->preferred_domains &
>           amdgpu_mem_type_to_domain(bo->tbo.resource->mem_type))
> -             amdgpu_vm_bo_needs_update(base);
> +             amdgpu_vm_bo_needs_update(base, false);
>       else
>               amdgpu_vm_bo_evicted(base);
>  }
> @@ -607,8 +623,7 @@ int amdgpu_vm_validate(struct amdgpu_device *adev, struct 
> amdgpu_vm *vm,
>                       return r;
>  
>               vm->update_funcs->map_table(to_amdgpu_bo_vm(bo_base->bo));
> -             bo_base->moved = true;
> -             amdgpu_vm_bo_needs_update(bo_base);
> +             amdgpu_vm_bo_needs_update(bo_base, true);
>       }
>  
>       /*
> @@ -625,8 +640,7 @@ int amdgpu_vm_validate(struct amdgpu_device *adev, struct 
> amdgpu_vm *vm,
>               if (r)
>                       return r;
>  
> -             bo_base->moved = true;
> -             amdgpu_vm_bo_needs_update(bo_base);
> +             amdgpu_vm_bo_needs_update(bo_base, true);
>       }
>  
>       if (!ticket)
> @@ -646,8 +660,7 @@ int amdgpu_vm_validate(struct amdgpu_device *adev, struct 
> amdgpu_vm *vm,
>               if (r)
>                       return r;
>  
> -             bo_base->moved = true;
> -             amdgpu_vm_bo_needs_update(bo_base);
> +             amdgpu_vm_bo_needs_update(bo_base, true);
>  
>               /* It's a bit inefficient to always jump back to the start, but
>                * we would need to re-structure the KFD for properly fixing
> @@ -1266,16 +1279,38 @@ int amdgpu_vm_bo_update(struct amdgpu_device *adev, 
> struct amdgpu_bo_va *bo_va,
>       struct amdgpu_bo *bo = bo_va->base.bo;
>       struct amdgpu_vm *vm = bo_va->base.vm;
>       struct amdgpu_bo_va_mapping *mapping;
> +     struct amdgpu_vm_bo_status *lists;
>       struct dma_fence **last_update;
>       dma_addr_t *pages_addr = NULL;
>       struct ttm_resource *mem;
>       struct amdgpu_sync sync;
>       bool flush_tlb = clear;
> +     bool uncached, moved;
>       uint64_t vram_base;
>       uint64_t flags;
> -     bool uncached;
>       int r;
>  
> +     /* Everything we access in here is protected by the VM PD lock. */
> +     amdgpu_vm_assert_locked(vm);
> +
> +     lists = amdgpu_vm_bo_lock_lists(&bo_va->base);
> +     /*
> +      * We can't hold the spinlock for the entire VM update, so temporarily 
> remove
> +      * the BO from the state machine entirely. This does not prevent all 
> types of
> +      * races: We might not hold the BO's resv here, so TTM is free to move 
> the buffer
> +      * and thereby invalidate it. However, it allows us to detect if we 
> raced with
> +      * something that invalidated the BO again and handle that 
> appropriately below.
> +      */
> +     list_del_init(&bo_va->base.vm_status);
> +
> +     /*
> +      * The moved flag is also protected by the status lock. It's fine if 
> some buffer
> +      * update changes bo_va->base.moved while we're updating the PTs after 
> unlocking
> +      * the status lock, since that also invalidates the BO's VM status 
> again.
> +      */
> +     moved = bo_va->base.moved;
> +     amdgpu_vm_bo_unlock_lists(&bo_va->base);
> +
>       amdgpu_sync_create(&sync);
>       if (clear) {
>               mem = NULL;
> @@ -1343,7 +1378,7 @@ int amdgpu_vm_bo_update(struct amdgpu_device *adev, 
> struct amdgpu_bo_va *bo_va,
>       else
>               last_update = &bo_va->last_pt_update;
>  
> -     if (!clear && bo_va->base.moved) {
> +     if (!clear && moved) {
>               flush_tlb = true;
>               list_splice_init(&bo_va->valids, &bo_va->invalids);
>  
> @@ -1389,20 +1424,38 @@ int amdgpu_vm_bo_update(struct amdgpu_device *adev, 
> struct amdgpu_bo_va *bo_va,
>               else
>                       amdgpu_vm_bo_idle(&bo_va->base);
>       } else {
> -             amdgpu_vm_bo_idle(&bo_va->base);
> +             lists = amdgpu_vm_bo_lock_lists(&bo_va->base);
> +             /*
> +              * Now that we're holding the lock again, check if the
> +              * buffer got invalidated while we weren't looking.
> +              * We initialized vm_status to an empty list head above,
> +              * if that's still the case we can safely mark the BO as
> +              * done.
> +              *
> +              * Note: We only need to do this for BOs that are not
> +              * VM-always-valid, because we hold the VM's reservation
> +              * which by definition reserves all VM-always-valid BOs.
> +              */
> +             if (list_empty(&bo_va->base.vm_status))
> +                     amdgpu_vm_bo_idle_locked(&bo_va->base, lists);
> +             amdgpu_vm_bo_unlock_lists(&bo_va->base);
>       }
>  
>       list_splice_init(&bo_va->invalids, &bo_va->valids);
>       bo_va->cleared = clear;
> -     bo_va->base.moved = false;
>  
>       if (trace_amdgpu_vm_bo_mapping_enabled()) {
>               list_for_each_entry(mapping, &bo_va->valids, list)
>                       trace_amdgpu_vm_bo_mapping(mapping);
>       }
>  
> +     amdgpu_sync_free(&sync);
> +     return 0;
> +
>  error_free:
>       amdgpu_sync_free(&sync);
> +     if (r)
> +             amdgpu_vm_bo_needs_update(&bo_va->base, false);
>       return r;
>  }
>  
> @@ -1779,7 +1832,6 @@ static void amdgpu_vm_bo_insert_map(struct 
> amdgpu_device *adev,
>                                   struct amdgpu_bo_va_mapping *mapping)
>  {
>       struct amdgpu_vm *vm = bo_va->base.vm;
> -     struct amdgpu_bo *bo = bo_va->base.bo;
>  
>       mapping->bo_va = bo_va;
>       list_add(&mapping->list, &bo_va->invalids);
> @@ -1788,8 +1840,7 @@ static void amdgpu_vm_bo_insert_map(struct 
> amdgpu_device *adev,
>       if (mapping->flags & AMDGPU_VM_PAGE_PRT)
>               amdgpu_vm_prt_get(adev);
>  
> -     if (amdgpu_vm_is_bo_always_valid(vm, bo) && !bo_va->base.moved)
> -             amdgpu_vm_bo_needs_update(&bo_va->base);
> +     amdgpu_vm_bo_needs_update(&bo_va->base, false);
>  
>       trace_amdgpu_vm_bo_map(bo_va, mapping);
>  }
> @@ -2090,30 +2141,22 @@ int amdgpu_vm_bo_clear_mappings(struct amdgpu_device 
> *adev,
>  
>       /* Insert partial mapping before the range */
>       if (!list_empty(&before->list)) {
> -             struct amdgpu_bo *bo = before->bo_va->base.bo;
> -
>               amdgpu_vm_it_insert(before, &vm->va);
>               if (before->flags & AMDGPU_VM_PAGE_PRT)
>                       amdgpu_vm_prt_get(adev);
>  
> -             if (amdgpu_vm_is_bo_always_valid(vm, bo) &&
> -                 !before->bo_va->base.moved)
> -                     amdgpu_vm_bo_needs_update(&before->bo_va->base);
> +             amdgpu_vm_bo_needs_update(&before->bo_va->base, false);
>       } else {
>               kfree(before);
>       }
>  
>       /* Insert partial mapping after the range */
>       if (!list_empty(&after->list)) {
> -             struct amdgpu_bo *bo = after->bo_va->base.bo;
> -
>               amdgpu_vm_it_insert(after, &vm->va);
>               if (after->flags & AMDGPU_VM_PAGE_PRT)
>                       amdgpu_vm_prt_get(adev);
>  
> -             if (amdgpu_vm_is_bo_always_valid(vm, bo) &&
> -                 !after->bo_va->base.moved)
> -                     amdgpu_vm_bo_needs_update(&after->bo_va->base);
> +             amdgpu_vm_bo_needs_update(&after->bo_va->base, false);
>       } else {
>               kfree(after);
>       }
> @@ -2285,10 +2328,7 @@ void amdgpu_vm_bo_invalidate(struct amdgpu_bo *bo, 
> bool evicted)
>                       continue;
>               }
>  
> -             if (bo_base->moved)
> -                     continue;
> -             bo_base->moved = true;
> -             amdgpu_vm_bo_needs_update(bo_base);
> +             amdgpu_vm_bo_needs_update(bo_base, true);
>       }
>  }
>  
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h 
> b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h
> index c1bd4d35831e0..0acd889568a38 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h
> @@ -212,8 +212,7 @@ struct amdgpu_vm_bo_base {
>        * protected by vm BO being reserved */
>       bool                            shared;
>  
> -     /* if the BO was moved and all mappings are invalid
> -      * protected by the BO being reserved */
> +     /* protected by the vm's status lock */
>       bool                            moved;
>  };
>  

Reply via email to