Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e6f34cea authored by Josef Ahmad's avatar Josef Ahmad Committed by Wolfram Sang
Browse files

i2c: designware: fix RX FIFO overrun



i2c_dw_xfer_msg() pushes a number of bytes to transmit/receive
to/from the bus into the TX FIFO.
For master-rx transactions, the maximum amount of data that can be
received is calculated depending solely on TX and RX FIFO load.

This is racy - TX FIFO may contain master-rx data yet to be
processed, which will eventually land into the RX FIFO. This
data is not taken into account and the function may request more
data than the controller is actually capable of storing.

This patch ensures the driver takes into account the outstanding
master-rx data in TX FIFO to prevent RX FIFO overrun.

Signed-off-by: default avatarJosef Ahmad <josef.ahmad@linux.intel.com>
Acked-by: default avatarMika Westerberg <mika.westerberg@linux.intel.com>
Signed-off-by: default avatarWolfram Sang <wsa@the-dreams.de>
Cc: stable@kernel.org
parent f722406f
Loading
Loading
Loading
Loading
+10 −1
Original line number Original line Diff line number Diff line
@@ -448,8 +448,14 @@ i2c_dw_xfer_msg(struct dw_i2c_dev *dev)
				cmd |= BIT(9);
				cmd |= BIT(9);


			if (msgs[dev->msg_write_idx].flags & I2C_M_RD) {
			if (msgs[dev->msg_write_idx].flags & I2C_M_RD) {

				/* avoid rx buffer overrun */
				if (rx_limit - dev->rx_outstanding <= 0)
					break;

				dw_writel(dev, cmd | 0x100, DW_IC_DATA_CMD);
				dw_writel(dev, cmd | 0x100, DW_IC_DATA_CMD);
				rx_limit--;
				rx_limit--;
				dev->rx_outstanding++;
			} else
			} else
				dw_writel(dev, cmd | *buf++, DW_IC_DATA_CMD);
				dw_writel(dev, cmd | *buf++, DW_IC_DATA_CMD);
			tx_limit--; buf_len--;
			tx_limit--; buf_len--;
@@ -502,8 +508,10 @@ i2c_dw_read(struct dw_i2c_dev *dev)


		rx_valid = dw_readl(dev, DW_IC_RXFLR);
		rx_valid = dw_readl(dev, DW_IC_RXFLR);


		for (; len > 0 && rx_valid > 0; len--, rx_valid--)
		for (; len > 0 && rx_valid > 0; len--, rx_valid--) {
			*buf++ = dw_readl(dev, DW_IC_DATA_CMD);
			*buf++ = dw_readl(dev, DW_IC_DATA_CMD);
			dev->rx_outstanding--;
		}


		if (len > 0) {
		if (len > 0) {
			dev->status |= STATUS_READ_IN_PROGRESS;
			dev->status |= STATUS_READ_IN_PROGRESS;
@@ -561,6 +569,7 @@ i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
	dev->msg_err = 0;
	dev->msg_err = 0;
	dev->status = STATUS_IDLE;
	dev->status = STATUS_IDLE;
	dev->abort_source = 0;
	dev->abort_source = 0;
	dev->rx_outstanding = 0;


	ret = i2c_dw_wait_bus_not_busy(dev);
	ret = i2c_dw_wait_bus_not_busy(dev);
	if (ret < 0)
	if (ret < 0)
+2 −0
Original line number Original line Diff line number Diff line
@@ -60,6 +60,7 @@
 * @adapter: i2c subsystem adapter node
 * @adapter: i2c subsystem adapter node
 * @tx_fifo_depth: depth of the hardware tx fifo
 * @tx_fifo_depth: depth of the hardware tx fifo
 * @rx_fifo_depth: depth of the hardware rx fifo
 * @rx_fifo_depth: depth of the hardware rx fifo
 * @rx_outstanding: current master-rx elements in tx fifo
 */
 */
struct dw_i2c_dev {
struct dw_i2c_dev {
	struct device		*dev;
	struct device		*dev;
@@ -88,6 +89,7 @@ struct dw_i2c_dev {
	u32			master_cfg;
	u32			master_cfg;
	unsigned int		tx_fifo_depth;
	unsigned int		tx_fifo_depth;
	unsigned int		rx_fifo_depth;
	unsigned int		rx_fifo_depth;
	int			rx_outstanding;
};
};


#define ACCESS_SWAP		0x00000001
#define ACCESS_SWAP		0x00000001