git: 26740e8f80da - main - compat/linux: Add Linux i2c-dev ioctl compatibility support

From: Pouria Mousavizadeh Tehrani <pouria_at_FreeBSD.org>
Date: Mon, 13 Apr 2026 12:33:45 UTC
The branch main has been updated by pouria:

URL: https://cgit.FreeBSD.org/src/commit/?id=26740e8f80da17c78bee6fa322e6bb1f2669be5c

commit 26740e8f80da17c78bee6fa322e6bb1f2669be5c
Author:     YAO, Xin <mr.yaoxin@outlook.com>
AuthorDate: 2026-04-13 12:28:48 +0000
Commit:     Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
CommitDate: 2026-04-13 12:31:47 +0000

    compat/linux: Add Linux i2c-dev ioctl compatibility support
    
    Implement Linux I2C ioctl translation in the Linux compatibility layer
    and wire iicbus cdevs up for in-kernel rdwr handling.
    Support common i2c-dev requests including SLAVE, FUNCS, and RDWR,
    while rejecting unsupported 10-bit and SMBus operations.
    
    Signed-off-by:  YAO, Xin <mr.yaoxin@outlook.com>
    Reviewed by:    imp, adrian, pouria
    Differential Revision: https://reviews.freebsd.org/D56251
---
 sys/compat/linux/linux_ioctl.c | 115 +++++++++++++++++++++++++++++++++++++++++
 sys/compat/linux/linux_ioctl.h |  24 +++++++++
 sys/dev/iicbus/iic.c           |  39 ++++++++++++--
 sys/dev/iicbus/iic.h           |   8 +++
 4 files changed, 182 insertions(+), 4 deletions(-)

diff --git a/sys/compat/linux/linux_ioctl.c b/sys/compat/linux/linux_ioctl.c
index d2fa0331026b..b68e950a2dcf 100644
--- a/sys/compat/linux/linux_ioctl.c
+++ b/sys/compat/linux/linux_ioctl.c
@@ -43,6 +43,7 @@
 #include <sys/malloc.h>
 #include <sys/mman.h>
 #include <sys/proc.h>
+#include <sys/vnode.h>
 #include <sys/sbuf.h>
 #include <sys/sockio.h>
 #include <sys/soundcard.h>
@@ -59,6 +60,7 @@
 
 #include <dev/evdev/input.h>
 #include <dev/hid/hidraw.h>
+#include <dev/iicbus/iic.h>
 #include <dev/usb/usb_ioctl.h>
 
 #ifdef COMPAT_LINUX32
@@ -100,6 +102,7 @@ DEFINE_LINUX_IOCTL_SET(vfat, VFAT);
 DEFINE_LINUX_IOCTL_SET(console, CONSOLE);
 DEFINE_LINUX_IOCTL_SET(hdio, HDIO);
 DEFINE_LINUX_IOCTL_SET(disk, DISK);
+DEFINE_LINUX_IOCTL_SET(i2c, I2C);
 DEFINE_LINUX_IOCTL_SET(socket, SOCKET);
 DEFINE_LINUX_IOCTL_SET(sound, SOUND);
 DEFINE_LINUX_IOCTL_SET(termio, TERMIO);
@@ -160,6 +163,18 @@ struct linux_hd_big_geometry {
 	uint32_t	start;
 };
 
+struct linux_i2c_msg {
+	uint16_t	addr;
+	uint16_t	flags;
+	uint16_t	len;
+	l_uintptr_t	buf;
+};
+
+struct linux_i2c_rdwr_data {
+	l_uintptr_t	msgs;
+	l_uint		nmsgs;
+};
+
 static int
 linux_ioctl_hdio(struct thread *td, struct linux_ioctl_args *args)
 {
@@ -3639,6 +3654,106 @@ linux_ioctl_nvme(struct thread *td, struct linux_ioctl_args *args)
 }
 #endif
 
