diff mbox series

[01/26] dma-buf: add dma_resv_for_each_fence_unlocked v2

Message ID 20210917123513.1106-2-christian.koenig@amd.com (mailing list archive)
State New, archived
Headers show
Series [01/26] dma-buf: add dma_resv_for_each_fence_unlocked v2 | expand

Commit Message

Christian König Sept. 17, 2021, 12:34 p.m. UTC
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

Signed-off-by: Christian König <christian.koenig@amd.com>
---
 drivers/dma-buf/dma-resv.c | 61 +++++++++++++++++++++++++++
 include/linux/dma-resv.h   | 84 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 145 insertions(+)

Comments

Daniel Vetter Sept. 17, 2021, 1:23 p.m. UTC | #1
On Fri, Sep 17, 2021 at 02:34:48PM +0200, 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
> 
> Signed-off-by: Christian König <christian.koenig@amd.com>
> ---
>  drivers/dma-buf/dma-resv.c | 61 +++++++++++++++++++++++++++
>  include/linux/dma-resv.h   | 84 ++++++++++++++++++++++++++++++++++++++
>  2 files changed, 145 insertions(+)
> 
> diff --git a/drivers/dma-buf/dma-resv.c b/drivers/dma-buf/dma-resv.c
> index 84fbe60629e3..3e77cad2c9d4 100644
> --- a/drivers/dma-buf/dma-resv.c
> +++ b/drivers/dma-buf/dma-resv.c
> @@ -323,6 +323,67 @@ void dma_resv_add_excl_fence(struct dma_resv *obj, struct dma_fence *fence)
>  }
>  EXPORT_SYMBOL(dma_resv_add_excl_fence);
>  
> +/**
> + * dma_resv_iter_walk_unlocked - walk over fences in a dma_resv obj
> + * @cursor: cursor to record the current position
> + * @first: if we should start over
> + *
> + * 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 iterration is started over again.
> + */
> +struct dma_fence *dma_resv_iter_walk_unlocked(struct dma_resv_iter *cursor,

Bit ocd, but I'd still just call that iter_next.

> +					      bool first)

Hm I'd put all the init code into iter_begin ...

