[PATCH RFC v3 09/14] i2c: core: implement I2C_RDWR_V2 ioctl - transfer with detailed report
From: Dmitry Guzman
Date: Thu Nov 05 2026 - 12:22:50 EST
I2C_RDWR_V2 is a new ioctl for I2C controller devices. It has the same
functionality as I2C_RDWR, with additional feature - in case of a fault
during transfer, it provides information how many messages and bytes were
successfully transferred and which message caused the fault. If there is
any read data, it also may be used.
This feature is available on drivers that implement i2c_xfer_v2() method
in their i2c_algorithm.
Signed-off-by: Dmitry Guzman <Dmitry.Guzman@xxxxxxxxxxxx>
---
drivers/i2c/i2c-dev.c | 123 ++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 104 insertions(+), 19 deletions(-)
diff --git a/drivers/i2c/i2c-dev.c b/drivers/i2c/i2c-dev.c
index d219eb89f978..14fe1dd7eecf 100644
--- a/drivers/i2c/i2c-dev.c
+++ b/drivers/i2c/i2c-dev.c
@@ -240,11 +240,19 @@ static int i2cdev_check_addr(struct i2c_adapter *adapter, unsigned int addr)
return result;
}
-static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,
- unsigned nmsgs, struct i2c_msg *msgs)
+static noinline int i2cdev_ioctl_rdwr_v2(struct i2c_client *client,
+ unsigned int nmsgs, struct i2c_msg *msgs,
+ struct i2c_transfer_report __user *user_report)
{
+ struct i2c_transfer_report report;
u8 __user **data_ptrs;
- int i, res;
+ int i, res, msgs_done;
+
+ i2c_transfer_report_init(&report);
+
+ if (user_report)
+ if (copy_to_user(user_report, &report, sizeof(report)))
+ return -EFAULT;
/* Adapter must support I2C transfers */
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
@@ -259,6 +267,7 @@ static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,
/* Limit the size of the message to a sane amount */
if (msgs[i].len > 8192) {
res = -EINVAL;
+ i2c_transfer_report_bad_msg(&report, i);
break;
}
@@ -266,6 +275,7 @@ static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,
msgs[i].buf = memdup_user(data_ptrs[i], msgs[i].len);
if (IS_ERR(msgs[i].buf)) {
res = PTR_ERR(msgs[i].buf);
+ i2c_transfer_report_bad_msg(&report, i);
break;
}
/* memdup_user allocates with GFP_KERNEL, so DMA is ok */
@@ -287,6 +297,7 @@ static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,
msgs[i].len < 1 || msgs[i].buf[0] < 1 ||
msgs[i].len < msgs[i].buf[0] +
I2C_SMBUS_BLOCK_MAX) {
+ i2c_transfer_report_bad_msg(&report, i);
i++;
res = -EINVAL;
break;
@@ -300,12 +311,44 @@ static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,
for (j = 0; j < i; ++j)
kfree(msgs[j].buf);
kfree(data_ptrs);
+ if (user_report && copy_to_user(user_report, &report, sizeof(report)))
+ res = -EFAULT;
return res;
}
- res = i2c_transfer(client->adapter, msgs, nmsgs);
+ if (user_report) {
+ res = i2c_transfer_v2(client->adapter, msgs, nmsgs, &report);
+ msgs_done = report.msgs_cplt;
+ } else {
+ res = i2c_transfer(client->adapter, msgs, nmsgs);
+ if (res < 0)
+ msgs_done = 0;
+ else
+ msgs_done = nmsgs;
+ }
+
+ if (user_report && copy_to_user(user_report, &report, sizeof(report)))
+ res = -EFAULT;
+
+ if (msgs_done > (int)nmsgs) {
+ //fixme - use dev_err?
+ pr_err("Bad i2c_transfer_report: msgs_cplt = %i, bytes_cplt = %i, nmsgs = %i\n",
+ report.msgs_cplt, report.bytes_cplt, nmsgs);
+ msgs_done = nmsgs;
+ }
+
+ if (report.bytes_cplt > 0) {
+ if (msgs_done < nmsgs && msgs_done >= 0) {
+ msgs[msgs_done].len = report.bytes_cplt;
+ msgs_done++;
+ } else {
+ pr_err("Bad i2c_transfer_report: msgs_cplt = %i, bytes_cplt = %i, nmsgs = %i\n",
+ report.msgs_cplt, report.bytes_cplt, nmsgs);
+ }
+ }
+
while (i-- > 0) {
- if (res >= 0 && (msgs[i].flags & I2C_M_RD)) {
+ if (msgs[i].flags & I2C_M_RD && i < msgs_done) {
if (copy_to_user(data_ptrs[i], msgs[i].buf,
msgs[i].len))
res = -EFAULT;
@@ -439,18 +482,39 @@ static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
funcs = i2c_get_functionality(client->adapter);
return put_user(funcs, (unsigned long __user *)arg);
- case I2C_RDWR: {
+ case I2C_RDWR:
+ case I2C_RDWR_V2:
+ {
+ struct i2c_rdwr_ioctl_data __user *user_arg;
+ struct i2c_transfer_report __user *user_rep;
struct i2c_rdwr_ioctl_data rdwr_arg;
struct i2c_msg *rdwr_pa;
int res;
- if (copy_from_user(&rdwr_arg,
- (struct i2c_rdwr_ioctl_data __user *)arg,
- sizeof(rdwr_arg)))
+ if (cmd == I2C_RDWR_V2) {
+ user_arg = &((struct i2c_rdwr_v2_ioctl_data __user *)arg)->rdwr_data;
+ user_rep = &((struct i2c_rdwr_v2_ioctl_data __user *)arg)->report;
+ } else {
+ user_arg = (struct i2c_rdwr_ioctl_data __user *)arg;
+ user_rep = NULL;
+ }
+
+ if (copy_from_user(&rdwr_arg, user_arg, sizeof(rdwr_arg)))
return -EFAULT;
- if (!rdwr_arg.msgs || rdwr_arg.nmsgs == 0)
- return -EINVAL;
+ if (!rdwr_arg.msgs || rdwr_arg.nmsgs == 0) {
+ /*
+ * I2C_RDWR_V2 ioctl with nmsgs == 0 is used for
+ * discovering of the controller capability to return
+ * detailed fault reports.
+ */
+ if (cmd == I2C_RDWR)
+ return -EINVAL;
+ if (client->adapter->algo->xfer_v2)
+ return 0;
+ else
+ return -EOPNOTSUPP;
+ }
/*
* Put an arbitrary limit on the number of messages that can
@@ -464,7 +528,7 @@ static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
if (IS_ERR(rdwr_pa))
return PTR_ERR(rdwr_pa);
- res = i2cdev_ioctl_rdwr(client, rdwr_arg.nmsgs, rdwr_pa);
+ res = i2cdev_ioctl_rdwr_v2(client, rdwr_arg.nmsgs, rdwr_pa, user_rep);
kfree(rdwr_pa);
return res;
}
@@ -531,6 +595,11 @@ struct i2c_rdwr_ioctl_data32 {
u32 nmsgs;
};
+struct i2c_rdwr_v2_ioctl_data32 {
+ struct i2c_rdwr_ioctl_data32 rdwr_data;
+ struct i2c_transfer_report report;
+};
+
static long compat_i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct i2c_client *client = file->private_data;
@@ -539,19 +608,35 @@ static long compat_i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned lo
case I2C_FUNCS:
funcs = i2c_get_functionality(client->adapter);
return put_user(funcs, (compat_ulong_t __user *)arg);
- case I2C_RDWR: {
+ case I2C_RDWR:
+ case I2C_RDWR_V2:
+ {
+ struct i2c_rdwr_ioctl_data32 __user *user_arg;
+ struct i2c_transfer_report __user *user_rep;
struct i2c_rdwr_ioctl_data32 rdwr_arg;
struct i2c_msg32 __user *p;
struct i2c_msg *rdwr_pa;
int i, res;
- if (copy_from_user(&rdwr_arg,
- (struct i2c_rdwr_ioctl_data32 __user *)arg,
- sizeof(rdwr_arg)))
+ if (cmd == I2C_RDWR_V2) {
+ user_arg = &((struct i2c_rdwr_v2_ioctl_data32 __user *)arg)->rdwr_data;
+ user_rep = &((struct i2c_rdwr_v2_ioctl_data32 __user *)arg)->report;
+ } else {
+ user_arg = (struct i2c_rdwr_ioctl_data32 __user *)arg;
+ user_rep = NULL;
+ }
+
+ if (copy_from_user(&rdwr_arg, user_arg, sizeof(rdwr_arg)))
return -EFAULT;
- if (!rdwr_arg.msgs || rdwr_arg.nmsgs == 0)
- return -EINVAL;
+ if (!rdwr_arg.msgs || rdwr_arg.nmsgs == 0) {
+ if (cmd == I2C_RDWR)
+ return -EINVAL;
+ if (client->adapter->algo->xfer_v2)
+ return 0;
+ else
+ return -EOPNOTSUPP;
+ }
if (rdwr_arg.nmsgs > I2C_RDWR_IOCTL_MAX_MSGS)
return -EINVAL;
@@ -575,7 +660,7 @@ static long compat_i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned lo
};
}
- res = i2cdev_ioctl_rdwr(client, rdwr_arg.nmsgs, rdwr_pa);
+ res = i2cdev_ioctl_rdwr_v2(client, rdwr_arg.nmsgs, rdwr_pa, user_rep);
kfree(rdwr_pa);
return res;
}
--
2.43.0