[PATCH v3] i2c_tools.c: add i2ctransfer utility
Nikolaus Voss
nv at vosn.de
Mon Jan 7 13:29:08 UTC 2019
i2ctransfer sends and receives user defined i2c messages
v2: apply Xabier's comments: add -a option, don't decrement argc,
use bb_show_usage() and xzalloc()
v3: fix possible out of bound access to msgs[nmsgs]
Reviewed-by: Xabier Oneca -- xOneca <xoneca at gmail.com>
Signed-off-by: Nikolaus Voss <nikolaus.voss at loewensteinmedical.de>
---
miscutils/i2c_tools.c | 206 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 205 insertions(+), 1 deletion(-)
diff --git a/miscutils/i2c_tools.c b/miscutils/i2c_tools.c
index 610fed5d6..fd27f5f83 100644
--- a/miscutils/i2c_tools.c
+++ b/miscutils/i2c_tools.c
@@ -36,17 +36,26 @@
//config: help
//config: Detect I2C chips.
//config:
+//config:config I2CTRANSFER
+//config: bool "i2ctransfer (4.0 kb)"
+//config: default y
+//config: select PLATFORM_LINUX
+//config: help
+//config: Send user-defined I2C messages in one transfer.
+//config:
//applet:IF_I2CGET(APPLET(i2cget, BB_DIR_USR_SBIN, BB_SUID_DROP))
//applet:IF_I2CSET(APPLET(i2cset, BB_DIR_USR_SBIN, BB_SUID_DROP))
//applet:IF_I2CDUMP(APPLET(i2cdump, BB_DIR_USR_SBIN, BB_SUID_DROP))
//applet:IF_I2CDETECT(APPLET(i2cdetect, BB_DIR_USR_SBIN, BB_SUID_DROP))
+//applet:IF_I2CTRANSFER(APPLET(i2ctransfer, BB_DIR_USR_SBIN, BB_SUID_DROP))
/* not NOEXEC: if hw operation stalls, use less memory in "hung" process */
//kbuild:lib-$(CONFIG_I2CGET) += i2c_tools.o
//kbuild:lib-$(CONFIG_I2CSET) += i2c_tools.o
//kbuild:lib-$(CONFIG_I2CDUMP) += i2c_tools.o
//kbuild:lib-$(CONFIG_I2CDETECT) += i2c_tools.o
+//kbuild:lib-$(CONFIG_I2CTRANSFER) += i2c_tools.o
/*
* Unsupported stuff:
@@ -80,12 +89,18 @@
#define I2C_FUNCS 0x0705
#define I2C_PEC 0x0708
#define I2C_SMBUS 0x0720
+#define I2C_RDWR 0x0707
+#define I2C_RDWR_IOCTL_MAX_MSGS 42
struct i2c_smbus_ioctl_data {
__u8 read_write;
__u8 command;
__u32 size;
union i2c_smbus_data *data;
};
+struct i2c_rdwr_ioctl_data {
+ struct i2c_msg *msgs; /* pointers to i2c_msgs */
+ __u32 nmsgs; /* number of i2c_msgs */
+};
/* end linux/i2c-dev.h */
/*
@@ -262,7 +277,7 @@ static int i2c_bus_lookup(const char *bus_str)
return xstrtou_range(bus_str, 10, 0, 0xfffff);
}
-#if ENABLE_I2CGET || ENABLE_I2CSET || ENABLE_I2CDUMP
+#if ENABLE_I2CGET || ENABLE_I2CSET || ENABLE_I2CDUMP || ENABLE_I2CTRANSFER
static int i2c_parse_bus_addr(const char *addr_str)
{
/* Slave address must be in range 0x03 - 0x77. */
@@ -1373,3 +1388,192 @@ int i2cdetect_main(int argc UNUSED_PARAM, char **argv)
return 0;
}
#endif /* ENABLE_I2CDETECT */
+
+#if ENABLE_I2CTRANSFER
+static void check_i2c_func(int fd)
+{
+ unsigned long funcs;
+
+ get_funcs_matrix(fd, &funcs);
+
+ if (!(funcs & I2C_FUNC_I2C))
+ bb_error_msg_and_die("warning: adapter does not support I2C transfers");
+}
+
+//usage:#define i2ctransfer_trivial_usage
+//usage: "[-f] [-y] I2CBUS DESC [DATA] [DESC [DATA]]..."
+//usage:#define i2ctransfer_full_usage "\n\n"
+//usage: "Send user-defined I2C messages in one transfer"
+//usage: "\n"
+//usage: "\nI2CBUS I2C bus number"
+//usage: "\nDESC describes the transfer in the form: {r|w}LENGTH[@address]"
+int i2ctransfer_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int i2ctransfer_main(int argc UNUSED_PARAM, char **argv)
+{
+ const unsigned opt_f = (1 << 0), opt_y = (1 << 1), opt_a = (1 << 2);
+
+ int bus_num, bus_addr = - 1;
+ int fd;
+ unsigned opts, first, last;
+ int nmsgs = 0, nmsgs_sent, i, j;
+ struct i2c_msg msgs[I2C_RDWR_IOCTL_MAX_MSGS];
+ unsigned buf_idx = 0;
+ struct i2c_rdwr_ioctl_data rdwr;
+ enum parse_state {
+ PARSE_GET_DESC,
+ PARSE_GET_DATA,
+ } state = PARSE_GET_DESC;
+
+ for (i = 0; i < I2C_RDWR_IOCTL_MAX_MSGS; i++)
+ msgs[i].buf = NULL;
+
+ opts = getopt32(argv, "^"
+ "fya"
+ "\0" "-2" /* minimum 2 args */
+ );
+ argv += optind;
+
+ if (opts & opt_a) {
+ first = 0x00;
+ last = 0x7f;
+ } else {
+ first = 0x03;
+ last = 0x77;
+ }
+
+ bus_num = i2c_bus_lookup(argv[0]);
+ fd = i2c_dev_open(bus_num);
+ check_i2c_func(fd);
+
+ while (*++argv) {
+ char *arg_ptr = *argv;
+ unsigned long len, raw_data;
+ __u16 flags;
+ __u8 data, *buf;
+ char *end;
+
+ if (nmsgs >= I2C_RDWR_IOCTL_MAX_MSGS)
+ bb_error_msg_and_die("Error: Too many messages (max: %d)\n",
+ I2C_RDWR_IOCTL_MAX_MSGS);
+
+ switch (state) {
+ case PARSE_GET_DESC:
+ flags = 0;
+
+ switch (*arg_ptr++) {
+ case 'r': flags |= I2C_M_RD; break;
+ case 'w': break;
+ default:
+ bb_show_usage();
+ }
+
+ len = strtoul(arg_ptr, &end, 0);
+ if (len > 0xffff || arg_ptr == end)
+ bb_error_msg_and_die("Error: Length invalid: %s\n", *argv);
+
+ arg_ptr = end;
+ if (*arg_ptr) {
+ if (*arg_ptr++ != '@')
+ bb_error_msg_and_die("Error: Unknown separator after length: %s\n",
+ *argv);
+ bus_addr = xstrtou_range(arg_ptr, 0, first, last);
+
+ if (!(opts & opt_f))
+ i2c_set_slave_addr(fd, bus_addr, opts & opt_f);
+ } else {
+ /* Reuse last address if possible */
+ if (bus_addr < 0)
+ bb_error_msg_and_die("Error: No address given: %s\n",
+ *argv);
+ }
+
+ msgs[nmsgs].addr = bus_addr;
+ msgs[nmsgs].flags = flags;
+ msgs[nmsgs].len = len;
+
+ if (len) {
+ buf = xzalloc(len);
+ msgs[nmsgs].buf = buf;
+ }
+
+ if (flags & I2C_M_RD || len == 0) {
+ nmsgs++;
+ } else {
+ buf_idx = 0;
+ state = PARSE_GET_DATA;
+ }
+ break;
+
+ case PARSE_GET_DATA:
+ raw_data = strtoul(arg_ptr, &end, 0);
+ if (raw_data > 0xff || arg_ptr == end)
+ bb_error_msg_and_die("Error: Invalid data byte: %s\n", *argv);
+
+ data = raw_data;
+ len = msgs[nmsgs].len;
+
+ while (buf_idx < len) {
+ msgs[nmsgs].buf[buf_idx++] = data;
+
+ if (!*end)
+ break;
+
+ switch (*end) {
+ /* Pseudo randomness (8 bit AXR with a=13 and b=27) */
+ case 'p':
+ data = (data ^ 27) + 13;
+ data = (data << 1) | (data >> 7);
+ break;
+ case '+': data++; break;
+ case '-': data--; break;
+ case '=': break;
+ default:
+ bb_error_msg_and_die("Error: Invalid data byte suffix: %s\n",
+ *argv);
+ }
+ }
+
+ if (buf_idx == len) {
+ nmsgs++;
+ state = PARSE_GET_DESC;
+ }
+
+ break;
+
+ default:
+ /* Should never happen */
+ bb_error_msg_and_die("Internal Error: Unknown state in state machine!\n");
+ }
+ }
+
+ if (state != PARSE_GET_DESC || nmsgs == 0)
+ bb_error_msg_and_die("Error: Incomplete message\n");
+
+ if (!(opts & opt_y))
+ confirm_action(bus_addr, 0, 0, 0);
+
+ rdwr.msgs = msgs;
+ rdwr.nmsgs = nmsgs;
+ nmsgs_sent = ioctl(fd, I2C_RDWR, &rdwr);
+ if (nmsgs_sent < 0)
+ bb_error_msg("Error: Sending messages failed: %s\n", strerror(errno));
+ else if (nmsgs_sent < nmsgs)
+ bb_error_msg("Warning: only %d/%d messages were sent\n", nmsgs_sent, nmsgs);
+
+ for (i = 0; i < nmsgs_sent; i++) {
+ if (msgs[i].len && msgs[i].flags & I2C_M_RD) {
+ for (j = 0; j < msgs[i].len - 1; j++)
+ printf("0x%02x ", msgs[i].buf[j]);
+ /* Print final byte with newline */
+ printf("0x%02x\n", msgs[i].buf[j]);
+ }
+ }
+
+ close(fd);
+
+ for (i = 0; i < nmsgs; i++)
+ free(msgs[i].buf);
+
+ return 0;
+}
+#endif /* ENABLE_I2CTRANSFER */
--
2.17.1
More information about the busybox
mailing list