git: 9c10d00bf8cd - main - i2c(8): Add interpreted mode for batch/scripted i2c operations
Poul-Henning Kamp
phk at FreeBSD.org
Wed May 19 18:58:03 UTC 2021
The branch main has been updated by phk:
URL: https://cgit.FreeBSD.org/src/commit/?id=9c10d00bf8cdf7735cdba7c09bc16495ce22095b
commit 9c10d00bf8cdf7735cdba7c09bc16495ce22095b
Author: Poul-Henning Kamp <phk at FreeBSD.org>
AuthorDate: 2021-05-19 18:56:59 +0000
Commit: Poul-Henning Kamp <phk at FreeBSD.org>
CommitDate: 2021-05-19 18:56:59 +0000
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 at semihalf.com
and
.An Michal Hajduk Aq Mt mih at semihalf.com .
+.Pp
+.An Poul-Henning Kamp Aq Mt phk at 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");
More information about the dev-commits-src-main
mailing list