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
copy_usage(hp_node_t root)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
rcm_resources(hp_node_t root,char *** rsrcsp)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
free_rcm_resources(char ** rsrcs)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
rcm_offline(char ** rsrcs,uint_t flags,hp_node_t root)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
rcm_online(char ** rsrcs)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
rcm_remove(char ** rsrcs)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
get_rcm_usage(char ** rsrcs,rcm_info_t ** info_p)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
merge_rcm_info(hp_node_t root,rcm_info_t * info)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
resource_callback(hp_node_t node,void * argp)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
merge_callback(hp_node_t node,void * argp)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
build_table(rcm_info_t * info,info_table_t ** tablep,size_t * table_lenp)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
free_table(info_table_t * table,size_t table_len)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
rsrc2path(const char * rsrc,char * path)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
compare_info(const void * a,const void * b)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