Stub I2C driver and client implementation
Driver
The stub driver creates a /dev/i2cX device, processes client requests returning simulated data when necessary, then logs the requests to standard output.
- init()
- fini()
- send()
- recv()
- set_slave_addr()
- set_bus_speed()
- version_info()
- driver_info()
- bus_reset()
qcc -V gcc_ntoaarch64le -o i2c-stub i2c-stub.c -li2c-master -lsecpolgcc_ntoaarch64le as above.# ./i2c-stub -a 0x25 --b 2100 --u 2
i2c-stub: Returned implementation table.
i2c-stub: VERSION=1.0.0
i2c-stub: Using device slave address 0x25
i2c-stub: Initialized stub device.
i2c-stub: Using bus speed 2000
i2c-stub: DRIVER=(3:2:0)
# Stub I2C manager
i2c-stub [options]
Options:
-a slave addr Slave address(only 7-bit addr supported. Default: 0x55)
Generic options:
--b bus_speed Default bus speed. Must be a multiple of 100; however,
the driver rounds down to the nearest multiple of
1000. (Default: 100000)
--u unit Unit number. Number to append to device name
prefix (/dev/i2c). (Default: 0)
Example:
i2c-stub -a 0x12 --b100000 --u1Once started, the driver runs in the background until you kill it.
i2c-stub.c code example
#include <hw/i2c.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/dispatch.h>
#include <sys/iofunc.h>
#include <sys/neutrino.h>
#include <time.h>
#include <unistd.h>
#define STRING_LEN 200u
/*
* functions are implemented by this stub driver
*/
int i2c_master_getfuncs(i2c_master_funcs_t *, int);
void *stub_init(int, char **);
void stub_fini(void *);
i2c_status_t stub_send(void *, void *, unsigned int, unsigned int);
i2c_status_t stub_recv(void *, void *, unsigned int, unsigned int);
int stub_set_bus_speed(void *, unsigned int, unsigned int *);
int stub_set_slave_addr(void *, unsigned int, i2c_addrfmt_t);
int stub_bus_reset(void *hdl);
int stub_driver_info(void *, i2c_driver_info_t *);
int stub_version_info(i2c_libversion_t *);
/*
* Define stuct (see I2C DDK Developer Guide)
*/
typedef struct stub_dev_s
{
unsigned number;
unsigned slave_addr;
i2c_addrfmt_t slave_add_fmt;
unsigned int bus_speed;
int ready;
char header_output[STRING_LEN];
unsigned header_len;
char output[STRING_LEN];
unsigned output_len;
int index;
} stub_dev_t;
/*
* Driver initialization (see I2C DDK Developer Guide)
*/
void *stub_init(int argc, char *argv[])
{
stub_dev_t *dev;
int c;
unsigned long narg;
// allocate device structure
dev = calloc(1, sizeof(stub_dev_t));
if (NULL == dev)
{
fprintf(stderr, "i2c-stub: Failed to allocate device memory.\n");
return NULL;
}
// assign default device values
dev->number = 0;
dev->slave_addr = 0x55;
dev->slave_add_fmt = I2C_ADDRFMT_7BIT;
dev->bus_speed = 1000000u;
dev->ready = 0;
strlcpy(dev->header_output, "START:", STRING_LEN);
dev->header_len = 6;
strlcpy(dev->output, "abcdefghijklmnopqrstuvwxyz", STRING_LEN);
dev->output_len = 26;
dev->index = 0;
// parse options
while (-1 != (c = getopt(argc, argv, ":a:")))
{
switch (c)
{
case ':':
fprintf(stderr, "Missing slave address.\n");
goto stub_init_error;
case 'a':
narg = strtoul(optarg, &optarg, 0);
if (0 != stub_set_slave_addr(dev, narg, I2C_ADDRFMT_7BIT))
{
fprintf(stderr, "i2c-stub: Could not set the slave address to 0x%lx\n", narg);
goto stub_init_error;
}
break;
}
}
// Thread control setup
if (-1 == ThreadCtl(_NTO_TCTL_IO, NULL))
{
fprintf(stderr, "i2c-stub: Failed to setup thread control.\n");
goto stub_init_error;
}
// Normally we would also map hardware registers and setup interrupt handler here
dev->ready = !0;
printf("i2c-stub: Initialized stub device.\n");
return dev;
stub_init_error:
free(dev);
return NULL;
}
/*
* Set slave address (see I2C DDK Developer Guide)
*/
int stub_set_slave_addr(void *hdl, unsigned int addr, i2c_addrfmt_t fmt)
{
stub_dev_t *dev = hdl;
if ((I2C_ADDRFMT_7BIT != fmt))
{
fprintf(stderr, "i2c-stub: Invalid address format.\n");
goto stub_set_slave_addr_error;
}
if (addr & ((unsigned)-1 ^ 0x7F))
{
fprintf(stderr, "i2c-stub: Invalid address %x.\n", addr);
goto stub_set_slave_addr_error;
}
dev->slave_addr = addr;
printf("i2c-stub: Using device slave address 0x%x\n", dev->slave_addr);
return 0;
stub_set_slave_addr_error:
errno = EINVAL;
return -1;
}
/*
* Send Data (see I2C DDK Developer Guide)
*/
i2c_status_t stub_send(void *hdl, void *buf, unsigned int len, unsigned int stop)
{
(void)hdl;
char *cbuf = buf;
printf("i2c-stub: Sending %u bytes: ", len);
for (unsigned int i = 0; i < len; i++)
{
if (cbuf[i] < 20 || cbuf[i] > 128)
{
printf(".");
}
else
{
printf("%c", cbuf[i]);
}
}
if (stop)
{
printf(" - STOP");
}
printf("\n");
return I2C_STATUS_DONE;
}
/*
* Receive Data (see I2C DDK Developer Guide)
*/
i2c_status_t stub_recv(void *hdl, void *buf, unsigned int len, unsigned int stop)
{
stub_dev_t *dev = hdl;
char *cbuf = buf;
printf("i2c-stub: Receiving %u bytes: ", len);
for (unsigned int i = 0; i < len; i++)
{
if (dev->index < dev->header_len)
{
cbuf[i] = dev->header_output[dev->index++];
}
else
{
cbuf[i] = dev->output[dev->index - dev->header_len];
dev->index = (dev->index + 1 - dev->header_len) % dev->output_len + dev->header_len;
}
if (cbuf[i] < 20 || cbuf[i] > 128)
{
printf(".");
}
else
{
printf("%c", cbuf[i]);
}
}
if (stop)
{
printf(" - STOP");
}
printf("\n");
return I2C_STATUS_DONE;
}
/*
* Clean up driver (see I2C DDK Developer Guide)
*/
void stub_fini(void *hdl)
{
stub_dev_t *dev = hdl;
if (NULL != dev)
{
dev->ready = 0;
}
printf("i2c-stub: Cleaned up stub device.\n");
}
/*
* Integrate the implementations (see I2C DDK Developer Guide)
*/
int i2c_master_getfuncs(i2c_master_funcs_t *funcs, int tabsize)
{
I2C_ADD_FUNC(i2c_master_funcs_t, funcs, init, stub_init, tabsize);
I2C_ADD_FUNC(i2c_master_funcs_t, funcs, fini, stub_fini, tabsize);
I2C_ADD_FUNC(i2c_master_funcs_t, funcs, send, stub_send, tabsize);
I2C_ADD_FUNC(i2c_master_funcs_t, funcs, recv, stub_recv, tabsize);
I2C_ADD_FUNC(i2c_master_funcs_t, funcs, set_slave_addr, stub_set_slave_addr, tabsize);
I2C_ADD_FUNC(i2c_master_funcs_t, funcs, set_bus_speed, stub_set_bus_speed, tabsize);
I2C_ADD_FUNC(i2c_master_funcs_t, funcs, version_info, stub_version_info, tabsize);
I2C_ADD_FUNC(i2c_master_funcs_t, funcs, driver_info, stub_driver_info, tabsize);
I2C_ADD_FUNC(i2c_master_funcs_t, funcs, bus_reset, stub_bus_reset, tabsize);
printf("i2c-stub: Returned implementation table.\n");
return 0;
}
/*
* Other implemented functions (see I2C DDK Samples)
*/
int stub_set_bus_speed(void *hdl, unsigned int speed, unsigned int *ospeed)
{
stub_dev_t *dev = hdl;
if (speed % 100u)
{
fprintf(stderr, "i2c-stub: Speed must be a multiple of 100, not %u.\n", speed);
errno = EINVAL;
return -1;
}
dev->bus_speed = speed - speed % 1000u;
if (NULL != ospeed)
{
*ospeed = dev->bus_speed;
}
printf("i2c-stub: Using bus speed %u\n", dev->bus_speed);
return 0;
}
int stub_version_info(i2c_libversion_t *version)
{
version->major = I2CLIB_VERSION_MAJOR;
version->minor = I2CLIB_VERSION_MINOR;
version->revision = I2CLIB_REVISION;
printf("i2c-stub: VERSION=%u.%u.%u\n", version->major, version->minor, version->revision);
return 0;
}
int stub_driver_info(void *hdl, i2c_driver_info_t *info)
{
(void)hdl;
info->speed_mode = I2C_SPEED_STANDARD | I2C_SPEED_FAST;
info->addr_mode = I2C_ADDRFMT_7BIT;
info->verbosity = 0;
printf("i2c-stub: DRIVER=(%u:%u:%u)\n", info->speed_mode, info->addr_mode, info->verbosity);
return 0;
}
int stub_bus_reset(void *hdl)
{
stub_dev_t *dev = hdl;
dev->index = 0;
printf("i2c-stub: Reset the bus.\n");
return EOK;
}
This stub driver uses:
- stub_dev_t
- Defines the device-specific structure.
- It's members include:
- number - device number append to
/dev/i2c (default =
0, for /dev/i2c0). - slave_addr - address of the slave
device, (default =
0x55). - slave_add_fmt - slave device address format, (default = 7-bit).
- bus_speed - a number representing bus
speed, which does not have any effect for this driver,
(default =
1000000). - ready - flag indicating that device
is initialized and ready (default =
0). - header_ouput - data to return after
initialization or bus reset (default =
"START:"). - header_len - length of the
header_output data (default =
6). - output - data to return on
recv, repeats from beginning when
end is reached (default =
"abcdefghijklmnopqrstuvwxyz"). - output_len - length of the
outputdata (default =26). index- index of the next byte to be returned. Ifindex < header_len,indexrepresents byte offset in header_output, otherwiseindex - header_lenrepresents byte offset in output (default =0).
- number - device number append to
/dev/i2c (default =
- Displays message to standard output.
- stub_init()
-
Allocates memory for the device structure.
-
Sets default device values, as described above.
-
Parses options, currently only -a is supported, to specify the slave device address.
-
Sets up thread control.
-
Flips the ready flag in the devices structure to
1. -
Displays message to standard output.
-
- stub_set_slave_addr()
-
Verifies that address format is 7-bit, as 10-bit addresses aren't supported.
-
Verifies that the address is no longer than 7 bits.
-
Sets the slave_addr member of the device structure.
-
Displays message to standard output.
-
- stub_send()
-
Accepts data to be sent to the device and prints it to standard output, replacing non-printable characters with dots (
.). -
Prints additional STOP message if requested.
-
Displays message to standard output.
-
- stub_recv()
-
Fills the buffer up to requested length with bytes from the header_output and output members of the device structure.
-
header_output is only read once after initialization of the driver, and once after every bus_reset() operation, for example:
-
init()
-
recv(3) →
"STA" -
recv(20) →
"RT:abcdefghijklmnopq" -
recv(20) → "
rstuvwxyzabcdefghijk" -
bus_reset()
-
recv(20) →
"START:abcdefghijklmn" -
bus_reset()
-
recv(20) →
"START:abcdefghijklmn
-
-
Prints the data to standard output replacing the non-printable characters with dot (
.). -
Prints optional stop message if requested.
-
- stub_fini()
-
Flips the ready flag in the device structure to
0. -
Displays message to standard output.
-
- stub_set_bus_speed()
-
Generates an error if given speed is not a multiple of
100. -
Rounds the speed down to a nearest multiple of
1000. -
Displays message to standard output.
-
- stub_version_info()
-
Returns library version
1.0.0. -
Displays message to standard output.
-
- stub_driver_info()
-
Returns:
-
100 kHz and 400 kHz speed modes
-
7-bit slave device address format
-
0verbosity
-
-
Displays message to standard output.
-
- stub_bus_reset()
-
resets the
indexvalue in the device structure to0, which will cause the header_output to be returned again -
displays message to standard output
-
- i2c_master_getfuncs()
-
Uses I2C_ADD_FUNC() macro to specify the implemented function in the callers structure.
-
Client
The client parses command line for the device number, opens the device for reading and writing, performs all specified commands, and closes the device. For simplicity, only four operations are supported by the code below:
- DCMD_I2C_BUS_RESET - bus reset
- DCMD_I2C_DRIVER_INFO - get driver information
- DCMD_I2C_RECV - receive data from driver
- DCMD_I2C_SEND - send data to driver
qcc -V gcc_ntoaarch64le -o i2c-stub-client i2c-stub-client.cgcc_ntoaarch64le as above.# ./i2c-stub-client -n 2 -dr "0x25--20" -s "0x25-+This-is-a-test"
i2c-stub-client: Using bus /dev/i2c2
i2c-stub: DRIVER=(3:2:0)
i2c-stub-client: DRIVER=(3:2:0)
i2c-stub: Using device slave address 0x25
i2c-stub: Receiving 20 bytes: START:abcdefghijklmn - STOP
i2c-stub-client: received data: START:abcdefghijklmn
i2c-stub: Using device slave address 0x25
i2c-stub: Sending 15 bytes: This-is-a-test.
i2c-stub-client: sent 15 bytes of data: This-is-a-test
i2c-stub-client: Closing bus.
# ./i2c-stub-client -n 2 -r 0x25--20
i2c-stub-client: Using bus /dev/i2c2
i2c-stub: Using device slave address 0x25
i2c-stub: Receiving 20 bytes: opqrstuvwxyzabcdefgh - STOP
i2c-stub-client: received data: opqrstuvwxyzabcdefgh
i2c-stub-client: No command specied.
i2c-stub-client: Closing bus.
# I2C stub client
stub-client [options]
Options:
-n unit Unit numnber. Number to append to device name
prefix /dev/i2c. (Default: 0)
-b Reset the device bus.
-d Retrive driver information - speed modes, address format, and
verbosity.
-r recv_string Receive data from driver, using the RECV_STRING specification
as described below.
-s send_string Send data to driver, using the SEND_STRING specification as
described below.
recv_string. Concatenation of addr ( fmt ( stop ( data )))
- addr - slave device address, for example: 0x55 or 85
- fmt - + - 10-bit
- anything else - 7-bit
- stop - + - no stop requested
- anything else - stop requested
- len - number of bytes to receive from the driver
For example: -r "0x25-+20"
Which indicates receive 20 bytes from slave device address
0x25, 7-bit address format, not stop reqiested.
send_string Concatenation of addr ( fmt ( stop ( len )))
- addr - slave device address, for example: 0x55 or 85
- fmt - + - 10-bit
- anything else - 7-bit
- stop - + - no stop requested
- anything else - stop requested
- data - text data to send to the driver
For example: -s "0x25+-Test"
Which indicates send 5 bytes ('T' 'e' 's' 't' '\0') to slave
device address 0x25, 10-bit address format, stop requested
after sending. This request will fail, because stub driver
does not support 10-bit address format.
Example:
stub-client -n 2 -dr "0x25--20" -s "0x25--This-is-a-test"i2c-stub-client.c code example
#include <errno.h>
#include <fcntl.h>
#include <hw/i2c.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define STRING_LEN 200u
int main(int argc, char * argv[]) {
char bus_name[STRING_LEN];
unsigned bus = 0;
int c;
int fd;
int done = 0;
int status = EOK;
i2c_driver_info_t ic2_driver_info;
struct {
i2c_recv_t i2c_recv;
char buf[STRING_LEN+1];
} recv;
struct {
i2c_send_t i2c_send;
char buf[STRING_LEN+1];
} send;
// parser common option only
while (-1 != (c = getopt(argc, argv, ":n:"))) {
switch(c) {
case ':':
fprintf(stderr, "i2c-stub-client: Missing bus number.\n");
return EXIT_FAILURE;
case 'n':
bus = strtoul(optarg, &optarg, 0);
break;
default:
break;
}
}
snprintf(bus_name, STRING_LEN, "/dev/i2c%u", bus);
// open the bus
printf("i2c-stub-client: Using bus %s\n", bus_name);
if(-1 == (fd = open(bus_name, O_RDWR))) {
perror("open");
return EXIT_FAILURE;
}
// reset getopt and run commands in order specified
optind = 1;
while (EOK == status && -1 != (c = getopt(argc, argv, "n:bdr:s:"))) {
switch(c) {
// BUS_RESET
case 'b':
done = !0;
if (EOK != (status = devctl(fd, DCMD_I2C_BUS_RESET, NULL, 0, NULL))) {
fprintf(stderr, "i2c-stub-client: Failed to reset bus.\n");
} else {
printf("i2c-stub-client: Bus reset.\n");
}
break;
// DRIVER_INFO
case 'd':
done = !0;
if (EOK != (status = devctl(fd, DCMD_I2C_DRIVER_INFO, &ic2_driver_info, sizeof(i2c_driver_info_t), NULL))) {
fprintf(stderr, "i2c-stub-client: Failed to get driver info.\n");
} else {
printf("i2c-stub-client: DRIVER=(%u:%u:%u)\n", ic2_driver_info.speed_mode, ic2_driver_info.addr_mode, ic2_driver_info.verbosity);
}
break;
// RECV
case 'r':
recv.i2c_recv.slave.addr = strtoul(optarg, &optarg, 0);
recv.i2c_recv.slave.fmt = I2C_ADDRFMT_7BIT;
recv.i2c_recv.len = STRING_LEN;
recv.i2c_recv.stop = 1;
if (*optarg) {
if ('+' == *optarg++) {
recv.i2c_recv.slave.fmt = I2C_ADDRFMT_10BIT;
}
if (*optarg) {
if ('+' == *optarg++) {
recv.i2c_recv.stop = 0;
}
if (*optarg) {
recv.i2c_recv.len = strtoul(optarg, &optarg, 0);
}
}
}
if (EOK != (status = devctl(fd, DCMD_I2C_RECV, &recv, sizeof(recv), NULL))) {
errno = status;
perror("i2c-stub-client: devctl");
} else {
recv.buf[recv.i2c_recv.len] = '\0';
printf("i2c-stub-client: received data: %s\n", recv.buf);
}
break;
// SEND
case 's':
send.i2c_send.slave.addr = strtoul(optarg, &optarg, 0);
send.i2c_send.slave.fmt = I2C_ADDRFMT_7BIT;
strcpy(send.buf, "This is test send data");
send.i2c_send.len = strlen(send.buf)+1;
send.i2c_send.stop = 1;
if (*optarg) {
if ('+' == *optarg++) {
send.i2c_send.slave.fmt = I2C_ADDRFMT_10BIT;
}
if (*optarg) {
if ('+' == *optarg++) {
send.i2c_send.stop = 0;
}
if (*optarg) {
send.i2c_send.len = strlen(optarg);
if (send.i2c_send.len > STRING_LEN) {
send.i2c_send.len = STRING_LEN;
optarg[STRING_LEN] = '\0';
}
strncpy(send.buf, optarg, send.i2c_send.len);
send.i2c_send.len++;
}
}
}
send.buf[send.i2c_send.len-1] = '\0';
if (EOK != (status = devctl(fd, DCMD_I2C_SEND, &send, sizeof(send), NULL))) {
errno = status;
perror("i2c-stub-client: devctl");
} else {
recv.buf[send.i2c_send.len] = '\0';
printf("i2c-stub-client: sent %u bytes of data: %s\n", send.i2c_send.len, send.buf);
}
break;
// ignore parsed already
case 'n':
break;
default:
fprintf(stderr, "i2c-stub-client: Unknown option %c.\n", c);
return EXIT_FAILURE;
}
}
if (!done) {
fprintf(stderr, "i2c-stub-client: No command specied.\n");
status = EINVAL;
}
printf("i2c-stub-client: Closing bus.\n");
close(fd);
if(EOK != status) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
./i2c-stub -a 0x25 --b 2100 --u 2- Client is invoked with arguments:
- -n 2 - use /dev/i2c2 device.
- -d - get driver information.
- -r "0x25--20" - receive 20 bytes with stop from slave
device with 7-bit address
0x25. - -s "0x25-+This-is-a-test" - send
"This-is-a-test\0"without stop to slave device with 7-but address0x25.
- Client opens the /dev/i2c2 device.
- Driver receives and services client's driver info request.
- Client outputs received driver info.
- Driver receives and services client's receive request, sets slave device address
to
0x25, and receives 20 bytes ('S' 'T' 'A' 'R' 'T' ':' 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n') from the hardware device, followed by a stop. - Client outputs received data.
- Driver receives and services client's send request, sets slave device address to
0x25, and sends 15 bytes ('T' 'h' 'i' 's' '-' 'i' 's' '-' 'a' '-' 't' 'e' 's' 't' \0) to the hardware device. - Client outputs sent data.
- Client closes the device.
- Client is invoked again, this time with arguments:
- -n 2 - use /dev/i2c2 device.
- -r 0x25--20 - receive 20 bytes with stop from slave
device with 7-bit address
0x25.
- Client opens the /dev/i2c2 device.
- Driver receives and services client's receive request, sets slave device address
to
0x25, and receives 20 bytes ('o' 'p' 'q' 'r' 's' 't' 'u' 'v' 'w' 'x' 'y' 'z' 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h') from the hardware device, followed by a stop.-
The output is different now. It continues from the a-z sequence from the previous request, rolling back to beginning of the a-z sequence when necessary.
-
START:Isn't received until bus reset request is made or the driver is restarted.
-
- Client outputs received data.
- Client closes the device.
