/* * 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); }