diff mbox

[19/72] gpu: ipu-v3: Protect more CM reg access with IPU lock

Message ID 1414796095-10107-20-git-send-email-steve_longerbeam@mentor.com (mailing list archive)
State New, archived
Headers show

Commit Message

Steve Longerbeam Oct. 31, 2014, 10:54 p.m. UTC
Some cm_reg accesses were not being protected by the IPU spin lock.

Signed-off-by: Steve Longerbeam <steve_longerbeam@mentor.com>
---
 drivers/gpu/ipu-v3/ipu-common.c |   22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

Comments

Philipp Zabel Nov. 4, 2014, 5:51 p.m. UTC | #1
Hi Steve,

Am Freitag, den 31.10.2014, 15:54 -0700 schrieb Steve Longerbeam:
> Some cm_reg accesses were not being protected by the IPU spin lock.
>
> Signed-off-by: Steve Longerbeam <steve_longerbeam@mentor.com>
> ---
>  drivers/gpu/ipu-v3/ipu-common.c |   22 ++++++++++++++++++++--
>  1 file changed, 20 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/gpu/ipu-v3/ipu-common.c b/drivers/gpu/ipu-v3/ipu-common.c
> index f707d25..d3af206 100644
> --- a/drivers/gpu/ipu-v3/ipu-common.c
> +++ b/drivers/gpu/ipu-v3/ipu-common.c
> @@ -46,11 +46,16 @@ static inline void ipu_cm_write(struct ipu_soc *ipu, u32 value, unsigned offset)
>  
>  void ipu_srm_dp_sync_update(struct ipu_soc *ipu)
>  {
> +	unsigned long flags;
>  	u32 val;
>  
> +	spin_lock_irqsave(&ipu->lock, flags);
> +
>  	val = ipu_cm_read(ipu, IPU_SRM_PRI2);
>  	val |= 0x8;
>  	ipu_cm_write(ipu, val, IPU_SRM_PRI2);
> +
> +	spin_unlock_irqrestore(&ipu->lock, flags);
>  }
>  EXPORT_SYMBOL_GPL(ipu_srm_dp_sync_update);

This is the only place this register touched, only bit 3 is ever enabled
by software. This lock is not needed.

