1
votes

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.

1
The obvious reason of the segfault is - free(data) at the end of main().SD.
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
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 eventuser3629249
the use of goto() is a sure sign that the code logic needs re-evaluated.user3629249
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

1 Answers

1
votes

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).