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:
committed by
Pouria Mousavizadeh Tehrani
parent
e9fc0c5382
commit
26740e8f80
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user