#include <stdlib.h>
#include <ctype.h>
#include <strings.h>
#include <sys/debug.h>
#include <sys/ilstr.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/obpdefs.h>
#include "libi2c_impl.h"
void
i2c_fini(i2c_hdl_t *hdl)
{
freelocale(hdl->ih_c_loc);
(void) close(hdl->ih_devfd);
free(hdl);
}
i2c_hdl_t *
i2c_init(void)
{
i2c_hdl_t *hdl;
hdl = calloc(1, sizeof (i2c_hdl_t));
if (hdl == NULL) {
return (NULL);
}
hdl->ih_devfd = open("/devices", O_RDONLY | O_DIRECTORY);
if (hdl->ih_devfd < 0) {
free(hdl);
return (NULL);
}
hdl->ih_c_loc = newlocale(LC_ALL_MASK, "C", NULL);
return (hdl);
}
bool
i2c_addr_reserved(const i2c_addr_t *addr)
{
switch (addr->ia_type) {
case I2C_ADDR_7BIT:
if (addr->ia_addr >= (1 << 7)) {
return (false);
}
break;
case I2C_ADDR_10BIT:
if (addr->ia_addr >= (1 << 10)) {
return (false);
}
break;
default:
return (false);
}
switch (addr->ia_addr) {
case I2C_RSVD_ADDR_GEN_CALL:
case I2C_RSVD_ADDR_C_BUS:
case I2C_RSVD_ADDR_DIFF_BUS:
case I2C_RSVD_ADDR_FUTURE:
case I2C_RSVD_ADDR_HS_0:
case I2C_RSVD_ADDR_HS_1:
case I2C_RSVD_ADDR_HS_2:
case I2C_RSVD_ADDR_HS_3:
case I2C_RSVD_ADDR_10B_0:
case I2C_RSVD_ADDR_10B_1:
case I2C_RSVD_ADDR_10B_2:
case I2C_RSVD_ADDR_10B_3:
case I2C_RSVD_ADDR_DID_0:
case I2C_RSVD_ADDR_DID_1:
case I2C_RSVD_ADDR_DID_2:
case I2C_RSVD_ADDR_DID_3:
return (true);
default:
return (false);
}
}
bool
i2c_addr_validate(i2c_hdl_t *hdl, const i2c_addr_t *addr)
{
uint16_t max;
if (addr == NULL) {
return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
"invalid i2c_addr_t pointer: %p", addr));
}
switch (addr->ia_type) {
case I2C_ADDR_7BIT:
max = 1 << 7;
break;
case I2C_ADDR_10BIT:
max = 1 << 10;
break;
default:
return (i2c_error(hdl, I2C_ERR_BAD_ADDR_TYPE, 0, "invalid "
"address type family 0x%x", addr->ia_type));
}
if (addr->ia_addr >= max) {
return (i2c_error(hdl, I2C_ERR_BAD_ADDR, 0, "address 0x%x is "
"outside the valid range for the address type: [0x00, "
"0x%02x]", addr->ia_addr, max - 1));
}
return (true);
}
CTASSERT(I2C_NAME_MAX == OBP_MAXDRVNAME);
bool
i2c_name_validate(i2c_hdl_t *hdl, const char *name, const char *desc)
{
size_t len;
if (name == NULL) {
return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
"invalid %s pointer: %p", desc, name));
}
len = strnlen(name, I2C_NAME_MAX);
if (len >= I2C_NAME_MAX) {
return (i2c_error(hdl, I2C_ERR_BAD_DEV_NAME, 0, "%s exceeds "
"%u character length limit, including NUL", desc,
I2C_NAME_MAX));
} else if (len == 0) {
return (i2c_error(hdl, I2C_ERR_BAD_DEV_NAME, 0, "%s cannot "
"have zero length", desc));
}
if (isalpha_l(name[0], hdl->ih_c_loc) == 0) {
return (i2c_error(hdl, I2C_ERR_BAD_DEV_NAME, 0, "%s must "
"have an ASCII upper or lowercase first letter: found 0x%x",
desc, name[0]));
}
for (size_t i = 1; i < len; i++) {
if (isalpha_l(name[i], hdl->ih_c_loc) ||
isdigit_l(name[i], hdl->ih_c_loc)) {
continue;
}
if (name[i] == ',' || name[i] == '.' || name[i] == '+' ||
name[i] == '-' || name[i] == '_') {
continue;
}
return (i2c_error(hdl, I2C_ERR_BAD_DEV_NAME, 0, "%s character "
"%zu is not from the valid set: found 0x%x", desc, i,
name[i]));
}
return (true);
}
i2c_node_type_t
i2c_node_type(di_node_t dn)
{
const char *drv = di_driver_name(dn);
char *strs;
int nstrs;
nstrs = di_prop_lookup_strings(DDI_DEV_T_ANY, dn, "device_type",
&strs);
if (nstrs == 1 && strcmp(strs, "i2c") == 0) {
return (I2C_NODE_T_DEV);
}
if (drv == NULL || strcmp(drv, I2C_NEX_DRV) != 0) {
return (I2C_NODE_T_OTHER);
}
nstrs = di_prop_lookup_strings(DDI_DEV_T_ANY, dn, I2C_NEXUS_TYPE_PROP,
&strs);
if (nstrs != 1) {
return (I2C_NODE_T_OTHER);
}
if (strcmp(strs, I2C_NEXUS_TYPE_PORT) == 0) {
return (I2C_NODE_T_PORT);
} else if (strcmp(strs, I2C_NEXUS_TYPE_CTRL) == 0) {
return (I2C_NODE_T_CTRL);
} else if (strcmp(strs, I2C_NEXUS_TYPE_MUX) == 0) {
return (I2C_NODE_T_MUX);
}
return (I2C_NODE_T_OTHER);
}
static di_minor_t
i2c_node_minor_device(di_node_t dn)
{
int nreg, *reg;
char name[32];
nreg = di_prop_lookup_ints(DDI_DEV_T_ANY, dn, "reg", ®);
if (nreg == 0 || (nreg % 2) != 0) {
return (DI_MINOR_NIL);
}
(void) snprintf(name, sizeof (name), "%x,%x", reg[0], reg[1]);
dn = di_parent_node(dn);
if (i2c_node_type(dn) != I2C_NODE_T_PORT) {
return (DI_MINOR_NIL);
}
for (di_minor_t m = di_minor_next(dn, DI_MINOR_NIL); m != DI_MINOR_NIL;
m = di_minor_next(dn, m)) {
if (strcmp(di_minor_nodetype(m), DDI_NT_I2C_DEV) == 0 &&
strcmp(di_minor_name(m), name) == 0) {
return (m);
}
}
return (DI_MINOR_NIL);
}
di_minor_t
i2c_node_minor(di_node_t dn)
{
const char *nt;
i2c_node_type_t type = i2c_node_type(dn);
switch (type) {
case I2C_NODE_T_CTRL:
nt = DDI_NT_I2C_CTRL;
break;
case I2C_NODE_T_PORT:
nt = DDI_NT_I2C_PORT;
break;
case I2C_NODE_T_MUX:
nt = DDI_NT_I2C_MUX;
break;
case I2C_NODE_T_DEV:
return (i2c_node_minor_device(dn));
default:
return (DI_MINOR_NIL);
}
for (di_minor_t m = di_minor_next(dn, DI_MINOR_NIL); m != DI_MINOR_NIL;
m = di_minor_next(dn, m)) {
if (strcmp(di_minor_nodetype(m), nt) == 0) {
return (m);
}
}
return (DI_MINOR_NIL);
}
bool
i2c_node_is_type(di_node_t dn, i2c_node_type_t type)
{
return (i2c_node_type(dn) == type);
}
bool
i2c_node_to_path(i2c_hdl_t *hdl, di_node_t dn, char *buf, size_t buflen)
{
ilstr_t ils;
bool first = true;
i2c_addr_t addr;
char addrstr[32];
ilstr_init_prealloc(&ils, buf, buflen);
for (;;) {
i2c_node_type_t type = i2c_node_type(dn);
switch (type) {
case I2C_NODE_T_CTRL:
case I2C_NODE_T_PORT:
if (!first) {
ilstr_prepend_str(&ils, "/");
}
ilstr_prepend_str(&ils, di_bus_addr(dn));
first = false;
break;
case I2C_NODE_T_MUX:
break;
case I2C_NODE_T_DEV:
if (!first) {
ilstr_prepend_str(&ils, "/");
}
if (!i2c_reg_to_addr(hdl, dn, &addr, 0)) {
return (false);
}
VERIFY(i2c_addr_to_string(hdl, &addr, addrstr,
sizeof (addrstr)));
ilstr_prepend_str(&ils, addrstr);
first = false;
break;
default:
return (i2c_error(hdl, I2C_ERR_INTERNAL, 0,
"encountered unknown node type constructing path: "
"0x%x", type));
}
if (type == I2C_NODE_T_CTRL)
break;
dn = di_parent_node(dn);
if (dn == DI_NODE_NIL)
break;
}
if (ilstr_errno(&ils) != ILSTR_ERROR_OK) {
return (i2c_error(hdl, I2C_ERR_INTERNAL, 0, "failed to "
"construct string for node %s: %s", di_node_name(dn),
ilstr_errstr(&ils)));
}
return (true);
}
bool
i2c_kernel_address_parse(i2c_hdl_t *hdl, const char *str, i2c_addr_t *addr)
{
char *eptr;
unsigned long ul;
errno = 0;
ul = strtoul(str, &eptr, 16);
if (errno != 0 || *eptr != ',') {
return (i2c_error(hdl, I2C_ERR_INTERNAL, 0, "kernel string %s "
"did not have a valid leading type", str));
}
if (ul == I2C_ADDR_7BIT) {
addr->ia_type = I2C_ADDR_7BIT;
} else if (ul == I2C_ADDR_10BIT) {
addr->ia_type = I2C_ADDR_10BIT;
} else {
return (i2c_error(hdl, I2C_ERR_INTERNAL, 0, "kernel string %s "
"did not have a valid type, found 0x%lx", str, ul));
}
errno = 0;
ul = strtoul(eptr + 1, &eptr, 16);
if (errno != 0 || *eptr != '\0') {
return (i2c_error(hdl, I2C_ERR_INTERNAL, 0, "kernel string %s "
"did not have a valid address", str));
}
if ((addr->ia_type == I2C_ADDR_7BIT && ul >= 1 << 7) ||
(addr->ia_type == I2C_ADDR_10BIT && ul >= 1 << 10)) {
return (i2c_error(hdl, I2C_ERR_INTERNAL, 0, "kernel string %s "
"address 0x%lx is too large for type", str, ul));
}
addr->ia_addr = (uint16_t)ul;
return (true);
}
bool
i2c_addr_parse(i2c_hdl_t *hdl, const char *buf, i2c_addr_t *addr)
{
char *eptr;
unsigned long ul;
const char *comma;
uint16_t max;
if (buf == NULL) {
return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
"invalid address string pointer: %p", buf));
}
if (addr == NULL) {
return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
"invalid i2c_addr_t pointer: %p", addr));
}
comma = strchr(buf, ',');
if (comma != NULL) {
size_t len = (uintptr_t)comma - (uintptr_t)buf;
if (len != 3 || strncmp("10b", buf, len) != 0) {
return (i2c_error(hdl, I2C_ERR_BAD_ADDR_TYPE, 0,
"found invalid address type on %s", buf));
}
addr->ia_type = I2C_ADDR_10BIT;
buf = comma + 1;
max = 1 << 10;
} else {
addr->ia_type = I2C_ADDR_7BIT;
max = 1 << 7;
}
errno = 0;
ul = strtoul(buf, &eptr, 0);
if (errno != 0 || *eptr != '\0') {
return (i2c_error(hdl, I2C_ERR_BAD_ADDR, 0, "address %s could "
"not be parsed", buf));
}
if (ul >= max) {
return (i2c_error(hdl, I2C_ERR_BAD_ADDR, 0, "address 0x%lx is "
"outside the valid range for the address type: [0x00, "
"0x%02x]", ul, max - 1));
}
addr->ia_addr = (uint16_t)ul;
return (true);
}
bool
i2c_addr_to_string(i2c_hdl_t *hdl, const i2c_addr_t *addr, char *buf,
size_t len)
{
size_t ret;
if (buf == NULL) {
return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
"invalid address string pointer: %p", buf));
}
if (addr == NULL) {
return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
"invalid i2c_addr_t pointer: %p", addr));
}
if (!i2c_addr_validate(hdl, addr)) {
return (false);
}
if (addr->ia_type == I2C_ADDR_10BIT) {
ret = snprintf(buf, len, "10b,0x%03x", addr->ia_addr);
} else {
ret = snprintf(buf, len, "0x%02x", addr->ia_addr);
}
if (ret >= len) {
return (i2c_error(hdl, I2C_ERR_BUF_TOO_SMALL, 0, "output "
"buffer is too small: need %zu bytes, have %zu", ret,
len));
}
return (true);
}
bool
i2c_reg_to_addr(i2c_hdl_t *hdl, di_node_t dn, i2c_addr_t *addr, uint32_t n)
{
int nreg, *reg;
uint32_t type_idx = n * 2;
uint32_t addr_idx = n * 2 + 1;
nreg = di_prop_lookup_ints(DDI_DEV_T_ANY, dn, "reg", ®);
if (nreg == 0 || (nreg % 2) != 0) {
return (i2c_error(hdl, I2C_ERR_INTERNAL, 0, "device %s@%s "
"does not have a valid i2c reg[] property",
di_node_name(dn), di_bus_addr(dn)));
}
if (addr_idx >= nreg) {
return (i2c_error(hdl, I2C_ERR_INTERNAL, 0, "device %s@%s "
"does not have a valid i2c reg[] property",
di_node_name(dn), di_bus_addr(dn)));
}
if (reg[type_idx] == I2C_ADDR_7BIT) {
addr->ia_type = I2C_ADDR_7BIT;
} else if (reg[type_idx] == I2C_ADDR_10BIT) {
addr->ia_type = I2C_ADDR_10BIT;
} else {
return (i2c_error(hdl, I2C_ERR_INTERNAL, 0, "device %s@%s "
"does not have a valid i2c address type, found 0x%x",
di_node_name(dn), di_bus_addr(dn), reg[type_idx]));
}
if ((addr->ia_type == I2C_ADDR_7BIT && reg[addr_idx] >= 1 << 7) ||
(addr->ia_type == I2C_ADDR_10BIT && reg[addr_idx] >= 1 << 10)) {
return (i2c_error(hdl, I2C_ERR_INTERNAL, 0, "device %s@%s "
"address 0x%x is too large for type", di_node_name(dn),
di_bus_addr(dn), reg[addr_idx]));
}
addr->ia_addr = (uint16_t)reg[1];
return (true);
}
bool
i2c_addr_equal(const i2c_addr_t *a, const i2c_addr_t *b)
{
return (a->ia_type == b->ia_type && a->ia_addr == b->ia_addr);
}
di_node_t
i2c_path_find_ctrl(di_node_t root, const char *name)
{
for (di_node_t di = di_drv_first_node(I2C_NEX_DRV, root); di != NULL;
di = di_drv_next_node(di)) {
if (!i2c_node_is_type(di, I2C_NODE_T_CTRL)) {
continue;
}
if (strcmp(name, di_bus_addr(di)) == 0) {
return (di);
}
}
return (DI_NODE_NIL);
}
di_node_t
i2c_path_find_mux(di_node_t dev)
{
for (di_node_t dn = di_child_node(dev); dn != NULL;
dn = di_sibling_node(dn)) {
if (i2c_node_type(dn) == I2C_NODE_T_MUX) {
return (dn);
}
}
return (DI_NODE_NIL);
}
di_node_t
i2c_path_find_port(di_node_t parent, const char *name)
{
for (di_node_t dn = di_child_node(parent); dn != NULL;
dn = di_sibling_node(dn)) {
if (!i2c_node_is_type(dn, I2C_NODE_T_PORT)) {
continue;
}
if (strcmp(di_bus_addr(dn), name) == 0) {
return (dn);
}
}
return (DI_NODE_NIL);
}
di_node_t
i2c_path_find_device(i2c_hdl_t *hdl, di_node_t port, const char *name)
{
for (di_node_t dn = di_child_node(port); dn != NULL;
dn = di_sibling_node(dn)) {
i2c_addr_t daddr;
char daddrstr[32];
if (i2c_node_type(dn) != I2C_NODE_T_DEV) {
continue;
}
if (!i2c_reg_to_addr(hdl, dn, &daddr, 0)) {
continue;
}
if (!i2c_addr_to_string(hdl, &daddr, daddrstr,
sizeof (daddrstr))) {
continue;
}
if (strcmp(name, daddrstr) == 0) {
return (dn);
}
if (di_driver_name(dn) != NULL && di_instance(dn) != -1) {
char buf[128];
(void) snprintf(buf, sizeof (buf), "%s%d",
di_driver_name(dn), di_instance(dn));
if (strcmp(name, buf) == 0) {
return (dn);
}
}
const char *at = strchr(name, '@');
if (at != NULL) {
char buf[128];
(void) snprintf(buf, sizeof (buf), "%s@%s",
di_node_name(dn), daddrstr);
if (strcmp(name, buf) == 0) {
return (dn);
}
}
}
return (DI_NODE_NIL);
}
bool
i2c_path_parse(i2c_hdl_t *hdl, const char *path, di_node_t root, di_node_t *dnp,
i2c_node_type_t *typep, i2c_err_t err)
{
di_node_t cur_devi;
char *dup, *state;
i2c_node_type_t cur;
bool ret = false;
if (path == NULL) {
return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
"invalid I2C path pointer: %p", path));
}
dup = strdup(path);
if (dup == NULL) {
int e = errno;
return (i2c_error(hdl, I2C_ERR_NO_MEM, e, "failed to duplicate "
"I2C path"));
}
cur = I2C_NODE_T_OTHER;
cur_devi = NULL;
for (const char *ent = strtok_r(dup, "/", &state); ent != NULL;
ent = strtok_r(NULL, "/", &state)) {
switch (cur) {
case I2C_NODE_T_OTHER:
cur_devi = i2c_path_find_ctrl(root, ent);
if (cur_devi == DI_NODE_NIL) {
(void) i2c_error(hdl, err, 0,
"failed to find controller %s as part of "
"parsing I2C path %s", ent, path);
goto err;
}
cur = I2C_NODE_T_CTRL;
break;
case I2C_NODE_T_DEV:
cur_devi = i2c_path_find_mux(cur_devi);
if (cur_devi == DI_NODE_NIL) {
(void) i2c_error(hdl, err, 0,
"failed to find mux %s as part of "
"parsing I2C path %s", ent, path);
goto err;
}
case I2C_NODE_T_CTRL:
cur_devi = i2c_path_find_port(cur_devi, ent);
if (cur_devi == DI_NODE_NIL) {
(void) i2c_error(hdl, err, 0,
"failed to find port %s as part of "
"parsing I2C path %s", ent, path);
goto err;
}
cur = I2C_NODE_T_PORT;
break;
case I2C_NODE_T_PORT:
cur_devi = i2c_path_find_device(hdl, cur_devi, ent);
if (cur_devi == DI_NODE_NIL) {
(void) i2c_error(hdl, err, 0,
"failed to find device %s as part of "
"parsing I2C path %s", ent, path);
goto err;
}
cur = I2C_NODE_T_DEV;
break;
default:
abort();
}
}
*dnp = cur_devi;
*typep = cur;
ret = true;
err:
free(dup);
return (ret);
}