git: 26740e8f80da - main - compat/linux: Add Linux i2c-dev ioctl compatibility support
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
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
{