1

Hey I am trying to write a user space application to move some data to an I2C for an embedded system running PetaLinux, an operating system for embedded Linux, although I do not think that is what is affecting the issue. I am getting a Connection timeout and a segmentation fault.

The function has macros that direct it to write to the first I2C bus. I specify the data that I want to write in main and pass it to i2c_write, which then passes it to i2c_ioctl_write.

Here is the code:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#include <errno.h>
#include <string.h>

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>

#define I2C_ADAPTER "/dev/i2c-0"
#define I2C_DEVICE  0x00
#define REG_ADDR 0x00


int i2c_ioctl_write (int fd, uint8_t dev, uint8_t regaddr, uint16_t *data)
{
    printf("i2c_ioctl_write\n");
    int i, j = 0;
    int ret;
    uint8_t *buf;

    buf = malloc(1 + 2 * (sizeof(data) / sizeof(data[0])));
    if (buf == NULL) {
        return -ENOMEM;
    }
    printf("\tBuffer Allocation Successful...\n");

    buf[j ++] = regaddr;
    for (i = 0; i < (sizeof(data) / sizeof(data[0])); i ++) {
        buf[j ++] = (data[i] & 0xff00) >> 8;
        buf[j ++] = data[i] & 0xff;
    }
    printf("\tBuffer Setup Successful...\n");

    struct i2c_msg messages[] = {
        {
            .addr = dev,
            .buf = buf,
            .len = sizeof(buf) / sizeof(buf[0]),
        },
    };
    printf("\tSetup I2C Messages...\n");

    struct i2c_rdwr_ioctl_data payload = {
        .msgs = messages,
        .nmsgs = sizeof(messages) / sizeof(messages[0]),
    };
    printf("\tSetup I2C IOCTL Payload...\n");


    ret = ioctl(fd, I2C_RDWR, &payload);
    printf("\tWrote with IOCTL...\n");
    if (ret < 0) {
        ret = -errno;
    }

    free (buf);
    return ret;
}

int i2c_ioctl_smbus_write (int fd, uint8_t dev, uint8_t regaddr, uint16_t *data)
{
    printf("i2c_ioctl_smbus_write\n");
    int i, j = 0;
    int ret;
    uint8_t *buf;

    buf = malloc(2 * (sizeof(data) / sizeof(data[0])));
    if (buf == NULL) {
        return -ENOMEM;
    }

    for (i = 0; i < (sizeof(data) / sizeof(data[0])); i ++) {
        buf[j ++] = (data[i] & 0xff00) >> 8;
        buf[j ++] = data[i] & 0xff;
    }

    struct i2c_smbus_ioctl_data payload = {
        .read_write = I2C_SMBUS_WRITE,
        .size = I2C_SMBUS_WORD_DATA,
        .command = regaddr,
        .data = (void *) buf,
    };

    ret = ioctl (fd, I2C_SLAVE_FORCE, dev);
    if (ret < 0)
    {
        ret = -errno;
        goto exit;
    }

    ret = ioctl (fd, I2C_SMBUS, &payload);
    if (ret < 0)
    {
        ret = -errno;
        goto exit;
    }

exit:
    free(buf);
    return ret;
}

int i2c_write (int fd, uint8_t dev, uint8_t regaddr, uint16_t *data)
{
    printf("i2x_write\n");
    uint64_t funcs;

    if (ioctl(fd, I2C_FUNCS, &funcs) < 0) {
        return -errno;
    }

    if (funcs & I2C_FUNC_I2C) {
        return i2c_ioctl_write (fd, dev, regaddr, data);
    } else if (funcs & I2C_FUNC_SMBUS_WORD_DATA) {
        return i2c_ioctl_smbus_write (fd, dev, regaddr, data);
    } else {
        return -ENOSYS;
    }
}

int main (int argc, char *argv[])
{
    printf("main\n");
    uint8_t regaddr;
    int fd;
    int ret = 0;

    uint16_t data[] = {1, 2, 4};

    fd = open(I2C_ADAPTER, O_RDWR | O_NONBLOCK);
    ret = i2c_write(fd, I2C_DEVICE, REG_ADDR, data);
    close(fd);

    if (ret) {
        fprintf (stderr, "%s.\n", strerror(-ret));
    }

    free(data);

    return ret;
}

