#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <sys/sysmacros.h>
#include <sys/debug.h>
#include <fcntl.h>
#include "libi2c_impl.h"
void
i2c_device_add_req_fini(i2c_dev_add_req_t *req)
{
nvlist_free(req->add_nvl);
free(req);
}
bool
i2c_device_add_req_init(i2c_port_t *port, i2c_dev_add_req_t **reqp)
{
i2c_hdl_t *hdl = port->port_hdl;
i2c_dev_add_req_t *req;
if (reqp == NULL) {
return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
"invalid i2c_dev_add_req_t output pointer: %p", reqp));
}
req = calloc(1, sizeof (i2c_dev_add_req_t));
if (req == NULL) {
int e = errno;
return (i2c_error(hdl, I2C_ERR_NO_MEM, e, "failed to allocate "
"memory for a new i2c_dev_add_req_t"));
}
req->add_port = port;
req->add_need = I2C_DEV_ADD_REQ_FIELD_NAME | I2C_DEV_ADD_REQ_FIELD_ADDR;
int ret = nvlist_alloc(&req->add_nvl, NV_UNIQUE_NAME, 0);
if (!i2c_nvlist_error(hdl, ret, "create a nvlist")) {
free(req);
return (false);
}
*reqp = req;
return (i2c_success(hdl));
}
bool
i2c_device_add_req_set_addr(i2c_dev_add_req_t *req, const i2c_addr_t *addr)
{
int ret;
i2c_hdl_t *hdl = req->add_port->port_hdl;
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);
}
ret = nvlist_add_uint16(req->add_nvl, UI2C_IOCTL_NVL_TYPE,
addr->ia_type);
if (!i2c_nvlist_error(hdl, ret, "insert address type")) {
return (false);
}
ret = nvlist_add_uint16(req->add_nvl, UI2C_IOCTL_NVL_ADDR,
addr->ia_addr);
if (!i2c_nvlist_error(hdl, ret, "insert address type")) {
return (false);
}
req->add_need &= ~I2C_DEV_ADD_REQ_FIELD_ADDR;
return (i2c_success(hdl));
}
bool
i2c_device_add_req_set_name(i2c_dev_add_req_t *req, const char *name)
{
i2c_hdl_t *hdl = req->add_port->port_hdl;
if (!i2c_name_validate(hdl, name, "name")) {
return (false);
}
int ret = nvlist_add_string(req->add_nvl, UI2C_IOCTL_NVL_NAME, name);
if (!i2c_nvlist_error(hdl, ret, "insert name string")) {
return (false);
}
req->add_need &= ~I2C_DEV_ADD_REQ_FIELD_NAME;
return (i2c_success(hdl));
}
bool
i2c_device_add_req_set_compatible(i2c_dev_add_req_t *req, char *const *compat,
size_t ncompat)
{
i2c_hdl_t *hdl = req->add_port->port_hdl;
if (compat == NULL && ncompat == 0) {
int ret = nvlist_remove(req->add_nvl, UI2C_IOCTL_NVL_COMPAT,
DATA_TYPE_STRING_ARRAY);
if (ret == 0 || ret == ENOENT) {
return (i2c_success(hdl));
}
return (i2c_error(hdl, I2C_ERR_INTERNAL, ret, "unexpected "
"internal error while trying to clear compatible[]"));
}
if (compat == NULL) {
return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
"invalid compatible pointer: %p", compat));
} else if (ncompat == 0) {
return (i2c_error(hdl, I2C_ERR_COMPAT_LEN_RANGE, 0, "number "
"of compatible entries cannot be zero when given a "
"non-NULL pointer (%p)", compat));
} else if (ncompat > UI2C_IOCTL_NVL_NCOMPAT_MAX) {
return (i2c_error(hdl, I2C_ERR_COMPAT_LEN_RANGE, 0, "device "
"compatible array is too long (%zu), valid range is [1, "
"%u]", ncompat, UI2C_IOCTL_NVL_NCOMPAT_MAX));
}
for (size_t i = 0; i < ncompat; i++) {
char desc[64];
(void) snprintf(desc, sizeof (desc), "compatible[%u]", i);
if (!i2c_name_validate(hdl, compat[i], desc)) {
return (false);
}
}
int ret = nvlist_add_string_array(req->add_nvl, UI2C_IOCTL_NVL_COMPAT,
compat, ncompat);
if (!i2c_nvlist_error(hdl, ret, "insert compatible string[]")) {
return (false);
}
return (i2c_success(hdl));
}
bool
i2c_device_add_req_exec(i2c_dev_add_req_t *req)
{
i2c_hdl_t *hdl = req->add_port->port_hdl;
size_t pack_size;
char *pack_buf = NULL;
int nvl_ret;
bool ret = false;
ui2c_dev_add_t dev;
if (req->add_need != 0) {
char buf[128];
bool comma = false;
buf[0] = '\0';
if ((req->add_need & I2C_DEV_ADD_REQ_FIELD_ADDR) != 0) {
(void) strlcat(buf, "device address", sizeof (buf));
comma = true;
}
if ((req->add_need & I2C_DEV_ADD_REQ_FIELD_NAME) != 0) {
if (comma) {
(void) strlcat(buf, ",", sizeof (buf));
}
(void) strlcat(buf, "name", sizeof (buf));
comma = true;
}
return (i2c_error(hdl, I2C_ERR_ADD_DEV_REQ_MISSING_FIELDS, 0,
"cannot execute add device request due to missing fields: "
"%s", buf));
}
nvl_ret = nvlist_size(req->add_nvl, &pack_size, NV_ENCODE_NATIVE);
if (!i2c_nvlist_error(hdl, nvl_ret, "determine packed nvlist size")) {
goto out;
}
pack_buf = malloc(pack_size);
if (pack_buf == NULL) {
ret = i2c_error(hdl, I2C_ERR_NO_MEM, errno, "failed to "
"allocate %zu bytes for packed request nvlist", pack_size);
goto out;
}
nvl_ret = nvlist_pack(req->add_nvl, &pack_buf, &pack_size,
NV_ENCODE_NATIVE, 0);
if (!i2c_nvlist_error(hdl, nvl_ret, "pack request nvlist")) {
goto out;
}
(void) memset(&dev, 0, sizeof (ui2c_dev_add_t));
dev.uda_nvl = (uintptr_t)pack_buf;
dev.uda_nvl_len = pack_size;
if (ioctl(req->add_port->port_fd, UI2C_IOCTL_DEVICE_ADD, &dev) != 0) {
int e = errno;
ret = i2c_ioctl_syserror(hdl, e, "add device request");
goto out;
}
if (dev.uda_error.i2c_error != I2C_CORE_E_OK) {
ret = i2c_ioctl_error(hdl, &dev.uda_error,
"add device request");
goto out;
}
ret = i2c_success(hdl);
out:
free(pack_buf);
return (ret);
}
bool
i2c_device_rem(i2c_port_t *port, const i2c_addr_t *addr)
{
ui2c_dev_rem_t rem;
i2c_hdl_t *hdl = port->port_hdl;
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);
}
(void) memset(&rem, 0, sizeof (ui2c_dev_rem_t));
rem.udr_addr = *addr;
if (ioctl(port->port_fd, UI2C_IOCTL_DEVICE_REMOVE, &rem) != 0) {
int e = errno;
return (i2c_ioctl_syserror(hdl, e, "remove device request"));
}
if (rem.udr_error.i2c_error != I2C_CORE_E_OK) {
return (i2c_ioctl_error(hdl, &rem.udr_error,
"remove device request"));
}
return (i2c_success(hdl));
}
void
i2c_device_discover_fini(i2c_dev_iter_t *iter)
{
if (iter == NULL)
return;
i2c_port_discover_fini(iter->di_iter);
free(iter);
}
static bool
i2c_device_discover_port(i2c_hdl_t *hdl, dev_port_info_t *dpi)
{
for (di_minor_t m = di_minor_next(dpi->dpi_port, DI_MINOR_NIL);
m != DI_MINOR_NIL; m = di_minor_next(dpi->dpi_port, m)) {
i2c_addr_t addr;
if (strcmp(di_minor_nodetype(m), DDI_NT_I2C_DEV) != 0)
continue;
if (!i2c_kernel_address_parse(hdl, di_minor_name(m), &addr)) {
return (false);
}
if (addr.ia_type == I2C_ADDR_7BIT) {
dpi->dpi_7b[addr.ia_addr].dmi_minor = m;
} else {
dpi->dpi_10b[addr.ia_addr].dmi_minor = m;
}
}
for (di_node_t di = di_child_node(dpi->dpi_port); di != DI_NODE_NIL;
di = di_sibling_node(di)) {
i2c_addr_t addr;
if (i2c_node_type(di) != I2C_NODE_T_DEV) {
continue;
}
if (!i2c_reg_to_addr(hdl, di, &addr, 0)) {
return (false);
}
if (addr.ia_type == I2C_ADDR_7BIT) {
dpi->dpi_7b[addr.ia_addr].dmi_node = di;
} else {
dpi->dpi_10b[addr.ia_addr].dmi_node = di;
}
}
return (true);
}
static bool
i2c_device_discover_one(i2c_dev_iter_t *iter, dev_map_info_t *map)
{
iter->di_disc.idd_map = map;
iter->di_disc.idd_port = &iter->di_info;
if (!i2c_node_to_path(iter->di_hdl, map->dmi_node,
iter->di_disc.idd_path, sizeof (iter->di_disc.idd_path))) {
return (false);
}
return (true);
}
i2c_iter_t
i2c_device_discover_step(i2c_dev_iter_t *iter, const i2c_dev_disc_t **discp)
{
for (;;) {
if (iter->di_done) {
return (I2C_ITER_DONE);
}
if (iter->di_curport == NULL) {
i2c_iter_t iret = i2c_port_discover_step(iter->di_iter,
&iter->di_curport);
if (iret == I2C_ITER_DONE) {
iter->di_done = true;
return (I2C_ITER_DONE);
} else if (iret != I2C_ITER_VALID) {
return (iret);
}
memset(&iter->di_info, 0, sizeof (dev_port_info_t));
iter->di_info.dpi_port =
i2c_port_disc_devi(iter->di_curport);
}
dev_port_info_t *pi = &iter->di_info;
if (!pi->dpi_scanned) {
pi->dpi_scanned = true;
if (!i2c_device_discover_port(iter->di_hdl, pi)) {
return (I2C_ITER_ERROR);
}
}
if (pi->dpi_7bit_done && pi->dpi_10bit_done) {
iter->di_curport = NULL;
continue;
}
if (!pi->dpi_7bit_done) {
while (pi->dpi_curidx < ARRAY_SIZE(pi->dpi_7b)) {
dev_map_info_t *map =
&pi->dpi_7b[pi->dpi_curidx];
pi->dpi_curidx++;
if (map->dmi_minor == DI_MINOR_NIL ||
map->dmi_node == DI_NODE_NIL) {
continue;
}
if (i2c_device_discover_one(iter, map)) {
*discp = &iter->di_disc;
return (I2C_ITER_VALID);
} else {
return (I2C_ITER_ERROR);
}
}
pi->dpi_7bit_done = true;
pi->dpi_curidx = 0;
}
if (!pi->dpi_10bit_done) {
while (pi->dpi_curidx < ARRAY_SIZE(pi->dpi_10b)) {
dev_map_info_t *map =
&pi->dpi_10b[pi->dpi_curidx];
pi->dpi_curidx++;
if (map->dmi_minor == DI_MINOR_NIL ||
map->dmi_node == DI_NODE_NIL) {
continue;
}
if (i2c_device_discover_one(iter, map)) {
*discp = &iter->di_disc;
return (I2C_ITER_VALID);
} else {
return (I2C_ITER_ERROR);
}
}
pi->dpi_10bit_done = true;
pi->dpi_curidx = 0;
}
}
return (I2C_ITER_ERROR);
}
bool
i2c_device_discover_init(i2c_hdl_t *hdl, i2c_dev_iter_t **iterp)
{
i2c_dev_iter_t *iter;
if (iterp == NULL) {
return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
"invalid i2c_dev_iter_t output pointer: %p", iterp));
}
iter = calloc(1, sizeof (i2c_dev_iter_t));
if (iter == NULL) {
int e = errno;
return (i2c_error(hdl, I2C_ERR_NO_MEM, e, "failed to allocate "
"memory for a new i2c_dev_iter_t"));
}
iter->di_hdl = hdl;
iter->di_done = false;
if (!i2c_port_discover_init(hdl, &iter->di_iter)) {
free(iter);
return (false);
}
*iterp = iter;
return (i2c_success(hdl));
}
bool
i2c_device_discover(i2c_hdl_t *hdl, i2c_dev_disc_f func, void *arg)
{
i2c_dev_iter_t *iter;
const i2c_dev_disc_t *disc;
i2c_iter_t ret;
if (func == NULL) {
return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
"invalid i2c_dev_disc_f function pointer: %p", func));
}
if (!i2c_device_discover_init(hdl, &iter)) {
return (false);
}
while ((ret = i2c_device_discover_step(iter, &disc)) ==
I2C_ITER_VALID) {
if (!func(hdl, disc, arg))
break;
}
i2c_device_discover_fini(iter);
if (ret == I2C_ITER_ERROR) {
return (false);
}
return (i2c_success(hdl));
}
const char *
i2c_device_disc_name(const i2c_dev_disc_t *disc)
{
return (di_node_name(disc->idd_map->dmi_node));
}
di_node_t
i2c_device_disc_devi(const i2c_dev_disc_t *disc)
{
return (disc->idd_map->dmi_node);
}
di_minor_t
i2c_device_disc_devctl(const i2c_dev_disc_t *disc)
{
return (disc->idd_map->dmi_minor);
}
const char *
i2c_device_disc_path(const i2c_dev_disc_t *disc)
{
return (disc->idd_path);
}
void
i2c_device_info_free(i2c_dev_info_t *info)
{
free(info->dinfo_name);
free(info->dinfo_driver);
free(info->dinfo_addrs);
di_devfs_path_free(info->dinfo_minor);
free(info);
}
const char *
i2c_device_info_path(const i2c_dev_info_t *info)
{
return (info->dinfo_path);
}
const char *
i2c_device_info_name(const i2c_dev_info_t *info)
{
return (info->dinfo_name);
}
const char *
i2c_device_info_driver(const i2c_dev_info_t *info)
{
return (info->dinfo_driver);
}
int
i2c_device_info_instance(const i2c_dev_info_t *info)
{
return (info->dinfo_inst);
}
uint32_t
i2c_device_info_naddrs(const i2c_dev_info_t *info)
{
return (info->dinfo_naddrs);
}
const i2c_addr_t *
i2c_device_info_addr_primary(const i2c_dev_info_t *info)
{
return (&info->dinfo_info.udi_primary);
}
const i2c_addr_t *
i2c_device_info_addr(const i2c_dev_info_t *info, uint32_t n)
{
if (n >= info->dinfo_naddrs) {
return (NULL);
}
return (&info->dinfo_addrs[n]);
}
i2c_addr_source_t
i2c_device_info_addr_source(const i2c_dev_info_t *info, uint32_t n)
{
if (n >= info->dinfo_naddrs) {
return (0);
}
return (info->dinfo_info.udi_7b[info->dinfo_addrs[n].ia_addr]);
}
bool
i2c_device_info_snap(i2c_hdl_t *hdl, di_node_t dn, i2c_dev_info_t **infop)
{
di_minor_t minor;
i2c_dev_info_t *info;
if (dn == DI_NODE_NIL) {
return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
"invalid di_node_t: %p", dn));
}
if (infop == NULL) {
return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
"invalid i2c_dev_info_t output pointer: %p", infop));
}
if (!i2c_node_is_type(dn, I2C_NODE_T_DEV)) {
return (i2c_error(hdl, I2C_ERR_BAD_DEVI, 0, "devi %s@%s is "
"not an i2c device", di_node_name(dn), di_bus_addr(dn)));
}
minor = i2c_node_minor(dn);
if (minor == DI_MINOR_NIL) {
return (i2c_error(hdl, I2C_ERR_BAD_DEVI, 0, "devi %s@%s is "
"not an i2c device: failed to find device minor",
di_node_name(dn), di_bus_addr(dn)));
}
info = calloc(1, sizeof (i2c_dev_info_t));
info->dinfo_name = strdup(di_node_name(dn));
if (info->dinfo_name == NULL) {
int e = errno;
i2c_device_info_free(info);
return (i2c_error(hdl, I2C_ERR_NO_MEM, e, "failed to duplicate "
"device node name"));
}
if (!i2c_node_to_path(hdl, dn, info->dinfo_path,
sizeof (info->dinfo_path))) {
i2c_device_info_free(info);
return (false);
}
if (di_driver_name(dn) != NULL) {
info->dinfo_driver = strdup(di_driver_name(dn));
if (info->dinfo_driver == NULL) {
int e = errno;
i2c_device_info_free(info);
return (i2c_error(hdl, I2C_ERR_NO_MEM, e, "failed to "
"duplicate device driver name"));
}
} else {
info->dinfo_driver = NULL;
}
info->dinfo_inst = di_instance(dn);
info->dinfo_minor = di_devfs_minor_path(minor);
if (info->dinfo_minor == NULL) {
int e = errno;
i2c_device_info_free(info);
return (i2c_error(hdl, I2C_ERR_LIBDEVINFO, e, "failed to "
"obtain devices's devfs path: %s", strerrordesc_np(e)));
}
int fd = openat(hdl->ih_devfd, info->dinfo_minor + 1, O_RDONLY);
if (fd < 0) {
int e = errno;
(void) i2c_error(hdl, I2C_ERR_OPEN_DEV, e, "failed to open "
"device path /devices%s: %s", info->dinfo_minor,
strerrordesc_np(e));
i2c_device_info_free(info);
return (false);
}
if (ioctl(fd, UI2C_IOCTL_DEV_INFO, &info->dinfo_info) != 0) {
int e = errno;
i2c_device_info_free(info);
return (i2c_ioctl_syserror(hdl, e, "device information "
"request"));
}
(void) close(fd);
if (info->dinfo_info.udi_error.i2c_error != I2C_CORE_E_OK) {
i2c_device_info_free(info);
return (i2c_ioctl_error(hdl, &info->dinfo_info.udi_error,
"device information request"));
}
for (uint32_t i = 0; i < ARRAY_SIZE(info->dinfo_info.udi_7b); i++) {
if (info->dinfo_info.udi_7b[i] != 0) {
info->dinfo_naddrs++;
}
}
VERIFY3U(info->dinfo_naddrs, >, 0);
info->dinfo_addrs = calloc(info->dinfo_naddrs, sizeof (i2c_addr_t));
if (info->dinfo_addrs == NULL) {
int e = errno;
(void) i2c_error(hdl, I2C_ERR_NO_MEM, e, "failed to allocate "
"memory for %u I2C addresses", info->dinfo_naddrs);
i2c_device_info_free(info);
return (false);
}
for (uint32_t i = 0, idx = 0; i < ARRAY_SIZE(info->dinfo_info.udi_7b);
i++) {
if (info->dinfo_info.udi_7b[i] != 0) {
info->dinfo_addrs[idx].ia_type = I2C_ADDR_7BIT;
info->dinfo_addrs[idx].ia_addr = i;
idx++;
}
}
*infop = info;
return (i2c_success(hdl));
}
bool
i2c_port_dev_init_by_path(i2c_hdl_t *hdl, const char *path, bool nodev_ok,
i2c_port_t **portp, i2c_dev_info_t **infop)
{
i2c_node_type_t type;
di_node_t dn, root, port_dn, dev_dn;
if (path == NULL) {
return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
"invalid i2c path: %p", path));
}
if (portp == NULL) {
return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
"invalid i2c_port_t output pointer: %p", infop));
}
*portp = NULL;
if (infop == NULL) {
return (i2c_error(hdl, I2C_ERR_BAD_PTR, 0, "encountered "
"invalid i2c_dev_info_t output pointer: %p", infop));
}
*infop = NULL;
root = di_init("/", DINFOCPYALL);
if (root == DI_NODE_NIL) {
int e = errno;
return (i2c_error(hdl, I2C_ERR_LIBDEVINFO, e, "failed to "
"initialize devinfo snapshot: %s", strerrordesc_np(e)));
}
if (!i2c_path_parse(hdl, path, root, &dn, &type, I2C_ERR_BAD_DEVICE)) {
di_fini(root);
return (false);
}
switch (type) {
case I2C_NODE_T_DEV:
dev_dn = dn;
port_dn = di_parent_node(dev_dn);
break;
case I2C_NODE_T_PORT:
if (!nodev_ok) {
return (i2c_error(hdl, I2C_ERR_BAD_DEVICE, 0, "parsed "
"I2C path %s did not end at a device", path));
}
dev_dn = DI_NODE_NIL;
port_dn = dn;
break;
default:
di_fini(root);
return (i2c_error(hdl, I2C_ERR_BAD_DEVICE, 0, "parsed I2C "
"path %s did not end at a device (or port)", path));
}
if (!i2c_port_init(hdl, port_dn, portp)) {
di_fini(root);
return (false);
}
if (dev_dn != DI_NODE_NIL) {
if (!i2c_device_info_snap(hdl, dev_dn, infop)) {
di_fini(root);
i2c_port_fini(*portp);
return (false);
}
}
return (true);
}