#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <alloca.h>
#include <sys/stat.h>
#include <malloc.h>
#include <fcntl.h>
#include <syslog.h>
#include <string.h>
#include <errno.h>
#include <sys/mdesc.h>
#include <sys/mdesc_impl.h>
#include <libdevinfo.h>
#include "ldma.h"
#include "mdesc_mutable.h"
static int get_devinfo(uint8_t **mdpp, size_t *size);
static boolean_t is_root_complex(di_prom_handle_t ph, di_node_t di);
static md_node_t *link_device_node(mmd_t *mdp,
di_prom_handle_t ph, di_node_t di, md_node_t *node, char *path);
static int create_children(mmd_t *mdp,
di_prom_handle_t ph, md_node_t *node, di_node_t parent);
static int create_peers(mmd_t *mdp,
di_prom_handle_t ph, md_node_t *node, di_node_t dev);
static int device_tree_to_md(mmd_t *mdp, md_node_t *top);
#define PCIEX "pciex"
#define LDMA_MODULE LDMA_NAME_DIO
static ds_ver_t ldma_dio_vers[] = { {1, 0} };
#define LDMA_DIO_NVERS (sizeof (ldma_dio_vers) / sizeof (ds_ver_t))
#define LDMA_DIO_NHANDLERS (sizeof (ldma_dio_handlers) / \
sizeof (ldma_msg_handler_t))
static ldm_msg_func_t ldma_dio_pcidev_info_handler;
static ldma_msg_handler_t ldma_dio_handlers[] = {
{MSGDIO_PCIDEV_INFO, LDMA_MSGFLG_ACCESS_CONTROL,
ldma_dio_pcidev_info_handler },
};
ldma_agent_info_t ldma_dio_info = {
LDMA_NAME_DIO,
ldma_dio_vers, LDMA_DIO_NVERS,
ldma_dio_handlers, LDMA_DIO_NHANDLERS
};
static ldma_request_status_t
ldma_dio_pcidev_info_handler(ds_ver_t *ver, ldma_message_header_t *request,
size_t request_dlen, ldma_message_header_t **replyp, size_t *reply_dlenp)
{
ldma_message_header_t *reply;
char *data;
uint8_t *md_bufp = NULL;
size_t md_size;
int rv;
LDMA_DBG("%s: PCI device info request", __func__);
rv = get_devinfo(&md_bufp, &md_size);
if (rv != 0) {
LDMA_ERR("Failed to generate devinfo MD");
return (LDMA_REQ_FAILED);
}
reply = ldma_alloc_result_msg(request, md_size);
if (reply == NULL) {
LDMA_ERR("Memory allocation failure");
free(md_bufp);
return (LDMA_REQ_FAILED);
}
reply->msg_info = md_size;
data = LDMA_HDR2DATA(reply);
(void) memcpy(data, md_bufp, md_size);
*replyp = reply;
*reply_dlenp = md_size;
free(md_bufp);
LDMA_DBG("%s: sending PCI device info", __func__);
return (LDMA_REQ_COMPLETED);
}
static boolean_t
is_root_complex(di_prom_handle_t ph, di_node_t di)
{
int len;
char *type;
len = di_prom_prop_lookup_strings(ph, di, "device_type", &type);
if ((len == 0) || (type == NULL))
return (B_FALSE);
if (strcmp(type, PCIEX) != 0)
return (B_FALSE);
return ((di_parent_node(di) != DI_NODE_NIL) &&
(di_parent_node(di_parent_node(di)) == DI_NODE_NIL));
}
static int
add_prom_string_prop(di_prom_handle_t ph,
mmd_t *mdp, md_node_t *np, di_node_t di, char *name, char *alt_name)
{
int count;
char *pp_data = NULL;
char *str;
int rv = 0;
if (alt_name != NULL) {
count = di_prom_prop_lookup_strings(ph, di, alt_name, &pp_data);
}
if (pp_data == NULL) {
count = di_prom_prop_lookup_strings(ph, di, name, &pp_data);
}
if (count > 0 && pp_data != NULL) {
for (str = pp_data; count > 0; str += strlen(str) + 1)
count--;
rv = md_add_data_property(mdp,
np, name, str - pp_data, (uint8_t *)pp_data);
}
return (rv);
}
static int
add_prom_int_prop(di_prom_handle_t ph,
mmd_t *mdp, md_node_t *np, di_node_t di, char *name, char *alt_name)
{
int count;
int rv = 0;
int *pp_data = NULL;
if (alt_name != NULL) {
count = di_prom_prop_lookup_ints(ph, di, alt_name, &pp_data);
}
if (pp_data == NULL) {
count = di_prom_prop_lookup_ints(ph, di, name, &pp_data);
}
if (count > 0 && pp_data != NULL) {
ASSERT(count == 1);
rv = md_add_value_property(mdp, np, name, *pp_data);
}
return (rv);
}
static md_node_t *
link_device_node(mmd_t *mdp,
di_prom_handle_t ph, di_node_t di, md_node_t *node, char *path)
{
md_node_t *np;
np = md_link_new_node(mdp, "iodevice", node, "fwd", "back");
if (np == NULL)
return (NULL);
if (md_add_string_property(mdp, np, "dev_path", path) != 0)
goto fail;
if (add_prom_string_prop(ph, mdp, np, di, "device_type", NULL) != 0)
goto fail;
if (add_prom_string_prop(ph, mdp, np, di, "compatible", NULL) != 0)
goto fail;
if (add_prom_int_prop(ph,
mdp, np, di, "device-id", "real-device-id") != 0)
goto fail;
if (add_prom_int_prop(ph,
mdp, np, di, "vendor-id", "real-vendor-id") != 0)
goto fail;
if (add_prom_int_prop(ph,
mdp, np, di, "class-code", "real-class-code") != 0)
goto fail;
return (np);
fail:
md_free_node(mdp, np);
return (NULL);
}
static int
create_children(mmd_t *mdp,
di_prom_handle_t ph, md_node_t *md_parent, di_node_t di_parent)
{
md_node_t *md_node;
md_node_t *md_child;
di_node_t di_child;
char *path;
int rv;
path = di_devfs_path(di_parent);
if (path == NULL)
return (EIO);
md_node = link_device_node(mdp, ph, di_parent, md_parent, path);
di_devfs_path_free(path);
if (md_node == NULL) {
return (ENOMEM);
}
while ((di_child = di_child_node(di_parent)) != DI_NODE_NIL) {
path = di_devfs_path(di_child);
if (path != NULL) {
md_child = link_device_node(mdp,
ph, di_child, md_node, path);
di_devfs_path_free(path);
if (md_child == NULL) {
return (ENOMEM);
}
}
rv = create_peers(mdp, ph, md_node, di_child);
if (rv != 0)
return (rv);
md_node = md_child;
di_parent = di_child;
}
return (0);
}
static int
create_peers(mmd_t *mdp, di_prom_handle_t ph, md_node_t *node, di_node_t dev)
{
di_node_t di_peer;
int rv;
while ((di_peer = di_sibling_node(dev)) != DI_NODE_NIL) {
rv = create_children(mdp, ph, node, di_peer);
if (rv != 0)
return (rv);
dev = di_peer;
}
return (0);
}
static int
device_tree_to_md(mmd_t *mdp, md_node_t *top)
{
di_node_t node;
di_node_t root;
di_prom_handle_t ph;
int rv = 0;
root = di_init("/", DINFOSUBTREE | DINFOPROP);
if (root == DI_NODE_NIL) {
LDMA_ERR("di_init cannot find device tree root node.");
return (errno);
}
ph = di_prom_init();
if (ph == DI_PROM_HANDLE_NIL) {
LDMA_ERR("di_prom_init failed.");
di_fini(root);
return (errno);
}
node = di_child_node(root);
while (node != NULL) {
if (is_root_complex(ph, node)) {
rv = create_children(mdp, ph, top, node);
if (rv != 0)
break;
}
node = di_sibling_node(node);
}
di_prom_fini(ph);
di_fini(root);
return (rv);
}
static int
get_devinfo(uint8_t **mdpp, size_t *size)
{
mmd_t *mdp;
md_node_t *rootp;
size_t md_size;
uint8_t *md_bufp;
mdp = md_new_md();
if (mdp == NULL) {
return (ENOMEM);
}
rootp = md_new_node(mdp, "root");
if (rootp == NULL) {
md_destroy(mdp);
return (ENOMEM);
}
if (device_tree_to_md(mdp, rootp) != 0) {
md_destroy(mdp);
return (ENOMEM);
}
md_size = (int)md_gen_bin(mdp, &md_bufp);
if (md_size == 0) {
md_destroy(mdp);
return (EIO);
}
*mdpp = md_bufp;
*size = md_size;
md_destroy(mdp);
return (0);
}