1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <string.h> 29 #include <errno.h> 30 #include <libdevinfo.h> 31 #include <libhotplug.h> 32 #include <libhotplug_impl.h> 33 #include <sys/sunddi.h> 34 #include <sys/ddi_hp.h> 35 #include "hotplugd_impl.h" 36 37 /* 38 * Define a list of hotplug nodes. 39 * (Only used within this module.) 40 */ 41 typedef struct { 42 hp_node_t head; 43 hp_node_t prev; 44 } hp_node_list_t; 45 46 /* 47 * Local functions. 48 */ 49 static int copy_devinfo(const char *, const char *, uint_t, 50 hp_node_t *); 51 static int copy_devices(hp_node_t, di_node_t, uint_t, hp_node_t *); 52 static int copy_hotplug(hp_node_t, di_node_t, const char *, uint_t, 53 hp_node_t *); 54 static char *base_path(const char *); 55 static int search_cb(di_node_t, void *); 56 static int check_search(di_node_t, uint_t); 57 static hp_node_t new_device_node(hp_node_t, di_node_t); 58 static hp_node_t new_hotplug_node(hp_node_t, di_hp_t); 59 static void node_list_add(hp_node_list_t *, hp_node_t); 60 61 /* 62 * getinfo() 63 * 64 * Build a hotplug information snapshot. The path, connection, 65 * and flags indicate what information should be included. 66 */ 67 int 68 getinfo(const char *path, const char *connection, uint_t flags, hp_node_t *retp) 69 { 70 hp_node_t root = NULL; 71 hp_node_t child; 72 char *basepath; 73 int rv; 74 75 if ((path == NULL) || (retp == NULL)) 76 return (EINVAL); 77 78 dprintf("getinfo: path=%s, connection=%s, flags=0x%x\n", path, 79 (connection == NULL) ? "NULL" : connection, flags); 80 81 /* Allocate the base path */ 82 if ((basepath = base_path(path)) == NULL) 83 return (ENOMEM); 84 85 /* Copy in device and hotplug nodes from libdevinfo */ 86 if ((rv = copy_devinfo(basepath, connection, flags, &root)) != 0) { 87 hp_fini(root); 88 free(basepath); 89 return (rv); 90 } 91 92 /* Check if there were no connections */ 93 if (root == NULL) { 94 dprintf("getinfo: no hotplug connections.\n"); 95 free(basepath); 96 return (ENOENT); 97 } 98 99 /* Special case: exclude root nexus from snapshot */ 100 if (strcmp(basepath, "/") == 0) { 101 child = root->hp_child; 102 if (root->hp_name != NULL) 103 free(root->hp_name); 104 free(root); 105 root = child; 106 for (child = root; child; child = child->hp_sibling) 107 child->hp_parent = NULL; 108 } 109 110 /* Store a pointer to the base path in each root node */ 111 for (child = root; child != NULL; child = child->hp_sibling) 112 child->hp_basepath = basepath; 113 114 /* Copy in usage information from RCM */ 115 if (flags & HPINFOUSAGE) { 116 if ((rv = copy_usage(root)) != 0) { 117 (void) hp_fini(root); 118 return (rv); 119 } 120 } 121 122 *retp = root; 123 return (0); 124 } 125 126 /* 127 * copy_devinfo() 128 * 129 * Copy information about device and hotplug nodes from libdevinfo. 130 * 131 * When path is set to "/", the results need to be limited only to 132 * branches that contain hotplug information. An initial search 133 * is performed to mark which branches contain hotplug nodes. 134 */ 135 static int 136 copy_devinfo(const char *path, const char *connection, uint_t flags, 137 hp_node_t *rootp) 138 { 139 hp_node_t hp_root = NULL; 140 di_node_t di_root; 141 int rv; 142 143 /* Get libdevinfo snapshot */ 144 if ((di_root = di_init(path, DINFOSUBTREE | DINFOHP)) == DI_NODE_NIL) 145 return (errno); 146 147 /* Do initial search pass, if required */ 148 if (strcmp(path, "/") == 0) { 149 flags |= HPINFOSEARCH; 150 (void) di_walk_node(di_root, DI_WALK_CLDFIRST, NULL, search_cb); 151 } 152 153 /* 154 * If a connection is specified, just copy immediate hotplug info. 155 * Else, copy the device tree normally. 156 */ 157 if (connection != NULL) 158 rv = copy_hotplug(NULL, di_root, connection, flags, &hp_root); 159 else 160 rv = copy_devices(NULL, di_root, flags, &hp_root); 161 162 /* Destroy devinfo snapshot */ 163 di_fini(di_root); 164 165 *rootp = (rv == 0) ? hp_root : NULL; 166 return (rv); 167 } 168 169 /* 170 * copy_devices() 171 * 172 * Copy a full branch of device nodes. Used by copy_devinfo() and 173 * copy_hotplug(). 174 */ 175 static int 176 copy_devices(hp_node_t parent, di_node_t dev, uint_t flags, hp_node_t *rootp) 177 { 178 hp_node_list_t children; 179 hp_node_t self, branch; 180 di_node_t child; 181 int rv = 0; 182 183 /* Initialize results */ 184 *rootp = NULL; 185 186 /* Enforce search semantics */ 187 if (check_search(dev, flags) == 0) 188 return (0); 189 190 /* Allocate new node for current device */ 191 if ((self = new_device_node(parent, dev)) == NULL) 192 return (ENOMEM); 193 194 /* 195 * If the device has hotplug nodes, then use copy_hotplug() 196 * instead to build the branch associated with current device. 197 */ 198 if (di_hp_next(dev, DI_HP_NIL) != DI_HP_NIL) { 199 if ((rv = copy_hotplug(self, dev, NULL, flags, 200 &self->hp_child)) != 0) { 201 free(self); 202 return (rv); 203 } 204 *rootp = self; 205 return (0); 206 } 207 208 /* 209 * The device does not have hotplug nodes. Use normal 210 * approach of iterating through its child device nodes. 211 */ 212 (void) memset(&children, 0, sizeof (hp_node_list_t)); 213 for (child = di_child_node(dev); child != DI_NODE_NIL; 214 child = di_sibling_node(child)) { 215 branch = NULL; 216 if ((rv = copy_devices(self, child, flags, &branch)) != 0) { 217 (void) hp_fini(children.head); 218 free(self); 219 return (rv); 220 } 221 if (branch != NULL) 222 node_list_add(&children, branch); 223 } 224 self->hp_child = children.head; 225 226 /* Done */ 227 *rootp = self; 228 return (0); 229 } 230 231 /* 232 * copy_hotplug() 233 * 234 * Copy a full branch of hotplug nodes. Used by copy_devinfo() 235 * and copy_devices(). 236 * 237 * If a connection is specified, the results are limited only 238 * to the branch associated with that specific connection. 239 */ 240 static int 241 copy_hotplug(hp_node_t parent, di_node_t dev, const char *connection, 242 uint_t flags, hp_node_t *retp) 243 { 244 hp_node_list_t connections, ports; 245 hp_node_t node, port_node; 246 di_node_t child_dev; 247 di_hp_t hp, port_hp; 248 uint_t child_flags; 249 int rv, physnum; 250 251 /* Stop implementing the HPINFOSEARCH flag */ 252 child_flags = flags & ~(HPINFOSEARCH); 253 254 /* Clear lists of discovered ports and connections */ 255 (void) memset(&ports, 0, sizeof (hp_node_list_t)); 256 (void) memset(&connections, 0, sizeof (hp_node_list_t)); 257 258 /* 259 * Scan virtual ports. 260 * 261 * If a connection is specified and it matches a virtual port, 262 * this will build the branch associated with that connection. 263 * Else, this will only build branches for virtual ports that 264 * are not associated with a physical connector. 265 */ 266 for (hp = DI_HP_NIL; (hp = di_hp_next(dev, hp)) != DI_HP_NIL; ) { 267 268 /* Ignore connectors */ 269 if (di_hp_type(hp) != DDI_HP_CN_TYPE_VIRTUAL_PORT) 270 continue; 271 272 /* 273 * Ignore ports associated with connectors, unless 274 * a specific connection is being sought. 275 */ 276 if ((connection == NULL) && (di_hp_depends_on(hp) != -1)) 277 continue; 278 279 /* If a connection is specified, ignore non-matching ports */ 280 if ((connection != NULL) && 281 (strcmp(di_hp_name(hp), connection) != 0)) 282 continue; 283 284 /* Create a new port node */ 285 if ((node = new_hotplug_node(parent, hp)) == NULL) { 286 rv = ENOMEM; 287 goto fail; 288 } 289 290 /* Add port node to connection list */ 291 node_list_add(&connections, node); 292 293 /* Add branch of child devices to port node */ 294 if ((child_dev = di_hp_child(hp)) != DI_NODE_NIL) 295 if ((rv = copy_devices(node, child_dev, child_flags, 296 &node->hp_child)) != 0) 297 goto fail; 298 } 299 300 /* 301 * Scan physical connectors. 302 * 303 * If a connection is specified, the results will be limited 304 * only to the branch associated with that connection. 305 */ 306 for (hp = DI_HP_NIL; (hp = di_hp_next(dev, hp)) != DI_HP_NIL; ) { 307 308 /* Ignore ports */ 309 if (di_hp_type(hp) == DDI_HP_CN_TYPE_VIRTUAL_PORT) 310 continue; 311 312 /* If a connection is specified, ignore non-matching ports */ 313 if ((connection != NULL) && 314 (strcmp(di_hp_name(hp), connection) != 0)) 315 continue; 316 317 /* Create a new connector node */ 318 if ((node = new_hotplug_node(parent, hp)) == NULL) { 319 rv = ENOMEM; 320 goto fail; 321 } 322 323 /* Add connector node to connection list */ 324 node_list_add(&connections, node); 325 326 /* Add branches of associated port nodes */ 327 physnum = di_hp_connection(hp); 328 port_hp = DI_HP_NIL; 329 while ((port_hp = di_hp_next(dev, port_hp)) != DI_HP_NIL) { 330 331 /* Ignore irrelevant connections */ 332 if (di_hp_depends_on(port_hp) != physnum) 333 continue; 334 335 /* Add new port node to port list */ 336 if ((port_node = new_hotplug_node(node, 337 port_hp)) == NULL) { 338 rv = ENOMEM; 339 goto fail; 340 } 341 node_list_add(&ports, port_node); 342 343 /* Add branch of child devices */ 344 if ((child_dev = di_hp_child(port_hp)) != DI_NODE_NIL) { 345 if ((rv = copy_devices(port_node, child_dev, 346 child_flags, &port_node->hp_child)) != 0) 347 goto fail; 348 } 349 } 350 node->hp_child = ports.head; 351 (void) memset(&ports, 0, sizeof (hp_node_list_t)); 352 } 353 354 if (connections.head == NULL) 355 return (ENXIO); 356 *retp = connections.head; 357 return (0); 358 359 fail: 360 (void) hp_fini(ports.head); 361 (void) hp_fini(connections.head); 362 return (rv); 363 } 364 365 /* 366 * base_path() 367 * 368 * Normalize the base path of a hotplug information snapshot. 369 * The caller must free the string that is allocated. 370 */ 371 static char * 372 base_path(const char *path) 373 { 374 char *base_path; 375 size_t devices_len; 376 377 devices_len = strlen(S_DEVICES); 378 379 if (strncmp(path, S_DEVICES, devices_len) == 0) 380 base_path = strdup(&path[devices_len]); 381 else 382 base_path = strdup(path); 383 384 return (base_path); 385 } 386 387 /* 388 * search_cb() 389 * 390 * Callback function used by di_walk_node() to search for branches 391 * of the libdevinfo snapshot that contain hotplug nodes. 392 */ 393 /*ARGSUSED*/ 394 static int 395 search_cb(di_node_t node, void *arg) 396 { 397 di_node_t parent; 398 uint_t flags; 399 400 (void) di_node_private_set(node, (void *)(uintptr_t)0); 401 402 if (di_hp_next(node, DI_HP_NIL) == DI_HP_NIL) 403 return (DI_WALK_CONTINUE); 404 405 for (parent = node; parent != DI_NODE_NIL; 406 parent = di_parent_node(parent)) { 407 flags = (uint_t)(uintptr_t)di_node_private_get(parent); 408 flags |= HPINFOSEARCH; 409 (void) di_node_private_set(parent, (void *)(uintptr_t)flags); 410 } 411 412 return (DI_WALK_CONTINUE); 413 } 414 415 /* 416 * check_search() 417 * 418 * Check if a device node was marked by an initial search pass. 419 */ 420 static int 421 check_search(di_node_t dev, uint_t flags) 422 { 423 uint_t dev_flags; 424 425 if (flags & HPINFOSEARCH) { 426 dev_flags = (uint_t)(uintptr_t)di_node_private_get(dev); 427 if ((dev_flags & HPINFOSEARCH) == 0) 428 return (0); 429 } 430 431 return (1); 432 } 433 434 /* 435 * node_list_add() 436 * 437 * Utility function to append one node to a list of hotplug nodes. 438 */ 439 static void 440 node_list_add(hp_node_list_t *listp, hp_node_t node) 441 { 442 if (listp->prev != NULL) 443 listp->prev->hp_sibling = node; 444 else 445 listp->head = node; 446 447 listp->prev = node; 448 } 449 450 /* 451 * new_device_node() 452 * 453 * Build a new hotplug node based on a specified devinfo node. 454 */ 455 static hp_node_t 456 new_device_node(hp_node_t parent, di_node_t dev) 457 { 458 hp_node_t node; 459 char *node_name, *bus_addr; 460 char name[MAXPATHLEN]; 461 462 node = (hp_node_t)calloc(1, sizeof (struct hp_node)); 463 464 if (node != NULL) { 465 node->hp_parent = parent; 466 node->hp_type = HP_NODE_DEVICE; 467 468 node_name = di_node_name(dev); 469 bus_addr = di_bus_addr(dev); 470 if (bus_addr && (strlen(bus_addr) > 0)) { 471 if (snprintf(name, sizeof (name), "%s@%s", node_name, 472 bus_addr) >= sizeof (name)) { 473 log_err("Path too long for device node.\n"); 474 free(node); 475 return (NULL); 476 } 477 node->hp_name = strdup(name); 478 } else 479 node->hp_name = strdup(node_name); 480 } 481 482 return (node); 483 } 484 485 /* 486 * new_hotplug_node() 487 * 488 * Build a new hotplug node based on a specified devinfo hotplug node. 489 */ 490 static hp_node_t 491 new_hotplug_node(hp_node_t parent, di_hp_t hp) 492 { 493 hp_node_t node; 494 char *s; 495 496 node = (hp_node_t)calloc(1, sizeof (struct hp_node)); 497 498 if (node != NULL) { 499 node->hp_parent = parent; 500 node->hp_state = di_hp_state(hp); 501 node->hp_last_change = di_hp_last_change(hp); 502 if ((s = di_hp_name(hp)) != NULL) 503 node->hp_name = strdup(s); 504 if ((s = di_hp_description(hp)) != NULL) 505 node->hp_description = strdup(s); 506 if (di_hp_type(hp) == DDI_HP_CN_TYPE_VIRTUAL_PORT) 507 node->hp_type = HP_NODE_PORT; 508 else 509 node->hp_type = HP_NODE_CONNECTOR; 510 } 511 512 return (node); 513 } 514