When I run the program on QEMU I get the following output:

main i2x_write i2c_ioctl_write Buffer Allocation Successful... Buffer Setup Successful... Setup I2C Messages Setup I2C IOCTL Payload cdns-i2c e0004000.i2c: timeout waiting on completion Wrote with IOCTL Connection timed out. Segmentation fault

I assume it is failing on the line

ret = ioctl(fd, I2C_RDWR, &payload);

but I am not sure why. Was the payload constructed improperly?

Update: Here is the current code:

 #include <stdio.h>
 #include <stdlib.h>
 #include <stdint.h>
 #include <inttypes.h>

 #include <errno.h>
 #include <string.h>

 #include <sys/stat.h>
 #include <fcntl.h>
 #include <unistd.h>

 #include <linux/i2c.h>
 #include <linux/i2c-dev.h>
 #include <sys/ioctl.h>

 #define I2C_ADAPTER "/dev/i2c-0"
 #define I2C_DEVICE  0x00

 int main (int argc, char *argv[])
{
    int fd;
    int ret = 0;


    fd = open(I2C_ADAPTER, O_RDWR | O_NONBLOCK);

    uint64_t funcs;

    int addr = 0X00;

    if (ioctl(fd, I2C_SLAVE, addr) < 0) {
        /* ERROR HANDLING; you can check errno to see what went wrong */
            printf("Cannot setup as slave");
            exit(1);
         }

    if (ioctl(fd, I2C_FUNCS, &funcs) < 0) {
        printf("ioctl failed");
        return -errno;
    }

    printf("funcs & I2C_FUNC_I2C:   %llu\n", funcs & I2C_FUNC_I2C);
    printf("funcs & I2C_FUNC_SMBUS_WORD_DATA:   %llu\n", funcs & I2C_FUNC_SMBUS_WORD_DATA);

    __u8 reg = 0x10;
    __s32 res;

    if (funcs & I2C_FUNC_I2C) {
        char buf[10];
        printf("Attempting to write to I2C bus via I2C protocol...\n");
        buf[0] = reg;
        buf[1] = 0x43;
        buf[2] = 0x65;
        int bytes_written = write(fd, buf, 3);
        if(bytes_written != 3) {
            printf("Wrote %d bytes", bytes_written);
            printf("\tFailed to write to I2C Bus\n");
            close(fd);
            return -1;
        }
        else {
            printf("\tSuccesful write to I2C Bus\n");
        }

        char buf2[10];
        printf("Attempting to read from I2C bus via I2C protocol...\n");
        if(read(fd, buf2, 1) != 1) {
            printf("\tFailed to do I2C read from Bus\n");
            close(fd);
            return -1;
        }
        else {
            printf("\tRead successful. Comparing read results from original write buffer...");
            printf("\t\tWritten value: %c", buf[0]);
            printf("\t\tRead value: %c", buf2[0]);
        }


        return 0;


    } else if (funcs & I2C_FUNC_SMBUS_WORD_DATA) {
        printf("Attempting to write to I2C bus via SMBus protocol...\n");
        //res = i2c_smbus_write_word_data(fd, REG_ADDR, 0x6543);
        res = 1;
        if(res < 0) {
            printf("\tFailed to write to I2C Bus\n");
            close(fd);
            return -1;
        }
        else {
            printf("\tSuccesful write to I2C Bus\n");
        }

        //res = i2c_smbus_read_word_data(fd, REG_ADDR);
        if(res < 0) {
            printf("\tFailed to read from I2C Bus\n");
            close(fd);
            return -1;
        }
        else {
            printf("\tRead successful. Comparing read results from original write buffer...");
            printf("\t\tWritten value: %c", 0x6543);
            printf("\t\tRead value: %c", res);
        }
    } else {
        printf("Cannot write to I2C");
        return -ENOSYS;
    }

    close(fd);

    if (ret) {
        fprintf (stderr, "%s.\n", strerror(-ret));
    }

    return ret;
}

