From 9c10d00bf8cdf7735cdba7c09bc16495ce22095b Mon Sep 17 00:00:00 2001 From: Poul-Henning Kamp Date: Wed, 19 May 2021 18:56:59 +0000 Subject: [PATCH] i2c(8): Add interpreted mode for batch/scripted i2c operations --- usr.sbin/i2c/i2c.8 | 63 +++++++++++-- usr.sbin/i2c/i2c.c | 218 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 274 insertions(+), 7 deletions(-) diff --git a/usr.sbin/i2c/i2c.8 b/usr.sbin/i2c/i2c.8 index 92cc62e983aa..7c995f5743a4 100644 --- a/usr.sbin/i2c/i2c.8 +++ b/usr.sbin/i2c/i2c.8 @@ -43,19 +43,26 @@ .Op Fl b .Op Fl v .Nm -.Cm -s -.Op Fl f Ar device -.Op Fl n Ar skip_addr +.Cm -h +.Nm +.Cm -i .Op Fl v +.Op Ar cmd ... +.Op Ar - .Nm .Cm -r .Op Fl f Ar device .Op Fl v +.Nm +.Cm -s +.Op Fl f Ar device +.Op Fl n Ar skip_addr +.Op Fl v .Sh DESCRIPTION The .Nm -utility can be used to perform raw data transfers (read or write) with devices -on the I2C bus. +utility can be used to perform raw data transfers (read or write) to devices +on an I2C bus. It can also scan the bus for available devices and reset the I2C controller. .Pp The options are as follows: @@ -72,6 +79,10 @@ transfer direction: r - read, w - write. Data to be written is read from stdin as binary bytes. .It Fl f Ar device I2C bus to use (default is /dev/iic0). +.It Fl i +Interpreted mode +.It Fl h +Help .It Fl m Ar tr|ss|rs|no addressing mode, i.e., I2C bus operations performed after the offset for the transfer has been written to the device and before the actual read/write @@ -121,6 +132,37 @@ to the slave. Zero means that the offset is ignored and not passed to the slave at all. The endianess defaults to little-endian. .El +.Sh INTERPRETED MODE +When started with +.Fl i +any remaining arguments are interpreted as commands, and +if the last argument is '-', or there are no arguments, +commands will (also) be read from stdin. +.Pp +Available commands: +.Bl -tag -compact +.It 'r' bus address [0|8|16|16LE|16BE] offset count +Read command, count bytes are read and hexdumped to stdout. +.It 'w' bus address [0|8|16|16LE|16BE] offset hexstring +Write command, hexstring (white-space is allowed) is written to device. +.It 'p' anything +Print command, the entire line is printed to stdout. (This can be used +for synchronization.) +.El +.Pp +All numeric fields accept canonical decimal/octal/hex notation. +.Pp +Without the +.Fl v +option, all errors are fatal with non-zero exit status. +.Pp +With the +.Fl v +option, no errors are fatal, and all commands will return +either "OK\en" or "ERROR\en" on stdout. +In case of error, detailed diagnostics will precede that on stderr. +.Pp +Blank lines and lines starting with '#' are ignored. .Sh EXAMPLES .Bl -bullet .It @@ -148,6 +190,14 @@ i2c -a 0x56 -f /dev/iic1 -d r -c 0x4 -b | i2c -a 0x57 -f /dev/iic0 -d w -c 4 -b Reset the controller: .Pp i2c -f /dev/iic1 -r +.It +Read 8 bytes at address 24 in an EEPROM: +.Pp +i2c -i 'r 0 0x50 16BE 24 8' +.It +Read 2x8 bytes at address 24 and 48 in an EEPROM: +.Pp +echo 'r 0 0x50 16BE 48 8' | i2c -i 'r 0 0x50 16BE 24 8' - .El .Sh WARNING Many systems store critical low-level information in I2C memories, and @@ -171,3 +221,6 @@ utility and this manual page were written by .An Bartlomiej Sieka Aq Mt tur@semihalf.com and .An Michal Hajduk Aq Mt mih@semihalf.com . +.Pp +.An Poul-Henning Kamp Aq Mt phk@FreeBSD.org +added interpreted mode. diff --git a/usr.sbin/i2c/i2c.c b/usr.sbin/i2c/i2c.c index a471b6533170..87bb1f0fe983 100644 --- a/usr.sbin/i2c/i2c.c +++ b/usr.sbin/i2c/i2c.c @@ -65,6 +65,9 @@ struct options { size_t off_len; }; +#define N_FDCACHE 128 +static int fd_cache[N_FDCACHE]; + __dead2 static void usage(const char *msg) { @@ -531,6 +534,210 @@ access_bus(int fd, struct options i2c_opt) return (error); } +static const char *widths[] = { + "0", + "8", + "16LE", + "16BE", + "16", + NULL, +}; + +static int +command_bus(struct options i2c_opt, char *cmd) +{ + int error, fd; + char devbuf[64]; + uint8_t dbuf[BUFSIZ]; + unsigned bus; + const char *width = NULL; + const char *err_msg; + unsigned offset; + unsigned u; + size_t length; + + while (isspace(*cmd)) + cmd++; + + switch(*cmd) { + case 0: + case '#': + return (0); + case 'p': + case 'P': + printf("%s", cmd); + return (0); + case 'r': + case 'R': + i2c_opt.dir = 'r'; + break; + case 'w': + case 'W': + i2c_opt.dir = 'w'; + break; + default: + fprintf(stderr, + "Did not understand command: 0x%02x ", *cmd); + if (isgraph(*cmd)) + fprintf(stderr, "'%c'", *cmd); + fprintf(stderr, "\n"); + return(-1); + } + cmd++; + + bus = strtoul(cmd, &cmd, 0); + if (bus == 0 && errno == EINVAL) { + fprintf(stderr, "Could not translate bus number\n"); + return(-1); + } + + i2c_opt.addr = strtoul(cmd, &cmd, 0); + if (i2c_opt.addr == 0 && errno == EINVAL) { + fprintf(stderr, "Could not translate device\n"); + return(-1); + } + if (i2c_opt.addr < 1 || i2c_opt.addr > 0x7f) { + fprintf(stderr, "Invalid device (0x%x)\n", i2c_opt.addr); + return(-1); + } + i2c_opt.addr <<= 1; + + while(isspace(*cmd)) + cmd++; + + for(u = 0; widths[u]; u++) { + length = strlen(widths[u]); + if (memcmp(cmd, widths[u], length)) + continue; + if (!isspace(cmd[length])) + continue; + width = widths[u]; + cmd += length; + break; + } + if (width == NULL) { + fprintf(stderr, "Invalid width\n"); + return(-1); + } + + offset = strtoul(cmd, &cmd, 0); + if (offset == 0 && errno == EINVAL) { + fprintf(stderr, "Could not translate offset\n"); + return(-1); + } + + err_msg = encode_offset(width, offset, + i2c_opt.off_buf, &i2c_opt.off_len); + if (err_msg) { + fprintf(stderr, "%s", err_msg); + return(-1); + } + + if (i2c_opt.dir == 'r') { + i2c_opt.count = strtoul(cmd, &cmd, 0); + if (i2c_opt.count == 0 && errno == EINVAL) { + fprintf(stderr, "Could not translate length\n"); + return(-1); + } + } else { + i2c_opt.count = 0; + while (1) { + while(isspace(*cmd)) + cmd++; + if (!*cmd) + break; + if (!isxdigit(*cmd)) { + fprintf(stderr, "Not a hex digit.\n"); + return(-1); + } + dbuf[i2c_opt.count] = digittoint(*cmd++) << 4; + while(isspace(*cmd)) + cmd++; + if (!*cmd) { + fprintf(stderr, + "Uneven number of hex digits.\n"); + return(-1); + } + if (!isxdigit(*cmd)) { + fprintf(stderr, "Not a hex digit.\n"); + return(-1); + } + dbuf[i2c_opt.count++] |= digittoint(*cmd++); + } + } + assert(bus < N_FDCACHE); + fd = fd_cache[bus]; + if (fd < 0) { + (void)sprintf(devbuf, "/dev/iic%u", bus); + fd = open(devbuf, O_RDWR); + if (fd == -1) { + fprintf(stderr, "Error opening I2C controller (%s): %s\n", + devbuf, strerror(errno)); + return (EX_NOINPUT); + } + fd_cache[bus] = fd; + } + + error = i2c_rdwr_transfer(fd, i2c_opt, dbuf); + if (error) + return(-1); + + if (i2c_opt.dir == 'r') { + for (u = 0; u < i2c_opt.count; u++) + printf("%02x", dbuf[u]); + printf("\n"); + } + return (0); +} + +static int +exec_bus(struct options i2c_opt, char *cmd) +{ + int error; + + while (isspace(*cmd)) + cmd++; + if (*cmd == '#' || *cmd == '\0') + return (0); + error = command_bus(i2c_opt, cmd); + if (i2c_opt.verbose) { + (void)fflush(stderr); + printf(error ? "ERROR\n" : "OK\n"); + error = 0; + } else if (error) { + fprintf(stderr, " in: %s", cmd); + } + (void)fflush(stdout); + return (error); +} + +static int +instruct_bus(struct options i2c_opt, int argc, char **argv) +{ + char buf[BUFSIZ]; + int rd_cmds = (argc == 0); + int error; + + while (argc-- > 0) { + if (argc == 0 && !strcmp(*argv, "-")) { + rd_cmds = 1; + } else { + error = exec_bus(i2c_opt, *argv); + if (error) + return (error); + } + argv++; + } + if (!rd_cmds) + return (0); + while (fgets(buf, sizeof buf, stdin) != NULL) { + error = exec_bus(i2c_opt, buf); + if (error) + return (error); + } + return (0); +} + int main(int argc, char** argv) { @@ -541,6 +748,8 @@ main(int argc, char** argv) char do_what = 0; dev = I2C_DEV; + for (ch = 0; ch < N_FDCACHE; ch++) + fd_cache[ch] = -1; /* Default values */ i2c_opt.off = 0; @@ -554,9 +763,10 @@ main(int argc, char** argv) /* Find out what we are going to do */ - while ((ch = getopt(argc, argv, "a:f:d:o:w:c:m:n:sbvrh")) != -1) { + while ((ch = getopt(argc, argv, "a:f:d:iw:c:m:n:sbvrh")) != -1) { switch(ch) { case 'a': + case 'i': case 'r': case 's': if (do_what) @@ -574,8 +784,9 @@ main(int argc, char** argv) /* Then handle the legal subset of arguments */ switch (do_what) { - case 0: usage("Pick one of [-a|-h|-r|-s]"); break; + case 0: usage("Pick one of [-a|-h|-i|-r|-s]"); break; case 'a': optflags = "a:f:d:w:o:c:m:bv"; break; + case 'i': optflags = "iv"; break; case 'r': optflags = "rf:v"; break; case 's': optflags = "sf:n:v"; break; default: assert("Bad do_what"); @@ -610,6 +821,7 @@ main(int argc, char** argv) case 'f': dev = optarg; break; + case 'i': break; case 'm': if (!strcmp(optarg, "no")) i2c_opt.mode = I2C_MODE_NONE; @@ -645,6 +857,8 @@ main(int argc, char** argv) } argc -= optind; argv += optind; + if (do_what == 'i') + return(instruct_bus(i2c_opt, argc, argv)); if (argc > 0) usage("Too many arguments");