> +{
> +	struct dma_resv *obj = cursor->obj;

Aren't we missing rcu_read_lock() around the entire thing here?

> +
> +	first |= read_seqcount_retry(&obj->seq, cursor->seq);
> +	do {
> +		/* Drop the reference from the previous round */
> +		dma_fence_put(cursor->fence);
> +
> +		cursor->is_first = first;
> +		if (first) {
> +			cursor->seq = read_seqcount_begin(&obj->seq);
> +			cursor->index = -1;
> +			cursor->fences = dma_resv_shared_list(obj);

And then also call iter_begin from here. That way we guarantee that
read_seqcount_begin is always called before _retry(). It's not a problem
with the seqcount implementation (I think at least), but it definitely
looks funny.

Calling iter_begin here also makes it clear that we're essentially
restarting.

> +
> +			cursor->fence = dma_resv_excl_fence(obj);
> +			if (cursor->fence &&
> +			    test_bit(DMA_FENCE_FLAG_SIGNALED_BIT,

Please use the right dma_fence wrapper here for this and don't look at the
bits/flags outside of dma_fence.[hc] code. I just realized that we don't
have the right amount of barriers in there for the fastpath, i.e. if we
have:

x = 0; /* static initializer */

thread a
	x = 1;
	dma_fence_signal(fence);


thread b;
	if (dma_fence_is_signalled(fence))
		printk("%i\n", x);

Then you might actually be able to observe x == 0 in thread b. Which is
not what we want at all.

So no open-coding of dma_fence flag bits code outside of drm_fence.[hc]
please. And yes i915-gem code is unfortunately a disaster.

> +				     &cursor->fence->flags))
> +				cursor->fence = NULL;
> +		} else {
> +			cursor->fence = NULL;
> +		}
> +
> +		if (cursor->fence) {
> +			cursor->fence = dma_fence_get_rcu(cursor->fence);
> +		} else if (cursor->all_fences && cursor->fences) {
> +			struct dma_resv_list *fences = cursor->fences;
> +
> +			while (++cursor->index < fences->shared_count) {
> +				cursor->fence = rcu_dereference(
> +					fences->shared[cursor->index]);
> +				if (!test_bit(DMA_FENCE_FLAG_SIGNALED_BIT,
> +					      &cursor->fence->flags))
> +					break;
> +			}
> +			if (cursor->index < fences->shared_count)
> +				cursor->fence =
> +					dma_fence_get_rcu(cursor->fence);
> +			else
> +				cursor->fence = NULL;
> +		}

The control flow here is very hairy, but I'm not sure how to best do this.
With my suggestion to move the read_seqcount_begin into iter_begin maybe
something like this:

iter_next()
{
	do {
		dma_fence_put(cursor->fence)
		cursor->fence = NULL;

		if (cursor->index == -1) { /* reset by iter_begin()
			cursor->fence = get_exclusive();
			cusor->index++;
		} else {
			cursor->fence = shared_fences[++cursor->index]
		}

		if (!dma_fence_is_signalled(cursor->fence))
			continue; /* just grab the next fence. */

		cursor->fence =  dma_fence_get_rcu(cursor->fence);

		if (!cursor->fence || read_seqcount_retry()) {
			/* we lost the race, restart completely */
			iter_begin(); /* ->fence will be cleaned up at beginning of the loop */
			continue;
		}

		return cursor->fence;
	} while (true);
}

Maybe I missed something, but that avoids the duplication of all the
tricky code, i.e. checking for signalling, rcu protected conditional
fence_get, and the retry is also nicely at the end.
> +
> +		/* For the eventually next round */
> +		first = true;
> +	} while (read_seqcount_retry(&obj->seq, cursor->seq));
> +
> +	return cursor->fence;
> +}
> +EXPORT_SYMBOL_GPL(dma_resv_iter_walk_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..693d16117153 100644
> --- a/include/linux/dma-resv.h
> +++ b/include/linux/dma-resv.h
> @@ -149,6 +149,90 @@ 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 */

If you go with my suggestion (assuming it works): Please add "-1 indicates
to pick the exclusive fence instead."

> +	unsigned int index;
> +
> +	/** @fences: the shared fences */
> +	struct dma_resv_list *fences;
> +
> +	/** @is_first: true if this is the first returned fence */
> +	bool is_first;

I think if we just rely on -1 == exclusive fence/is_first we don't need
this one here?

> +};
> +
> +struct dma_fence *dma_resv_iter_walk_unlocked(struct dma_resv_iter *cursor,
> +					      bool first);
> +
> +/**
> + * 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 iterator over
> + * @all_fences: If all fences should be returned or just the exclusive one

Please add: "Callers must clean up the iterator with dma_resv_iter_end()."

> + */
> +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.

Please add:

"This function must be called every time dma_resv_iter_begin() was called
to clean up any references."
> + */
> +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_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. The RCU read side lock must be hold when using this, but can
> + * be dropped and re-taken as necessary inside the loop. The cursor needs to be
> + * initialized with dma_resv_iter_begin_unlocked() and cleaned up with

We don't have an _unlocked version?

> + * dma_resv_iter_end_unlocked().
> + */
> +#define dma_resv_for_each_fence_unlocked(cursor, fence)			\
> +	for (fence = dma_resv_iter_walk_unlocked(cursor, true);		\
> +	     fence; fence = dma_resv_iter_walk_unlocked(cursor, false))
> +
>  #define dma_resv_held(obj) lockdep_is_held(&(obj)->lock.base)
>  #define dma_resv_assert_held(obj) lockdep_assert_held(&(obj)->lock.base)
>  
> -- 
> 2.25.1
>
Tvrtko Ursulin Sept. 20, 2021, 8:43 a.m. UTC | #2
On 17/09/2021 14:23, Daniel Vetter wrote:
> On Fri, Sep 17, 2021 at 02:34:48PM +0200, 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
>>
>> Signed-off-by: Christian König <christian.koenig@amd.com>
>> ---
>>   drivers/dma-buf/dma-resv.c | 61 +++++++++++++++++++++++++++
>>   include/linux/dma-resv.h   | 84 ++++++++++++++++++++++++++++++++++++++
>>   2 files changed, 145 insertions(+)
>>
>> diff --git a/drivers/dma-buf/dma-resv.c b/drivers/dma-buf/dma-resv.c
>> index 84fbe60629e3..3e77cad2c9d4 100644
>> --- a/drivers/dma-buf/dma-resv.c
>> +++ b/drivers/dma-buf/dma-resv.c
>> @@ -323,6 +323,67 @@ void dma_resv_add_excl_fence(struct dma_resv *obj, struct dma_fence *fence)
>>   }
>>   EXPORT_SYMBOL(dma_resv_add_excl_fence);
>>   
>> +/**
>> + * dma_resv_iter_walk_unlocked - walk over fences in a dma_resv obj
>> + * @cursor: cursor to record the current position
>> + * @first: if we should start over
>> + *
>> + * 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 iterration is started over again.
>> + */
>> +struct dma_fence *dma_resv_iter_walk_unlocked(struct dma_resv_iter *cursor,
> 
> Bit ocd, but I'd still just call that iter_next.
> 
>> +					      bool first)
> 
> Hm I'd put all the init code into iter_begin ...