+static int
+linux_ioctl_i2c(struct thread *td, struct linux_ioctl_args *args)
+{
+	struct linux_i2c_rdwr_data lrdwr;
+	struct linux_i2c_msg *lmsgs = NULL;
+	struct iic_rdwr_data rdwr;
+	struct iic_msg *msgs = NULL;
+	struct file *fp;
+	iic_linux_rdwr_t *linux_rdwr;
+	l_ulong funcs;
+	uint16_t lflags;
+	uint8_t addr;
+	int error;
+	l_uint i;
+
+	error = fget(td, args->fd, &cap_ioctl_rights, &fp);
+	if (error != 0)
+		return (error);
+
+	linux_rdwr = NULL;
+	if (fp->f_type == DTYPE_VNODE && fp->f_vnode != NULL &&
+	    fp->f_vnode->v_rdev != NULL)
+		linux_rdwr = (iic_linux_rdwr_t *)fp->f_vnode->v_rdev->si_drv2;
+
+	switch (args->cmd & 0xffff) {
+	case LINUX_I2C_RETRIES:
+	case LINUX_I2C_TIMEOUT:
+	case LINUX_I2C_PEC:
+		error = 0;
+		break;
+	case LINUX_I2C_TENBIT:
+		error = (args->arg == 0) ? 0 : ENOTSUP;
+		break;
+	case LINUX_I2C_FUNCS:
+		funcs = LINUX_I2C_FUNC_I2C | LINUX_I2C_FUNC_NOSTART;
+		error = copyout(&funcs, (void *)args->arg, sizeof(funcs));
+		break;
+	case LINUX_I2C_SLAVE:
+	case LINUX_I2C_SLAVE_FORCE:
+		if (args->arg > 0x7f) {
+			error = EINVAL;
+			break;
+		}
+		addr = (uint8_t)(args->arg << 1);
+		error = fo_ioctl(fp, I2CSADDR, (caddr_t)&addr, td->td_ucred, td);
+		break;
+	case LINUX_I2C_RDWR:
+		error = copyin((void *)args->arg, &lrdwr, sizeof(lrdwr));
+		if (error != 0)
+			break;
+		if (lrdwr.nmsgs > IIC_RDRW_MAX_MSGS) {
+			error = EINVAL;
+			break;
+		}
+		lmsgs = malloc(sizeof(*lmsgs) * lrdwr.nmsgs, M_TEMP, M_WAITOK);
+		msgs = malloc(sizeof(*msgs) * lrdwr.nmsgs, M_TEMP, M_WAITOK);
+		error = copyin((void *)(uintptr_t)lrdwr.msgs, lmsgs,
+		    sizeof(*lmsgs) * lrdwr.nmsgs);
+		if (error != 0)
+			break;
+		for (i = 0; i < lrdwr.nmsgs; i++) {
+			lflags = lmsgs[i].flags;
+			if (lmsgs[i].addr > 0x7f || (lflags & LINUX_I2C_M_TEN) != 0) {
+				error = ENOTSUP;
+				break;
+			}
+			if ((lflags & ~(LINUX_I2C_M_RD | LINUX_I2C_M_NOSTART)) != 0) {
+				error = ENOTSUP;
+				break;
+			}
+			msgs[i].slave = lmsgs[i].addr << 1;
+			msgs[i].flags = (lflags & LINUX_I2C_M_RD) ? IIC_M_RD : IIC_M_WR;
+			if ((lflags & LINUX_I2C_M_NOSTART) != 0)
+				msgs[i].flags |= IIC_M_NOSTART;
+			msgs[i].len = lmsgs[i].len;
+			msgs[i].buf = (uint8_t *)(uintptr_t)lmsgs[i].buf;
+		}
+		if (error == 0) {
+			if (linux_rdwr == NULL) {
+				error = ENOTTY;
+			} else {
+				rdwr.msgs = msgs;
+				rdwr.nmsgs = lrdwr.nmsgs;
+				error = linux_rdwr(fp, &rdwr, fp->f_flag, td);
+				if (error == 0)
+					td->td_retval[0] = lrdwr.nmsgs;
+			}
+		}
+		break;
+	case LINUX_I2C_SMBUS:
+	default:
+		error = ENOTSUP;
+		break;
+	}
+	free(msgs, M_TEMP);
+	free(lmsgs, M_TEMP);
+	fdrop(fp, td);
+	return (error);
+}
+
 static int
 linux_ioctl_hidraw(struct thread *td, struct linux_ioctl_args *args)
 {
diff --git a/sys/compat/linux/linux_ioctl.h b/sys/compat/linux/linux_ioctl.h
index 116a4e676228..769f56b7de51 100644
--- a/sys/compat/linux/linux_ioctl.h
+++ b/sys/compat/linux/linux_ioctl.h
@@ -71,6 +71,30 @@
 #define LINUX_IOCTL_HDIO_MIN	LINUX_HDIO_GET_GEO
 #define LINUX_IOCTL_HDIO_MAX	LINUX_HDIO_GET_GEO_BIG
 
+/*
+ * i2c
+ */
+#define	LINUX_I2C_RETRIES	0x0701
+#define	LINUX_I2C_TIMEOUT	0x0702
+#define	LINUX_I2C_SLAVE		0x0703
+#define	LINUX_I2C_TENBIT	0x0704
+#define	LINUX_I2C_FUNCS		0x0705
+#define	LINUX_I2C_SLAVE_FORCE	0x0706
+#define	LINUX_I2C_RDWR		0x0707
+#define	LINUX_I2C_PEC		0x0708
+#define	LINUX_I2C_SMBUS		0x0720
+
+#define	LINUX_IOCTL_I2C_MIN	LINUX_I2C_RETRIES
+#define	LINUX_IOCTL_I2C_MAX	LINUX_I2C_SMBUS
+
+#define	LINUX_I2C_M_RD		0x0001
+#define	LINUX_I2C_M_TEN		0x0010
+#define	LINUX_I2C_M_NOSTART	0x4000
+
+#define	LINUX_I2C_FUNC_I2C		0x00000001UL
+#define	LINUX_I2C_FUNC_10BIT_ADDR	0x00000002UL
+#define	LINUX_I2C_FUNC_NOSTART		0x00000010UL
+
 /*
  * cdrom
  */
diff --git a/sys/dev/iicbus/iic.c b/sys/dev/iicbus/iic.c
index efb8569a23c0..c3fcb2dbdaed 100644
--- a/sys/dev/iicbus/iic.c
+++ b/sys/dev/iicbus/iic.c
@@ -31,11 +31,13 @@
 #include <sys/abi_compat.h>
 #include <sys/bus.h>
 #include <sys/conf.h>
+#include <sys/file.h>
 #include <sys/fcntl.h>
 #include <sys/lock.h>
 #include <sys/kernel.h>
 #include <sys/malloc.h>
 #include <sys/module.h>
+#include <sys/proc.h>
 #include <sys/sx.h>
 #include <sys/systm.h>
 #include <sys/uio.h>
@@ -96,7 +98,10 @@ static void iic_identify(driver_t *driver, device_t parent);
 static void iicdtor(void *data);
 static int iicuio_move(struct iic_cdevpriv *priv, struct uio *uio, int last);
 static int iicuio(struct cdev *dev, struct uio *uio, int ioflag);
-static int iicrdwr(struct iic_cdevpriv *priv, struct iic_rdwr_data *d, int flags, bool compat32);
+static int iicrdwr(struct iic_cdevpriv *priv, struct iic_rdwr_data *d,
+    int flags, bool compat32, bool kernel_msgs);
+static int iic_linux_rdwr(struct file *fp, struct iic_rdwr_data *d,
+    int flags, struct thread *td);
 
 static device_method_t iic_methods[] = {
 	/* device interface */
@@ -163,6 +168,7 @@ iic_attach(device_t dev)
 		return (ENXIO);
 	}
 	sc->sc_devnode->si_drv1 = sc;
+	sc->sc_devnode->si_drv2 = (void *)iic_linux_rdwr;
 
 	return (0);
 }
