diff options
Diffstat (limited to 'usr/src/cmd/hotplugd/hotplugd_info.c')
-rw-r--r-- | usr/src/cmd/hotplugd/hotplugd_info.c | 513 |
1 files changed, 513 insertions, 0 deletions
diff --git a/usr/src/cmd/hotplugd/hotplugd_info.c b/usr/src/cmd/hotplugd/hotplugd_info.c new file mode 100644 index 0000000000..2daaad6e91 --- /dev/null +++ b/usr/src/cmd/hotplugd/hotplugd_info.c @@ -0,0 +1,513 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <libdevinfo.h> +#include <libhotplug.h> +#include <libhotplug_impl.h> +#include <sys/sunddi.h> +#include <sys/ddi_hp.h> +#include "hotplugd_impl.h" + +/* + * Define a list of hotplug nodes. + * (Only used within this module.) + */ +typedef struct { + hp_node_t head; + hp_node_t prev; +} hp_node_list_t; + +/* + * Local functions. + */ +static int copy_devinfo(const char *, const char *, uint_t, + hp_node_t *); +static int copy_devices(hp_node_t, di_node_t, uint_t, hp_node_t *); +static int copy_hotplug(hp_node_t, di_node_t, const char *, uint_t, + hp_node_t *); +static char *base_path(const char *); +static int search_cb(di_node_t, void *); +static int check_search(di_node_t, uint_t); +static hp_node_t new_device_node(hp_node_t, di_node_t); +static hp_node_t new_hotplug_node(hp_node_t, di_hp_t); +static void node_list_add(hp_node_list_t *, hp_node_t); + +/* + * getinfo() + * + * Build a hotplug information snapshot. The path, connection, + * and flags indicate what information should be included. + */ +int +getinfo(const char *path, const char *connection, uint_t flags, hp_node_t *retp) +{ + hp_node_t root = NULL; + hp_node_t child; + char *basepath; + int rv; + + if ((path == NULL) || (retp == NULL)) + return (EINVAL); + + dprintf("getinfo: path=%s, connection=%s, flags=0x%x\n", path, + (connection == NULL) ? "NULL" : connection, flags); + + /* Allocate the base path */ + if ((basepath = base_path(path)) == NULL) + return (ENOMEM); + + /* Copy in device and hotplug nodes from libdevinfo */ + if ((rv = copy_devinfo(basepath, connection, flags, &root)) != 0) { + hp_fini(root); + free(basepath); + return (rv); + } + + /* Check if there were no connections */ + if (root == NULL) { + dprintf("getinfo: no hotplug connections.\n"); + free(basepath); + return (ENOENT); + } + + /* Special case: exclude root nexus from snapshot */ + if (strcmp(basepath, "/") == 0) { + child = root->hp_child; + if (root->hp_name != NULL) + free(root->hp_name); + free(root); + root = child; + for (child = root; child; child = child->hp_sibling) + child->hp_parent = NULL; + } + + /* Store a pointer to the base path in each root node */ + for (child = root; child != NULL; child = child->hp_sibling) + child->hp_basepath = basepath; + + /* Copy in usage information from RCM */ + if (flags & HPINFOUSAGE) { + if ((rv = copy_usage(root)) != 0) { + (void) hp_fini(root); + return (rv); + } + } + + *retp = root; + return (0); +} + +/* + * copy_devinfo() + * + * Copy information about device and hotplug nodes from libdevinfo. + * + * When path is set to "/", the results need to be limited only to + * branches that contain hotplug information. An initial search + * is performed to mark which branches contain hotplug nodes. + */ +static int +copy_devinfo(const char *path, const char *connection, uint_t flags, + hp_node_t *rootp) +{ + hp_node_t hp_root = NULL; + di_node_t di_root; + int rv; + + /* Get libdevinfo snapshot */ + if ((di_root = di_init(path, DINFOSUBTREE | DINFOHP)) == DI_NODE_NIL) + return (errno); + + /* Do initial search pass, if required */ + if (strcmp(path, "/") == 0) { + flags |= HPINFOSEARCH; + (void) di_walk_node(di_root, DI_WALK_CLDFIRST, NULL, search_cb); + } + + /* + * If a connection is specified, just copy immediate hotplug info. + * Else, copy the device tree normally. + */ + if (connection != NULL) + rv = copy_hotplug(NULL, di_root, connection, flags, &hp_root); + else + rv = copy_devices(NULL, di_root, flags, &hp_root); + + /* Destroy devinfo snapshot */ + di_fini(di_root); + + *rootp = (rv == 0) ? hp_root : NULL; + return (rv); +} + +/* + * copy_devices() + * + * Copy a full branch of device nodes. Used by copy_devinfo() and + * copy_hotplug(). + */ +static int +copy_devices(hp_node_t parent, di_node_t dev, uint_t flags, hp_node_t *rootp) +{ + hp_node_list_t children; + hp_node_t self, branch; + di_node_t child; + int rv = 0; + + /* Initialize results */ + *rootp = NULL; + + /* Enforce search semantics */ + if (check_search(dev, flags) == 0) + return (0); + + /* Allocate new node for current device */ + if ((self = new_device_node(parent, dev)) == NULL) + return (ENOMEM); + + /* + * If the device has hotplug nodes, then use copy_hotplug() + * instead to build the branch associated with current device. + */ + if (di_hp_next(dev, DI_HP_NIL) != DI_HP_NIL) { + if ((rv = copy_hotplug(self, dev, NULL, flags, + &self->hp_child)) != 0) { + free(self); + return (rv); + } + *rootp = self; + return (0); + } + + /* + * The device does not have hotplug nodes. Use normal + * approach of iterating through its child device nodes. + */ + (void) memset(&children, 0, sizeof (hp_node_list_t)); + for (child = di_child_node(dev); child != DI_NODE_NIL; + child = di_sibling_node(child)) { + branch = NULL; + if ((rv = copy_devices(self, child, flags, &branch)) != 0) { + (void) hp_fini(children.head); + free(self); + return (rv); + } + if (branch != NULL) + node_list_add(&children, branch); + } + self->hp_child = children.head; + + /* Done */ + *rootp = self; + return (0); +} + +/* + * copy_hotplug() + * + * Copy a full branch of hotplug nodes. Used by copy_devinfo() + * and copy_devices(). + * + * If a connection is specified, the results are limited only + * to the branch associated with that specific connection. + */ +static int +copy_hotplug(hp_node_t parent, di_node_t dev, const char *connection, + uint_t flags, hp_node_t *retp) +{ + hp_node_list_t connections, ports; + hp_node_t node, port_node; + di_node_t child_dev; + di_hp_t hp, port_hp; + uint_t child_flags; + int rv, physnum; + + /* Stop implementing the HPINFOSEARCH flag */ + child_flags = flags & ~(HPINFOSEARCH); + + /* Clear lists of discovered ports and connections */ + (void) memset(&ports, 0, sizeof (hp_node_list_t)); + (void) memset(&connections, 0, sizeof (hp_node_list_t)); + + /* + * Scan virtual ports. + * + * If a connection is specified and it matches a virtual port, + * this will build the branch associated with that connection. + * Else, this will only build branches for virtual ports that + * are not associated with a physical connector. + */ + for (hp = DI_HP_NIL; (hp = di_hp_next(dev, hp)) != DI_HP_NIL; ) { + + /* Ignore connectors */ + if (di_hp_type(hp) != DDI_HP_CN_TYPE_VIRTUAL_PORT) + continue; + + /* + * Ignore ports associated with connectors, unless + * a specific connection is being sought. + */ + if ((connection == NULL) && (di_hp_depends_on(hp) != -1)) + continue; + + /* If a connection is specified, ignore non-matching ports */ + if ((connection != NULL) && + (strcmp(di_hp_name(hp), connection) != 0)) + continue; + + /* Create a new port node */ + if ((node = new_hotplug_node(parent, hp)) == NULL) { + rv = ENOMEM; + goto fail; + } + + /* Add port node to connection list */ + node_list_add(&connections, node); + + /* Add branch of child devices to port node */ + if ((child_dev = di_hp_child(hp)) != DI_NODE_NIL) + if ((rv = copy_devices(node, child_dev, child_flags, + &node->hp_child)) != 0) + goto fail; + } + + /* + * Scan physical connectors. + * + * If a connection is specified, the results will be limited + * only to the branch associated with that connection. + */ + for (hp = DI_HP_NIL; (hp = di_hp_next(dev, hp)) != DI_HP_NIL; ) { + + /* Ignore ports */ + if (di_hp_type(hp) == DDI_HP_CN_TYPE_VIRTUAL_PORT) + continue; + + /* If a connection is specified, ignore non-matching ports */ + if ((connection != NULL) && + (strcmp(di_hp_name(hp), connection) != 0)) + continue; + + /* Create a new connector node */ + if ((node = new_hotplug_node(parent, hp)) == NULL) { + rv = ENOMEM; + goto fail; + } + + /* Add connector node to connection list */ + node_list_add(&connections, node); + + /* Add branches of associated port nodes */ + physnum = di_hp_connection(hp); + port_hp = DI_HP_NIL; + while ((port_hp = di_hp_next(dev, port_hp)) != DI_HP_NIL) { + + /* Ignore irrelevant connections */ + if (di_hp_depends_on(port_hp) != physnum) + continue; + + /* Add new port node to port list */ + if ((port_node = new_hotplug_node(node, + port_hp)) == NULL) { + rv = ENOMEM; + goto fail; + } + node_list_add(&ports, port_node); + + /* Add branch of child devices */ + if ((child_dev = di_hp_child(port_hp)) != DI_NODE_NIL) { + if ((rv = copy_devices(port_node, child_dev, + child_flags, &port_node->hp_child)) != 0) + goto fail; + } + } + node->hp_child = ports.head; + (void) memset(&ports, 0, sizeof (hp_node_list_t)); + } + + if (connections.head == NULL) + return (ENXIO); + *retp = connections.head; + return (0); + +fail: + (void) hp_fini(ports.head); + (void) hp_fini(connections.head); + return (rv); +} + +/* + * base_path() + * + * Normalize the base path of a hotplug information snapshot. + * The caller must free the string that is allocated. + */ +static char * +base_path(const char *path) +{ + char *base_path; + size_t devices_len; + + devices_len = strlen(S_DEVICES); + + if (strncmp(path, S_DEVICES, devices_len) == 0) + base_path = strdup(&path[devices_len]); + else + base_path = strdup(path); + + return (base_path); +} + +/* + * search_cb() + * + * Callback function used by di_walk_node() to search for branches + * of the libdevinfo snapshot that contain hotplug nodes. + */ +/*ARGSUSED*/ +static int +search_cb(di_node_t node, void *arg) +{ + di_node_t parent; + uint_t flags; + + (void) di_node_private_set(node, (void *)(uintptr_t)0); + + if (di_hp_next(node, DI_HP_NIL) == DI_HP_NIL) + return (DI_WALK_CONTINUE); + + for (parent = node; parent != DI_NODE_NIL; + parent = di_parent_node(parent)) { + flags = (uint_t)(uintptr_t)di_node_private_get(parent); + flags |= HPINFOSEARCH; + (void) di_node_private_set(parent, (void *)(uintptr_t)flags); + } + + return (DI_WALK_CONTINUE); +} + +/* + * check_search() + * + * Check if a device node was marked by an initial search pass. + */ +static int +check_search(di_node_t dev, uint_t flags) +{ + uint_t dev_flags; + + if (flags & HPINFOSEARCH) { + dev_flags = (uint_t)(uintptr_t)di_node_private_get(dev); + if ((dev_flags & HPINFOSEARCH) == 0) + return (0); + } + + return (1); +} + +/* + * node_list_add() + * + * Utility function to append one node to a list of hotplug nodes. + */ +static void +node_list_add(hp_node_list_t *listp, hp_node_t node) +{ + if (listp->prev != NULL) + listp->prev->hp_sibling = node; + else + listp->head = node; + + listp->prev = node; +} + +/* + * new_device_node() + * + * Build a new hotplug node based on a specified devinfo node. + */ +static hp_node_t +new_device_node(hp_node_t parent, di_node_t dev) +{ + hp_node_t node; + char *node_name, *bus_addr; + char name[MAXPATHLEN]; + + node = (hp_node_t)calloc(1, sizeof (struct hp_node)); + + if (node != NULL) { + node->hp_parent = parent; + node->hp_type = HP_NODE_DEVICE; + + node_name = di_node_name(dev); + bus_addr = di_bus_addr(dev); + if (bus_addr && (strlen(bus_addr) > 0)) { + if (snprintf(name, sizeof (name), "%s@%s", node_name, + bus_addr) >= sizeof (name)) { + log_err("Path too long for device node.\n"); + free(node); + return (NULL); + } + node->hp_name = strdup(name); + } else + node->hp_name = strdup(node_name); + } + + return (node); +} + +/* + * new_hotplug_node() + * + * Build a new hotplug node based on a specified devinfo hotplug node. + */ +static hp_node_t +new_hotplug_node(hp_node_t parent, di_hp_t hp) +{ + hp_node_t node; + char *s; + + node = (hp_node_t)calloc(1, sizeof (struct hp_node)); + + if (node != NULL) { + node->hp_parent = parent; + node->hp_state = di_hp_state(hp); + node->hp_last_change = di_hp_last_change(hp); + if ((s = di_hp_name(hp)) != NULL) + node->hp_name = strdup(s); + if ((s = di_hp_description(hp)) != NULL) + node->hp_description = strdup(s); + if (di_hp_type(hp) == DDI_HP_CN_TYPE_VIRTUAL_PORT) + node->hp_type = HP_NODE_PORT; + else + node->hp_type = HP_NODE_CONNECTOR; + } + + return (node); +} |