[PATCH 01/28] dma-buf: add dma_resv_for_each_fence_unlocked v7

Tvrtko Ursulin tvrtko.ursulin at linux.intel.com
Mon Oct 4 10:50:16 UTC 2021


On 04/10/2021 11:44, Christian König wrote:
> Am 04.10.21 um 12:34 schrieb Tvrtko Ursulin:
>>
>> On 04/10/2021 10:53, Christian König wrote:
>>> Am 04.10.21 um 11:29 schrieb Tvrtko Ursulin:
>>>>
>>>> On 01/10/2021 11:05, Christian König wrote:
>>>>> Abstract the complexity of iterating over all the fences
>>>>> in a dma_resv object.
>>>>>
>>>>> The new loop handles the whole RCU and retry dance and
>>>>> returns only fences where we can be sure we grabbed the
>>>>> right one.
>>>>>
>>>>> v2: fix accessing the shared fences while they might be freed,
>>>>>      improve kerneldoc, rename _cursor to _iter, add
>>>>>      dma_resv_iter_is_exclusive, add dma_resv_iter_begin/end
>>>>>
>>>>> v3: restructor the code, move rcu_read_lock()/unlock() into the
>>>>>      iterator, add dma_resv_iter_is_restarted()
>>>>>
>>>>> v4: fix NULL deref when no explicit fence exists, drop superflous
>>>>>      rcu_read_lock()/unlock() calls.
>>>>>
>>>>> v5: fix typos in the documentation
>>>>>
>>>>> v6: fix coding error when excl fence is NULL
>>>>>
>>>>> v7: one more logic fix
>>>>>
>>>>> Signed-off-by: Christian König <christian.koenig at amd.com>
>>>>> ---
>>>>>   drivers/dma-buf/dma-resv.c | 100 
>>>>> +++++++++++++++++++++++++++++++++++++
>>>>>   include/linux/dma-resv.h   |  95 +++++++++++++++++++++++++++++++++++
>>>>>   2 files changed, 195 insertions(+)
>>>>>
>>>>> diff --git a/drivers/dma-buf/dma-resv.c b/drivers/dma-buf/dma-resv.c
>>>>> index 84fbe60629e3..3cbcf66a137e 100644
>>>>> --- a/drivers/dma-buf/dma-resv.c
>>>>> +++ b/drivers/dma-buf/dma-resv.c
>>>>> @@ -323,6 +323,106 @@ void dma_resv_add_excl_fence(struct dma_resv 
>>>>> *obj, struct dma_fence *fence)
>>>>>   }
>>>>>   EXPORT_SYMBOL(dma_resv_add_excl_fence);
>>>>>   +/**
>>>>> + * dma_resv_iter_restart_unlocked - restart the unlocked iterator
>>>>> + * @cursor: The dma_resv_iter object to restart
>>>>> + *
>>>>> + * Restart the unlocked iteration by initializing the cursor object.
>>>>> + */
>>>>> +static void dma_resv_iter_restart_unlocked(struct dma_resv_iter 
>>>>> *cursor)
>>>>> +{
>>>>> +    cursor->seq = read_seqcount_begin(&cursor->obj->seq);
>>>>> +    cursor->index = -1;
>>>>> +    if (cursor->all_fences)
>>>>> +        cursor->fences = dma_resv_shared_list(cursor->obj);
>>>>> +    else
>>>>> +        cursor->fences = NULL;
>>>>> +    cursor->is_restarted = true;
>>>>> +}
>>>>> +
>>>>> +/**
>>>>> + * dma_resv_iter_walk_unlocked - walk over fences in a dma_resv obj
>>>>> + * @cursor: cursor to record the current position
>>>>> + *
>>>>> + * Return all the fences in the dma_resv object which are not yet 
>>>>> signaled.
>>>>> + * The returned fence has an extra local reference so will stay 
>>>>> alive.
>>>>> + * If a concurrent modify is detected the whole iteration is 
>>>>> started over again.
>>>>> + */
>>>>> +static void dma_resv_iter_walk_unlocked(struct dma_resv_iter *cursor)
>>>>> +{
>>>>> +    struct dma_resv *obj = cursor->obj;
>>>>> +
>>>>> +    do {
>>>>> +        /* Drop the reference from the previous round */
>>>>> +        dma_fence_put(cursor->fence);
>>>>> +
>>>>> +        if (cursor->index == -1) {
>>>>> +            cursor->fence = dma_resv_excl_fence(obj);
>>>>> +            cursor->index++;
>>>>> +            if (!cursor->fence)
>>>>> +                continue;
>>>>> +
>>>>> +        } else if (!cursor->fences ||
>>>>> +               cursor->index >= cursor->fences->shared_count) {
>>>>> +            cursor->fence = NULL;
>>>>> +            break;
>>>>> +
>>>>> +        } else {
>>>>> +            struct dma_resv_list *fences = cursor->fences;
>>>>> +            unsigned int idx = cursor->index++;
>>>>> +
>>>>> +            cursor->fence = rcu_dereference(fences->shared[idx]);
>>>>> +        }
>>>>> +        cursor->fence = dma_fence_get_rcu(cursor->fence);
>>>>
>>>> Worth having an assert dma_fence_get_rcu does not fail here? Not 
>>>> sure that I have seen debug build only asserts though on the DRM 
>>>> core side.
>>>
>>> That won't work. It's perfectly valid for dma_fence_get_rcu() to 
>>> return NULL when we are racing here. Keep in mind that we don't hold 
>>> any locks.
>>
>> Ah yes.. No need to change anything then, sorry for the confusion. I 
>> did not find any holes, the rest was just about how to maybe make the 
>> flow more obvious. Let me know if you want r-b now or later.
> 
> Now would be good. I've tried to make that more cleaner, but this only 
> lead to repeating the code more often.

Reviewed-by: Tvrtko Ursulin <tvrtko.ursulin at intel.com>

Regards,

Tvrtko

> 
> Regards,
> Christian.
> 
>>
>> Regards,
>>
>> Tvrtko
>>
>>> What we could do is to return NULL and repeat with a new sequence 
>>> immediately though.
>>>
>>>>
>>>> On the bike shedding front, would it be clearer if the continue 
>>>> condition on signaled fences was standalone, using the continue 
>>>> statement? I'd also possibly re-arrange the three if-else blocks so 
>>>> that the end of iteration is not sandwiched between blocks handling 
>>>> exclusive and shared, and flow tweaked a bit, like:
>>>>
>>>>   struct dma_fence *fence = cursor->fence;
>>>>   int index = cursor->index;
>>>>
>>>>   dma_fence_put(fence);
>>>>   fence = NULL;
>>>>
>>>> next:
>>>>   if (index == -1) {
>>>>     /* Try picking the exclusive fence. */
>>>>     index++;
>>>>     fence = dma_resv_excl_fence(obj);
>>>>     if (!fence)
>>>>         goto next;
>>>>   } else if (cursor->fences && index < cursor->fences->shared_count) {
>>>>       /* Try picking next shared fence. */
>>>>     struct dma_resv_list *fences = cursor->fences;
>>>>
>>>>     fence = rcu_dereference(fences->shared[index++]);
>>>>   }
>>>>
>>>>   if (fence) {
>>>>       if (dma_fence_is_signaled(fence))
>>>>         goto next; /* Skip signaled. */
>>>>
>>>>     fence = dma_fence_get_rcu(fence);
>>>>     WARN_ON(!fence);
>>>> }
>>>>
>>>>   cursor->fence = fence;
>>>>   cursor->index = index;
>>>>
>>>> (I started with a loop here but ended with goto based flow since it 
>>>> ended up more succinct.)
>>>>
>>>> At least if I don't have a handling flaw in there it looks like 
>>>> easier to follow flow to me. Plus picking a not signaled fence works 
>>>> without a reference FWIW.
>>>
>>> I strongly don't think that this will work correctly. You need to 
>>> grab a reference first when you want to call dma_fence_is_signaled(), 
>>> that's why I used the testbit approach initially.
>>>
>>>> How does it look to you?
>>>
>>> Mhm, let me try to reorder the loop once more.
>>>
>>> Thanks,
>>> Christian.
>>>
>>>>
>>>> Regards,
>>>>
>>>> Tvrtko
>>>>
>>>>> +        if (!cursor->fence || !dma_fence_is_signaled(cursor->fence))
>>>>> +            break;
>>>>> +    } while (true);
>>>>> +}
>>>>> +
>>>>> +/**
>>>>> + * dma_resv_iter_first_unlocked - first fence in an unlocked 
>>>>> dma_resv obj.
>>>>> + * @cursor: the cursor with the current position
>>>>> + *
>>>>> + * Returns the first fence from an unlocked dma_resv obj.
>>>>> + */
>>>>> +struct dma_fence *dma_resv_iter_first_unlocked(struct 
>>>>> dma_resv_iter *cursor)
>>>>> +{
>>>>> +    rcu_read_lock();
>>>>> +    do {
>>>>> +        dma_resv_iter_restart_unlocked(cursor);
>>>>> +        dma_resv_iter_walk_unlocked(cursor);
>>>>> +    } while (read_seqcount_retry(&cursor->obj->seq, cursor->seq));
>>>>> +    rcu_read_unlock();
>>>>> +
>>>>> +    return cursor->fence;
>>>>> +}
>>>>> +EXPORT_SYMBOL(dma_resv_iter_first_unlocked);
>>>>> +
>>>>> +/**
>>>>> + * dma_resv_iter_next_unlocked - next fence in an unlocked 
>>>>> dma_resv obj.
>>>>> + * @cursor: the cursor with the current position
>>>>> + *
>>>>> + * Returns the next fence from an unlocked dma_resv obj.
>>>>> + */
>>>>> +struct dma_fence *dma_resv_iter_next_unlocked(struct dma_resv_iter 
>>>>> *cursor)
>>>>> +{
>>>>> +    bool restart;
>>>>> +
>>>>> +    rcu_read_lock();
>>>>> +    cursor->is_restarted = false;
>>>>> +    restart = read_seqcount_retry(&cursor->obj->seq, cursor->seq);
>>>>> +    do {
>>>>> +        if (restart)
>>>>> +            dma_resv_iter_restart_unlocked(cursor);
>>>>> +        dma_resv_iter_walk_unlocked(cursor);
>>>>> +        restart = true;
>>>>> +    } while (read_seqcount_retry(&cursor->obj->seq, cursor->seq));
>>>>> +    rcu_read_unlock();
>>>>> +
>>>>> +    return cursor->fence;
>>>>> +}
>>>>> +EXPORT_SYMBOL(dma_resv_iter_next_unlocked);
>>>>> +
>>>>>   /**
>>>>>    * dma_resv_copy_fences - Copy all fences from src to dst.
>>>>>    * @dst: the destination reservation object
>>>>> diff --git a/include/linux/dma-resv.h b/include/linux/dma-resv.h
>>>>> index 9100dd3dc21f..5d7d28cb9008 100644
>>>>> --- a/include/linux/dma-resv.h
>>>>> +++ b/include/linux/dma-resv.h
>>>>> @@ -149,6 +149,101 @@ struct dma_resv {
>>>>>       struct dma_resv_list __rcu *fence;
>>>>>   };
>>>>>   +/**
>>>>> + * struct dma_resv_iter - current position into the dma_resv fences
>>>>> + *
>>>>> + * Don't touch this directly in the driver, use the accessor 
>>>>> function instead.
>>>>> + */
>>>>> +struct dma_resv_iter {
>>>>> +    /** @obj: The dma_resv object we iterate over */
>>>>> +    struct dma_resv *obj;
>>>>> +
>>>>> +    /** @all_fences: If all fences should be returned */
>>>>> +    bool all_fences;
>>>>> +
>>>>> +    /** @fence: the currently handled fence */
>>>>> +    struct dma_fence *fence;
>>>>> +
>>>>> +    /** @seq: sequence number to check for modifications */
>>>>> +    unsigned int seq;
>>>>> +
>>>>> +    /** @index: index into the shared fences */
>>>>> +    unsigned int index;
>>>>> +
>>>>> +    /** @fences: the shared fences */
>>>>> +    struct dma_resv_list *fences;
>>>>> +
>>>>> +    /** @is_restarted: true if this is the first returned fence */
>>>>> +    bool is_restarted;
>>>>> +};
>>>>> +
>>>>> +struct dma_fence *dma_resv_iter_first_unlocked(struct 
>>>>> dma_resv_iter *cursor);
>>>>> +struct dma_fence *dma_resv_iter_next_unlocked(struct dma_resv_iter 
>>>>> *cursor);
>>>>> +
>>>>> +/**
>>>>> + * dma_resv_iter_begin - initialize a dma_resv_iter object
>>>>> + * @cursor: The dma_resv_iter object to initialize
>>>>> + * @obj: The dma_resv object which we want to iterate over
>>>>> + * @all_fences: If all fences should be returned or just the 
>>>>> exclusive one
>>>>> + */
>>>>> +static inline void dma_resv_iter_begin(struct dma_resv_iter *cursor,
>>>>> +                       struct dma_resv *obj,
>>>>> +                       bool all_fences)
>>>>> +{
>>>>> +    cursor->obj = obj;
>>>>> +    cursor->all_fences = all_fences;
>>>>> +    cursor->fence = NULL;
>>>>> +}
>>>>> +
>>>>> +/**
>>>>> + * dma_resv_iter_end - cleanup a dma_resv_iter object
>>>>> + * @cursor: the dma_resv_iter object which should be cleaned up
>>>>> + *
>>>>> + * Make sure that the reference to the fence in the cursor is 
>>>>> properly
>>>>> + * dropped.
>>>>> + */
>>>>> +static inline void dma_resv_iter_end(struct dma_resv_iter *cursor)
>>>>> +{
>>>>> +    dma_fence_put(cursor->fence);
>>>>> +}
>>>>> +
>>>>> +/**
>>>>> + * dma_resv_iter_is_exclusive - test if the current fence is the 
>>>>> exclusive one
>>>>> + * @cursor: the cursor of the current position
>>>>> + *
>>>>> + * Returns true if the currently returned fence is the exclusive one.
>>>>> + */
>>>>> +static inline bool dma_resv_iter_is_exclusive(struct dma_resv_iter 
>>>>> *cursor)
>>>>> +{
>>>>> +    return cursor->index == -1;
>>>>> +}
>>>>> +
>>>>> +/**
>>>>> + * dma_resv_iter_is_restarted - test if this is the first fence 
>>>>> after a restart
>>>>> + * @cursor: the cursor with the current position
>>>>> + *
>>>>> + * Return true if this is the first fence in an iteration after a 
>>>>> restart.
>>>>> + */
>>>>> +static inline bool dma_resv_iter_is_restarted(struct dma_resv_iter 
>>>>> *cursor)
>>>>> +{
>>>>> +    return cursor->is_restarted;
>>>>> +}
>>>>> +
>>>>> +/**
>>>>> + * dma_resv_for_each_fence_unlocked - unlocked fence iterator
>>>>> + * @cursor: a struct dma_resv_iter pointer
>>>>> + * @fence: the current fence
>>>>> + *
>>>>> + * Iterate over the fences in a struct dma_resv object without 
>>>>> holding the
>>>>> + * &dma_resv.lock and using RCU instead. The cursor needs to be 
>>>>> initialized
>>>>> + * with dma_resv_iter_begin() and cleaned up with 
>>>>> dma_resv_iter_end(). Inside
>>>>> + * the iterator a reference to the dma_fence is held and the RCU 
>>>>> lock dropped.
>>>>> + * When the dma_resv is modified the iteration starts over again.
>>>>> + */
>>>>> +#define dma_resv_for_each_fence_unlocked(cursor, fence)            \
>>>>> +    for (fence = dma_resv_iter_first_unlocked(cursor);        \
>>>>> +         fence; fence = dma_resv_iter_next_unlocked(cursor))
>>>>> +
>>>>>   #define dma_resv_held(obj) lockdep_is_held(&(obj)->lock.base)
>>>>>   #define dma_resv_assert_held(obj) 
>>>>> lockdep_assert_held(&(obj)->lock.base)
>>>>>
>>>
> 


More information about the dri-devel mailing list