i2c(8): Add interpreted mode for batch/scripted i2c operations

This commit is contained in:
Poul-Henning Kamp 2021-05-19 18:56:59 +00:00
parent e32b2bcff0
commit 9c10d00bf8
2 changed files with 274 additions and 7 deletions

View File

@ -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.

View File

@ -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");