[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:34:01 UTC 2021
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.
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