[PATCH 2/3] drm/amdkfd: handle VMA remove race
Felix Kuehling
felix.kuehling at amd.com
Thu Nov 18 15:07:22 UTC 2021
Am 2021-11-18 um 10:00 a.m. schrieb philip yang:
>
>
> On 2021-11-17 7:10 p.m., Felix Kuehling wrote:
>> On 2021-11-16 10:43 p.m., Philip Yang wrote:
>>> VMA may be removed before unmap notifier callback, restore pages take
>>> mmap write lock to lookup VMA to avoid race,
>>
>> The old code looked up the VMA after taking the mmap lock (either
>> read or write) and kept holding the lock afterwards. I think even
>> with your new code it's possible that the VMA disappears before you
>> take the lock the first time, so always taking the write lock only
>> reduces the time window in which things can go wrong, but it doesn't
>> remove the race.
> Take mmap write lock will serialize with __do_munmap,
__do_munmap runs with the mmap write lock. Taking the read lock should
be sufficient to serialize with it.
Regards,
Felix
> to ensure vma remove and unmap callback are done, because unmap
> callback set drain_retryfaults flag, so we can safely drain the
> faults, and it is app bug if vma not found after taking mmap write lock.
>>
>> I still struggle to understand the race you're trying to fix. The
>> only time the svm_restore_pages can see that the VMA is gone AND the
>> prange is gone is after the deferred worker has removed the prange.
>> But the fault draining in the deferred worker should prevent us from
>> ever seeing stale faults in that situation. That means, if no prange
>> is found and no VMA is found, it's definitely an application bug.
>>
>> The only possible race is in the case where the prange still exists
>> but the VMA is gone (needed by svm_fault_allowed). We can treat that
>> as a special case where we just return success because we know that
>> we're handling a stale fault for a VMA that's in the middle of being
>> unmapped. The fact that the prange still existed means that there
>> once was a valid mapping at the address but the deferred worker just
>> hasn't had a chance to clean it up yet.
>>
> Yes, that is only possible race.
>> One more comment inline.
>>
>>
>>> and then create unregister
>>> new range and check VMA access permission, then downgrade to take mmap
>>> read lock to recover fault. Refactor code to avoid duplicate VMA
>>> lookup.
>>>
>>> Signed-off-by: Philip Yang <Philip.Yang at amd.com>
>>> ---
>>> drivers/gpu/drm/amd/amdkfd/kfd_svm.c | 65
>>> ++++++++++------------------
>>> 1 file changed, 24 insertions(+), 41 deletions(-)
>>>
>>> diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c
>>> b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c
>>> index c1f367934428..3eb0a9491755 100644
>>> --- a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c
>>> +++ b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c
>>> @@ -2329,20 +2329,13 @@ svm_range_best_restore_location(struct
>>> svm_range *prange,
>>> }
>>> static int
>>> -svm_range_get_range_boundaries(struct kfd_process *p, int64_t addr,
>>> - unsigned long *start, unsigned long *last,
>>> - bool *is_heap_stack)
>>> +svm_range_get_range_boundaries(struct kfd_process *p, struct
>>> vm_area_struct *vma,
>>> + int64_t addr, unsigned long *start,
>>> + unsigned long *last, bool *is_heap_stack)
>>> {
>>> - struct vm_area_struct *vma;
>>> struct interval_tree_node *node;
>>> unsigned long start_limit, end_limit;
>>> - vma = find_vma(p->mm, addr << PAGE_SHIFT);
>>> - if (!vma || (addr << PAGE_SHIFT) < vma->vm_start) {
>>> - pr_debug("VMA does not exist in address [0x%llx]\n", addr);
>>> - return -EFAULT;
>>> - }
>>> -
>>> *is_heap_stack = (vma->vm_start <= vma->vm_mm->brk &&
>>> vma->vm_end >= vma->vm_mm->start_brk) ||
>>> (vma->vm_start <= vma->vm_mm->start_stack &&
>>> @@ -2437,9 +2430,10 @@ svm_range_check_vm_userptr(struct kfd_process
>>> *p, uint64_t start, uint64_t last,
>>> static struct
>>> svm_range *svm_range_create_unregistered_range(struct
>>> amdgpu_device *adev,
>>> - struct kfd_process *p,
>>> - struct mm_struct *mm,
>>> - int64_t addr)
>>> + struct kfd_process *p,
>>> + struct mm_struct *mm,
>>> + struct vm_area_struct *vma,
>>> + int64_t addr)
>>> {
>>> struct svm_range *prange = NULL;
>>> unsigned long start, last;
>>> @@ -2449,7 +2443,7 @@ svm_range
>>> *svm_range_create_unregistered_range(struct amdgpu_device *adev,
>>> uint64_t bo_l = 0;
>>> int r;
>>> - if (svm_range_get_range_boundaries(p, addr, &start, &last,
>>> + if (svm_range_get_range_boundaries(p, vma, addr, &start, &last,
>>> &is_heap_stack))
>>> return NULL;
>>> @@ -2552,20 +2546,13 @@ svm_range_count_fault(struct amdgpu_device
>>> *adev, struct kfd_process *p,
>>> }
>>> static bool
>>> -svm_fault_allowed(struct mm_struct *mm, uint64_t addr, bool
>>> write_fault)
>>> +svm_fault_allowed(struct vm_area_struct *vma, bool write_fault)
>>> {
>>> unsigned long requested = VM_READ;
>>> - struct vm_area_struct *vma;
>>> if (write_fault)
>>> requested |= VM_WRITE;
>>> - vma = find_vma(mm, addr << PAGE_SHIFT);
>>> - if (!vma || (addr << PAGE_SHIFT) < vma->vm_start) {
>>> - pr_debug("address 0x%llx VMA is removed\n", addr);
>>> - return true;
>>> - }
>>> -
>>> pr_debug("requested 0x%lx, vma permission flags 0x%lx\n",
>>> requested,
>>> vma->vm_flags);
>>> return (vma->vm_flags & requested) == requested;
>>> @@ -2582,7 +2569,7 @@ svm_range_restore_pages(struct amdgpu_device
>>> *adev, unsigned int pasid,
>>> uint64_t timestamp;
>>> int32_t best_loc;
>>> int32_t gpuidx = MAX_GPU_INSTANCE;
>>> - bool write_locked = false;
>>> + struct vm_area_struct *vma = NULL;
>>> int r = 0;
>>> if (!KFD_IS_SVM_API_SUPPORTED(adev->kfd.dev)) {
>>> @@ -2606,26 +2593,22 @@ svm_range_restore_pages(struct amdgpu_device
>>> *adev, unsigned int pasid,
>>> /* mm is available because kfd_process_notifier_release
>>> drain fault */
>>> mm = p->mm;
>>> + mmap_write_lock(mm);
>>
>> Always taking the write lock is unnecessary. I think we can keep the
>> old strategy of retrying with the write lock only when necessary. I
>> think this should work correctly as long as you lookup the VMA every
>> time after taking either the mmap read or write lock. The vma you
>> looked up should be valid as long as you hold that lock.
>>
>> As I pointed out above, if svm_range_from_addr finds a prange but the
>> VMA is missing, we can treat that as a special case and return
>> success (just draining a stale fault on a VMA that's being unmapped).
>
> ok, I will change svm_fault_allowed to return success if VMA is
> missing, it is simpler to handle this special race case, without
> taking mmap write lock.
>
> Regards,
>
> Philip
>
>>
>> Regards,
>> Felix
>>
>>
>>> +
>>> + vma = find_vma(p->mm, addr << PAGE_SHIFT);
>>> + if (!vma || (addr << PAGE_SHIFT) < vma->vm_start) {
>>> + pr_debug("VMA not found for address 0x%llx\n", addr);
>>> + mmap_write_downgrade(mm);
>>> + r = -EFAULT;
>>> + goto out_unlock_mm;
>>> + }
>>> - mmap_read_lock(mm);
>>> -retry_write_locked:
>>> mutex_lock(&svms->lock);
>>> prange = svm_range_from_addr(svms, addr, NULL);
>>> if (!prange) {
>>> pr_debug("failed to find prange svms 0x%p address
>>> [0x%llx]\n",
>>> svms, addr);
>>> - if (!write_locked) {
>>> - /* Need the write lock to create new range with MMU
>>> notifier.
>>> - * Also flush pending deferred work to make sure the
>>> interval
>>> - * tree is up to date before we add a new range
>>> - */
>>> - mutex_unlock(&svms->lock);
>>> - mmap_read_unlock(mm);
>>> - mmap_write_lock(mm);
>>> - write_locked = true;
>>> - goto retry_write_locked;
>>> - }
>>> - prange = svm_range_create_unregistered_range(adev, p, mm,
>>> addr);
>>> + prange = svm_range_create_unregistered_range(adev, p, mm,
>>> vma, addr);
>>> if (!prange) {
>>> pr_debug("failed to create unregistered range svms
>>> 0x%p address [0x%llx]\n",
>>> svms, addr);
>>> @@ -2634,8 +2617,8 @@ svm_range_restore_pages(struct amdgpu_device
>>> *adev, unsigned int pasid,
>>> goto out_unlock_svms;
>>> }
>>> }
>>> - if (write_locked)
>>> - mmap_write_downgrade(mm);
>>> +
>>> + mmap_write_downgrade(mm);
>>> mutex_lock(&prange->migrate_mutex);
>>> @@ -2652,7 +2635,7 @@ svm_range_restore_pages(struct amdgpu_device
>>> *adev, unsigned int pasid,
>>> goto out_unlock_range;
>>> }
>>> - if (!svm_fault_allowed(mm, addr, write_fault)) {
>>> + if (!svm_fault_allowed(vma, write_fault)) {
>>> pr_debug("fault addr 0x%llx no %s permission\n", addr,
>>> write_fault ? "write" : "read");
>>> r = -EPERM;
>>> @@ -2704,10 +2687,10 @@ svm_range_restore_pages(struct amdgpu_device
>>> *adev, unsigned int pasid,
>>> mutex_unlock(&prange->migrate_mutex);
>>> out_unlock_svms:
>>> mutex_unlock(&svms->lock);
>>> +out_unlock_mm:
>>> mmap_read_unlock(mm);
>>> svm_range_count_fault(adev, p, gpuidx);
>>> -
>>> out:
>>> kfd_unref_process(p);
>>>
More information about the amd-gfx
mailing list