#include <unistd.h>
#include <strings.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/debug.h>
#include "libxpio_impl.h"
CTASSERT((uint32_t)XPIO_DPIO_F_READ == (uint32_t)KGPIO_DPIO_F_READ);
CTASSERT((uint32_t)XPIO_DPIO_F_WRITE == (uint32_t)KGPIO_DPIO_F_WRITE);
CTASSERT((uint32_t)XPIO_DPIO_F_KERNEL == (uint32_t)KGPIO_DPIO_F_KERNEL);
static const char *xpio_dpinfo_path = "/devices/pseudo/kgpio@0:dpinfo";
typedef struct {
xpio_t *xdc_xpio;
xpio_dpio_disc_f xdc_func;
void *xdc_arg;
} xpio_dpio_cb_t;
static int
xpio_dpio_discover_cb(di_node_t di, di_minor_t minor, void *arg)
{
bool ret;
xpio_dpio_cb_t *cb = arg;
xpio_dpio_disc_t disc;
disc.xdd_minor = minor;
ret = cb->xdc_func(cb->xdc_xpio, &disc, cb->xdc_arg);
if (ret) {
return (DI_WALK_CONTINUE);
} else {
return (DI_WALK_TERMINATE);
}
}
void
xpio_dpio_discover(xpio_t *xpio, xpio_dpio_disc_f func, void *arg)
{
xpio_dpio_cb_t cb;
cb.xdc_xpio = xpio;
cb.xdc_func = func;
cb.xdc_arg = arg;
(void) di_walk_minor(xpio->xp_devinfo, DDI_NT_GPIO_DPIO, 0, &cb,
xpio_dpio_discover_cb);
}
void
xpio_dpio_info_free(xpio_dpio_info_t *info)
{
free(info);
}
const char *
xpio_dpio_info_ctrl(xpio_dpio_info_t *info)
{
return (info->xdi_ctrl);
}
const char *
xpio_dpio_info_name(xpio_dpio_info_t *info)
{
return (info->xdi_dpio + 5);
}
uint32_t
xpio_dpio_info_gpionum(xpio_dpio_info_t *info)
{
return (info->xdi_gpio);
}
dpio_caps_t
xpio_dpio_info_caps(xpio_dpio_info_t *info)
{
return (info->xdi_caps);
}
dpio_flags_t
xpio_dpio_info_flags(xpio_dpio_info_t *info)
{
return (info->xdi_flags);
}
bool
xpio_dpio_info(xpio_t *xpio, di_minor_t minor, xpio_dpio_info_t **outp)
{
int fd = -1;
xpio_dpio_info_t *info = NULL;
dpio_info_t dpi;
if (minor == DI_NODE_NIL) {
return (xpio_error(xpio, XPIO_ERR_BAD_PTR, 0, "encountered "
"invalid di_minor_t: %p", minor));
}
if (outp == NULL) {
return (xpio_error(xpio, XPIO_ERR_BAD_PTR, 0, "encountered "
"invalid xpio_ctrl_t output pointer: %p", outp));
}
*outp = NULL;
if (strcmp(di_minor_nodetype(minor), DDI_NT_GPIO_DPIO) != 0) {
return (xpio_error(xpio, XPIO_ERR_WRONG_MINOR_TYPE, 0,
"minor %s has incorrect node type: %s, expected %s",
di_minor_name(minor), di_minor_nodetype(minor),
DDI_NT_GPIO_DPIO));
}
if ((fd = open(xpio_dpinfo_path, O_RDONLY)) < 0) {
int e = errno;
return (xpio_error(xpio, XPIO_ERR_OPEN_DEV, e, "failed to open "
"DPIO information minor: %s", strerror(e)));
}
info = calloc(1, sizeof (xpio_dpio_info_t));
if (info == NULL) {
int e = errno;
(void) xpio_error(xpio, XPIO_ERR_NO_MEM, e, "failed to "
"allocate memory for a xpio_dpio_info_t: %s", strerror(e));
goto err;
}
(void) memset(&dpi, 0, sizeof (dpi));
if (strlcpy(dpi.dpi_dpio, di_minor_name(minor),
sizeof (dpi.dpi_dpio)) >= sizeof (dpi.dpi_dpio)) {
(void) xpio_error(xpio, XPIO_ERR_INTERNAL, 0, "DPIO name "
"somehow exceeded expected system length");
goto err;
}
if (ioctl(fd, DPIO_IOC_INFO, &dpi) != 0) {
int e = errno;
switch (e) {
case ENOENT:
(void) xpio_error(xpio, XPIO_ERR_BAD_DPIO_NAME, 0,
"DPIO %s does not exist", di_minor_name(minor));
goto err;
case EFAULT:
abort();
default:
(void) xpio_error(xpio, XPIO_ERR_KGPIO, e, "failed to "
"issue dpio information ioctl for dpio %s: %s",
di_minor_name(minor), strerror(e));
goto err;
}
}
(void) memcpy(info->xdi_dpio, dpi.dpi_dpio, sizeof (dpi.dpi_dpio));
(void) memcpy(info->xdi_ctrl, dpi.dpi_ctrl, sizeof (dpi.dpi_ctrl));
info->xdi_gpio = dpi.dpi_gpio;
info->xdi_caps = dpi.dpi_caps;
info->xdi_flags = dpi.dpi_flags;
(void) close(fd);
*outp = info;
return (xpio_success(xpio));
err:
if (fd >= 0) {
(void) close(fd);
}
free(info);
return (false);
}
bool
xpio_dpio_create(xpio_ctrl_t *ctrl, xpio_gpio_info_t *gi, const char *name,
xpio_dpio_features_t feat)
{
kgpio_dpio_create_t create;
xpio_t *xpio = ctrl->xc_xpio;
const uint32_t all_feats = XPIO_DPIO_F_READ | XPIO_DPIO_F_WRITE |
XPIO_DPIO_F_KERNEL;
if (gi == NULL) {
return (xpio_error(xpio, XPIO_ERR_BAD_PTR, 0, "encountered "
"invalid xpio_gpio_info_t pointer: %p", gi));
}
if (name == NULL) {
return (xpio_error(xpio, XPIO_ERR_BAD_PTR, 0, "encountered "
"invalid pointer for DPIO name: %p", name));
}
if ((feat & ~all_feats) != 0) {
return (xpio_error(xpio, XPIO_ERR_BAD_DPIO_FEAT, 0, "found "
"unknown dpio features specified: 0x%x",
feat & ~all_feats));
}
(void) memset(&create, 0, sizeof (create));
if (strlcpy(create.kdc_name, name, sizeof (create.kdc_name)) >=
sizeof (create.kdc_name)) {
return (xpio_error(xpio, XPIO_ERR_BAD_DPIO_NAME, 0,
"requested DPIO name is longer than the maximum supported "
"(%zu characters including '\\0')",
sizeof (create.kdc_name)));
}
create.kdc_flags = (kgpio_dpio_flags_t)feat;
create.kdc_id = gi->xgi_id;
if (ioctl(ctrl->xc_fd, KGPIO_IOC_DPIO_CREATE, &create) != 0) {
int e = errno;
return (xpio_error(xpio, XPIO_ERR_KGPIO, e, "failed to create "
"dpio %s: %s", name, strerror(e)));
}
return (xpio_success(xpio));
}
bool
xpio_dpio_destroy(xpio_ctrl_t *ctrl, xpio_gpio_info_t *gi)
{
kgpio_dpio_destroy_t destroy;
xpio_t *xpio = ctrl->xc_xpio;
if (gi == NULL) {
return (xpio_error(xpio, XPIO_ERR_BAD_PTR, 0, "encountered "
"invalid xpio_gpio_info_t pointer: %p", gi));
}
destroy.kdd_id = gi->xgi_id;
if (ioctl(ctrl->xc_fd, KGPIO_IOC_DPIO_DESTROY, &destroy) != 0) {
int e = errno;
return (xpio_error(xpio, XPIO_ERR_KGPIO, e, "failed to destroy "
"dpio %s/%u: %s", ctrl->xc_name, gi->xgi_id, strerror(e)));
}
return (xpio_success(xpio));
}