1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2025 Oxide Computer Company
14 */
15
16 /*
17 * i2cadm port related operations.
18 */
19
20 #include <stdarg.h>
21 #include <string.h>
22 #include <err.h>
23 #include <sys/sysmacros.h>
24 #include <ofmt.h>
25 #include <sys/debug.h>
26
27 #include "i2cadm.h"
28
29 static void
i2cadm_port_map_usage(FILE * f)30 i2cadm_port_map_usage(FILE *f)
31 {
32 (void) fprintf(f, "\ti2cadm port map [-o field,[...] [-H] [-p]] "
33 "<port>\n");
34 }
35
36 static void
i2cadm_port_map_help(const char * fmt,...)37 i2cadm_port_map_help(const char *fmt, ...)
38 {
39 if (fmt != NULL) {
40 va_list ap;
41
42 va_start(ap, fmt);
43 vwarnx(fmt, ap);
44 va_end(ap);
45 }
46
47 (void) fprintf(stderr, "Usage: i2cadm port map [-o field,[...] [-H] "
48 "[-p]] <port>\n");
49 (void) fprintf(stderr, "\nPrint port address usage\n\n"
50 "\t-H\t\tomit the column header (requires -o)\n"
51 "\t-o field\toutput fields to print\n"
52 "\t-p\t\tparseable output (requires -o)\n");
53 (void) fprintf(stderr, "\nThe following fields are supported when "
54 "using -o:\n"
55 "\taddr\t\tthe I2C address\n"
56 "\tcount\t\tthe number of devices using address\n"
57 "\ttype\t\tdescribes how the address is being used\n"
58 "\tmajor\t\tthe major number using a shared address\n"
59 "\tdriver\t\tthe driver name using a shared address\n");
60 }
61
62 typedef enum {
63 I2CADM_MAP_TYPE_NONE,
64 I2CADM_MAP_TYPE_LOCAL,
65 I2CADM_MAP_TYPE_DS,
66 I2CADM_MAP_TYPE_SHARED,
67 I2CADM_MAP_TYPE_ERROR
68 } i2cadm_map_type_t;
69
70 typedef struct i2cadm_map {
71 i2cadm_map_type_t map_type;
72 uint32_t map_count;
73 major_t map_major;
74 char *map_shared;
75 } i2cadm_map_t;
76
77 typedef struct {
78 major_t mn_major;
79 char *mn_name;
80 } major_to_name_t;
81
82 int
i2cadm_major_to_name_cb(di_node_t node,void * arg)83 i2cadm_major_to_name_cb(di_node_t node, void *arg)
84 {
85 major_to_name_t *m = arg;
86 if (di_driver_major(node) == m->mn_major) {
87 const char *name = di_driver_name(node);
88 if (name != NULL) {
89 m->mn_name = strdup(name);
90 if (m->mn_name == NULL) {
91 err(EXIT_FAILURE, "failed to allocate memory "
92 "to duplicate driver name for major 0x%x",
93 m->mn_major);
94 }
95 }
96 return (DI_WALK_TERMINATE);
97 }
98 return (DI_WALK_CONTINUE);
99 }
100
101 /*
102 * Major number to name, the kind of max power way. While we could maybe parse
103 * /etc/name_to_major, which really should be some set of library routines to be
104 * honest, we're instead going to just walk a devinfo snapshot until we we find
105 * a node with a matching major. The thing is, the node is present and a driver
106 * is attached, otherwise it wouldn't have a shared address.
107 */
108 static char *
i2cadm_major_to_name(major_t m)109 i2cadm_major_to_name(major_t m)
110 {
111 major_to_name_t arg = { .mn_major = m };
112 di_node_t root = di_init("/", DINFOSUBTREE);
113 if (root == DI_NODE_NIL) {
114 err(EXIT_FAILURE, "failed to take devinfo snapshot");
115 }
116
117 (void) di_walk_node(root, DI_WALK_CLDFIRST, &arg,
118 i2cadm_major_to_name_cb);
119
120 di_fini(root);
121 return (arg.mn_name);
122 }
123
124 typedef enum {
125 I2CADM_PORT_MAP_ADDR,
126 I2CADM_PORT_MAP_COUNT,
127 I2CADM_PORT_MAP_TYPE,
128 I2CADM_PORT_MAP_MAJOR,
129 I2CADM_PORT_MAP_DRIVER
130 } i2adm_port_map_otype_t;
131
132 typedef struct {
133 uint16_t ipm_addr;
134 const i2cadm_map_t *ipm_map;
135 } i2cadm_port_map_ofmt_t;
136
137 static boolean_t
i2cadm_port_map_ofmt_cb(ofmt_arg_t * ofarg,char * buf,uint_t buflen)138 i2cadm_port_map_ofmt_cb(ofmt_arg_t *ofarg, char *buf, uint_t buflen)
139 {
140 i2cadm_port_map_ofmt_t *arg = ofarg->ofmt_cbarg;
141 size_t len;
142 const char *str;
143
144 switch (ofarg->ofmt_id) {
145 case I2CADM_PORT_MAP_ADDR:
146 len = snprintf(buf, buflen, "%u", arg->ipm_addr);
147 break;
148 case I2CADM_PORT_MAP_COUNT:
149 len = snprintf(buf, buflen, "%u", arg->ipm_map->map_count);
150 break;
151 case I2CADM_PORT_MAP_TYPE:
152 switch (arg->ipm_map->map_type) {
153 case I2CADM_MAP_TYPE_NONE:
154 str = "none";
155 break;
156 case I2CADM_MAP_TYPE_LOCAL:
157 str = "local";
158 break;
159 case I2CADM_MAP_TYPE_DS:
160 str = "downstream";
161 break;
162 case I2CADM_MAP_TYPE_SHARED:
163 str = "shared";
164 break;
165 case I2CADM_MAP_TYPE_ERROR:
166 str = "error";
167 break;
168 default:
169 abort();
170 }
171 len = strlcpy(buf, str, buflen);
172 break;
173 case I2CADM_PORT_MAP_MAJOR:
174 if (arg->ipm_map->map_type == I2CADM_MAP_TYPE_SHARED) {
175 len = snprintf(buf, buflen, "%u",
176 arg->ipm_map->map_major);
177 } else {
178 len = strlcpy(buf, "-", buflen);
179 }
180 break;
181 case I2CADM_PORT_MAP_DRIVER:
182 if (arg->ipm_map->map_type == I2CADM_MAP_TYPE_SHARED) {
183 str = arg->ipm_map->map_shared;
184 if (str == NULL)
185 str = "unknown";
186 } else {
187 str = "-";
188 }
189 len = strlcpy(buf, str, buflen);
190 break;
191 default:
192 return (B_FALSE);
193 }
194
195 return (len < buflen);
196 }
197
198 static const ofmt_field_t i2cadm_port_map_ofmt[] = {
199 { "ADDR", 8, I2CADM_PORT_MAP_ADDR, i2cadm_port_map_ofmt_cb },
200 { "COUNT", 8, I2CADM_PORT_MAP_COUNT, i2cadm_port_map_ofmt_cb },
201 { "TYPE", 16, I2CADM_PORT_MAP_TYPE, i2cadm_port_map_ofmt_cb },
202 { "MAJOR", 8, I2CADM_PORT_MAP_MAJOR, i2cadm_port_map_ofmt_cb },
203 { "DRIVER", 16, I2CADM_PORT_MAP_DRIVER, i2cadm_port_map_ofmt_cb },
204 { NULL, 0, 0, NULL }
205 };
206
207 static const char *key = ""
208 "\t- = No Device L = Local Device\n"
209 "\tS = Shared v = Downstream\n"
210 "\t E = Error\n";
211
212 static bool
i2cadm_port_map_table_cb(void * arg,uint16_t addr)213 i2cadm_port_map_table_cb(void *arg, uint16_t addr)
214 {
215 const i2cadm_map_t *results = arg;
216 bool shared = false;
217
218 switch (results[addr].map_type) {
219 case I2CADM_MAP_TYPE_NONE:
220 (void) printf("%3s", "-");
221 break;
222 case I2CADM_MAP_TYPE_LOCAL:
223 (void) printf("%3s", "L");
224 break;
225 case I2CADM_MAP_TYPE_DS:
226 (void) printf("%2uv", results[addr].map_count);
227 break;
228 case I2CADM_MAP_TYPE_SHARED:
229 shared = true;
230 (void) printf("%2uS", results[addr].map_count);
231 break;
232 case I2CADM_MAP_TYPE_ERROR:
233 (void) printf("%3s", "E");
234 break;
235 }
236
237 return (shared);
238 }
239
240 static void
i2cadm_port_map_table_post(void * arg,uint16_t max_addr)241 i2cadm_port_map_table_post(void *arg, uint16_t max_addr)
242 {
243 const i2cadm_map_t *results = arg;
244 (void) printf("\nShared Address Owners:\n");
245
246 for (uint16_t i = 0; i < max_addr; i++) {
247 if (results[i].map_type != I2CADM_MAP_TYPE_SHARED)
248 continue;
249
250 const char *name = results[i].map_shared;
251 if (name == NULL)
252 name = "unknown";
253 if (max_addr > UINT8_MAX) {
254 (void) printf("0x%03x: %s (%u)\n", i, name,
255 results[i].map_major);
256 } else {
257 (void) printf("0x%02x: %s (%u)\n", i, name,
258 results[i].map_major);
259 }
260 }
261 }
262
263 static int
i2cadm_port_map(int argc,char * argv[])264 i2cadm_port_map(int argc, char *argv[])
265 {
266 int c;
267 i2c_port_t *port;
268 i2c_port_map_t *map;
269 uint16_t max_addr = 1 << 7;
270 i2cadm_map_t *results;
271 boolean_t parse = B_FALSE;
272 uint_t flags = 0;
273 const char *fields = NULL;
274 ofmt_status_t oferr;
275 ofmt_handle_t ofmt;
276
277 while ((c = getopt(argc, argv, ":Ho:p")) != -1) {
278 switch (c) {
279 case 'H':
280 flags |= OFMT_NOHEADER;
281 break;
282 case 'o':
283 fields = optarg;
284 break;
285 case 'p':
286 parse = B_TRUE;
287 flags |= OFMT_PARSABLE;
288 break;
289 case ':':
290 i2cadm_port_map_help("option -%c requires an argument",
291 optopt);
292 exit(EXIT_USAGE);
293 case '?':
294 i2cadm_port_map_help("unknown option: -%c", optopt);
295 exit(EXIT_USAGE);
296 }
297 }
298
299 argv += optind;
300 argc -= optind;
301 if (argc == 0) {
302 errx(EXIT_USAGE, "missing required port");
303 } else if (argc > 1) {
304 errx(EXIT_USAGE, "encountered extraneous arguments starting "
305 "with %s", argv[1]);
306 }
307
308 if (!i2c_port_init_by_path(i2cadm.i2c_hdl, argv[0], &port)) {
309 i2cadm_fatal("failed to parse port path %s", argv[0]);
310 }
311
312 if (!i2c_port_map_snap(port, &map)) {
313 i2cadm_fatal("failed to get port map");
314 }
315
316 if (parse && fields == NULL) {
317 errx(EXIT_USAGE, "-p requires fields specified with -o");
318 }
319
320 if (flags != 0 && fields == NULL) {
321 errx(EXIT_USAGE, "-H can only be used with -o");
322 }
323
324 if (fields != NULL) {
325 if (!parse) {
326 flags |= OFMT_WRAP;
327 }
328
329 oferr = ofmt_open(fields, i2cadm_port_map_ofmt, flags, 0,
330 &ofmt);
331 ofmt_check(oferr, parse, ofmt, i2cadm_ofmt_errx, warnx);
332 }
333
334 results = calloc(max_addr, sizeof (i2cadm_map_t));
335 if (results == NULL) {
336 err(EXIT_FAILURE, "failed to allocate port map results "
337 "tracking structure");
338 }
339
340 for (uint16_t i = 0; i < max_addr; i++) {
341 i2c_addr_t addr = { I2C_ADDR_7BIT, i };
342 bool ds;
343 uint32_t ndevs;
344 major_t major;
345
346 if (!i2c_port_map_addr_info(map, &addr, &ndevs, &ds, &major)) {
347 results[i].map_type = I2CADM_MAP_TYPE_ERROR;
348 continue;
349 }
350
351 if (ndevs == 0) {
352 results[i].map_type = I2CADM_MAP_TYPE_NONE;
353 continue;
354 }
355
356 results[i].map_count = ndevs;
357 if (major != DDI_MAJOR_T_NONE) {
358 results[i].map_type = I2CADM_MAP_TYPE_SHARED;
359 results[i].map_shared = i2cadm_major_to_name(major);
360 results[i].map_major = major;
361 } else if (ds) {
362 results[i].map_type = I2CADM_MAP_TYPE_DS;
363 } else {
364 VERIFY3U(ndevs, ==, 1);
365 results[i].map_type = I2CADM_MAP_TYPE_LOCAL;
366 }
367 }
368
369 if (fields == NULL) {
370 i2cadm_table_t table = {
371 .table_port = argv[0],
372 .table_key = key,
373 .table_msg = "Address map for",
374 .table_max = max_addr,
375 .table_cb = i2cadm_port_map_table_cb,
376 .table_post = i2cadm_port_map_table_post
377 };
378 i2cadm_print_table(&table, results);
379 } else {
380 for (uint16_t i = 0; i < max_addr; i++) {
381 i2cadm_port_map_ofmt_t arg = {
382 .ipm_addr = i,
383 .ipm_map = &results[i]
384 };
385 ofmt_print(ofmt, &arg);
386 }
387 ofmt_close(ofmt);
388 }
389
390 for (uint16_t i = 0; i < max_addr; i++) {
391 free(results[i].map_shared);
392 }
393 free(results);
394 i2c_port_map_free(map);
395 i2c_port_fini(port);
396 return (0);
397 }
398
399 static void
i2cadm_port_list_usage(FILE * f)400 i2cadm_port_list_usage(FILE *f)
401 {
402 (void) fprintf(f, "\ti2cadm port list [-H] [-o field,[...] [-p]] "
403 "[filter]\n");
404 }
405
406 typedef enum {
407 I2CADM_PORT_LIST_PATH,
408 I2CADM_PORT_LIST_TYPE,
409 I2CADM_PORT_LIST_NAME,
410 I2CADM_PORT_LIST_NUM,
411 I2CADM_PORT_LIST_NDEVS,
412 I2CADM_PORT_LIST_TDEVS
413 } i2cadm_port_list_otype_tt;
414
415 typedef struct i2cadm_port_list_ofmt {
416 i2c_port_t *ipl_port;
417 i2c_port_map_t *ipl_map;
418 } i2cadm_port_list_ofmt_t;
419
420 static void
i2cadm_port_list_help(const char * fmt,...)421 i2cadm_port_list_help(const char *fmt, ...)
422 {
423 if (fmt != NULL) {
424 va_list ap;
425
426 va_start(ap, fmt);
427 vwarnx(fmt, ap);
428 va_end(ap);
429 }
430
431 (void) fprintf(stderr, "Usage: i2cadm port list [-H] "
432 "[-o field[,...] [-p]] [filter...]\n\n");
433 (void) fprintf(stderr, "List I2C ports in the system. Each <filter> "
434 "selects ports based upon its\ntype, name, or the I2C path. "
435 "Multiple filters are treated as an OR. It is an\nerror if a "
436 "filter isn't used.\n\n"
437 "\t-H\t\tomit the column header\n"
438 "\t-o field\toutput fields to print\n"
439 "\t-p\t\tparseable output (requires -o)\n");
440 (void) fprintf(stderr, "\nThe following fields are supported:\n"
441 "\tpath\t\tthe I2C path of the port\n"
442 "\ttype\t\tthe type of I2C port: controller or multiplexor\n"
443 "\tname\t\tthe port's name\n"
444 "\tportno\t\tthe system's port number (zero based)\n"
445 "\tndevs\t\tthe number of device's directly attached to this port\n"
446 "\ttdevs\t\tthe total number of devices under this port\n");
447 }
448
449 static boolean_t
i2cadm_port_list_ofmt_cb(ofmt_arg_t * ofarg,char * buf,uint_t buflen)450 i2cadm_port_list_ofmt_cb(ofmt_arg_t *ofarg, char *buf, uint_t buflen)
451 {
452 uint32_t local, ds;
453 i2cadm_port_list_ofmt_t *arg = ofarg->ofmt_cbarg;
454 size_t len;
455
456 switch (ofarg->ofmt_id) {
457 case I2CADM_PORT_LIST_PATH:
458 len = strlcpy(buf, i2c_port_path(arg->ipl_port), buflen);
459 break;
460 case I2CADM_PORT_LIST_TYPE:
461 switch (i2c_port_type(arg->ipl_port)) {
462 case I2C_PORT_TYPE_CTRL:
463 len = strlcat(buf, "controller", buflen);
464 break;
465 case I2C_PORT_TYPE_MUX:
466 len = strlcat(buf, "multiplexor", buflen);
467 break;
468 default:
469 len = snprintf(buf, buflen, "unknown: 0x%x",
470 i2c_port_type(arg->ipl_port));
471 break;
472 }
473 break;
474 case I2CADM_PORT_LIST_NAME:
475 len = strlcpy(buf, i2c_port_name(arg->ipl_port), buflen);
476 break;
477 case I2CADM_PORT_LIST_NUM:
478 len = snprintf(buf, buflen, "%u",
479 i2c_port_portno(arg->ipl_port));
480 break;
481 case I2CADM_PORT_LIST_NDEVS:
482 i2c_port_map_ndevs(arg->ipl_map, &local, NULL);
483 len = snprintf(buf, buflen, "%u", local);
484 break;
485 case I2CADM_PORT_LIST_TDEVS:
486 i2c_port_map_ndevs(arg->ipl_map, &local, &ds);
487 len = snprintf(buf, buflen, "%u", local + ds);
488 break;
489 default:
490 return (B_FALSE);
491 }
492
493 return (len < buflen);
494 }
495
496 static const char *i2cadm_port_list_fields = "path,type,portno,ndevs,tdevs";
497 static const ofmt_field_t i2cadm_port_list_ofmt[] = {
498 { "NAME", 12, I2CADM_PORT_LIST_NAME, i2cadm_port_list_ofmt_cb },
499 { "TYPE", 14, I2CADM_PORT_LIST_TYPE, i2cadm_port_list_ofmt_cb },
500 { "PORTNO", 8, I2CADM_PORT_LIST_NUM, i2cadm_port_list_ofmt_cb },
501 { "NDEVS", 8, I2CADM_PORT_LIST_NDEVS, i2cadm_port_list_ofmt_cb },
502 { "TDEVS", 8, I2CADM_PORT_LIST_TDEVS, i2cadm_port_list_ofmt_cb },
503 { "PATH", 32, I2CADM_PORT_LIST_PATH, i2cadm_port_list_ofmt_cb },
504 { NULL, 0, 0, NULL }
505 };
506
507 /*
508 * We accept the following filters for matching ports:
509 *
510 * - Matching on the ports name
511 * - Matching on the port's type
512 * - Matching on a portion of the port's path
513 */
514 static bool
i2cadm_port_list_filt(const i2cadm_port_list_ofmt_t * arg,int nfilts,char ** filts,bool * used)515 i2cadm_port_list_filt(const i2cadm_port_list_ofmt_t *arg, int nfilts,
516 char **filts, bool *used)
517 {
518 bool match = false;
519 const char *type, *name, *path;
520
521 if (nfilts == 0) {
522 return (true);
523 }
524
525 name = i2c_port_name(arg->ipl_port);
526 path = i2c_port_path(arg->ipl_port);
527 size_t pathlen = strlen(path);
528 if (i2c_port_type(arg->ipl_port) == I2C_PORT_TYPE_CTRL) {
529 type = "controller";
530 } else {
531 type = "multiplexor";
532 }
533
534 for (int i = 0; i < nfilts; i++) {
535 if (strcmp(filts[i], name) == 0) {
536 used[i] = true;
537 match = true;
538 continue;
539 }
540
541 if (strcmp(filts[i], type) == 0) {
542 used[i] = true;
543 match = true;
544 continue;
545 }
546
547 if (strcmp(filts[i], path) == 0) {
548 used[i] = true;
549 match = true;
550 continue;
551 }
552
553 size_t len = strlen(filts[i]);
554 if (len < pathlen && strncmp(path, filts[i], len) == 0) {
555 used[i] = true;
556 match = true;
557 continue;
558 }
559 }
560
561 return (match);
562 }
563
564 static int
i2cadm_port_list(int argc,char * argv[])565 i2cadm_port_list(int argc, char *argv[])
566 {
567 int c, ret = EXIT_SUCCESS;
568 uint_t flags = 0;
569 boolean_t parse = B_FALSE;
570 const char *fields = NULL;
571 bool *filts = NULL, print = false;
572 ofmt_status_t oferr;
573 ofmt_handle_t ofmt;
574 i2c_port_iter_t *iter;
575 i2c_iter_t iret;
576 const i2c_port_disc_t *disc;
577
578 while ((c = getopt(argc, argv, ":Ho:p")) != -1) {
579 switch (c) {
580 case 'H':
581 flags |= OFMT_NOHEADER;
582 break;
583 case 'o':
584 fields = optarg;
585 break;
586 case 'p':
587 parse = B_TRUE;
588 flags |= OFMT_PARSABLE;
589 break;
590 case ':':
591 i2cadm_port_list_help("option -%c requires an "
592 "argument", optopt);
593 exit(EXIT_USAGE);
594 case '?':
595 i2cadm_port_list_help("unknown option: -%c",
596 optopt);
597 exit(EXIT_USAGE);
598 }
599 }
600
601 if (parse && fields == NULL) {
602 errx(EXIT_USAGE, "-p requires fields specified with -o");
603 }
604
605 if (!parse) {
606 flags |= OFMT_WRAP;
607 }
608
609 if (fields == NULL) {
610 fields = i2cadm_port_list_fields;
611 }
612
613 argc -= optind;
614 argv += optind;
615
616 if (argc > 0) {
617 filts = calloc(argc, sizeof (bool));
618 if (filts == NULL) {
619 err(EXIT_FAILURE, "failed to allocate memory for "
620 "filter tracking");
621 }
622 }
623
624 oferr = ofmt_open(fields, i2cadm_port_list_ofmt, flags, 0, &ofmt);
625 ofmt_check(oferr, parse, ofmt, i2cadm_ofmt_errx, warnx);
626
627
628 if (!i2c_port_discover_init(i2cadm.i2c_hdl, &iter)) {
629 i2cadm_fatal("failed to in initialize port discovery");
630 }
631
632 while ((iret = i2c_port_discover_step(iter, &disc)) == I2C_ITER_VALID) {
633 i2cadm_port_list_ofmt_t arg;
634
635 if (!i2c_port_init(i2cadm.i2c_hdl, i2c_port_disc_devi(disc),
636 &arg.ipl_port)) {
637 i2cadm_warn("failed to initialize port %s",
638 i2c_port_disc_path(disc));
639 continue;
640 }
641
642 if (!i2c_port_map_snap(arg.ipl_port, &arg.ipl_map)) {
643 i2cadm_warn("failed to get port map for %s",
644 i2c_port_disc_path(disc));
645 i2c_port_fini(arg.ipl_port);
646 continue;
647 }
648
649 if (i2cadm_port_list_filt(&arg, argc, argv, filts)) {
650 ofmt_print(ofmt, &arg);
651 print = true;
652 }
653
654 i2c_port_map_free(arg.ipl_map);
655 i2c_port_fini(arg.ipl_port);
656 }
657
658 if (iret == I2C_ITER_ERROR) {
659 i2cadm_warn("failed to discover ports");
660 ret = EXIT_FAILURE;
661 }
662
663 for (int i = 0; i < argc; i++) {
664 if (!filts[i]) {
665 warnx("filter '%s' did not match any ports",
666 argv[i]);
667 ret = EXIT_FAILURE;
668 }
669 }
670
671 if (!print && argc == 0) {
672 warnx("no I2C ports found");
673 ret = EXIT_FAILURE;
674 }
675
676 i2c_port_discover_fini(iter);
677 return (ret);
678 }
679
680 static i2cadm_cmdtab_t i2cadm_port_cmds[] = {
681 { "list", i2cadm_port_list, i2cadm_port_list_usage },
682 { "map", i2cadm_port_map, i2cadm_port_map_usage },
683 };
684
685 int
i2cadm_port(int argc,char * argv[])686 i2cadm_port(int argc, char *argv[])
687 {
688 return (i2cadm_walk_tab(i2cadm_port_cmds, ARRAY_SIZE(i2cadm_port_cmds),
689 argc, argv));
690 }
691
692 void
i2cadm_port_usage(FILE * f)693 i2cadm_port_usage(FILE *f)
694 {
695 i2cadm_walk_usage(i2cadm_port_cmds, ARRAY_SIZE(i2cadm_port_cmds), f);
696 }
697