I was able to get rid of the seg fault by removing free(), so thanks there. I have pinpointed the exact issue of the timeout which occurs in the Cadence I2C Driver here:

https://github.com/Xilinx/linux-xlnx/blob/3f3c7b60919d56119a68813998d3005bca501a40/drivers/i2c/busses/i2c-cadence.c#L825

which is still occurring.

As mentioned, there is probably some issue with the way I am writing to slave causing the slave to not send ACK, resulting in a timeout. I am not sure which registers I will need to write what to. I have a feeling the I2C_DEVICE macro and addr and reg variables will need to be changed.

underscore_d
  • 6,309
  • 3
  • 38
  • 64
John Frye
  • 255
  • 6
  • 22
  • The obvious reason of the segfault is - `free(data)` at the end of main(). – SD. Oct 26 '17 at 05:26
  • when compiling, always enable the warnings, then fix those warnings. (for `gcc`, at a minimum use: `-Wall -Wextra -pedantic -Wconvertion -std=gnu11 ) To get you started, when the parameters from `main()` are not used, then use the signature: `int main( void )` – user3629249 Oct 26 '17 at 07:12
  • regarding: `for (i = 0; i < (sizeof(data) / sizeof(data[0])); i ++) {` the variable 'data' is just a pointer, not the actual array so `sizeof(data)` will return the size of a pointer, not the size of the array and `sizeof(data[0]) will return 2, Similarity this statement: `buf = malloc(2 * (sizeof(data) / sizeof(data[0])));` becomes 4/2*2 I.E. 4 so the code will be accessing beyond the end of the allocated memory. This is undefined behavior and (as you have seen) can result in a seg fault event – user3629249 Oct 26 '17 at 07:20
  • the use of `goto()` is a sure sign that the code logic needs re-evaluated. – user3629249 Oct 26 '17 at 07:23
  • `sizeof` returns a type of `size_t` (which is unsigned) however, in this statement: `for (i = 0; i < (sizeof(data) / sizeof(data[0])); i ++)` that `size_t` is being compared to an `int` (variable 'i'), Such comparisons between signed and unsigned variables is fraught with problems Suggest keeping variable scope limited as much as possible to use: `for ( size_t i = 0; i < (sizeof(data) / sizeof(data[0])); i ++)` – user3629249 Oct 26 '17 at 07:34
  • Can you please mention output of the modified code. – Krupal Tharwala Oct 26 '17 at 12:53

1 Answers1

1

cdns-i2c e0004000.i2c: timeout waiting on completion

It seems that i2c driver (cdns-i2s) doesnt recieves the acknowledgment from the slave. It may occur as you are using I2C-slave address as 0x00 which is a general call address. While using general call address the second byte that is sent has a special purpose which is mentioned in the i2c-specification (section 3.1.13).

If you use general call address you need to follow the specification or else Try using the exact i2c slave address instead of general call address(0x00).

Krupal Tharwala
  • 1,036
  • 9
  • 16
  • when you say exact address, you mean look at the Xilinx Zynq reference manual to find the physical address of the i2c controller and write to that? – John Frye Oct 26 '17 at 12:00
  • @JohnFrye No, You must be sending data over I2C to some slave device connected to your Xilinx Znyc I2C controller. So, I am saying you to refer datasheet of the peripheral that is connected to your I2C which is acting as slave. – Krupal Tharwala Oct 26 '17 at 12:46
  • From the datasheet of the slave device, you will get the slave address. just replace I2C_DEVICE with that address – Krupal Tharwala Oct 26 '17 at 12:48
  • I am running on QEMU, so I am not sure how to tell which peripheral is connected to the I2C to look it up on the datasheet. – John Frye Oct 26 '17 at 12:55
  • Check https://stackoverflow.com/questions/18501660/how-to-emulate-an-i2c-device-on-qemu-x86. Check if you can create and use virtual I2C slave on QEMU. – Krupal Tharwala Oct 26 '17 at 13:05
  • It looks like that link you posted might be the underlying problem. QEMU doesn't simulate I2C devices it lists under /dev . I am not sure what that answer means by "a shared memory on guest Linux kernel driver, and QEMU transfers the data directly through the shared memory" – John Frye Oct 26 '17 at 13:25