Woodrow Barlow Woodrow Barlow - 4 months ago 5
Linux Question

What does ENXIO mean for an i2c ioctl?

I have an i2c adapter exposed to userspace as

/dev/i2c-0
. When I use
i2cdetect
from the i2c-tools package on that adapter, I see my device listed on this adapter and I am able to perform get, set, and dump operations on it from the command line.

I have some C code that tries to perform a write on it using the ioctl
I2C_RDWR
operation.

Here's the stripped-down code that I am working with (provided only for context, and running it isn't important to answering the questions).

fd = open ("/dev/i2c-0", O_RDWR | O_NONBLOCK);

/* other stuff happens here */

if (ioctl (fd, I2C_FUNCS, &funcs) != 0)
return -1;
if (!(funcs & I2C_FUNC_I2C))
return -1;

/* i added this bit of debug just to be sure fd didn't
* get inadvertently closed along the way.
*/
if (fcntl (fd, F_GETFD) != 0) {
return -1;
}

/* build the ioctl message payload */

ret = ioctl (fd, I2C_RDWR, &payload)
if (ret) {
fprintf (stderr, "ioctl returned %d. Reason: %s (errno=%d).",
ret, strerror(errno), errno);
return -1;
}

return 0;


I've also tried using the smbus ioctl functions, which looked like this.

fd = open ("/dev/i2c-0", O_RDWR | O_NONBLOCK);

/* other stuff happens here */

if (ioctl (fd, I2C_FUNCS, &funcs) != 0)
return -1;
if (!(funcs & I2C_FUNC_SMBUS_WORD_DATA))
return -1;

/* i added this bit of debug just to be sure fd didn't
* get inadvertently closed along the way.
*/
if (fcntl (fd, F_GETFD) != 0) {
return -1;
}

/* build the ioctl smbus message payload */

if (ioctl (fd, I2C_SLAVE_FORCE, dev) != 0)
return -1;

ret = ioctl (fd, I2C_SMBUS, &payload);
if (ret) {
fprintf (stderr, "ioctl returned %d. Reason: %s (errno=%d).",
ret, strerror(errno), errno);
return -1;
}

return 0;


In all cases, the program always hits the
fprintf
and this is the output:


ioctl returned -1. Reason: No such device or address (errno=6).


According to
man 3 ioctl
, the "generic" reason for an ENXIO error is:


ENXIO The request and arg arguments are valid for this device driver, but the service requested cannot be performed on this particular sub-device.


As Ian pointed out in the comments, however, the information regarding STREAMS in the above man page isn't relevant on Linux and I should be using
man 2 ioctl
-- which, unfortunately, mostly just says that ioctls don't really conform to any standard, and anything goes.

This raises three questions.


  1. What does this imply for an i2c adapter? Does the driver for this device not support reading and writing using this method?

  2. The man page contains specific descriptions of return codes for the I_ prefixed operations, but not for other operations. Is there somewhere else I can look for information specific to I2C ioctl operations?

  3. Where can I find the source for the i2c ioctl functions?


Answer

Usually, it means the device did not respond. The specifics depend on your hardware.

The common I2C ioctl code is part of the i2c-dev package, which is part of the lm-sensors project. These ioctls do not have their own man page, but do have documentation in the kernel source tree.

In the kernel, the implementation is all kept in drivers/i2c/i2c-dev.c and drivers/i2c/i2c-core.c. The entry point is i2cdev_ioctl(). None of this common code returns ENXIO.

Eventually, this common code needs to call down to hardware-level driver functions on the i2c bus. The hardware drivers for i2c busses are stored in drivers/i2c/busses/. Each of these drivers can implement one or both of the master_xfer (used for I2C_RDWR ioctls) or smbus_xfer (used for I2C_SMBUS) functions.

In my case, the device actually didn't have dedicated i2c hardware and was using what appears to be a bit-banging software emulation implemented in function bit_xfer of drivers/i2c/algos/i2c-algo-bit.c. The ENXIO can from function bit_doAddress.