> @@ -451,8 +456,17 @@ int ipu_idmac_get_current_buffer(struct ipuv3_channel *channel)
>  {
>  	struct ipu_soc *ipu = channel->ipu;
>  	unsigned int chno = channel->num;
> +	unsigned long flags;
> +	int ret;
> +
> +	spin_lock_irqsave(&ipu->lock, flags);
>  
> -	return (ipu_cm_read(ipu, IPU_CHA_CUR_BUF(chno)) & idma_mask(chno)) ? 1 : 0;
> +	ret = (ipu_cm_read(ipu, IPU_CHA_CUR_BUF(chno)) & idma_mask(chno)) ?
> +		1 : 0;
> +
> +	spin_unlock_irqrestore(&ipu->lock, flags);
> +
> +	return ret;

Dito. This register isn't written partially multiple times under the
spinlock anywhere, so there is no gain from this lock around a register
read.

>  }
>  EXPORT_SYMBOL_GPL(ipu_idmac_get_current_buffer);
>  
> @@ -569,10 +583,14 @@ EXPORT_SYMBOL_GPL(ipu_idmac_wait_busy);
>  
>  int ipu_wait_interrupt(struct ipu_soc *ipu, int irq, int ms)
>  {
> -	unsigned long timeout;
> +	unsigned long flags, timeout;
>  
>  	timeout = jiffies + msecs_to_jiffies(ms);
> +
> +	spin_lock_irqsave(&ipu->lock, flags);
>  	ipu_cm_write(ipu, BIT(irq % 32), IPU_INT_STAT(irq / 32));
> +	spin_unlock_irqrestore(&ipu->lock, flags);
> +
>  	while (!(ipu_cm_read(ipu, IPU_INT_STAT(irq / 32) & BIT(irq % 32)))) {
>  		if (time_after(jiffies, timeout))
>  			return -ETIMEDOUT;

This is a write to clear register, the only other place it is accessed
is by the regmap irq handler. Can you think of a scenario where this
lock would protect anything?

regards
Philipp
Steve Longerbeam Nov. 5, 2014, 1:54 a.m. UTC | #2
On 11/04/2014 09:51 AM, Philipp Zabel wrote:
> Hi Steve,
>
> Am Freitag, den 31.10.2014, 15:54 -0700 schrieb Steve Longerbeam:
>> Some cm_reg accesses were not being protected by the IPU spin lock.
>>
>> Signed-off-by: Steve Longerbeam <steve_longerbeam@mentor.com>
>> ---
>>  drivers/gpu/ipu-v3/ipu-common.c |   22 ++++++++++++++++++++--
>>  1 file changed, 20 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/gpu/ipu-v3/ipu-common.c b/drivers/gpu/ipu-v3/ipu-common.c
>> index f707d25..d3af206 100644
>> --- a/drivers/gpu/ipu-v3/ipu-common.c
>> +++ b/drivers/gpu/ipu-v3/ipu-common.c
>> @@ -46,11 +46,16 @@ static inline void ipu_cm_write(struct ipu_soc *ipu, u32 value, unsigned offset)
>>  
>>  void ipu_srm_dp_sync_update(struct ipu_soc *ipu)
>>  {
>> +	unsigned long flags;
>>  	u32 val;
>>  
>> +	spin_lock_irqsave(&ipu->lock, flags);
>> +
>>  	val = ipu_cm_read(ipu, IPU_SRM_PRI2);
>>  	val |= 0x8;
>>  	ipu_cm_write(ipu, val, IPU_SRM_PRI2);
>> +
>> +	spin_unlock_irqrestore(&ipu->lock, flags);
>>  }
>>  EXPORT_SYMBOL_GPL(ipu_srm_dp_sync_update);
> This is the only place this register touched, only bit 3 is ever enabled
> by software. This lock is not needed.

yes, I verified that too. Never mind then.

>> @@ -451,8 +456,17 @@ int ipu_idmac_get_current_buffer(struct ipuv3_channel *channel)
>>  {
>>  	struct ipu_soc *ipu = channel->ipu;
>>  	unsigned int chno = channel->num;
>> +	unsigned long flags;
>> +	int ret;
>> +
>> +	spin_lock_irqsave(&ipu->lock, flags);
>>  
>> -	return (ipu_cm_read(ipu, IPU_CHA_CUR_BUF(chno)) & idma_mask(chno)) ? 1 : 0;
>> +	ret = (ipu_cm_read(ipu, IPU_CHA_CUR_BUF(chno)) & idma_mask(chno)) ?
>> +		1 : 0;
>> +
>> +	spin_unlock_irqrestore(&ipu->lock, flags);
>> +
>> +	return ret;
> Dito. This register isn't written partially multiple times under the
> spinlock anywhere, so there is no gain from this lock around a register
> read.

Well, I was thinking get_current_buffer could race with
reset_current_buffer, but these functions are doing plain
readl() and writel() (i.e. not read-modify-write), so they
are atomic operations. So never mind this either.

>>  }
>>  EXPORT_SYMBOL_GPL(ipu_idmac_get_current_buffer);
>>  
>> @@ -569,10 +583,14 @@ EXPORT_SYMBOL_GPL(ipu_idmac_wait_busy);
>>  
>>  int ipu_wait_interrupt(struct ipu_soc *ipu, int irq, int ms)
>>  {
>> -	unsigned long timeout;
>> +	unsigned long flags, timeout;
>>  
>>  	timeout = jiffies + msecs_to_jiffies(ms);
>> +
>> +	spin_lock_irqsave(&ipu->lock, flags);
>>  	ipu_cm_write(ipu, BIT(irq % 32), IPU_INT_STAT(irq / 32));
>> +	spin_unlock_irqrestore(&ipu->lock, flags);
>> +
>>  	while (!(ipu_cm_read(ipu, IPU_INT_STAT(irq / 32) & BIT(irq % 32)))) {
>>  		if (time_after(jiffies, timeout))
>>  			return -ETIMEDOUT;
> This is a write to clear register, the only other place it is accessed
> is by the regmap irq handler. Can you think of a scenario where this
> lock would protect anything?

In this case I do see potential problems. What if thread A calls
ipu_wait_interrupt() while thread B is currently looping through
irq status bits in ipu_irq_handle()? A has now cleared an irq status
before B has had a chance to get to that status bit. Thus the irq
status is missed and the irq not handled. That being said this patch
will not fix the problem because ipu_wait_interrupt() and ipu_irq_handle()
are under different locks.

Maybe the solution is to verify ipu_wait_interrupt() is never called
on enabled irqs so that it doesn't interfere with irq handling. But I
don't know that that is true, is it?

Steve
Steve Longerbeam Nov. 5, 2014, 1:55 a.m. UTC | #3
On 11/04/2014 09:51 AM, Philipp Zabel wrote:
> Hi Steve,
>
> Am Freitag, den 31.10.2014, 15:54 -0700 schrieb Steve Longerbeam:
>> Some cm_reg accesses were not being protected by the IPU spin lock.
>>
>> Signed-off-by: Steve Longerbeam <steve_longerbeam@mentor.com>
>> ---
>>  drivers/gpu/ipu-v3/ipu-common.c |   22 ++++++++++++++++++++--
>>  1 file changed, 20 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/gpu/ipu-v3/ipu-common.c b/drivers/gpu/ipu-v3/ipu-common.c
>> index f707d25..d3af206 100644
>> --- a/drivers/gpu/ipu-v3/ipu-common.c
>> +++ b/drivers/gpu/ipu-v3/ipu-common.c
>> @@ -46,11 +46,16 @@ static inline void ipu_cm_write(struct ipu_soc *ipu, u32 value, unsigned offset)
>>  
>>  void ipu_srm_dp_sync_update(struct ipu_soc *ipu)
>>  {
>> +	unsigned long flags;
>>  	u32 val;
>>  
>> +	spin_lock_irqsave(&ipu->lock, flags);
>> +
>>  	val = ipu_cm_read(ipu, IPU_SRM_PRI2);
>>  	val |= 0x8;
>>  	ipu_cm_write(ipu, val, IPU_SRM_PRI2);
>> +
>> +	spin_unlock_irqrestore(&ipu->lock, flags);
>>  }
>>  EXPORT_SYMBOL_GPL(ipu_srm_dp_sync_update);
> This is the only place this register touched, only bit 3 is ever enabled
> by software. This lock is not needed.

yes, I verified that too. Never mind then.

>> @@ -451,8 +456,17 @@ int ipu_idmac_get_current_buffer(struct ipuv3_channel *channel)
>>  {
>>  	struct ipu_soc *ipu = channel->ipu;
>>  	unsigned int chno = channel->num;
>> +	unsigned long flags;
>> +	int ret;
>> +
>> +	spin_lock_irqsave(&ipu->lock, flags);
>>  
>> -	return (ipu_cm_read(ipu, IPU_CHA_CUR_BUF(chno)) & idma_mask(chno)) ? 1 : 0;
>> +	ret = (ipu_cm_read(ipu, IPU_CHA_CUR_BUF(chno)) & idma_mask(chno)) ?
>> +		1 : 0;
>> +
>> +	spin_unlock_irqrestore(&ipu->lock, flags);
>> +
>> +	return ret;
> Dito. This register isn't written partially multiple times under the
> spinlock anywhere, so there is no gain from this lock around a register
> read.

Well, I was thinking get_current_buffer could race with
reset_current_buffer, but these functions are doing plain
readl() and writel() (i.e. not read-modify-write), so they
are atomic operations. So never mind this either.

>>  }
>>  EXPORT_SYMBOL_GPL(ipu_idmac_get_current_buffer);
>>  
>> @@ -569,10 +583,14 @@ EXPORT_SYMBOL_GPL(ipu_idmac_wait_busy);
>>  
>>  int ipu_wait_interrupt(struct ipu_soc *ipu, int irq, int ms)
>>  {
>> -	unsigned long timeout;
>> +	unsigned long flags, timeout;
>>  
>>  	timeout = jiffies + msecs_to_jiffies(ms);
>> +
>> +	spin_lock_irqsave(&ipu->lock, flags);
>>  	ipu_cm_write(ipu, BIT(irq % 32), IPU_INT_STAT(irq / 32));
>> +	spin_unlock_irqrestore(&ipu->lock, flags);
>> +
>>  	while (!(ipu_cm_read(ipu, IPU_INT_STAT(irq / 32) & BIT(irq % 32)))) {
>>  		if (time_after(jiffies, timeout))
>>  			return -ETIMEDOUT;
> This is a write to clear register, the only other place it is accessed
> is by the regmap irq handler. Can you think of a scenario where this
> lock would protect anything?

In this case I do see potential problems. What if thread A calls
ipu_wait_interrupt() while thread B is currently looping through
irq status bits in ipu_irq_handle()? A has now cleared an irq status
before B has had a chance to get to that status bit. Thus the irq
status is missed and the irq not handled. That being said this patch
will not fix the problem because ipu_wait_interrupt() and ipu_irq_handle()
are under different locks.

Maybe the solution is to verify ipu_wait_interrupt() is never called
on enabled irqs so that it doesn't interfere with irq handling. But I
don't know that that is true, is it?

Steve
diff mbox

Patch

diff --git a/drivers/gpu/ipu-v3/ipu-common.c b/drivers/gpu/ipu-v3/ipu-common.c
index f707d25..d3af206 100644
--- a/drivers/gpu/ipu-v3/ipu-common.c
+++ b/drivers/gpu/ipu-v3/ipu-common.c
@@ -46,11 +46,16 @@  static inline void ipu_cm_write(struct ipu_soc *ipu, u32 value, unsigned offset)
 
 void ipu_srm_dp_sync_update(struct ipu_soc *ipu)
 {
+	unsigned long flags;
 	u32 val;
 
+	spin_lock_irqsave(&ipu->lock, flags);
+
 	val = ipu_cm_read(ipu, IPU_SRM_PRI2);
 	val |= 0x8;
 	ipu_cm_write(ipu, val, IPU_SRM_PRI2);
+
+	spin_unlock_irqrestore(&ipu->lock, flags);
 }
 EXPORT_SYMBOL_GPL(ipu_srm_dp_sync_update);
 
@@ -451,8 +456,17 @@  int ipu_idmac_get_current_buffer(struct ipuv3_channel *channel)
 {
 	struct ipu_soc *ipu = channel->ipu;
 	unsigned int chno = channel->num;
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&ipu->lock, flags);
 
-	return (ipu_cm_read(ipu, IPU_CHA_CUR_BUF(chno)) & idma_mask(chno)) ? 1 : 0;
+	ret = (ipu_cm_read(ipu, IPU_CHA_CUR_BUF(chno)) & idma_mask(chno)) ?
+		1 : 0;
+
+	spin_unlock_irqrestore(&ipu->lock, flags);
+
+	return ret;
 }
 EXPORT_SYMBOL_GPL(ipu_idmac_get_current_buffer);
 
@@ -569,10 +583,14 @@  EXPORT_SYMBOL_GPL(ipu_idmac_wait_busy);
 
 int ipu_wait_interrupt(struct ipu_soc *ipu, int irq, int ms)
 {
-	unsigned long timeout;
+	unsigned long flags, timeout;
 
 	timeout = jiffies + msecs_to_jiffies(ms);
+
+	spin_lock_irqsave(&ipu->lock, flags);
 	ipu_cm_write(ipu, BIT(irq % 32), IPU_INT_STAT(irq / 32));
+	spin_unlock_irqrestore(&ipu->lock, flags);
+
 	while (!(ipu_cm_read(ipu, IPU_INT_STAT(irq / 32) & BIT(irq % 32)))) {
 		if (time_after(jiffies, timeout))
 			return -ETIMEDOUT;