xref: /illumos-gate/usr/src/cmd/hotplugd/hotplugd_rcm.c (revision 168665f7ddaca2a65705c3b127078ffc6b24adc3)
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