On Fri Aug 14, 2026 at 9:32 AM CEST, Matthew Brost wrote:
> So let a driver set drm_gpuvm_exec::two_pass and have
> drm_gpuvm_exec_lock() acquire its locks in two steps:
>
>   DRM_GPUVM_EXEC_PASS_EARLY validates what the transaction already
>   holds. The GPUVM's own dma-resv is locked from the start, so that is
>   every private object, and the driver validates the evicted ones. The
>   pass also opportunistically locks the external objects which are
>   evicted, since those need validating anyway, and the driver validates
>   those too. The resident external objects are left unlocked, then
>
>   DRM_GPUVM_EXEC_PASS_LATE locks everything else, i.e. exactly what the
>   early pass left out. The driver validates anything which raced with
>   the early pass and does whatever needs every lock held, such as
>   attaching its job's fence.
>
> Both passes share one drm_exec transaction. The early pass keeps
> everything it locked and the late pass only ever adds to it, so there is
> no window in which another thread can undo the early pass' work, and no
> recheck or retry logic is needed. The extra.fn callback is invoked once
> per pass, with drm_gpuvm_exec::pass telling it which one it is in.
>
> Only the external objects are divided up like this. The passes have to
> agree on which objects belong to which, and the GPUVM's common dma-resv
> is what gives that, so it is held throughout.

That's a nice optimization!

> The early pass reads drm_gpuvm_bo::evicted without holding the object's
> dma-resv, that being the lock it is trying not to take. The race is
> benign: an object evicted just after being skipped is validated by the
> late pass instead, exactly as if it had been evicted a moment later
> still.

I only read this after I stumbled across this in the code below.  Leaving this
as-is is still a data race per LKMM and I'd expect KCSAN to flag it.

In any case, please also add the corresponding WRITE_ONCE() as well as a comment
that explains why in this specific case it is OK to read the value without the
dma-resv lock held and why ordering is not an issue.

> Two pass locking requires a DRM_GPUVM_RESV_PROTECTED drm_gpuvm.

This is unfortunate, I don't want to have any second class citizens.

That said, I think I can make nouveau switch to DRM_GPUVM_RESV_PROTECTED. With
this, only MSM is left, and it says

        * We mostly want to use DRM_GPUVM_RESV_PROTECTED, except that
        * makes drm_gpuvm_bo_evict() a no-op for extobjs (ie. we loose
        * tracking that an extobj is evicted) :facepalm:

which is probably similar to why nouveau didn't do it in the first place. I
could have a look after LPC so we can get rid of !DRM_GPUVM_RESV_PROTECTED
entirely, which I think would be great.

> Assisted-by: GitHub_Copilot:claude-opus-5

Please use Assisted-by: LLM instead.

> +/**
> + * DOC: Two pass locking

I think this should say that this is about "preparing" objects and object
validation.

Maybe "GPUVM EXEC two-pass locking"?

> +static bool
> +drm_gpuvm_prepare_skip(struct drm_gpuvm_bo *vm_bo,
> +                    enum drm_gpuvm_exec_pass pass)
> +{
> +     drm_gpuvm_pass_assert_held(vm_bo->vm, pass);
> +
> +     switch (pass) {
> +     case DRM_GPUVM_EXEC_PASS_EARLY:
> +             vm_bo->lock_skipped = !READ_ONCE(vm_bo->evicted);

Ick! Not a huge fan of this, but I guess it makes sense. AFAICS this can race
with drm_gpuvm_bo_evict() though.

> +int
> +drm_gpuvm_prepare_objects_pass(struct drm_gpuvm *gpuvm,
> +                            struct drm_exec *exec,
> +                            unsigned int num_fences,
> +                            enum drm_gpuvm_exec_pass pass)
> +{
> +     if (pass != DRM_GPUVM_EXEC_PASS_ALL &&
> +         drm_WARN_ON(gpuvm->drm, !drm_gpuvm_resv_protected(gpuvm)))

drm_WARN_ON_ONCE() seems to make more sense here.

> @@ -1360,6 +1603,10 @@ drm_gpuvm_exec_lock(struct drm_gpuvm_exec *vm_exec)
>       unsigned int num_fences = vm_exec->num_fences;
>       int ret;
>  
> +     if (vm_exec->two_pass &&
> +         drm_WARN_ON(gpuvm->drm, !drm_gpuvm_resv_protected(gpuvm)))

Same here...

> @@ -1422,6 +1680,9 @@ drm_gpuvm_exec_lock_array(struct drm_gpuvm_exec 
> *vm_exec,
>               unsigned int num_objs;
>       } args;
>  
> +     if (drm_WARN_ON(vm_exec->vm->drm, vm_exec->two_pass))

...and here.

> +int
> +drm_gpuvm_validate_pass(struct drm_gpuvm *gpuvm, struct drm_exec *exec,
> +                     enum drm_gpuvm_exec_pass pass)
>  {
>       const struct drm_gpuvm_ops *ops = gpuvm->ops;
>  
>       if (unlikely(!ops || !ops->vm_bo_validate))
>               return -EOPNOTSUPP;
>  
> +     if (pass != DRM_GPUVM_EXEC_PASS_ALL &&
> +         drm_WARN_ON(gpuvm->drm, !drm_gpuvm_resv_protected(gpuvm)))

That's really a lot of !drm_gpuvm_resv_protected() checks needed. :(

> +bool
> +drm_gpuvm_has_evicted(struct drm_gpuvm *gpuvm, enum drm_gpuvm_exec_pass pass)

The name is a bit confusing, as the scope of the function is limited to an
exec_pass, but not the VM in general.

Even though a bit verbose, I think drm_gpuvm_exec_pass_has_evicted() is better.
I'd probably also use the drm_gpuvm_exec_pass prefiy consistently for the
functions newly introduced for the feature.

> @@ -680,6 +799,18 @@ struct drm_gpuvm_bo {
>        */
>       bool evicted;
>  
> +     /**
> +      * @lock_skipped: Indicates that the current &drm_exec transaction does
> +      * not hold this &drm_gpuvm_bo's dma-resv, because
> +      * %DRM_GPUVM_EXEC_PASS_EARLY skipped it as not needing validation.
> +      * Unlike @evicted this is stable for the duration of a locking
> +      * sequence, which is what makes it safe for drm_gpuvm_validate_pass()
> +      * to key off, and what tells %DRM_GPUVM_EXEC_PASS_LATE which objects
> +      * are still missing. Field protected the same way as the &drm_gpuvm's
> +      * extobj list.
> +      */
> +     bool lock_skipped;

That's a bit nasty, but it works and I can't think of a cleaner solution.

Reply via email to