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@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)