[i2c] [PATCH] i2c: Add write support to the EEPROM driver

Martin Hicks mort at bork.org
Thu Aug 9 17:47:40 CEST 2007


This adds write support to the i2c eeprom driver.  I suspect that
this is extremely dangerous due to what is sometimes stored in these
chips.  Added a separate Kconfig option to enable it.

Signed-off-by: Martin Hicks <mort at bork.org>
---
 drivers/i2c/chips/Kconfig  |    8 ++
 drivers/i2c/chips/eeprom.c |  154 +++++++++++++++++++++++++++++++++++++------
 2 files changed, 140 insertions(+), 22 deletions(-)

diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig
index 87ee3ce..5335cfb 100644
--- a/drivers/i2c/chips/Kconfig
+++ b/drivers/i2c/chips/Kconfig
@@ -36,6 +36,14 @@ config SENSORS_EEPROM
 	  This driver can also be built as a module.  If so, the module
 	  will be called eeprom.
 
+config SENSORS_EEPROM_WRITE
+	bool "EEPROM write support"
+	depends on SENSORS_EEPROM && EXPERIMENTAL
+	help
+	  If you say yes here you will be able to write to the EEPROM.
+	  This could be extremely dangerous, depending on what the EEPROM
+	  data is used for.
+
 config SENSORS_PCF8574
 	tristate "Philips PCF8574 and PCF8574A"
 	depends on I2C && EXPERIMENTAL
diff --git a/drivers/i2c/chips/eeprom.c b/drivers/i2c/chips/eeprom.c
index cec3a0c..e4bbb52 100644
--- a/drivers/i2c/chips/eeprom.c
+++ b/drivers/i2c/chips/eeprom.c
@@ -45,6 +45,8 @@ I2C_CLIENT_INSMOD_1(eeprom);
 
 /* Size of EEPROM in bytes */
 #define EEPROM_SIZE		256
+/* Number of bytes that can be written in a block. */
+#define EEPROM_PAGE_SIZE	8
 
 /* possible types of eeprom devices */
 enum eeprom_nature {
@@ -77,38 +79,97 @@ static struct i2c_driver eeprom_driver = {
 	.detach_client	= eeprom_detach_client,
 };
 
-static void eeprom_update_client(struct i2c_client *client, u8 slice)
+static int eeprom_update_client_write(struct i2c_client *client, u8 slice)
 {
 	struct eeprom_data *data = i2c_get_clientdata(client);
-	int i, j;
+	int ret = 0;
+	int i;
 
 	mutex_lock(&data->update_lock);
 
-	if (!(data->valid & (1 << slice)) ||
-	    time_after(jiffies, data->last_updated[slice] + 300 * HZ)) {
-		dev_dbg(&client->dev, "Starting eeprom update, slice %u\n", slice);
-
-		if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
-			for (i = slice << 5; i < (slice + 1) << 5; i += I2C_SMBUS_BLOCK_MAX)
-				if (i2c_smbus_read_i2c_block_data(client, i, data->data + i) != I2C_SMBUS_BLOCK_MAX)
+	dev_dbg(&client->dev, "Starting eeprom write update, slice %u\n", slice);
+
+	if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) {
+		for (i = slice << 5; i < (slice + 1) << 5; i+= EEPROM_PAGE_SIZE) {
+			/* Wait for the chip to finish current writes */
+			int count = 0;
+			do {
+				ret = i2c_smbus_write_byte(client, i);
+				/* Chip is not responding */
+				if (count++ > 10000) {
+					dev_dbg(&client->dev,
+						"eeprom write failed\n");
+					ret = -1;
 					goto exit;
-		} else {
-			if (i2c_smbus_write_byte(client, slice << 5)) {
-				dev_dbg(&client->dev, "eeprom read start has failed!\n");
+				}
+			} while (ret != 0);
+
+			if ((ret = i2c_smbus_write_i2c_block_data(client, i, EEPROM_PAGE_SIZE,
+						data->data + i)))
 				goto exit;
-			}
-			for (i = slice << 5; i < (slice + 1) << 5; i++) {
-				j = i2c_smbus_read_byte(client);
-				if (j < 0)
+		}
+	} else {
+		for (i = slice << 5; i < (slice + 1) << 5; i++) {
+			/* Wait for the chip to finish current writes */
+			int count = 0;
+			do {
+				ret = i2c_smbus_write_byte(client, i);
+				/* Chip is not responding */
+				if (count++ > 10000) {
+					dev_dbg(&client->dev,
+						"eeprom write failed\n");
+					ret = -1;
 					goto exit;
-				data->data[i] = (u8) j;
-			}
+				}
+			} while (ret != 0);
+
+			if ((ret = i2c_smbus_write_byte_data(client, i, data->data[i])))
+				goto exit;
 		}
-		data->last_updated[slice] = jiffies;
-		data->valid |= (1 << slice);
 	}
+	data->last_updated[slice] = jiffies;
+	data->valid |= (1 << slice);
 exit:
 	mutex_unlock(&data->update_lock);
+	return ret;
+}
+
+static int eeprom_update_client_read(struct i2c_client *client, u8 slice)
+{
+	struct eeprom_data *data = i2c_get_clientdata(client);
+	int ret = 0;
+	int i;
+
+	mutex_lock(&data->update_lock);
+
+	if ((data->valid & (1 << slice)) &&
+	    time_before(jiffies, data->last_updated[slice] + 300 * HZ))
+		goto exit;
+
+	dev_dbg(&client->dev, "Starting eeprom read update, slice %u\n", slice);
+
+	if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
+		for (i = slice << 5; i < (slice + 1) << 5; i += I2C_SMBUS_BLOCK_MAX)
+			if (i2c_smbus_read_i2c_block_data(client, i, data->data + i) != I2C_SMBUS_BLOCK_MAX)
+				goto exit;
+	} else {
+		if (i2c_smbus_write_byte(client, slice << 5)) {
+			dev_dbg(&client->dev, "eeprom read start has failed!\n");
+			ret = -1;
+			goto exit;
+		}
+		for (i = slice << 5; i < (slice + 1) << 5; i++) {
+			ret = i2c_smbus_read_byte(client);
+			if (ret < 0)
+				goto exit;
+			data->data[i] = (u8)ret;
+		}
+	}
+	data->last_updated[slice] = jiffies;
+	data->valid |= (1 << slice);
+exit:
+	mutex_unlock(&data->update_lock);
+	return ret;
 }
 
 static ssize_t eeprom_read(struct kobject *kobj, char *buf, loff_t off, size_t count)
