[PATCH v7 1/2] i2c: core: Add i2c_update_timeout() helper for dynamic transfer timeouts
Aniket Randive <[email protected]> Thu, 13 Aug 2026 11:15:02 +0530
| Newsgroups | org.kernel.vger.linux-i2c,org.kernel.vger.linux-arm-msm,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
The transfer timeout for an I2C controller should reflect the actual message length and bus frequency rather than a static 1-second value. A static timeout causes unnecessary delays on error paths for short messages, and may be insufficient for very long transfers. Add i2c_update_timeout() to i2c-core which computes a transfer-specific timeout and stores it directly in the standard adap->timeout field. The formula accounts for 9 bits per byte (8 data + 1 ACK) at the configured bus frequency. The caller supplies a safety multiplier and a minimum floor so that each driver retains full control over its timing policy without those values becoming public API. Storing the result in adap->timeout makes it visible to all consumers of that field, including the arbitration-loss retry loop in __i2c_transfer(). The function is gated by CONFIG_I2C_DYNAMIC_TIMEOUT. When the config is disabled, i2c_update_timeout() compiles to a no-op inline stub so drivers that call it build cleanly and the existing static 1-second default is preserved unchanged. A timeout explicitly configured by userspace via the I2C_TIMEOUT ioctl is stored in a new adap->user_timeout field and always takes precedence over the kernel-computed value. When userspace has not configured a timeout, the computed value is used. Signed-off-by: Aniket Randive <[email protected]> --- drivers/i2c/Kconfig | 13 +++++++++++++ drivers/i2c/i2c-core-base.c | 40 ++++++++++++++++++++++++++++++++++++++++ drivers/i2c/i2c-dev.c | 4 ++++ include/linux/i2c.h | 12 ++++++++++++ 4 files changed, 69 insertions(+) diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index c232054fddd6..1d9691ec0e7e 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -136,6 +136,19 @@ config I2C_SLAVE_TESTUNIT endif +config I2C_DYNAMIC_TIMEOUT + bool "Dynamic per-transfer timeout based on message length" + depends on I2C + help + When enabled, the I2C core computes a per-transfer timeout from the + message length and bus frequency instead of using a static 1-second + default. A timeout explicitly configured via the I2C_TIMEOUT userspace + interface always takes precedence over the computed value. + + When disabled, the existing static 1-second timeout is preserved. + + If unsure, say N. + config I2C_DEBUG_CORE bool "I2C Core debugging messages" help diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c index fb25704219c7..caa0748253ab 100644 --- a/drivers/i2c/i2c-core-base.c +++ b/drivers/i2c/i2c-core-base.c @@ -29,6 +29,7 @@ #include <linux/irq.h> #include <linux/jump_label.h> #include <linux/kernel.h> +#include <linux/math64.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/of_device.h> @@ -2001,6 +2002,45 @@ void i2c_parse_fw_timings(struct device *dev, struct i2c_timings *t, bool use_de } EXPORT_SYMBOL_GPL(i2c_parse_fw_timings); +#ifdef CONFIG_I2C_DYNAMIC_TIMEOUT +/** + * i2c_update_timeout - compute and set a dynamic transfer timeout on an adapter + * @adap: the i2c_adapter whose timeout field will be updated + * @bus_freq_hz: I2C bus clock frequency in Hz + * @len: transfer length in bytes + * @safety_coeff: multiplier applied over the theoretical wire time + * @min_usec: minimum timeout floor in microseconds + * + * Computes a transfer-specific timeout from the message length and bus + * frequency, applies a safety multiplier and a minimum floor, then stores + * the result in adap->timeout (in jiffies). The caller supplies the policy + * constants so they remain internal to the driver. + * + * A timeout explicitly configured by userspace via the I2C_TIMEOUT ioctl + * always takes precedence; the computed value is used only when userspace + * has not configured one. + */ +void i2c_update_timeout(struct i2c_adapter *adap, u32 bus_freq_hz, + size_t len, unsigned int safety_coeff, + unsigned int min_usec) +{ + u64 bit_usec = mul_u64_u32_div(len * 9, USEC_PER_SEC, bus_freq_hz); + u64 total_usec = bit_usec * safety_coeff + min_usec; + unsigned long jiffies_val; + + /* Userspace-configured timeout always takes precedence. */ + if (adap->user_timeout > 0) { + adap->timeout = adap->user_timeout; + return; + } + + jiffies_val = usecs_to_jiffies((unsigned int)min_t(u64, total_usec, UINT_MAX)); + /* adap->timeout is int; guard against signed overflow. */ + adap->timeout = (int)min_t(unsigned long, jiffies_val, INT_MAX); +} +EXPORT_SYMBOL_GPL(i2c_update_timeout); +#endif /* CONFIG_I2C_DYNAMIC_TIMEOUT */ + /* ------------------------------------------------------------------------- */ int i2c_for_each_dev(void *data, int (*fn)(struct device *dev, void *data)) diff --git a/drivers/i2c/i2c-dev.c b/drivers/i2c/i2c-dev.c index ccaac5e29f90..2d918bcbf8f7 100644 --- a/drivers/i2c/i2c-dev.c +++ b/drivers/i2c/i2c-dev.c @@ -494,7 +494,11 @@ static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) if (arg > INT_MAX / 10) return -EINVAL; +#ifdef CONFIG_I2C_DYNAMIC_TIMEOUT + client->adapter->user_timeout = msecs_to_jiffies(arg * 10); +#else client->adapter->timeout = msecs_to_jiffies(arg * 10); +#endif break; default: /* NOTE: returning a fault code here could cause trouble diff --git a/include/linux/i2c.h b/include/linux/i2c.h index 14ab4d3055af..068217549266 100644 --- a/include/linux/i2c.h +++ b/include/linux/i2c.h @@ -742,6 +742,9 @@ struct i2c_adapter { struct rt_mutex mux_lock; int timeout; /* in jiffies */ +#ifdef CONFIG_I2C_DYNAMIC_TIMEOUT + int user_timeout; /* I2C_TIMEOUT ioctl value in jiffies; 0 = not set */ +#endif int retries; struct device dev; /* the adapter device */ unsigned long locked_flags; /* owned by the I2C core */ @@ -912,6 +915,15 @@ void i2c_put_adapter(struct i2c_adapter *adap); unsigned int i2c_adapter_depth(struct i2c_adapter *adapter); void i2c_parse_fw_timings(struct device *dev, struct i2c_timings *t, bool use_defaults); +#ifdef CONFIG_I2C_DYNAMIC_TIMEOUT +void i2c_update_timeout(struct i2c_adapter *adap, u32 bus_freq_hz, + size_t len, unsigned int safety_coeff, + unsigned int min_usec); +#else +static inline void i2c_update_timeout(struct i2c_adapter *adap, u32 bus_freq_hz, + size_t len, unsigned int safety_coeff, + unsigned int min_usec) {} +#endif /* Return the functionality mask */ static inline u32 i2c_get_functionality(struct i2c_adapter *adap) -- 2.34.1