@Christian:

Could you engineer something in here which would, at least in debug 
builds, catch failures to call "iter begin" before using the iterator macro?

> 
>> +{
>> +	struct dma_resv *obj = cursor->obj;
> 
> Aren't we missing rcu_read_lock() around the entire thing here?
> 
>> +
>> +	first |= read_seqcount_retry(&obj->seq, cursor->seq);
>> +	do {
>> +		/* Drop the reference from the previous round */
>> +		dma_fence_put(cursor->fence);
>> +
>> +		cursor->is_first = first;
>> +		if (first) {
>> +			cursor->seq = read_seqcount_begin(&obj->seq);
>> +			cursor->index = -1;
>> +			cursor->fences = dma_resv_shared_list(obj);
> 
> And then also call iter_begin from here. That way we guarantee that
> read_seqcount_begin is always called before _retry(). It's not a problem
> with the seqcount implementation (I think at least), but it definitely
> looks funny.
> 
> Calling iter_begin here also makes it clear that we're essentially
> restarting.
> 
>> +
>> +			cursor->fence = dma_resv_excl_fence(obj);
>> +			if (cursor->fence &&
>> +			    test_bit(DMA_FENCE_FLAG_SIGNALED_BIT,
> 
> Please use the right dma_fence wrapper here for this and don't look at the
> bits/flags outside of dma_fence.[hc] code. I just realized that we don't
> have the right amount of barriers in there for the fastpath, i.e. if we
> have:
> 
> x = 0; /* static initializer */
> 
> thread a
> 	x = 1;
> 	dma_fence_signal(fence);
> 
> 
> thread b;
> 	if (dma_fence_is_signalled(fence))
> 		printk("%i\n", x);
> 
> Then you might actually be able to observe x == 0 in thread b. Which is
> not what we want at all.

@Daniel:

What do you mean here - in terms of if 'x' is "external" (not part of 
dma-fence), then are you suggesting dma-fence code should serialise it 
by using barriers?

That would sound incorrect to me, or in other words, I think it's fine 
if x == 0 is observed in your example thread B since that code is mixing 
external data with dma-fence.

Hm also, there is that annoying bit where by using dma_fence_is_signaled 
any code becomes a fence signaling critical path, which I never bought 
into. There should be a way to test the signaled status without actually 
doing the signaling. Or I am misunderstanding something so badly that is 
really really has to be like this?

> So no open-coding of dma_fence flag bits code outside of drm_fence.[hc]
> please. And yes i915-gem code is unfortunately a disaster.

Don't even miss an opportunity for some good trashing no? :D

But yes, deconstructed dma_fence_signal I thought we were supposed to 
add to core. Or at least propose, don't exactly remember how that went.

> 
>> +				     &cursor->fence->flags))
>> +				cursor->fence = NULL;
>> +		} else {
>> +			cursor->fence = NULL;
>> +		}
>> +
>> +		if (cursor->fence) {
>> +			cursor->fence = dma_fence_get_rcu(cursor->fence);
>> +		} else if (cursor->all_fences && cursor->fences) {
>> +			struct dma_resv_list *fences = cursor->fences;
>> +
>> +			while (++cursor->index < fences->shared_count) {
>> +				cursor->fence = rcu_dereference(
>> +					fences->shared[cursor->index]);
>> +				if (!test_bit(DMA_FENCE_FLAG_SIGNALED_BIT,
>> +					      &cursor->fence->flags))
>> +					break;
>> +			}
>> +			if (cursor->index < fences->shared_count)
>> +				cursor->fence =
>> +					dma_fence_get_rcu(cursor->fence);
>> +			else
>> +				cursor->fence = NULL;
>> +		}
> 
> The control flow here is very hairy, but I'm not sure how to best do this.
> With my suggestion to move the read_seqcount_begin into iter_begin maybe
> something like this:
> 
> iter_next()
> {
> 	do {
> 		dma_fence_put(cursor->fence)
> 		cursor->fence = NULL;
> 
> 		if (cursor->index == -1) { /* reset by iter_begin()
> 			cursor->fence = get_exclusive();
> 			cusor->index++;
> 		} else {
> 			cursor->fence = shared_fences[++cursor->index]
> 		}
> 
> 		if (!dma_fence_is_signalled(cursor->fence))
> 			continue; /* just grab the next fence. */
> 
> 		cursor->fence =  dma_fence_get_rcu(cursor->fence);
> 
> 		if (!cursor->fence || read_seqcount_retry()) {
> 			/* we lost the race, restart completely */
> 			iter_begin(); /* ->fence will be cleaned up at beginning of the loop */
> 			continue;
> 		}
> 
> 		return cursor->fence;
> 	} while (true);
> }
> 
> Maybe I missed something, but that avoids the duplication of all the
> tricky code, i.e. checking for signalling, rcu protected conditional
> fence_get, and the retry is also nicely at the end.
>> +
>> +		/* For the eventually next round */
>> +		first = true;
>> +	} while (read_seqcount_retry(&obj->seq, cursor->seq));
>> +
>> +	return cursor->fence;
>> +}
>> +EXPORT_SYMBOL_GPL(dma_resv_iter_walk_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..693d16117153 100644
>> --- a/include/linux/dma-resv.h
>> +++ b/include/linux/dma-resv.h
>> @@ -149,6 +149,90 @@ 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 */
> 
> If you go with my suggestion (assuming it works): Please add "-1 indicates
> to pick the exclusive fence instead."
> 
>> +	unsigned int index;
>> +
>> +	/** @fences: the shared fences */
>> +	struct dma_resv_list *fences;
>> +
>> +	/** @is_first: true if this is the first returned fence */
>> +	bool is_first;
> 
> I think if we just rely on -1 == exclusive fence/is_first we don't need
> this one here?
> 
>> +};
>> +
>> +struct dma_fence *dma_resv_iter_walk_unlocked(struct dma_resv_iter *cursor,
>> +					      bool first);
>> +
>> +/**
>> + * 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 iterator over
>> + * @all_fences: If all fences should be returned or just the exclusive one
> 
> Please add: "Callers must clean up the iterator with dma_resv_iter_end()."
> 
>> + */
>> +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.
> 
> Please add:
> 
> "This function must be called every time dma_resv_iter_begin() was called
> to clean up any references."
>> + */
>> +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_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. The RCU read side lock must be hold when using this, but can
>> + * be dropped and re-taken as necessary inside the loop. The cursor needs to be
>> + * initialized with dma_resv_iter_begin_unlocked() and cleaned up with
> 
> We don't have an _unlocked version?

@Christian:

I'd also mention that the fence reference is held during the walk so 
someone is less likely to grab extra ones.

> 
>> + * dma_resv_iter_end_unlocked().
>> + */
>> +#define dma_resv_for_each_fence_unlocked(cursor, fence)			\
>> +	for (fence = dma_resv_iter_walk_unlocked(cursor, true);		\
>> +	     fence; fence = dma_resv_iter_walk_unlocked(cursor, false))
>> +
>>   #define dma_resv_held(obj) lockdep_is_held(&(obj)->lock.base)
>>   #define dma_resv_assert_held(obj) lockdep_assert_held(&(obj)->lock.base)
>>   
>> -- 
>> 2.25.1
>>
> 

Regards,

Tvrtko
Christian König Sept. 20, 2021, 10:09 a.m. UTC | #3
Am 20.09.21 um 10:43 schrieb Tvrtko Ursulin:
> On 17/09/2021 14:23, Daniel Vetter wrote:
>> On Fri, Sep 17, 2021 at 02:34:48PM +0200, 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
>>>
>>> Signed-off-by: Christian König <christian.koenig@amd.com>
>>> ---
>>>   drivers/dma-buf/dma-resv.c | 61 +++++++++++++++++++++++++++
>>>   include/linux/dma-resv.h   | 84 
>>> ++++++++++++++++++++++++++++++++++++++
>>>   2 files changed, 145 insertions(+)
>>>
>>> diff --git a/drivers/dma-buf/dma-resv.c b/drivers/dma-buf/dma-resv.c
>>> index 84fbe60629e3..3e77cad2c9d4 100644
>>> --- a/drivers/dma-buf/dma-resv.c
>>> +++ b/drivers/dma-buf/dma-resv.c
>>> @@ -323,6 +323,67 @@ void dma_resv_add_excl_fence(struct dma_resv 
>>> *obj, struct dma_fence *fence)
>>>   }
>>>   EXPORT_SYMBOL(dma_resv_add_excl_fence);
>>>   +/**
>>> + * dma_resv_iter_walk_unlocked - walk over fences in a dma_resv obj
>>> + * @cursor: cursor to record the current position
>>> + * @first: if we should start over
>>> + *
>>> + * 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 iterration is 
>>> started over again.
>>> + */
>>> +struct dma_fence *dma_resv_iter_walk_unlocked(struct dma_resv_iter 
>>> *cursor,
>>
>> Bit ocd, but I'd still just call that iter_next.
>>
>>> +                          bool first)
>>
>> Hm I'd put all the init code into iter_begin ...
>
> @Christian:
>
> Could you engineer something in here which would, at least in debug 
> builds, catch failures to call "iter begin" before using the iterator 
> macro?

Yeah, I've already played with the thought of somehow teaching lockdep 
that. But then abandoned this as abusive of lockdep.

>
>>
>>> +{
>>> +    struct dma_resv *obj = cursor->obj;
>>
>> Aren't we missing rcu_read_lock() around the entire thing here?
>>
>>> +
>>> +    first |= read_seqcount_retry(&obj->seq, cursor->seq);
>>> +    do {
>>> +        /* Drop the reference from the previous round */
>>> +        dma_fence_put(cursor->fence);
>>> +
>>> +        cursor->is_first = first;
>>> +        if (first) {
>>> +            cursor->seq = read_seqcount_begin(&obj->seq);
>>> +            cursor->index = -1;
>>> +            cursor->fences = dma_resv_shared_list(obj);
>>
>> And then also call iter_begin from here. That way we guarantee that
>> read_seqcount_begin is always called before _retry(). It's not a problem
>> with the seqcount implementation (I think at least), but it definitely
>> looks funny.
>>
>> Calling iter_begin here also makes it clear that we're essentially
>> restarting.
>>
>>> +
>>> +            cursor->fence = dma_resv_excl_fence(obj);
>>> +            if (cursor->fence &&
>>> +                test_bit(DMA_FENCE_FLAG_SIGNALED_BIT,
>>
>> Please use the right dma_fence wrapper here for this and don't look 
>> at the
>> bits/flags outside of dma_fence.[hc] code. I just realized that we don't
>> have the right amount of barriers in there for the fastpath, i.e. if we
>> have:
>>
>> x = 0; /* static initializer */
>>
>> thread a
>>     x = 1;
>>     dma_fence_signal(fence);
>>
>>
>> thread b;
>>     if (dma_fence_is_signalled(fence))
>>         printk("%i\n", x);
>>
>> Then you might actually be able to observe x == 0 in thread b. Which is
>> not what we want at all.
>
> @Daniel:
>
> What do you mean here - in terms of if 'x' is "external" (not part of 
> dma-fence), then are you suggesting dma-fence code should serialise it 
> by using barriers?
>
> That would sound incorrect to me, or in other words, I think it's fine 
> if x == 0 is observed in your example thread B since that code is 
> mixing external data with dma-fence.

No, Daniel is right. The problem is that on architectures other than x86 
barriers are per memory address (or rather cache line in practice).

So you need to be really careful that you see the fully consistent state 
and not just one variable but others in the old state.

But this was buggy before as well. I'm just pulling the existing test 
into the new iterator.

>
> Hm also, there is that annoying bit where by using 
> dma_fence_is_signaled any code becomes a fence signaling critical 
> path, which I never bought into. There should be a way to test the 
> signaled status without actually doing the signaling. Or I am 
> misunderstanding something so badly that is really really has to be 
> like this?

You are mixing things up. Testing is unproblematic, signaling is the 
problematic one.

>
>> So no open-coding of dma_fence flag bits code outside of drm_fence.[hc]
>> please. And yes i915-gem code is unfortunately a disaster.
>
> Don't even miss an opportunity for some good trashing no? :D
>
> But yes, deconstructed dma_fence_signal I thought we were supposed to 
> add to core. Or at least propose, don't exactly remember how that went.

The problem is that you need to grab a reference to call 
dma_fence_signal while testing the flag works without one.

Regards,
Christian.

>
>>
>>> + &cursor->fence->flags))
>>> +                cursor->fence = NULL;
>>> +        } else {
>>> +            cursor->fence = NULL;
>>> +        }
>>> +
>>> +        if (cursor->fence) {
>>> +            cursor->fence = dma_fence_get_rcu(cursor->fence);
>>> +        } else if (cursor->all_fences && cursor->fences) {
>>> +            struct dma_resv_list *fences = cursor->fences;
>>> +
>>> +            while (++cursor->index < fences->shared_count) {
>>> +                cursor->fence = rcu_dereference(
>>> +                    fences->shared[cursor->index]);
>>> +                if (!test_bit(DMA_FENCE_FLAG_SIGNALED_BIT,
>>> +                          &cursor->fence->flags))
>>> +                    break;
>>> +            }
>>> +            if (cursor->index < fences->shared_count)
>>> +                cursor->fence =
>>> +                    dma_fence_get_rcu(cursor->fence);
>>> +            else
>>> +                cursor->fence = NULL;
>>> +        }
>>
>> The control flow here is very hairy, but I'm not sure how to best do 
>> this.
>> With my suggestion to move the read_seqcount_begin into iter_begin maybe
>> something like this:
>>
>> iter_next()
>> {
>>     do {
>>         dma_fence_put(cursor->fence)
>>         cursor->fence = NULL;
>>
>>         if (cursor->index == -1) { /* reset by iter_begin()
>>             cursor->fence = get_exclusive();
>>             cusor->index++;
>>         } else {
>>             cursor->fence = shared_fences[++cursor->index]
>>         }
>>
>>         if (!dma_fence_is_signalled(cursor->fence))
>>             continue; /* just grab the next fence. */
>>
>>         cursor->fence =  dma_fence_get_rcu(cursor->fence);
>>
>>         if (!cursor->fence || read_seqcount_retry()) {
>>             /* we lost the race, restart completely */
>>             iter_begin(); /* ->fence will be cleaned up at beginning 
>> of the loop */
>>             continue;
>>         }
>>
>>         return cursor->fence;
>>     } while (true);
>> }
>>
>> Maybe I missed something, but that avoids the duplication of all the
>> tricky code, i.e. checking for signalling, rcu protected conditional
>> fence_get, and the retry is also nicely at the end.
>>> +
>>> +        /* For the eventually next round */
>>> +        first = true;
>>> +    } while (read_seqcount_retry(&obj->seq, cursor->seq));
>>> +
>>> +    return cursor->fence;
>>> +}
>>> +EXPORT_SYMBOL_GPL(dma_resv_iter_walk_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..693d16117153 100644
>>> --- a/include/linux/dma-resv.h
>>> +++ b/include/linux/dma-resv.h
>>> @@ -149,6 +149,90 @@ 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 */
>>
>> If you go with my suggestion (assuming it works): Please add "-1 
>> indicates
>> to pick the exclusive fence instead."
>>
>>> +    unsigned int index;
>>> +
>>> +    /** @fences: the shared fences */
>>> +    struct dma_resv_list *fences;
>>> +
>>> +    /** @is_first: true if this is the first returned fence */
>>> +    bool is_first;
>>
>> I think if we just rely on -1 == exclusive fence/is_first we don't need
>> this one here?
>>
>>> +};
>>> +
>>> +struct dma_fence *dma_resv_iter_walk_unlocked(struct dma_resv_iter 
>>> *cursor,
>>> +                          bool first);
>>> +
>>> +/**
>>> + * 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 iterator over
>>> + * @all_fences: If all fences should be returned or just the 
>>> exclusive one
>>
>> Please add: "Callers must clean up the iterator with 
>> dma_resv_iter_end()."
>>
>>> + */
>>> +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.
>>
>> Please add:
>>
>> "This function must be called every time dma_resv_iter_begin() was 
>> called
>> to clean up any references."
>>> + */
>>> +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_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. The RCU read side lock must be hold when using 
>>> this, but can
>>> + * be dropped and re-taken as necessary inside the loop. The cursor 
>>> needs to be
>>> + * initialized with dma_resv_iter_begin_unlocked() and cleaned up with
>>
>> We don't have an _unlocked version?
>
> @Christian:
>
> I'd also mention that the fence reference is held during the walk so 
> someone is less likely to grab extra ones.
>
>>
>>> + * dma_resv_iter_end_unlocked().
>>> + */
>>> +#define dma_resv_for_each_fence_unlocked(cursor, fence)            \
>>> +    for (fence = dma_resv_iter_walk_unlocked(cursor, true);        \
>>> +         fence; fence = dma_resv_iter_walk_unlocked(cursor, false))
>>> +
>>>   #define dma_resv_held(obj) lockdep_is_held(&(obj)->lock.base)
>>>   #define dma_resv_assert_held(obj) 
>>> lockdep_assert_held(&(obj)->lock.base)
>>>   --
>>> 2.25.1
>>>
>>
>
> Regards,
>
> Tvrtko
>
Tvrtko Ursulin Sept. 20, 2021, 10:26 a.m. UTC | #4
On 20/09/2021 11:09, Christian König wrote:
> Am 20.09.21 um 10:43 schrieb Tvrtko Ursulin:
>> On 17/09/2021 14:23, Daniel Vetter wrote:
>>> On Fri, Sep 17, 2021 at 02:34:48PM +0200, 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
>>>>
>>>> Signed-off-by: Christian König <christian.koenig@amd.com>
>>>> ---
>>>>   drivers/dma-buf/dma-resv.c | 61 +++++++++++++++++++++++++++
>>>>   include/linux/dma-resv.h   | 84 
>>>> ++++++++++++++++++++++++++++++++++++++
>>>>   2 files changed, 145 insertions(+)
>>>>
>>>> diff --git a/drivers/dma-buf/dma-resv.c b/drivers/dma-buf/dma-resv.c
>>>> index 84fbe60629e3..3e77cad2c9d4 100644
>>>> --- a/drivers/dma-buf/dma-resv.c
>>>> +++ b/drivers/dma-buf/dma-resv.c
>>>> @@ -323,6 +323,67 @@ void dma_resv_add_excl_fence(struct dma_resv 
>>>> *obj, struct dma_fence *fence)
>>>>   }
>>>>   EXPORT_SYMBOL(dma_resv_add_excl_fence);
>>>>   +/**
>>>> + * dma_resv_iter_walk_unlocked - walk over fences in a dma_resv obj
>>>> + * @cursor: cursor to record the current position
>>>> + * @first: if we should start over
>>>> + *
>>>> + * 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 iterration is 
>>>> started over again.
>>>> + */
>>>> +struct dma_fence *dma_resv_iter_walk_unlocked(struct dma_resv_iter 
>>>> *cursor,
>>>
>>> Bit ocd, but I'd still just call that iter_next.
>>>
>>>> +                          bool first)
>>>
>>> Hm I'd put all the init code into iter_begin ...
>>
>> @Christian:
>>
>> Could you engineer something in here which would, at least in debug 
>> builds, catch failures to call "iter begin" before using the iterator 
>> macro?
> 
> Yeah, I've already played with the thought of somehow teaching lockdep 
> that. But then abandoned this as abusive of lockdep.

