# # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2025 Oxide Computer Company # PROG= i2cadm OBJS = i2cadm.o \ i2cadm_controller.o \ i2cadm_device.o \ i2cadm_io.o \ i2cadm_mux.o \ i2cadm_port.o \ i2cadm_scan.o \ $(HEXDUMP_OBJS) include ../Makefile.cmd include ../Makefile.cmd.64 include ../Makefile.ctf include $(SRC)/common/hexdump/Makefile.com CFLAGS += $(CCVERBOSE) CSTD = $(CSTD_GNU17) LDLIBS += -li2c -ldevinfo -lofmt CTF_MODE = link .KEEP_STATE: $(PROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) all: $(PROG) install: all $(ROOTUSRSBINPROG) clean: $(RM) $(OBJS) include $(SRC)/common/hexdump/Makefile.targ include ../Makefile.targ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * i2c, SMBus, and i3c administration */ #include #include #include #include #include #include #include #include "i2cadm.h" i2cadm_t i2cadm; /* * The number of devices per line for the pretty printed address table. */ #define DEVS_PER_LINE 16 void i2cadm_print_table(const i2cadm_table_t *table, void *arg) { bool post = false; (void) printf("%s %s:\n\n%s\n", table->table_msg, table->table_port, table->table_key); (void) printf("ADDR"); for (uint32_t i = 0; i < DEVS_PER_LINE; i++) { (void) printf("%s0x%x", i != 0 ? " " : "\t", i); } (void) printf("\n"); for (uint16_t i = 0; i < table->table_max; i++) { bool line_start = (i % DEVS_PER_LINE) == 0; if (line_start) { if (table->table_max > UINT8_MAX) { (void) printf("0x%03x\t", i); } else { (void) printf("0x%02x\t", i); } } if (!line_start) { (void) printf(" "); } if (table->table_cb(arg, i)) post = true; if ((i % DEVS_PER_LINE) == DEVS_PER_LINE - 1) { (void) printf("\n"); } } if (post) { table->table_post(arg, table->table_max); } } static void i2cadm_vwarn(const char *fmt, va_list ap) { i2c_hdl_t *hdl = i2cadm.i2c_hdl; (void) fprintf(stderr, "i2cadm: "); (void) vfprintf(stderr, fmt, ap); (void) fprintf(stderr, ": %s: %s (libi2c: 0x%x, sys: %d)\n", i2c_errmsg(hdl), i2c_errtostr(hdl, i2c_err(hdl)), i2c_err(hdl), i2c_syserr(hdl)); } void i2cadm_warn(const char *fmt, ...) { va_list ap; va_start(ap, fmt); i2cadm_vwarn(fmt, ap); va_end(ap); } void __NORETURN i2cadm_fatal(const char *fmt, ...) { va_list ap; va_start(ap, fmt); i2cadm_vwarn(fmt, ap); va_end(ap); exit(EXIT_FAILURE); } void i2cadm_ofmt_errx(const char *fmt, ...) { va_list ap; va_start(ap, fmt); verrx(EXIT_FAILURE, fmt, ap); } void i2cadm_walk_usage(const i2cadm_cmdtab_t *tab, size_t len, FILE *f) { for (size_t i = 0; i < len; i++) { tab[i].icmd_use(f); } } static void i2cadm_usage(const i2cadm_cmdtab_t *tab, size_t len, const char *format, ...) { if (format != NULL) { va_list ap; va_start(ap, format); vwarnx(format, ap); va_end(ap); } if (tab == NULL) return; fprintf(stderr, "Usage: i2cadm ... \n\n"); i2cadm_walk_usage(tab, len, stderr); } int i2cadm_walk_tab(const i2cadm_cmdtab_t *tab, size_t len, int argc, char *argv[]) { if (argc == 0) { i2cadm_usage(tab, len, "missing required sub-command"); return (EXIT_FAILURE); } for (size_t i = 0; i < len; i++) { if (strcmp(argv[0], tab[i].icmd_name) != 0) continue; argc--; argv++; optind = 0; return (tab[i].icmd_func(argc, argv)); } i2cadm_usage(tab, len, "unknown subcommand %s", argv[0]); return (EXIT_USAGE); } /* * The order commands are listed below impacts the order in usage and help * statements. We order these roughly based upon the general operations that one * needs to take starting with all operations that list different entities, * roughly ordered by importance, followed by operations which operate on the * device. */ static const i2cadm_cmdtab_t i2cadm_cmds[] = { { "controller", i2cadm_controller, i2cadm_controller_usage }, { "device", i2cadm_device, i2cadm_device_usage }, { "mux", i2cadm_mux, i2cadm_mux_usage }, { "port", i2cadm_port, i2cadm_port_usage }, { "io", i2cadm_io, i2cadm_io_usage }, { "scan", i2cadm_scan, i2cadm_scan_usage } }; int main(int argc, char *argv[]) { i2cadm.i2c_progname = basename(argv[0]); if (argc < 2) { i2cadm_usage(i2cadm_cmds, ARRAY_SIZE(i2cadm_cmds), "missing required sub-command"); exit(EXIT_USAGE); } i2cadm.i2c_hdl = i2c_init(); if (i2cadm.i2c_hdl == NULL) { err(EXIT_FAILURE, "failed to initialize libi2c"); } argc--; argv++; return (i2cadm_walk_tab(i2cadm_cmds, ARRAY_SIZE(i2cadm_cmds), argc, argv)); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ #ifndef _I2CADM_H #define _I2CADM_H /* * Common i2cadm(8) interfaces */ #include #include #include #ifdef __cplusplus extern "C" { #endif #define EXIT_USAGE 2 typedef struct { const char *i2c_progname; i2c_hdl_t *i2c_hdl; } i2cadm_t; typedef struct i2cadm_cmdtab { const char *icmd_name; int (*icmd_func)(int, char *[]); void (*icmd_use)(FILE *); } i2cadm_cmdtab_t; extern i2cadm_t i2cadm; extern void i2cadm_warn(const char *, ...) __PRINTFLIKE(1); extern void i2cadm_fatal(const char *, ...) __PRINTFLIKE(1) __NORETURN; extern void i2cadm_ofmt_errx(const char *, ...) __PRINTFLIKE(1) __NORETURN; extern void i2cadm_walk_usage(const i2cadm_cmdtab_t *, size_t, FILE *); extern int i2cadm_walk_tab(const i2cadm_cmdtab_t *, size_t, int, char *[]); extern int i2cadm_controller(int, char *[]); extern void i2cadm_controller_usage(FILE *); extern int i2cadm_mux(int, char *[]); extern void i2cadm_mux_usage(FILE *); extern int i2cadm_device(int, char *[]); extern void i2cadm_device_usage(FILE *); extern int i2cadm_io(int, char *[]); extern void i2cadm_io_usage(FILE *); extern int i2cadm_port(int, char *[]); extern void i2cadm_port_usage(FILE *); extern int i2cadm_scan(int, char *[]); extern void i2cadm_scan_usage(FILE *); /* * Pretty print an address table. */ typedef struct { const char *table_port; const char *table_key; const char *table_msg; uint16_t table_max; bool (*table_cb)(void *, uint16_t); void (*table_post)(void *, uint16_t); } i2cadm_table_t; extern void i2cadm_print_table(const i2cadm_table_t *, void *); #ifdef __cplusplus } #endif #endif /* _I2CADM_H */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * i2cadm controller related operations. */ #include #include #include #include #include #include #include #include #include "i2cadm.h" /* * Various property conversion routines. These could also potentially be in * libi2c if we find it useful for other consumers. */ typedef struct op_map { uint32_t om_op; const char *om_name; } op_map_t; static const op_map_t speed_op_map[] = { { I2C_SPEED_STD, "standard" }, { I2C_SPEED_FAST, "fast" }, { I2C_SPEED_FPLUS, "fast-plus" }, { I2C_SPEED_HIGH, "high" }, { I2C_SPEED_ULTRA, "ultra" } }; static const op_map_t type_op_map[] = { { I2C_CTRL_TYPE_I2C, "i2c" }, { I2C_CTRL_TYPE_I3C, "i3c" }, { I2C_CTRL_TYPE_SMBUS, "smbus" } }; static const op_map_t smbus_op_map[] = { { SMBUS_PROP_OP_QUICK_COMMAND, "quick" }, { SMBUS_PROP_OP_SEND_BYTE, "send-byte", }, { SMBUS_PROP_OP_RECV_BYTE, "recv-byte" }, { SMBUS_PROP_OP_WRITE_BYTE, "write-byte" }, { SMBUS_PROP_OP_READ_BYTE, "read-byte" }, { SMBUS_PROP_OP_WRITE_WORD, "write-word" }, { SMBUS_PROP_OP_READ_WORD, "read-word" }, { SMBUS_PROP_OP_PROCESS_CALL, "process-call" }, { SMBUS_PROP_OP_WRITE_BLOCK, "write-block" }, { SMBUS_PROP_OP_READ_BLOCK, "read-block" }, { SMBUS_PROP_OP_HOST_NOTIFY, "host-notify" }, { SMBUS_PROP_OP_BLOCK_PROCESS_CALL, "block-call" }, { SMBUS_PROP_OP_WRITE_U32, "write-u32" }, { SMBUS_PROP_OP_READ_U32, "read-u32" }, { SMBUS_PROP_OP_WRITE_U64, "write-u64" }, { SMBUS_PROP_OP_READ_U64, "read-u64" }, { SMBUS_PROP_OP_I2C_WRITE_BLOCK, "write-i2c-block" }, { SMBUS_PROP_OP_I2C_READ_BLOCK, "read-i2c-block" } }; static boolean_t i2cadm_map_to_str_one(uint32_t val, char *buf, uint_t buflen, const op_map_t *map, size_t nents) { if (val == 0) { return (strlcpy(buf, "--", buflen) < buflen); } for (size_t i = 0; i < nents; i++) { if (map[i].om_op == val) { return (strlcpy(buf, map[i].om_name, buflen) < buflen); } } return (B_FALSE); } static boolean_t i2cadm_map_to_str(uint32_t val, char *buf, uint_t buflen, const op_map_t *map, size_t nents) { ilstr_t ilstr; if (val == 0) { return (strlcpy(buf, "--", buflen) < buflen); } ilstr_init_prealloc(&ilstr, buf, buflen); for (size_t i = 0; i < nents; i++) { if ((val & map[i].om_op) == 0) continue; val &= ~map[i].om_op; if (i > 0) { ilstr_append_char(&ilstr, ','); } ilstr_append_str(&ilstr, map[i].om_name); } if (val != 0) { char str[32]; (void) snprintf(str, sizeof (str), ",0x%x", val); if (ilstr_len(&ilstr) > 0) { ilstr_append_char(&ilstr, ','); } ilstr_append_str(&ilstr, str); } ilstr_errno_t err = ilstr_errno(&ilstr); ilstr_fini(&ilstr); return (err == ILSTR_ERROR_OK); } static boolean_t i2cadm_value_print(i2c_prop_info_t *info, uint32_t val, char *buf, uint_t buflen) { uint_t len; switch (i2c_prop_info_id(info)) { case I2C_PROP_BUS_SPEED: return (i2cadm_map_to_str_one(val, buf, buflen, speed_op_map, ARRAY_SIZE(speed_op_map))); case I2C_PROP_TYPE: return (i2cadm_map_to_str_one(val, buf, buflen, type_op_map, ARRAY_SIZE(type_op_map))); case SMBUS_PROP_SUP_OPS: return (i2cadm_map_to_str(val, buf, buflen, smbus_op_map, ARRAY_SIZE(smbus_op_map))); default: len = snprintf(buf, buflen, "%u", val); } return (len < buflen); } static boolean_t i2cadm_value_print_pos_u32(const i2c_prop_range_t *range, char *buf, uint_t buflen) { ilstr_t ilstr; ilstr_init_prealloc(&ilstr, buf, buflen); for (uint32_t i = 0; i < range->ipr_count; i++) { const i2c_prop_u32_range_t *r; char str[64]; r = &range->ipr_range[i].ipvr_u32; if (r->ipur_min == r->ipur_max) { (void) snprintf(str, sizeof (str), "u", r->ipur_min); } else { (void) snprintf(str, sizeof (str), "%u-%u", r->ipur_min, r->ipur_max); } if (i > 0) { ilstr_append_char(&ilstr, ','); } ilstr_append_str(&ilstr, str); } ilstr_errno_t err = ilstr_errno(&ilstr); ilstr_fini(&ilstr); return (err == ILSTR_ERROR_OK); } static boolean_t i2cadm_value_print_pos_bit32(i2c_prop_info_t *info, const i2c_prop_range_t *range, char *buf, uint_t buflen) { if (range->ipr_count != 1) { return (B_FALSE); } uint32_t val = range->ipr_range[0].ipvr_bit32; switch (i2c_prop_info_id(info)) { case I2C_PROP_BUS_SPEED: return (i2cadm_map_to_str(val, buf, buflen, speed_op_map, ARRAY_SIZE(speed_op_map))); case SMBUS_PROP_SUP_OPS: return (i2cadm_map_to_str(val, buf, buflen, smbus_op_map, ARRAY_SIZE(smbus_op_map))); default: return (snprintf(buf, buflen, "0x%x", val) < buflen); } } static boolean_t i2cadm_value_print_pos(i2c_prop_info_t *info, char *buf, uint_t buflen) { uint_t len; const i2c_prop_range_t *range = i2c_prop_info_pos(info); if (range == NULL) { if (i2c_err(i2cadm.i2c_hdl) == I2C_ERR_PROP_UNSUP) { return (strlcpy(buf, "--", buflen) < buflen); } return (B_FALSE); } if (range->ipr_count == 0) { return (strlcpy(buf, "--", buflen) < buflen); } switch (range->ipr_type) { case I2C_PROP_TYPE_U32: return (i2cadm_value_print_pos_u32(range, buf, buflen)); case I2C_PROP_TYPE_BIT32: return (i2cadm_value_print_pos_bit32(info, range, buf, buflen)); default: return (B_FALSE); } return (len <= buflen); } static void i2cadm_controller_prop_get_usage(FILE *f) { (void) fprintf(stderr, "\ti2cadm controller prop get [-Hp] " "[-o field[,...] [filter]\n"); } static void i2cadm_controller_prop_get_help(const char *fmt, ...) { if (fmt != NULL) { va_list ap; va_start(ap, fmt); vwarnx(fmt, ap); va_end(ap); } (void) fprintf(stderr, "Usage: i2cadm controller prop get [-H] " "[-o field[,...] [-p]] [filter...]\n\n"); (void) fprintf(stderr, "List properties on the specified controller. " "Each selects a property\nbased on its name. When " "multiple filters are specified, they are treated like\nan OR. It " "is an error if a filter isn't used.\n\n" "\t-H\t\tomit the column header\n" "\t-o field\toutput fields to print\n" "\t-p\t\tparseable output (requires -o)\n"); (void) fprintf(stderr, "\nThe following fields are supported:\n" "\tproperty\tthe name of the property\n" "\tperm\t\tthe property's permissions\n" "\tvalue\t\tthe property's value\n" "\tdefault\t\tthe property's default value\n" "\tpossible\tthe property's possible values\n" "\ttype\t\tthe property's type\n" "\tctrl\t\tthe name of the controller\n" "\tid\t\tthe system id for the property\n"); } typedef enum { I2CADM_CTRL_PROP_GET_PROP, I2CADM_CTRL_PROP_GET_PERM, I2CADM_CTRL_PROP_GET_VALUE, I2CADM_CTRL_PROP_GET_DEF, I2CADM_CTRL_PROP_GET_POS, I2CADM_CTRL_PROP_GET_TYPE, I2CADM_CTRL_PROP_GET_CTRL, I2CADM_CTRL_PROP_GET_ID } i2cadm_ctrl_prop_get_otype_t; typedef struct i2cadm_ctrl_prop_get_ofmt { const char *icpg_ctrl; i2c_prop_info_t *icpg_info; bool icpg_valid; uint32_t icpg_u32; } i2cadm_ctrl_prop_get_ofmt_t; static boolean_t i2cadm_ctrl_prop_get_ofmt_cb(ofmt_arg_t *ofarg, char *buf, uint_t buflen) { i2cadm_ctrl_prop_get_ofmt_t *arg = ofarg->ofmt_cbarg; size_t len; uint32_t def; switch (ofarg->ofmt_id) { case I2CADM_CTRL_PROP_GET_PROP: len = strlcpy(buf, i2c_prop_info_name(arg->icpg_info), buflen); break; case I2CADM_CTRL_PROP_GET_PERM: switch (i2c_prop_info_perm(arg->icpg_info)) { case I2C_PROP_PERM_RO: len = strlcpy(buf, "r-", buflen); break; case I2C_PROP_PERM_RW: len = strlcpy(buf, "rw", buflen); break; default: return (B_FALSE); } break; case I2CADM_CTRL_PROP_GET_VALUE: if (!arg->icpg_valid) { len = strlcpy(buf, "--", buflen); break; } return (i2cadm_value_print(arg->icpg_info, arg->icpg_u32, buf, buflen)); case I2CADM_CTRL_PROP_GET_DEF: if (!i2c_prop_info_def_u32(arg->icpg_info, &def)) { len = strlcpy(buf, "--", buflen); break; } return (i2cadm_value_print(arg->icpg_info, def, buf, buflen)); case I2CADM_CTRL_PROP_GET_POS: return (i2cadm_value_print_pos(arg->icpg_info, buf, buflen)); case I2CADM_CTRL_PROP_GET_TYPE: switch (i2c_prop_info_type(arg->icpg_info)) { case I2C_PROP_TYPE_U32: len = strlcpy(buf, "u32", buflen); break; case I2C_PROP_TYPE_BIT32: len = strlcpy(buf, "bit32", buflen); break; default: return (B_FALSE); } break; case I2CADM_CTRL_PROP_GET_CTRL: len = strlcpy(buf, arg->icpg_ctrl, buflen); break; case I2CADM_CTRL_PROP_GET_ID: len = snprintf(buf, buflen, "%u", i2c_prop_info_id(arg->icpg_info)); break; default: return (B_FALSE); } return (len < buflen); } static const char *i2cadm_ctrl_prop_get_fields = "property,perm,value,default,possible"; static const ofmt_field_t i2cadm_ctrl_prop_get_ofmt[] = { { "PROPERTY", 20, I2CADM_CTRL_PROP_GET_PROP, i2cadm_ctrl_prop_get_ofmt_cb }, { "PERM", 6, I2CADM_CTRL_PROP_GET_PERM, i2cadm_ctrl_prop_get_ofmt_cb }, { "VALUE", 16, I2CADM_CTRL_PROP_GET_VALUE, i2cadm_ctrl_prop_get_ofmt_cb }, { "DEFAULT", 16, I2CADM_CTRL_PROP_GET_DEF, i2cadm_ctrl_prop_get_ofmt_cb }, { "POSSIBLE", 16, I2CADM_CTRL_PROP_GET_POS, i2cadm_ctrl_prop_get_ofmt_cb }, { "TYPE", 6, I2CADM_CTRL_PROP_GET_TYPE, i2cadm_ctrl_prop_get_ofmt_cb }, { "CONTROLLER", 12, I2CADM_CTRL_PROP_GET_CTRL, i2cadm_ctrl_prop_get_ofmt_cb }, { "ID", 8, I2CADM_CTRL_PROP_GET_ID, i2cadm_ctrl_prop_get_ofmt_cb }, { NULL, 0, 0, NULL } }; static int i2cadm_controller_prop_get(int argc, char *argv[]) { int c, ret = EXIT_SUCCESS; uint_t flags = 0; boolean_t parse = B_FALSE; const char *fields = NULL, *cname; i2c_ctrl_t *ctrl; bool *filts = NULL; ofmt_status_t oferr; ofmt_handle_t ofmt; while ((c = getopt(argc, argv, ":Ho:p")) != -1) { switch (c) { case 'H': flags |= OFMT_NOHEADER; break; case 'o': fields = optarg; break; case 'p': parse = B_TRUE; flags |= OFMT_PARSABLE; break; case ':': i2cadm_controller_prop_get_help("option -%c requires " "an argument", optopt); exit(EXIT_USAGE); case '?': i2cadm_controller_prop_get_help("unknown option: -%c", optopt); exit(EXIT_USAGE); } } if (parse && fields == NULL) { errx(EXIT_USAGE, "-p requires fields specified with -o"); } if (!parse) { flags |= OFMT_WRAP; } if (fields == NULL) { fields = i2cadm_ctrl_prop_get_fields; } argc -= optind; argv += optind; if (argc == 0) { errx(EXIT_FAILURE, "missing required controller"); } cname = argv[0]; argc--; argv++; if (!i2c_ctrl_init_by_path(i2cadm.i2c_hdl, cname, &ctrl)) { i2cadm_fatal("failed to initialize controller %s", cname); } if (argc > 0) { filts = calloc(argc, sizeof (bool)); if (filts == NULL) { err(EXIT_FAILURE, "failed to allocate memory for " "filter tracking"); } } oferr = ofmt_open(fields, i2cadm_ctrl_prop_get_ofmt, flags, 0, &ofmt); ofmt_check(oferr, parse, ofmt, i2cadm_ofmt_errx, warnx); uint32_t nprops = i2c_ctrl_nprops(ctrl); for (uint32_t i = 0; i < nprops; i++) { i2c_prop_info_t *info; i2cadm_ctrl_prop_get_ofmt_t arg; if (!i2c_prop_info(ctrl, i, &info)) { i2cadm_warn("failed to get property %u information", i); ret = EXIT_FAILURE; continue; } if (argc > 0) { const char *name = i2c_prop_info_name(info); bool match = false; for (int i = 0; i < argc; i++) { if (strcmp(argv[i], name) == 0) { match = true; filts[i] = true; } } if (!match) { i2c_prop_info_free(info); continue; } } (void) memset(&arg, 0, sizeof (arg)); arg.icpg_ctrl = cname; arg.icpg_info = info; if (i2c_prop_info_sup(info)) { i2c_prop_type_t type = i2c_prop_info_type(info); if (type == I2C_PROP_TYPE_U32 || type == I2C_PROP_TYPE_BIT32) { size_t len = sizeof (uint32_t); if (!i2c_prop_get(ctrl, i, &arg.icpg_u32, &len)) { i2cadm_warn("failed to get property " "%s (%u)", i2c_prop_info_name(info), i); ret = EXIT_FAILURE; } else if (len != sizeof (uint32_t)) { warnx("property %s (%u) returned " "unexpected property size of %zu, " "but %zu was expected, unable to " "print value", i2c_prop_info_name(info), i, len, sizeof (uint32_t)); ret = EXIT_FAILURE; } else { arg.icpg_valid = true; } } else { warnx("property %s (%u) has unknown type 0x%x, " "cannot get or display value", i2c_prop_info_name(info), i, type); ret = EXIT_FAILURE; } } ofmt_print(ofmt, &arg); free(info); } for (int i = 0; i < argc; i++) { if (!filts[i]) { warnx("filter '%s' did not match any properties", argv[i]); ret = EXIT_FAILURE; } } free(filts); ofmt_close(ofmt); i2c_ctrl_fini(ctrl); return (ret); } static void i2cadm_controller_prop_set_usage(FILE *f) { (void) fprintf(stderr, "\ti2cadm controller prop set " "=\n"); } static int i2cadm_controller_prop_set(int argc, char *argv[]) { i2c_ctrl_t *ctrl; i2c_prop_info_t *info; char *prop, *val; size_t buflen = 0; void *buf = NULL; if (argc == 0) { errx(EXIT_FAILURE, "missing required controller and property"); } else if (argc == 1) { errx(EXIT_FAILURE, "missing required property"); } else if (argc > 2) { errx(EXIT_FAILURE, "only one property can be set at a time, " "extraneous arguments start with %s", argv[2]); } if (!i2c_ctrl_init_by_path(i2cadm.i2c_hdl, argv[0], &ctrl)) { i2cadm_fatal("failed to initialize controller %s", argv[0]); } prop = argv[1]; val = strchr(prop, '='); if (val == NULL) { errx(EXIT_FAILURE, "could not parse property name and value " "from %s: missing = separator", argv[1]); } *val = '\0'; val++; if (!i2c_prop_info_by_name(ctrl, prop, &info)) { i2cadm_fatal("failed to get information for property %s", prop); } if (!i2c_prop_info_sup(info)) { errx(EXIT_FAILURE, "controller %s does not support property %s", argv[0], prop); } if (i2c_prop_info_perm(info) != I2C_PROP_PERM_RW) { errx(EXIT_FAILURE, "property %s is read-only on controller %s", prop, argv[0]); } /* * See if this property is one that we parse via string transformations. */ switch (i2c_prop_info_id(info)) { case I2C_PROP_BUS_SPEED: for (size_t i = 0; i < ARRAY_SIZE(speed_op_map); i++) { if (strcmp(val, speed_op_map[i].om_name) == 0) { buflen = sizeof (uint32_t); buf = calloc(1, buflen); if (buf == NULL) { errx(EXIT_FAILURE, "failed to allocate " "%zu bytes of memory to hold %s " "property value", buflen, prop); } (void) memcpy(buf, &speed_op_map[i].om_op, sizeof (uint32_t)); } } break; default: break; } if (buf == NULL) { uint32_t u32; const char *errstr; switch (i2c_prop_info_type(info)) { case I2C_PROP_TYPE_U32: case I2C_PROP_TYPE_BIT32: u32 = (uint32_t)strtonumx(val, 0, UINT32_MAX, &errstr, 0); if (errstr != NULL) { errx(EXIT_FAILURE, "invalid 32-bit %s property " "values: %s is %s", prop, val, errstr); } buflen = sizeof (uint32_t); buf = calloc(1, buflen); if (buf == NULL) { errx(EXIT_FAILURE, "failed to allocate %zu " "bytes of memory to hold %s property value", buflen, prop); } (void) memcpy(buf, &u32, sizeof (uint32_t)); break; default: errx(EXIT_FAILURE, "unable to parse property %s type " "0x%x", prop, i2c_prop_info_type(info)); } } if (!i2c_prop_set(ctrl, i2c_prop_info_id(info), buf, buflen)) { i2cadm_fatal("failed to set property %s to %s", prop, val); } i2c_prop_info_free(info); i2c_ctrl_fini(ctrl); return (EXIT_SUCCESS); } static i2cadm_cmdtab_t i2cadm_ctrl_prop_cmds[] = { { "get", i2cadm_controller_prop_get, i2cadm_controller_prop_get_usage }, { "set", i2cadm_controller_prop_set, i2cadm_controller_prop_set_usage } }; static int i2cadm_controller_prop(int argc, char *argv[]) { return (i2cadm_walk_tab(i2cadm_ctrl_prop_cmds, ARRAY_SIZE(i2cadm_ctrl_prop_cmds), argc, argv)); } static void i2cadm_controller_prop_usage(FILE *f) { i2cadm_walk_usage(i2cadm_ctrl_prop_cmds, ARRAY_SIZE(i2cadm_ctrl_prop_cmds), f); } static void i2cadm_controller_list_usage(FILE *f) { (void) fprintf(f, "\ti2cadm controller list [-H] [-o field,[...] [-p]] " "[filter]\n"); } static void i2cadm_controller_list_help(const char *fmt, ...) { if (fmt != NULL) { va_list ap; va_start(ap, fmt); vwarnx(fmt, ap); va_end(ap); } (void) fprintf(stderr, "Usage: i2cadm controller list [-H] " "[-o field[,...] [-p]] [filter...]\n\n"); (void) fprintf(stderr, "List I2C Controllers. Each selects a " "set of controllers to show and\ncan be a controller or driver " "name. When multiple filters are specified, they\nare treated like " "an OR. It is an error if a filter isn't used.\n\n" "\t-H\t\tomit the column header\n" "\t-o field\toutput fields to print\n" "\t-p\t\tparseable output (requires -o)\n"); (void) fprintf(stderr, "\nThe following fields are supported:\n" "\tname\t\tthe controller's name\n" "\ttype\t\tthe controller's type (e.g. i2c, smbus)\n" "\tspeed\t\tthe controller's current speed\n" "\tdriver\t\tthe name of the driver for the controller\n" "\tinstance\tthe driver instance for the controller\n" "\tprovider\tthe /devices path of the provider\n"); } typedef enum { I2CADM_CTRL_LIST_NAME, I2CADM_CTRL_LIST_TYPE, I2CADM_CTRL_LIST_SPEED, I2CADM_CTRL_LIST_NPORTS, I2CADM_CTRL_LIST_DRIVER, I2CADM_CTRL_LIST_INSTANCE, I2CADM_CTRL_LIST_PROVIDER } i2cadm_ctrl_list_otype_t; typedef struct i2cadm_ctrl_list_ofmt { di_node_t icl_nexus; di_node_t icl_drv; i2c_ctrl_t *icl_ctrl; uint32_t icl_speed; uint32_t icl_type; uint32_t icl_nports; } i2cadm_ctrl_list_ofmt_t; static boolean_t i2cadm_ctrl_list_ofmt_cb(ofmt_arg_t *ofarg, char *buf, uint_t buflen) { i2cadm_ctrl_list_ofmt_t *arg = ofarg->ofmt_cbarg; size_t len; switch (ofarg->ofmt_id) { case I2CADM_CTRL_LIST_NAME: len = strlcat(buf, di_bus_addr(arg->icl_nexus), buflen); break; case I2CADM_CTRL_LIST_TYPE: return (i2cadm_map_to_str_one(arg->icl_type, buf, buflen, type_op_map, ARRAY_SIZE(type_op_map))); case I2CADM_CTRL_LIST_SPEED: return (i2cadm_map_to_str_one(arg->icl_speed, buf, buflen, speed_op_map, ARRAY_SIZE(speed_op_map))); case I2CADM_CTRL_LIST_NPORTS: len = snprintf(buf, buflen, "%u", arg->icl_nports); break; case I2CADM_CTRL_LIST_DRIVER: len = strlcat(buf, di_driver_name(arg->icl_drv), buflen); break; case I2CADM_CTRL_LIST_INSTANCE: len = snprintf(buf, buflen, "%s%d", di_driver_name(arg->icl_drv), di_instance(arg->icl_drv)); break; case I2CADM_CTRL_LIST_PROVIDER: len = strlcat(buf, i2c_ctrl_path(arg->icl_ctrl), buflen); break; default: return (B_FALSE); } return (len < buflen); } static const char *i2cadm_ctrl_list_fields = "name,type,speed,nports,provider"; static const ofmt_field_t i2cadm_ctrl_list_ofmt[] = { { "NAME", 12, I2CADM_CTRL_LIST_NAME, i2cadm_ctrl_list_ofmt_cb }, { "TYPE", 10, I2CADM_CTRL_LIST_TYPE, i2cadm_ctrl_list_ofmt_cb }, { "SPEED", 12, I2CADM_CTRL_LIST_SPEED, i2cadm_ctrl_list_ofmt_cb }, { "NPORTS", 8, I2CADM_CTRL_LIST_NPORTS, i2cadm_ctrl_list_ofmt_cb }, { "DRIVER", 10, I2CADM_CTRL_LIST_DRIVER, i2cadm_ctrl_list_ofmt_cb }, { "INSTANCE", 16, I2CADM_CTRL_LIST_INSTANCE, i2cadm_ctrl_list_ofmt_cb }, { "PROVIDER", 40, I2CADM_CTRL_LIST_PROVIDER, i2cadm_ctrl_list_ofmt_cb }, { NULL, 0, 0, NULL } }; static int i2cadm_controller_list(int argc, char *argv[]) { int c, ret = EXIT_SUCCESS; uint_t flags = 0; boolean_t parse = B_FALSE; const char *fields = NULL; bool *filts = NULL, print = false; ofmt_status_t oferr; ofmt_handle_t ofmt; i2c_ctrl_iter_t *iter; i2c_iter_t iret; const i2c_ctrl_disc_t *disc; while ((c = getopt(argc, argv, ":Ho:p")) != -1) { switch (c) { case 'H': flags |= OFMT_NOHEADER; break; case 'o': fields = optarg; break; case 'p': parse = B_TRUE; flags |= OFMT_PARSABLE; break; case ':': i2cadm_controller_list_help("option -%c requires an " "argument", optopt); exit(EXIT_USAGE); case '?': i2cadm_controller_list_help("unknown option: -%c", optopt); exit(EXIT_USAGE); } } if (parse && fields == NULL) { errx(EXIT_USAGE, "-p requires fields specified with -o"); } if (!parse) { flags |= OFMT_WRAP; } if (fields == NULL) { fields = i2cadm_ctrl_list_fields; } argc -= optind; argv += optind; if (argc > 0) { filts = calloc(argc, sizeof (bool)); if (filts == NULL) { err(EXIT_FAILURE, "failed to allocate memory for " "filter tracking"); } } oferr = ofmt_open(fields, i2cadm_ctrl_list_ofmt, flags, 0, &ofmt); ofmt_check(oferr, parse, ofmt, i2cadm_ofmt_errx, warnx); if (!i2c_ctrl_discover_init(i2cadm.i2c_hdl, &iter)) { i2cadm_fatal("failed to initialize controller walk"); } while ((iret = i2c_ctrl_discover_step(iter, &disc)) == I2C_ITER_VALID) { i2cadm_ctrl_list_ofmt_t arg; i2c_ctrl_t *ctrl; (void) memset(&arg, 0, sizeof (arg)); arg.icl_nexus = i2c_ctrl_disc_devi(disc); arg.icl_drv = di_parent_node(arg.icl_nexus); if (argc > 0) { const char *name = di_bus_addr(arg.icl_nexus); const char *drv = di_driver_name(arg.icl_drv); bool match = false; for (int i = 0; i < argc; i++) { if (strcmp(argv[i], name) == 0 || strcmp(argv[i], drv) == 0) { match = true; filts[i] = true; } } if (!match) { continue; } } if (!i2c_ctrl_init(i2cadm.i2c_hdl, arg.icl_nexus, &ctrl)) { i2cadm_warn("failed to initialize controller %s", di_bus_addr(arg.icl_nexus)); continue; } size_t len = sizeof (uint32_t); if (!i2c_prop_get(ctrl, I2C_PROP_BUS_SPEED, &arg.icl_speed, &len)) { i2cadm_warn("failed to get controller %s speed", di_bus_addr(arg.icl_nexus)); i2c_ctrl_fini(ctrl); continue; } VERIFY3U(len, ==, sizeof (uint32_t)); if (!i2c_prop_get(ctrl, I2C_PROP_TYPE, &arg.icl_type, &len)) { i2cadm_warn("failed to get controller %s type", di_bus_addr(arg.icl_nexus)); i2c_ctrl_fini(ctrl); continue; } VERIFY3U(len, ==, sizeof (uint32_t)); if (!i2c_prop_get(ctrl, I2C_PROP_NPORTS, &arg.icl_nports, &len)) { i2cadm_warn("failed to get controller %s ports", di_bus_addr(arg.icl_nexus)); i2c_ctrl_fini(ctrl); continue; } VERIFY3U(len, ==, sizeof (uint32_t)); arg.icl_ctrl = ctrl; ofmt_print(ofmt, &arg); i2c_ctrl_fini(ctrl); print = true; } if (iret == I2C_ITER_ERROR) { i2cadm_warn("failed to iterate controllers"); ret = EXIT_FAILURE; } for (int i = 0; i < argc; i++) { if (!filts[i]) { warnx("filter '%s' did not match any controllers", argv[i]); ret = EXIT_FAILURE; } } if (!print && argc == 0) { warnx("no controllers found"); ret = EXIT_FAILURE; } free(filts); ofmt_close(ofmt); i2c_ctrl_discover_fini(iter); return (ret); } static i2cadm_cmdtab_t i2cadm_ctrl_cmds[] = { { "list", i2cadm_controller_list, i2cadm_controller_list_usage }, { "prop", i2cadm_controller_prop, i2cadm_controller_prop_usage } }; int i2cadm_controller(int argc, char *argv[]) { return (i2cadm_walk_tab(i2cadm_ctrl_cmds, ARRAY_SIZE(i2cadm_ctrl_cmds), argc, argv)); } void i2cadm_controller_usage(FILE *f) { i2cadm_walk_usage(i2cadm_ctrl_cmds, ARRAY_SIZE(i2cadm_ctrl_cmds), f); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * i2cadm device related operations. */ #include #include #include #include #include #include #include "i2cadm.h" /* * Attempt to apply filters. We accept the following filters that are trying to * match devices: * * - Matching a specific address * - Matching a portion of a device path * - Matching a specific driver or instance * - Matching a node name * * These filters are shared between both device list and device addrs. */ static bool i2cadm_device_filt(const i2c_dev_info_t *info, int nfilts, char **filts, bool *used) { bool match = false; char inst[128] = { '\0' }, addr[64] = { '\0' }; const char *name, *path, *driver; size_t pathlen; if (nfilts == 0) { return (true); } name = i2c_device_info_name(info); path = i2c_device_info_path(info); pathlen = strlen(path); driver = i2c_device_info_driver(info); if (i2c_device_info_instance(info) != -1 && driver != NULL) { (void) snprintf(inst, sizeof (inst), "%s%d", driver, i2c_device_info_instance(info)); } const i2c_addr_t *ia = i2c_device_info_addr_primary(info); if (!i2c_addr_to_string(i2cadm.i2c_hdl, ia, addr, sizeof (addr))) { addr[0] = '\0'; } /* * Note, we have to go through all the filters to see if they match as * someone could have specified something more than once. */ for (int i = 0; i < nfilts; i++) { if (strcmp(filts[i], name) == 0) { used[i] = true; match = true; continue; } if (addr[0] != '\0' && strcmp(filts[i], addr) == 0) { used[i] = true; match = true; continue; } if (driver != NULL && strcmp(filts[i], driver) == 0) { used[i] = true; match = true; continue; } if (inst[0] != '\0' && strcmp(filts[i], inst) == 0) { used[i] = true; match = true; continue; } if (strcmp(path, filts[i]) == 0) { used[i] = true; match = true; continue; } size_t len = strlen(filts[i]); if (len < pathlen && strncmp(path, filts[i], len) == 0) { used[i] = true; match = true; continue; } } return (match); } static int i2cadm_device_iter(ofmt_handle_t ofmt, int argc, char *argv[], bool *filts, void (*func)(ofmt_handle_t, const i2c_dev_info_t *)) { int ret = EXIT_SUCCESS; bool print = false; const i2c_dev_disc_t *disc; i2c_dev_iter_t *iter; i2c_iter_t iret; if (!i2c_device_discover_init(i2cadm.i2c_hdl, &iter)) { i2cadm_fatal("failed to initialize device discovery"); } while ((iret = i2c_device_discover_step(iter, &disc)) == I2C_ITER_VALID) { i2c_dev_info_t *info; if (!i2c_device_info_snap(i2cadm.i2c_hdl, i2c_device_disc_devi(disc), &info)) { i2cadm_warn("failed to get device information for " "%s", i2c_device_disc_path(disc)); ret = EXIT_FAILURE; continue; } if (!i2cadm_device_filt(info, argc, argv, filts)) { i2c_device_info_free(info); continue; } func(ofmt, info); i2c_device_info_free(info); print = true; } if (iret == I2C_ITER_ERROR) { i2cadm_warn("failed to discover devices"); ret = EXIT_FAILURE; } for (int i = 0; i < argc; i++) { if (!filts[i]) { warnx("filter '%s' did not match any devices", argv[i]); ret = EXIT_FAILURE; } } if (!print && argc == 0) { warnx("no I2C devices found"); ret = EXIT_FAILURE; } free(filts); ofmt_close(ofmt); i2c_device_discover_fini(iter); return (ret); } static void i2cadm_device_addrs_usage(FILE *f) { (void) fprintf(f, "\ti2cadm device addrs [-H] [-o field,[...] [-p]] " "[filter]\n"); } static void i2cadm_device_addrs_help(const char *fmt, ...) { if (fmt != NULL) { va_list ap; va_start(ap, fmt); vwarnx(fmt, ap); va_end(ap); } (void) fprintf(stderr, "Usage: i2cadm device addrs [-H] " "[-o field[,...] [-p]] [filters]\n\n"); (void) fprintf(stderr, "List addresses assigned to devices and their " "source. Each selects\ndevices based upon its address, " "the device's name, the driver's name, the\ndriver's instance, or " "the I2C path. Multiple filters are treated as an OR. It\n is an " "error if a filter isn't used.\n\n" "\t-H\t\tomit the column header\n" "\t-o field\toutput fields to print\n" "\t-p\t\tparseable output (requires -o)\n"); (void) fprintf(stderr, "\nThe following fields are supported:\n" "\tpath\t\tthe device path\n" "\ttype\t\tthe address's type\n" "\taddr\t\tthe specific address\n" "\tsource\tindicates where the address came from\n"); } typedef enum { I2CADM_DEVICE_ADDRS_PATH, I2CADM_DEVICE_ADDRS_TYPE, I2CADM_DEVICE_ADDRS_ADDR, I2CADM_DEVICE_ADDRS_SOURCE } i2cadm_device_addrs_otype_t; typedef struct i2cadm_device_addrs_ofmt { const i2c_dev_info_t *idoa_info; const i2c_addr_t *idoa_addr; i2c_addr_source_t idoa_source; } i2cadm_device_addrs_ofmt_t; static boolean_t i2cadm_device_addrs_ofmt_cb(ofmt_arg_t *ofarg, char *buf, uint_t buflen) { const i2cadm_device_addrs_ofmt_t *arg = ofarg->ofmt_cbarg; size_t len; switch (ofarg->ofmt_id) { case I2CADM_DEVICE_ADDRS_PATH: len = strlcpy(buf, i2c_device_info_path(arg->idoa_info), buflen); break; case I2CADM_DEVICE_ADDRS_TYPE: if (arg->idoa_addr->ia_type == I2C_ADDR_7BIT) { len = strlcpy(buf, "7-bit", buflen); } else if (arg->idoa_addr->ia_type == I2C_ADDR_10BIT) { len = strlcpy(buf, "10-bit", buflen); } else { len = snprintf(buf, buflen, "unknown (0x%x)", arg->idoa_addr->ia_type); } break; case I2CADM_DEVICE_ADDRS_ADDR: len = snprintf(buf, buflen, "0x%02x", arg->idoa_addr->ia_addr); break; case I2CADM_DEVICE_ADDRS_SOURCE: switch (arg->idoa_source) { case I2C_ADDR_SOURCE_REG: len = strlcpy(buf, "platform", buflen); break; case I2C_ADDR_SOURCE_CLAIMED: len = strlcpy(buf, "claimed", buflen); break; case I2C_ADDR_SOURCE_SHARED: len = strlcpy(buf, "shared", buflen); break; default: len = snprintf(buf, buflen, "unknown (0x%x)", arg->idoa_source); } break; default: return (B_FALSE); } return (len < buflen); } static const char *i2cadm_device_addrs_fields = "path,type,addr,source"; static const ofmt_field_t i2cadm_device_addrs_ofmt[] = { { "PATH", 40, I2CADM_DEVICE_ADDRS_PATH, i2cadm_device_addrs_ofmt_cb }, { "TYPE", 10, I2CADM_DEVICE_ADDRS_TYPE, i2cadm_device_addrs_ofmt_cb }, { "ADDR", 10, I2CADM_DEVICE_ADDRS_ADDR, i2cadm_device_addrs_ofmt_cb }, { "SOURCE", 16, I2CADM_DEVICE_ADDRS_SOURCE, i2cadm_device_addrs_ofmt_cb }, { NULL, 0, 0, NULL } }; static void i2cadm_device_addrs_cb(ofmt_handle_t ofmt, const i2c_dev_info_t *info) { for (uint32_t i = 0; i < i2c_device_info_naddrs(info); i++) { i2cadm_device_addrs_ofmt_t arg; arg.idoa_info = info; arg.idoa_addr = i2c_device_info_addr(info, i); arg.idoa_source = i2c_device_info_addr_source(info, i); ofmt_print(ofmt, &arg); } } static int i2cadm_device_addrs(int argc, char *argv[]) { int c; uint_t flags = 0; boolean_t parse = B_FALSE; const char *fields = NULL; bool *filts = NULL; ofmt_status_t oferr; ofmt_handle_t ofmt; while ((c = getopt(argc, argv, ":Ho:p")) != -1) { switch (c) { case 'H': flags |= OFMT_NOHEADER; break; case 'o': fields = optarg; break; case 'p': parse = B_TRUE; flags |= OFMT_PARSABLE; break; case ':': i2cadm_device_addrs_help("option -%c requires an " "argument", optopt); exit(EXIT_USAGE); case '?': i2cadm_device_addrs_help("unknown option: -%c", optopt); exit(EXIT_USAGE); } } if (parse && fields == NULL) { errx(EXIT_USAGE, "-p requires fields specified with -o"); } if (!parse) { flags |= OFMT_WRAP; } if (fields == NULL) { fields = i2cadm_device_addrs_fields; } argc -= optind; argv += optind; if (argc > 0) { filts = calloc(argc, sizeof (bool)); if (filts == NULL) { err(EXIT_FAILURE, "failed to allocate memory for " "filter tracking"); } } oferr = ofmt_open(fields, i2cadm_device_addrs_ofmt, flags, 0, &ofmt); ofmt_check(oferr, parse, ofmt, i2cadm_ofmt_errx, warnx); return (i2cadm_device_iter(ofmt, argc, argv, filts, i2cadm_device_addrs_cb)); } static void i2cadm_device_list_usage(FILE *f) { (void) fprintf(f, "\ti2cadm device list [-H] [-o field,[...] [-p]] " "[filter]\n"); } static void i2cadm_device_list_help(const char *fmt, ...) { if (fmt != NULL) { va_list ap; va_start(ap, fmt); vwarnx(fmt, ap); va_end(ap); } (void) fprintf(stderr, "Usage: i2cadm device list [-H] " "[-o field[,...] [-p]] [filter...]\n\n"); (void) fprintf(stderr, "List I2C devices in the system. Each " "selects devices based upon its\naddress, the device's name, the " "driver's name, the driver's instance, or the\nI2C path. Multiple " "filters are treated as an OR. It is an error if a filter\nisn't " "used.\n\n" "\t-H\t\tomit the column header\n" "\t-o field\toutput fields to print\n" "\t-p\t\tparseable output (requires -o)\n"); (void) fprintf(stderr, "\nThe following fields are supported:\n" "\tname\t\tthe name of the device\n" "\taddr\t\tthe primary address of the device\n" "\tinstance\tthe driver instance of the device\n" "\tpath\t\tthe I2C path of the device\n"); } typedef enum { I2CADM_DEVICE_LIST_NAME, I2CADM_DEVICE_LIST_ADDR, I2CADM_DEVICE_LIST_INSTANCE, I2CADM_DEVICE_LIST_PATH } i2cadm_device_list_otype_t; static boolean_t i2cadm_device_list_ofmt_cb(ofmt_arg_t *ofarg, char *buf, uint_t buflen) { const i2c_dev_info_t *info = ofarg->ofmt_cbarg; size_t len; const i2c_addr_t *addr; switch (ofarg->ofmt_id) { case I2CADM_DEVICE_LIST_NAME: len = strlcpy(buf, i2c_device_info_name(info), buflen); break; case I2CADM_DEVICE_LIST_ADDR: addr = i2c_device_info_addr_primary(info); if (!i2c_addr_to_string(i2cadm.i2c_hdl, addr, buf, buflen)) { return (B_FALSE); } return (B_TRUE); case I2CADM_DEVICE_LIST_INSTANCE: if (i2c_device_info_driver(info) != NULL && i2c_device_info_instance(info) != -1) { len = snprintf(buf, buflen, "%s%d", i2c_device_info_driver(info), i2c_device_info_instance(info)); } else { len = strlcpy(buf, "--", buflen); } break; case I2CADM_DEVICE_LIST_PATH: len = strlcpy(buf, i2c_device_info_path(info), buflen); break; default: return (B_FALSE); } return (len < buflen); } static const char *i2cadm_device_list_fields = "name,addr,instance,path"; static const ofmt_field_t i2cadm_device_list_ofmt[] = { { "NAME", 12, I2CADM_DEVICE_LIST_NAME, i2cadm_device_list_ofmt_cb }, { "ADDR", 12, I2CADM_DEVICE_LIST_ADDR, i2cadm_device_list_ofmt_cb }, { "INSTANCE", 16, I2CADM_DEVICE_LIST_INSTANCE, i2cadm_device_list_ofmt_cb }, { "PATH", 40, I2CADM_DEVICE_LIST_PATH, i2cadm_device_list_ofmt_cb }, { NULL, 0, 0, NULL } }; static void i2cadm_device_list_cb(ofmt_handle_t ofmt, const i2c_dev_info_t *info) { ofmt_print(ofmt, (void *)info); } static int i2cadm_device_list(int argc, char *argv[]) { int c; uint_t flags = 0; boolean_t parse = B_FALSE; const char *fields = NULL; bool *filts = NULL; ofmt_status_t oferr; ofmt_handle_t ofmt; while ((c = getopt(argc, argv, ":Ho:p")) != -1) { switch (c) { case 'H': flags |= OFMT_NOHEADER; break; case 'o': fields = optarg; break; case 'p': parse = B_TRUE; flags |= OFMT_PARSABLE; break; case ':': i2cadm_device_list_help("option -%c requires an " "argument", optopt); exit(EXIT_USAGE); case '?': i2cadm_device_list_help("unknown option: -%c", optopt); exit(EXIT_USAGE); } } if (parse && fields == NULL) { errx(EXIT_USAGE, "-p requires fields specified with -o"); } if (!parse) { flags |= OFMT_WRAP; } if (fields == NULL) { fields = i2cadm_device_list_fields; } argc -= optind; argv += optind; if (argc > 0) { filts = calloc(argc, sizeof (bool)); if (filts == NULL) { err(EXIT_FAILURE, "failed to allocate memory for " "filter tracking"); } } oferr = ofmt_open(fields, i2cadm_device_list_ofmt, flags, 0, &ofmt); ofmt_check(oferr, parse, ofmt, i2cadm_ofmt_errx, warnx); return (i2cadm_device_iter(ofmt, argc, argv, filts, i2cadm_device_list_cb)); } static void i2cadm_device_add_usage(FILE *f) { (void) fprintf(f, "\ti2cadm device add [-c compat] port name addr\n"); } static void i2cadm_device_add_help(const char *fmt, ...) { if (fmt != NULL) { va_list ap; va_start(ap, fmt); vwarnx(fmt, ap); va_end(ap); } (void) fprintf(stderr, "Usage: i2cadm device add [-c comapt] " "port name addr\n\n"); (void) fprintf(stderr, "Inform the system of a new I2C device with the " "specified address. The device\nwill be inserted under the given " "bus (or multiplexer port if specified). The\naddress must be " "unique on its multiplexor (if applicable) and bus.\n\n" "\t-c compat\tAdd the driver compatible entry to the device. This " "may\n\t\t\tbe specified multiple times. They will be added to " "the\n\t\t\tdevice in the order specified.\n"); } static int i2cadm_device_add(int argc, char *argv[]) { int c; char **compat = NULL; size_t ncompat = 0, nalloc = 0; i2c_port_t *port; i2c_addr_t addr; i2c_dev_add_req_t *req; while ((c = getopt(argc, argv, ":c")) != -1) { switch (c) { case 'c': if (ncompat == nalloc) { nalloc += 8; compat = recallocarray(compat, ncompat, nalloc, sizeof (char *)); if (compat == NULL) { err(EXIT_FAILURE, "failed to allocate " "memory for %zu compatible array " "entries", nalloc); } } compat[ncompat] = optarg; ncompat++; break; case ':': i2cadm_device_add_help("option -%c requires an " "argument", optopt); exit(EXIT_USAGE); case '?': i2cadm_device_add_help("unknown option: -%c", optopt); exit(EXIT_USAGE); } } argv += optind; argc -= optind; if (argc > 3) { errx(EXIT_USAGE, "encountered extraneous arguments starting " "with %s", argv[3]); } else if (argc == 0) { errx(EXIT_FAILURE, "missing required port path, device name, " "and device address"); } else if (argc == 1) { errx(EXIT_FAILURE, "missing required device name and device " "address"); } else if (argc == 2) { errx(EXIT_FAILURE, "missing required device address"); } if (!i2c_port_init_by_path(i2cadm.i2c_hdl, argv[0], &port)) { i2cadm_fatal("failed to parse port path %s", argv[0]); } if (!i2c_addr_parse(i2cadm.i2c_hdl, argv[2], &addr)) { i2cadm_fatal("failed to parse address %s", argv[2]); } if (!i2c_device_add_req_init(port, &req)) { i2cadm_fatal("failed to initialize device add request"); } if (!i2c_device_add_req_set_addr(req, &addr)) { i2cadm_fatal("failed to set device address"); } if (!i2c_device_add_req_set_name(req, argv[1])) { i2cadm_fatal("failed to set device name"); } if (ncompat > 0 && !i2c_device_add_req_set_compatible(req, compat, ncompat)) { i2cadm_fatal("failed to set device compatible[]"); } if (!i2c_device_add_req_exec(req)) { i2cadm_fatal("failed to add device"); } i2c_device_add_req_fini(req); i2c_port_fini(port); free(compat); return (EXIT_SUCCESS); } static void i2cadm_device_remove_usage(FILE *f) { (void) fprintf(f, "\ti2cadm device remove \n"); } static void i2cadm_device_remove_help(const char *fmt, ...) { if (fmt != NULL) { va_list ap; va_start(ap, fmt); vwarnx(fmt, ap); va_end(ap); } (void) fprintf(stderr, "Usage: i2cadm device remove \n\n"); (void) fprintf(stderr, "Remove the I2C device identified by the " "specified path. If the device is in use,\nthis may " "fail.\n"); } static int i2cadm_device_remove(int argc, char *argv[]) { int c; i2c_port_t *port; i2c_dev_info_t *info; while ((c = getopt(argc, argv, "")) != -1) { switch (c) { case ':': i2cadm_device_remove_help("option -%c requires an " "argument", optopt); exit(EXIT_USAGE); case '?': i2cadm_device_remove_help("unknown option: -%c", optopt); exit(EXIT_USAGE); } } argv += optind; argc -= optind; if (argc > 1) { errx(EXIT_USAGE, "encountered extraneous arguments starting " "with %s", argv[1]); } else if (argc == 0) { errx(EXIT_FAILURE, "missing required device path"); } if (!i2c_port_dev_init_by_path(i2cadm.i2c_hdl, argv[0], false, &port, &info)) { i2cadm_fatal("failed to parse device path %s", argv[0]); } if (!i2c_device_rem(port, i2c_device_info_addr_primary(info))) { i2cadm_fatal("failed to remove device %s", argv[0]); } i2c_device_info_free(info); i2c_port_fini(port); return (EXIT_SUCCESS); } static i2cadm_cmdtab_t i2cadm_device_cmds[] = { { "list", i2cadm_device_list, i2cadm_device_list_usage }, { "addrs", i2cadm_device_addrs, i2cadm_device_addrs_usage }, { "add", i2cadm_device_add, i2cadm_device_add_usage }, { "remove", i2cadm_device_remove, i2cadm_device_remove_usage } }; int i2cadm_device(int argc, char *argv[]) { return (i2cadm_walk_tab(i2cadm_device_cmds, ARRAY_SIZE(i2cadm_device_cmds), argc, argv)); } void i2cadm_device_usage(FILE *f) { i2cadm_walk_usage(i2cadm_device_cmds, ARRAY_SIZE(i2cadm_device_cmds), f); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * Perform I/O on a given I2C bus, allowing a given mux segment to be activated * or a specific device. */ #include #include #include #include #include #include #include #include #include #include #include "i2cadm.h" /* * Currently we don't have an SMBus block read present here. The main reason is * that we haven't been able to test this end-to-end and therefore don't have a * great API for extracting the target read length. If we have something we can * test against, then we can go ahead and add this. */ typedef enum { I2CADM_IO_M_I2C, I2CADM_IO_M_QUICK_READ, I2CADM_IO_M_QUICK_WRITE, I2CADM_IO_M_RECV_U8, I2CADM_IO_M_READ_U8, I2CADM_IO_M_READ_U16, I2CADM_IO_M_READ_U32, I2CADM_IO_M_READ_U64, I2CADM_IO_M_READ_BLOCK_I2C, I2CADM_IO_M_SEND_U8, I2CADM_IO_M_WRITE_U8, I2CADM_IO_M_WRITE_U16, I2CADM_IO_M_WRITE_U32, I2CADM_IO_M_WRITE_U64, I2CADM_IO_M_WRITE_BLOCK, I2CADM_IO_M_WRITE_BLOCK_I2C, I2CADM_IO_M_CALL } i2cadm_io_mode_t; typedef enum { /* * Indicates that the size of this is fixed and the target size is * specified in the mode_rlen and mode_wlen fields. */ I2CADM_IO_T_FIXED, /* * Indicates that a variable read or write length is required, but not * both. */ I2CADM_IO_T_VAR_READ, I2CADM_IO_T_VAR_WRITE, /* * Indicates that both a variable read and write length is required. The * next one is that only one of them is required, but both are allowed. */ I2CADM_IO_T_VAR_RW, I2CADM_IO_T_VAR_R_OR_W } i2cadm_io_type_t; typedef struct { const char *mode_str; const char *mode_help; i2cadm_io_mode_t mode_val; i2cadm_io_type_t mode_io; bool mode_need_cmd; uint32_t mode_rlen; uint32_t mode_wlen; size_t mode_dlen; } i2cadm_mode_info_t; typedef struct i2cadm_io_req { const i2cadm_mode_info_t *io_mode; i2c_io_req_t *io_i2c; smbus_io_req_t *io_smbus; uint8_t io_cmd; uint16_t io_rlen; uint16_t io_wlen; void *io_wdata; void *io_rdata; } i2cadm_io_req_t; static const i2cadm_mode_info_t i2cadm_io_modes[] = { [I2CADM_IO_M_I2C] = { .mode_str = "i2c", .mode_help = "\t\t\tgeneral-purpose I2C I/O", .mode_val = I2CADM_IO_M_I2C, .mode_io = I2CADM_IO_T_VAR_R_OR_W, .mode_dlen = sizeof (uint8_t) }, [I2CADM_IO_M_QUICK_READ] = { .mode_str = "quick-read", .mode_help = "\t\tSMBus quick read", .mode_val = I2CADM_IO_M_QUICK_READ, .mode_io = I2CADM_IO_T_FIXED }, [I2CADM_IO_M_QUICK_WRITE] = { .mode_str = "quick-write", .mode_help = "\t\tSMBus write read", .mode_val = I2CADM_IO_M_QUICK_WRITE, .mode_io = I2CADM_IO_T_FIXED }, [I2CADM_IO_M_RECV_U8] = { .mode_str = "recv-u8", .mode_help = "\t\t\tSMBus receive byte", .mode_val = I2CADM_IO_M_RECV_U8, .mode_io = I2CADM_IO_T_FIXED, .mode_rlen = sizeof (uint8_t), .mode_dlen = sizeof (uint8_t) }, [I2CADM_IO_M_READ_U8] = { .mode_str = "read-u8", .mode_help = "\t\t\tSMBus read byte with command", .mode_val = I2CADM_IO_M_READ_U8, .mode_io = I2CADM_IO_T_FIXED, .mode_need_cmd = true, .mode_rlen = sizeof (uint8_t), .mode_dlen = sizeof (uint8_t) }, [I2CADM_IO_M_READ_U16] = { .mode_str = "read-u16", .mode_help = "\t\tSMBus read word with command", .mode_val = I2CADM_IO_M_READ_U16, .mode_io = I2CADM_IO_T_FIXED, .mode_need_cmd = true, .mode_rlen = sizeof (uint16_t), .mode_dlen = sizeof (uint16_t) }, [I2CADM_IO_M_READ_U32] = { .mode_str = "read-u32", .mode_help = "\t\tSMBus read u32 with command", .mode_val = I2CADM_IO_M_READ_U32, .mode_io = I2CADM_IO_T_FIXED, .mode_need_cmd = true, .mode_rlen = sizeof (uint32_t), .mode_dlen = sizeof (uint32_t) }, [I2CADM_IO_M_READ_U64] = { .mode_str = "read-u64", .mode_help = "\t\tSMBus read u64 with command", .mode_val = I2CADM_IO_M_READ_U64, .mode_io = I2CADM_IO_T_FIXED, .mode_need_cmd = true, .mode_rlen = sizeof (uint64_t), .mode_dlen = sizeof (uint64_t) }, [I2CADM_IO_M_READ_BLOCK_I2C] = { .mode_str = "read-block-i2c", .mode_help = "\t\tSMBus I2C block read with command (length " "not sent)", .mode_val = I2CADM_IO_M_READ_BLOCK_I2C, .mode_io = I2CADM_IO_T_VAR_READ, .mode_need_cmd = true, .mode_dlen = sizeof (uint8_t) }, [I2CADM_IO_M_SEND_U8] = { .mode_str = "send-u8", .mode_help = "\t\t\tSMBus send byte", .mode_val = I2CADM_IO_M_SEND_U8, .mode_io = I2CADM_IO_T_FIXED, .mode_wlen = sizeof (uint8_t), .mode_dlen = sizeof (uint8_t) }, [I2CADM_IO_M_WRITE_U8] = { .mode_str = "write-u8", .mode_help = "\t\tSMBus write byte with command", .mode_val = I2CADM_IO_M_WRITE_U8, .mode_io = I2CADM_IO_T_FIXED, .mode_need_cmd = true, .mode_wlen = sizeof (uint8_t), .mode_dlen = sizeof (uint8_t) }, [I2CADM_IO_M_WRITE_U16] = { .mode_str = "write-u16", .mode_help = "\t\tSMBus write word with command", .mode_val = I2CADM_IO_M_WRITE_U16, .mode_io = I2CADM_IO_T_FIXED, .mode_need_cmd = true, .mode_wlen = sizeof (uint16_t), .mode_dlen = sizeof (uint16_t) }, [I2CADM_IO_M_WRITE_U32] = { .mode_str = "write-u32", .mode_help = "\t\tSMBus write u32 with command", .mode_val = I2CADM_IO_M_WRITE_U32, .mode_io = I2CADM_IO_T_FIXED, .mode_need_cmd = true, .mode_wlen = sizeof (uint32_t), .mode_dlen = sizeof (uint32_t) }, [I2CADM_IO_M_WRITE_U64] = { .mode_str = "write-u64", .mode_help = "\t\tSMBus write u64 with command", .mode_val = I2CADM_IO_M_WRITE_U64, .mode_io = I2CADM_IO_T_FIXED, .mode_need_cmd = true, .mode_wlen = sizeof (uint64_t), .mode_dlen = sizeof (uint64_t) }, [I2CADM_IO_M_WRITE_BLOCK] = { .mode_str = "write-block", .mode_help = "\t\tSMBus block write with command and length", .mode_val = I2CADM_IO_M_WRITE_BLOCK, .mode_io = I2CADM_IO_T_VAR_WRITE, .mode_need_cmd = true, .mode_dlen = sizeof (uint8_t) }, [I2CADM_IO_M_WRITE_BLOCK_I2C] = { .mode_str = "write-block-i2c", .mode_help = "\t\tSMBus I2C block write with command (length " "not sent)", .mode_val = I2CADM_IO_M_WRITE_BLOCK_I2C, .mode_io = I2CADM_IO_T_VAR_WRITE, .mode_need_cmd = true, .mode_dlen = sizeof (uint8_t) }, [I2CADM_IO_M_CALL] = { .mode_str = "call", .mode_help = "\t\t\tSMBus process call with command (tx and " "rx a u16)", .mode_val = I2CADM_IO_M_CALL, .mode_io = I2CADM_IO_T_FIXED, .mode_need_cmd = true, .mode_rlen = sizeof (uint16_t), .mode_wlen = sizeof (uint16_t), .mode_dlen = sizeof (uint16_t) } }; void i2cadm_io_usage(FILE *f) { (void) fprintf(f, "\ti2cadm io [-m mode] -d dest [-a addr] [-c cmd] " "[-w wlen] [-r rlen] [-o output] \n"); } static void i2cadm_io_help(const char *fmt, ...) { if (fmt != NULL) { va_list ap; va_start(ap, fmt); vwarnx(fmt, ap); va_end(ap); } (void) fprintf(stderr, "Usage: i2cadm io [-m mode] -d dest [-a addr] " "[-r rlen] [-w wlen] [-o output]\n\t\n"); (void) fprintf(stderr, "\nPerform I/O to any arbitrary I2C address on " "the specified controller and\nport. If a mux is part of the " "destination path, then it will be activated\nprior to issuing " "the I/O. Transmitted data will be taken from positional\n" "arguments.\n\nThe following options are supported:\n\n" "\t-a addr\t\tthe 7-bit address to send the I/O to\n" "\t-d dest\t\tspecifies the controller and port to target\n" "\t-m mode\t\tsets the type of I/O issued, defaults to I2C\n" "\t-o output\twrite raw data read to file output\n" "\t-r rlen\t\tsets the number of bytes to read\n" "\t-w wlen\t\tsets the number of bytes to write\n" "\nThe following I/O modes are supported:\n"); for (size_t i = 0; i < ARRAY_SIZE(i2cadm_io_modes); i++) { (void) fprintf(stderr, "\t%s%s\n", i2cadm_io_modes[i].mode_str, i2cadm_io_modes[i].mode_help); } exit(EXIT_FAILURE); } static const i2cadm_mode_info_t * i2cadm_io_parse_mode(const char *str) { for (size_t i = 0; i < ARRAY_SIZE(i2cadm_io_modes); i++) { if (strcasecmp(str, i2cadm_io_modes[i].mode_str) == 0) { return (&i2cadm_io_modes[i]); } } warnx("unknown I/O mode: %s", str); (void) printf("Valid I/O Modes:\n"); for (size_t i = 0; i < ARRAY_SIZE(i2cadm_io_modes); i++) { (void) printf("\t%s%s\n", i2cadm_io_modes[i].mode_str, i2cadm_io_modes[i].mode_help); } exit(EXIT_FAILURE); } static bool i2cadm_io_read_ok(const i2cadm_mode_info_t *mode) { switch (mode->mode_io) { case I2CADM_IO_T_FIXED: return (mode->mode_rlen != 0); case I2CADM_IO_T_VAR_READ: case I2CADM_IO_T_VAR_RW: case I2CADM_IO_T_VAR_R_OR_W: return (true); case I2CADM_IO_T_VAR_WRITE: default: return (false); } } static bool i2cadm_io_read_req(const i2cadm_mode_info_t *mode) { switch (mode->mode_io) { case I2CADM_IO_T_VAR_READ: case I2CADM_IO_T_VAR_RW: return (true); case I2CADM_IO_T_FIXED: case I2CADM_IO_T_VAR_WRITE: case I2CADM_IO_T_VAR_R_OR_W: default: return (false); } } static bool i2cadm_io_write_req(const i2cadm_mode_info_t *mode) { switch (mode->mode_io) { case I2CADM_IO_T_VAR_WRITE: case I2CADM_IO_T_VAR_RW: return (true); case I2CADM_IO_T_FIXED: case I2CADM_IO_T_VAR_READ: case I2CADM_IO_T_VAR_R_OR_W: default: return (false); } } static bool i2cadm_io_write_ok(const i2cadm_mode_info_t *mode) { switch (mode->mode_io) { case I2CADM_IO_T_FIXED: return (mode->mode_wlen != 0); case I2CADM_IO_T_VAR_WRITE: case I2CADM_IO_T_VAR_RW: case I2CADM_IO_T_VAR_R_OR_W: return (true); case I2CADM_IO_T_VAR_READ: default: return (false); } } /* * Look at the specific requested mode and parse the corresponding read and * write lengths. There are a few different cases for how a command performs * I/O: * * - Commands that have a built-in length. For example, SMBus read/write * u8/16 commands. Here if someone specifies the exact required length, * that's fine, otherwise it's an error. * - Commands that require both a read and write length: block call. * - Commands that require either a read or a write length: other block * operations. * - Commands that require at least one I/O direction, but can use both, aka * I2C. */ static void i2cadm_io_parse_rw_len(i2cadm_io_req_t *req, const char *rstr, const char *wstr) { const i2cadm_mode_info_t *mode = req->io_mode; const char *modestr = mode->mode_str; i2cadm_io_type_t type = mode->mode_io; /* * First check if we have the required strings for the command mode. */ if (rstr == NULL && i2cadm_io_read_req(mode)) { errx(EXIT_FAILURE, "missing required I/O read length " "(-r) which is required for I/O mode %s", modestr); } if (wstr == NULL && i2cadm_io_write_req(mode)) { errx(EXIT_FAILURE, "missing required I/O write length " "(-w) which is required for I/O mode %s", modestr); } if (type == I2CADM_IO_T_VAR_R_OR_W && rstr == NULL && wstr == NULL) { errx(EXIT_FAILURE, "I/O mode %s requires at least one or both " "of a read length (-r) and write length (-w) to be " "specified", modestr); } /* * Now if we have a string, check if it is allowed. If so, then parse * it. If this was a fixed length we need to verify that things match. */ if (rstr != NULL) { const char *errstr; if (!i2cadm_io_read_ok(mode)) { errx(EXIT_FAILURE, "I/O mode %s does not allow " "specifying a read length (-r)", modestr); } req->io_rlen = (uint16_t)strtonumx(rstr, 1, I2C_REQ_MAX, &errstr, 0); if (errstr != NULL) { errx(EXIT_FAILURE, "invalid read length: %s is %s, " "valid values are between 1 and %u", rstr, errstr, I2C_REQ_MAX); } if (type == I2CADM_IO_T_FIXED && req->io_rlen != mode->mode_rlen) { errx(EXIT_FAILURE, "I/O mode %s has a fixed read " "length of %u bytes, either do not specify -r or " "set it to %u, not %s", modestr, mode->mode_rlen, mode->mode_rlen, rstr); } } else if (type == I2CADM_IO_T_FIXED) { req->io_rlen = mode->mode_rlen; } if (wstr != NULL) { const char *errstr; if (!i2cadm_io_write_ok(mode)) { errx(EXIT_FAILURE, "I/O mode %s does not allow " "specifying a write length (-w)", modestr); } req->io_wlen = (uint16_t)strtonumx(wstr, 1, I2C_REQ_MAX, &errstr, 0); if (errstr != NULL) { errx(EXIT_FAILURE, "invalid write length: %s is %s, " "valid values are between 1 and %u", wstr, errstr, I2C_REQ_MAX); } if (type == I2CADM_IO_T_FIXED && req->io_wlen != mode->mode_wlen) { errx(EXIT_FAILURE, "I/O mode %s has a fixed write " "length of %u bytes, either do not specify -w or " "set it to %u, not %s", modestr, mode->mode_wlen, mode->mode_wlen, wstr); } } else if (type == I2CADM_IO_T_FIXED) { req->io_wlen = mode->mode_wlen; } } /* * Go through and parse data into the requisite format. Different commands have * a different data size element. While most are a uint8_t, some are larger. We * adjust what we are parsing at this phase. * * We require one argument per data point. We should probably in the future * allow for something like looking for comma characters, but this works for * now. */ static void i2cadm_io_parse_data(i2cadm_io_req_t *req, int argc, char *argv[]) { uint32_t nents; VERIFY3U(req->io_wlen, !=, 0); VERIFY3U(req->io_mode->mode_dlen, !=, 0); VERIFY0(req->io_wlen % req->io_mode->mode_dlen); nents = req->io_wlen / req->io_mode->mode_dlen; if (nents > 1 && req->io_mode->mode_dlen != 1) { errx(EXIT_FAILURE, "fatal internal error, cannot handle " "I/O request with multiple non-byte sized data points"); } req->io_wdata = calloc(nents, req->io_mode->mode_dlen); if (req->io_wdata == NULL) { err(EXIT_FAILURE, "failed to allocate write data buffer (%u " "elements, %zu bytes)", nents, req->io_mode->mode_dlen); } if (argc != nents) { errx(EXIT_FAILURE, "write data requires %u elements, but only " "found %d remaining arguments", nents, argc); } for (int i = 0; i < argc; i++) { unsigned long long ull, max; char *eptr; uint8_t *u8; uint16_t *u16; uint32_t *u32; uint64_t *u64; /* * Note, we can't use strtonumx here because we want to be able * to parse a uint64_t but strtonumx maxes out at a long long. */ errno = 0; ull = strtoull(argv[i], &eptr, 0); if (errno != 0 || *eptr != '\0') { errx(EXIT_FAILURE, "failed to parse data element %s", argv[i]); } switch (req->io_mode->mode_dlen) { case 1: max = UINT8_MAX; break; case 2: max = UINT16_MAX; break; case 4: max = UINT32_MAX; break; case 8: max = UINT64_MAX; break; default: abort(); } if (ull > max) { errx(EXIT_FAILURE, "data element %s is outside the " "bounds for a %zu byte datum ([0, 0x%llx])", argv[i], req->io_mode->mode_dlen, max); } switch (req->io_mode->mode_dlen) { case 1: u8 = req->io_wdata; u8[i] = (uint8_t)ull; break; case 2: u16 = req->io_wdata; u16[i] = (uint16_t)ull; break; case 4: u32 = req->io_wdata; u32[i] = (uint32_t)ull; break; case 8: u64 = req->io_wdata; u64[i] = (uint64_t)ull; break; default: abort(); } } } static void i2cadm_io_write(const i2cadm_io_req_t *req, const i2cadm_mode_info_t *mode, int ofd) { size_t to_write = 0, off = 0; switch (mode->mode_val) { case I2CADM_IO_M_I2C: case I2CADM_IO_M_READ_BLOCK_I2C: to_write = req->io_rlen; break; case I2CADM_IO_M_RECV_U8: case I2CADM_IO_M_READ_U8: to_write = sizeof (uint8_t); break; case I2CADM_IO_M_READ_U16: to_write = sizeof (uint16_t); break; case I2CADM_IO_M_READ_U32: to_write = sizeof (uint32_t); break; case I2CADM_IO_M_READ_U64: to_write = sizeof (uint64_t); break; default: break; } while (to_write > 0) { ssize_t ret = write(ofd, req->io_rdata + off, to_write); if (ret < 0) { err(EXIT_FAILURE, "failed to write %zu bytes to " "output file at offset %zu", to_write, off); } to_write -= ret; off += ret; } } static void i2cadm_io_init(const i2cadm_io_req_t *req, const i2cadm_mode_info_t *mode) { switch (mode->mode_val) { case I2CADM_IO_M_I2C: if (req->io_rlen != 0 && !i2c_io_req_set_receive_buf(req->io_i2c, req->io_rdata, req->io_rlen)) { i2cadm_fatal("failed to set I2C read buffer"); } if (req->io_wlen != 0 && !i2c_io_req_set_transmit_data(req->io_i2c, req->io_wdata, req->io_wlen)) { i2cadm_fatal("Failed to set I2C write buffer"); } break; case I2CADM_IO_M_QUICK_READ: if (!smbus_io_req_set_quick_cmd(req->io_smbus, false)) { i2cadm_fatal("failed to set quick command request"); } break; case I2CADM_IO_M_QUICK_WRITE: if (!smbus_io_req_set_quick_cmd(req->io_smbus, true)) { i2cadm_fatal("failed to set quick command request"); } break; case I2CADM_IO_M_RECV_U8: if (!smbus_io_req_set_recv_byte(req->io_smbus, req->io_rdata)) { i2cadm_fatal("failed to set receive byte request"); } break; case I2CADM_IO_M_READ_U8: if (!smbus_io_req_set_read_u8(req->io_smbus, req->io_cmd, req->io_rdata)) { i2cadm_fatal("failed to set read byte request"); } break; case I2CADM_IO_M_READ_U16: if (!smbus_io_req_set_read_u16(req->io_smbus, req->io_cmd, req->io_rdata)) { i2cadm_fatal("failed to set read word request"); } break; case I2CADM_IO_M_READ_U32: if (!smbus_io_req_set_read_u32(req->io_smbus, req->io_cmd, req->io_rdata)) { i2cadm_fatal("failed to set read u32 request"); } break; case I2CADM_IO_M_READ_U64: if (!smbus_io_req_set_read_u64(req->io_smbus, req->io_cmd, req->io_rdata)) { i2cadm_fatal("failed to set read u64 request"); } break; case I2CADM_IO_M_READ_BLOCK_I2C: if (!smbus_io_req_set_read_block_i2c(req->io_smbus, req->io_cmd, req->io_rdata, req->io_rlen)) { i2cadm_fatal("failed to set read block request"); } break; case I2CADM_IO_M_SEND_U8: if (!smbus_io_req_set_send_byte(req->io_smbus, *(uint8_t *)req->io_wdata)) { i2cadm_fatal("failed to set send byte request"); } break; case I2CADM_IO_M_WRITE_U8: if (!smbus_io_req_set_write_u8(req->io_smbus, req->io_cmd, *(uint8_t *)req->io_wdata)) { i2cadm_fatal("failed to set write byte request"); } break; case I2CADM_IO_M_WRITE_U16: if (!smbus_io_req_set_write_u16(req->io_smbus, req->io_cmd, *(uint16_t *)req->io_wdata)) { i2cadm_fatal("failed to set write word request"); } break; case I2CADM_IO_M_WRITE_U32: if (!smbus_io_req_set_write_u32(req->io_smbus, req->io_cmd, *(uint32_t *)req->io_wdata)) { i2cadm_fatal("failed to set write u32 request"); } break; case I2CADM_IO_M_WRITE_U64: if (!smbus_io_req_set_write_u64(req->io_smbus, req->io_cmd, *(uint64_t *)req->io_wdata)) { i2cadm_fatal("failed to set write u64 request"); } break; case I2CADM_IO_M_WRITE_BLOCK: case I2CADM_IO_M_WRITE_BLOCK_I2C: if (!smbus_io_req_set_write_block(req->io_smbus, req->io_cmd, req->io_wdata, req->io_wlen, mode->mode_val == I2CADM_IO_M_WRITE_BLOCK_I2C)) { i2cadm_fatal("failed to set write block request"); } break; case I2CADM_IO_M_CALL: if (!smbus_io_req_set_process_call(req->io_smbus, req->io_cmd, *(uint16_t *)req->io_wdata, req->io_rdata)) { i2cadm_fatal("failed to set process call request"); } break; } } static void i2cadm_io_print(const i2cadm_io_req_t *req, const i2cadm_mode_info_t *mode) { switch (mode->mode_val) { case I2CADM_IO_M_I2C: case I2CADM_IO_M_READ_BLOCK_I2C: /* * If we didn't actually get any bytes (READ BLOCK) or this * request didn't include a read (I2C), don't do anything. */ if (req->io_rlen == 0) break; /* * While convention wants to include HDF_ADDR here, we do not * since we may be reading at some arbitrary offset via * registers. We're not going to try to interpret that. */ (void) hexdump_file(req->io_rdata, req->io_rlen, HDF_HEADER | HDF_ASCII, stdout); break; case I2CADM_IO_M_RECV_U8: case I2CADM_IO_M_READ_U8: (void) printf("0x%x\n", *(uint8_t *)req->io_rdata); break; case I2CADM_IO_M_READ_U16: (void) printf("0x%x\n", *(uint16_t *)req->io_rdata); break; case I2CADM_IO_M_READ_U32: (void) printf("0x%x\n", *(uint32_t *)req->io_rdata); break; case I2CADM_IO_M_READ_U64: (void) printf("0x%" PRIx64 "\n", *(uint64_t *)req->io_rdata); break; default: VERIFY3U(req->io_rlen, ==, 0); break; } } int i2cadm_io(int argc, char *argv[]) { int c, ofd = -1; const i2cadm_mode_info_t *mode = &i2cadm_io_modes[I2CADM_IO_M_I2C]; const char *dpath = NULL, *addrstr = NULL, *cmdstr = NULL; const char *wstr = NULL, *rstr = NULL, *output = NULL; i2c_port_t *port; i2c_dev_info_t *info; i2c_addr_t addr; i2cadm_io_req_t req; while ((c = getopt(argc, argv, ":a:c:d:m:o:r:w:")) != -1) { switch (c) { case 'a': addrstr = optarg; break; case 'c': cmdstr = optarg; break; case 'd': dpath = optarg; break; case 'm': mode = i2cadm_io_parse_mode(optarg); break; case 'o': output = optarg; break; case 'r': rstr = optarg; break; case 'w': wstr = optarg; break; case ':': i2cadm_io_help("option -%c requires an argument", optopt); exit(EXIT_USAGE); case '?': i2cadm_io_help("unknown option: -%c", optopt); exit(EXIT_USAGE); } } /* * First establish that we have a valid destination and address that * we're targetting. If the user gives us a full path to a device, then * we don't want -a to be specified. if not, then we need -a * ("addrstr"). */ if (dpath == NULL) { errx(EXIT_FAILURE, "missing required destination path"); } if (!i2c_port_dev_init_by_path(i2cadm.i2c_hdl, dpath, true, &port, &info)) { i2cadm_fatal("failed to parse path %s", dpath); } if (info != NULL) { if (addrstr != NULL) { errx(EXIT_FAILURE, "target address specified twice: " "either use an I2C path that specified a device or " "-a, not both"); } addr = *i2c_device_info_addr_primary(info); i2c_device_info_free(info); info = NULL; } else { if (addrstr == NULL) { errx(EXIT_FAILURE, "missing target address: specify an " "I2C path that refers to a device or use -a"); } if (!i2c_addr_parse(i2cadm.i2c_hdl, addrstr, &addr)) { i2cadm_fatal("failed to parse address %s", addrstr); } } bzero(&req, sizeof (req)); req.io_mode = mode; if (mode->mode_val == I2CADM_IO_M_I2C) { if (!i2c_io_req_init(port, &req.io_i2c)) { i2cadm_fatal("failed to initialize I2C I/O request"); } if (!i2c_io_req_set_addr(req.io_i2c, &addr)) { i2cadm_fatal("failed to set I2C request address"); } } else { if (!smbus_io_req_init(port, &req.io_smbus)) { i2cadm_fatal("failed to initialize SMBus I/O request"); } if (!smbus_io_req_set_addr(req.io_smbus, &addr)) { i2cadm_fatal("failed to set I2C request address"); } } if (mode->mode_need_cmd) { const char *errstr = NULL; if (cmdstr == NULL) { errx(EXIT_FAILURE, "missing required SMBus command " "value (-c) for I/O mode %s", mode->mode_str); } req.io_cmd = (uint8_t)strtonumx(cmdstr, 0, UINT8_MAX, &errstr, 0); if (errstr != NULL) { errx(EXIT_FAILURE, "invalid command value (-c): %s " "is %s, valid values are between 0x00 and 0x%x", cmdstr, errstr, UINT8_MAX); } } else { if (cmdstr != NULL) { errx(EXIT_FAILURE, "I/O mode %s does not allow " "specifying an SMBus cmd (-c)", mode->mode_str); } } i2cadm_io_parse_rw_len(&req, rstr, wstr); argc -= optind; argv += optind; if (req.io_wlen == 0) { if (argc != 0) { errx(EXIT_USAGE, "encountered extraneous arguments " "starting with %s", argv[0]); } } else { i2cadm_io_parse_data(&req, argc, argv); } if (req.io_rlen != 0) { req.io_rdata = calloc(req.io_rlen, sizeof (uint8_t)); if (req.io_rdata == NULL) { err(EXIT_FAILURE, "failed to allocate %u bytes for " "request read buffer", req.io_rlen); } if (output != NULL) { ofd = open(output, O_RDWR | O_TRUNC | O_CREAT); if (ofd < 0) { err(EXIT_FAILURE, "failed to open ouput " "file (-o) %s", output); } } } else if (output != NULL) { errx(EXIT_FAILURE, "cannot specify output file -o when no " "data is being read"); } i2cadm_io_init(&req, mode); if (req.io_i2c != NULL) { if (!i2c_io_req_exec(req.io_i2c)) { i2cadm_fatal("failed to execute I2C request"); } } else { if (!smbus_io_req_exec(req.io_smbus)) { i2cadm_fatal("failed to execute SMBus request"); } } if (ofd != -1) { i2cadm_io_write(&req, mode, ofd); (void) close(ofd); } else { i2cadm_io_print(&req, mode); } if (req.io_i2c != NULL) { i2c_io_req_fini(req.io_i2c); } if (req.io_smbus != NULL) { smbus_io_req_fini(req.io_smbus); } free(req.io_wdata); free(req.io_rdata); i2c_port_fini(port); return (EXIT_SUCCESS); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * i2cadm mux related operations. */ #include #include #include #include #include #include "i2cadm.h" static void i2cadm_mux_list_usage(FILE *f) { (void) fprintf(f, "\ti2cadm mux list [-H] [-o field,[...] [-p]] " "[filter]\n"); } static void i2cadm_mux_list_help(const char *fmt, ...) { if (fmt != NULL) { va_list ap; va_start(ap, fmt); vwarnx(fmt, ap); va_end(ap); } (void) fprintf(stderr, "Usage: i2cadm mux list [-H] " "[-o field[,...] [-p]] [filter...]\n\n"); (void) fprintf(stderr, "List multiplexors in the system. Each " "selects a multiplexor based " "on\nits device's name, device " "driver, or the mux's name. When multiple filters are\nspecified, " "they are treated like an OR. It is an error if a filter isn't " "used.\n\n" "\t-H\t\tomit the column header\n" "\t-o field\toutput fields to print\n" "\t-p\t\tparseable output (requires -o)\n"); (void) fprintf(stderr, "\nThe following fields are supported:\n" "\tdevice\t\tthe name of the device that powers the mux\n" "\tnports\t\tthe number of ports on the mux\n" "\tname\t\tthe name of the mux\n" "\tinstance\tthe instance of the driver for the mux\n" "\tpath\t\tthe I2C path of the mux\n"); } typedef enum { I2CADM_MUX_LIST_DEVICE, I2CADM_MUX_LIST_NAME, I2CADM_MUX_LIST_NPORTS, I2CADM_MUX_LIST_INSTANCE, I2CADM_MUX_LIST_PATH } i2cadm_mux_list_otype_t; static boolean_t i2cadm_mux_list_ofmt_cb(ofmt_arg_t *ofarg, char *buf, uint_t buflen) { const i2c_mux_disc_t *disc = ofarg->ofmt_cbarg; size_t len; di_node_t dn; switch (ofarg->ofmt_id) { case I2CADM_MUX_LIST_DEVICE: dn = di_parent_node(i2c_mux_disc_devi(disc)); len = snprintf(buf, buflen, "%s", di_node_name(dn)); break; case I2CADM_MUX_LIST_NAME: len = strlcpy(buf, i2c_mux_disc_name(disc), buflen); break; case I2CADM_MUX_LIST_NPORTS: len = snprintf(buf, buflen, "%u", i2c_mux_disc_nports(disc)); break; case I2CADM_MUX_LIST_INSTANCE: /* * Because a mux exists here, we know our parent instance must * be active and attached. */ dn = di_parent_node(i2c_mux_disc_devi(disc)); len = snprintf(buf, buflen, "%s%d", di_driver_name(dn), di_instance(dn)); break; case I2CADM_MUX_LIST_PATH: len = strlcpy(buf, i2c_mux_disc_path(disc), buflen); break; default: return (B_FALSE); } return (len < buflen); } static const char *i2cadm_mux_list_fields = "device,nports,name,instance,path"; static const ofmt_field_t i2cadm_mux_list_ofmt[] = { { "DEVICE", 12, I2CADM_MUX_LIST_DEVICE, i2cadm_mux_list_ofmt_cb }, { "NAME", 12, I2CADM_MUX_LIST_NAME, i2cadm_mux_list_ofmt_cb }, { "NPORTS", 12, I2CADM_MUX_LIST_NPORTS, i2cadm_mux_list_ofmt_cb }, { "INSTANCE", 16, I2CADM_MUX_LIST_INSTANCE, i2cadm_mux_list_ofmt_cb }, { "PATH", 40, I2CADM_MUX_LIST_PATH, i2cadm_mux_list_ofmt_cb }, { NULL, 0, 0, NULL } }; static int i2cadm_mux_list(int argc, char *argv[]) { int c, ret = EXIT_SUCCESS; uint_t flags = 0; boolean_t parse = B_FALSE; const char *fields = NULL; bool *filts = NULL, print = false; ofmt_status_t oferr; ofmt_handle_t ofmt; const i2c_mux_disc_t *disc; i2c_mux_iter_t *iter; i2c_iter_t iret; while ((c = getopt(argc, argv, ":Ho:p")) != -1) { switch (c) { case 'H': flags |= OFMT_NOHEADER; break; case 'o': fields = optarg; break; case 'p': parse = B_TRUE; flags |= OFMT_PARSABLE; break; case ':': i2cadm_mux_list_help("option -%c requires an " "argument", optopt); exit(EXIT_USAGE); case '?': i2cadm_mux_list_help("unknown option: -%c", optopt); exit(EXIT_USAGE); } } if (parse && fields == NULL) { errx(EXIT_USAGE, "-p requires fields specified with -o"); } if (!parse) { flags |= OFMT_WRAP; } if (fields == NULL) { fields = i2cadm_mux_list_fields; } argc -= optind; argv += optind; if (argc > 0) { filts = calloc(argc, sizeof (bool)); if (filts == NULL) { err(EXIT_FAILURE, "failed to allocate memory for " "filter tracking"); } } oferr = ofmt_open(fields, i2cadm_mux_list_ofmt, flags, 0, &ofmt); ofmt_check(oferr, parse, ofmt, i2cadm_ofmt_errx, warnx); if (!i2c_mux_discover_init(i2cadm.i2c_hdl, &iter)) { i2cadm_fatal("failed to in initialize mux walk"); } while ((iret = i2c_mux_discover_step(iter, &disc)) == I2C_ITER_VALID) { if (argc > 0) { const char *name = i2c_mux_disc_name(disc); di_node_t dn = di_parent_node(i2c_mux_disc_devi(disc)); const char *drv = di_driver_name(dn); const char *pname = di_node_name(dn); bool match = false; for (int i = 0; i < argc; i++) { if (strcmp(argv[i], name) == 0 || strcmp(argv[i], drv) == 0 || strcmp(argv[i], pname) == 0) { match = true; filts[i] = true; } } if (!match) { continue; } } ofmt_print(ofmt, (void *)disc); print = true; } if (iret == I2C_ITER_ERROR) { i2cadm_warn("failed to iterate muxes"); ret = EXIT_FAILURE; } for (int i = 0; i < argc; i++) { if (!filts[i]) { warnx("filter '%s' did not match any muxes", argv[i]); ret = EXIT_FAILURE; } } if (!print && argc == 0) { warnx("no I2C muxes found"); ret = EXIT_FAILURE; } free(filts); ofmt_close(ofmt); return (ret); } static i2cadm_cmdtab_t i2cadm_mux_cmds[] = { { "list", i2cadm_mux_list, i2cadm_mux_list_usage }, }; int i2cadm_mux(int argc, char *argv[]) { return (i2cadm_walk_tab(i2cadm_mux_cmds, ARRAY_SIZE(i2cadm_mux_cmds), argc, argv)); } void i2cadm_mux_usage(FILE *f) { i2cadm_walk_usage(i2cadm_mux_cmds, ARRAY_SIZE(i2cadm_mux_cmds), f); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * i2cadm port related operations. */ #include #include #include #include #include #include #include "i2cadm.h" static void i2cadm_port_map_usage(FILE *f) { (void) fprintf(f, "\ti2cadm port map [-o field,[...] [-H] [-p]] " "\n"); } static void i2cadm_port_map_help(const char *fmt, ...) { if (fmt != NULL) { va_list ap; va_start(ap, fmt); vwarnx(fmt, ap); va_end(ap); } (void) fprintf(stderr, "Usage: i2cadm port map [-o field,[...] [-H] " "[-p]] \n"); (void) fprintf(stderr, "\nPrint port address usage\n\n" "\t-H\t\tomit the column header (requires -o)\n" "\t-o field\toutput fields to print\n" "\t-p\t\tparseable output (requires -o)\n"); (void) fprintf(stderr, "\nThe following fields are supported when " "using -o:\n" "\taddr\t\tthe I2C address\n" "\tcount\t\tthe number of devices using address\n" "\ttype\t\tdescribes how the address is being used\n" "\tmajor\t\tthe major number using a shared address\n" "\tdriver\t\tthe driver name using a shared address\n"); } typedef enum { I2CADM_MAP_TYPE_NONE, I2CADM_MAP_TYPE_LOCAL, I2CADM_MAP_TYPE_DS, I2CADM_MAP_TYPE_SHARED, I2CADM_MAP_TYPE_ERROR } i2cadm_map_type_t; typedef struct i2cadm_map { i2cadm_map_type_t map_type; uint32_t map_count; major_t map_major; char *map_shared; } i2cadm_map_t; typedef struct { major_t mn_major; char *mn_name; } major_to_name_t; int i2cadm_major_to_name_cb(di_node_t node, void *arg) { major_to_name_t *m = arg; if (di_driver_major(node) == m->mn_major) { const char *name = di_driver_name(node); if (name != NULL) { m->mn_name = strdup(name); if (m->mn_name == NULL) { err(EXIT_FAILURE, "failed to allocate memory " "to duplicate driver name for major 0x%x", m->mn_major); } } return (DI_WALK_TERMINATE); } return (DI_WALK_CONTINUE); } /* * Major number to name, the kind of max power way. While we could maybe parse * /etc/name_to_major, which really should be some set of library routines to be * honest, we're instead going to just walk a devinfo snapshot until we we find * a node with a matching major. The thing is, the node is present and a driver * is attached, otherwise it wouldn't have a shared address. */ static char * i2cadm_major_to_name(major_t m) { major_to_name_t arg = { .mn_major = m }; di_node_t root = di_init("/", DINFOSUBTREE); if (root == DI_NODE_NIL) { err(EXIT_FAILURE, "failed to take devinfo snapshot"); } (void) di_walk_node(root, DI_WALK_CLDFIRST, &arg, i2cadm_major_to_name_cb); di_fini(root); return (arg.mn_name); } typedef enum { I2CADM_PORT_MAP_ADDR, I2CADM_PORT_MAP_COUNT, I2CADM_PORT_MAP_TYPE, I2CADM_PORT_MAP_MAJOR, I2CADM_PORT_MAP_DRIVER } i2adm_port_map_otype_t; typedef struct { uint16_t ipm_addr; const i2cadm_map_t *ipm_map; } i2cadm_port_map_ofmt_t; static boolean_t i2cadm_port_map_ofmt_cb(ofmt_arg_t *ofarg, char *buf, uint_t buflen) { i2cadm_port_map_ofmt_t *arg = ofarg->ofmt_cbarg; size_t len; const char *str; switch (ofarg->ofmt_id) { case I2CADM_PORT_MAP_ADDR: len = snprintf(buf, buflen, "%u", arg->ipm_addr); break; case I2CADM_PORT_MAP_COUNT: len = snprintf(buf, buflen, "%u", arg->ipm_map->map_count); break; case I2CADM_PORT_MAP_TYPE: switch (arg->ipm_map->map_type) { case I2CADM_MAP_TYPE_NONE: str = "none"; break; case I2CADM_MAP_TYPE_LOCAL: str = "local"; break; case I2CADM_MAP_TYPE_DS: str = "downstream"; break; case I2CADM_MAP_TYPE_SHARED: str = "shared"; break; case I2CADM_MAP_TYPE_ERROR: str = "error"; break; default: abort(); } len = strlcpy(buf, str, buflen); break; case I2CADM_PORT_MAP_MAJOR: if (arg->ipm_map->map_type == I2CADM_MAP_TYPE_SHARED) { len = snprintf(buf, buflen, "%u", arg->ipm_map->map_major); } else { len = strlcpy(buf, "-", buflen); } break; case I2CADM_PORT_MAP_DRIVER: if (arg->ipm_map->map_type == I2CADM_MAP_TYPE_SHARED) { str = arg->ipm_map->map_shared; if (str == NULL) str = "unknown"; } else { str = "-"; } len = strlcpy(buf, str, buflen); break; default: return (B_FALSE); } return (len < buflen); } static const ofmt_field_t i2cadm_port_map_ofmt[] = { { "ADDR", 8, I2CADM_PORT_MAP_ADDR, i2cadm_port_map_ofmt_cb }, { "COUNT", 8, I2CADM_PORT_MAP_COUNT, i2cadm_port_map_ofmt_cb }, { "TYPE", 16, I2CADM_PORT_MAP_TYPE, i2cadm_port_map_ofmt_cb }, { "MAJOR", 8, I2CADM_PORT_MAP_MAJOR, i2cadm_port_map_ofmt_cb }, { "DRIVER", 16, I2CADM_PORT_MAP_DRIVER, i2cadm_port_map_ofmt_cb }, { NULL, 0, 0, NULL } }; static const char *key = "" "\t- = No Device L = Local Device\n" "\tS = Shared v = Downstream\n" "\t E = Error\n"; static bool i2cadm_port_map_table_cb(void *arg, uint16_t addr) { const i2cadm_map_t *results = arg; bool shared = false; switch (results[addr].map_type) { case I2CADM_MAP_TYPE_NONE: (void) printf("%3s", "-"); break; case I2CADM_MAP_TYPE_LOCAL: (void) printf("%3s", "L"); break; case I2CADM_MAP_TYPE_DS: (void) printf("%2uv", results[addr].map_count); break; case I2CADM_MAP_TYPE_SHARED: shared = true; (void) printf("%2uS", results[addr].map_count); break; case I2CADM_MAP_TYPE_ERROR: (void) printf("%3s", "E"); break; } return (shared); } static void i2cadm_port_map_table_post(void *arg, uint16_t max_addr) { const i2cadm_map_t *results = arg; (void) printf("\nShared Address Owners:\n"); for (uint16_t i = 0; i < max_addr; i++) { if (results[i].map_type != I2CADM_MAP_TYPE_SHARED) continue; const char *name = results[i].map_shared; if (name == NULL) name = "unknown"; if (max_addr > UINT8_MAX) { (void) printf("0x%03x: %s (%u)\n", i, name, results[i].map_major); } else { (void) printf("0x%02x: %s (%u)\n", i, name, results[i].map_major); } } } static int i2cadm_port_map(int argc, char *argv[]) { int c; i2c_port_t *port; i2c_port_map_t *map; uint16_t max_addr = 1 << 7; i2cadm_map_t *results; boolean_t parse = B_FALSE; uint_t flags = 0; const char *fields = NULL; ofmt_status_t oferr; ofmt_handle_t ofmt; while ((c = getopt(argc, argv, ":Ho:p")) != -1) { switch (c) { case 'H': flags |= OFMT_NOHEADER; break; case 'o': fields = optarg; break; case 'p': parse = B_TRUE; flags |= OFMT_PARSABLE; break; case ':': i2cadm_port_map_help("option -%c requires an argument", optopt); exit(EXIT_USAGE); case '?': i2cadm_port_map_help("unknown option: -%c", optopt); exit(EXIT_USAGE); } } argv += optind; argc -= optind; if (argc == 0) { errx(EXIT_USAGE, "missing required port"); } else if (argc > 1) { errx(EXIT_USAGE, "encountered extraneous arguments starting " "with %s", argv[1]); } if (!i2c_port_init_by_path(i2cadm.i2c_hdl, argv[0], &port)) { i2cadm_fatal("failed to parse port path %s", argv[0]); } if (!i2c_port_map_snap(port, &map)) { i2cadm_fatal("failed to get port map"); } if (parse && fields == NULL) { errx(EXIT_USAGE, "-p requires fields specified with -o"); } if (flags != 0 && fields == NULL) { errx(EXIT_USAGE, "-H can only be used with -o"); } if (fields != NULL) { if (!parse) { flags |= OFMT_WRAP; } oferr = ofmt_open(fields, i2cadm_port_map_ofmt, flags, 0, &ofmt); ofmt_check(oferr, parse, ofmt, i2cadm_ofmt_errx, warnx); } results = calloc(max_addr, sizeof (i2cadm_map_t)); if (results == NULL) { err(EXIT_FAILURE, "failed to allocate port map results " "tracking structure"); } for (uint16_t i = 0; i < max_addr; i++) { i2c_addr_t addr = { I2C_ADDR_7BIT, i }; bool ds; uint32_t ndevs; major_t major; if (!i2c_port_map_addr_info(map, &addr, &ndevs, &ds, &major)) { results[i].map_type = I2CADM_MAP_TYPE_ERROR; continue; } if (ndevs == 0) { results[i].map_type = I2CADM_MAP_TYPE_NONE; continue; } results[i].map_count = ndevs; if (major != DDI_MAJOR_T_NONE) { results[i].map_type = I2CADM_MAP_TYPE_SHARED; results[i].map_shared = i2cadm_major_to_name(major); results[i].map_major = major; } else if (ds) { results[i].map_type = I2CADM_MAP_TYPE_DS; } else { VERIFY3U(ndevs, ==, 1); results[i].map_type = I2CADM_MAP_TYPE_LOCAL; } } if (fields == NULL) { i2cadm_table_t table = { .table_port = argv[0], .table_key = key, .table_msg = "Address map for", .table_max = max_addr, .table_cb = i2cadm_port_map_table_cb, .table_post = i2cadm_port_map_table_post }; i2cadm_print_table(&table, results); } else { for (uint16_t i = 0; i < max_addr; i++) { i2cadm_port_map_ofmt_t arg = { .ipm_addr = i, .ipm_map = &results[i] }; ofmt_print(ofmt, &arg); } ofmt_close(ofmt); } for (uint16_t i = 0; i < max_addr; i++) { free(results[i].map_shared); } free(results); i2c_port_map_free(map); i2c_port_fini(port); return (0); } static void i2cadm_port_list_usage(FILE *f) { (void) fprintf(f, "\ti2cadm port list [-H] [-o field,[...] [-p]] " "[filter]\n"); } typedef enum { I2CADM_PORT_LIST_PATH, I2CADM_PORT_LIST_TYPE, I2CADM_PORT_LIST_NAME, I2CADM_PORT_LIST_NUM, I2CADM_PORT_LIST_NDEVS, I2CADM_PORT_LIST_TDEVS } i2cadm_port_list_otype_tt; typedef struct i2cadm_port_list_ofmt { i2c_port_t *ipl_port; i2c_port_map_t *ipl_map; } i2cadm_port_list_ofmt_t; static void i2cadm_port_list_help(const char *fmt, ...) { if (fmt != NULL) { va_list ap; va_start(ap, fmt); vwarnx(fmt, ap); va_end(ap); } (void) fprintf(stderr, "Usage: i2cadm port list [-H] " "[-o field[,...] [-p]] [filter...]\n\n"); (void) fprintf(stderr, "List I2C ports in the system. Each " "selects ports based upon its\ntype, name, or the I2C path. " "Multiple filters are treated as an OR. It is an\nerror if a " "filter isn't used.\n\n" "\t-H\t\tomit the column header\n" "\t-o field\toutput fields to print\n" "\t-p\t\tparseable output (requires -o)\n"); (void) fprintf(stderr, "\nThe following fields are supported:\n" "\tpath\t\tthe I2C path of the port\n" "\ttype\t\tthe type of I2C port: controller or multiplexor\n" "\tname\t\tthe port's name\n" "\tportno\t\tthe system's port number (zero based)\n" "\tndevs\t\tthe number of device's directly attached to this port\n" "\ttdevs\t\tthe total number of devices under this port\n"); } static boolean_t i2cadm_port_list_ofmt_cb(ofmt_arg_t *ofarg, char *buf, uint_t buflen) { uint32_t local, ds; i2cadm_port_list_ofmt_t *arg = ofarg->ofmt_cbarg; size_t len; switch (ofarg->ofmt_id) { case I2CADM_PORT_LIST_PATH: len = strlcpy(buf, i2c_port_path(arg->ipl_port), buflen); break; case I2CADM_PORT_LIST_TYPE: switch (i2c_port_type(arg->ipl_port)) { case I2C_PORT_TYPE_CTRL: len = strlcat(buf, "controller", buflen); break; case I2C_PORT_TYPE_MUX: len = strlcat(buf, "multiplexor", buflen); break; default: len = snprintf(buf, buflen, "unknown: 0x%x", i2c_port_type(arg->ipl_port)); break; } break; case I2CADM_PORT_LIST_NAME: len = strlcpy(buf, i2c_port_name(arg->ipl_port), buflen); break; case I2CADM_PORT_LIST_NUM: len = snprintf(buf, buflen, "%u", i2c_port_portno(arg->ipl_port)); break; case I2CADM_PORT_LIST_NDEVS: i2c_port_map_ndevs(arg->ipl_map, &local, NULL); len = snprintf(buf, buflen, "%u", local); break; case I2CADM_PORT_LIST_TDEVS: i2c_port_map_ndevs(arg->ipl_map, &local, &ds); len = snprintf(buf, buflen, "%u", local + ds); break; default: return (B_FALSE); } return (len < buflen); } static const char *i2cadm_port_list_fields = "path,type,portno,ndevs,tdevs"; static const ofmt_field_t i2cadm_port_list_ofmt[] = { { "NAME", 12, I2CADM_PORT_LIST_NAME, i2cadm_port_list_ofmt_cb }, { "TYPE", 14, I2CADM_PORT_LIST_TYPE, i2cadm_port_list_ofmt_cb }, { "PORTNO", 8, I2CADM_PORT_LIST_NUM, i2cadm_port_list_ofmt_cb }, { "NDEVS", 8, I2CADM_PORT_LIST_NDEVS, i2cadm_port_list_ofmt_cb }, { "TDEVS", 8, I2CADM_PORT_LIST_TDEVS, i2cadm_port_list_ofmt_cb }, { "PATH", 32, I2CADM_PORT_LIST_PATH, i2cadm_port_list_ofmt_cb }, { NULL, 0, 0, NULL } }; /* * We accept the following filters for matching ports: * * - Matching on the ports name * - Matching on the port's type * - Matching on a portion of the port's path */ static bool i2cadm_port_list_filt(const i2cadm_port_list_ofmt_t *arg, int nfilts, char **filts, bool *used) { bool match = false; const char *type, *name, *path; if (nfilts == 0) { return (true); } name = i2c_port_name(arg->ipl_port); path = i2c_port_path(arg->ipl_port); size_t pathlen = strlen(path); if (i2c_port_type(arg->ipl_port) == I2C_PORT_TYPE_CTRL) { type = "controller"; } else { type = "multiplexor"; } for (int i = 0; i < nfilts; i++) { if (strcmp(filts[i], name) == 0) { used[i] = true; match = true; continue; } if (strcmp(filts[i], type) == 0) { used[i] = true; match = true; continue; } if (strcmp(filts[i], path) == 0) { used[i] = true; match = true; continue; } size_t len = strlen(filts[i]); if (len < pathlen && strncmp(path, filts[i], len) == 0) { used[i] = true; match = true; continue; } } return (match); } static int i2cadm_port_list(int argc, char *argv[]) { int c, ret = EXIT_SUCCESS; uint_t flags = 0; boolean_t parse = B_FALSE; const char *fields = NULL; bool *filts = NULL, print = false; ofmt_status_t oferr; ofmt_handle_t ofmt; i2c_port_iter_t *iter; i2c_iter_t iret; const i2c_port_disc_t *disc; while ((c = getopt(argc, argv, ":Ho:p")) != -1) { switch (c) { case 'H': flags |= OFMT_NOHEADER; break; case 'o': fields = optarg; break; case 'p': parse = B_TRUE; flags |= OFMT_PARSABLE; break; case ':': i2cadm_port_list_help("option -%c requires an " "argument", optopt); exit(EXIT_USAGE); case '?': i2cadm_port_list_help("unknown option: -%c", optopt); exit(EXIT_USAGE); } } if (parse && fields == NULL) { errx(EXIT_USAGE, "-p requires fields specified with -o"); } if (!parse) { flags |= OFMT_WRAP; } if (fields == NULL) { fields = i2cadm_port_list_fields; } argc -= optind; argv += optind; if (argc > 0) { filts = calloc(argc, sizeof (bool)); if (filts == NULL) { err(EXIT_FAILURE, "failed to allocate memory for " "filter tracking"); } } oferr = ofmt_open(fields, i2cadm_port_list_ofmt, flags, 0, &ofmt); ofmt_check(oferr, parse, ofmt, i2cadm_ofmt_errx, warnx); if (!i2c_port_discover_init(i2cadm.i2c_hdl, &iter)) { i2cadm_fatal("failed to in initialize port discovery"); } while ((iret = i2c_port_discover_step(iter, &disc)) == I2C_ITER_VALID) { i2cadm_port_list_ofmt_t arg; if (!i2c_port_init(i2cadm.i2c_hdl, i2c_port_disc_devi(disc), &arg.ipl_port)) { i2cadm_warn("failed to initialize port %s", i2c_port_disc_path(disc)); continue; } if (!i2c_port_map_snap(arg.ipl_port, &arg.ipl_map)) { i2cadm_warn("failed to get port map for %s", i2c_port_disc_path(disc)); i2c_port_fini(arg.ipl_port); continue; } if (i2cadm_port_list_filt(&arg, argc, argv, filts)) { ofmt_print(ofmt, &arg); print = true; } i2c_port_map_free(arg.ipl_map); i2c_port_fini(arg.ipl_port); } if (iret == I2C_ITER_ERROR) { i2cadm_warn("failed to discover ports"); ret = EXIT_FAILURE; } for (int i = 0; i < argc; i++) { if (!filts[i]) { warnx("filter '%s' did not match any ports", argv[i]); ret = EXIT_FAILURE; } } if (!print && argc == 0) { warnx("no I2C ports found"); ret = EXIT_FAILURE; } i2c_port_discover_fini(iter); return (ret); } static i2cadm_cmdtab_t i2cadm_port_cmds[] = { { "list", i2cadm_port_list, i2cadm_port_list_usage }, { "map", i2cadm_port_map, i2cadm_port_map_usage }, }; int i2cadm_port(int argc, char *argv[]) { return (i2cadm_walk_tab(i2cadm_port_cmds, ARRAY_SIZE(i2cadm_port_cmds), argc, argv)); } void i2cadm_port_usage(FILE *f) { i2cadm_walk_usage(i2cadm_port_cmds, ARRAY_SIZE(i2cadm_port_cmds), f); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2025 Oxide Computer Company */ /* * i2cadm scan -- scan a single port for devices. By default all devices are * scanned under the port, unless a specific set of devices is specified. */ #include #include #include #include #include #include #include "i2cadm.h" void i2cadm_scan_usage(FILE *f) { (void) fprintf(f, "\ti2cadm scan [-d dev] [-o field,[...] [-H] [-p]] " "\n"); } static void i2cadm_scan_help(const char *fmt, ...) { if (fmt != NULL) { va_list ap; va_start(ap, fmt); vwarnx(fmt, ap); va_end(ap); } (void) fprintf(stderr, "Usage: i2cadm scan [-d dev] [-o field,[...] " "[-H] [-p]] \n"); (void) fprintf(stderr, "\nScan for I2C devices\n\n" "\t-d dev\t\tonly scan device address dev, can be specified " "multiple\n\t\t\ttimes\n" "\t-H\t\tomit the column header (requires -o)\n" "\t-o field\toutput fields to print\n" "\t-p\t\tparseable output (requires -o)\n"); (void) fprintf(stderr, "\nThe following fields are supported when " "using -o:\n" "\taddr\t\tthe I2C address\n" "\tresult\t\tthe address scan result\n" "\terror\t\tthe error message if an error occurred\n"); } typedef enum { I2CADM_BUS_SCAN_UNKNOWN = 0, I2CADM_BUS_SCAN_FOUND, I2CADM_BUS_SCAN_NO_DEV, I2CADM_BUS_SCAN_RESERVED, I2CADM_BUS_SCAN_TIMEOUT, I2CADM_BUS_SCAN_ERROR, I2CADM_BUS_SCAN_SKIPPED } i2cadm_scan_result_t; typedef struct { i2c_addr_t scan_addr; i2cadm_scan_result_t scan_res; char *scan_error; } i2cadm_scan_t; static void i2cadm_scan_error(i2c_hdl_t *hdl, i2cadm_scan_t *scan) { i2c_err_t err = i2c_err(hdl); if (err != I2C_ERR_CONTROLLER) { scan->scan_res = I2CADM_BUS_SCAN_ERROR; scan->scan_error = strdup(i2c_errmsg(hdl)); if (scan->scan_error == NULL) { scan->scan_error = "libi2c error; but failed to " "duplicate libi2c error message"; } return; } switch (i2c_ctrl_err(hdl)) { case I2C_CTRL_E_ADDR_NACK: case I2C_CTRL_E_DATA_NACK: case I2C_CTRL_E_NACK: scan->scan_res = I2CADM_BUS_SCAN_NO_DEV; break; case I2C_CTRL_E_REQ_TO: scan->scan_res = I2CADM_BUS_SCAN_TIMEOUT; break; default: scan->scan_res = I2CADM_BUS_SCAN_ERROR; scan->scan_error = strdup(i2c_errmsg(hdl)); if (scan->scan_error == NULL) { scan->scan_error = "i2c controller error; but " "failed to duplicate libi2c error message"; } break; } } /* * One does not simply scan an i2c device. In essence, we're trying to perform * some I/O such that it will ack an address, but without causing the device to * wreak havoc. Given the plethora of devices that are out there, this may not * be possible. Safety cannot be guaranteed by construction. * * We basically do a one byte read from the device. Most devices will respond to * this. The alternative that some other tools have done is to try to perform an * SMBus Quick action. */ static void i2cadm_scan_one(i2c_hdl_t *hdl, i2c_port_t *port, i2cadm_scan_t *scan) { i2c_io_req_t *req; uint8_t data = 0x77; if (!i2c_io_req_init(port, &req)) { i2cadm_fatal("failed to initialize I/O request"); } if (!i2c_io_req_set_addr(req, &scan->scan_addr)) { i2cadm_fatal("failed to set scan address"); } if (!i2c_io_req_set_receive_buf(req, &data, sizeof (data))) { i2cadm_fatal("failed to set receive buffer"); } if (i2c_io_req_exec(req)) { scan->scan_res = I2CADM_BUS_SCAN_FOUND; } else { i2cadm_scan_error(hdl, scan); } i2c_io_req_fini(req); } typedef enum { I2CADM_SCAN_ADDR, I2CADM_SCAN_RESULT, I2CADM_SCAN_ERROR } i2cadm_scan_otype_t; static boolean_t i2cadm_scan_ofmt_cb(ofmt_arg_t *ofarg, char *buf, uint_t buflen) { const i2cadm_scan_t *scan = ofarg->ofmt_cbarg; const char *str; size_t len; switch (ofarg->ofmt_id) { case I2CADM_SCAN_ADDR: len = snprintf(buf, buflen, "0x%x", scan->scan_addr.ia_addr); break; case I2CADM_SCAN_RESULT: switch (scan->scan_res) { case I2CADM_BUS_SCAN_FOUND: str = "found"; break; case I2CADM_BUS_SCAN_NO_DEV: str = "missing"; break; case I2CADM_BUS_SCAN_RESERVED: str = "reserved"; break; case I2CADM_BUS_SCAN_TIMEOUT: str = "timeout"; break; case I2CADM_BUS_SCAN_ERROR: str = "error"; break; case I2CADM_BUS_SCAN_SKIPPED: str = "skipped"; break; default: str = "unknown"; break; } len = strlcpy(buf, str, buflen); break; case I2CADM_SCAN_ERROR: if (scan->scan_error != NULL) { len = strlcpy(buf, scan->scan_error, buflen); } else { len = strlcpy(buf, "-", buflen); } break; default: return (B_FALSE); } return (len < buflen); } static const ofmt_field_t i2cadm_scan_ofmt[] = { { "ADDR", 8, I2CADM_SCAN_ADDR, i2cadm_scan_ofmt_cb }, { "RESULT", 16, I2CADM_SCAN_RESULT, i2cadm_scan_ofmt_cb }, { "ERROR", 40, I2CADM_SCAN_ERROR, i2cadm_scan_ofmt_cb }, { NULL, 0, 0, NULL } }; /* * We can fit 16 devices on a line. */ #define SCAN_DEVS_PER_LINE 0x10 static const char *key = "" "\t- = No Device @ = Device Found\n" "\tR = Reserved S = Skipped\n" "\tX = Timed Out Err = Error\n"; static bool i2cadm_scan_table_cb(void *arg, uint16_t i) { const i2cadm_scan_t *results = arg; const char *msgs[] = { "???", "@", "-", "R", "X", "Err", "S" }; (void) printf("%3s", msgs[results[i].scan_res]); return (results[i].scan_res == I2CADM_BUS_SCAN_ERROR); } static void i2cadm_scan_table_post(void *arg, uint16_t max_addr) { const i2cadm_scan_t *results = arg; (void) printf("\nErrors\n"); for (uint16_t i = 0; i < max_addr; i++) { if (results[i].scan_res != I2CADM_BUS_SCAN_ERROR) continue; if (max_addr > UINT8_MAX) { (void) printf("0x%03x: %s\n", i, results[i].scan_error); } else { (void) printf("0x%02x: %s\n", i, results[i].scan_error); } } } int i2cadm_scan(int argc, char *argv[]) { int c; i2c_port_t *port; bool ten_bit = false; uint16_t max_addr = 1 << 7; i2cadm_scan_t *results; uint16_t *dev_addrs = NULL; size_t naddrs = 0, nalloc = 0; boolean_t parse = B_FALSE; uint_t flags = 0; const char *fields = NULL; ofmt_status_t oferr; ofmt_handle_t ofmt; /* * In the future we should consider -T for 10-bit addressing. */ while ((c = getopt(argc, argv, ":d:Ho:p")) != -1) { switch (c) { case 'd': { if (naddrs == nalloc) { nalloc += 8; dev_addrs = recallocarray(dev_addrs, naddrs, nalloc, sizeof (uint16_t)); if (dev_addrs == NULL) { err(EXIT_FAILURE, "failed to allocate " "memory for %zu I2C addresses", nalloc); } } const char *err; long long l = strtonumx(optarg, 0, max_addr - 1, &err, 0); if (err != NULL) { errx(EXIT_FAILURE, "invalid device address %s: " "address is %s", optarg, err); } dev_addrs[naddrs] = (uint16_t)l; naddrs++; break; } case 'H': flags |= OFMT_NOHEADER; break; case 'o': fields = optarg; break; case 'p': parse = B_TRUE; flags |= OFMT_PARSABLE; break; case ':': i2cadm_scan_help("option -%c requires an argument", optopt); exit(EXIT_USAGE); case '?': i2cadm_scan_help("unknown option: -%c", optopt); exit(EXIT_USAGE); } } argv += optind; argc -= optind; if (argc == 0) { errx(EXIT_USAGE, "missing required port to scan"); } else if (argc > 1) { errx(EXIT_USAGE, "encountered extraneous arguments starting " "with %s", argv[1]); } if (parse && fields == NULL) { errx(EXIT_USAGE, "-p requires fields specified with -o"); } if (flags != 0 && fields == NULL) { errx(EXIT_USAGE, "-H can only be used with -o"); } if (fields != NULL) { if (!parse) { flags |= OFMT_WRAP; } oferr = ofmt_open(fields, i2cadm_scan_ofmt, flags, 0, &ofmt); ofmt_check(oferr, parse, ofmt, i2cadm_ofmt_errx, warnx); } if (!i2c_port_init_by_path(i2cadm.i2c_hdl, argv[0], &port)) { i2cadm_fatal("failed to parse port path %s", argv[0]); } results = calloc(max_addr, sizeof (i2cadm_scan_t)); if (results == NULL) { err(EXIT_FAILURE, "failed to allocate scan results tracking " "structure"); } /* * If we have a specific device list, then mark everything skipped and * come back and mark the specific instances we care about as things to * check. */ if (dev_addrs != NULL) { for (uint16_t i = 0; i < max_addr; i++) { results[i].scan_res = I2CADM_BUS_SCAN_SKIPPED; } for (uint16_t i = 0; i < naddrs; i++) { results[dev_addrs[i]].scan_res = I2CADM_BUS_SCAN_UNKNOWN; } } for (uint16_t i = 0; i < max_addr; i++) { i2cadm_scan_t *scan = &results[i]; scan->scan_addr.ia_type = ten_bit ? I2C_ADDR_10BIT : I2C_ADDR_7BIT; scan->scan_addr.ia_addr = i; if (scan->scan_res == I2CADM_BUS_SCAN_SKIPPED) continue; /* * Determine if this is a reserved address or not. */ if (i2c_addr_reserved(&scan->scan_addr)) { scan->scan_res = I2CADM_BUS_SCAN_RESERVED; continue; } i2cadm_scan_one(i2cadm.i2c_hdl, port, scan); } if (fields == NULL) { i2cadm_table_t table = { .table_port = argv[0], .table_key = key, .table_msg = "Device scan on", .table_max = max_addr, .table_cb = i2cadm_scan_table_cb, .table_post = i2cadm_scan_table_post }; i2cadm_print_table(&table, results); } else { for (uint16_t i = 0; i < max_addr; i++) { ofmt_print(ofmt, &results[i]); } ofmt_close(ofmt); } for (uint16_t i = 0; i < max_addr; i++) { free(results[i].scan_error); } free(results); i2c_port_fini(port); return (EXIT_SUCCESS); }