[PATCH v2] libmisc/shell: Add an i2c command
chrisj at rtems.org
chrisj at rtems.org
Mon Aug 29 04:36:43 UTC 2022
From: Chris Johns <chrisj at rtems.org>
Closes #4708
---
cpukit/include/rtems/shellconfig.h | 7 +
cpukit/libmisc/shell/main_i2c.c | 653 +++++++++++++++++++++++++++++
spec/build/cpukit/objshell.yml | 1 +
3 files changed, 661 insertions(+)
create mode 100644 cpukit/libmisc/shell/main_i2c.c
diff --git a/cpukit/include/rtems/shellconfig.h b/cpukit/include/rtems/shellconfig.h
index a013840ee7..bd44d9d310 100644
--- a/cpukit/include/rtems/shellconfig.h
+++ b/cpukit/include/rtems/shellconfig.h
@@ -98,6 +98,7 @@ extern rtems_shell_cmd_t rtems_shell_MD5_Command;
extern rtems_shell_cmd_t rtems_shell_RTC_Command;
extern rtems_shell_cmd_t rtems_shell_SPI_Command;
+extern rtems_shell_cmd_t rtems_shell_I2C_Command;
extern rtems_shell_cmd_t rtems_shell_I2CDETECT_Command;
extern rtems_shell_cmd_t rtems_shell_I2CGET_Command;
extern rtems_shell_cmd_t rtems_shell_I2CSET_Command;
@@ -556,6 +557,12 @@ extern rtems_shell_alias_t * const rtems_shell_Initial_aliases[];
&rtems_shell_SPI_Command,
#endif
+ #if (defined(CONFIGURE_SHELL_COMMANDS_ALL) \
+ && !defined(CONFIGURE_SHELL_NO_COMMAND_I2C)) \
+ || defined(CONFIGURE_SHELL_COMMAND_I2C)
+ &rtems_shell_I2C_Command,
+ #endif
+
#if (defined(CONFIGURE_SHELL_COMMANDS_ALL) \
&& !defined(CONFIGURE_SHELL_NO_COMMAND_I2CDETECT)) \
|| defined(CONFIGURE_SHELL_COMMAND_I2CDETECT)
diff --git a/cpukit/libmisc/shell/main_i2c.c b/cpukit/libmisc/shell/main_i2c.c
new file mode 100644
index 0000000000..bd43afbe38
--- /dev/null
+++ b/cpukit/libmisc/shell/main_i2c.c
@@ -0,0 +1,653 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+
+/**
+ * @file
+ *
+ * @ingroup
+ *
+ * @brief This source file contains the I2C command.
+ */
+
+/*
+ * Copyright (c) 2022 Chris Johns. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <errno.h>
+
+#include <rtems.h>
+#include <rtems/shell.h>
+
+#include <dev/i2c/i2c.h>
+#include <linux/i2c-dev.h>
+
+#define BUF_SIZE 2048
+#define MAX_BUSES 8
+#define MIN_ADDR 1
+#define MAX_ADDR 127 /* needs updating for 10bit addresses */
+
+static char bus_template[128] = "/dev/i2c-%d";
+
+typedef struct {
+ int argc;
+ char** argv;
+ int arg;
+ int bus[MAX_BUSES];
+ uint8_t buffer[BUF_SIZE];
+} i2c_data;
+
+static void dump_memory(const uint8_t* mem, size_t len) {
+ size_t i;
+ for (i = 0; i < len; ++i) {
+ if ((i % 16) == 0) {
+ if (i > 0) {
+ printf("\n");
+ }
+ printf("%04zu", i);
+ }
+ if ((i % 8) == 0) {
+ printf(" ");
+ }
+ printf(" %02x", (unsigned int) mem[i]);
+ }
+ printf("\n");
+}
+
+static int hex_to_bin(const char hex) {
+ int bin = -1;
+ if (hex >= '0' && hex <= '9') {
+ bin = hex - '0';
+ } else if (hex >= 'a' && hex <= 'z') {
+ bin = hex - 'a' + 10;
+ } else if (hex >= 'A' && hex <= 'A') {
+ bin = hex - 'A' + 10;
+ }
+ return bin;
+}
+
+static bool check_args(const char* cmd, int count, i2c_data* i2c) {
+ bool ok = count + i2c->arg <= i2c->argc;
+ if (!ok) {
+ printf("error: %s: not enough arguments\n", cmd);
+ }
+ return ok;
+}
+
+static bool check_flag(i2c_data* i2c) {
+ if (i2c->arg >= i2c->argc) {
+ return false;
+ }
+ return i2c->argv[i2c->arg][0] == '-';
+}
+
+static const char* get_arg_inc(i2c_data* i2c) {
+ if (i2c->arg < i2c->argc) {
+ return i2c->argv[i2c->arg++];
+ }
+ return "";
+}
+
+static int get_value(
+ const char* cmd, const char* type, int min, int max, int* val, i2c_data* i2c) {
+ const char* arg;
+ if (!check_args(cmd, 1, i2c)) {
+ return -1;
+ }
+ arg = get_arg_inc(i2c);
+ *val = (int) strtol(arg, NULL, 0);
+ if (*val < min || *val > max) {
+ printf("error: %s: invalid %s: %s\n", cmd, type, arg);
+ return false;
+ }
+ return true;
+}
+
+/*
+ * Address format is `bus:device`
+ */
+static bool get_device_address(const char* cmd, int* bus, int* addr, i2c_data* i2c) {
+ const char* arg;
+ const char* colon;
+ char* end = NULL;
+ *bus = -1;
+ *addr = -1;
+ if (!check_args(cmd, 1, i2c)) {
+ return -1;
+ }
+ arg = get_arg_inc(i2c);
+ colon = strchr(arg, ':');
+ if (colon != NULL) {
+ *bus = (int) strtol(arg, &end, 0);
+ if (end != NULL) {
+ if (colon != end) {
+ printf("error: invalid address: %s\n", arg);
+ return false;
+ }
+ }
+ arg = colon + 1;
+ }
+ *addr = (int) strtol(arg, &end, 16);
+ if (end != NULL && *end != '\0') {
+ printf("error: invalid address: %s\n", arg);
+ return false;
+ }
+ if (*addr < MIN_ADDR || *addr > MAX_ADDR) {
+ printf("error: invalid address: %s\n", arg);
+ return false;
+ }
+ return true;
+}
+
+static bool get_data(const char* cmd, int* len, i2c_data* i2c) {
+ const char* hex;
+ int l;
+ *len = -1;
+ if (!check_args(cmd, 1, i2c)) {
+ return false;
+ }
+ hex = get_arg_inc(i2c);
+ l = 0;
+ while (hex[l] != '\0') {
+ int bin;
+ bin = hex_to_bin(hex[l]);
+ if (bin < 0) {
+ printf("error: wr: invalid data: at %d in %s\n", l, hex);
+ return false;
+ }
+ i2c->buffer[l / 2] = bin << 4;
+ ++l;
+ if (hex[l] == '\0') {
+ printf("error: wr: data length not even: %s\n", hex);
+ return false;
+ }
+ bin = hex_to_bin(hex[l]);
+ if (bin < 0) {
+ printf("error: wr: invalid data: at %d in %s\n", l, hex);
+ return false;
+ }
+ i2c->buffer[l / 2] |= bin;
+ ++l;
+ if (l / 2 >= BUF_SIZE) {
+ printf("error: wr: data string to long\n");
+ return false;
+ }
+ }
+ *len = l;
+ return true;
+}
+
+static bool get_address_8(const char* cmd, int* addr, i2c_data* i2c) {
+ return get_value(cmd, "address", 0, 255, addr, i2c);
+}
+
+static bool get_address_16(const char* cmd, int* addr, i2c_data* i2c) {
+ return get_value(cmd, "address", 0, 65535, addr, i2c);
+}
+
+static bool get_length(const char* cmd, int* len, i2c_data* i2c) {
+ return get_value(cmd, "length", 1, sizeof(i2c->buffer), len, i2c);
+}
+
+static int i2c_bus_open(int bus, bool error) {
+ char path[32];
+ snprintf(path, sizeof(path), bus_template, bus);
+ int fd = open(path, O_RDWR);
+ if (fd < 0 && error) {
+ printf("error: open: %s: %s\n", path, strerror(errno));
+ }
+ return fd;
+}
+
+static void i2c_bus_close(int bus) {
+ if (bus >= 0) {
+ close(bus);
+ }
+}
+
+static i2c_data* i2c_data_alloc(int argc, char *argv[]) {
+ i2c_data* i2c;
+ int i;
+ i2c = calloc(1, sizeof(i2c_data));
+ if (i2c == NULL) {
+ printf("error: no memory\n");
+ return NULL;
+ }
+ i2c->argc = argc;
+ i2c->argv = argv;
+ i2c->arg = 1;
+ for (i = 0; i < MAX_BUSES; ++i) {
+ i2c->bus[i] = -1;
+ }
+ return i2c;
+}
+
+static void i2c_data_free(i2c_data* i2c) {
+ int i;
+ for (i = 0; i < MAX_BUSES; ++i) {
+ i2c_bus_close(i2c->bus[i]);
+ }
+ free(i2c);
+}
+
+static bool i2c_connect(i2c_data* i2c, int bus, bool error) {
+ if (bus >= MAX_BUSES) {
+ printf("error: bus: invalid bus %d\n", bus);
+ return false;
+ }
+ if (i2c->bus[bus] < 0) {
+ i2c->bus[bus] = i2c_bus_open(bus, error);
+ }
+ return i2c->bus[bus] >= 0;
+}
+
+static bool i2c_set_slave(i2c_data* i2c, int bus, int addr, bool error) {
+ int r;
+ if (addr < MIN_ADDR || addr > MAX_ADDR) {
+ if (error) {
+ printf("error: set slave: invalid address: %d\n", addr);
+ }
+ return false;
+ }
+ if (!i2c_connect(i2c, bus, error)) {
+ return false;
+ }
+ r = ioctl(i2c->bus[bus], I2C_SLAVE, (intptr_t) addr);
+ if (r < 0) {
+ if (error) {
+ printf("error: set slave: ioctl: %s\n", strerror(errno));
+ }
+ return false;
+ }
+ return true;
+}
+
+static bool i2c_set_bus_template(i2c_data* i2c) {
+ const char* bus_templ;
+ if (!check_args("bus", 1, i2c)) {
+ return false;
+ }
+ bus_templ = get_arg_inc(i2c);
+ if (strnlen(bus_templ, sizeof(bus_template)) > sizeof(bus_template)) {
+ printf("error: bus template: too big\n");
+ return false;
+ }
+ strcpy(bus_template, bus_templ);
+ return true;
+}
+
+static bool i2c_detect(i2c_data* i2c) {
+ int bus = 0;
+ int bus_max = MAX_BUSES;
+ bool mode_zero_len_write = true;
+ if (check_flag(i2c)) {
+ const char* opt = get_arg_inc(i2c);
+ if (strcmp(opt, "-b") == 0) {
+ char* end;
+ if (!check_args("detect", 1, i2c)) {
+ return false;
+ }
+ opt = get_arg_inc(i2c);
+ bus = (int) strtol(opt, &end, 0);
+ if (end == opt || *end != '\0') {
+ printf("error: detect: invalid bus: %s\n", opt);
+ return false;
+ }
+ if (bus >= MAX_BUSES) {
+ printf("error: detect: invalid bus: %s\n", opt);
+ return false;
+ }
+ bus_max = bus + 1;
+ } else if (strcmp(opt, "-rw") == 0) {
+ mode_zero_len_write = false;
+ } else {
+ printf("error: detect: invalid option\n");
+ return false;
+ }
+ }
+ while (bus < bus_max) {
+ if (i2c_connect(i2c, bus, false)) {
+ char state[MAX_ADDR + 1] = { '\0' };
+ int addr;
+ int p;
+ for (addr = MIN_ADDR; addr <= MAX_ADDR; ++addr) {
+ if (!i2c_set_slave(i2c, bus, addr, false)) {
+ state[addr] = 'E';
+ } else {
+ if (mode_zero_len_write) {
+ /*
+ * Zero length write is a write transaction that does not
+ * write any data. The slave responds to the start and
+ * address phases of the I2C protocol. I am not sure what
+ * happens with a read-only device. Maybe an ACK or NACK
+ * is is considered a valid response for this type of
+ * detection.
+ */
+ struct i2c_rdwr_ioctl_data data;
+ i2c_msg msg[1];
+ int rc;
+ msg[0].addr = addr;
+ msg[0].flags = 0;
+ msg[0].len = 0;
+ msg[0].buf = i2c->buffer;
+ data.msgs = &msg[0];
+ data.nmsgs = 1;
+ rc = ioctl(i2c->bus[bus], I2C_RDWR, &data);
+ if (rc == 0) {
+ state[addr] = ' ';
+ } else {
+ if (errno == ETIMEDOUT) {
+ state[addr] = 'T';
+ }
+ }
+ } else {
+ uint8_t buf;
+ ssize_t r = read(i2c->bus[bus], &buf, 1);
+ if (r == 1) {
+ state[addr] = ' ';
+ } else {
+ buf = I2C_SMBUS_WRITE;
+ ssize_t r = write(i2c->bus[bus], &buf, 1);
+ if (r == 1) {
+ state[addr] = ' ';
+ }
+ }
+ }
+ }
+ }
+ p = 0;
+ for (addr = MIN_ADDR; addr <= MAX_ADDR; ++addr) {
+ if (state[addr] != '\0') {
+ if ((p % 16) == 0) {
+ printf("%2d ", bus);
+ }
+ printf(" %02x%c", addr, state[addr]);
+ ++p;
+ if ((p % 16) == 0) {
+ printf("\n");
+ }
+ }
+ }
+ if (p > 0 && (p % 16) != 0) {
+ printf("\n");
+ }
+ }
+ ++bus;
+ }
+ return true;
+}
+
+static bool i2c_reader(i2c_data* i2c) {
+ struct i2c_rdwr_ioctl_data data;
+ i2c_msg msg[1];
+ int bus = 0;
+ int addr = 0;
+ int len = -1;
+ int rc;
+ if (!get_device_address("rd", &bus, &addr, i2c)) {
+ return false;
+ }
+ if (!i2c_connect(i2c, bus, true)) {
+ return false;
+ }
+ if (!get_length("rd", &len, i2c)) {
+ return false;
+ }
+ msg[0].addr = addr;
+ msg[0].flags = I2C_M_RD;
+ msg[0].len = len;
+ msg[0].buf = i2c->buffer;
+ data.msgs = &msg[0];
+ data.nmsgs = 1;
+ rc = ioctl(i2c->bus[bus], I2C_RDWR, &data);
+ if (rc < 0) {
+ printf("error: rd: ioctl: %s\n", strerror(errno));
+ return false;
+ }
+ dump_memory(i2c->buffer, len);
+ return true;
+}
+
+static bool i2c_writer(i2c_data* i2c) {
+ struct i2c_rdwr_ioctl_data data;
+ i2c_msg msg[1];
+ int bus = 0;
+ int addr = 0;
+ int len = 0;
+ int rc;
+ if (!get_device_address("wr", &bus, &addr, i2c)) {
+ return false;
+ }
+ if (!i2c_connect(i2c, bus, true)) {
+ return false;
+ }
+ if (!get_data("wr", &len, i2c)) {
+ return false;
+ }
+ msg[0].addr = addr;
+ msg[0].flags = 0;
+ msg[0].len = len / 2;
+ msg[0].buf = i2c->buffer;
+ data.msgs = &msg[0];
+ data.nmsgs = 1;
+ rc = ioctl(i2c->bus[bus], I2C_RDWR, &data);
+ if (rc < 0) {
+ printf("error: wr: ioctl: %s\n", strerror(errno));
+ return false;
+ }
+ return true;
+}
+
+static bool i2c_addr_reader(i2c_data* i2c) {
+ struct i2c_rdwr_ioctl_data data;
+ i2c_msg msg[2];
+ int bus = 0;
+ int addr = 0;
+ uint8_t dev_addr[2];
+ int dev_addr_len = 1;
+ int len = -1;
+ int rc;
+ if (check_flag(i2c)) {
+ const char* opt = get_arg_inc(i2c);
+ if (strcmp(opt, "-8") == 0) {
+ dev_addr_len = 1;
+ } else if (strcmp(opt, "-16") == 0) {
+ dev_addr_len = 2;
+ } else {
+ printf("error: rda: invalid option\n");
+ return false;
+ }
+ }
+ if (!get_device_address("rda", &bus, &addr, i2c)) {
+ return false;
+ }
+ if (!i2c_connect(i2c, bus, true)) {
+ return false;
+ }
+ if (dev_addr_len == 1) {
+ int daddr = 0;
+ if (!get_address_8("rda", &daddr, i2c)) {
+ return false;
+ }
+ dev_addr[0] = daddr;
+ } else {
+ int daddr = 0;
+ if (!get_address_16("rda", &daddr, i2c)) {
+ return false;
+ }
+ dev_addr[0] = (uint8_t) (daddr >> 8);
+ dev_addr[1] = (uint8_t) daddr;
+ }
+ if (!get_length("rda", &len, i2c)) {
+ return false;
+ }
+ msg[0].addr = addr;
+ msg[0].flags = 0;
+ msg[0].len = dev_addr_len;
+ msg[0].buf = &dev_addr[0];
+ msg[1].addr = addr;
+ msg[1].flags = I2C_M_RD;
+ msg[1].len = len;
+ msg[1].buf = i2c->buffer;
+ data.msgs = &msg[0];
+ data.nmsgs = 2;
+ rc = ioctl(i2c->bus[bus], I2C_RDWR, &data);
+ if (rc < 0) {
+ printf("error: rda: ioctl: %s\n", strerror(errno));
+ return false;
+ }
+ dump_memory(i2c->buffer, len);
+ return true;
+}
+
+static bool i2c_addr_writer(i2c_data* i2c) {
+ struct i2c_rdwr_ioctl_data data;
+ i2c_msg msg[2];
+ int bus = 0;
+ int addr = 0;
+ uint8_t dev_addr[2];
+ int dev_addr_len = 1;
+ int len = 0;
+ int rc;
+ if (check_flag(i2c)) {
+ const char* opt = get_arg_inc(i2c);
+ if (strcmp(opt, "-8") == 0) {
+ dev_addr_len = 1;
+ } else if (strcmp(opt, "-16") == 0) {
+ dev_addr_len = 2;
+ } else {
+ printf("error: wra: invalid option\n");
+ return false;
+ }
+ }
+ if (!get_device_address("wra", &bus, &addr, i2c)) {
+ return false;
+ }
+ if (!i2c_connect(i2c, bus, true)) {
+ return false;
+ }
+ if (dev_addr_len == 1) {
+ int daddr = 0;
+ if (!get_address_8("wra", &daddr, i2c)) {
+ return false;
+ }
+ dev_addr[0] = daddr;
+ } else {
+ int daddr = 0;
+ if (!get_address_16("wra", &daddr, i2c)) {
+ return false;
+ }
+ dev_addr[0] = (uint8_t) (daddr >> 8);
+ dev_addr[1] = (uint8_t) daddr;
+ }
+ if (!get_data("wra", &len, i2c)) {
+ return false;
+ }
+ msg[0].addr = addr;
+ msg[0].flags = 0;
+ msg[0].len = dev_addr_len;
+ msg[0].buf = &dev_addr[0];
+ msg[1].addr = addr;
+ msg[1].flags = I2C_M_NOSTART;
+ msg[1].len = len / 2;
+ msg[1].buf = i2c->buffer;
+ data.msgs = &msg[0];
+ data.nmsgs = 2;
+ rc = ioctl(i2c->bus[bus], I2C_RDWR, &data);
+ if (rc < 0) {
+ printf("error: ioctl: %s\n", strerror(errno));
+ return false;
+ }
+ return true;
+}
+
+static void help(void) {
+ printf( "Usage: i2c <command> ...\n");
+ printf( " where <command> is:\n");
+ printf( " help : this help\n");
+ printf( " bus path : set the bus path template, include '%%d' for bus\n");
+ printf( " det <-b bus> : detect devices on bus(es)\n");
+ printf( " rd bus:addr len : read of data from slave\n");
+ printf( " wr bus:addr data.. : write of data to slave\n");
+ printf( " rda <-8> <-16> bus:addr addr len : write address then read data from slave\n");
+ printf( " wra <-8> <-16> bus:addr addr data.. : write address then write data to slave\n");
+ printf( " bus:addr - decimal bus number and hex address, eg 0:74 \n");
+ printf( " data - even length hex data string, eg 001122334455 \n");
+}
+
+static int rtems_shell_main_i2c(
+ int argc, char *argv[]) {
+ bool error = true;;
+ if (argc == 1) {
+ help();
+ } else {
+ i2c_data* i2c = i2c_data_alloc(argc, argv);
+ if (i2c != NULL) {
+ error = false;
+ while (!error && i2c->arg < i2c->argc) {
+ int cmd = i2c->arg++;
+ if (strcmp(i2c->argv[cmd], "help") == 0) {
+ help();
+ error = true;
+ } else if (strcmp(i2c->argv[cmd], "bus") == 0) {
+ error = i2c_set_bus_template(i2c);
+ } else if (strcmp(i2c->argv[cmd], "det") == 0) {
+ error = i2c_detect(i2c);
+ } else if (strcmp(i2c->argv[cmd], "rd") == 0) {
+ error = i2c_reader(i2c);
+ } else if (strcmp(i2c->argv[cmd], "wr") == 0) {
+ error = i2c_writer(i2c);
+ } else if (strcmp(i2c->argv[cmd], "rda") == 0) {
+ error = i2c_addr_reader(i2c);
+ } else if (strcmp(i2c->argv[cmd], "wra") == 0) {
+ error = i2c_addr_writer(i2c);
+ } else {
+ printf("error: invalid command: %s\n", i2c->argv[cmd]);
+ error = true;
+ }
+ }
+ i2c_data_free(i2c);
+ }
+ }
+ return error ? 1 : 0;
+}
+
+#define HELP_LINE \
+ "i2c <command> ... (try: i2c help))"
+
+rtems_shell_cmd_t rtems_shell_I2C_Command = {
+ "i2c", /* name */
+ HELP_LINE, /* usage */
+ "rtems", /* topic */
+ rtems_shell_main_i2c, /* command */
+ NULL, /* alias */
+ NULL, /* next */
+ 0500, /* mode */
+ 0, /* uid */
+ 0 /* gid */
+};
diff --git a/spec/build/cpukit/objshell.yml b/spec/build/cpukit/objshell.yml
index 5fd259bee3..190223b37b 100644
--- a/spec/build/cpukit/objshell.yml
+++ b/spec/build/cpukit/objshell.yml
@@ -56,6 +56,7 @@ source:
- cpukit/libmisc/shell/main_halt.c
- cpukit/libmisc/shell/main_help.c
- cpukit/libmisc/shell/main_hexdump.c
+- cpukit/libmisc/shell/main_i2c.c
- cpukit/libmisc/shell/main_i2cdetect.c
- cpukit/libmisc/shell/main_i2cget.c
- cpukit/libmisc/shell/main_i2cset.c
--
2.19.1
More information about the devel
mailing list