@@ -341,7 +347,7 @@ iic_copyinmsgs32(struct iic_rdwr_data *d, struct iic_msg *buf)
 
 static int
 iicrdwr(struct iic_cdevpriv *priv, struct iic_rdwr_data *d, int flags,
-    bool compat32 __unused)
+    bool compat32 __unused, bool kernel_msgs)
 {
 #ifdef COMPAT_FREEBSD32
 	struct iic_rdwr_data dswab;
@@ -375,7 +381,11 @@ iicrdwr(struct iic_cdevpriv *priv, struct iic_rdwr_data *d, int flags,
 		error = iic_copyinmsgs32(d, buf);
 	else
 #endif
-	error = copyin(d->msgs, buf, sizeof(*d->msgs) * d->nmsgs);
+	if (kernel_msgs)
+		memcpy(buf, d->msgs, sizeof(*d->msgs) * d->nmsgs);
+	else
+		error = copyin(d->msgs, buf,
+		    sizeof(*d->msgs) * d->nmsgs);
 	if (error != 0) {
 		free(buf, M_IIC);
 		return (error);
@@ -424,6 +434,27 @@ iicrdwr(struct iic_cdevpriv *priv, struct iic_rdwr_data *d, int flags,
 	return (error);
 }
 
+static int
+iic_linux_rdwr(struct file *fp, struct iic_rdwr_data *d, int flags,
+    struct thread *td)
+{
+	struct file *saved_fp;
+	struct iic_cdevpriv *priv;
+	int error;
+
+	saved_fp = td->td_fpop;
+	td->td_fpop = fp;
+	error = devfs_get_cdevpriv((void **)&priv);
+	td->td_fpop = saved_fp;
+	if (error != 0)
+		return (error);
+
+	IIC_LOCK(priv);
+	error = iicrdwr(priv, d, flags, false, true);
+	IIC_UNLOCK(priv);
+	return (error);
+}
+
 static int
 iicioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *td)
 {
@@ -582,7 +613,7 @@ iicioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *t
 		compat32 = false;
 #endif
 		error = iicrdwr(priv, (struct iic_rdwr_data *)data, flags,
-		    compat32);
+		    compat32, false);
 
 		break;
 
diff --git a/sys/dev/iicbus/iic.h b/sys/dev/iicbus/iic.h
index f6e9360186e0..badfb76e92eb 100644
--- a/sys/dev/iicbus/iic.h
+++ b/sys/dev/iicbus/iic.h
@@ -31,6 +31,14 @@
 
 #include <sys/ioccom.h>
 
+#ifdef _KERNEL
+struct file;
+struct iic_rdwr_data;
+struct thread;
+typedef int iic_linux_rdwr_t(struct file *fp, struct iic_rdwr_data *d,
+    int flags, struct thread *td);
+#endif
+
 /* Designed to be compatible with linux's struct i2c_msg */
 struct iic_msg
 {