Re: [PATCH] i2c: iproc: handle invalid slave state

From: Ray Jui
Date: Mon Aug 28 2023 - 12:04:07 EST


Hi Roman,

On 8/24/2023 2:23 PM, roman.bacik@xxxxxxxxxxxx wrote:
> From: Roman Bacik <roman.bacik@xxxxxxxxxxxx>
>
> Add the code to handle an invalid state when both bits S_RX_EVENT
> (indicating a transaction) and S_START_BUSY (indicating the end
> of transaction - transition of START_BUSY from 1 to 0) are set in
> the interrupt status register during a slave read.
>
> Signed-off-by: Roman Bacik <roman.bacik@xxxxxxxxxxxx>
> Fixes: 1ca1b4516088 ("i2c: iproc: handle Master aborted error")
> ---
> drivers/i2c/busses/i2c-bcm-iproc.c | 133 ++++++++++++++++-------------
> 1 file changed, 75 insertions(+), 58 deletions(-)
>
> diff --git a/drivers/i2c/busses/i2c-bcm-iproc.c b/drivers/i2c/busses/i2c-bcm-iproc.c
> index 05c80680dff4..68438d4e5d73 100644
> --- a/drivers/i2c/busses/i2c-bcm-iproc.c
> +++ b/drivers/i2c/busses/i2c-bcm-iproc.c
> @@ -316,26 +316,44 @@ static void bcm_iproc_i2c_slave_init(
> iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, val);
> }
>
> -static void bcm_iproc_i2c_check_slave_status(
> - struct bcm_iproc_i2c_dev *iproc_i2c)
> +static bool bcm_iproc_i2c_check_slave_status
> + (struct bcm_iproc_i2c_dev *iproc_i2c, u32 status)
> {
> u32 val;
> + bool recover = false;
>
> - val = iproc_i2c_rd_reg(iproc_i2c, S_CMD_OFFSET);
> - /* status is valid only when START_BUSY is cleared after it was set */
> - if (val & BIT(S_CMD_START_BUSY_SHIFT))
> - return;
> + /* check slave transmit status only if slave is transmitting */
> + if (!iproc_i2c->slave_rx_only) {
> + val = iproc_i2c_rd_reg(iproc_i2c, S_CMD_OFFSET);
> + /* status is valid only when START_BUSY is cleared */
> + if (!(val & BIT(S_CMD_START_BUSY_SHIFT))) {
> + val = (val >> S_CMD_STATUS_SHIFT) & S_CMD_STATUS_MASK;
> + if (val == S_CMD_STATUS_TIMEOUT ||
> + val == S_CMD_STATUS_MASTER_ABORT) {
> + dev_warn(iproc_i2c->device,
> + (val == S_CMD_STATUS_TIMEOUT) ?
> + "slave random stretch time timeout\n" :
> + "Master aborted read transaction\n");
> + recover = true;
> + }
> + }
> + }
> +
> + /* RX_EVENT is not valid when START_BUSY is set */
> + if ((status & BIT(IS_S_RX_EVENT_SHIFT)) &&
> + (status & BIT(IS_S_START_BUSY_SHIFT))) {
> + dev_warn(iproc_i2c->device, "Slave aborted read transaction\n");
> + recover = true;
> + }
>
> - val = (val >> S_CMD_STATUS_SHIFT) & S_CMD_STATUS_MASK;
> - if (val == S_CMD_STATUS_TIMEOUT || val == S_CMD_STATUS_MASTER_ABORT) {
> - dev_err(iproc_i2c->device, (val == S_CMD_STATUS_TIMEOUT) ?
> - "slave random stretch time timeout\n" :
> - "Master aborted read transaction\n");
> + if (recover) {
> /* re-initialize i2c for recovery */
> bcm_iproc_i2c_enable_disable(iproc_i2c, false);
> bcm_iproc_i2c_slave_init(iproc_i2c, true);
> bcm_iproc_i2c_enable_disable(iproc_i2c, true);
> }
> +
> + return recover;
> }
>
> static void bcm_iproc_i2c_slave_read(struct bcm_iproc_i2c_dev *iproc_i2c)
> @@ -420,48 +438,6 @@ static bool bcm_iproc_i2c_slave_isr(struct bcm_iproc_i2c_dev *iproc_i2c,
> u32 val;
> u8 value;
>
> - /*
> - * Slave events in case of master-write, master-write-read and,
> - * master-read
> - *
> - * Master-write : only IS_S_RX_EVENT_SHIFT event
> - * Master-write-read: both IS_S_RX_EVENT_SHIFT and IS_S_RD_EVENT_SHIFT
> - * events
> - * Master-read : both IS_S_RX_EVENT_SHIFT and IS_S_RD_EVENT_SHIFT
> - * events or only IS_S_RD_EVENT_SHIFT
> - *
> - * iproc has a slave rx fifo size of 64 bytes. Rx fifo full interrupt
> - * (IS_S_RX_FIFO_FULL_SHIFT) will be generated when RX fifo becomes
> - * full. This can happen if Master issues write requests of more than
> - * 64 bytes.
> - */
> - if (status & BIT(IS_S_RX_EVENT_SHIFT) ||
> - status & BIT(IS_S_RD_EVENT_SHIFT) ||
> - status & BIT(IS_S_RX_FIFO_FULL_SHIFT)) {
> - /* disable slave interrupts */
> - val = iproc_i2c_rd_reg(iproc_i2c, IE_OFFSET);
> - val &= ~iproc_i2c->slave_int_mask;
> - iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, val);
> -
> - if (status & BIT(IS_S_RD_EVENT_SHIFT))
> - /* Master-write-read request */
> - iproc_i2c->slave_rx_only = false;
> - else
> - /* Master-write request only */
> - iproc_i2c->slave_rx_only = true;
> -
> - /* schedule tasklet to read data later */
> - tasklet_schedule(&iproc_i2c->slave_rx_tasklet);
> -
> - /*
> - * clear only IS_S_RX_EVENT_SHIFT and
> - * IS_S_RX_FIFO_FULL_SHIFT interrupt.
> - */
> - val = BIT(IS_S_RX_EVENT_SHIFT);
> - if (status & BIT(IS_S_RX_FIFO_FULL_SHIFT))
> - val |= BIT(IS_S_RX_FIFO_FULL_SHIFT);
> - iproc_i2c_wr_reg(iproc_i2c, IS_OFFSET, val);
> - }
>
> if (status & BIT(IS_S_TX_UNDERRUN_SHIFT)) {
> iproc_i2c->tx_underrun++;
> @@ -493,8 +469,9 @@ static bool bcm_iproc_i2c_slave_isr(struct bcm_iproc_i2c_dev *iproc_i2c,
> * less than PKT_LENGTH bytes were output on the SMBUS
> */
> iproc_i2c->slave_int_mask &= ~BIT(IE_S_TX_UNDERRUN_SHIFT);
> - iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET,
> - iproc_i2c->slave_int_mask);
> + val = iproc_i2c_rd_reg(iproc_i2c, IE_OFFSET);
> + val &= ~BIT(IE_S_TX_UNDERRUN_SHIFT);
> + iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, val);
>
> /* End of SMBUS for Master Read */
> val = BIT(S_TX_WR_STATUS_SHIFT);
> @@ -515,9 +492,49 @@ static bool bcm_iproc_i2c_slave_isr(struct bcm_iproc_i2c_dev *iproc_i2c,
> BIT(IS_S_START_BUSY_SHIFT));
> }
>
> - /* check slave transmit status only if slave is transmitting */
> - if (!iproc_i2c->slave_rx_only)
> - bcm_iproc_i2c_check_slave_status(iproc_i2c);
> + /* if the controller has been reset, immediately return from the ISR */
> + if (bcm_iproc_i2c_check_slave_status(iproc_i2c, status))
> + return true;
> +
> + /*
> + * Slave events in case of master-write, master-write-read and,
> + * master-read
> + *
> + * Master-write : only IS_S_RX_EVENT_SHIFT event
> + * Master-write-read: both IS_S_RX_EVENT_SHIFT and IS_S_RD_EVENT_SHIFT
> + * events
> + * Master-read : both IS_S_RX_EVENT_SHIFT and IS_S_RD_EVENT_SHIFT
> + * events or only IS_S_RD_EVENT_SHIFT
> + *
> + * iproc has a slave rx fifo size of 64 bytes. Rx fifo full interrupt
> + * (IS_S_RX_FIFO_FULL_SHIFT) will be generated when RX fifo becomes
> + * full. This can happen if Master issues write requests of more than
> + * 64 bytes.
> + */
> + if (status & BIT(IS_S_RX_EVENT_SHIFT) ||
> + status & BIT(IS_S_RD_EVENT_SHIFT) ||
> + status & BIT(IS_S_RX_FIFO_FULL_SHIFT)) {
> + /* disable slave interrupts */
> + val = iproc_i2c_rd_reg(iproc_i2c, IE_OFFSET);
> + val &= ~iproc_i2c->slave_int_mask;
> + iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, val);
> +
> + if (status & BIT(IS_S_RD_EVENT_SHIFT))
> + /* Master-write-read request */
> + iproc_i2c->slave_rx_only = false;
> + else
> + /* Master-write request only */
> + iproc_i2c->slave_rx_only = true;
> +
> + /* schedule tasklet to read data later */
> + tasklet_schedule(&iproc_i2c->slave_rx_tasklet);
> +
> + /* clear IS_S_RX_FIFO_FULL_SHIFT interrupt */
> + if (status & BIT(IS_S_RX_FIFO_FULL_SHIFT)) {
> + val = BIT(IS_S_RX_FIFO_FULL_SHIFT);
> + iproc_i2c_wr_reg(iproc_i2c, IS_OFFSET, val);
> + }
> + }
>
> return true;
> }

This fix looks good to me, and we have reviewed and thoroughly tested it
internally. Thanks.

Acked-by: Ray Jui <ray.jui@xxxxxxxxxxxx>

Attachment: smime.p7s
Description: S/MIME Cryptographic Signature