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 /* 23 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #include <stdio.h> 28 #include <stdlib.h> 29 #include <unistd.h> 30 #include <errno.h> 31 #include <string.h> 32 #include <librcm.h> 33 #include <libhotplug.h> 34 #include <libhotplug_impl.h> 35 #include <sys/sunddi.h> 36 #include <sys/ddi_hp.h> 37 #include "hotplugd_impl.h" 38 39 /* 40 * Define structures for a path-to-usage lookup table. 41 */ 42 typedef struct info_entry { 43 char *rsrc; 44 char *usage; 45 struct info_entry *next; 46 } info_entry_t; 47 48 typedef struct { 49 char *path; 50 info_entry_t *entries; 51 } info_table_t; 52 53 /* 54 * Define callback argument used when getting resources. 55 */ 56 typedef struct { 57 int error; 58 int n_rsrcs; 59 char **rsrcs; 60 char path[MAXPATHLEN]; 61 char connection[MAXPATHLEN]; 62 char dev_path[MAXPATHLEN]; 63 } resource_cb_arg_t; 64 65 /* 66 * Define callback argument used when merging info. 67 */ 68 typedef struct { 69 int error; 70 info_table_t *table; 71 size_t table_len; 72 char path[MAXPATHLEN]; 73 char connection[MAXPATHLEN]; 74 } merge_cb_arg_t; 75 76 /* 77 * Local functions. 78 */ 79 static int merge_rcm_info(hp_node_t root, rcm_info_t *info); 80 static int get_rcm_usage(char **rsrcs, rcm_info_t **info_p); 81 static int build_table(rcm_info_t *info, info_table_t **tablep, 82 size_t *table_lenp); 83 static void free_table(info_table_t *table, size_t table_len); 84 static int resource_callback(hp_node_t node, void *argp); 85 static int merge_callback(hp_node_t node, void *argp); 86 static int rsrc2path(const char *rsrc, char *path); 87 static int compare_info(const void *a, const void *b); 88 89 /* 90 * copy_usage() 91 * 92 * Given an information snapshot, get the corresponding 93 * RCM usage information and merge it into the snapshot. 94 */ 95 int 96 copy_usage(hp_node_t root) 97 { 98 rcm_info_t *info = NULL; 99 char **rsrcs = NULL; 100 int rv; 101 102 /* Get resource names */ 103 if ((rv = rcm_resources(root, &rsrcs)) != 0) { 104 log_err("Cannot get RCM resources (%s)\n", strerror(rv)); 105 return (rv); 106 } 107 108 /* Do nothing if no resources */ 109 if (rsrcs == NULL) 110 return (0); 111 112 /* Get RCM usage information */ 113 if ((rv = get_rcm_usage(rsrcs, &info)) != 0) { 114 log_err("Cannot get RCM information (%s)\n", strerror(rv)); 115 free_rcm_resources(rsrcs); 116 return (rv); 117 } 118 119 /* Done with resource names */ 120 free_rcm_resources(rsrcs); 121 122 /* If there is RCM usage information, merge it in */ 123 if (info != NULL) { 124 rv = merge_rcm_info(root, info); 125 rcm_free_info(info); 126 return (rv); 127 } 128 129 return (0); 130 } 131 132 /* 133 * rcm_resources() 134 * 135 * Given the root of a hotplug information snapshot, 136 * construct a list of RCM compatible resource names. 137 */ 138 int 139 rcm_resources(hp_node_t root, char ***rsrcsp) 140 { 141 resource_cb_arg_t arg; 142 143 /* Initialize results */ 144 *rsrcsp = NULL; 145 146 /* Traverse snapshot to get resources */ 147 (void) memset(&arg, 0, sizeof (resource_cb_arg_t)); 148 (void) hp_traverse(root, &arg, resource_callback); 149 150 /* Check for errors */ 151 if (arg.error != 0) { 152 free_rcm_resources(arg.rsrcs); 153 return (arg.error); 154 } 155 156 /* Success */ 157 *rsrcsp = arg.rsrcs; 158 return (0); 159 } 160 161 /* 162 * free_rcm_resources() 163 * 164 * Free a table of RCM resource names. 165 */ 166 void 167 free_rcm_resources(char **rsrcs) 168 { 169 int i; 170 171 if (rsrcs != NULL) { 172 for (i = 0; rsrcs[i] != NULL; i++) 173 free(rsrcs[i]); 174 free(rsrcs); 175 } 176 } 177 178 /* 179 * rcm_offline() 180 * 181 * Implement an RCM offline request. 182 * 183 * NOTE: errors from RCM will be merged into the snapshot. 184 */ 185 int 186 rcm_offline(char **rsrcs, uint_t flags, hp_node_t root) 187 { 188 rcm_handle_t *handle; 189 rcm_info_t *info = NULL; 190 uint_t rcm_flags = 0; 191 int rv = 0; 192 193 dprintf("rcm_offline()\n"); 194 195 /* Set flags */ 196 if (flags & HPFORCE) 197 rcm_flags |= RCM_FORCE; 198 if (flags & HPQUERY) 199 rcm_flags |= RCM_QUERY; 200 201 /* Allocate RCM handle */ 202 if (rcm_alloc_handle(NULL, 0, NULL, &handle) != RCM_SUCCESS) { 203 log_err("Cannot allocate RCM handle (%s)\n", strerror(errno)); 204 return (EFAULT); 205 } 206 207 /* Request RCM offline */ 208 if (rcm_request_offline_list(handle, rsrcs, rcm_flags, 209 &info) != RCM_SUCCESS) 210 rv = EBUSY; 211 212 /* RCM handle is no longer needed */ 213 (void) rcm_free_handle(handle); 214 215 /* 216 * Check if RCM returned any information tuples. If so, 217 * then also check if the RCM operation failed, and possibly 218 * merge the RCM info into the caller's hotplug snapshot. 219 */ 220 if (info != NULL) { 221 if (rv != 0) 222 (void) merge_rcm_info(root, info); 223 rcm_free_info(info); 224 } 225 226 return (rv); 227 } 228 229 /* 230 * rcm_online() 231 * 232 * Implement an RCM online notification. 233 */ 234 void 235 rcm_online(char **rsrcs) 236 { 237 rcm_handle_t *handle; 238 rcm_info_t *info = NULL; 239 240 dprintf("rcm_online()\n"); 241 242 if (rcm_alloc_handle(NULL, 0, NULL, &handle) != RCM_SUCCESS) { 243 log_err("Cannot allocate RCM handle (%s)\n", strerror(errno)); 244 return; 245 } 246 247 (void) rcm_notify_online_list(handle, rsrcs, 0, &info); 248 249 (void) rcm_free_handle(handle); 250 251 if (info != NULL) 252 rcm_free_info(info); 253 } 254 255 /* 256 * rcm_remove() 257 * 258 * Implement an RCM remove notification. 259 */ 260 void 261 rcm_remove(char **rsrcs) 262 { 263 rcm_handle_t *handle; 264 rcm_info_t *info = NULL; 265 266 dprintf("rcm_remove()\n"); 267 268 if (rcm_alloc_handle(NULL, 0, NULL, &handle) != RCM_SUCCESS) { 269 log_err("Cannot allocate RCM handle (%s)\n", strerror(errno)); 270 return; 271 } 272 273 (void) rcm_notify_remove_list(handle, rsrcs, 0, &info); 274 275 (void) rcm_free_handle(handle); 276 277 if (info != NULL) 278 rcm_free_info(info); 279 } 280 281 /* 282 * get_rcm_usage() 283 * 284 * Lookup usage information for a set of resources from RCM. 285 */ 286 static int 287 get_rcm_usage(char **rsrcs, rcm_info_t **info_p) 288 { 289 rcm_handle_t *handle; 290 rcm_info_t *info = NULL; 291 int rv = 0; 292 293 /* No-op if no RCM resources */ 294 if (rsrcs == NULL) 295 return (0); 296 297 /* Allocate RCM handle */ 298 if (rcm_alloc_handle(NULL, RCM_NOPID, NULL, &handle) != RCM_SUCCESS) { 299 log_err("Cannot allocate RCM handle (%s)\n", strerror(errno)); 300 return (EFAULT); 301 } 302 303 /* Get usage information from RCM */ 304 if (rcm_get_info_list(handle, rsrcs, 305 RCM_INCLUDE_DEPENDENT | RCM_INCLUDE_SUBTREE, 306 &info) != RCM_SUCCESS) { 307 log_err("Failed to get RCM information (%s)\n", 308 strerror(errno)); 309 rv = EFAULT; 310 } 311 312 /* RCM handle is no longer needed */ 313 (void) rcm_free_handle(handle); 314 315 *info_p = info; 316 return (rv); 317 } 318 319 /* 320 * merge_rcm_info() 321 * 322 * Merge RCM information into a hotplug information snapshot. 323 * First a lookup table is built to map lists of RCM usage to 324 * pathnames. Then during a full traversal of the snapshot, 325 * the lookup table is used for each node to find matching 326 * RCM info tuples for each path in the snapshot. 327 */ 328 static int 329 merge_rcm_info(hp_node_t root, rcm_info_t *info) 330 { 331 merge_cb_arg_t arg; 332 info_table_t *table; 333 size_t table_len; 334 int rv; 335 336 /* Build a lookup table, mapping paths to usage information */ 337 if ((rv = build_table(info, &table, &table_len)) != 0) { 338 log_err("Cannot build RCM lookup table (%s)\n", strerror(rv)); 339 return (rv); 340 } 341 342 /* Stop if no valid entries were inserted in table */ 343 if ((table == NULL) || (table_len == 0)) { 344 log_err("Unable to gather RCM usage.\n"); 345 return (0); 346 } 347 348 /* Initialize callback argument */ 349 (void) memset(&arg, 0, sizeof (merge_cb_arg_t)); 350 arg.table = table; 351 arg.table_len = table_len; 352 353 /* Perform a merge traversal */ 354 (void) hp_traverse(root, (void *)&arg, merge_callback); 355 356 /* Done with the table */ 357 free_table(table, table_len); 358 359 /* Check for errors */ 360 if (arg.error != 0) { 361 log_err("Cannot merge RCM information (%s)\n", strerror(rv)); 362 return (rv); 363 } 364 365 return (0); 366 } 367 368 /* 369 * resource_callback() 370 * 371 * A callback function for hp_traverse() that builds an RCM 372 * compatible list of resource path names. The array has 373 * been pre-allocated based on results from the related 374 * callback resource_count_callback(). 375 */ 376 static int 377 resource_callback(hp_node_t node, void *argp) 378 { 379 resource_cb_arg_t *arg = (resource_cb_arg_t *)argp; 380 char **new_rsrcs; 381 size_t new_size; 382 int type; 383 384 /* Get node type */ 385 type = hp_type(node); 386 387 /* Prune OFFLINE ports */ 388 if ((type == HP_NODE_PORT) && HP_IS_OFFLINE(hp_state(node))) 389 return (HP_WALK_PRUNECHILD); 390 391 /* Skip past non-devices */ 392 if (type != HP_NODE_DEVICE) 393 return (HP_WALK_CONTINUE); 394 395 /* Lookup resource path */ 396 if (hp_path(node, arg->path, arg->connection) != 0) { 397 log_err("Cannot get RCM resource path.\n"); 398 arg->error = EFAULT; 399 return (HP_WALK_TERMINATE); 400 } 401 402 /* Insert "/devices" to path name */ 403 (void) snprintf(arg->dev_path, MAXPATHLEN, "/devices%s", arg->path); 404 405 /* 406 * Grow resource array to accomodate new /devices path. 407 * NOTE: include an extra NULL pointer at end of array. 408 */ 409 new_size = (arg->n_rsrcs + 2) * sizeof (char *); 410 if (arg->rsrcs == NULL) 411 new_rsrcs = (char **)malloc(new_size); 412 else 413 new_rsrcs = (char **)realloc(arg->rsrcs, new_size); 414 if (new_rsrcs != NULL) { 415 arg->rsrcs = new_rsrcs; 416 } else { 417 log_err("Cannot allocate RCM resource array.\n"); 418 arg->error = ENOMEM; 419 return (HP_WALK_TERMINATE); 420 } 421 422 /* Initialize new entries */ 423 arg->rsrcs[arg->n_rsrcs] = strdup(arg->dev_path); 424 arg->rsrcs[arg->n_rsrcs + 1] = NULL; 425 426 /* Check for errors */ 427 if (arg->rsrcs[arg->n_rsrcs] == NULL) { 428 log_err("Cannot allocate RCM resource path.\n"); 429 arg->error = ENOMEM; 430 return (HP_WALK_TERMINATE); 431 } 432 433 /* Increment resource count */ 434 arg->n_rsrcs += 1; 435 436 /* Do not visit children */ 437 return (HP_WALK_PRUNECHILD); 438 } 439 440 /* 441 * merge_callback() 442 * 443 * A callback function for hp_traverse() that merges RCM information 444 * tuples into an existing hotplug information snapshot. The RCM 445 * information will be turned into HP_NODE_USAGE nodes. 446 */ 447 static int 448 merge_callback(hp_node_t node, void *argp) 449 { 450 merge_cb_arg_t *arg = (merge_cb_arg_t *)argp; 451 hp_node_t usage; 452 info_table_t lookup; 453 info_table_t *slot; 454 info_entry_t *entry; 455 int rv; 456 457 /* Only process device nodes (other nodes cannot have usage) */ 458 if (hp_type(node) != HP_NODE_DEVICE) 459 return (HP_WALK_CONTINUE); 460 461 /* Get path of current node, using buffer provided in 'arg' */ 462 if ((rv = hp_path(node, arg->path, arg->connection)) != 0) { 463 log_err("Cannot lookup hotplug path (%s)\n", strerror(rv)); 464 arg->error = rv; 465 return (HP_WALK_TERMINATE); 466 } 467 468 /* Check the lookup table for associated usage */ 469 lookup.path = arg->path; 470 if ((slot = bsearch(&lookup, arg->table, arg->table_len, 471 sizeof (info_table_t), compare_info)) == NULL) 472 return (HP_WALK_CONTINUE); 473 474 /* Usage information was found. Append HP_NODE_USAGE nodes. */ 475 for (entry = slot->entries; entry != NULL; entry = entry->next) { 476 477 /* Allocate a new usage node */ 478 usage = (hp_node_t)calloc(1, sizeof (struct hp_node)); 479 if (usage == NULL) { 480 log_err("Cannot allocate hotplug usage node.\n"); 481 arg->error = ENOMEM; 482 return (HP_WALK_TERMINATE); 483 } 484 485 /* Initialize the usage node's contents */ 486 usage->hp_type = HP_NODE_USAGE; 487 if ((usage->hp_name = strdup(entry->rsrc)) == NULL) { 488 log_err("Cannot allocate hotplug usage node name.\n"); 489 free(usage); 490 arg->error = ENOMEM; 491 return (HP_WALK_TERMINATE); 492 } 493 if ((usage->hp_usage = strdup(entry->usage)) == NULL) { 494 log_err("Cannot allocate hotplug usage node info.\n"); 495 free(usage->hp_name); 496 free(usage); 497 arg->error = ENOMEM; 498 return (HP_WALK_TERMINATE); 499 } 500 501 /* Link the usage node as a child of the device node */ 502 usage->hp_parent = node; 503 usage->hp_sibling = node->hp_child; 504 node->hp_child = usage; 505 } 506 507 return (HP_WALK_CONTINUE); 508 } 509 510 /* 511 * build_table() 512 * 513 * Build a lookup table that will be used to map paths to their 514 * corresponding RCM information tuples. 515 */ 516 static int 517 build_table(rcm_info_t *info, info_table_t **tablep, size_t *table_lenp) 518 { 519 rcm_info_tuple_t *tuple; 520 info_entry_t *entry; 521 info_table_t *slot; 522 info_table_t *table; 523 size_t table_len; 524 const char *rsrc; 525 const char *usage; 526 char path[MAXPATHLEN]; 527 528 /* Initialize results */ 529 *tablep = NULL; 530 *table_lenp = 0; 531 532 /* Count the RCM info tuples to determine the table's size */ 533 table_len = 0; 534 for (tuple = NULL; (tuple = rcm_info_next(info, tuple)) != NULL; ) 535 table_len++; 536 537 /* If the table would be empty, then do nothing */ 538 if (table_len == 0) 539 return (ENOENT); 540 541 /* Allocate the lookup table */ 542 table = (info_table_t *)calloc(table_len, sizeof (info_table_t)); 543 if (table == NULL) 544 return (ENOMEM); 545 546 /* 547 * Fill in the lookup table. Fill one slot in the table 548 * for each device path that has a set of associated RCM 549 * information tuples. In some cases multiple tuples will 550 * be joined together within the same slot. 551 */ 552 slot = NULL; 553 table_len = 0; 554 for (tuple = NULL; (tuple = rcm_info_next(info, tuple)) != NULL; ) { 555 556 /* 557 * Extract RCM resource name and usage description. 558 * 559 * NOTE: skip invalid tuples to return as much as possible. 560 */ 561 if (((rsrc = rcm_info_rsrc(tuple)) == NULL) || 562 ((usage = rcm_info_info(tuple)) == NULL)) { 563 log_err("RCM returned invalid resource or usage.\n"); 564 continue; 565 } 566 567 /* 568 * Try to convert the RCM resource name to a hotplug path. 569 * If conversion succeeds and this path differs from the 570 * current slot in the table, then initialize the next 571 * slot in the table. 572 */ 573 if ((rsrc2path(rsrc, path) == 0) && 574 ((slot == NULL) || (strcmp(slot->path, path) != 0))) { 575 slot = &table[table_len]; 576 if ((slot->path = strdup(path)) == NULL) { 577 log_err("Cannot build info table slot.\n"); 578 free_table(table, table_len); 579 return (ENOMEM); 580 } 581 table_len++; 582 } 583 584 /* Append current usage to entry list in the current slot */ 585 if (slot != NULL) { 586 587 /* Allocate new entry */ 588 entry = (info_entry_t *)malloc(sizeof (info_entry_t)); 589 if (entry == NULL) { 590 log_err("Cannot allocate info table entry.\n"); 591 free_table(table, table_len); 592 return (ENOMEM); 593 } 594 595 /* Link entry into current slot list */ 596 entry->next = slot->entries; 597 slot->entries = entry; 598 599 /* Initialize entry values */ 600 if (((entry->rsrc = strdup(rsrc)) == NULL) || 601 ((entry->usage = strdup(usage)) == NULL)) { 602 log_err("Cannot build info table entry.\n"); 603 free_table(table, table_len); 604 return (ENOMEM); 605 } 606 } 607 } 608 609 /* Check if valid entries were inserted in table */ 610 if (table_len == 0) { 611 free(table); 612 return (0); 613 } 614 615 /* Sort the lookup table by hotplug path */ 616 qsort(table, table_len, sizeof (info_table_t), compare_info); 617 618 /* Done */ 619 *tablep = table; 620 *table_lenp = table_len; 621 return (0); 622 } 623 624 /* 625 * free_table() 626 * 627 * Destroy a lookup table. 628 */ 629 static void 630 free_table(info_table_t *table, size_t table_len) 631 { 632 info_entry_t *entry; 633 int index; 634 635 if (table != NULL) { 636 for (index = 0; index < table_len; index++) { 637 if (table[index].path != NULL) 638 free(table[index].path); 639 while (table[index].entries != NULL) { 640 entry = table[index].entries; 641 table[index].entries = entry->next; 642 if (entry->rsrc != NULL) 643 free(entry->rsrc); 644 if (entry->usage != NULL) 645 free(entry->usage); 646 free(entry); 647 } 648 } 649 free(table); 650 } 651 } 652 653 /* 654 * rsrc2path() 655 * 656 * Convert from an RCM resource name to a hotplug device path. 657 */ 658 static int 659 rsrc2path(const char *rsrc, char *path) 660 { 661 char *s; 662 char tmp[MAXPATHLEN]; 663 664 /* Only convert /dev and /devices paths */ 665 if (strncmp(rsrc, "/dev", 4) == 0) { 666 667 /* Follow symbolic links for /dev paths */ 668 if (realpath(rsrc, tmp) == NULL) { 669 log_err("Cannot resolve RCM resource (%s)\n", 670 strerror(errno)); 671 return (-1); 672 } 673 674 /* Remove the leading "/devices" part */ 675 (void) strlcpy(path, &tmp[strlen(S_DEVICES)], MAXPATHLEN); 676 677 /* Remove any trailing minor node part */ 678 if ((s = strrchr(path, ':')) != NULL) 679 *s = '\0'; 680 681 /* Successfully converted */ 682 return (0); 683 } 684 685 /* Not converted */ 686 return (-1); 687 } 688 689 /* 690 * compare_info() 691 * 692 * Compare two slots in the lookup table that maps paths to usage. 693 * 694 * NOTE: for use with qsort() and bsearch(). 695 */ 696 static int 697 compare_info(const void *a, const void *b) 698 { 699 info_table_t *slot_a = (info_table_t *)a; 700 info_table_t *slot_b = (info_table_t *)b; 701 702 return (strcmp(slot_a->path, slot_b->path)); 703 } 704