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
prev parent reply other threads:[~2026-11-11 2:40 UTC|newest]
Thread overview: 9+ 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-12 18:16 ` Aniket Limaye
2026-11-12 18:40 ` 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.