From 3587384f4d04f4db41c6967ca9b1576b490745f1 Mon Sep 17 00:00:00 2001 From: Wang Xin Date: Thu, 16 Aug 2018 19:45:34 +0200 Subject: eeprom: at24: fix unexpected timeout under high load commit 9a9e295e7c5c0409c020088b0ae017e6c2b7df6e upstream. Within at24_loop_until_timeout the timestamp used for timeout checking is recorded after the I2C transfer and sleep_range(). Under high CPU load either the execution time for I2C transfer or sleep_range() could actually be larger than the timeout value. Worst case the I2C transfer is only tried once because the loop will exit due to the timeout although the EEPROM is now ready. To fix this issue the timestamp is recorded at the beginning of each iteration. That is, before I2C transfer and sleep. Then the timeout is actually checked against the timestamp of the previous iteration. This makes sure that even if the timeout is reached, there is still one more chance to try the I2C transfer in case the EEPROM is ready. Example: If you have a system which combines high CPU load with repeated EEPROM writes you will run into the following scenario. - System makes a successful regmap_bulk_write() to EEPROM. - System wants to perform another write to EEPROM but EEPROM is still busy with the last write. - Because of high CPU load the usleep_range() will sleep more than 25 ms (at24_write_timeout). - Within the over-long sleeping the EEPROM finished the previous write operation and is ready again. - at24_loop_until_timeout() will detect timeout and won't try to write. Signed-off-by: Wang Xin Signed-off-by: Mark Jonas Signed-off-by: Bartosz Golaszewski Signed-off-by: Greg Kroah-Hartman --- drivers/misc/eeprom/at24.c | 107 ++++++++++++++++++++++++++++++++------------- 1 file changed, 77 insertions(+), 30 deletions(-) (limited to 'drivers/misc/eeprom/at24.c') diff --git a/drivers/misc/eeprom/at24.c b/drivers/misc/eeprom/at24.c index 59dcd97ee3de..6b58ee2e2a25 100644 --- a/drivers/misc/eeprom/at24.c +++ b/drivers/misc/eeprom/at24.c @@ -113,22 +113,6 @@ MODULE_PARM_DESC(write_timeout, "Time (in ms) to try writes (default 25)"); ((1 << AT24_SIZE_FLAGS | (_flags)) \ << AT24_SIZE_BYTELEN | ilog2(_len)) -/* - * Both reads and writes fail if the previous write didn't complete yet. This - * macro loops a few times waiting at least long enough for one entire page - * write to work while making sure that at least one iteration is run before - * checking the break condition. - * - * It takes two parameters: a variable in which the future timeout in jiffies - * will be stored and a temporary variable holding the time of the last - * iteration of processing the request. Both should be unsigned integers - * holding at least 32 bits. - */ -#define loop_until_timeout(tout, op_time) \ - for (tout = jiffies + msecs_to_jiffies(write_timeout), op_time = 0; \ - op_time ? time_before(op_time, tout) : true; \ - usleep_range(1000, 1500), op_time = jiffies) - static const struct i2c_device_id at24_ids[] = { /* needs 8 addresses as A0-A2 are ignored */ { "24c00", AT24_DEVICE_MAGIC(128 / 8, AT24_FLAG_TAKE8ADDR) }, @@ -234,7 +218,14 @@ static ssize_t at24_eeprom_read_smbus(struct at24_data *at24, char *buf, if (count > I2C_SMBUS_BLOCK_MAX) count = I2C_SMBUS_BLOCK_MAX; - loop_until_timeout(timeout, read_time) { + timeout = jiffies + msecs_to_jiffies(write_timeout); + do { + /* + * The timestamp shall be taken before the actual operation + * to avoid a premature timeout in case of high CPU load. + */ + read_time = jiffies; + status = i2c_smbus_read_i2c_block_data_or_emulated(client, offset, count, buf); @@ -244,7 +235,9 @@ static ssize_t at24_eeprom_read_smbus(struct at24_data *at24, char *buf, if (status == count) return count; - } + + usleep_range(1000, 1500); + } while (time_before(read_time, timeout)); return -ETIMEDOUT; } @@ -284,7 +277,14 @@ static ssize_t at24_eeprom_read_i2c(struct at24_data *at24, char *buf, msg[1].buf = buf; msg[1].len = count; - loop_until_timeout(timeout, read_time) { + timeout = jiffies + msecs_to_jiffies(write_timeout); + do { + /* + * The timestamp shall be taken before the actual operation + * to avoid a premature timeout in case of high CPU load. + */ + read_time = jiffies; + status = i2c_transfer(client->adapter, msg, 2); if (status == 2) status = count; @@ -294,7 +294,9 @@ static ssize_t at24_eeprom_read_i2c(struct at24_data *at24, char *buf, if (status == count) return count; - } + + usleep_range(1000, 1500); + } while (time_before(read_time, timeout)); return -ETIMEDOUT; } @@ -343,11 +345,20 @@ static ssize_t at24_eeprom_read_serial(struct at24_data *at24, char *buf, msg[1].buf = buf; msg[1].len = count; - loop_until_timeout(timeout, read_time) { + timeout = jiffies + msecs_to_jiffies(write_timeout); + do { + /* + * The timestamp shall be taken before the actual operation + * to avoid a premature timeout in case of high CPU load. + */ + read_time = jiffies; + status = i2c_transfer(client->adapter, msg, 2); if (status == 2) return count; - } + + usleep_range(1000, 1500); + } while (time_before(read_time, timeout)); return -ETIMEDOUT; } @@ -374,11 +385,20 @@ static ssize_t at24_eeprom_read_mac(struct at24_data *at24, char *buf, msg[1].buf = buf; msg[1].len = count; - loop_until_timeout(timeout, read_time) { + timeout = jiffies + msecs_to_jiffies(write_timeout); + do { + /* + * The timestamp shall be taken before the actual operation + * to avoid a premature timeout in case of high CPU load. + */ + read_time = jiffies; + status = i2c_transfer(client->adapter, msg, 2); if (status == 2) return count; - } + + usleep_range(1000, 1500); + } while (time_before(read_time, timeout)); return -ETIMEDOUT; } @@ -420,7 +440,14 @@ static ssize_t at24_eeprom_write_smbus_block(struct at24_data *at24, client = at24_translate_offset(at24, &offset); count = at24_adjust_write_count(at24, offset, count); - loop_until_timeout(timeout, write_time) { + timeout = jiffies + msecs_to_jiffies(write_timeout); + do { + /* + * The timestamp shall be taken before the actual operation + * to avoid a premature timeout in case of high CPU load. + */ + write_time = jiffies; + status = i2c_smbus_write_i2c_block_data(client, offset, count, buf); if (status == 0) @@ -431,7 +458,9 @@ static ssize_t at24_eeprom_write_smbus_block(struct at24_data *at24, if (status == count) return count; - } + + usleep_range(1000, 1500); + } while (time_before(write_time, timeout)); return -ETIMEDOUT; } @@ -446,7 +475,14 @@ static ssize_t at24_eeprom_write_smbus_byte(struct at24_data *at24, client = at24_translate_offset(at24, &offset); - loop_until_timeout(timeout, write_time) { + timeout = jiffies + msecs_to_jiffies(write_timeout); + do { + /* + * The timestamp shall be taken before the actual operation + * to avoid a premature timeout in case of high CPU load. + */ + write_time = jiffies; + status = i2c_smbus_write_byte_data(client, offset, buf[0]); if (status == 0) status = count; @@ -456,7 +492,9 @@ static ssize_t at24_eeprom_write_smbus_byte(struct at24_data *at24, if (status == count) return count; - } + + usleep_range(1000, 1500); + } while (time_before(write_time, timeout)); return -ETIMEDOUT; } @@ -485,7 +523,14 @@ static ssize_t at24_eeprom_write_i2c(struct at24_data *at24, const char *buf, memcpy(&msg.buf[i], buf, count); msg.len = i + count; - loop_until_timeout(timeout, write_time) { + timeout = jiffies + msecs_to_jiffies(write_timeout); + do { + /* + * The timestamp shall be taken before the actual operation + * to avoid a premature timeout in case of high CPU load. + */ + write_time = jiffies; + status = i2c_transfer(client->adapter, &msg, 1); if (status == 1) status = count; @@ -495,7 +540,9 @@ static ssize_t at24_eeprom_write_i2c(struct at24_data *at24, const char *buf, if (status == count) return count; - } + + usleep_range(1000, 1500); + } while (time_before(write_time, timeout)); return -ETIMEDOUT; } -- cgit v1.2.3 From 321d6579dc7ba1170e1058c78a57461d4a65aacd Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Sun, 28 Jul 2019 18:41:38 +0200 Subject: eeprom: at24: make spd world-readable again commit 25e5ef302c24a6fead369c0cfe88c073d7b97ca8 upstream. The integration of the at24 driver into the nvmem framework broke the world-readability of spd EEPROMs. Fix it. Signed-off-by: Jean Delvare Cc: stable@vger.kernel.org Fixes: 57d155506dd5 ("eeprom: at24: extend driver to plug into the NVMEM framework") Cc: Andrew Lunn Cc: Srinivas Kandagatla Cc: Greg Kroah-Hartman Cc: Bartosz Golaszewski Cc: Arnd Bergmann Signed-off-by: Bartosz Golaszewski [Bartosz: backported the patch to older branches] Signed-off-by: Bartosz Golaszewski Signed-off-by: Greg Kroah-Hartman --- drivers/misc/eeprom/at24.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/misc/eeprom/at24.c') diff --git a/drivers/misc/eeprom/at24.c b/drivers/misc/eeprom/at24.c index 6b58ee2e2a25..f8cfcd063649 100644 --- a/drivers/misc/eeprom/at24.c +++ b/drivers/misc/eeprom/at24.c @@ -834,7 +834,7 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id) at24->nvmem_config.name = dev_name(&client->dev); at24->nvmem_config.dev = &client->dev; at24->nvmem_config.read_only = !writable; - at24->nvmem_config.root_only = true; + at24->nvmem_config.root_only = !(chip.flags & AT24_FLAG_IRUGO); at24->nvmem_config.owner = THIS_MODULE; at24->nvmem_config.compat = true; at24->nvmem_config.base_dev = &client->dev; -- cgit v1.2.3