Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 19 May 2021 18:58:02 GMT
From:      Poul-Henning Kamp <phk@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 9c10d00bf8cd - main - i2c(8): Add interpreted mode for batch/scripted i2c operations
Message-ID:  <202105191858.14JIw28t001040@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help
The branch main has been updated by phk:

URL: https://cgit.FreeBSD.org/src/commit/?id=9c10d00bf8cdf7735cdba7c09bc16495ce22095b

commit 9c10d00bf8cdf7735cdba7c09bc16495ce22095b
Author:     Poul-Henning Kamp <phk@FreeBSD.org>
AuthorDate: 2021-05-19 18:56:59 +0000
Commit:     Poul-Henning Kamp <phk@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@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");
 



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202105191858.14JIw28t001040>