Yes probably not lockdep but would need to be a separate build time 
option akin to DEBUG_WW_MUTEXES and similar.

>>
>>>
>>>> +{
>>>> +    struct dma_resv *obj = cursor->obj;
>>>
>>> Aren't we missing rcu_read_lock() around the entire thing here?
>>>
>>>> +
>>>> +    first |= read_seqcount_retry(&obj->seq, cursor->seq);
>>>> +    do {
>>>> +        /* Drop the reference from the previous round */
>>>> +        dma_fence_put(cursor->fence);
>>>> +
>>>> +        cursor->is_first = first;
>>>> +        if (first) {
>>>> +            cursor->seq = read_seqcount_begin(&obj->seq);
>>>> +            cursor->index = -1;
>>>> +            cursor->fences = dma_resv_shared_list(obj);
>>>
>>> And then also call iter_begin from here. That way we guarantee that
>>> read_seqcount_begin is always called before _retry(). It's not a problem
>>> with the seqcount implementation (I think at least), but it definitely
>>> looks funny.
>>>
>>> Calling iter_begin here also makes it clear that we're essentially
>>> restarting.
>>>
>>>> +
>>>> +            cursor->fence = dma_resv_excl_fence(obj);
>>>> +            if (cursor->fence &&
>>>> +                test_bit(DMA_FENCE_FLAG_SIGNALED_BIT,
>>>
>>> Please use the right dma_fence wrapper here for this and don't look 
>>> at the
>>> bits/flags outside of dma_fence.[hc] code. I just realized that we don't
>>> have the right amount of barriers in there for the fastpath, i.e. if we
>>> have:
>>>
>>> x = 0; /* static initializer */
>>>
>>> thread a
>>>     x = 1;
>>>     dma_fence_signal(fence);
>>>
>>>
>>> thread b;
>>>     if (dma_fence_is_signalled(fence))
>>>         printk("%i\n", x);
>>>
>>> Then you might actually be able to observe x == 0 in thread b. Which is
>>> not what we want at all.
>>
>> @Daniel:
>>
>> What do you mean here - in terms of if 'x' is "external" (not part of 
>> dma-fence), then are you suggesting dma-fence code should serialise it 
>> by using barriers?
>>
>> That would sound incorrect to me, or in other words, I think it's fine 
>> if x == 0 is observed in your example thread B since that code is 
>> mixing external data with dma-fence.
> 
> No, Daniel is right. The problem is that on architectures other than x86 
> barriers are per memory address (or rather cache line in practice).
> 
> So you need to be really careful that you see the fully consistent state 
> and not just one variable but others in the old state.

I don't see it yet - what are the variables we are talking about here? 
Ordering relating to the iterator code in here or something truly external?

Iterator can obviously race and "return" and already signaled fence 
(transitioned from unsignaled to signaled between iterator checking and 
deciding to walk it). But that I don't think you can, or plan to, fix.

> 
> But this was buggy before as well. I'm just pulling the existing test 
> into the new iterator.

Okay.

> 
>>
>> Hm also, there is that annoying bit where by using 
>> dma_fence_is_signaled any code becomes a fence signaling critical 
>> path, which I never bought into. There should be a way to test the 
>> signaled status without actually doing the signaling. Or I am 
>> misunderstanding something so badly that is really really has to be 
>> like this?
> 
> You are mixing things up. Testing is unproblematic, signaling is the 
> problematic one.

I was pointing out dma_fence_is_signaled can call dma_fence_signal. And 
that has in the past, AFAIR at least, caused some fence annotation 
splats which IMO are questionable.

Regards,

Tvrtko
diff mbox series

Patch

diff --git a/drivers/dma-buf/dma-resv.c b/drivers/dma-buf/dma-resv.c
index 84fbe60629e3..3e77cad2c9d4 100644
--- a/drivers/dma-buf/dma-resv.c
+++ b/drivers/dma-buf/dma-resv.c
@@ -323,6 +323,67 @@  void dma_resv_add_excl_fence(struct dma_resv *obj, struct dma_fence *fence)
 }
 EXPORT_SYMBOL(dma_resv_add_excl_fence);
 
+/**
+ * dma_resv_iter_walk_unlocked - walk over fences in a dma_resv obj
+ * @cursor: cursor to record the current position
+ * @first: if we should start over
+ *
+ * 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 iterration is started over again.
+ */
+struct dma_fence *dma_resv_iter_walk_unlocked(struct dma_resv_iter *cursor,
+					      bool first)
+{
+	struct dma_resv *obj = cursor->obj;
+
+	first |= read_seqcount_retry(&obj->seq, cursor->seq);
+	do {
+		/* Drop the reference from the previous round */
+		dma_fence_put(cursor->fence);
+
+		cursor->is_first = first;
+		if (first) {
+			cursor->seq = read_seqcount_begin(&obj->seq);
+			cursor->index = -1;
+			cursor->fences = dma_resv_shared_list(obj);
+
+			cursor->fence = dma_resv_excl_fence(obj);
+			if (cursor->fence &&
+			    test_bit(DMA_FENCE_FLAG_SIGNALED_BIT,
+				     &cursor->fence->flags))
+				cursor->fence = NULL;
+		} else {
+			cursor->fence = NULL;
+		}
+
+		if (cursor->fence) {
+			cursor->fence = dma_fence_get_rcu(cursor->fence);
+		} else if (cursor->all_fences && cursor->fences) {
+			struct dma_resv_list *fences = cursor->fences;
+
+			while (++cursor->index < fences->shared_count) {
+				cursor->fence = rcu_dereference(
+					fences->shared[cursor->index]);
+				if (!test_bit(DMA_FENCE_FLAG_SIGNALED_BIT,
+					      &cursor->fence->flags))
+					break;
+			}
+			if (cursor->index < fences->shared_count)
+				cursor->fence =
+					dma_fence_get_rcu(cursor->fence);
+			else
+				cursor->fence = NULL;
+		}
+
+		/* For the eventually next round */
+		first = true;
+	} while (read_seqcount_retry(&obj->seq, cursor->seq));
+
+	return cursor->fence;
+}
+EXPORT_SYMBOL_GPL(dma_resv_iter_walk_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..693d16117153 100644
--- a/include/linux/dma-resv.h
+++ b/include/linux/dma-resv.h
@@ -149,6 +149,90 @@  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_first: true if this is the first returned fence */
+	bool is_first;
+};
+
+struct dma_fence *dma_resv_iter_walk_unlocked(struct dma_resv_iter *cursor,
+					      bool first);
+
+/**
+ * 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 iterator 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_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. The RCU read side lock must be hold when using this, but can
+ * be dropped and re-taken as necessary inside the loop. The cursor needs to be
+ * initialized with dma_resv_iter_begin_unlocked() and cleaned up with
+ * dma_resv_iter_end_unlocked().
+ */
+#define dma_resv_for_each_fence_unlocked(cursor, fence)			\
+	for (fence = dma_resv_iter_walk_unlocked(cursor, true);		\
+	     fence; fence = dma_resv_iter_walk_unlocked(cursor, false))
+
 #define dma_resv_held(obj) lockdep_is_held(&(obj)->lock.base)
 #define dma_resv_assert_held(obj) lockdep_assert_held(&(obj)->lock.base)