From: sashiko-bot@kernel.org
To: "Dmitry Guzman" <Dmitry.Guzman@mobileye.com>
Cc: linux-trace-kernel@vger.kernel.org
Subject: Re: [PATCH RFC v3 14/14] i2c: designware: add support for I2C_XFER_V2 - detailed fault reporting
Date: Thu, 05 Nov 2026 18:21:32 +0000 [thread overview]
Message-ID: <sashiko-outbox-199740@kernel.org> (raw)
In-Reply-To: <20261105-i2c-fault-reporting-v3-14-515ad74ac4c9@mobileye.com>
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] i2c: designware: stale rx_buf_len used in i2c_dw_fault_report()
- [High] i2c: designware: stale msgs_cplt across retries in i2c_dw_fault_report()
- [Medium] i2c: designware: missing outer loop break on abort in i2c_dw_xfer_msg()
- [Medium] i2c: designware: unread DW_IC_TXFLR on early stop in __i2c_dw_xfer_one_part()
- [Low] i2c: designware: unaddressed v2 review feedback on style and loop index type
--
commit 99b998b5107033a19225ae77e0693b6c68191b43
Author: Dmitry Guzman <Dmitry.Guzman@mobileye.com>
i2c: designware: add support for I2C_XFER_V2 - detailed fault reporting
This commit adds i2c_dw_xfer_v2() and fault reporting support to the
DesignWare I2C master driver using the ABORT_SOURCE FLUSH_CNT field,
TXFLR register, and per-byte abort checks in the FIFO read and write
loops.
> diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c
> index 14350c53a5241..c7efb8551aa30 100644
> --- a/drivers/i2c/busses/i2c-designware-master.c
> +++ b/drivers/i2c/busses/i2c-designware-master.c
> @@ -11,6 +11,7 @@
>
> #define DEFAULT_SYMBOL_NAMESPACE "I2C_DW"
>
> +#include <linux/bitfield.h>
> #include <linux/delay.h>
> #include <linux/err.h>
> #include <linux/errno.h>
> @@ -33,6 +34,14 @@
> #define AMD_TIMEOUT_MAX_US 250
> #define AMD_MASTERCFG_MASK GENMASK(15, 0)
>
> +static bool i2c_dw_check_abort_flag(struct dw_i2c_dev *dev)
> +{
> + u32 stat;
> +
> + regmap_read(dev->map, DW_IC_RAW_INTR_STAT, &stat);
> + return (!!(stat & DW_IC_INTR_TX_ABRT));
> +}
> +
> static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev)
> {
> unsigned int comp_param1;
> @@ -198,6 +207,8 @@ static void i2c_dw_xfer_init(struct dw_i2c_dev *dev)
>
> i2c_dw_set_mode(dev, DW_IC_MASTER);
>
> + dev->rx_buf_len = 0;
> +
> /* If the slave address is ten bit address, enable 10BITADDR */
> if (msgs[dev->msg_write_idx].flags & I2C_M_TEN) {
> ic_con = DW_IC_CON_10BITADDR_MASTER;
> @@ -459,6 +470,21 @@ i2c_dw_xfer_msg(struct dw_i2c_dev *dev)
> cmd | *buf++);
> }
> tx_limit--; buf_len--;
> +
> + /* If precise fault reporting is required, check if the transfer
> + * is aborted after writing each byte.
> + * Otherwise, if it is aborted during filling FIFO, there is no way
> + * to know how many bytes was written to FIFO after transfer abort
> + * and thus are not counted in FLUSH_CNT.
> + * If we are checking abort flag after each byte, we can lose only 1 byte.
> + */
> + if (dev->need_precise_report) {
> + if (i2c_dw_check_abort_flag(dev)) {
> + dev_warn_ratelimited(dev->dev, "Transfer aborted during FIFO writing. Report may be imprecise.");
> + break;
> + }
> + dev->bytes_written++;
> + }
> }
[Severity: Medium]
In i2c_dw_xfer_msg(), buf_len is decremented before checking
i2c_dw_check_abort_flag(dev), and the break only exits the inner while
loop:
tx_limit--; buf_len--;
if (dev->need_precise_report) {
if (i2c_dw_check_abort_flag(dev)) {
...
break;
}
dev->bytes_written++;
}
After the inner while loop, the outer loop over dev->msg_write_idx only
breaks when flags & I2C_M_RECV_LEN or buf_len > 0:
if (flags & I2C_M_RECV_LEN) {
dev->status |= STATUS_WRITE_IN_PROGRESS;
intr_mask &= ~DW_IC_INTR_TX_EMPTY;
break;
} else if (buf_len > 0) {
/* more bytes to be written */
dev->status |= STATUS_WRITE_IN_PROGRESS;
break;
} else
dev->status &= ~STATUS_WRITE_IN_PROGRESS;
If the abort flag is detected while writing the final byte of a message
(where buf_len was 1 and has just been decremented to 0), won't buf_len > 0
be false, causing the outer for loop to clear STATUS_WRITE_IN_PROGRESS,
advance dev->msg_write_idx, and continue writing subsequent messages into
DW_IC_DATA_CMD after the abort?
[ ... ]
> @@ -784,6 +819,8 @@ __i2c_dw_xfer_one_part(struct dw_i2c_dev *dev, struct i2c_msg *msgs, size_t num)
> dev->status = 0;
> dev->abort_source = 0;
> dev->rx_outstanding = 0;
> + dev->bytes_written = 0;
> + dev->tx_flushed = 0;
>
> ret = i2c_dw_wait_bus_not_busy(dev);
> if (ret < 0)
> @@ -795,6 +832,9 @@ __i2c_dw_xfer_one_part(struct dw_i2c_dev *dev, struct i2c_msg *msgs, size_t num)
> /* Wait for tx to complete */
> ret = i2c_dw_wait_transfer(dev);
> if (ret) {
> + if (dev->need_precise_report)
> + regmap_read(dev->map, DW_IC_TXFLR, &dev->tx_flushed);
> +
> dev_err(dev->dev, "controller timed out\n");
> /* i2c_dw_init() implicitly disables the adapter */
> i2c_recover_bus(&dev->adapter);
[Severity: Medium]
As reported by Sashiko on v2 [1], __i2c_dw_xfer_one_part() only reads
DW_IC_TXFLR into dev->tx_flushed when i2c_dw_wait_transfer() times out, and
i2c_dw_fault_report() only reads FLUSH_CNT when dev->abort_source is
non-zero.
However, __i2c_dw_xfer_one_part() can also fail with -EIO without a timeout
or TX_ABRT when i2c_dw_process_transfer() detects a spurious STOP
mid-transfer (setting dev->msg_err = -EIO) or when dev->status remains
non-zero on early termination:
if (dev->msg_err)
return dev->msg_err;
...
if (dev->status)
dev_err(dev->dev,
"transfer terminated early - interrupt latency too high?\n");
return -EIO;
In addition, in i2c_dw_process_transfer(), if DW_IC_INTR_TX_EMPTY and
DW_IC_INTR_STOP_DET are both set in stat, i2c_dw_xfer_msg() refills the TX
FIFO and increments dev->bytes_written before the spurious STOP check sets
dev->msg_err = -EIO. Because __i2c_dw_xfer_one_part() then disables or
reinitializes the controller before returning, the hardware FIFO is flushed
while both dev->abort_source and dev->tx_flushed remain 0.
When i2c_dw_fault_report() runs for these -EIO errors, won't unsent bytes
still sitting in the TX FIFO be counted as transmitted on the wire?
> @@ -886,13 +926,72 @@ i2c_dw_msg_is_valid(struct dw_i2c_dev *dev, const struct i2c_msg *msgs, size_t i
> return true;
> }
>
> +static int i2c_dw_fault_report(struct dw_i2c_dev *dev, struct i2c_transfer_report *report)
> +{
> + unsigned int idx;
> +
> + /*
> + * Get the number of bytes that were put into TX FIFO but were not sent on wire.
> + * If abort IRQ was received, this number is stored in abort_source register.
> + * Otherwise, if ic_dw_wait_transfer() returned timeout, use TXFLR register value
> + * read before the controller was reset.
> + */
> + if (dev->abort_source)
> + dev->tx_flushed = FIELD_GET(DW_IC_TX_ABRT_SOURCE_FLUSH_CNT_MASK, dev->abort_source);
> +
> + if (dev->tx_flushed <= dev->bytes_written)
> + dev->bytes_written -= dev->tx_flushed;
> + else
> + dev->bytes_written = 0;
> +
> + /*
> + * The last byte that the transmission was interrupted on is not counted as flushed.
> + * We should not report it as transferred successfully, so decrement the counter.
> + */
> + if (dev->bytes_written)
> + dev->bytes_written--;
> +
> + for (idx = 0; idx < dev->msg_write_idx; idx++) {
> + if (dev->msgs[idx].len <= dev->bytes_written)
> + dev->bytes_written -= dev->msgs[idx].len;
> + else
> + break;
> + }
> +
> + report->fault_msg_idx = idx;
> + for (int i = dev->msg_read_idx; i <= idx && i < dev->msgs_num; i++) {
[Severity: Low]
As reported by Andy Shevchenko on v2 [2], there are a few style and type
issues remaining from the previous revision:
1. In i2c_dw_fault_report(), idx was changed to unsigned int, but the loop
counter i in for (int i = dev->msg_read_idx; i <= idx && i <
dev->msgs_num; i++) is still a signed int compared against unsigned int
idx.
2. In i2c_dw_check_abort_flag(), return (!!(stat & DW_IC_INTR_TX_ABRT));
still uses redundant outer parentheses and double negation in a function
returning bool.
3. In i2c_dw_xfer_msg(), the multi-line comment starting with "/* If
precise fault reporting is required..." still places text on the opening
/* line instead of starting with a separate /* line.
Could these be cleaned up to match the feedback from v2?
> + if (dev->msgs[i].flags & I2C_M_RD) {
> + report->msgs_cplt = i;
> + if (!(dev->rx_buf_len))
> + report->bytes_cplt = 0;
> + else
> + report->bytes_cplt = dev->rx_buf - dev->msgs[i].buf;
[Severity: High]
Can i2c_dw_fault_report() use a stale dev->rx_buf_len and dangling
dev->rx_buf pointer from an earlier transfer if i2c_dw_wait_bus_not_busy()
fails?
When an earlier I2C_M_RD transfer is interrupted in i2c_dw_read() while
len > 0, i2c_dw_read() saves dev->rx_buf_len = len and dev->rx_buf = buf
(pointing into that transfer's message buffer) and returns.
On the next transfer (or a subsequent part in a multi-part transfer),
__i2c_dw_xfer_one_part() calls i2c_dw_wait_bus_not_busy(dev) before calling
i2c_dw_xfer_init(dev), which is where dev->rx_buf_len = 0 was placed:
ret = i2c_dw_wait_bus_not_busy(dev);
if (ret < 0)
return ret;
/* Start the transfers */
i2c_dw_xfer_init(dev);
If i2c_dw_wait_bus_not_busy(dev) returns a negative error,
__i2c_dw_xfer_one_part() returns early without calling i2c_dw_xfer_init().
i2c_dw_xfer_common_v2() then calls i2c_dw_fault_report(dev, report) with
idx == 0 and dev->msg_read_idx == 0. If the new transfer's msgs[0] has
I2C_M_RD set, dev->rx_buf_len is still non-zero from the previous failed
transfer, so i2c_dw_fault_report() computes:
report->bytes_cplt = dev->rx_buf - dev->msgs[i].buf;
subtracting the current message's buffer address from the previous
transfer's freed dev->rx_buf pointer. In i2cdev_ioctl_rdwr_v2(),
msgs[msgs_done].len is then set to report.bytes_cplt and passed to
copy_to_user(), which can read out of bounds past msgs[0].buf. Should
dev->rx_buf_len = 0 be initialized at the start of __i2c_dw_xfer_one_part()
alongside dev->msg_read_idx and dev->bytes_written?
> + if ((i < idx) || (report->bytes_cplt < dev->bytes_written))
> + dev_warn_ratelimited(dev->dev, "Read data lost due to FIFO flush");
> + break;
> + }
> + }
> +
> + if (report->msgs_cplt < 0) {
> + report->msgs_cplt = idx;
> + report->bytes_cplt = dev->bytes_written;
> + }
> +
> + return idx;
> +}
[ ... ]
> @@ -937,16 +1048,34 @@ i2c_dw_xfer_common(struct dw_i2c_dev *dev, struct i2c_msg msgs[], int num)
>
> i2c_dw_release_lock(dev);
>
> - if (ret < 0)
> + if (ret < 0) {
> + if (report) {
> + i2c_dw_fault_report(dev, report);
> + report->msgs_cplt += msgs_in_prev_parts;
> + report->fault_msg_idx += msgs_in_prev_parts;
> + }
> return ret;
> + }
[Severity: High]
What happens to report->msgs_cplt and report->bytes_cplt when an attempt
fails with -EAGAIN (for example on DW_IC_TX_ARB_LOST) and __i2c_transfer_v2()
retries the transfer?
In __i2c_transfer_v2(), i2c_transfer_report_init(report) (which sets
report->msgs_cplt = -EOPNOTSUPP) is called only once before the retry loop:
if (report)
i2c_transfer_report_init(report);
...
for (ret = 0, try = 0; try <= adap->retries; try++) {
...
if (report)
ret = adap->algo->xfer_v2(adap, msgs, num, report);
...
if (ret != -EAGAIN)
break;
}
On the first failed attempt returning -EAGAIN, i2c_dw_fault_report() sets
report->msgs_cplt to a non-negative index and sets report->bytes_cplt. When
__i2c_transfer_v2() calls i2c_dw_xfer_v2() again for the next try and that
attempt also fails, if there is no I2C_M_RD message in
[dev->msg_read_idx, idx], i2c_dw_fault_report() checks:
if (report->msgs_cplt < 0) {
report->msgs_cplt = idx;
report->bytes_cplt = dev->bytes_written;
}
Because report->msgs_cplt is already >= 0 from the earlier try, neither
report->msgs_cplt nor report->bytes_cplt is updated. Furthermore, if
msgs_in_prev_parts > 0, i2c_dw_xfer_common_v2() executes
report->msgs_cplt += msgs_in_prev_parts on each failed retry, accumulating
msgs_in_prev_parts onto the stale report->msgs_cplt until it can exceed num.
Should report be reinitialized on entry to i2c_dw_xfer_common_v2() or
i2c_dw_fault_report()?
[1] https://lore.kernel.org/r/20260903-i2c-fault-reporting-v2-12-fedeb91792e6@mobileye.com
[2] https://lore.kernel.org/r/apkl_XHaZreKoipX@ashevche-desk.local
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261105-i2c-fault-reporting-v3-0-515ad74ac4c9@mobileye.com?part=14
prev parent reply other threads:[~2026-11-05 18:21 UTC|newest]
Thread overview: 23+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-05 17:15 [PATCH RFC v3 00/14] I2C - detailed transfer reporting in case of a fault Dmitry Guzman
2026-11-05 17:15 ` [PATCH RFC v3 01/14] i2c: uapi: add i2c_transfer_report structure Dmitry Guzman
2026-11-05 17:15 ` [PATCH RFC v3 02/14] i2c: core: add xfer_v2() to i2c_algorithm structure Dmitry Guzman
2026-11-05 17:15 ` [PATCH RFC v3 03/14] i2c: core: add helper functions for transfer report generation Dmitry Guzman
2026-11-05 17:15 ` [PATCH RFC v3 04/14] i2c: core: add i2c_xfer_to_xfer_v2() helper function Dmitry Guzman
2026-11-05 17:15 ` [PATCH RFC v3 05/14] i2c: trace: pass message length separately in trace_i2c_reply() Dmitry Guzman
2026-11-05 17:15 ` [PATCH RFC v3 06/14] i2c: core: make i2c_check_for_quirks() return pointer to the message Dmitry Guzman
2026-11-05 17:15 ` [PATCH RFC v3 07/14] i2c: core: implement i2c_transfer_v2() - transfer with detailed reporting Dmitry Guzman
2026-11-05 17:34 ` sashiko-bot
2026-11-05 18:28 ` Andy Shevchenko
2026-11-05 17:15 ` [PATCH RFC v3 08/14] i2c: uapi: add I2C_XFER_V2 ioctl - detailed fault reporting Dmitry Guzman
2026-11-05 17:35 ` sashiko-bot
2026-11-05 17:15 ` [PATCH RFC v3 09/14] i2c: core: implement I2C_RDWR_V2 ioctl - transfer with detailed report Dmitry Guzman
2026-11-05 17:51 ` sashiko-bot
2026-11-05 17:15 ` [PATCH RFC v3 10/14] i2c: doc: add section for I2C_RDWR_V2 ioctl Dmitry Guzman
2026-11-05 17:44 ` sashiko-bot
2026-11-05 17:15 ` [PATCH RFC v3 11/14] i2c: nomadik: save I2C_CR register before resetting HW in IRQ handler Dmitry Guzman
2026-11-05 17:53 ` sashiko-bot
2026-11-05 17:15 ` [PATCH RFC v3 12/14] i2c: nomadik: add support for I2C_XFER_V2 - detailed fault reporting Dmitry Guzman
2026-11-05 18:13 ` sashiko-bot
2026-11-05 17:15 ` [PATCH RFC v3 13/14] i2c: designware: use separate `i2c_algorithm` for AMD_NAVI_GPU Dmitry Guzman
2026-11-05 17:15 ` [PATCH RFC v3 14/14] i2c: designware: add support for I2C_XFER_V2 - detailed fault reporting Dmitry Guzman
2026-11-05 18:21 ` 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-199740@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=Dmitry.Guzman@mobileye.com \
--cc=linux-trace-kernel@vger.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