lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <d23263dcb0f1535275ff37524b0203b2@codeaurora.org>
Date:   Fri, 07 May 2021 15:37:42 +0530
From:   rojay@...eaurora.org
To:     Stephen Boyd <swboyd@...omium.org>
Cc:     wsa@...nel.org, dianders@...omium.org,
        saiprakash.ranjan@...eaurora.org, gregkh@...uxfoundation.org,
        mka@...omium.org, skananth@...eaurora.org,
        msavaliy@....qualcomm.com, skakit@...eaurora.org,
        rnayak@...eaurora.org, agross@...nel.org,
        bjorn.andersson@...aro.org, linux-arm-msm@...r.kernel.org,
        linux-i2c@...r.kernel.org, linux-kernel@...r.kernel.org,
        sumit.semwal@...aro.org, linux-media@...r.kernel.org
Subject: Re: [PATCH V9] i2c: i2c-qcom-geni: Add shutdown callback for i2c

Hi Stephen,

On 2021-05-05 07:08, Stephen Boyd wrote:
> Quoting Roja Rani Yarubandi (2021-04-20 04:13:55)
>> diff --git a/drivers/i2c/busses/i2c-qcom-geni.c 
>> b/drivers/i2c/busses/i2c-qcom-geni.c
>> index 214b4c913a13..8ae17ccad99e 100644
>> --- a/drivers/i2c/busses/i2c-qcom-geni.c
>> +++ b/drivers/i2c/busses/i2c-qcom-geni.c
>> @@ -71,6 +71,8 @@ enum geni_i2c_err_code {
>>  #define ABORT_TIMEOUT          HZ
>>  #define XFER_TIMEOUT           HZ
>>  #define RST_TIMEOUT            HZ
>> +#define ABORT_XFER             0
>> +#define STOP_AND_ABORT_XFER    1
> 
> These should be an enum.
> 

Okay.

>> 
>>  struct geni_i2c_dev {
>>         struct geni_se se;
>> @@ -89,6 +91,7 @@ struct geni_i2c_dev {
>>         void *dma_buf;
>>         size_t xfer_len;
>>         dma_addr_t dma_addr;
>> +       bool stop_xfer;
>>  };
>> 
>>  struct geni_i2c_err_log {
>> @@ -215,6 +218,11 @@ static irqreturn_t geni_i2c_irq(int irq, void 
>> *dev)
>>         struct i2c_msg *cur;
>> 
>>         spin_lock(&gi2c->lock);
>> +       if (!gi2c->cur) {
>> +               dev_err(gi2c->se.dev, "Can't process irq, gi2c->cur is 
>> NULL\n");
> 
> This error message is worthless. The user won't know what to do and 
> then
> we return IRQ_HANDLED? If the device is misbehaving we should return
> IRQ_NONE and shut down the irq storm that will soon be upon us, not
> print an error message and hope for the best.
> 

Okay, will return IRQ_NONE. The reason I have kept this error message is 
to know
that this case has occurred.

>> +               spin_unlock(&gi2c->lock);
>> +               return IRQ_HANDLED;
>> +       }
>>         m_stat = readl_relaxed(base + SE_GENI_M_IRQ_STATUS);
>>         rx_st = readl_relaxed(base + SE_GENI_RX_FIFO_STATUS);
>>         dm_tx_st = readl_relaxed(base + SE_DMA_TX_IRQ_STAT);
>> @@ -222,8 +230,7 @@ static irqreturn_t geni_i2c_irq(int irq, void 
>> *dev)
>>         dma = readl_relaxed(base + SE_GENI_DMA_MODE_EN);
>>         cur = gi2c->cur;
>> 
>> -       if (!cur ||
>> -           m_stat & (M_CMD_FAILURE_EN | M_CMD_ABORT_EN) ||
>> +       if (m_stat & (M_CMD_FAILURE_EN | M_CMD_ABORT_EN) ||
>>             dm_rx_st & (DM_I2C_CB_ERR)) {
>>                 if (m_stat & M_GP_IRQ_1_EN)
>>                         geni_i2c_err(gi2c, NACK);
>> @@ -301,17 +308,19 @@ static irqreturn_t geni_i2c_irq(int irq, void 
>> *dev)
>>         return IRQ_HANDLED;
>>  }
>> 
>> -static void geni_i2c_abort_xfer(struct geni_i2c_dev *gi2c)
>> +static void geni_i2c_abort_xfer(struct geni_i2c_dev *gi2c, bool 
>> is_stop_xfer)
> 
> The bool should be an enum, but a better approach would be to have a
> locked and unlocked version of this function.
> 

Okay, will try to come up with the better approach.

>>  {
>>         u32 val;
>>         unsigned long time_left = ABORT_TIMEOUT;
>>         unsigned long flags;
>> 
>> -       spin_lock_irqsave(&gi2c->lock, flags);
>> +       if (!is_stop_xfer)
>> +               spin_lock_irqsave(&gi2c->lock, flags);
>>         geni_i2c_err(gi2c, GENI_TIMEOUT);
>>         gi2c->cur = NULL;
>>         geni_se_abort_m_cmd(&gi2c->se);
>> -       spin_unlock_irqrestore(&gi2c->lock, flags);
>> +       if (!is_stop_xfer)
>> +               spin_unlock_irqrestore(&gi2c->lock, flags);
> 
> Please no conditional locking. It's too hard to reason about.
> 

This will be fixed with the above mentioned optimal approach.

>>         do {
>>                 time_left = wait_for_completion_timeout(&gi2c->done, 
>> time_left);
>>                 val = readl_relaxed(gi2c->se.base + 
>> SE_GENI_M_IRQ_STATUS);
>> @@ -375,6 +384,38 @@ static void geni_i2c_tx_msg_cleanup(struct 
>> geni_i2c_dev *gi2c,
>>         }
>>  }
>> 
>> +static void geni_i2c_stop_xfer(struct geni_i2c_dev *gi2c)
>> +{
>> +       int ret;
>> +       u32 geni_status;
>> +       struct i2c_msg *cur;
>> +       unsigned long flags;
>> +
>> +       /* Resume device, as runtime suspend can happen anytime during 
>> transfer */
> 
> This comment doesn't make any sense. Hopefully a suspend can't happen
> during a transfer, but only before or after a transfer. Otherwise, the
> transfer code is broken and isn't properly keeping the device runtime
> resumed during the transfer.
> 

Okay, instead of resuming the device, I will check for the device 
status,
and if it is in resume state will proceed accordingly.

>> +       ret = pm_runtime_get_sync(gi2c->se.dev);
>> +       if (ret < 0) {
>> +               dev_err(gi2c->se.dev, "Failed to resume device: %d\n", 
>> ret);
>> +               return;
>> +       }
>> +
>> +       spin_lock_irqsave(&gi2c->lock, flags);
>> +       gi2c->stop_xfer = 1;
>> +       geni_status = readl_relaxed(gi2c->se.base + SE_GENI_STATUS);
>> +       if (geni_status & M_GENI_CMD_ACTIVE) {
>> +               cur = gi2c->cur;
>> +               geni_i2c_abort_xfer(gi2c, STOP_AND_ABORT_XFER);
>> +               spin_unlock_irqrestore(&gi2c->lock, flags);
>> +               if (cur->flags & I2C_M_RD)
>> +                       geni_i2c_rx_msg_cleanup(gi2c, cur);
>> +               else
>> +                       geni_i2c_tx_msg_cleanup(gi2c, cur);
>> +       } else {
>> +               spin_unlock_irqrestore(&gi2c->lock, flags);
>> +       }
> 
> Please unlock outside of an if condition. A local variable can be used
> outside of the unlock, but then the code is easier to follow
> 

Okay.

> 	spin_lock_irqsave(&gi2c->lock, flags);
> 	if (geni_status & M_GENI_CMD_ACTIVE) {
> 		cur = gic2->cur;
> 		geni_i2c_abort_xfer(....);
> 	}
> 	spin_unlock_irqrestore(gi2c->lock, flags);
> 
> 	if (cur) {
> 		if (cur->flags & I2C_M_RD)
> 			...
> 		else
> 			...
> 	}
> 
> And then I don't really know if grabbing 'cur' out of the struct and
> then messing with it outside the lock is correct.
> 

I think it shouldn't be a problem, gi2c->cur will be NULL, and 'cur' 
should be
fine to use, and we have fixed the race between this and geni_i2c_xfer()
with proper protection.

>> +
>> +       pm_runtime_put_sync_suspend(gi2c->se.dev);
>> +}
>> +
>>  static int geni_i2c_rx_one_msg(struct geni_i2c_dev *gi2c, struct 
>> i2c_msg *msg,
>>                                 u32 m_param)
>>  {
>> @@ -407,7 +448,7 @@ static int geni_i2c_rx_one_msg(struct geni_i2c_dev 
>> *gi2c, struct i2c_msg *msg,
>>         cur = gi2c->cur;
>>         time_left = wait_for_completion_timeout(&gi2c->done, 
>> XFER_TIMEOUT);
>>         if (!time_left)
>> -               geni_i2c_abort_xfer(gi2c);
>> +               geni_i2c_abort_xfer(gi2c, ABORT_XFER);
> 
> So this would say geni_i2c_abort_xfer() but the one above would say
> geni_i2c_abort_xfer_locked() because the lock is already held.
> 

Correct.

>> 
>>         geni_i2c_rx_msg_cleanup(gi2c, cur);
>> 
>> @@ -449,7 +490,7 @@ static int geni_i2c_tx_one_msg(struct geni_i2c_dev 
>> *gi2c, struct i2c_msg *msg,
>>         cur = gi2c->cur;
>>         time_left = wait_for_completion_timeout(&gi2c->done, 
>> XFER_TIMEOUT);
>>         if (!time_left)
>> -               geni_i2c_abort_xfer(gi2c);
>> +               geni_i2c_abort_xfer(gi2c, ABORT_XFER);
>> 
>>         geni_i2c_tx_msg_cleanup(gi2c, cur);
>> 
>> @@ -462,6 +503,7 @@ static int geni_i2c_xfer(struct i2c_adapter *adap,
>>  {
>>         struct geni_i2c_dev *gi2c = i2c_get_adapdata(adap);
>>         int i, ret;
>> +       unsigned long flags;
>> 
>>         gi2c->err = 0;
>>         reinit_completion(&gi2c->done);
>> @@ -480,7 +522,13 @@ static int geni_i2c_xfer(struct i2c_adapter 
>> *adap,
>> 
>>                 m_param |= ((msgs[i].addr << SLV_ADDR_SHFT) & 
>> SLV_ADDR_MSK);
>> 
>> +               spin_lock_irqsave(&gi2c->lock, flags);
>> +               if (gi2c->stop_xfer) {
>> +                       spin_unlock_irqrestore(&gi2c->lock, flags);
>> +                       break;
>> +               }
>>                 gi2c->cur = &msgs[i];
>> +               spin_unlock_irqrestore(&gi2c->lock, flags);
> 
> Is this to jump into the transfer real fast and break out if we're in
> the middle of a transfer?
> 

Yes, to break the loop.

>>                 if (msgs[i].flags & I2C_M_RD)
>>                         ret = geni_i2c_rx_one_msg(gi2c, &msgs[i], 
>> m_param);
>>                 else
>> @@ -624,6 +672,7 @@ static int geni_i2c_probe(struct platform_device 
>> *pdev)
>>         dev_dbg(dev, "i2c fifo/se-dma mode. fifo depth:%d\n", 
>> tx_depth);
>> 
>>         gi2c->suspended = 1;
>> +       gi2c->stop_xfer = 0;
>>         pm_runtime_set_suspended(gi2c->se.dev);
>>         pm_runtime_set_autosuspend_delay(gi2c->se.dev, 
>> I2C_AUTO_SUSPEND_DELAY);
>>         pm_runtime_use_autosuspend(gi2c->se.dev);
>> @@ -650,6 +699,13 @@ static int geni_i2c_remove(struct platform_device 
>> *pdev)
>>         return 0;
>>  }
>> 
>> +static void  geni_i2c_shutdown(struct platform_device *pdev)
>> +{
>> +       struct geni_i2c_dev *gi2c = platform_get_drvdata(pdev);
>> +
>> +       geni_i2c_stop_xfer(gi2c);
> 
> It would read better as
> 
> 	geni_i2c_plug_xfer(gi2c);
> 
> or
> 
> 	geni_i2c_flush_and_teardown(gi2c);
> 
> or
> 
> 	geni_i2c_teardown_tx_rx(gi2c);
> 
> Something that says we're waiting for any transfer to complete, and 
> then
> plugging the queue and removing the i2c bus entirely.
> 

Sure, will change.

> In fact, where is that code? I'd expect to see i2c_del_adapter() in 
> here
> so we know the adapter can't accept transfers anymore. Maybe
> i2c_del_adapter() could be called, and then there's nothing to do after
> that? This whole patch is trying to rip the adapter out from under the
> i2c core framework, when we should take the opposite approach and 
> remove
> it from the core framework so that it can't transfer anything anymore
> and thus the IOMMU can remove the mapping.
> 

IIUC about probe/remove/shutdown calls, during "remove" we will unplug 
the
device with opposite calls to "probe's" plug operations.
For example i2c_add_adapter() from 'probe' and i2c_del_adapter() from 
'remove'.
For "shutdown", as system is going to shutdown, there is no need of 
unplug
operations to be done.

And also, I had a glance on other upstream i2c drivers, and noticed 
"i2c-i801.c"
driver has i2c_del_adapter() called from remove callback but not from 
shutdown
callback.

And actually I tried calling i2c_del_adapter() from geni_i2c_shutdown(),
and it resulted in below WARNING trace
[   90.320282] Call trace:
[   90.322807]  _regulator_put+0xc4/0xcc
[   90.326583]  regulator_bulk_free+0x48/0x6c
[   90.330808]  devm_regulator_bulk_release+0x20/0x2c
[   90.335744]  release_nodes+0x1d0/0x244
[   90.339609]  devres_release_all+0x3c/0x54
[   90.343735]  device_release_driver_internal+0x108/0x194
[   90.349109]  device_release_driver+0x24/0x30
[   90.353510]  bus_remove_device+0xd0/0xf4
[   90.357548]  device_del+0x1a8/0x2f8
[   90.361143]  device_unregister+0x1c/0x34
[   90.365181]  __unregister_client+0x78/0x88
[   90.369397]  device_for_each_child+0x64/0xb4
[   90.373797]  i2c_del_adapter+0xf0/0x1d4
[   90.377745]  geni_i2c_shutdown+0x9c/0xc0
[   90.381783]  platform_drv_shutdown+0x28/0x34
[   90.386182]  device_shutdown+0x148/0x1f0

Can you please suggest me what might be missing here?

>> +}
>> +
>>  static int __maybe_unused geni_i2c_runtime_suspend(struct device 
>> *dev)
>>  {
>>         int ret;

Thanks,
Roja

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