i2c: cadence: Handle transfer_size rollover
authorAlex Williams <alex.williams@ni.com>
Thu, 31 Jan 2019 21:39:57 +0000 (13:39 -0800)
committerWolfram Sang <wsa@the-dreams.de>
Thu, 30 Jan 2020 08:01:27 +0000 (09:01 +0100)
Under certain conditions, Cadence's I2C controller's transfer_size
register will roll over and generate invalid read transactions. Before
this change, the ISR relied solely on the RXDV bit to determine when to
write more data to the user's buffer. The invalid read data would cause
overruns, smashing stacks and worse.

This change stops the buffer writes to the requested boundary and
reports the error. The controller will be reset so normal transactions
may resume.

Signed-off-by: Alex Williams <alex.williams@ni.com>
Reviewed-by: Shubhrajyoti Datta <shubhrajyoti.datta@xilinx.com>
Reviewed-by: Michal Simek <michal.simek@xilinx.com> # in a seperate mail
Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
drivers/i2c/busses/i2c-cadence.c

index 9d71ce15db050e5acc8af87659a7520d2bc05736..179776bef5c484add8821092719f1bff9874aae7 100644 (file)
@@ -208,6 +208,7 @@ static irqreturn_t cdns_i2c_isr(int irq, void *ptr)
 
        isr_status = cdns_i2c_readreg(CDNS_I2C_ISR_OFFSET);
        cdns_i2c_writereg(isr_status, CDNS_I2C_ISR_OFFSET);
+       id->err_status = 0;
 
        /* Handling nack and arbitration lost interrupt */
        if (isr_status & (CDNS_I2C_IXR_NACK | CDNS_I2C_IXR_ARB_LOST)) {
@@ -241,10 +242,17 @@ static irqreturn_t cdns_i2c_isr(int irq, void *ptr)
                            !id->bus_hold_flag)
                                cdns_i2c_clear_bus_hold(id);
 
-                       *(id->p_recv_buf)++ =
-                               cdns_i2c_readreg(CDNS_I2C_DATA_OFFSET);
-                       id->recv_count--;
-                       id->curr_recv_count--;
+                       if (id->recv_count > 0) {
+                               *(id->p_recv_buf)++ =
+                                       cdns_i2c_readreg(CDNS_I2C_DATA_OFFSET);
+                               id->recv_count--;
+                               id->curr_recv_count--;
+                       } else {
+                               dev_err(id->adap.dev.parent,
+                                       "xfer_size reg rollover. xfer aborted!\n");
+                               id->err_status |= CDNS_I2C_IXR_TO;
+                               break;
+                       }
 
                        if (cdns_is_holdquirk(id, hold_quirk))
                                break;
@@ -342,7 +350,7 @@ static irqreturn_t cdns_i2c_isr(int irq, void *ptr)
        }
 
        /* Update the status for errors */
-       id->err_status = isr_status & CDNS_I2C_IXR_ERR_INTR_MASK;
+       id->err_status |= isr_status & CDNS_I2C_IXR_ERR_INTR_MASK;
        if (id->err_status)
                status = IRQ_HANDLED;