Devicetree
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Aniket Limaye" <a-limaye@ti.com>
Cc: devicetree@vger.kernel.org, robh@kernel.org, conor+dt@kernel.org
Subject: Re: [PATCH v5 2/2] i2c: Add Synopsys Advanced I2C (DWC_i2c) driver
Date: Wed, 11 Nov 2026 02:40:36 +0000	[thread overview]
Message-ID: <sashiko-outbox-208105@kernel.org> (raw)
In-Reply-To: <20261111-tda54-upstream-i2c-v5-2-280a4a779b24@ti.com>

Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [High] i2c: dwc-adv: pre-queued reads in dwc_adv_i2c_xfer_msg() hang
  block reads
- [High] i2c: dwc-adv: broken STOP_DET handling in
  dwc_adv_i2c_process_transfer()
--

commit 661b42138a542f38b7cf29c492367e656df39940
Author: Aniket Limaye <a-limaye@ti.com>

i2c: Add Synopsys Advanced I2C (DWC_i2c) driver

This commit adds a platform driver for the Synopsys Advanced I2C
(DWC_i2c) controller in controller mode, discovering per-block register
offsets at runtime via the block header chain.

> diff --git a/drivers/i2c/busses/i2c-dwc-adv.c b/drivers/i2c/busses/i2c-dwc-adv.c
> new file mode 100644
> index 0000000000000..990026e09bdce
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-dwc-adv.c

[ ... ]

