xref: /illumos-gate/usr/src/lib/libi2c/common/libi2c_io.c (revision 2671fc51ca6b63fc20cd2e852a326c63e2a958d4)
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  * I/O related functions.
18  */
19 
20 #include <stdlib.h>
21 #include <string.h>
22 #include <unistd.h>
23 #include <endian.h>
24 
25 #include "libi2c_impl.h"
26 
27 void
28 i2c_io_req_fini(i2c_io_req_t *req)
29 {
30 	free(req);
31 }
32 
33 bool
34 i2c_io_req_init(i2c_port_t *port, i2c_io_req_t **reqp)
35 {
36 	i2c_hdl_t *hdl = port->port_hdl;
37 	i2c_io_req_t *req;
38 
39 	if (reqp == NULL) {
40 		return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
41 		    "invalid i2c_io_req_t output pointer: %p", reqp));
42 	}
43 
44 	req = calloc(1, sizeof (i2c_io_req_t));
45 	if (req == NULL) {
46 		int e = errno;
47 		return (i2c_error(hdl, I2C_ERR_NO_MEM, e, "failed to allocate "
48 		    "memory for a new i2c_io_req_t"));
49 	}
50 	req->io_port = port;
51 
52 	*reqp = req;
53 	return (i2c_success(hdl));
54 }
55 
56 /*
57  * Set the address for a request. Note that we don't care if the address is
58  * reserved or not in the library. We ultimately leave that to the kernel.
59  */
60 bool
61 i2c_io_req_set_addr(i2c_io_req_t *req, const i2c_addr_t *addr)
62 {
63 	i2c_hdl_t *hdl = req->io_port->port_hdl;
64 
65 	if (addr == NULL) {
66 		return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
67 		    "invalid i2c_addr_t pointer: %p", addr));
68 	}
69 
70 	if (!i2c_addr_validate(hdl, addr)) {
71 		return (false);
72 	}
73 
74 	req->io_addr = *addr;
75 	req->io_addr_valid = true;
76 	return (i2c_success(hdl));
77 }
78 
79 bool
80 i2c_io_req_set_transmit_data(i2c_io_req_t *req, const void *buf, size_t len)
81 {
82 	i2c_hdl_t *hdl = req->io_port->port_hdl;
83 
84 	if (buf == NULL && len > 0) {
85 		return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "transmit "
86 		    "buffer cannot be a NULL pointer when the length is "
87 		    "non-zero (0x%zu)", len));
88 	} else if (buf != NULL && len == 0) {
89 		return (i2c_error(hdl, I2C_ERR_IO_WRITE_LEN_RANGE, 0,
90 		    "transmit data length cannot be zero when given a "
91 		    "non-NULL pointer " "(%p)", buf));
92 	} else if (len > I2C_REQ_MAX) {
93 		return (i2c_error(hdl, I2C_ERR_IO_WRITE_LEN_RANGE, 0, "cannot "
94 		    "transmit more than %zu bytes in a request, valid range is "
95 		    "[0x00, 0x%x]", len, I2C_REQ_MAX));
96 	}
97 
98 	req->io_tx_len = len;
99 	req->io_tx_buf = buf;
100 	return (i2c_success(hdl));
101 }
102 
103 bool
104 i2c_io_req_set_receive_buf(i2c_io_req_t *req, void *buf, size_t len)
105 {
106 	i2c_hdl_t *hdl = req->io_port->port_hdl;
107 
108 	if (buf == NULL && len > 0) {
109 		return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "receive "
110 		    "buffer cannot be a NULL pointer when the length is "
111 		    "non-zero (0x%zu)", len));
112 	} else if (buf != NULL && len == 0) {
113 		return (i2c_error(hdl, I2C_ERR_IO_READ_LEN_RANGE, 0, "receive "
114 		    "data length cannot be zero when given a non-NULL pointer "
115 		    "(%p)", buf));
116 	} else if (len > I2C_REQ_MAX) {
117 		return (i2c_error(hdl, I2C_ERR_IO_READ_LEN_RANGE, 0, "cannot "
118 		    "receive more %zu bytes in a request, valid range is "
119 		    "[0x00, 0x%x]", len, I2C_REQ_MAX));
120 	}
121 
122 	req->io_rx_len = len;
123 	req->io_rx_buf = buf;
124 	return (i2c_success(hdl));
125 }
126 
127 bool
128 i2c_io_req_exec(i2c_io_req_t *req)
129 {
130 	i2c_hdl_t *hdl = req->io_port->port_hdl;
131 	i2c_req_t i2c;
132 
133 	if (!req->io_addr_valid) {
134 		return (i2c_error(hdl, I2C_ERR_IO_REQ_MISSING_FIELDS, 0,
135 		    "cannot execute I/O request due to missing fields: "
136 		    "device address"));
137 	}
138 
139 	if (req->io_tx_len == 0 && req->io_rx_len == 0) {
140 		return (i2c_error(hdl, I2C_ERR_IO_REQ_IO_INVALID, 0,
141 		    "I/O request invalid: no transmit or receive specified"));
142 	}
143 
144 	(void) memset(&i2c, 0, sizeof (i2c_req_t));
145 	i2c.ir_addr = req->io_addr;
146 	i2c.ir_wlen = req->io_tx_len;
147 	i2c.ir_rlen = req->io_rx_len;
148 	if (i2c.ir_wlen > 0) {
149 		(void) memcpy(i2c.ir_wdata, req->io_tx_buf, req->io_tx_len);
150 	}
151 
152 	if (ioctl(req->io_port->port_fd, UI2C_IOCTL_I2C_REQ, &i2c) != 0) {
153 		int e = errno;
154 		return (i2c_ioctl_syserror(hdl, e, "I2C I/O request"));
155 	}
156 
157 	if (i2c.ir_error.i2c_error != I2C_CORE_E_OK) {
158 		return (i2c_ioctl_error(hdl, &i2c.ir_error, "I2C I/O request"));
159 	}
160 
161 	if (i2c.ir_rlen > 0) {
162 		(void) memcpy(req->io_rx_buf, i2c.ir_rdata, req->io_rx_len);
163 	}
164 
165 	return (i2c_success(hdl));
166 }
167 
168 void
169 smbus_io_req_fini(smbus_io_req_t *req)
170 {
171 	free(req);
172 }
173 
174 
175 /*
176  * Reset all I/O fields before we set something.
177  */
178 static void
179 smbus_io_req_reset(smbus_io_req_t *req)
180 {
181 	req->sir_op_valid = false;
182 	req->sir_op = UINT32_MAX;
183 	req->sir_flags = 0;
184 	req->sir_cmd = 0;
185 	req->sir_write = 0;
186 	req->sir_writep = NULL;
187 	req->sir_wlen = 0;
188 	req->sir_rlen = 0;
189 }
190 
191 bool
192 smbus_io_req_init(i2c_port_t *port, smbus_io_req_t **reqp)
193 {
194 	i2c_hdl_t *hdl = port->port_hdl;
195 	smbus_io_req_t *req;
196 
197 	if (reqp == NULL) {
198 		return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
199 		    "invalid smbus_io_req_t output pointer: %p", reqp));
200 	}
201 
202 	req = calloc(1, sizeof (smbus_io_req_t));
203 	if (req == NULL) {
204 		int e = errno;
205 		return (i2c_error(hdl, I2C_ERR_NO_MEM, e, "failed to allocate "
206 		    "memory for a new smbus_io_req_t"));
207 	}
208 	req->sir_port = port;
209 	smbus_io_req_reset(req);
210 
211 	*reqp = req;
212 	return (i2c_success(hdl));
213 }
214 
215 bool
216 smbus_io_req_set_addr(smbus_io_req_t *req, const i2c_addr_t *addr)
217 {
218 	i2c_hdl_t *hdl = req->sir_port->port_hdl;
219 
220 	if (addr == NULL) {
221 		return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
222 		    "invalid i2c_addr_t pointer: %p", addr));
223 	}
224 
225 	if (!i2c_addr_validate(hdl, addr)) {
226 		return (false);
227 	}
228 
229 	req->sir_addr = *addr;
230 	req->sir_addr_valid = true;
231 	return (i2c_success(hdl));
232 }
233 
234 bool
235 smbus_io_req_set_quick_cmd(smbus_io_req_t *req, bool write)
236 {
237 	smbus_io_req_reset(req);
238 	req->sir_op_valid = true;
239 	req->sir_op = SMBUS_OP_QUICK_COMMAND;
240 	req->sir_flags = write ? I2C_IO_REQ_F_QUICK_WRITE : 0;
241 
242 	return (i2c_success(req->sir_port->port_hdl));
243 }
244 
245 bool
246 smbus_io_req_set_send_byte(smbus_io_req_t *req, uint8_t u8)
247 {
248 	smbus_io_req_reset(req);
249 	req->sir_op_valid = true;
250 	req->sir_op = SMBUS_OP_SEND_BYTE;
251 	req->sir_write = u8;
252 
253 	return (i2c_success(req->sir_port->port_hdl));
254 }
255 
256 bool
257 smbus_io_req_set_write_u8(smbus_io_req_t *req, uint8_t cmd, uint8_t u8)
258 {
259 	smbus_io_req_reset(req);
260 	req->sir_op_valid = true;
261 	req->sir_op = SMBUS_OP_WRITE_BYTE;
262 	req->sir_cmd = cmd;
263 	req->sir_write = u8;
264 
265 	return (i2c_success(req->sir_port->port_hdl));
266 }
267 
268 bool
269 smbus_io_req_set_write_u16(smbus_io_req_t *req, uint8_t cmd, uint16_t u16)
270 {
271 	smbus_io_req_reset(req);
272 	req->sir_op_valid = true;
273 	req->sir_op = SMBUS_OP_WRITE_WORD;
274 	req->sir_cmd = cmd;
275 	req->sir_write = u16;
276 
277 	return (i2c_success(req->sir_port->port_hdl));
278 }
279 
280 bool
281 smbus_io_req_set_write_u32(smbus_io_req_t *req, uint8_t cmd, uint32_t u32)
282 {
283 	smbus_io_req_reset(req);
284 	req->sir_op_valid = true;
285 	req->sir_op = SMBUS_OP_WRITE_U32;
286 	req->sir_cmd = cmd;
287 	req->sir_write = u32;
288 
289 	return (i2c_success(req->sir_port->port_hdl));
290 }
291 
292 bool
293 smbus_io_req_set_write_u64(smbus_io_req_t *req, uint8_t cmd, uint64_t u64)
294 {
295 	smbus_io_req_reset(req);
296 	req->sir_op_valid = true;
297 	req->sir_op = SMBUS_OP_WRITE_U64;
298 	req->sir_cmd = cmd;
299 	req->sir_write = u64;
300 
301 	return (i2c_success(req->sir_port->port_hdl));
302 }
303 
304 bool
305 smbus_io_req_set_write_block(smbus_io_req_t *req, uint8_t cmd,
306     const void *wdata, size_t wlen, bool i2c)
307 {
308 	i2c_hdl_t *hdl = req->sir_port->port_hdl;
309 
310 	if (wdata == NULL) {
311 		return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
312 		    "invalid input data pointer: %p", wdata));
313 	}
314 
315 	if (wlen == 0) {
316 		return (i2c_error(hdl, I2C_ERR_IO_WRITE_LEN_RANGE, 0, "write "
317 		    "block requests must tranmit a non-zero amount of data"));
318 	} else if (wlen > I2C_REQ_MAX) {
319 		/*
320 		 * We only check against the maximum size range and leave it to
321 		 * the kernel to do the actual SMBus check as some block I2C
322 		 * writes can exceed SMBus 2.0 limits (especially after
323 		 * translation).
324 		 */
325 		return (i2c_error(hdl, I2C_ERR_IO_WRITE_LEN_RANGE, 0, "cannot "
326 		    "transmit %zu bytes in a request, valid range is [0x00, "
327 		    "0x%x]", wlen, I2C_REQ_MAX));
328 	}
329 
330 	smbus_io_req_reset(req);
331 	req->sir_op_valid = true;
332 	req->sir_op = i2c ? SMBUS_OP_I2C_WRITE_BLOCK : SMBUS_OP_WRITE_BLOCK;
333 	req->sir_cmd = cmd;
334 	req->sir_writep = wdata;
335 	req->sir_wlen = wlen;
336 
337 	return (i2c_success(hdl));
338 }
339 
340 bool
341 smbus_io_req_set_recv_byte(smbus_io_req_t *req, uint8_t *u8p)
342 {
343 	i2c_hdl_t *hdl = req->sir_port->port_hdl;
344 
345 	if (u8p == NULL) {
346 		return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
347 		    "invalid uint8_t pointer: %p", u8p));
348 	}
349 
350 	smbus_io_req_reset(req);
351 	req->sir_op_valid = true;
352 	req->sir_op = SMBUS_OP_RECV_BYTE;
353 	req->sir_readp = u8p;
354 
355 	return (i2c_success(hdl));
356 }
357 
358 bool
359 smbus_io_req_set_read_u8(smbus_io_req_t *req, uint8_t cmd, uint8_t *u8p)
360 {
361 	i2c_hdl_t *hdl = req->sir_port->port_hdl;
362 
363 	if (u8p == NULL) {
364 		return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
365 		    "invalid uint8_t pointer: %p", u8p));
366 	}
367 
368 	smbus_io_req_reset(req);
369 	req->sir_op_valid = true;
370 	req->sir_op = SMBUS_OP_READ_BYTE;
371 	req->sir_cmd = cmd;
372 	req->sir_readp = u8p;
373 
374 	return (i2c_success(hdl));
375 }
376 
377 bool
378 smbus_io_req_set_read_u16(smbus_io_req_t *req, uint8_t cmd, uint16_t *u16p)
379 {
380 	i2c_hdl_t *hdl = req->sir_port->port_hdl;
381 
382 	if (u16p == NULL) {
383 		return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
384 		    "invalid uint16_t pointer: %p", u16p));
385 	}
386 
387 	smbus_io_req_reset(req);
388 	req->sir_op_valid = true;
389 	req->sir_op = SMBUS_OP_READ_WORD;
390 	req->sir_cmd = cmd;
391 	req->sir_readp = u16p;
392 
393 	return (i2c_success(hdl));
394 }
395 
396 bool
397 smbus_io_req_set_read_u32(smbus_io_req_t *req, uint8_t cmd, uint32_t *u32p)
398 {
399 	i2c_hdl_t *hdl = req->sir_port->port_hdl;
400 
401 	if (u32p == NULL) {
402 		return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
403 		    "invalid uint32_t pointer: %p", u32p));
404 	}
405 
406 	smbus_io_req_reset(req);
407 	req->sir_op_valid = true;
408 	req->sir_op = SMBUS_OP_READ_U32;
409 	req->sir_cmd = cmd;
410 	req->sir_readp = u32p;
411 
412 	return (i2c_success(hdl));
413 }
414 
415 bool
416 smbus_io_req_set_read_u64(smbus_io_req_t *req, uint8_t cmd, uint64_t *u64p)
417 {
418 	i2c_hdl_t *hdl = req->sir_port->port_hdl;
419 
420 	if (u64p == NULL) {
421 		return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
422 		    "invalid uint64_t pointer: %p", u64p));
423 	}
424 
425 	smbus_io_req_reset(req);
426 	req->sir_op_valid = true;
427 	req->sir_op = SMBUS_OP_READ_U64;
428 	req->sir_cmd = cmd;
429 	req->sir_readp = u64p;
430 
431 	return (i2c_success(hdl));
432 }
433 
434 bool
435 smbus_io_req_set_read_block_i2c(smbus_io_req_t *req, uint8_t cmd, void *rdata,
436     size_t rlen)
437 {
438 	i2c_hdl_t *hdl = req->sir_port->port_hdl;
439 
440 	if (rdata == NULL) {
441 		return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
442 		    "invalid output data pointer: %p", rdata));
443 	}
444 
445 	if (rlen == 0) {
446 		return (i2c_error(hdl, I2C_ERR_IO_READ_LEN_RANGE, 0, "read "
447 		    "block requests must tranmit a non-zero amount of data"));
448 	} else if (rlen > I2C_REQ_MAX) {
449 		return (i2c_error(hdl, I2C_ERR_IO_WRITE_LEN_RANGE, 0, "cannot "
450 		    "receive %zu bytes in a request, valid range is [0x00, "
451 		    "0x%x]", rlen, I2C_REQ_MAX));
452 	}
453 
454 	smbus_io_req_reset(req);
455 	req->sir_op_valid = true;
456 	req->sir_op = SMBUS_OP_I2C_READ_BLOCK;
457 	req->sir_cmd = cmd;
458 	req->sir_readp = rdata;
459 	req->sir_rlen = rlen;
460 
461 	return (i2c_success(req->sir_port->port_hdl));
462 }
463 
464 bool
465 smbus_io_req_set_process_call(smbus_io_req_t *req, uint8_t cmd, uint16_t wdata,
466     uint16_t *rdatap)
467 {
468 	i2c_hdl_t *hdl = req->sir_port->port_hdl;
469 
470 	if (rdatap == NULL) {
471 		return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
472 		    "invalid output data pointer: %p", rdatap));
473 	}
474 
475 	smbus_io_req_reset(req);
476 	req->sir_op_valid = true;
477 	req->sir_op = SMBUS_OP_PROCESS_CALL;
478 	req->sir_cmd = cmd;
479 	req->sir_write = wdata;
480 	req->sir_readp = rdatap;
481 
482 	return (i2c_success(req->sir_port->port_hdl));
483 }
484 
485 bool
486 smbus_io_req_exec(smbus_io_req_t *req)
487 {
488 	i2c_hdl_t *hdl = req->sir_port->port_hdl;
489 	smbus_req_t smbus;
490 
491 	if (!req->sir_addr_valid || !req->sir_op_valid) {
492 		const char *miss;
493 
494 		if (!req->sir_addr_valid && !req->sir_op_valid) {
495 			miss = "device address, SMBus operation code";
496 		} else if (!req->sir_op_valid) {
497 			miss = "SMBus operation code";
498 		} else {
499 			miss = "device address";
500 		}
501 		return (i2c_error(hdl, I2C_ERR_IO_REQ_MISSING_FIELDS, 0,
502 		    "cannot execute I/O request due to missing fields: %s",
503 		    miss));
504 	}
505 
506 	(void) memset(&smbus, 0, sizeof (smbus_req_t));
507 	smbus.smbr_op = req->sir_op;
508 	smbus.smbr_flags = req->sir_flags;
509 	smbus.smbr_addr = req->sir_addr;
510 	smbus.smbr_cmd = req->sir_cmd;
511 
512 	/*
513 	 * Copy relevant data into the request for anything that needs to write.
514 	 * The actual write length or read length is only relevant for block
515 	 * requests. The rest get their length dictated by the actual opcode.
516 	 * SMBus transmits all data in little endian.
517 	 */
518 	switch (req->sir_op) {
519 	case SMBUS_OP_SEND_BYTE:
520 	case SMBUS_OP_WRITE_BYTE:
521 		smbus.smbr_wdata[0] = (uint8_t)req->sir_write;
522 		break;
523 	case SMBUS_OP_WRITE_WORD:
524 	case SMBUS_OP_PROCESS_CALL: {
525 		uint16_t u16 = htole16((uint16_t)req->sir_write);
526 		(void) memcpy(smbus.smbr_wdata, &u16, sizeof (u16));
527 		break;
528 	}
529 	case SMBUS_OP_WRITE_U32: {
530 		uint32_t u32 = htole32((uint32_t)req->sir_write);
531 		(void) memcpy(smbus.smbr_wdata, &u32, sizeof (u32));
532 		break;
533 	}
534 	case SMBUS_OP_WRITE_U64: {
535 		uint64_t u64 = htole64(req->sir_write);
536 		(void) memcpy(smbus.smbr_wdata, &u64, sizeof (u64));
537 		break;
538 	}
539 	case SMBUS_OP_I2C_WRITE_BLOCK:
540 	case SMBUS_OP_WRITE_BLOCK:
541 		smbus.smbr_wlen = req->sir_wlen;
542 		(void) memcpy(smbus.smbr_wdata, req->sir_writep, req->sir_wlen);
543 		break;
544 	case SMBUS_OP_I2C_READ_BLOCK:
545 		smbus.smbr_rlen = req->sir_rlen;
546 		break;
547 	default:
548 		break;
549 	}
550 
551 	if (ioctl(req->sir_port->port_fd, UI2C_IOCTL_SMBUS_REQ, &smbus) != 0) {
552 		int e = errno;
553 		return (i2c_ioctl_syserror(hdl, e, "SMBus I/O request"));
554 	}
555 
556 	if (smbus.smbr_error.i2c_error != I2C_CORE_E_OK) {
557 		return (i2c_ioctl_error(hdl, &smbus.smbr_error,
558 		    "SMBus I/O request"));
559 	}
560 
561 	switch (req->sir_op) {
562 	case SMBUS_OP_RECV_BYTE:
563 	case SMBUS_OP_READ_BYTE:
564 		*(uint8_t *)req->sir_readp = smbus.smbr_rdata[0];
565 		break;
566 	case SMBUS_OP_READ_WORD:
567 	case SMBUS_OP_PROCESS_CALL: {
568 		uint16_t u16;
569 		(void) memcpy(&u16, smbus.smbr_rdata, sizeof (uint16_t));
570 		*(uint16_t *)req->sir_readp = letoh16(u16);
571 		break;
572 	}
573 	case SMBUS_OP_READ_U32: {
574 		uint32_t u32;
575 		(void) memcpy(&u32, smbus.smbr_rdata, sizeof (uint32_t));
576 		*(uint32_t *)req->sir_readp = letoh32(u32);
577 		break;
578 	}
579 	case SMBUS_OP_READ_U64: {
580 		uint64_t u64;
581 		(void) memcpy(&u64, smbus.smbr_rdata, sizeof (uint64_t));
582 		*(uint64_t *)req->sir_readp = letoh64(u64);
583 		break;
584 	}
585 	/*
586 	 * Right now, only I2C Read block is supported earlier since we haven't
587 	 * plumbed through all the variable length read functions for lack of
588 	 * testing.
589 	 */
590 	case SMBUS_OP_READ_BLOCK:
591 	case SMBUS_OP_I2C_READ_BLOCK:
592 	case SMBUS_OP_BLOCK_PROCESS_CALL:
593 		(void) memcpy(req->sir_readp, smbus.smbr_rdata,
594 		    smbus.smbr_rlen);
595 	default:
596 		break;
597 	}
598 
599 	return (i2c_success(hdl));
600 }
601