@@ -124,7 +185,8 @@ static ssize_t eeprom_read(struct kobject *kobj, char *buf, loff_t off, size_t c
 
 	/* Only refresh slices which contain requested bytes */
 	for (slice = off >> 5; slice <= (off + count - 1) >> 5; slice++)
-		eeprom_update_client(client, slice);
+		if (eeprom_update_client_read(client, slice) < 0)
+			return -EIO;
 
 	/* Hide Vaio security settings to regular users (16 first bytes) */
 	if (data->nature == VAIO && off < 16 && !capable(CAP_SYS_ADMIN)) {
@@ -140,14 +202,62 @@ static ssize_t eeprom_read(struct kobject *kobj, char *buf, loff_t off, size_t c
 	return count;
 }
 
+#ifdef CONFIG_SENSORS_EEPROM_WRITE
+#define EEPROM_ATTR_MODE (S_IRUGO | S_IWUSR)
+static ssize_t eeprom_write(struct kobject *kobj, char *buf, loff_t off, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(container_of(kobj, struct device, kobj));
+	struct eeprom_data *data = i2c_get_clientdata(client);
+	int ret = 0;
+	u8 slice;
+
+	if (off > EEPROM_SIZE)
+		return 0;
+	if (off + count > EEPROM_SIZE)
+		count = EEPROM_SIZE - off;
+
+	/* Don't allow anyone to overwrite the Vaio security settings */
+	if (data->nature == VAIO && off < 16) {
+		size_t buf_off = 16 - off;
+		buf_off = min(buf_off, count);
+		if (count - buf_off > 0)
+			memcpy(&data->data[16], buf + buf_off, count - buf_off);
+	} else {
+		memcpy(&data->data[off], buf, count);
+	}
+
+	/* Only write slices which contain modified bytes */
+	for (slice = off >> 5; slice <= (off + count - 1) >> 5; slice++) {
+		if (eeprom_update_client_write(client, slice) < 0) {
+			/*
+			 * If write fails, then re-read the slice to get rid
+			 * of stale data in data->data
+			 */
+			data->valid &= ~(1 << slice);
+			eeprom_update_client_read(client, slice);
+			ret = -1;
+		}
+	}
+
+	return (ret == 0) ? count : -EIO;
+}
+#else /* !CONFIG_SENSORS_EEPROM_WRITE */
+#define EEPROM_ATTR_MODE S_IRUGO
+static ssize_t eeprom_write(struct kobject *kobj, char *buf, loff_t off, size_t count)
+{
+	return 0;
+}
+#endif
+
 static struct bin_attribute eeprom_attr = {
 	.attr = {
 		.name = "eeprom",
-		.mode = S_IRUGO,
+		.mode = EEPROM_ATTR_MODE,
 		.owner = THIS_MODULE,
 	},
 	.size = EEPROM_SIZE,
 	.read = eeprom_read,
+	.write = eeprom_write,
 };
 
 static int eeprom_attach_adapter(struct i2c_adapter *adapter)
-- 
1.5.1.104.g5bcbc


-- 
Martin Hicks || mort at bork.org || PGP/GnuPG: 0x4C7F2BEE



More information about the i2c mailing list