* [PATCH v2] i2c: qcom-geni: Add dynamic transfer timeout based on transfer length and frequency
@ 2026-07-05 13:57 Aniket Randive
2026-07-05 14:09 ` sashiko-bot
` (2 more replies)
0 siblings, 3 replies; 10+ messages in thread
From: Aniket Randive @ 2026-07-05 13:57 UTC (permalink / raw)
To: mukesh.savaliya, viken.dadhaniya, andi.shyti, sumit.semwal,
christian.koenig
Cc: linux-i2c, linux-arm-msm, linux-kernel, linux-media, dri-devel,
linaro-mm-sig, naresh.maramaina, aniket.randive
The driver uses a static XFER_TIMEOUT of HZ (1 second) for all transfers
regardless of message length or bus frequency, causing unnecessary
delays on error paths.
Compute the timeout dynamically from message length and bus frequency
with a 10x safety margin over the theoretical wire time and a 300ms
floor. For GPI multi-descriptor transfers, use the maximum message
length across all queued messages as the per-completion timeout.
Signed-off-by: Aniket Randive <aniket.randive@oss.qualcomm.com>
---
Changes in v2:
- Remove accidental defconfig change.
drivers/i2c/busses/i2c-qcom-geni.c | 37 +++++++++++++++++++++++-------
1 file changed, 29 insertions(+), 8 deletions(-)
diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c
index 96dbf04138be..43ae2121f01c 100644
--- a/drivers/i2c/busses/i2c-qcom-geni.c
+++ b/drivers/i2c/busses/i2c-qcom-geni.c
@@ -74,9 +74,12 @@ enum geni_i2c_err_code {
#define PACKING_BYTES_PW 4
#define ABORT_TIMEOUT HZ
-#define XFER_TIMEOUT HZ
#define RST_TIMEOUT HZ
+/* 9 bits per byte (8 data + 1 ACK), 10x safety margin, 300ms floor */
+#define I2C_TIMEOUT_SAFETY_COEFFICIENT 10
+#define I2C_TIMEOUT_MIN_USEC 300000
+
struct geni_i2c_desc {
bool no_dma_support;
unsigned int tx_fifo_depth;
@@ -204,6 +207,16 @@ static int geni_i2c_clk_map_idx(struct geni_i2c_dev *gi2c)
return -EINVAL;
}
+static unsigned long geni_i2c_xfer_timeout(struct geni_i2c_dev *gi2c, size_t len)
+{
+ size_t bit_cnt = len * 9;
+ size_t bit_usec = (bit_cnt * USEC_PER_SEC) / gi2c->clk_freq_out;
+ size_t xfer_max_usec = (bit_usec * I2C_TIMEOUT_SAFETY_COEFFICIENT) +
+ I2C_TIMEOUT_MIN_USEC;
+
+ return usecs_to_jiffies(xfer_max_usec);
+}
+
static int qcom_geni_i2c_conf(struct geni_se *se, unsigned long freq)
{
struct geni_i2c_dev *gi2c = dev_get_drvdata(se->dev);
@@ -471,7 +484,7 @@ static int geni_i2c_rx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg,
}
cur = gi2c->cur;
- time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT);
+ time_left = wait_for_completion_timeout(&gi2c->done, geni_i2c_xfer_timeout(gi2c, len));
if (!time_left)
geni_i2c_abort_xfer(gi2c);
@@ -513,7 +526,7 @@ static int geni_i2c_tx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg,
writel_relaxed(1, se->base + SE_GENI_TX_WATERMARK_REG);
cur = gi2c->cur;
- time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT);
+ time_left = wait_for_completion_timeout(&gi2c->done, geni_i2c_xfer_timeout(gi2c, len));
if (!time_left)
geni_i2c_abort_xfer(gi2c);
@@ -591,7 +604,7 @@ static void geni_i2c_gpi_multi_desc_unmap(struct geni_i2c_dev *gi2c, struct i2c_
* geni_i2c_gpi_multi_xfer_timeout_handler() - Handles multi message transfer timeout
* @dev: Pointer to the corresponding dev node
* @multi_xfer: Pointer to the geni_i2c_gpi_multi_desc_xfer
- * @transfer_timeout_msecs: Timeout value in milliseconds
+ * @transfer_timeout_msecs: Per-message completion timeout in jiffies
* @transfer_comp: Completion object of the transfer
*
* This function waits for the completion of each processed transfer messages
@@ -601,7 +614,7 @@ static void geni_i2c_gpi_multi_desc_unmap(struct geni_i2c_dev *gi2c, struct i2c_
*/
static int geni_i2c_gpi_multi_xfer_timeout_handler(struct device *dev,
struct geni_i2c_gpi_multi_desc_xfer *multi_xfer,
- u32 transfer_timeout_msecs,
+ unsigned long timeout_jiffies,
struct completion *transfer_comp)
{
int i;
@@ -612,7 +625,7 @@ static int geni_i2c_gpi_multi_xfer_timeout_handler(struct device *dev,
if (multi_xfer->msg_idx_cnt != multi_xfer->irq_cnt) {
time_left = wait_for_completion_timeout(transfer_comp,
- transfer_timeout_msecs);
+ timeout_jiffies);
if (!time_left) {
dev_err(dev, "%s: Transfer timeout\n", __func__);
return -ETIMEDOUT;
@@ -736,8 +749,15 @@ static int geni_i2c_gpi(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[],
dma_async_issue_pending(gi2c->tx_c);
if ((msg_idx == (gi2c->num_msgs - 1)) || flags & DMA_PREP_INTERRUPT) {
+ size_t max_len = 0;
+ int j;
+
+ for (j = 0; j < gi2c->num_msgs; j++)
+ max_len = max_t(size_t, max_len, msgs[j].len);
+
ret = geni_i2c_gpi_multi_xfer_timeout_handler(gi2c->se.dev, gi2c_gpi_xfer,
- XFER_TIMEOUT, &gi2c->done);
+ geni_i2c_xfer_timeout(
+ gi2c, max_len), &gi2c->done);
if (ret) {
dev_err(gi2c->se.dev,
"I2C multi write msg transfer timeout: %d\n",
@@ -852,7 +872,8 @@ static int geni_i2c_gpi_xfer(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[], i
if (!gi2c->is_tx_multi_desc_xfer) {
dma_async_issue_pending(gi2c->tx_c);
- time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT);
+ time_left = wait_for_completion_timeout(&gi2c->done, geni_i2c_xfer_timeout(
+ gi2c, msgs[i].len));
if (!time_left) {
dev_err(gi2c->se.dev, "%s:I2C timeout\n", __func__);
gi2c->err = -ETIMEDOUT;
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v2] i2c: qcom-geni: Add dynamic transfer timeout based on transfer length and frequency
2026-07-05 13:57 [PATCH v2] i2c: qcom-geni: Add dynamic transfer timeout based on transfer length and frequency Aniket Randive
@ 2026-07-05 14:09 ` sashiko-bot
2026-07-06 11:41 ` Konrad Dybcio
2026-07-07 12:07 ` Mukesh Savaliya
2 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-07-05 14:09 UTC (permalink / raw)
To: Aniket Randive; +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Integer overflow on 32-bit architectures in dynamic timeout calculation causes premature timeouts for large messages.
- [High] Multi-descriptor DMA transfers incorrectly calculate the batched transfer timeout using the maximum single message length rather than the aggregate transfer length, causing premature timeouts.
- [Low] Kernel-doc parameter name mismatch for `timeout_jiffies` in `geni_i2c_gpi_multi_xfer_timeout_handler`.
--
commit 7cdbc3fca002264681a23e9e8f274275a5451975
Author: Aniket Randive <aniket.randive@oss.qualcomm.com>
i2c: qcom-geni: Add dynamic transfer timeout based on transfer length and frequency
The driver uses a static XFER_TIMEOUT of HZ (1 second) for all transfers
regardless of message length or bus frequency, causing unnecessary delays
on error paths. Compute the timeout dynamically from message length and
bus frequency.
> diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c
> index 96dbf04138bee..43ae2121f01c2 100644
> --- a/drivers/i2c/busses/i2c-qcom-geni.c
> +++ b/drivers/i2c/busses/i2c-qcom-geni.c
[ ... ]
> @@ -204,6 +207,16 @@ static int geni_i2c_clk_map_idx(struct geni_i2c_dev *gi2c)
> return -EINVAL;
> }
>
> +static unsigned long geni_i2c_xfer_timeout(struct geni_i2c_dev *gi2c, size_t len)
> +{
> + size_t bit_cnt = len * 9;
> + size_t bit_usec = (bit_cnt * USEC_PER_SEC) / gi2c->clk_freq_out;
[Severity: High]
Can this calculation overflow on 32-bit architectures?
If size_t is 32-bit, bit_cnt * USEC_PER_SEC can wrap around for large
I2C transfers, such as firmware uploads or EEPROM writes over 8KB. This
would produce a calculated timeout much smaller than the actual wire time,
causing operations to prematurely abort.
> + size_t xfer_max_usec = (bit_usec * I2C_TIMEOUT_SAFETY_COEFFICIENT) +
> + I2C_TIMEOUT_MIN_USEC;
> +
> + return usecs_to_jiffies(xfer_max_usec);
> +}
[ ... ]
> * geni_i2c_gpi_multi_xfer_timeout_handler() - Handles multi message transfer timeout
> * @dev: Pointer to the corresponding dev node
> * @multi_xfer: Pointer to the geni_i2c_gpi_multi_desc_xfer
> - * @transfer_timeout_msecs: Timeout value in milliseconds
> + * @transfer_timeout_msecs: Per-message completion timeout in jiffies
[Severity: Low]
This isn't a bug, but does the kernel-doc parameter name match the function
signature?
The comment documents transfer_timeout_msecs, but the function signature
below was changed to take timeout_jiffies.
> * @transfer_comp: Completion object of the transfer
> *
> * This function waits for the completion of each processed transfer messages
> @@ -601,7 +614,7 @@ static void geni_i2c_gpi_multi_desc_unmap(struct geni_i2c_dev *gi2c, struct i2c_
> */
> static int geni_i2c_gpi_multi_xfer_timeout_handler(struct device *dev,
> struct geni_i2c_gpi_multi_desc_xfer *multi_xfer,
> - u32 transfer_timeout_msecs,
> + unsigned long timeout_jiffies,
> struct completion *transfer_comp)
[ ... ]
> @@ -736,8 +749,15 @@ static int geni_i2c_gpi(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[],
> dma_async_issue_pending(gi2c->tx_c);
>
> if ((msg_idx == (gi2c->num_msgs - 1)) || flags & DMA_PREP_INTERRUPT) {
> + size_t max_len = 0;
> + int j;
> +
> + for (j = 0; j < gi2c->num_msgs; j++)
> + max_len = max_t(size_t, max_len, msgs[j].len);
> +
> ret = geni_i2c_gpi_multi_xfer_timeout_handler(gi2c->se.dev, gi2c_gpi_xfer,
> - XFER_TIMEOUT, &gi2c->done);
> + geni_i2c_xfer_timeout(
> + gi2c, max_len), &gi2c->done);
[Severity: High]
Does this calculate the timeout correctly for multi-descriptor batches?
For multi-descriptor transfers, the hardware processes the entire batch before
raising a single interrupt on the final descriptor. Wait for completion uses a
timeout computed from max_len (the length of the single longest message).
If a batch contains many small messages, their cumulative wire time could
easily exceed the timeout calculated for the single longest message, leading
to premature timeouts. Should this calculate the sum of all message lengths
instead of taking the maximum?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260705135739.4038826-1-aniket.randive@oss.qualcomm.com?part=1
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2] i2c: qcom-geni: Add dynamic transfer timeout based on transfer length and frequency
2026-07-05 13:57 [PATCH v2] i2c: qcom-geni: Add dynamic transfer timeout based on transfer length and frequency Aniket Randive
2026-07-05 14:09 ` sashiko-bot
@ 2026-07-06 11:41 ` Konrad Dybcio
2026-07-09 6:28 ` Aniket RANDIVE
2026-07-07 12:07 ` Mukesh Savaliya
2 siblings, 1 reply; 10+ messages in thread
From: Konrad Dybcio @ 2026-07-06 11:41 UTC (permalink / raw)
To: Aniket Randive, mukesh.savaliya, viken.dadhaniya, andi.shyti,
sumit.semwal, christian.koenig
Cc: linux-i2c, linux-arm-msm, linux-kernel, linux-media, dri-devel,
linaro-mm-sig, naresh.maramaina
On 7/5/26 3:57 PM, Aniket Randive wrote:
> The driver uses a static XFER_TIMEOUT of HZ (1 second) for all transfers
> regardless of message length or bus frequency, causing unnecessary
> delays on error paths.
>
> Compute the timeout dynamically from message length and bus frequency
> with a 10x safety margin over the theoretical wire time and a 300ms
> floor. For GPI multi-descriptor transfers, use the maximum message
> length across all queued messages as the per-completion timeout.
What's the reason for a 0.3 s floor?
Why a 10x safety margin specifically?
[...]
> +static unsigned long geni_i2c_xfer_timeout(struct geni_i2c_dev *gi2c, size_t len)
> +{
> + size_t bit_cnt = len * 9;
> + size_t bit_usec = (bit_cnt * USEC_PER_SEC) / gi2c->clk_freq_out;
mult_frac()
Konrad
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2] i2c: qcom-geni: Add dynamic transfer timeout based on transfer length and frequency
2026-07-05 13:57 [PATCH v2] i2c: qcom-geni: Add dynamic transfer timeout based on transfer length and frequency Aniket Randive
2026-07-05 14:09 ` sashiko-bot
2026-07-06 11:41 ` Konrad Dybcio
@ 2026-07-07 12:07 ` Mukesh Savaliya
2026-07-09 5:49 ` Aniket RANDIVE
2 siblings, 1 reply; 10+ messages in thread
From: Mukesh Savaliya @ 2026-07-07 12:07 UTC (permalink / raw)
To: Aniket Randive, viken.dadhaniya, andi.shyti, sumit.semwal,
christian.koenig
Cc: linux-i2c, linux-arm-msm, linux-kernel, linux-media, dri-devel,
linaro-mm-sig, naresh.maramaina
On 7/5/2026 7:27 PM, Aniket Randive wrote:
[...]
> static int qcom_geni_i2c_conf(struct geni_se *se, unsigned long freq)
> {
> struct geni_i2c_dev *gi2c = dev_get_drvdata(se->dev);
> @@ -471,7 +484,7 @@ static int geni_i2c_rx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg,
> }
>
> cur = gi2c->cur;
> - time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT);
> + time_left = wait_for_completion_timeout(&gi2c->done, geni_i2c_xfer_timeout(gi2c, len));
Simplify with timeout = geni_i2c_xfer_timeout(gi2c, len) and use as an arg ?
> if (!time_left)
> geni_i2c_abort_xfer(gi2c);
>
> @@ -513,7 +526,7 @@ static int geni_i2c_tx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg,
> writel_relaxed(1, se->base + SE_GENI_TX_WATERMARK_REG);
>
> cur = gi2c->cur;
> - time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT);
> + time_left = wait_for_completion_timeout(&gi2c->done, geni_i2c_xfer_timeout(gi2c, len));
> if (!time_left)
> geni_i2c_abort_xfer(gi2c);
>
> @@ -591,7 +604,7 @@ static void geni_i2c_gpi_multi_desc_unmap(struct geni_i2c_dev *gi2c, struct i2c_
> * geni_i2c_gpi_multi_xfer_timeout_handler() - Handles multi message transfer timeout
> * @dev: Pointer to the corresponding dev node
> * @multi_xfer: Pointer to the geni_i2c_gpi_multi_desc_xfer
> - * @transfer_timeout_msecs: Timeout value in milliseconds
> + * @transfer_timeout_msecs: Per-message completion timeout in jiffies
> * @transfer_comp: Completion object of the transfer
> *
> * This function waits for the completion of each processed transfer messages
> @@ -601,7 +614,7 @@ static void geni_i2c_gpi_multi_desc_unmap(struct geni_i2c_dev *gi2c, struct i2c_
> */
> static int geni_i2c_gpi_multi_xfer_timeout_handler(struct device *dev,
> struct geni_i2c_gpi_multi_desc_xfer *multi_xfer,
> - u32 transfer_timeout_msecs,
> + unsigned long timeout_jiffies,
> struct completion *transfer_comp)
> {
> int i;
> @@ -612,7 +625,7 @@ static int geni_i2c_gpi_multi_xfer_timeout_handler(struct device *dev,
>
> if (multi_xfer->msg_idx_cnt != multi_xfer->irq_cnt) {
> time_left = wait_for_completion_timeout(transfer_comp,
> - transfer_timeout_msecs);
> + timeout_jiffies);
> if (!time_left) {
> dev_err(dev, "%s: Transfer timeout\n", __func__);
> return -ETIMEDOUT;
> @@ -736,8 +749,15 @@ static int geni_i2c_gpi(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[],
> dma_async_issue_pending(gi2c->tx_c);
>
> if ((msg_idx == (gi2c->num_msgs - 1)) || flags & DMA_PREP_INTERRUPT) {
> + size_t max_len = 0;
> + int j;
> +
> + for (j = 0; j < gi2c->num_msgs; j++)
> + max_len = max_t(size_t, max_len, msgs[j].len);
> +
> ret = geni_i2c_gpi_multi_xfer_timeout_handler(gi2c->se.dev, gi2c_gpi_xfer,
> - XFER_TIMEOUT, &gi2c->done);
> + geni_i2c_xfer_timeout(
> + gi2c, max_len), &gi2c->done);
Simplify with timeout = geni_i2c_xfer_timeout(gi2c, len) and use as an arg ?
> if (ret) {
> dev_err(gi2c->se.dev,
> "I2C multi write msg transfer timeout: %d\n",
> @@ -852,7 +872,8 @@ static int geni_i2c_gpi_xfer(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[], i
>
> if (!gi2c->is_tx_multi_desc_xfer) {
> dma_async_issue_pending(gi2c->tx_c);
> - time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT);
> + time_left = wait_for_completion_timeout(&gi2c->done, geni_i2c_xfer_timeout(
> + gi2c, msgs[i].len));
> if (!time_left) {
> dev_err(gi2c->se.dev, "%s:I2C timeout\n", __func__);
> gi2c->err = -ETIMEDOUT;
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2] i2c: qcom-geni: Add dynamic transfer timeout based on transfer length and frequency
2026-07-07 12:07 ` Mukesh Savaliya
@ 2026-07-09 5:49 ` Aniket RANDIVE
0 siblings, 0 replies; 10+ messages in thread
From: Aniket RANDIVE @ 2026-07-09 5:49 UTC (permalink / raw)
To: Mukesh Savaliya, viken.dadhaniya, andi.shyti, sumit.semwal,
christian.koenig
Cc: linux-i2c, linux-arm-msm, linux-kernel, linux-media, dri-devel,
linaro-mm-sig, naresh.maramaina
Thanks Mukesh for the review.
On 7/7/2026 5:37 PM, Mukesh Savaliya wrote:
>
>
> On 7/5/2026 7:27 PM, Aniket Randive wrote:
> [...]
>
>> static int qcom_geni_i2c_conf(struct geni_se *se, unsigned long freq)
>> {
>> struct geni_i2c_dev *gi2c = dev_get_drvdata(se->dev);
>> @@ -471,7 +484,7 @@ static int geni_i2c_rx_one_msg(struct geni_i2c_dev
>> *gi2c, struct i2c_msg *msg,
>> }
>> cur = gi2c->cur;
>> - time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT);
>> + time_left = wait_for_completion_timeout(&gi2c->done,
>> geni_i2c_xfer_timeout(gi2c, len));
> Simplify with timeout = geni_i2c_xfer_timeout(gi2c, len) and use as an
> arg ?
Yes. we can add extra variable. I though to avoid extra variable.
>> if (!time_left)
>> geni_i2c_abort_xfer(gi2c);
>> @@ -513,7 +526,7 @@ static int geni_i2c_tx_one_msg(struct geni_i2c_dev
>> *gi2c, struct i2c_msg *msg,
>> writel_relaxed(1, se->base + SE_GENI_TX_WATERMARK_REG);
>> cur = gi2c->cur;
>> - time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT);
>> + time_left = wait_for_completion_timeout(&gi2c->done,
>> geni_i2c_xfer_timeout(gi2c, len));
>> if (!time_left)
>> geni_i2c_abort_xfer(gi2c);
>> @@ -591,7 +604,7 @@ static void geni_i2c_gpi_multi_desc_unmap(struct
>> geni_i2c_dev *gi2c, struct i2c_
>> * geni_i2c_gpi_multi_xfer_timeout_handler() - Handles multi message
>> transfer timeout
>> * @dev: Pointer to the corresponding dev node
>> * @multi_xfer: Pointer to the geni_i2c_gpi_multi_desc_xfer
>> - * @transfer_timeout_msecs: Timeout value in milliseconds
>> + * @transfer_timeout_msecs: Per-message completion timeout in jiffies
>> * @transfer_comp: Completion object of the transfer
>> *
>> * This function waits for the completion of each processed transfer
>> messages
>> @@ -601,7 +614,7 @@ static void geni_i2c_gpi_multi_desc_unmap(struct
>> geni_i2c_dev *gi2c, struct i2c_
>> */
>> static int geni_i2c_gpi_multi_xfer_timeout_handler(struct device *dev,
>> struct geni_i2c_gpi_multi_desc_xfer
>> *multi_xfer,
>> - u32 transfer_timeout_msecs,
>> + unsigned long timeout_jiffies,
>> struct completion *transfer_comp)
>> {
>> int i;
>> @@ -612,7 +625,7 @@ static int
>> geni_i2c_gpi_multi_xfer_timeout_handler(struct device *dev,
>> if (multi_xfer->msg_idx_cnt != multi_xfer->irq_cnt) {
>> time_left = wait_for_completion_timeout(transfer_comp,
>> - transfer_timeout_msecs);
>> + timeout_jiffies);
>> if (!time_left) {
>> dev_err(dev, "%s: Transfer timeout\n", __func__);
>> return -ETIMEDOUT;
>> @@ -736,8 +749,15 @@ static int geni_i2c_gpi(struct geni_i2c_dev
>> *gi2c, struct i2c_msg msgs[],
>> dma_async_issue_pending(gi2c->tx_c);
>> if ((msg_idx == (gi2c->num_msgs - 1)) || flags &
>> DMA_PREP_INTERRUPT) {
>> + size_t max_len = 0;
>> + int j;
>> +
>> + for (j = 0; j < gi2c->num_msgs; j++)
>> + max_len = max_t(size_t, max_len, msgs[j].len);
>> +
>> ret = geni_i2c_gpi_multi_xfer_timeout_handler(gi2c-
>> >se.dev, gi2c_gpi_xfer,
>> - XFER_TIMEOUT, &gi2c->done);
>> + geni_i2c_xfer_timeout(
>> + gi2c, max_len), &gi2c->done);
> Simplify with timeout = geni_i2c_xfer_timeout(gi2c, len) and use as an
> arg ?
>> if (ret) {
>> dev_err(gi2c->se.dev,
>> "I2C multi write msg transfer timeout: %d\n",
>> @@ -852,7 +872,8 @@ static int geni_i2c_gpi_xfer(struct geni_i2c_dev
>> *gi2c, struct i2c_msg msgs[], i
>> if (!gi2c->is_tx_multi_desc_xfer) {
>> dma_async_issue_pending(gi2c->tx_c);
>> - time_left = wait_for_completion_timeout(&gi2c->done,
>> XFER_TIMEOUT);
>> + time_left = wait_for_completion_timeout(&gi2c->done,
>> geni_i2c_xfer_timeout(
>> + gi2c, msgs[i].len));
>> if (!time_left) {
>> dev_err(gi2c->se.dev, "%s:I2C timeout\n", __func__);
>> gi2c->err = -ETIMEDOUT;
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2] i2c: qcom-geni: Add dynamic transfer timeout based on transfer length and frequency
@ 2026-07-09 5:59 Aniket Randive
2026-07-09 6:12 ` sashiko-bot
0 siblings, 1 reply; 10+ messages in thread
From: Aniket Randive @ 2026-07-09 5:59 UTC (permalink / raw)
To: mukesh.savaliya, viken.dadhaniya, andi.shyti, sumit.semwal,
christian.koenig
Cc: linux-i2c, linux-arm-msm, linux-kernel, linux-media, dri-devel,
linaro-mm-sig, naresh.maramaina, aniket.randive
The driver uses a static XFER_TIMEOUT of HZ (1 second) for all transfers
regardless of message length or bus frequency, causing unnecessary
delays on error paths.
Compute the timeout dynamically from message length and bus frequency
with a 10x safety margin over the theoretical wire time and a 300ms
floor. For GPI multi-descriptor transfers, use the maximum message
length across all queued messages as the per-completion timeout.
Signed-off-by: Aniket Randive <aniket.randive@oss.qualcomm.com>
---
drivers/i2c/busses/i2c-qcom-geni.c | 45 +++++++++++++++++++++++-------
1 file changed, 35 insertions(+), 10 deletions(-)
diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c
index 96dbf04138be..d43db77b3678 100644
--- a/drivers/i2c/busses/i2c-qcom-geni.c
+++ b/drivers/i2c/busses/i2c-qcom-geni.c
@@ -74,9 +74,12 @@ enum geni_i2c_err_code {
#define PACKING_BYTES_PW 4
#define ABORT_TIMEOUT HZ
-#define XFER_TIMEOUT HZ
#define RST_TIMEOUT HZ
+/* 9 bits per byte (8 data + 1 ACK), 10x safety margin, 300ms floor */
+#define I2C_TIMEOUT_SAFETY_COEFFICIENT 10
+#define I2C_TIMEOUT_MIN_USEC 300000
+
struct geni_i2c_desc {
bool no_dma_support;
unsigned int tx_fifo_depth;
@@ -204,6 +207,16 @@ static int geni_i2c_clk_map_idx(struct geni_i2c_dev *gi2c)
return -EINVAL;
}
+static unsigned long geni_i2c_xfer_timeout(struct geni_i2c_dev *gi2c, size_t len)
+{
+ size_t bit_cnt = len * 9;
+ size_t bit_usec = (bit_cnt * USEC_PER_SEC) / gi2c->clk_freq_out;
+ size_t xfer_max_usec = (bit_usec * I2C_TIMEOUT_SAFETY_COEFFICIENT) +
+ I2C_TIMEOUT_MIN_USEC;
+
+ return usecs_to_jiffies(xfer_max_usec);
+}
+
static int qcom_geni_i2c_conf(struct geni_se *se, unsigned long freq)
{
struct geni_i2c_dev *gi2c = dev_get_drvdata(se->dev);
@@ -445,7 +458,7 @@ static int geni_i2c_rx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg,
u32 m_param)
{
dma_addr_t rx_dma = 0;
- unsigned long time_left;
+ unsigned long time_left, timeout;
void *dma_buf;
struct geni_se *se = &gi2c->se;
size_t len = msg->len;
@@ -470,8 +483,9 @@ static int geni_i2c_rx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg,
gi2c->dma_buf = dma_buf;
}
+ timeout = geni_i2c_xfer_timeout(gi2c, len);
cur = gi2c->cur;
- time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT);
+ time_left = wait_for_completion_timeout(&gi2c->done, timeout);
if (!time_left)
geni_i2c_abort_xfer(gi2c);
@@ -484,7 +498,7 @@ static int geni_i2c_tx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg,
u32 m_param)
{
dma_addr_t tx_dma = 0;
- unsigned long time_left;
+ unsigned long time_left, timeout;
void *dma_buf;
struct geni_se *se = &gi2c->se;
size_t len = msg->len;
@@ -512,8 +526,9 @@ static int geni_i2c_tx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg,
if (!dma_buf) /* Get FIFO IRQ */
writel_relaxed(1, se->base + SE_GENI_TX_WATERMARK_REG);
+ timeout = geni_i2c_xfer_timeout(gi2c, len);
cur = gi2c->cur;
- time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT);
+ time_left = wait_for_completion_timeout(&gi2c->done, timeout);
if (!time_left)
geni_i2c_abort_xfer(gi2c);
@@ -591,7 +606,7 @@ static void geni_i2c_gpi_multi_desc_unmap(struct geni_i2c_dev *gi2c, struct i2c_
* geni_i2c_gpi_multi_xfer_timeout_handler() - Handles multi message transfer timeout
* @dev: Pointer to the corresponding dev node
* @multi_xfer: Pointer to the geni_i2c_gpi_multi_desc_xfer
- * @transfer_timeout_msecs: Timeout value in milliseconds
+ * @transfer_timeout_msecs: Per-message completion timeout in jiffies
* @transfer_comp: Completion object of the transfer
*
* This function waits for the completion of each processed transfer messages
@@ -601,7 +616,7 @@ static void geni_i2c_gpi_multi_desc_unmap(struct geni_i2c_dev *gi2c, struct i2c_
*/
static int geni_i2c_gpi_multi_xfer_timeout_handler(struct device *dev,
struct geni_i2c_gpi_multi_desc_xfer *multi_xfer,
- u32 transfer_timeout_msecs,
+ unsigned long timeout_jiffies,
struct completion *transfer_comp)
{
int i;
@@ -612,7 +627,7 @@ static int geni_i2c_gpi_multi_xfer_timeout_handler(struct device *dev,
if (multi_xfer->msg_idx_cnt != multi_xfer->irq_cnt) {
time_left = wait_for_completion_timeout(transfer_comp,
- transfer_timeout_msecs);
+ timeout_jiffies);
if (!time_left) {
dev_err(dev, "%s: Transfer timeout\n", __func__);
return -ETIMEDOUT;
@@ -736,8 +751,16 @@ static int geni_i2c_gpi(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[],
dma_async_issue_pending(gi2c->tx_c);
if ((msg_idx == (gi2c->num_msgs - 1)) || flags & DMA_PREP_INTERRUPT) {
+ unsigned long timeout;
+ size_t max_len = 0;
+ int j;
+
+ for (j = 0; j < gi2c->num_msgs; j++)
+ max_len = max_t(size_t, max_len, msgs[j].len);
+
+ timeout = geni_i2c_xfer_timeout(gi2c, max_len);
ret = geni_i2c_gpi_multi_xfer_timeout_handler(gi2c->se.dev, gi2c_gpi_xfer,
- XFER_TIMEOUT, &gi2c->done);
+ timeout, &gi2c->done);
if (ret) {
dev_err(gi2c->se.dev,
"I2C multi write msg transfer timeout: %d\n",
@@ -851,8 +874,10 @@ static int geni_i2c_gpi_xfer(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[], i
}
if (!gi2c->is_tx_multi_desc_xfer) {
+ unsigned long timeout = geni_i2c_xfer_timeout(gi2c, msgs[i].len);
+
dma_async_issue_pending(gi2c->tx_c);
- time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT);
+ time_left = wait_for_completion_timeout(&gi2c->done, timeout);
if (!time_left) {
dev_err(gi2c->se.dev, "%s:I2C timeout\n", __func__);
gi2c->err = -ETIMEDOUT;
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v2] i2c: qcom-geni: Add dynamic transfer timeout based on transfer length and frequency
2026-07-09 5:59 Aniket Randive
@ 2026-07-09 6:12 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-07-09 6:12 UTC (permalink / raw)
To: Aniket Randive; +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The calculation of `bit_usec` can overflow `size_t` on 32-bit architectures for large I2C transfers, leading to drastically reduced timeouts.
- [High] GPI multi-descriptor timeout relies on the maximum single message length rather than the total transfer length, causing premature timeouts for batched transfers.
--
--- Patch [1]: [PATCH v2] i2c: qcom-geni: Add dynamic transfer timeout based on transfer length and frequency ---
commit 347c92514f9af6dc3f13bb2e23106b1dc0aa2e87
Author: Aniket Randive <aniket.randive@oss.qualcomm.com>
i2c: qcom-geni: Add dynamic transfer timeout based on transfer length and frequency
This commit replaces the static 1-second timeout for I2C transfers with a
dynamic calculation based on message length and bus frequency. It uses a 10x
safety margin with a 300ms floor, and applies the maximum message length for
batched multi-descriptor transfers.
> diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c
> index 96dbf04138bee..d43db77b36781 100644
> --- a/drivers/i2c/busses/i2c-qcom-geni.c
> +++ b/drivers/i2c/busses/i2c-qcom-geni.c
> @@ -204,6 +207,16 @@ static int geni_i2c_clk_map_idx(struct geni_i2c_dev *gi2c)
> return -EINVAL;
> }
>
> +static unsigned long geni_i2c_xfer_timeout(struct geni_i2c_dev *gi2c, size_t len)
> +{
> + size_t bit_cnt = len * 9;
> + size_t bit_usec = (bit_cnt * USEC_PER_SEC) / gi2c->clk_freq_out;
[Severity: High]
Can this calculation overflow size_t on 32-bit architectures?
Since size_t is 32 bits on 32-bit platforms, the intermediate result of
bit_cnt * USEC_PER_SEC can exceed the 32-bit maximum before the division
takes place.
If a message is larger than 477 bytes, bit_cnt becomes greater than 4293.
Multiplying that by 1,000,000 overflows the 32-bit value, which truncates
the result.
This could lead to drastically reduced timeouts and premature -ETIMEDOUT
failures for large I2C transfers.
> + size_t xfer_max_usec = (bit_usec * I2C_TIMEOUT_SAFETY_COEFFICIENT) +
> + I2C_TIMEOUT_MIN_USEC;
> +
> + return usecs_to_jiffies(xfer_max_usec);
> +}
> +
[ ... ]
> @@ -736,8 +751,16 @@ static int geni_i2c_gpi(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[],
> dma_async_issue_pending(gi2c->tx_c);
>
> if ((msg_idx == (gi2c->num_msgs - 1)) || flags & DMA_PREP_INTERRUPT) {
> + unsigned long timeout;
> + size_t max_len = 0;
> + int j;
> +
> + for (j = 0; j < gi2c->num_msgs; j++)
> + max_len = max_t(size_t, max_len, msgs[j].len);
> +
> + timeout = geni_i2c_xfer_timeout(gi2c, max_len);
> ret = geni_i2c_gpi_multi_xfer_timeout_handler(gi2c->se.dev, gi2c_gpi_xfer,
> - XFER_TIMEOUT, &gi2c->done);
> + timeout, &gi2c->done);
[Severity: High]
Does this timeout calculation correctly account for the time required to
transmit all messages in a batched multi-descriptor transfer?
The loop calculates max_len based on the longest single message in the
chain. However, the hardware processes the entire batch of messages
sequentially.
Because DMA_PREP_INTERRUPT is typically only set on the final message
in geni_i2c_gpi(), the completion handler blocks in
wait_for_completion_timeout() while waiting for the entire batch to finish
transmitting.
If a batch contains many messages, the cumulative transmission time might
exceed the 10x safety margin of the single largest message, resulting in a
spurious transfer timeout.
> if (ret) {
> dev_err(gi2c->se.dev,
> "I2C multi write msg transfer timeout: %d\n",
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260709055958.4089039-1-aniket.randive@oss.qualcomm.com?part=1
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2] i2c: qcom-geni: Add dynamic transfer timeout based on transfer length and frequency
2026-07-06 11:41 ` Konrad Dybcio
@ 2026-07-09 6:28 ` Aniket RANDIVE
2026-07-10 9:27 ` Konrad Dybcio
0 siblings, 1 reply; 10+ messages in thread
From: Aniket RANDIVE @ 2026-07-09 6:28 UTC (permalink / raw)
To: Konrad Dybcio, mukesh.savaliya, viken.dadhaniya, andi.shyti,
sumit.semwal, christian.koenig
Cc: linux-i2c, linux-arm-msm, linux-kernel, linux-media, dri-devel,
linaro-mm-sig, naresh.maramaina
Thanks Konrad for the review.
Sorry, I missed your comments on the v2 patch and ended up posting v3.
I'm happy to post a v4 incorporating your feedback once the discussion
concludes.
On 7/6/2026 5:11 PM, Konrad Dybcio wrote:
> On 7/5/26 3:57 PM, Aniket Randive wrote:
>> The driver uses a static XFER_TIMEOUT of HZ (1 second) for all transfers
>> regardless of message length or bus frequency, causing unnecessary
>> delays on error paths.
>>
>> Compute the timeout dynamically from message length and bus frequency
>> with a 10x safety margin over the theoretical wire time and a 300ms
>> floor. For GPI multi-descriptor transfers, use the maximum message
>> length across all queued messages as the per-completion timeout.
>
> What's the reason for a 0.3 s floor?
The floor accounts for I2C clock stretching. The spec allows slaves to
hold SCL low indefinitely during internal processing. A dynamically
computed xfer time alone gives no time for that.
300ms value covers worst-case stretching while still detecting real
hangs 3x faster than the old 1s static timeout.
Thanks,
Aniket
>
> Why a 10x safety margin specifically?
>
> [...]
The multiplier covers the gap between theoretical xfer time and actual
completion time (DMA descriptor setup, interrupt latency, and scheduling
jitter on a loaded system)
Without it, short transfers would have almost no extra time before a
spurious timeout.
Thanks,
Aniket
>
>> +static unsigned long geni_i2c_xfer_timeout(struct geni_i2c_dev *gi2c, size_t len)
>> +{
>> + size_t bit_cnt = len * 9;
>> + size_t bit_usec = (bit_cnt * USEC_PER_SEC) / gi2c->clk_freq_out;
>
> mult_frac()
>
> Konrad
Good catch. I'll switch the calculation to mult_frac() as suggested in
the next v4 patch.
Thanks,
Aniket
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2] i2c: qcom-geni: Add dynamic transfer timeout based on transfer length and frequency
2026-07-09 6:28 ` Aniket RANDIVE
@ 2026-07-10 9:27 ` Konrad Dybcio
2026-07-10 11:11 ` Aniket RANDIVE
0 siblings, 1 reply; 10+ messages in thread
From: Konrad Dybcio @ 2026-07-10 9:27 UTC (permalink / raw)
To: Aniket RANDIVE, mukesh.savaliya, viken.dadhaniya, andi.shyti,
sumit.semwal, christian.koenig
Cc: linux-i2c, linux-arm-msm, linux-kernel, linux-media, dri-devel,
linaro-mm-sig, naresh.maramaina
On 7/9/26 8:28 AM, Aniket RANDIVE wrote:
> Thanks Konrad for the review.
>
> Sorry, I missed your comments on the v2 patch and ended up posting v3. I'm happy to post a v4 incorporating your feedback once the discussion concludes.
>
> On 7/6/2026 5:11 PM, Konrad Dybcio wrote:
>> On 7/5/26 3:57 PM, Aniket Randive wrote:
>>> The driver uses a static XFER_TIMEOUT of HZ (1 second) for all transfers
>>> regardless of message length or bus frequency, causing unnecessary
>>> delays on error paths.
>>>
>>> Compute the timeout dynamically from message length and bus frequency
>>> with a 10x safety margin over the theoretical wire time and a 300ms
>>> floor. For GPI multi-descriptor transfers, use the maximum message
>>> length across all queued messages as the per-completion timeout.
>>
>> What's the reason for a 0.3 s floor?
>
> The floor accounts for I2C clock stretching. The spec allows slaves to hold SCL low indefinitely during internal processing. A dynamically computed xfer time alone gives no time for that.
> 300ms value covers worst-case stretching while still detecting real hangs 3x faster than the old 1s static timeout.
Please put that in the commit message and possibly in the code as a comment
>
> Thanks,
> Aniket
>
>>
>> Why a 10x safety margin specifically?
>>
>> [...]
>
> The multiplier covers the gap between theoretical xfer time and actual completion time (DMA descriptor setup, interrupt latency, and scheduling jitter on a loaded system)
> Without it, short transfers would have almost no extra time before a spurious timeout.
Likewise
(Should there be a constant safety margin added to account for all
that? Keep in mind this driver will run on a turbofast Glymur and on
a notsofast Agatti so any numbers that depend on the processor's
speed must be reasonable for both)
Konrad
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2] i2c: qcom-geni: Add dynamic transfer timeout based on transfer length and frequency
2026-07-10 9:27 ` Konrad Dybcio
@ 2026-07-10 11:11 ` Aniket RANDIVE
0 siblings, 0 replies; 10+ messages in thread
From: Aniket RANDIVE @ 2026-07-10 11:11 UTC (permalink / raw)
To: Konrad Dybcio, mukesh.savaliya, viken.dadhaniya, andi.shyti,
sumit.semwal, christian.koenig
Cc: linux-i2c, linux-arm-msm, linux-kernel, linux-media, dri-devel,
linaro-mm-sig, naresh.maramaina
On 7/10/2026 2:57 PM, Konrad Dybcio wrote:
> On 7/9/26 8:28 AM, Aniket RANDIVE wrote:
>> Thanks Konrad for the review.
>>
>> Sorry, I missed your comments on the v2 patch and ended up posting v3. I'm happy to post a v4 incorporating your feedback once the discussion concludes.
>>
>> On 7/6/2026 5:11 PM, Konrad Dybcio wrote:
>>> On 7/5/26 3:57 PM, Aniket Randive wrote:
>>>> The driver uses a static XFER_TIMEOUT of HZ (1 second) for all transfers
>>>> regardless of message length or bus frequency, causing unnecessary
>>>> delays on error paths.
>>>>
>>>> Compute the timeout dynamically from message length and bus frequency
>>>> with a 10x safety margin over the theoretical wire time and a 300ms
>>>> floor. For GPI multi-descriptor transfers, use the maximum message
>>>> length across all queued messages as the per-completion timeout.
>>>
>>> What's the reason for a 0.3 s floor?
>>
>> The floor accounts for I2C clock stretching. The spec allows slaves to hold SCL low indefinitely during internal processing. A dynamically computed xfer time alone gives no time for that.
>> 300ms value covers worst-case stretching while still detecting real hangs 3x faster than the old 1s static timeout.
>
> Please put that in the commit message and possibly in the code as a comment
Sure. I will update the commit message accordingly and add a
corresponding comment in the driver as well.
Thanks,
Aniket
>
>>
>> Thanks,
>> Aniket
>>
>>>
>>> Why a 10x safety margin specifically?
>>>
>>> [...]
>>
>> The multiplier covers the gap between theoretical xfer time and actual completion time (DMA descriptor setup, interrupt latency, and scheduling jitter on a loaded system)
>> Without it, short transfers would have almost no extra time before a spurious timeout.
>
> Likewise
>
> (Should there be a constant safety margin added to account for all
> that? Keep in mind this driver will run on a turbofast Glymur and on
> a notsofast Agatti so any numbers that depend on the processor's
> speed must be reasonable for both)
>
> Konrad
The 10x multiplier was chosen as a conservative guard band to account
for the gap between theoretical transfer time and actual completion
time, including DMA setup, interrupt handling, and scheduling delays.
Although the value is not derived from a formal worst-case latency
analysis, it provides sufficient tolerance to accommodate runtime
variability and helps avoid spurious timeouts across a wide range of
platforms and system load conditions.
Thanks,
Aniket
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-07-10 11:11 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-05 13:57 [PATCH v2] i2c: qcom-geni: Add dynamic transfer timeout based on transfer length and frequency Aniket Randive
2026-07-05 14:09 ` sashiko-bot
2026-07-06 11:41 ` Konrad Dybcio
2026-07-09 6:28 ` Aniket RANDIVE
2026-07-10 9:27 ` Konrad Dybcio
2026-07-10 11:11 ` Aniket RANDIVE
2026-07-07 12:07 ` Mukesh Savaliya
2026-07-09 5:49 ` Aniket RANDIVE
-- strict thread matches above, loose matches on Subject: below --
2026-07-09 5:59 Aniket Randive
2026-07-09 6:12 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox