xref: /illumos-gate/usr/src/cmd/i2cadm/i2cadm_io.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  * Perform I/O on a given I2C bus, allowing a given mux segment to be activated
18  * or a specific device.
19  */
20 
21 #include <err.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <strings.h>
26 #include <sys/sysmacros.h>
27 #include <sys/debug.h>
28 #include <sys/hexdump.h>
29 #include <fcntl.h>
30 #include <unistd.h>
31 
32 #include "i2cadm.h"
33 
34 /*
35  * Currently we don't have an SMBus block read present here. The main reason is
36  * that we haven't been able to test this end-to-end and therefore don't have a
37  * great API for extracting the target read length. If we have something we can
38  * test against, then we can go ahead and add this.
39  */
40 typedef enum {
41 	I2CADM_IO_M_I2C,
42 	I2CADM_IO_M_QUICK_READ,
43 	I2CADM_IO_M_QUICK_WRITE,
44 	I2CADM_IO_M_RECV_U8,
45 	I2CADM_IO_M_READ_U8,
46 	I2CADM_IO_M_READ_U16,
47 	I2CADM_IO_M_READ_U32,
48 	I2CADM_IO_M_READ_U64,
49 	I2CADM_IO_M_READ_BLOCK_I2C,
50 	I2CADM_IO_M_SEND_U8,
51 	I2CADM_IO_M_WRITE_U8,
52 	I2CADM_IO_M_WRITE_U16,
53 	I2CADM_IO_M_WRITE_U32,
54 	I2CADM_IO_M_WRITE_U64,
55 	I2CADM_IO_M_WRITE_BLOCK,
56 	I2CADM_IO_M_WRITE_BLOCK_I2C,
57 	I2CADM_IO_M_CALL
58 } i2cadm_io_mode_t;
59 
60 typedef enum {
61 	/*
62 	 * Indicates that the size of this is fixed and the target size is
63 	 * specified in the mode_rlen and mode_wlen fields.
64 	 */
65 	I2CADM_IO_T_FIXED,
66 	/*
67 	 * Indicates that a variable read or write length is required, but not
68 	 * both.
69 	 */
70 	I2CADM_IO_T_VAR_READ,
71 	I2CADM_IO_T_VAR_WRITE,
72 	/*
73 	 * Indicates that both a variable read and write length is required. The
74 	 * next one is that only one of them is required, but both are allowed.
75 	 */
76 	I2CADM_IO_T_VAR_RW,
77 	I2CADM_IO_T_VAR_R_OR_W
78 } i2cadm_io_type_t;
79 
80 typedef struct {
81 	const char *mode_str;
82 	const char *mode_help;
83 	i2cadm_io_mode_t mode_val;
84 	i2cadm_io_type_t mode_io;
85 	bool mode_need_cmd;
86 	uint32_t mode_rlen;
87 	uint32_t mode_wlen;
88 	size_t mode_dlen;
89 } i2cadm_mode_info_t;
90 
91 typedef struct i2cadm_io_req {
92 	const i2cadm_mode_info_t *io_mode;
93 	i2c_io_req_t *io_i2c;
94 	smbus_io_req_t *io_smbus;
95 	uint8_t io_cmd;
96 	uint16_t io_rlen;
97 	uint16_t io_wlen;
98 	void *io_wdata;
99 	void *io_rdata;
100 } i2cadm_io_req_t;
101 
102 static const i2cadm_mode_info_t i2cadm_io_modes[] = {
103 	[I2CADM_IO_M_I2C] = {
104 		.mode_str = "i2c",
105 		.mode_help = "\t\t\tgeneral-purpose I2C I/O",
106 		.mode_val = I2CADM_IO_M_I2C,
107 		.mode_io = I2CADM_IO_T_VAR_R_OR_W,
108 		.mode_dlen = sizeof (uint8_t)
109 	},
110 	[I2CADM_IO_M_QUICK_READ] = {
111 		.mode_str = "quick-read",
112 		.mode_help = "\t\tSMBus quick read",
113 		.mode_val = I2CADM_IO_M_QUICK_READ,
114 		.mode_io = I2CADM_IO_T_FIXED
115 	},
116 	[I2CADM_IO_M_QUICK_WRITE] = {
117 		.mode_str = "quick-write",
118 		.mode_help = "\t\tSMBus write read",
119 		.mode_val = I2CADM_IO_M_QUICK_WRITE,
120 		.mode_io = I2CADM_IO_T_FIXED
121 	},
122 	[I2CADM_IO_M_RECV_U8] = {
123 		.mode_str = "recv-u8",
124 		.mode_help = "\t\t\tSMBus receive byte",
125 		.mode_val = I2CADM_IO_M_RECV_U8,
126 		.mode_io = I2CADM_IO_T_FIXED,
127 		.mode_rlen = sizeof (uint8_t),
128 		.mode_dlen = sizeof (uint8_t)
129 	},
130 	[I2CADM_IO_M_READ_U8] = {
131 		.mode_str = "read-u8",
132 		.mode_help = "\t\t\tSMBus read byte with command",
133 		.mode_val = I2CADM_IO_M_READ_U8,
134 		.mode_io = I2CADM_IO_T_FIXED,
135 		.mode_need_cmd = true,
136 		.mode_rlen = sizeof (uint8_t),
137 		.mode_dlen = sizeof (uint8_t)
138 	},
139 	[I2CADM_IO_M_READ_U16] = {
140 		.mode_str = "read-u16",
141 		.mode_help = "\t\tSMBus read word with command",
142 		.mode_val = I2CADM_IO_M_READ_U16,
143 		.mode_io = I2CADM_IO_T_FIXED,
144 		.mode_need_cmd = true,
145 		.mode_rlen = sizeof (uint16_t),
146 		.mode_dlen = sizeof (uint16_t)
147 	},
148 	[I2CADM_IO_M_READ_U32] = {
149 		.mode_str = "read-u32",
150 		.mode_help = "\t\tSMBus read u32 with command",
151 		.mode_val = I2CADM_IO_M_READ_U32,
152 		.mode_io = I2CADM_IO_T_FIXED,
153 		.mode_need_cmd = true,
154 		.mode_rlen = sizeof (uint32_t),
155 		.mode_dlen = sizeof (uint32_t)
156 	},
157 	[I2CADM_IO_M_READ_U64] = {
158 		.mode_str = "read-u64",
159 		.mode_help = "\t\tSMBus read u64 with command",
160 		.mode_val = I2CADM_IO_M_READ_U64,
161 		.mode_io = I2CADM_IO_T_FIXED,
162 		.mode_need_cmd = true,
163 		.mode_rlen = sizeof (uint64_t),
164 		.mode_dlen = sizeof (uint64_t)
165 	},
166 	[I2CADM_IO_M_READ_BLOCK_I2C] = {
167 		.mode_str = "read-block-i2c",
168 		.mode_help = "\t\tSMBus I2C block read with command (length "
169 		    "not sent)",
170 		.mode_val = I2CADM_IO_M_READ_BLOCK_I2C,
171 		.mode_io = I2CADM_IO_T_VAR_READ,
172 		.mode_need_cmd = true,
173 		.mode_dlen = sizeof (uint8_t)
174 	},
175 	[I2CADM_IO_M_SEND_U8] = {
176 		.mode_str = "send-u8",
177 		.mode_help = "\t\t\tSMBus send byte",
178 		.mode_val = I2CADM_IO_M_SEND_U8,
179 		.mode_io = I2CADM_IO_T_FIXED,
180 		.mode_wlen = sizeof (uint8_t),
181 		.mode_dlen = sizeof (uint8_t)
182 	},
183 	[I2CADM_IO_M_WRITE_U8] = {
184 		.mode_str = "write-u8",
185 		.mode_help = "\t\tSMBus write byte with command",
186 		.mode_val = I2CADM_IO_M_WRITE_U8,
187 		.mode_io = I2CADM_IO_T_FIXED,
188 		.mode_need_cmd = true,
189 		.mode_wlen = sizeof (uint8_t),
190 		.mode_dlen = sizeof (uint8_t)
191 	},
192 	[I2CADM_IO_M_WRITE_U16] = {
193 		.mode_str = "write-u16",
194 		.mode_help = "\t\tSMBus write word with command",
195 		.mode_val = I2CADM_IO_M_WRITE_U16,
196 		.mode_io = I2CADM_IO_T_FIXED,
197 		.mode_need_cmd = true,
198 		.mode_wlen = sizeof (uint16_t),
199 		.mode_dlen = sizeof (uint16_t)
200 	},
201 	[I2CADM_IO_M_WRITE_U32] = {
202 		.mode_str = "write-u32",
203 		.mode_help = "\t\tSMBus write u32 with command",
204 		.mode_val = I2CADM_IO_M_WRITE_U32,
205 		.mode_io = I2CADM_IO_T_FIXED,
206 		.mode_need_cmd = true,
207 		.mode_wlen = sizeof (uint32_t),
208 		.mode_dlen = sizeof (uint32_t)
209 	},
210 	[I2CADM_IO_M_WRITE_U64] = {
211 		.mode_str = "write-u64",
212 		.mode_help = "\t\tSMBus write u64 with command",
213 		.mode_val = I2CADM_IO_M_WRITE_U64,
214 		.mode_io = I2CADM_IO_T_FIXED,
215 		.mode_need_cmd = true,
216 		.mode_wlen = sizeof (uint64_t),
217 		.mode_dlen = sizeof (uint64_t)
218 	},
219 	[I2CADM_IO_M_WRITE_BLOCK] = {
220 		.mode_str = "write-block",
221 		.mode_help = "\t\tSMBus block write with command and length",
222 		.mode_val = I2CADM_IO_M_WRITE_BLOCK,
223 		.mode_io = I2CADM_IO_T_VAR_WRITE,
224 		.mode_need_cmd = true,
225 		.mode_dlen = sizeof (uint8_t)
226 	},
227 	[I2CADM_IO_M_WRITE_BLOCK_I2C] = {
228 		.mode_str = "write-block-i2c",
229 		.mode_help = "\t\tSMBus I2C block write with command (length "
230 		    "not sent)",
231 		.mode_val = I2CADM_IO_M_WRITE_BLOCK_I2C,
232 		.mode_io = I2CADM_IO_T_VAR_WRITE,
233 		.mode_need_cmd = true,
234 		.mode_dlen = sizeof (uint8_t)
235 	},
236 	[I2CADM_IO_M_CALL] = {
237 		.mode_str = "call",
238 		.mode_help = "\t\t\tSMBus process call with command (tx and "
239 		    "rx a u16)",
240 		.mode_val = I2CADM_IO_M_CALL,
241 		.mode_io = I2CADM_IO_T_FIXED,
242 		.mode_need_cmd = true,
243 		.mode_rlen = sizeof (uint16_t),
244 		.mode_wlen = sizeof (uint16_t),
245 		.mode_dlen = sizeof (uint16_t)
246 	}
247 };
248 
249 void
i2cadm_io_usage(FILE * f)250 i2cadm_io_usage(FILE *f)
251 {
252 	(void) fprintf(f, "\ti2cadm io [-m mode] -d dest [-a addr] [-c cmd] "
253 	    "[-w wlen] [-r rlen] [-o output] <data>\n");
254 }
255 
256 static void
i2cadm_io_help(const char * fmt,...)257 i2cadm_io_help(const char *fmt, ...)
258 {
259 	if (fmt != NULL) {
260 		va_list ap;
261 
262 		va_start(ap, fmt);
263 		vwarnx(fmt, ap);
264 		va_end(ap);
265 	}
266 
267 	(void) fprintf(stderr, "Usage:  i2cadm io [-m mode] -d dest [-a addr] "
268 	    "[-r rlen] [-w wlen] [-o output]\n\t<data>\n");
269 	(void) fprintf(stderr, "\nPerform I/O to any arbitrary I2C address on "
270 	    "the specified controller and\nport. If a mux is part of the "
271 	    "destination path, then it will be activated\nprior to issuing "
272 	    "the I/O. Transmitted data will be taken from positional\n"
273 	    "arguments.\n\nThe following options are supported:\n\n"
274 	    "\t-a addr\t\tthe 7-bit address to send the I/O to\n"
275 	    "\t-d dest\t\tspecifies the controller and port to target\n"
276 	    "\t-m mode\t\tsets the type of I/O issued, defaults to I2C\n"
277 	    "\t-o output\twrite raw data read to file output\n"
278 	    "\t-r rlen\t\tsets the number of bytes to read\n"
279 	    "\t-w wlen\t\tsets the number of bytes to write\n"
280 	    "\nThe following I/O modes are supported:\n");
281 
282 	for (size_t i = 0; i < ARRAY_SIZE(i2cadm_io_modes); i++) {
283 		(void) fprintf(stderr, "\t%s%s\n", i2cadm_io_modes[i].mode_str,
284 		    i2cadm_io_modes[i].mode_help);
285 	}
286 	exit(EXIT_FAILURE);
287 }
288 
289 static const i2cadm_mode_info_t *
i2cadm_io_parse_mode(const char * str)290 i2cadm_io_parse_mode(const char *str)
291 {
292 	for (size_t i = 0; i < ARRAY_SIZE(i2cadm_io_modes); i++) {
293 		if (strcasecmp(str, i2cadm_io_modes[i].mode_str) == 0) {
294 			return (&i2cadm_io_modes[i]);
295 		}
296 	}
297 
298 	warnx("unknown I/O mode: %s", str);
299 	(void) printf("Valid I/O Modes:\n");
300 	for (size_t i = 0; i < ARRAY_SIZE(i2cadm_io_modes); i++) {
301 		(void) printf("\t%s%s\n", i2cadm_io_modes[i].mode_str,
302 		    i2cadm_io_modes[i].mode_help);
303 	}
304 	exit(EXIT_FAILURE);
305 }
306 
307 static bool
i2cadm_io_read_ok(const i2cadm_mode_info_t * mode)308 i2cadm_io_read_ok(const i2cadm_mode_info_t *mode)
309 {
310 	switch (mode->mode_io) {
311 	case I2CADM_IO_T_FIXED:
312 		return (mode->mode_rlen != 0);
313 	case I2CADM_IO_T_VAR_READ:
314 	case I2CADM_IO_T_VAR_RW:
315 	case I2CADM_IO_T_VAR_R_OR_W:
316 		return (true);
317 	case I2CADM_IO_T_VAR_WRITE:
318 	default:
319 		return (false);
320 	}
321 }
322 
323 static bool
i2cadm_io_read_req(const i2cadm_mode_info_t * mode)324 i2cadm_io_read_req(const i2cadm_mode_info_t *mode)
325 {
326 	switch (mode->mode_io) {
327 	case I2CADM_IO_T_VAR_READ:
328 	case I2CADM_IO_T_VAR_RW:
329 		return (true);
330 	case I2CADM_IO_T_FIXED:
331 	case I2CADM_IO_T_VAR_WRITE:
332 	case I2CADM_IO_T_VAR_R_OR_W:
333 	default:
334 		return (false);
335 	}
336 }
337 
338 static bool
i2cadm_io_write_req(const i2cadm_mode_info_t * mode)339 i2cadm_io_write_req(const i2cadm_mode_info_t *mode)
340 {
341 	switch (mode->mode_io) {
342 	case I2CADM_IO_T_VAR_WRITE:
343 	case I2CADM_IO_T_VAR_RW:
344 		return (true);
345 	case I2CADM_IO_T_FIXED:
346 	case I2CADM_IO_T_VAR_READ:
347 	case I2CADM_IO_T_VAR_R_OR_W:
348 	default:
349 		return (false);
350 	}
351 }
352 
353 static bool
i2cadm_io_write_ok(const i2cadm_mode_info_t * mode)354 i2cadm_io_write_ok(const i2cadm_mode_info_t *mode)
355 {
356 	switch (mode->mode_io) {
357 	case I2CADM_IO_T_FIXED:
358 		return (mode->mode_wlen != 0);
359 	case I2CADM_IO_T_VAR_WRITE:
360 	case I2CADM_IO_T_VAR_RW:
361 	case I2CADM_IO_T_VAR_R_OR_W:
362 		return (true);
363 	case I2CADM_IO_T_VAR_READ:
364 	default:
365 		return (false);
366 	}
367 }
368 
369 /*
370  * Look at the specific requested mode and parse the corresponding read and
371  * write lengths. There are a few different cases for how a command performs
372  * I/O:
373  *
374  *  - Commands that have a built-in length. For example, SMBus read/write
375  *    u8/16 commands. Here if someone specifies the exact required length,
376  *    that's fine, otherwise it's an error.
377  *  - Commands that require both a read and write length: block call.
378  *  - Commands that require either a read or a write length: other block
379  *    operations.
380  *  - Commands that require at least one I/O direction, but can use both, aka
381  *    I2C.
382  */
383 static void
i2cadm_io_parse_rw_len(i2cadm_io_req_t * req,const char * rstr,const char * wstr)384 i2cadm_io_parse_rw_len(i2cadm_io_req_t *req, const char *rstr, const char *wstr)
385 {
386 	const i2cadm_mode_info_t *mode = req->io_mode;
387 	const char *modestr = mode->mode_str;
388 	i2cadm_io_type_t type = mode->mode_io;
389 
390 	/*
391 	 * First check if we have the required strings for the command mode.
392 	 */
393 	if (rstr == NULL && i2cadm_io_read_req(mode)) {
394 		errx(EXIT_FAILURE, "missing required I/O read length "
395 		    "(-r) which is required for I/O mode %s", modestr);
396 	}
397 
398 	if (wstr == NULL && i2cadm_io_write_req(mode)) {
399 		errx(EXIT_FAILURE, "missing required I/O write length "
400 		    "(-w) which is required for I/O mode %s", modestr);
401 	}
402 
403 	if (type == I2CADM_IO_T_VAR_R_OR_W && rstr == NULL && wstr == NULL) {
404 		errx(EXIT_FAILURE, "I/O mode %s requires at least one or both "
405 		    "of a read length (-r) and write length (-w) to be "
406 		    "specified", modestr);
407 	}
408 
409 	/*
410 	 * Now if we have a string, check if it is allowed. If so, then parse
411 	 * it. If this was a fixed length we need to verify that things match.
412 	 */
413 	if (rstr != NULL) {
414 		const char *errstr;
415 
416 		if (!i2cadm_io_read_ok(mode)) {
417 			errx(EXIT_FAILURE, "I/O mode %s does not allow "
418 			    "specifying a read length (-r)", modestr);
419 		}
420 
421 		req->io_rlen = (uint16_t)strtonumx(rstr, 1, I2C_REQ_MAX,
422 		    &errstr, 0);
423 		if (errstr != NULL) {
424 			errx(EXIT_FAILURE, "invalid read length: %s is %s, "
425 			    "valid values are between 1 and %u", rstr, errstr,
426 			    I2C_REQ_MAX);
427 		}
428 
429 		if (type == I2CADM_IO_T_FIXED && req->io_rlen !=
430 		    mode->mode_rlen) {
431 			errx(EXIT_FAILURE, "I/O mode %s has a fixed read "
432 			    "length of %u bytes, either do not specify -r or "
433 			    "set it to %u, not %s", modestr, mode->mode_rlen,
434 			    mode->mode_rlen, rstr);
435 		}
436 	} else if (type == I2CADM_IO_T_FIXED) {
437 		req->io_rlen = mode->mode_rlen;
438 	}
439 
440 	if (wstr != NULL) {
441 		const char *errstr;
442 
443 		if (!i2cadm_io_write_ok(mode)) {
444 			errx(EXIT_FAILURE, "I/O mode %s does not allow "
445 			    "specifying a write length (-w)", modestr);
446 		}
447 
448 		req->io_wlen = (uint16_t)strtonumx(wstr, 1, I2C_REQ_MAX,
449 		    &errstr, 0);
450 		if (errstr != NULL) {
451 			errx(EXIT_FAILURE, "invalid write length: %s is %s, "
452 			    "valid values are between 1 and %u", wstr, errstr,
453 			    I2C_REQ_MAX);
454 		}
455 
456 		if (type == I2CADM_IO_T_FIXED && req->io_wlen !=
457 		    mode->mode_wlen) {
458 			errx(EXIT_FAILURE, "I/O mode %s has a fixed write "
459 			    "length of %u bytes, either do not specify -w or "
460 			    "set it to %u, not %s", modestr, mode->mode_wlen,
461 			    mode->mode_wlen, wstr);
462 		}
463 	} else if (type == I2CADM_IO_T_FIXED) {
464 		req->io_wlen = mode->mode_wlen;
465 	}
466 }
467 
468 /*
469  * Go through and parse data into the requisite format. Different commands have
470  * a different data size element. While most are a uint8_t, some are larger. We
471  * adjust what we are parsing at this phase.
472  *
473  * We require one argument per data point. We should probably in the future
474  * allow for something like looking for comma characters, but this works for
475  * now.
476  */
477 static void
i2cadm_io_parse_data(i2cadm_io_req_t * req,int argc,char * argv[])478 i2cadm_io_parse_data(i2cadm_io_req_t *req, int argc, char *argv[])
479 {
480 	uint32_t nents;
481 
482 	VERIFY3U(req->io_wlen, !=, 0);
483 	VERIFY3U(req->io_mode->mode_dlen, !=, 0);
484 	VERIFY0(req->io_wlen % req->io_mode->mode_dlen);
485 
486 	nents = req->io_wlen / req->io_mode->mode_dlen;
487 	if (nents > 1 && req->io_mode->mode_dlen != 1) {
488 		errx(EXIT_FAILURE, "fatal internal error, cannot handle "
489 		    "I/O request with multiple non-byte sized data points");
490 	}
491 
492 	req->io_wdata = calloc(nents, req->io_mode->mode_dlen);
493 	if (req->io_wdata == NULL) {
494 		err(EXIT_FAILURE, "failed to allocate write data buffer (%u "
495 		    "elements, %zu bytes)", nents, req->io_mode->mode_dlen);
496 	}
497 
498 	if (argc != nents) {
499 		errx(EXIT_FAILURE, "write data requires %u elements, but only "
500 		    "found %d remaining arguments", nents, argc);
501 	}
502 
503 	for (int i = 0; i < argc; i++) {
504 		unsigned long long ull, max;
505 		char *eptr;
506 		uint8_t *u8;
507 		uint16_t *u16;
508 		uint32_t *u32;
509 		uint64_t *u64;
510 
511 		/*
512 		 * Note, we can't use strtonumx here because we want to be able
513 		 * to parse a uint64_t but strtonumx maxes out at a long long.
514 		 */
515 		errno = 0;
516 		ull = strtoull(argv[i], &eptr, 0);
517 		if (errno != 0 || *eptr != '\0') {
518 			errx(EXIT_FAILURE, "failed to parse data element %s",
519 			    argv[i]);
520 		}
521 
522 		switch (req->io_mode->mode_dlen) {
523 		case 1:
524 			max = UINT8_MAX;
525 			break;
526 		case 2:
527 			max = UINT16_MAX;
528 			break;
529 		case 4:
530 			max = UINT32_MAX;
531 			break;
532 		case 8:
533 			max = UINT64_MAX;
534 			break;
535 		default:
536 			abort();
537 		}
538 
539 		if (ull > max) {
540 			errx(EXIT_FAILURE, "data element %s is outside the "
541 			    "bounds for a %zu byte datum ([0, 0x%llx])",
542 			    argv[i], req->io_mode->mode_dlen, max);
543 		}
544 
545 		switch (req->io_mode->mode_dlen) {
546 		case 1:
547 			u8 = req->io_wdata;
548 			u8[i] = (uint8_t)ull;
549 			break;
550 		case 2:
551 			u16 = req->io_wdata;
552 			u16[i] = (uint16_t)ull;
553 			break;
554 		case 4:
555 			u32 = req->io_wdata;
556 			u32[i] = (uint32_t)ull;
557 			break;
558 		case 8:
559 			u64 = req->io_wdata;
560 			u64[i] = (uint64_t)ull;
561 			break;
562 		default:
563 			abort();
564 		}
565 	}
566 }
567 
568 static void
i2cadm_io_write(const i2cadm_io_req_t * req,const i2cadm_mode_info_t * mode,int ofd)569 i2cadm_io_write(const i2cadm_io_req_t *req, const i2cadm_mode_info_t *mode,
570     int ofd)
571 {
572 	size_t to_write = 0, off = 0;
573 
574 	switch (mode->mode_val) {
575 	case I2CADM_IO_M_I2C:
576 	case I2CADM_IO_M_READ_BLOCK_I2C:
577 		to_write = req->io_rlen;
578 		break;
579 	case I2CADM_IO_M_RECV_U8:
580 	case I2CADM_IO_M_READ_U8:
581 		to_write = sizeof (uint8_t);
582 		break;
583 	case I2CADM_IO_M_READ_U16:
584 		to_write = sizeof (uint16_t);
585 		break;
586 	case I2CADM_IO_M_READ_U32:
587 		to_write = sizeof (uint32_t);
588 		break;
589 	case I2CADM_IO_M_READ_U64:
590 		to_write = sizeof (uint64_t);
591 		break;
592 	default:
593 		break;
594 	}
595 
596 	while (to_write > 0) {
597 		ssize_t ret = write(ofd, req->io_rdata + off, to_write);
598 		if (ret < 0) {
599 			err(EXIT_FAILURE, "failed to write %zu bytes to "
600 			    "output file at offset %zu", to_write, off);
601 		}
602 
603 		to_write -= ret;
604 		off += ret;
605 	}
606 }
607 
608 static void
i2cadm_io_init(const i2cadm_io_req_t * req,const i2cadm_mode_info_t * mode)609 i2cadm_io_init(const i2cadm_io_req_t *req, const i2cadm_mode_info_t *mode)
610 {
611 	switch (mode->mode_val) {
612 	case I2CADM_IO_M_I2C:
613 		if (req->io_rlen != 0 &&
614 		    !i2c_io_req_set_receive_buf(req->io_i2c, req->io_rdata,
615 		    req->io_rlen)) {
616 			i2cadm_fatal("failed to set I2C read buffer");
617 		}
618 
619 		if (req->io_wlen != 0 &&
620 		    !i2c_io_req_set_transmit_data(req->io_i2c, req->io_wdata,
621 		    req->io_wlen)) {
622 			i2cadm_fatal("Failed to set I2C write buffer");
623 		}
624 		break;
625 	case I2CADM_IO_M_QUICK_READ:
626 		if (!smbus_io_req_set_quick_cmd(req->io_smbus, false)) {
627 			i2cadm_fatal("failed to set quick command request");
628 		}
629 		break;
630 	case I2CADM_IO_M_QUICK_WRITE:
631 		if (!smbus_io_req_set_quick_cmd(req->io_smbus, true)) {
632 			i2cadm_fatal("failed to set quick command request");
633 		}
634 		break;
635 	case I2CADM_IO_M_RECV_U8:
636 		if (!smbus_io_req_set_recv_byte(req->io_smbus, req->io_rdata)) {
637 			i2cadm_fatal("failed to set receive byte request");
638 		}
639 		break;
640 	case I2CADM_IO_M_READ_U8:
641 		if (!smbus_io_req_set_read_u8(req->io_smbus, req->io_cmd,
642 		    req->io_rdata)) {
643 			i2cadm_fatal("failed to set read byte request");
644 		}
645 		break;
646 	case I2CADM_IO_M_READ_U16:
647 		if (!smbus_io_req_set_read_u16(req->io_smbus, req->io_cmd,
648 		    req->io_rdata)) {
649 			i2cadm_fatal("failed to set read word request");
650 		}
651 		break;
652 	case I2CADM_IO_M_READ_U32:
653 		if (!smbus_io_req_set_read_u32(req->io_smbus, req->io_cmd,
654 		    req->io_rdata)) {
655 			i2cadm_fatal("failed to set read u32 request");
656 		}
657 		break;
658 	case I2CADM_IO_M_READ_U64:
659 		if (!smbus_io_req_set_read_u64(req->io_smbus, req->io_cmd,
660 		    req->io_rdata)) {
661 			i2cadm_fatal("failed to set read u64 request");
662 		}
663 		break;
664 	case I2CADM_IO_M_READ_BLOCK_I2C:
665 		if (!smbus_io_req_set_read_block_i2c(req->io_smbus, req->io_cmd,
666 		    req->io_rdata, req->io_rlen)) {
667 			i2cadm_fatal("failed to set read block request");
668 		}
669 		break;
670 	case I2CADM_IO_M_SEND_U8:
671 		if (!smbus_io_req_set_send_byte(req->io_smbus,
672 		    *(uint8_t *)req->io_wdata)) {
673 			i2cadm_fatal("failed to set send byte request");
674 		}
675 		break;
676 	case I2CADM_IO_M_WRITE_U8:
677 		if (!smbus_io_req_set_write_u8(req->io_smbus, req->io_cmd,
678 		    *(uint8_t *)req->io_wdata)) {
679 			i2cadm_fatal("failed to set write byte request");
680 		}
681 		break;
682 	case I2CADM_IO_M_WRITE_U16:
683 		if (!smbus_io_req_set_write_u16(req->io_smbus, req->io_cmd,
684 		    *(uint16_t *)req->io_wdata)) {
685 			i2cadm_fatal("failed to set write word request");
686 		}
687 		break;
688 	case I2CADM_IO_M_WRITE_U32:
689 		if (!smbus_io_req_set_write_u32(req->io_smbus, req->io_cmd,
690 		    *(uint32_t *)req->io_wdata)) {
691 			i2cadm_fatal("failed to set write u32 request");
692 		}
693 		break;
694 	case I2CADM_IO_M_WRITE_U64:
695 		if (!smbus_io_req_set_write_u64(req->io_smbus, req->io_cmd,
696 		    *(uint64_t *)req->io_wdata)) {
697 			i2cadm_fatal("failed to set write u64 request");
698 		}
699 		break;
700 	case I2CADM_IO_M_WRITE_BLOCK:
701 	case I2CADM_IO_M_WRITE_BLOCK_I2C:
702 		if (!smbus_io_req_set_write_block(req->io_smbus, req->io_cmd,
703 		    req->io_wdata, req->io_wlen,
704 		    mode->mode_val == I2CADM_IO_M_WRITE_BLOCK_I2C)) {
705 			i2cadm_fatal("failed to set write block request");
706 		}
707 		break;
708 	case I2CADM_IO_M_CALL:
709 		if (!smbus_io_req_set_process_call(req->io_smbus, req->io_cmd,
710 		    *(uint16_t *)req->io_wdata, req->io_rdata)) {
711 			i2cadm_fatal("failed to set process call request");
712 		}
713 		break;
714 	}
715 }
716 
717 static void
i2cadm_io_print(const i2cadm_io_req_t * req,const i2cadm_mode_info_t * mode)718 i2cadm_io_print(const i2cadm_io_req_t *req, const i2cadm_mode_info_t *mode)
719 {
720 	switch (mode->mode_val) {
721 	case I2CADM_IO_M_I2C:
722 	case I2CADM_IO_M_READ_BLOCK_I2C:
723 		/*
724 		 * If we didn't actually get any bytes (READ BLOCK) or this
725 		 * request didn't include a read (I2C), don't do anything.
726 		 */
727 		if (req->io_rlen == 0)
728 			break;
729 
730 		/*
731 		 * While convention wants to include HDF_ADDR here, we do not
732 		 * since we may be reading at some arbitrary offset via
733 		 * registers. We're not going to try to interpret that.
734 		 */
735 		(void) hexdump_file(req->io_rdata, req->io_rlen, HDF_HEADER |
736 		    HDF_ASCII, stdout);
737 		break;
738 	case I2CADM_IO_M_RECV_U8:
739 	case I2CADM_IO_M_READ_U8:
740 		(void) printf("0x%x\n", *(uint8_t *)req->io_rdata);
741 		break;
742 	case I2CADM_IO_M_READ_U16:
743 		(void) printf("0x%x\n", *(uint16_t *)req->io_rdata);
744 		break;
745 	case I2CADM_IO_M_READ_U32:
746 		(void) printf("0x%x\n", *(uint32_t *)req->io_rdata);
747 		break;
748 	case I2CADM_IO_M_READ_U64:
749 		(void) printf("0x%" PRIx64 "\n", *(uint64_t *)req->io_rdata);
750 		break;
751 	default:
752 		VERIFY3U(req->io_rlen, ==, 0);
753 		break;
754 	}
755 }
756 
757 int
i2cadm_io(int argc,char * argv[])758 i2cadm_io(int argc, char *argv[])
759 {
760 	int c, ofd = -1;
761 	const i2cadm_mode_info_t *mode = &i2cadm_io_modes[I2CADM_IO_M_I2C];
762 	const char *dpath = NULL, *addrstr = NULL, *cmdstr = NULL;
763 	const char *wstr = NULL, *rstr = NULL, *output = NULL;
764 	i2c_port_t *port;
765 	i2c_dev_info_t *info;
766 	i2c_addr_t addr;
767 	i2cadm_io_req_t req;
768 
769 	while ((c = getopt(argc, argv, ":a:c:d:m:o:r:w:")) != -1) {
770 		switch (c) {
771 		case 'a':
772 			addrstr = optarg;
773 			break;
774 		case 'c':
775 			cmdstr = optarg;
776 			break;
777 		case 'd':
778 			dpath = optarg;
779 			break;
780 		case 'm':
781 			mode = i2cadm_io_parse_mode(optarg);
782 			break;
783 		case 'o':
784 			output = optarg;
785 			break;
786 		case 'r':
787 			rstr = optarg;
788 			break;
789 		case 'w':
790 			wstr = optarg;
791 			break;
792 		case ':':
793 			i2cadm_io_help("option -%c requires an argument",
794 			    optopt);
795 			exit(EXIT_USAGE);
796 		case '?':
797 			i2cadm_io_help("unknown option: -%c", optopt);
798 			exit(EXIT_USAGE);
799 		}
800 	}
801 
802 	/*
803 	 * First establish that we have a valid destination and address that
804 	 * we're targetting. If the user gives us a full path to a device, then
805 	 * we don't want -a to be specified. if not, then we need -a
806 	 * ("addrstr").
807 	 */
808 	if (dpath == NULL) {
809 		errx(EXIT_FAILURE, "missing required destination path");
810 	}
811 
812 	if (!i2c_port_dev_init_by_path(i2cadm.i2c_hdl, dpath, true, &port,
813 	    &info)) {
814 		i2cadm_fatal("failed to parse path %s", dpath);
815 	}
816 
817 	if (info != NULL) {
818 		if (addrstr != NULL) {
819 			errx(EXIT_FAILURE, "target address specified twice: "
820 			    "either use an I2C path that specified a device or "
821 			    "-a, not both");
822 		}
823 
824 		addr = *i2c_device_info_addr_primary(info);
825 		i2c_device_info_free(info);
826 		info = NULL;
827 	} else {
828 		if (addrstr == NULL) {
829 			errx(EXIT_FAILURE, "missing target address: specify an "
830 			    "I2C path that refers to a device or use -a");
831 		}
832 
833 		if (!i2c_addr_parse(i2cadm.i2c_hdl, addrstr, &addr)) {
834 			i2cadm_fatal("failed to parse address %s", addrstr);
835 		}
836 	}
837 
838 	bzero(&req, sizeof (req));
839 	req.io_mode = mode;
840 	if (mode->mode_val == I2CADM_IO_M_I2C) {
841 		if (!i2c_io_req_init(port, &req.io_i2c)) {
842 			i2cadm_fatal("failed to initialize I2C I/O request");
843 		}
844 
845 		if (!i2c_io_req_set_addr(req.io_i2c, &addr)) {
846 			i2cadm_fatal("failed to set I2C request address");
847 		}
848 	} else {
849 		if (!smbus_io_req_init(port, &req.io_smbus)) {
850 			i2cadm_fatal("failed to initialize SMBus I/O request");
851 		}
852 
853 		if (!smbus_io_req_set_addr(req.io_smbus, &addr)) {
854 			i2cadm_fatal("failed to set I2C request address");
855 		}
856 	}
857 
858 	if (mode->mode_need_cmd) {
859 		const char *errstr = NULL;
860 		if (cmdstr == NULL) {
861 			errx(EXIT_FAILURE, "missing required SMBus command "
862 			    "value (-c) for I/O mode %s", mode->mode_str);
863 		}
864 		req.io_cmd = (uint8_t)strtonumx(cmdstr, 0, UINT8_MAX, &errstr,
865 		    0);
866 		if (errstr != NULL) {
867 			errx(EXIT_FAILURE, "invalid command value (-c): %s "
868 			    "is %s, valid values are between 0x00 and 0x%x",
869 			    cmdstr, errstr, UINT8_MAX);
870 		}
871 	} else {
872 		if (cmdstr != NULL) {
873 			errx(EXIT_FAILURE, "I/O mode %s does not allow "
874 			    "specifying an SMBus cmd (-c)", mode->mode_str);
875 		}
876 	}
877 
878 	i2cadm_io_parse_rw_len(&req, rstr, wstr);
879 	argc -= optind;
880 	argv += optind;
881 
882 	if (req.io_wlen == 0) {
883 		if (argc != 0) {
884 			errx(EXIT_USAGE, "encountered extraneous arguments "
885 			    "starting with %s", argv[0]);
886 		}
887 	} else {
888 		i2cadm_io_parse_data(&req, argc, argv);
889 	}
890 
891 	if (req.io_rlen != 0) {
892 		req.io_rdata = calloc(req.io_rlen, sizeof (uint8_t));
893 		if (req.io_rdata == NULL) {
894 			err(EXIT_FAILURE, "failed to allocate %u bytes for "
895 			    "request read buffer", req.io_rlen);
896 		}
897 
898 		if (output != NULL) {
899 			ofd = open(output, O_RDWR | O_TRUNC | O_CREAT);
900 			if (ofd < 0) {
901 				err(EXIT_FAILURE, "failed to open ouput "
902 				    "file (-o) %s", output);
903 			}
904 		}
905 	} else if (output != NULL) {
906 		errx(EXIT_FAILURE, "cannot specify output file -o when no "
907 		    "data is being read");
908 	}
909 
910 	i2cadm_io_init(&req, mode);
911 
912 	if (req.io_i2c != NULL) {
913 		if (!i2c_io_req_exec(req.io_i2c)) {
914 			i2cadm_fatal("failed to execute I2C request");
915 		}
916 	} else {
917 		if (!smbus_io_req_exec(req.io_smbus)) {
918 			i2cadm_fatal("failed to execute SMBus request");
919 		}
920 	}
921 
922 	if (ofd != -1) {
923 		i2cadm_io_write(&req, mode, ofd);
924 		(void) close(ofd);
925 	} else {
926 		i2cadm_io_print(&req, mode);
927 	}
928 
929 	if (req.io_i2c != NULL) {
930 		i2c_io_req_fini(req.io_i2c);
931 	}
932 	if (req.io_smbus != NULL) {
933 		smbus_io_req_fini(req.io_smbus);
934 	}
935 
936 	free(req.io_wdata);
937 	free(req.io_rdata);
938 	i2c_port_fini(port);
939 	return (EXIT_SUCCESS);
940 }
941