From: Chris Johns <chr...@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 _______________________________________________ devel mailing list devel@rtems.org http://lists.rtems.org/mailman/listinfo/devel