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
This commit is contained in:
YAO, Xin
2026-04-13 15:58:48 +03:30
committed by Pouria Mousavizadeh Tehrani
parent e9fc0c5382
commit 26740e8f80
4 changed files with 182 additions and 4 deletions
+115
View File
@@ -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)
{
+24
View File
@@ -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
*/
+35 -4
View File
@@ -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;
+8
View File
@@ -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
{