Date: Mon, 13 Apr 2026 12:33:45 +0000 From: Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Cc: YAO, Xin <mr.yaoxin@outlook.com> Subject: git: 26740e8f80da - main - compat/linux: Add Linux i2c-dev ioctl compatibility support Message-ID: <69dce2a9.3c629.544d35cd@gitrepo.freebsd.org>
index | next in thread | raw e-mail
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 {home | help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?69dce2a9.3c629.544d35cd>
