4
votes

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?
1
The reference to STREAMS devices is a bit of an irrelevance for Linux as it does not support STREAMS. You should really be looking at man 2 ioctl.Ian Abbott
@IanAbbott hmmm, that one doesn't even mention ENXIO. :(Woodrow Barlow
See the "CONFORMING TO" section. :)Ian Abbott
For i2c, I think ENXIO means it got an unexpected NAK.Ian Abbott
Have you read the kernel documentation about this Dev Interface? kernel.org/doc/Documentation/i2c/dev-interface imho, the I2C_RDRW ioctl interface is quite complex, would be interesting how your payload is built. Maybe there's the error. The doc also says that I2C_SMBUS ioctls are "Not meant to be called directly; instead, use the access functions below." So I'd try using them. This Interface looks much easier anyways.yussuf

1 Answers

0
votes

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 came from function bit_doAddress.