Commit 84a107e6 authored by Matt Johnston's avatar Matt Johnston Committed by David S. Miller
Browse files

i2c: dev: Handle 255 byte blocks for i2c ioctl



I2C_SMBUS is limited to 32 bytes due to compatibility with the
32 byte i2c_smbus_data.block

I2C_RDWR allows larger transfers if sufficient sized buffers are passed.

Signed-off-by: default avatarMatt Johnston <matt@codeconstruct.com.au>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 13cae4a1
Loading
Loading
Loading
Loading
+79 −14
Original line number Diff line number Diff line
@@ -46,6 +46,24 @@ struct i2c_dev {
	struct cdev cdev;
};

/* The userspace union i2c_smbus_data for I2C_SMBUS ioctl is limited
 * to 32 bytes (I2C_SMBUS_BLOCK_MAX) for compatibility.
 */
union compat_i2c_smbus_data {
	__u8 byte;
	__u16 word;
	__u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */
			       /* and one more for user-space compatibility */
};

/* Must match i2c-dev.h definition with compat .data member */
struct i2c_smbus_ioctl_data {
	__u8 read_write;
	__u8 command;
	__u32 size;
	union compat_i2c_smbus_data __user *data;
};

#define I2C_MINORS	(MINORMASK + 1)
static LIST_HEAD(i2c_dev_list);
static DEFINE_SPINLOCK(i2c_dev_list_lock);
@@ -235,14 +253,17 @@ static int i2cdev_check_addr(struct i2c_adapter *adapter, unsigned int addr)
static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,
		unsigned nmsgs, struct i2c_msg *msgs)
{
	u8 __user **data_ptrs;
	u8 __user **data_ptrs = NULL;
	u16 *orig_lens = NULL;
	int i, res;

	res = -ENOMEM;
	data_ptrs = kmalloc_array(nmsgs, sizeof(u8 __user *), GFP_KERNEL);
	if (data_ptrs == NULL) {
		kfree(msgs);
		return -ENOMEM;
	}
	if (data_ptrs == NULL)
		goto out;
	orig_lens = kmalloc_array(nmsgs, sizeof(u16), GFP_KERNEL);
	if (orig_lens == NULL)
		goto out;

	res = 0;
	for (i = 0; i < nmsgs; i++) {
@@ -253,12 +274,30 @@ static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,
		}

		data_ptrs[i] = (u8 __user *)msgs[i].buf;
		msgs[i].buf = memdup_user(data_ptrs[i], msgs[i].len);
		msgs[i].buf = NULL;
		if (msgs[i].len < 1) {
			/* Sanity check */
			res = -EINVAL;
			break;

		}
		/* Allocate a larger buffer to accommodate possible 255 byte
		 * blocks. Read results will be dropped later
		 * if they are too large for the original length.
		 */
		orig_lens[i] = msgs[i].len;
		msgs[i].buf = kmalloc(msgs[i].len + I2C_SMBUS_V3_BLOCK_MAX,
			GFP_USER | __GFP_NOWARN);
		if (IS_ERR(msgs[i].buf)) {
			res = PTR_ERR(msgs[i].buf);
			break;
		}
		/* memdup_user allocates with GFP_KERNEL, so DMA is ok */
		if (copy_from_user(msgs[i].buf, data_ptrs[i], msgs[i].len)) {
			kfree(msgs[i].buf);
			res = -EFAULT;
			break;
		}
		/* Buffer from kmalloc, so DMA is ok */
		msgs[i].flags |= I2C_M_DMA_SAFE;

		/*
@@ -274,7 +313,7 @@ static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,
		 */
		if (msgs[i].flags & I2C_M_RECV_LEN) {
			if (!(msgs[i].flags & I2C_M_RD) ||
			    msgs[i].len < 1 || msgs[i].buf[0] < 1 ||
			    msgs[i].buf[0] < 1 ||
			    msgs[i].len < msgs[i].buf[0] +
					     I2C_SMBUS_BLOCK_MAX) {
				i++;
@@ -297,12 +336,16 @@ static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,
	res = i2c_transfer(client->adapter, msgs, nmsgs);
	while (i-- > 0) {
		if (res >= 0 && (msgs[i].flags & I2C_M_RD)) {
			if (copy_to_user(data_ptrs[i], msgs[i].buf,
			if (orig_lens[i] < msgs[i].len)
				res = -EINVAL;
			else if (copy_to_user(data_ptrs[i], msgs[i].buf,
						 msgs[i].len))
				res = -EFAULT;
		}
		kfree(msgs[i].buf);
	}
out:
	kfree(orig_lens);
	kfree(data_ptrs);
	kfree(msgs);
	return res;
@@ -310,7 +353,7 @@ static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,

static noinline int i2cdev_ioctl_smbus(struct i2c_client *client,
		u8 read_write, u8 command, u32 size,
		union i2c_smbus_data __user *data)
		union compat_i2c_smbus_data __user *data)
{
	union i2c_smbus_data temp = {};
	int datasize, res;
@@ -371,6 +414,16 @@ static noinline int i2cdev_ioctl_smbus(struct i2c_client *client,
		if (copy_from_user(&temp, data, datasize))
			return -EFAULT;
	}
	if ((size == I2C_SMBUS_BLOCK_PROC_CALL ||
	    size == I2C_SMBUS_I2C_BLOCK_DATA ||
	    size == I2C_SMBUS_BLOCK_DATA) &&
	    read_write == I2C_SMBUS_WRITE &&
	    temp.block[0] > I2C_SMBUS_BLOCK_MAX) {
		/* Don't accept writes larger than the buffer size */
		dev_dbg(&client->adapter->dev, "block write is too large");
		return -EINVAL;

	}
	if (size == I2C_SMBUS_I2C_BLOCK_BROKEN) {
		/* Convert old I2C block commands to the new
		   convention. This preserves binary compatibility. */
@@ -380,9 +433,21 @@ static noinline int i2cdev_ioctl_smbus(struct i2c_client *client,
	}
	res = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
	      read_write, command, size, &temp);
	if (!res && ((size == I2C_SMBUS_PROC_CALL) ||
	if (res)
		return res;
	if ((size == I2C_SMBUS_BLOCK_PROC_CALL ||
	    size == I2C_SMBUS_I2C_BLOCK_DATA ||
	    size == I2C_SMBUS_BLOCK_DATA) &&
	    read_write == I2C_SMBUS_READ &&
	    temp.block[0] > I2C_SMBUS_BLOCK_MAX) {
		/* Don't accept reads larger than the buffer size */
		dev_dbg(&client->adapter->dev, "block read is too large");
		return -EINVAL;

	}
	if ((size == I2C_SMBUS_PROC_CALL) ||
	    (size == I2C_SMBUS_BLOCK_PROC_CALL) ||
		     (read_write == I2C_SMBUS_READ))) {
	    (read_write == I2C_SMBUS_READ)) {
		if (copy_to_user(data, &temp, datasize))
			return -EFAULT;
	}
+2 −0
Original line number Diff line number Diff line
@@ -39,12 +39,14 @@


/* This is the structure as used in the I2C_SMBUS ioctl call */
#ifndef __KERNEL__
struct i2c_smbus_ioctl_data {
	__u8 read_write;
	__u8 command;
	__u32 size;
	union i2c_smbus_data __user *data;
};
#endif

/* This is the structure as used in the I2C_RDWR ioctl call */
struct i2c_rdwr_ioctl_data {
+2 −0
Original line number Diff line number Diff line
@@ -109,6 +109,8 @@ struct i2c_msg {
#define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK	0x08000000 /* w/ 1-byte reg. addr. */
#define I2C_FUNC_SMBUS_HOST_NOTIFY	0x10000000 /* SMBus 2.0 or later */
#define I2C_FUNC_SMBUS_V3_BLOCK		0x20000000 /* Device supports 255 byte block */
						   /* Note that I2C_SMBUS ioctl only */
						   /* supports a 32 byte block */

#define I2C_FUNC_SMBUS_BYTE		(I2C_FUNC_SMBUS_READ_BYTE | \
					 I2C_FUNC_SMBUS_WRITE_BYTE)