> +static void dwc_adv_i2c_xfer_msg(struct dwc_adv_i2c_dev *dev)
> +{
> +	struct i2c_msg *msgs = dev->msgs;
> +	u32 intr_mask = DWC_ADV_I2C_CTRLR_INTR_MASK;
> +	int tx_limit, rx_limit;
> +	u32 buf_len = dev->tx_buf_len;
> +	u8 *buf = dev->tx_buf;
> +	u32 cmd;
> +	int rx_tl;
> +
> +	for (; dev->msg_write_idx < dev->msgs_num; dev->msg_write_idx++) {
> +		u32 flags = msgs[dev->msg_write_idx].flags;
> +
> +		if (!(dev->status & DWC_ADV_I2C_STATUS_SW_WRITE_IN_PROGRESS)) {
> +			buf = msgs[dev->msg_write_idx].buf;
> +			buf_len = msgs[dev->msg_write_idx].len;
> +		}
> +
> +		tx_limit = dev->tx_fifo_depth - dwc_adv_reg_read(dev, dev->regs.txflr);
> +		rx_limit = dev->rx_fifo_depth - dwc_adv_reg_read(dev, dev->regs.rxflr);
> +
> +		while (buf_len > 0 && tx_limit > 0 && rx_limit > 0) {
> +			/* Avoid rx buffer overrun */
> +			if ((flags & I2C_M_RD) &&
> +			    dev->rx_outstanding >= dev->rx_fifo_depth)
> +				break;
> +
> +			cmd = 0;
> +
> +			/*
> +			 * The bus is held while the TX FIFO is empty unless
> +			 * STOP is requested in IC_DATA_CMD, so always set it
> +			 * on the last byte of the last message.
> +			 *
> +			 * i2c-core always sets the buffer length of
> +			 * I2C_FUNC_SMBUS_BLOCK_DATA to 1. The length will be
> +			 * adjusted when receiving the first byte. Thus we
> +			 * can't stop the transaction here.
> +			 */
> +			if (dev->msg_write_idx == dev->msgs_num - 1 &&
> +			    buf_len == 1 && !(flags & I2C_M_RECV_LEN))
> +				cmd |= DWC_ADV_I2C_DATA_CMD_STOP;
> +
> +			/*
> +			 * A RESTART is only issued automatically when the
> +			 * direction changes. Request it explicitly at the
> +			 * start of each following message.
> +			 */
> +			if (dev->need_restart) {
> +				cmd |= DWC_ADV_I2C_DATA_CMD_RESTART;
> +				dev->need_restart = false;
> +			}
> +
> +			if (msgs[dev->msg_write_idx].flags & I2C_M_RD) {
> +				dwc_adv_reg_write(dev, dev->regs.data_cmd,
> +						  cmd | DWC_ADV_I2C_DATA_CMD_READ);
> +				rx_limit--;
> +				dev->rx_outstanding++;
> +			} else {
> +				dwc_adv_reg_write(dev, dev->regs.data_cmd, cmd | *buf++);
> +			}
> +			tx_limit--;
> +			buf_len--;
> +		}
> +
> +		dev->tx_buf = buf;
> +		dev->tx_buf_len = buf_len;
> +
> +		if (buf_len > 0) {
> +			dev->status |= DWC_ADV_I2C_STATUS_SW_WRITE_IN_PROGRESS;
> +			break;
> +		}
> +
> +		/*
> +		 * Because we don't know the buffer length in the
> +		 * I2C_FUNC_SMBUS_BLOCK_DATA case, we can't stop the
> +		 * transaction here. Also disable the TX_EMPTY IRQ while
> +		 * waiting for the data length byte to avoid a flood of
> +		 * bogus interrupts.
> +		 */
> +		if (flags & I2C_M_RECV_LEN) {
> +			dev->status |= DWC_ADV_I2C_STATUS_SW_WRITE_IN_PROGRESS;
> +			intr_mask &= ~DWC_ADV_I2C_INTR_TX_EMPTY;
> +			break;
> +		}

[Severity: High]
Does dwc_adv_i2c_xfer_msg() pre-queue multiple read commands when an
I2C_M_RECV_LEN message starts with len > 1?

In i2c_smbus_xfer_emulated(), enabling I2C_CLIENT_PEC increments
msg[1].len from 1 to 2 before starting an SMBus block read:

drivers/i2c/i2c-core-smbus.c:i2c_smbus_xfer_emulated() {
    ...
	if (read_write == I2C_SMBUS_READ)
		msg[1].len += 1;	/* Until we know the true length */
    ...
}

Unlike i2c_dw_xfer_msg(), the loop in dwc_adv_i2c_xfer_msg() does not
break after queuing the single length-byte read when I2C_M_RECV_LEN is
set.  When initial len > 1, it queues multiple read commands without
DWC_ADV_I2C_DATA_CMD_STOP and raises dev->rx_outstanding >= 2, which sets
regs.rx_tl >= 1 so RX_FULL does not fire when the length byte arrives.
Once dwc_adv_i2c_recv_len() runs, if the total message length is less
than or equal to the already queued dev->rx_outstanding (such as when the
target returns an invalid block length clamped to count = 1, or when
I2C_RDWR passes a larger initial len), dev->tx_buf_len becomes 0 and no
final read command with DWC_ADV_I2C_DATA_CMD_STOP is ever queued, hanging
the bus until the transfer times out.

Also, checking buf_len > 0 before flags & I2C_M_RECV_LEN means that
breaking out of the loop after queuing only the first byte of a message
with initial len > 1 skips masking DWC_ADV_I2C_INTR_TX_EMPTY.

[ ... ]

> +static u32 dwc_adv_i2c_read_clear_intrbits(struct dwc_adv_i2c_dev *dev)
> +{
> +	u32 stat = dwc_adv_reg_read(dev, dev->regs.intr_stat);
> +
> +	if (stat & DWC_ADV_I2C_INTR_RX_UNDER)
> +		dwc_adv_i2c_ack_intr(dev, DWC_ADV_I2C_INTR_CLR_RX_UNDER);
> +	if (stat & DWC_ADV_I2C_INTR_RX_OVER)
> +		dwc_adv_i2c_ack_intr(dev, DWC_ADV_I2C_INTR_CLR_RX_OVER);
> +	if (stat & DWC_ADV_I2C_INTR_TX_OVER)
> +		dwc_adv_i2c_ack_intr(dev, DWC_ADV_I2C_INTR_CLR_TX_OVER);
> +	if (stat & DWC_ADV_I2C_INTR_RD_REQ)
> +		dwc_adv_i2c_ack_intr(dev, DWC_ADV_I2C_INTR_CLR_RD_REQ);
> +	if (stat & DWC_ADV_I2C_INTR_TX_ABRT) {
> +		dev->abort_source = dwc_adv_reg_read(dev, dev->regs.tx_abrt_source);
> +		dwc_adv_i2c_ack_intr(dev, DWC_ADV_I2C_INTR_CLR_TX_ABRT);
> +	}
> +	if (stat & DWC_ADV_I2C_INTR_RX_DONE)
> +		dwc_adv_i2c_ack_intr(dev, DWC_ADV_I2C_INTR_CLR_RX_DONE);
> +	if (stat & DWC_ADV_I2C_INTR_ACTIVITY)
> +		dwc_adv_i2c_ack_intr(dev, DWC_ADV_I2C_INTR_CLR_ACTIVITY);
> +	if (stat & DWC_ADV_I2C_INTR_STOP_DET &&
> +	    (dev->rx_outstanding == 0 || (stat & DWC_ADV_I2C_INTR_RX_FULL)))
> +		dwc_adv_i2c_ack_intr(dev, DWC_ADV_I2C_INTR_CLR_STOP_DET);
> +	if (stat & DWC_ADV_I2C_INTR_START_DET)
> +		dwc_adv_i2c_ack_intr(dev, DWC_ADV_I2C_INTR_CLR_START_DET);
> +	if (stat & DWC_ADV_I2C_INTR_GEN_CALL)
> +		dwc_adv_i2c_ack_intr(dev, DWC_ADV_I2C_INTR_CLR_GEN_CALL);
> +
> +	return stat;
> +}
> +
> +static void dwc_adv_i2c_process_transfer(struct dwc_adv_i2c_dev *dev, u32 stat)
> +{
> +	if (stat & DWC_ADV_I2C_INTR_TX_ABRT) {
> +		/* TX_ABRT flushes the TX/RX FIFOs, nothing left to process */
> +		dev->cmd_err |= DWC_ADV_I2C_INTR_TX_ABRT;
> +		dev->status &= ~DWC_ADV_I2C_STATUS_SW_MASK;
> +		dev->rx_outstanding = 0;
> +		dwc_adv_reg_write(dev, dev->regs.intr_mask, 0);
> +		goto tx_aborted;
> +	}
> +
> +	if (stat & DWC_ADV_I2C_INTR_RX_FULL) {
> +		dwc_adv_i2c_read(dev);
> +		/* Re-evaluate RX_TL and TX_EMPTY for the reads still pending */
> +		stat |= DWC_ADV_I2C_INTR_TX_EMPTY;
> +	}
> +
> +	if (stat & DWC_ADV_I2C_INTR_TX_EMPTY)
> +		dwc_adv_i2c_xfer_msg(dev);
> +
> +	/* A STOP in the middle of a read or write is an error */
> +	if ((stat & DWC_ADV_I2C_INTR_STOP_DET) &&
> +	    (dev->status & (DWC_ADV_I2C_STATUS_SW_WRITE_IN_PROGRESS |
> +			     DWC_ADV_I2C_STATUS_SW_READ_IN_PROGRESS))) {
> +		dev_err(dev->dev, "spurious STOP, status 0x%x\n", dev->status);
> +		dev->rx_outstanding = 0;
> +		dev->msg_err = -EIO;
> +	}
> +
> +tx_aborted:
> +	if (((stat & (DWC_ADV_I2C_INTR_TX_ABRT | DWC_ADV_I2C_INTR_STOP_DET)) ||
> +	     dev->msg_err) && dev->rx_outstanding == 0)
> +		complete(&dev->cmd_complete);
> +}

[Severity: High]
Can dwc_adv_i2c_process_transfer() falsely abort a multi-batch read with
-EIO and leave DWC_ADV_I2C_INTR_STOP_DET unacknowledged when STOP_DET
arrives before the final RX_FULL?

dwc_adv_i2c_read_clear_intrbits() skips clearing DWC_ADV_I2C_INTR_STOP_DET
when dev->rx_outstanding > 0 and DWC_ADV_I2C_INTR_RX_FULL is not set, but
still returns stat with DWC_ADV_I2C_INTR_STOP_DET set.  On any read that
takes more than one RX_FULL interrupt (such as a read larger than
rx_fifo_depth / 2 or an I2C_M_RECV_LEN block read), the earlier
dwc_adv_i2c_read() leaves DWC_ADV_I2C_STATUS_SW_READ_IN_PROGRESS set in
dev->status.  If STOP_DET then fires before the final RX_FULL,
dwc_adv_i2c_process_transfer() treats it as a spurious STOP, zeroes
dev->rx_outstanding, sets dev->msg_err = -EIO, and completes
dev->cmd_complete without draining the RX FIFO.

Because dev->msg_err is set after dwc_adv_i2c_xfer_msg() has already run,
the if (dev->msg_err) intr_mask = 0; check in dwc_adv_i2c_xfer_msg() is
also bypassed on that interrupt, leaving interrupts unmasked and STOP_DET
asserted in hardware.  Similarly, if a premature STOP occurs during a
single-batch read after all read commands were queued (so neither
SW_WRITE_IN_PROGRESS nor SW_READ_IN_PROGRESS is set while
dev->rx_outstanding > 0 and RX_FULL is clear), STOP_DET is neither cleared
nor masked, causing an interrupt storm.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261111-tda54-upstream-i2c-v5-0-280a4a779b24@ti.com?part=2

      reply	other threads:[~2026-11-11  2:40 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-10 20:35 [PATCH v5 0/2] i2c: Add Synopsys Advanced I2C (DWC_i2c) support for TI TDA54 Aniket Limaye
2026-11-10 20:35 ` [PATCH v5 1/2] dt-bindings: i2c: Add Synopsys DWC_i2c controller Aniket Limaye
2026-11-11  0:05   ` Mark Brown
2026-11-11  8:41     ` Krzysztof Kozlowski
2026-11-11 13:24       ` Mark Brown
2026-11-10 20:35 ` [PATCH v5 2/2] i2c: Add Synopsys Advanced I2C (DWC_i2c) driver Aniket Limaye
2026-11-11  2:40   ` sashiko-bot [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-208105@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=a-limaye@ti.com \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=robh@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox