xref: /illumos-gate/usr/src/cmd/i2cadm/i2cadm_scan.c (revision 32002227574cf0a435dc03de622191ca53724f0a)
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 scan -- scan a single port for devices. By default all devices are
18  * scanned under the port, unless a specific set of devices is specified.
19  */
20 
21 #include <err.h>
22 #include <stdio.h>
23 #include <stdarg.h>
24 #include <string.h>
25 #include <sys/sysmacros.h>
26 #include <ofmt.h>
27 
28 #include "i2cadm.h"
29 
30 void
i2cadm_scan_usage(FILE * f)31 i2cadm_scan_usage(FILE *f)
32 {
33 	(void) fprintf(f, "\ti2cadm scan [-d dev] [-o field,[...] [-H] [-p]] "
34 	    "<port>\n");
35 }
36 
37 static void
i2cadm_scan_help(const char * fmt,...)38 i2cadm_scan_help(const char *fmt, ...)
39 {
40 	if (fmt != NULL) {
41 		va_list ap;
42 
43 		va_start(ap, fmt);
44 		vwarnx(fmt, ap);
45 		va_end(ap);
46 	}
47 
48 	(void) fprintf(stderr, "Usage:  i2cadm scan [-d dev] [-o field,[...] "
49 	    "[-H] [-p]] <port>\n");
50 	(void) fprintf(stderr, "\nScan for I2C devices\n\n"
51 	    "\t-d dev\t\tonly scan device address dev, can be specified "
52 	    "multiple\n\t\t\ttimes\n"
53 	    "\t-H\t\tomit the column header (requires -o)\n"
54 	    "\t-o field\toutput fields to print\n"
55 	    "\t-p\t\tparseable output (requires -o)\n");
56 	(void) fprintf(stderr, "\nThe following fields are supported when "
57 	    "using -o:\n"
58 	    "\taddr\t\tthe I2C address\n"
59 	    "\tresult\t\tthe address scan result\n"
60 	    "\terror\t\tthe error message if an error occurred\n");
61 }
62 
63 typedef enum {
64 	I2CADM_BUS_SCAN_UNKNOWN = 0,
65 	I2CADM_BUS_SCAN_FOUND,
66 	I2CADM_BUS_SCAN_NO_DEV,
67 	I2CADM_BUS_SCAN_RESERVED,
68 	I2CADM_BUS_SCAN_TIMEOUT,
69 	I2CADM_BUS_SCAN_ERROR,
70 	I2CADM_BUS_SCAN_SKIPPED
71 } i2cadm_scan_result_t;
72 
73 typedef struct {
74 	i2c_addr_t scan_addr;
75 	i2cadm_scan_result_t scan_res;
76 	char *scan_error;
77 } i2cadm_scan_t;
78 
79 static void
i2cadm_scan_error(i2c_hdl_t * hdl,i2cadm_scan_t * scan)80 i2cadm_scan_error(i2c_hdl_t *hdl, i2cadm_scan_t *scan)
81 {
82 	i2c_err_t err = i2c_err(hdl);
83 	if (err != I2C_ERR_CONTROLLER) {
84 		scan->scan_res = I2CADM_BUS_SCAN_ERROR;
85 		scan->scan_error = strdup(i2c_errmsg(hdl));
86 		if (scan->scan_error == NULL) {
87 			scan->scan_error = "libi2c error; but failed to "
88 			    "duplicate libi2c error message";
89 		}
90 		return;
91 	}
92 
93 	switch (i2c_ctrl_err(hdl)) {
94 	case I2C_CTRL_E_ADDR_NACK:
95 	case I2C_CTRL_E_DATA_NACK:
96 	case I2C_CTRL_E_NACK:
97 		scan->scan_res = I2CADM_BUS_SCAN_NO_DEV;
98 		break;
99 	case I2C_CTRL_E_REQ_TO:
100 		scan->scan_res = I2CADM_BUS_SCAN_TIMEOUT;
101 		break;
102 	default:
103 		scan->scan_res = I2CADM_BUS_SCAN_ERROR;
104 		scan->scan_error = strdup(i2c_errmsg(hdl));
105 		if (scan->scan_error == NULL) {
106 			scan->scan_error = "i2c controller error; but "
107 			    "failed to duplicate libi2c error message";
108 		}
109 		break;
110 	}
111 }
112 
113 /*
114  * One does not simply scan an i2c device. In essence, we're trying to perform
115  * some I/O such that it will ack an address, but without causing the device to
116  * wreak havoc. Given the plethora of devices that are out there, this may not
117  * be possible. Safety cannot be guaranteed by construction.
118  *
119  * We basically do a one byte read from the device. Most devices will respond to
120  * this. The alternative that some other tools have done is to try to perform an
121  * SMBus Quick action.
122  */
123 static void
i2cadm_scan_one(i2c_hdl_t * hdl,i2c_port_t * port,i2cadm_scan_t * scan)124 i2cadm_scan_one(i2c_hdl_t *hdl, i2c_port_t *port, i2cadm_scan_t *scan)
125 {
126 	i2c_io_req_t *req;
127 	uint8_t data = 0x77;
128 
129 	if (!i2c_io_req_init(port, &req)) {
130 		i2cadm_fatal("failed to initialize I/O request");
131 	}
132 
133 	if (!i2c_io_req_set_addr(req, &scan->scan_addr)) {
134 		i2cadm_fatal("failed to set scan address");
135 	}
136 
137 	if (!i2c_io_req_set_receive_buf(req, &data, sizeof (data))) {
138 		i2cadm_fatal("failed to set receive buffer");
139 	}
140 
141 	if (i2c_io_req_exec(req)) {
142 		scan->scan_res = I2CADM_BUS_SCAN_FOUND;
143 	} else {
144 		i2cadm_scan_error(hdl, scan);
145 	}
146 
147 	i2c_io_req_fini(req);
148 }
149 
150 typedef enum {
151 	I2CADM_SCAN_ADDR,
152 	I2CADM_SCAN_RESULT,
153 	I2CADM_SCAN_ERROR
154 } i2cadm_scan_otype_t;
155 
156 static boolean_t
i2cadm_scan_ofmt_cb(ofmt_arg_t * ofarg,char * buf,uint_t buflen)157 i2cadm_scan_ofmt_cb(ofmt_arg_t *ofarg, char *buf, uint_t buflen)
158 {
159 	const i2cadm_scan_t *scan = ofarg->ofmt_cbarg;
160 	const char *str;
161 	size_t len;
162 
163 	switch (ofarg->ofmt_id) {
164 	case I2CADM_SCAN_ADDR:
165 		len = snprintf(buf, buflen, "0x%x", scan->scan_addr.ia_addr);
166 		break;
167 	case I2CADM_SCAN_RESULT:
168 		switch (scan->scan_res) {
169 		case I2CADM_BUS_SCAN_FOUND:
170 			str = "found";
171 			break;
172 		case I2CADM_BUS_SCAN_NO_DEV:
173 			str = "missing";
174 			break;
175 		case I2CADM_BUS_SCAN_RESERVED:
176 			str = "reserved";
177 			break;
178 		case I2CADM_BUS_SCAN_TIMEOUT:
179 			str = "timeout";
180 			break;
181 		case I2CADM_BUS_SCAN_ERROR:
182 			str = "error";
183 			break;
184 		case I2CADM_BUS_SCAN_SKIPPED:
185 			str = "skipped";
186 			break;
187 		default:
188 			str = "unknown";
189 			break;
190 		}
191 		len = strlcpy(buf, str, buflen);
192 		break;
193 	case I2CADM_SCAN_ERROR:
194 		if (scan->scan_error != NULL) {
195 			len = strlcpy(buf, scan->scan_error, buflen);
196 		} else {
197 			len = strlcpy(buf, "-", buflen);
198 		}
199 		break;
200 	default:
201 		return (B_FALSE);
202 	}
203 
204 	return (len < buflen);
205 }
206 
207 static const ofmt_field_t i2cadm_scan_ofmt[] = {
208 	{ "ADDR", 8, I2CADM_SCAN_ADDR, i2cadm_scan_ofmt_cb },
209 	{ "RESULT", 16, I2CADM_SCAN_RESULT, i2cadm_scan_ofmt_cb },
210 	{ "ERROR", 40, I2CADM_SCAN_ERROR, i2cadm_scan_ofmt_cb },
211 	{ NULL, 0, 0, NULL }
212 };
213 
214 /*
215  * We can fit 16 devices on a line.
216  */
217 #define	SCAN_DEVS_PER_LINE	0x10
218 
219 static const char *key = ""
220 "\t- = No Device      @ = Device Found\n"
221 "\tR = Reserved       S = Skipped\n"
222 "\tX = Timed Out    Err = Error\n";
223 
224 static bool
i2cadm_scan_table_cb(void * arg,uint16_t i)225 i2cadm_scan_table_cb(void *arg, uint16_t i)
226 {
227 	const i2cadm_scan_t *results = arg;
228 	const char *msgs[] = { "???", "@", "-", "R", "X", "Err", "S" };
229 
230 	(void) printf("%3s", msgs[results[i].scan_res]);
231 
232 	return (results[i].scan_res == I2CADM_BUS_SCAN_ERROR);
233 }
234 
235 static void
i2cadm_scan_table_post(void * arg,uint16_t max_addr)236 i2cadm_scan_table_post(void *arg, uint16_t max_addr)
237 {
238 	const i2cadm_scan_t *results = arg;
239 	(void) printf("\nErrors\n");
240 	for (uint16_t i = 0; i < max_addr; i++) {
241 		if (results[i].scan_res != I2CADM_BUS_SCAN_ERROR)
242 			continue;
243 
244 		if (max_addr > UINT8_MAX) {
245 			(void) printf("0x%03x: %s\n", i, results[i].scan_error);
246 		} else {
247 			(void) printf("0x%02x: %s\n", i, results[i].scan_error);
248 		}
249 	}
250 }
251 
252 int
i2cadm_scan(int argc,char * argv[])253 i2cadm_scan(int argc, char *argv[])
254 {
255 	int c;
256 	i2c_port_t *port;
257 	bool ten_bit = false;
258 	uint16_t max_addr = 1 << 7;
259 	i2cadm_scan_t *results;
260 	uint16_t *dev_addrs = NULL;
261 	size_t naddrs = 0, nalloc = 0;
262 	boolean_t parse = B_FALSE;
263 	uint_t flags = 0;
264 	const char *fields = NULL;
265 	ofmt_status_t oferr;
266 	ofmt_handle_t ofmt;
267 
268 	/*
269 	 * In the future we should consider -T for 10-bit addressing.
270 	 */
271 	while ((c = getopt(argc, argv, ":d:Ho:p")) != -1) {
272 		switch (c) {
273 		case 'd': {
274 			if (naddrs == nalloc) {
275 				nalloc += 8;
276 				dev_addrs = recallocarray(dev_addrs, naddrs,
277 				    nalloc, sizeof (uint16_t));
278 				if (dev_addrs == NULL) {
279 					err(EXIT_FAILURE, "failed to allocate "
280 					    "memory for %zu I2C addresses",
281 					    nalloc);
282 				}
283 			}
284 
285 			const char *err;
286 			long long l = strtonumx(optarg, 0, max_addr - 1, &err,
287 			    0);
288 			if (err != NULL) {
289 				errx(EXIT_FAILURE, "invalid device address %s: "
290 				    "address is %s", optarg, err);
291 			}
292 			dev_addrs[naddrs] = (uint16_t)l;
293 			naddrs++;
294 			break;
295 		}
296 		case 'H':
297 			flags |= OFMT_NOHEADER;
298 			break;
299 		case 'o':
300 			fields = optarg;
301 			break;
302 		case 'p':
303 			parse = B_TRUE;
304 			flags |= OFMT_PARSABLE;
305 			break;
306 		case ':':
307 			i2cadm_scan_help("option -%c requires an argument",
308 			    optopt);
309 			exit(EXIT_USAGE);
310 		case '?':
311 			i2cadm_scan_help("unknown option: -%c", optopt);
312 			exit(EXIT_USAGE);
313 		}
314 	}
315 
316 	argv += optind;
317 	argc -= optind;
318 	if (argc == 0) {
319 		errx(EXIT_USAGE, "missing required port to scan");
320 	} else if (argc > 1) {
321 		errx(EXIT_USAGE, "encountered extraneous arguments starting "
322 		    "with %s", argv[1]);
323 	}
324 
325 	if (parse && fields == NULL) {
326 		errx(EXIT_USAGE, "-p requires fields specified with -o");
327 	}
328 
329 	if (flags != 0 && fields == NULL) {
330 		errx(EXIT_USAGE, "-H can only be used with -o");
331 	}
332 
333 	if (fields != NULL) {
334 		if (!parse) {
335 			flags |= OFMT_WRAP;
336 		}
337 
338 		oferr = ofmt_open(fields, i2cadm_scan_ofmt, flags, 0,
339 		    &ofmt);
340 		ofmt_check(oferr, parse, ofmt, i2cadm_ofmt_errx, warnx);
341 	}
342 
343 	if (!i2c_port_init_by_path(i2cadm.i2c_hdl, argv[0], &port)) {
344 		i2cadm_fatal("failed to parse port path %s", argv[0]);
345 	}
346 
347 	results = calloc(max_addr, sizeof (i2cadm_scan_t));
348 	if (results == NULL) {
349 		err(EXIT_FAILURE, "failed to allocate scan results tracking "
350 		    "structure");
351 	}
352 
353 	/*
354 	 * If we have a specific device list, then mark everything skipped and
355 	 * come back and mark the specific instances we care about as things to
356 	 * check.
357 	 */
358 	if (dev_addrs != NULL) {
359 		for (uint16_t i = 0; i < max_addr; i++) {
360 			results[i].scan_res = I2CADM_BUS_SCAN_SKIPPED;
361 		}
362 
363 		for (uint16_t i = 0; i < naddrs; i++) {
364 			results[dev_addrs[i]].scan_res =
365 			    I2CADM_BUS_SCAN_UNKNOWN;
366 		}
367 	}
368 
369 	for (uint16_t i = 0; i < max_addr; i++) {
370 		i2cadm_scan_t *scan = &results[i];
371 
372 		scan->scan_addr.ia_type = ten_bit ? I2C_ADDR_10BIT :
373 		    I2C_ADDR_7BIT;
374 		scan->scan_addr.ia_addr = i;
375 
376 		if (scan->scan_res == I2CADM_BUS_SCAN_SKIPPED)
377 			continue;
378 
379 		/*
380 		 * Determine if this is a reserved address or not.
381 		 */
382 		if (i2c_addr_reserved(&scan->scan_addr)) {
383 			scan->scan_res = I2CADM_BUS_SCAN_RESERVED;
384 			continue;
385 		}
386 
387 		i2cadm_scan_one(i2cadm.i2c_hdl, port, scan);
388 	}
389 
390 	if (fields == NULL) {
391 		i2cadm_table_t table = {
392 			.table_port = argv[0],
393 			.table_key = key,
394 			.table_msg = "Device scan on",
395 			.table_max = max_addr,
396 			.table_cb = i2cadm_scan_table_cb,
397 			.table_post = i2cadm_scan_table_post
398 		};
399 		i2cadm_print_table(&table, results);
400 	} else {
401 		for (uint16_t i = 0; i < max_addr; i++) {
402 			ofmt_print(ofmt, &results[i]);
403 		}
404 		ofmt_close(ofmt);
405 	}
406 
407 	for (uint16_t i = 0; i < max_addr; i++) {
408 		free(results[i].scan_error);
409 	}
410 	free(results);
411 	i2c_port_fini(port);
412 	return (EXIT_SUCCESS);
413 }
414