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 <stdarg.h>
29#include <unistd.h>
30#include <fcntl.h>
31#include <errno.h>
32#include <string.h>
33#include <door.h>
34#include <libnvpair.h>
35#include <libhotplug.h>
36#include <libhotplug_impl.h>
37#include <sys/sunddi.h>
38#include <sys/ddi_hp.h>
39
40static void	i_hp_dprintf(const char *fmt, ...);
41static int	i_hp_pack_branch(hp_node_t, char **, size_t *);
42static int	i_hp_pack_node(hp_node_t, char **, size_t *);
43static int	i_hp_unpack_node(char *, size_t, hp_node_t, hp_node_t *);
44static int	i_hp_unpack_branch(char *, size_t, hp_node_t, hp_node_t *);
45static int	i_hp_call_hotplugd(nvlist_t *, nvlist_t **);
46static nvlist_t	*i_hp_set_args(hp_cmd_t, const char *, const char *, uint_t,
47		    const char *, int);
48static int	i_hp_parse_results(nvlist_t *, hp_node_t *, char **);
49
50/*
51 * Global flag to enable debug features.
52 */
53int	libhotplug_debug = 0;
54
55/*
56 * hp_init()
57 *
58 *	Initialize a hotplug information snapshot.
59 */
60hp_node_t
61hp_init(const char *path, const char *connection, uint_t flags)
62{
63	nvlist_t	*args;
64	nvlist_t	*results;
65	hp_node_t	root = NULL;
66	int		rv;
67
68	i_hp_dprintf("hp_init: path=%p, connection=%p, flags=0x%x\n",
69	    (void *)path, (void *)connection, flags);
70
71	/* Check arguments */
72	if ((path == NULL) || !HP_INIT_FLAGS_VALID(flags)) {
73		i_hp_dprintf("hp_init: invalid arguments.\n");
74		errno = EINVAL;
75		return (NULL);
76	}
77
78	/* Build arguments for door call */
79	if ((args = i_hp_set_args(HP_CMD_GETINFO, path, connection, flags,
80	    NULL, 0)) == NULL) {
81		i_hp_dprintf("hp_init: cannot build arguments nvlist.\n");
82		errno = ENOMEM;
83		return (NULL);
84	}
85
86	/* Make the door call to hotplugd */
87	rv = i_hp_call_hotplugd(args, &results);
88
89	/* Arguments no longer needed */
90	nvlist_free(args);
91
92	/* Parse additional results, if any */
93	if ((rv == 0) && (results != NULL)) {
94		rv = i_hp_parse_results(results, &root, NULL);
95		nvlist_free(results);
96	}
97
98	/* Check for errors */
99	if (rv != 0) {
100		i_hp_dprintf("hp_init: failure (%s).\n", strerror(rv));
101		if (root)
102			hp_fini(root);
103		errno = rv;
104		return (NULL);
105	}
106
107	/* Success requires an info snapshot */
108	if (root == NULL) {
109		i_hp_dprintf("hp_init: missing info snapshot.\n");
110		errno = EFAULT;
111		return (NULL);
112	}
113
114	/* Success */
115	return (root);
116}
117
118/*
119 * hp_fini()
120 *
121 *	Terminate and clean-up a hotplug information snapshot.
122 */
123void
124hp_fini(hp_node_t root)
125{
126	hp_node_t	node;
127	hp_node_t	sibling;
128	char		*basepath;
129
130	i_hp_dprintf("hp_fini: root=%p\n", (void *)root);
131
132	if (root == NULL) {
133		i_hp_dprintf("hp_fini: invalid arguments.\n");
134		return;
135	}
136
137	/* Extract and free base path */
138	if (root->hp_basepath) {
139		basepath = root->hp_basepath;
140		for (node = root; node != NULL; node = node->hp_sibling)
141			node->hp_basepath = NULL;
142		free(basepath);
143	}
144
145	/* Destroy the nodes */
146	node = root;
147	while (node) {
148		sibling = node->hp_sibling;
149		if (node->hp_child)
150			hp_fini(node->hp_child);
151		if (node->hp_name)
152			free(node->hp_name);
153		if (node->hp_usage)
154			free(node->hp_usage);
155		if (node->hp_description)
156			free(node->hp_description);
157		free(node);
158		node = sibling;
159	}
160}
161
162/*
163 * hp_traverse()
164 *
165 *	Walk a graph of hotplug nodes, executing a callback on each node.
166 */
167int
168hp_traverse(hp_node_t root, void *arg, int (*hp_callback)(hp_node_t, void *arg))
169{
170	int		rv;
171	hp_node_t	node;
172
173	i_hp_dprintf("hp_traverse: root=%p, arg=%p, hp_callback=%p\n",
174	    (void *)root, arg, (void *)hp_callback);
175
176	/* Check arguments */
177	if ((root == NULL) || (hp_callback == NULL)) {
178		i_hp_dprintf("hp_traverse: invalid arguments.\n");
179		errno = EINVAL;
180		return (-1);
181	}
182
183	for (node = root; node; node = node->hp_sibling) {
184		rv = hp_callback(node, arg);
185
186		if (rv == HP_WALK_TERMINATE) {
187			i_hp_dprintf("hp_traverse: walk terminated.\n");
188			return (HP_WALK_TERMINATE);
189		}
190
191		if (node->hp_child && (rv != HP_WALK_PRUNECHILD))
192			if (hp_traverse(node->hp_child, arg, hp_callback) ==
193			    HP_WALK_TERMINATE) {
194				i_hp_dprintf("hp_traverse: walk terminated.\n");
195				return (HP_WALK_TERMINATE);
196			}
197
198		if (rv == HP_WALK_PRUNESIBLING)
199			break;
200	}
201
202	return (0);
203}
204
205/*
206 * hp_type()
207 *
208 *	Return a node's type.
209 */
210int
211hp_type(hp_node_t node)
212{
213	i_hp_dprintf("hp_type: node=%p\n", (void *)node);
214
215	if (node == NULL) {
216		i_hp_dprintf("hp_type: invalid arguments.\n");
217		errno = EINVAL;
218		return (-1);
219	}
220
221	return (node->hp_type);
222}
223
224/*
225 * hp_name()
226 *
227 *	Return a node's name.
228 */
229char *
230hp_name(hp_node_t node)
231{
232	i_hp_dprintf("hp_name: node=%p\n", (void *)node);
233
234	if (node == NULL) {
235		i_hp_dprintf("hp_name: invalid arguments.\n");
236		errno = EINVAL;
237		return (NULL);
238	}
239
240	if (node->hp_name == NULL) {
241		i_hp_dprintf("hp_name: missing name value.\n");
242		errno = EFAULT;
243	}
244
245	return (node->hp_name);
246}
247
248/*
249 * hp_state()
250 *
251 *	Return a node's current state.
252 */
253int
254hp_state(hp_node_t node)
255{
256	i_hp_dprintf("hp_state: node=%p\n", (void *)node);
257
258	if (node == NULL) {
259		i_hp_dprintf("hp_state: invalid arguments.\n");
260		errno = EINVAL;
261		return (-1);
262	}
263
264	if ((node->hp_type != HP_NODE_CONNECTOR) &&
265	    (node->hp_type != HP_NODE_PORT)) {
266		i_hp_dprintf("hp_state: operation not supported.\n");
267		errno = ENOTSUP;
268		return (-1);
269	}
270
271	return (node->hp_state);
272}
273
274/*
275 * hp_usage()
276 *
277 *	Return a usage description for usage nodes.
278 */
279char *
280hp_usage(hp_node_t node)
281{
282	i_hp_dprintf("hp_usage: node=%p\n", (void *)node);
283
284	if (node == NULL) {
285		i_hp_dprintf("hp_usage: invalid arguments.\n");
286		errno = EINVAL;
287		return (NULL);
288	}
289
290	if (node->hp_type != HP_NODE_USAGE) {
291		i_hp_dprintf("hp_usage: operation not supported.\n");
292		errno = ENOTSUP;
293		return (NULL);
294	}
295
296	if (node->hp_usage == NULL) {
297		i_hp_dprintf("hp_usage: missing usage value.\n");
298		errno = EFAULT;
299	}
300
301	return (node->hp_usage);
302}
303
304/*
305 * hp_description()
306 *
307 *	Return a type description (e.g. "PCI slot") for connection nodes.
308 */
309char *
310hp_description(hp_node_t node)
311{
312	i_hp_dprintf("hp_description: node=%p\n", (void *)node);
313
314	if (node == NULL) {
315		i_hp_dprintf("hp_description: invalid arguments.\n");
316		errno = EINVAL;
317		return (NULL);
318	}
319
320	if ((node->hp_type != HP_NODE_CONNECTOR) &&
321	    (node->hp_type != HP_NODE_PORT)) {
322		i_hp_dprintf("hp_description: operation not supported.\n");
323		errno = ENOTSUP;
324		return (NULL);
325	}
326
327	if (node->hp_description == NULL) {
328		i_hp_dprintf("hp_description: missing description value.\n");
329		errno = EFAULT;
330	}
331
332	return (node->hp_description);
333}
334
335/*
336 * hp_last_change()
337 *
338 *	Return when the state of a connection was last changed.
339 */
340time_t
341hp_last_change(hp_node_t node)
342{
343	i_hp_dprintf("hp_last_change: node=%p\n", (void *)node);
344
345	if (node == NULL) {
346		i_hp_dprintf("hp_last_change: invalid arguments.\n");
347		errno = EINVAL;
348		return (0);
349	}
350
351	if ((node->hp_type != HP_NODE_CONNECTOR) &&
352	    (node->hp_type != HP_NODE_PORT)) {
353		i_hp_dprintf("hp_last_change: operation not supported.\n");
354		errno = ENOTSUP;
355		return (0);
356	}
357
358	return (node->hp_last_change);
359}
360
361/*
362 * hp_parent()
363 *
364 *	Return a node's parent node.
365 */
366hp_node_t
367hp_parent(hp_node_t node)
368{
369	i_hp_dprintf("hp_parent: node=%p\n", (void *)node);
370
371	if (node == NULL) {
372		i_hp_dprintf("hp_parent: invalid arguments.\n");
373		errno = EINVAL;
374		return (NULL);
375	}
376
377	if (node->hp_parent == NULL) {
378		i_hp_dprintf("hp_parent: node has no parent.\n");
379		errno = ENXIO;
380	}
381
382	return (node->hp_parent);
383}
384
385/*
386 * hp_child()
387 *
388 *	Return a node's first child node.
389 */
390hp_node_t
391hp_child(hp_node_t node)
392{
393	i_hp_dprintf("hp_child: node=%p\n", (void *)node);
394
395	if (node == NULL) {
396		i_hp_dprintf("hp_child: invalid arguments.\n");
397		errno = EINVAL;
398		return (NULL);
399	}
400
401	if (node->hp_child == NULL) {
402		i_hp_dprintf("hp_child: node has no child.\n");
403		errno = ENXIO;
404	}
405
406	return (node->hp_child);
407}
408
409/*
410 * hp_sibling()
411 *
412 *	Return a node's next sibling node.
413 */
414hp_node_t
415hp_sibling(hp_node_t node)
416{
417	i_hp_dprintf("hp_sibling: node=%p\n", (void *)node);
418
419	if (node == NULL) {
420		i_hp_dprintf("hp_sibling: invalid arguments.\n");
421		errno = EINVAL;
422		return (NULL);
423	}
424
425	if (node->hp_sibling == NULL) {
426		i_hp_dprintf("hp_sibling: node has no sibling.\n");
427		errno = ENXIO;
428	}
429
430	return (node->hp_sibling);
431}
432
433/*
434 * hp_path()
435 *
436 *	Return the path (and maybe connection name) of a node.
437 *	The caller must supply two buffers, each MAXPATHLEN size.
438 */
439int
440hp_path(hp_node_t node, char *path, char *connection)
441{
442	hp_node_t	root = NULL;
443	hp_node_t	parent;
444	int		i;
445	char		*s;
446	char		components[MAXPATHLEN];
447
448	i_hp_dprintf("hp_path: node=%p, path=%p, connection=%p\n", (void *)node,
449	    (void *)path, (void *)connection);
450
451	if ((node == NULL) || (path == NULL) || (connection == NULL)) {
452		i_hp_dprintf("hp_path: invalid arguments.\n");
453		return (EINVAL);
454	}
455
456	(void) memset(path, 0, MAXPATHLEN);
457	(void) memset(connection, 0, MAXPATHLEN);
458	(void) memset(components, 0, MAXPATHLEN);
459
460	/*  Set 'connection' only for connectors and ports */
461	if ((node->hp_type == HP_NODE_CONNECTOR) ||
462	    (node->hp_type == HP_NODE_PORT))
463		(void) strlcpy(connection, node->hp_name, MAXPATHLEN);
464
465	/* Trace back to the root node, accumulating components */
466	for (parent = node; parent != NULL; parent = parent->hp_parent) {
467		if (parent->hp_type == HP_NODE_DEVICE) {
468			(void) strlcat(components, "/", MAXPATHLEN);
469			(void) strlcat(components, parent->hp_name, MAXPATHLEN);
470		}
471		if (parent->hp_parent == NULL)
472			root = parent;
473	}
474
475	/* Ensure the snapshot actually contains a base path */
476	if (root->hp_basepath == NULL) {
477		i_hp_dprintf("hp_path: missing base pathname.\n");
478		return (EFAULT);
479	}
480
481	/*
482	 * Construct the path.  Start with the base path from the root
483	 * node, then append the accumulated components in reverse order.
484	 */
485	if (strcmp(root->hp_basepath, "/") != 0) {
486		(void) strlcat(path, root->hp_basepath, MAXPATHLEN);
487		if ((root->hp_type == HP_NODE_DEVICE) &&
488		    ((s = strrchr(path, '/')) != NULL))
489			*s = '\0';
490	}
491	for (i = strlen(components) - 1; i >= 0; i--) {
492		if (components[i] == '/') {
493			(void) strlcat(path, &components[i], MAXPATHLEN);
494			components[i] = '\0';
495		}
496	}
497
498	return (0);
499}
500
501/*
502 * hp_set_state()
503 *
504 *	Initiate a state change operation on a node.
505 */
506int
507hp_set_state(hp_node_t node, uint_t flags, int state, hp_node_t *resultsp)
508{
509	hp_node_t	root = NULL;
510	nvlist_t	*args;
511	nvlist_t	*results;
512	int		rv;
513	char		path[MAXPATHLEN];
514	char		connection[MAXPATHLEN];
515
516	i_hp_dprintf("hp_set_state: node=%p, flags=0x%x, state=0x%x, "
517	    "resultsp=%p\n", (void *)node, flags, state, (void *)resultsp);
518
519	/* Check arguments */
520	if ((node == NULL) || (resultsp == NULL) ||
521	    !HP_SET_STATE_FLAGS_VALID(flags)) {
522		i_hp_dprintf("hp_set_state: invalid arguments.\n");
523		return (EINVAL);
524	}
525
526	/* Check node type */
527	if ((node->hp_type != HP_NODE_CONNECTOR) &&
528	    (node->hp_type != HP_NODE_PORT)) {
529		i_hp_dprintf("hp_set_state: operation not supported.\n");
530		return (ENOTSUP);
531	}
532
533	/* Check that target state is valid */
534	switch (state) {
535	case DDI_HP_CN_STATE_PRESENT:
536	case DDI_HP_CN_STATE_POWERED:
537	case DDI_HP_CN_STATE_ENABLED:
538		if (node->hp_type != HP_NODE_CONNECTOR) {
539			i_hp_dprintf("hp_set_state: mismatched target.\n");
540			return (ENOTSUP);
541		}
542		break;
543	case DDI_HP_CN_STATE_PORT_PRESENT:
544	case DDI_HP_CN_STATE_OFFLINE:
545	case DDI_HP_CN_STATE_ONLINE:
546		if (node->hp_type != HP_NODE_PORT) {
547			i_hp_dprintf("hp_set_state: mismatched target.\n");
548			return (ENOTSUP);
549		}
550		break;
551	default:
552		i_hp_dprintf("hp_set_state: invalid target state.\n");
553		return (EINVAL);
554	}
555
556	/* Get path and connection of specified node */
557	if ((rv = hp_path(node, path, connection)) != 0)
558		return (rv);
559
560	/* Build arguments for door call */
561	if ((args = i_hp_set_args(HP_CMD_CHANGESTATE, path, connection, flags,
562	    NULL, state)) == NULL)
563		return (ENOMEM);
564
565	/* Make the door call to hotplugd */
566	rv = i_hp_call_hotplugd(args, &results);
567
568	/* Arguments no longer needed */
569	nvlist_free(args);
570
571	/* Parse additional results, if any */
572	if ((rv == 0) && (results != NULL)) {
573		rv = i_hp_parse_results(results, &root, NULL);
574		nvlist_free(results);
575		*resultsp = root;
576	}
577
578	/* Done */
579	return (rv);
580}
581
582/*
583 * hp_set_private()
584 *
585 *	Set bus private options on the hotplug connection
586 *	indicated by the given hotplug information node.
587 */
588int
589hp_set_private(hp_node_t node, const char *options, char **resultsp)
590{
591	int		rv;
592	nvlist_t	*args;
593	nvlist_t	*results;
594	char		*values = NULL;
595	char		path[MAXPATHLEN];
596	char		connection[MAXPATHLEN];
597
598	i_hp_dprintf("hp_set_private: node=%p, options=%p, resultsp=%p\n",
599	    (void *)node, (void *)options, (void *)resultsp);
600
601	/* Check arguments */
602	if ((node == NULL) || (options == NULL) || (resultsp == NULL)) {
603		i_hp_dprintf("hp_set_private: invalid arguments.\n");
604		return (EINVAL);
605	}
606
607	/* Check node type */
608	if (node->hp_type != HP_NODE_CONNECTOR) {
609		i_hp_dprintf("hp_set_private: operation not supported.\n");
610		return (ENOTSUP);
611	}
612
613	/* Initialize results */
614	*resultsp = NULL;
615
616	/* Get path and connection of specified node */
617	if ((rv = hp_path(node, path, connection)) != 0)
618		return (rv);
619
620	/* Build arguments for door call */
621	if ((args = i_hp_set_args(HP_CMD_SETPRIVATE, path, connection, 0,
622	    options, 0)) == NULL)
623		return (ENOMEM);
624
625	/* Make the door call to hotplugd */
626	rv = i_hp_call_hotplugd(args, &results);
627
628	/* Arguments no longer needed */
629	nvlist_free(args);
630
631	/* Parse additional results, if any */
632	if ((rv == 0) && (results != NULL)) {
633		rv = i_hp_parse_results(results, NULL, &values);
634		nvlist_free(results);
635		*resultsp = values;
636	}
637
638	/* Done */
639	return (rv);
640}
641
642/*
643 * hp_get_private()
644 *
645 *	Get bus private options on the hotplug connection
646 *	indicated by the given hotplug information node.
647 */
648int
649hp_get_private(hp_node_t node, const char *options, char **resultsp)
650{
651	int		rv;
652	nvlist_t	*args;
653	nvlist_t	*results;
654	char		*values = NULL;
655	char		path[MAXPATHLEN];
656	char		connection[MAXPATHLEN];
657
658	i_hp_dprintf("hp_get_private: node=%p, options=%p, resultsp=%p\n",
659	    (void *)node, (void *)options, (void *)resultsp);
660
661	/* Check arguments */
662	if ((node == NULL) || (options == NULL) || (resultsp == NULL)) {
663		i_hp_dprintf("hp_get_private: invalid arguments.\n");
664		return (EINVAL);
665	}
666
667	/* Check node type */
668	if (node->hp_type != HP_NODE_CONNECTOR) {
669		i_hp_dprintf("hp_get_private: operation not supported.\n");
670		return (ENOTSUP);
671	}
672
673	/* Initialize results */
674	*resultsp = NULL;
675
676	/* Get path and connection of specified node */
677	if ((rv = hp_path(node, path, connection)) != 0)
678		return (rv);
679
680	/* Build arguments for door call */
681	if ((args = i_hp_set_args(HP_CMD_GETPRIVATE, path, connection, 0,
682	    options, 0)) == NULL)
683		return (ENOMEM);
684
685	/* Make the door call to hotplugd */
686	rv = i_hp_call_hotplugd(args, &results);
687
688	/* Arguments no longer needed */
689	nvlist_free(args);
690
691	/* Parse additional results, if any */
692	if ((rv == 0) && (results != NULL)) {
693		rv = i_hp_parse_results(results, NULL, &values);
694		nvlist_free(results);
695		*resultsp = values;
696	}
697
698	/* Done */
699	return (rv);
700}
701
702/*
703 * hp_pack()
704 *
705 *	Given the root of a hotplug information snapshot, pack
706 *	it into a contiguous byte array so that it is suitable
707 *	for network transport.
708 */
709int
710hp_pack(hp_node_t root, char **bufp, size_t *lenp)
711{
712	hp_node_t	node;
713	nvlist_t	*nvl;
714	char		*buf;
715	size_t		len;
716	int		rv;
717
718	i_hp_dprintf("hp_pack: root=%p, bufp=%p, lenp=%p\n", (void *)root,
719	    (void *)bufp, (void *)lenp);
720
721	if ((root == NULL) || (bufp == NULL) || (lenp == NULL)) {
722		i_hp_dprintf("hp_pack: invalid arguments.\n");
723		return (EINVAL);
724	}
725
726	*lenp = 0;
727	*bufp = NULL;
728
729	if (nvlist_alloc(&nvl, 0, 0) != 0) {
730		i_hp_dprintf("hp_pack: nvlist_alloc() failed (%s).\n",
731		    strerror(errno));
732		return (ENOMEM);
733	}
734
735	if (root->hp_basepath != NULL) {
736		rv = nvlist_add_string(nvl, HP_INFO_BASE, root->hp_basepath);
737		if (rv != 0) {
738			nvlist_free(nvl);
739			return (rv);
740		}
741	}
742
743	for (node = root; node != NULL; node = node->hp_sibling) {
744		if ((rv = i_hp_pack_branch(node, &buf, &len)) == 0) {
745			rv = nvlist_add_byte_array(nvl, HP_INFO_BRANCH,
746			    (uchar_t *)buf, len);
747			free(buf);
748		}
749		if (rv != 0) {
750			nvlist_free(nvl);
751			return (rv);
752		}
753	}
754
755	len = 0;
756	buf = NULL;
757	if ((rv = nvlist_pack(nvl, &buf, &len, NV_ENCODE_NATIVE, 0)) == 0) {
758		*lenp = len;
759		*bufp = buf;
760	}
761
762	nvlist_free(nvl);
763
764	return (rv);
765}
766
767/*
768 * hp_unpack()
769 *
770 *	Unpack a hotplug information snapshot for normal usage.
771 */
772int
773hp_unpack(char *packed_buf, size_t packed_len, hp_node_t *retp)
774{
775	hp_node_t	root;
776	hp_node_t	root_list = NULL;
777	hp_node_t	prev_root = NULL;
778	nvlist_t	*nvl = NULL;
779	nvpair_t	*nvp;
780	char		*basepath = NULL;
781	int		rv;
782
783	i_hp_dprintf("hp_unpack: packed_buf=%p, packed_len=%u, retp=%p\n",
784	    (void *)packed_buf, (uint32_t)packed_len, (void *)retp);
785
786	if ((packed_buf == NULL) || (packed_len == 0) || (retp == NULL)) {
787		i_hp_dprintf("hp_unpack: invalid arguments.\n");
788		return (EINVAL);
789	}
790
791	if ((rv = nvlist_unpack(packed_buf, packed_len, &nvl, 0)) != 0)
792		return (rv);
793
794	if (nvlist_next_nvpair(nvl, NULL) == NULL) {
795		nvlist_free(nvl);
796		errno = EINVAL;
797		return (0);
798	}
799
800	for (nvp = NULL; nvp = nvlist_next_nvpair(nvl, nvp); ) {
801
802		rv = EINVAL;
803
804		if (strcmp(nvpair_name(nvp), HP_INFO_BASE) == 0) {
805			char	*val_string;
806
807			if ((rv = nvpair_value_string(nvp, &val_string)) == 0) {
808				if ((basepath = strdup(val_string)) == NULL)
809					rv = ENOMEM;
810			}
811
812		} else if (strcmp(nvpair_name(nvp), HP_INFO_BRANCH) == 0) {
813			size_t		len = 0;
814			char		*buf = NULL;
815
816			if ((rv = nvpair_value_byte_array(nvp,
817			    (uchar_t **)&buf, (uint_t *)&len)) == 0) {
818				rv = i_hp_unpack_branch(buf, len, NULL, &root);
819			}
820
821			if (rv == 0) {
822				if (prev_root) {
823					prev_root->hp_sibling = root;
824				} else {
825					root_list = root;
826				}
827				prev_root = root;
828			}
829		}
830
831		if (rv != 0) {
832			if (basepath)
833				free(basepath);
834			nvlist_free(nvl);
835			hp_fini(root_list);
836			*retp = NULL;
837			return (rv);
838		}
839	}
840
841	/* Store the base path in each root node */
842	if (basepath) {
843		for (root = root_list; root; root = root->hp_sibling)
844			root->hp_basepath = basepath;
845	}
846
847	nvlist_free(nvl);
848	*retp = root_list;
849	return (0);
850}
851
852/*
853 * i_hp_dprintf()
854 *
855 *	Print debug messages to stderr, but only when the debug flag
856 *	(libhotplug_debug) is set.
857 */
858/*PRINTFLIKE1*/
859static void
860i_hp_dprintf(const char *fmt, ...)
861{
862	va_list	ap;
863
864	if (libhotplug_debug) {
865		va_start(ap, fmt);
866		(void) vfprintf(stderr, fmt, ap);
867		va_end(ap);
868	}
869}
870
871/*
872 * i_hp_pack_branch()
873 *
874 *	Pack an individual branch of a hotplug information snapshot.
875 */
876static int
877i_hp_pack_branch(hp_node_t root, char **bufp, size_t *lenp)
878{
879	hp_node_t	child;
880	nvlist_t	*nvl;
881	char		*buf;
882	size_t		len;
883	int		rv;
884
885	*lenp = 0;
886	*bufp = NULL;
887
888	/* Allocate an nvlist for this branch */
889	if (nvlist_alloc(&nvl, 0, 0) != 0)
890		return (ENOMEM);
891
892	/* Pack the root of the branch and add it to the nvlist */
893	if ((rv = i_hp_pack_node(root, &buf, &len)) == 0) {
894		rv = nvlist_add_byte_array(nvl, HP_INFO_NODE,
895		    (uchar_t *)buf, len);
896		free(buf);
897	}
898	if (rv != 0) {
899		nvlist_free(nvl);
900		return (rv);
901	}
902
903	/* Pack each subordinate branch, and add it to the nvlist */
904	for (child = root->hp_child; child != NULL; child = child->hp_sibling) {
905		if ((rv = i_hp_pack_branch(child, &buf, &len)) == 0) {
906			rv = nvlist_add_byte_array(nvl, HP_INFO_BRANCH,
907			    (uchar_t *)buf, len);
908			free(buf);
909		}
910		if (rv != 0) {
911			nvlist_free(nvl);
912			return (rv);
913		}
914	}
915
916	/* Pack the resulting nvlist into a single buffer */
917	len = 0;
918	buf = NULL;
919	if ((rv = nvlist_pack(nvl, &buf, &len, NV_ENCODE_NATIVE, 0)) == 0) {
920		*lenp = len;
921		*bufp = buf;
922	}
923
924	/* Free the nvlist */
925	nvlist_free(nvl);
926
927	return (rv);
928}
929
930/*
931 * i_hp_pack_node()
932 *
933 *	Pack an individual node of a hotplug information snapshot.
934 */
935static int
936i_hp_pack_node(hp_node_t node, char **bufp, size_t *lenp)
937{
938	nvlist_t	*nvl;
939	char		*buf = NULL;
940	size_t		len = 0;
941	int		rv;
942
943	if (nvlist_alloc(&nvl, 0, 0) != 0)
944		return (ENOMEM);
945
946	if ((rv = nvlist_add_uint32(nvl, HP_INFO_TYPE,
947	    (uint32_t)node->hp_type)) != 0)
948		goto fail;
949
950	if ((node->hp_name) &&
951	    ((rv = nvlist_add_string(nvl, HP_INFO_NAME, node->hp_name)) != 0))
952		goto fail;
953
954	if ((node->hp_usage) &&
955	    ((rv = nvlist_add_string(nvl, HP_INFO_USAGE, node->hp_usage)) != 0))
956		goto fail;
957
958	if ((node->hp_description) &&
959	    ((rv = nvlist_add_string(nvl, HP_INFO_DESC,
960	    node->hp_description)) != 0))
961		goto fail;
962
963	if ((rv = nvlist_add_uint32(nvl, HP_INFO_STATE, node->hp_state)) != 0)
964		goto fail;
965
966	if ((node->hp_last_change != 0) &&
967	    ((rv = nvlist_add_uint32(nvl, HP_INFO_TIME,
968	    node->hp_last_change)) != 0))
969		goto fail;
970
971	if ((rv = nvlist_pack(nvl, &buf, &len, NV_ENCODE_NATIVE, 0)) != 0)
972		goto fail;
973
974	*bufp = buf;
975	*lenp = len;
976	nvlist_free(nvl);
977	return (0);
978
979fail:
980	*bufp = NULL;
981	*lenp = 0;
982	nvlist_free(nvl);
983	return (rv);
984}
985
986/*
987 * i_hp_unpack_branch()
988 *
989 *	Unpack a branch of hotplug information nodes.
990 */
991static int
992i_hp_unpack_branch(char *packed_buf, size_t packed_len, hp_node_t parent,
993    hp_node_t *retp)
994{
995	hp_node_t	node = NULL;
996	hp_node_t	child;
997	hp_node_t	prev_child = NULL;
998	nvlist_t	*nvl = NULL;
999	nvpair_t	*nvp;
1000	char		*buf;
1001	size_t		len;
1002	int		rv;
1003
1004	/* Initialize results */
1005	*retp = NULL;
1006
1007	/* Unpack the nvlist for this branch */
1008	if ((rv = nvlist_unpack(packed_buf, packed_len, &nvl, 0)) != 0)
1009		return (rv);
1010
1011	/*
1012	 * Unpack the branch.  The first item in the nvlist is
1013	 * always the root node.  And zero or more subordinate
1014	 * branches may be packed afterward.
1015	 */
1016	for (nvp = NULL; nvp = nvlist_next_nvpair(nvl, nvp); ) {
1017
1018		len = 0;
1019		buf = NULL;
1020
1021		if (strcmp(nvpair_name(nvp), HP_INFO_NODE) == 0) {
1022
1023			/* Check that there is only one root node */
1024			if (node != NULL) {
1025				hp_fini(node);
1026				nvlist_free(nvl);
1027				return (EFAULT);
1028			}
1029
1030			if ((rv = nvpair_value_byte_array(nvp, (uchar_t **)&buf,
1031			    (uint_t *)&len)) == 0)
1032				rv = i_hp_unpack_node(buf, len, parent, &node);
1033
1034			if (rv != 0) {
1035				nvlist_free(nvl);
1036				return (rv);
1037			}
1038
1039		} else if (strcmp(nvpair_name(nvp), HP_INFO_BRANCH) == 0) {
1040
1041			if ((rv = nvpair_value_byte_array(nvp, (uchar_t **)&buf,
1042			    (uint_t *)&len)) == 0)
1043				rv = i_hp_unpack_branch(buf, len, node, &child);
1044
1045			if (rv != 0) {
1046				hp_fini(node);
1047				nvlist_free(nvl);
1048				return (rv);
1049			}
1050
1051			if (prev_child) {
1052				prev_child->hp_sibling = child;
1053			} else {
1054				node->hp_child = child;
1055			}
1056			prev_child = child;
1057		}
1058	}
1059
1060	nvlist_free(nvl);
1061	*retp = node;
1062	return (0);
1063}
1064
1065/*
1066 * i_hp_unpack_node()
1067 *
1068 *	Unpack an individual hotplug information node.
1069 */
1070static int
1071i_hp_unpack_node(char *buf, size_t len, hp_node_t parent, hp_node_t *retp)
1072{
1073	hp_node_t	node;
1074	nvlist_t	*nvl;
1075	nvpair_t	*nvp;
1076	uint32_t	val_uint32;
1077	char		*val_string;
1078	int		rv = 0;
1079
1080	/* Initialize results */
1081	*retp = NULL;
1082
1083	/* Unpack node into an nvlist */
1084	if ((nvlist_unpack(buf, len, &nvl, 0) != 0))
1085		return (EINVAL);
1086
1087	/* Allocate the new node */
1088	if ((node = (hp_node_t)calloc(1, sizeof (struct hp_node))) == NULL) {
1089		nvlist_free(nvl);
1090		return (ENOMEM);
1091	}
1092
1093	/* Iterate through nvlist, unpacking each field */
1094	for (nvp = NULL; nvp = nvlist_next_nvpair(nvl, nvp); ) {
1095
1096		if ((strcmp(nvpair_name(nvp), HP_INFO_TYPE) == 0) &&
1097		    (nvpair_type(nvp) == DATA_TYPE_UINT32)) {
1098
1099			(void) nvpair_value_uint32(nvp, &val_uint32);
1100			node->hp_type = val_uint32;
1101
1102		} else if ((strcmp(nvpair_name(nvp), HP_INFO_NAME) == 0) &&
1103		    (nvpair_type(nvp) == DATA_TYPE_STRING)) {
1104
1105			(void) nvpair_value_string(nvp, &val_string);
1106			if ((node->hp_name = strdup(val_string)) == NULL) {
1107				rv = ENOMEM;
1108				break;
1109			}
1110
1111		} else if ((strcmp(nvpair_name(nvp), HP_INFO_STATE) == 0) &&
1112		    (nvpair_type(nvp) == DATA_TYPE_UINT32)) {
1113
1114			(void) nvpair_value_uint32(nvp, &val_uint32);
1115			node->hp_state = val_uint32;
1116
1117		} else if ((strcmp(nvpair_name(nvp), HP_INFO_USAGE) == 0) &&
1118		    (nvpair_type(nvp) == DATA_TYPE_STRING)) {
1119
1120			(void) nvpair_value_string(nvp, &val_string);
1121			if ((node->hp_usage = strdup(val_string)) == NULL) {
1122				rv = ENOMEM;
1123				break;
1124			}
1125
1126		} else if ((strcmp(nvpair_name(nvp), HP_INFO_DESC) == 0) &&
1127		    (nvpair_type(nvp) == DATA_TYPE_STRING)) {
1128
1129			(void) nvpair_value_string(nvp, &val_string);
1130			if ((node->hp_description = strdup(val_string))
1131			    == NULL) {
1132				rv = ENOMEM;
1133				break;
1134			}
1135
1136		} else if ((strcmp(nvpair_name(nvp), HP_INFO_TIME) == 0) &&
1137		    (nvpair_type(nvp) == DATA_TYPE_UINT32)) {
1138
1139			(void) nvpair_value_uint32(nvp, &val_uint32);
1140			node->hp_last_change = (time_t)val_uint32;
1141
1142		} else {
1143			i_hp_dprintf("i_hp_unpack_node: unrecognized: '%s'\n",
1144			    nvpair_name(nvp));
1145		}
1146	}
1147
1148	/* Unpacked nvlist no longer needed */
1149	nvlist_free(nvl);
1150
1151	/* Check for errors */
1152	if (rv != 0) {
1153		hp_fini(node);
1154		return (rv);
1155	}
1156
1157	/* Success */
1158	node->hp_parent = parent;
1159	*retp = node;
1160	return (0);
1161}
1162
1163/*
1164 * i_hp_call_hotplugd()
1165 *
1166 *	Perform a door call to the hotplug daemon.
1167 */
1168static int
1169i_hp_call_hotplugd(nvlist_t *args, nvlist_t **resultsp)
1170{
1171	door_arg_t	door_arg;
1172	nvlist_t	*results = NULL;
1173	char		*buf = NULL;
1174	size_t		len = 0;
1175	uint64_t	seqnum;
1176	int		door_fd;
1177	int		rv;
1178
1179	/* Initialize results */
1180	*resultsp = NULL;
1181
1182	/* Open door */
1183	if ((door_fd = open(HOTPLUGD_DOOR, O_RDONLY)) < 0) {
1184		i_hp_dprintf("i_hp_call_hotplugd: cannot open door (%s)\n",
1185		    strerror(errno));
1186		return (EBADF);
1187	}
1188
1189	/* Pack the nvlist of arguments */
1190	if ((rv = nvlist_pack(args, &buf, &len, NV_ENCODE_NATIVE, 0)) != 0) {
1191		i_hp_dprintf("i_hp_call_hotplugd: cannot pack arguments (%s)\n",
1192		    strerror(rv));
1193		return (rv);
1194	}
1195
1196	/* Set the door argument using the packed arguments */
1197	door_arg.data_ptr = buf;
1198	door_arg.data_size = len;
1199	door_arg.desc_ptr = NULL;
1200	door_arg.desc_num = 0;
1201	door_arg.rbuf = (char *)(uintptr_t)&rv;
1202	door_arg.rsize = sizeof (rv);
1203
1204	/* Attempt the door call */
1205	if (door_call(door_fd, &door_arg) != 0) {
1206		rv = errno;
1207		i_hp_dprintf("i_hp_call_hotplugd: door call failed (%s)\n",
1208		    strerror(rv));
1209		(void) close(door_fd);
1210		free(buf);
1211		return (rv);
1212	}
1213
1214	/* The arguments are no longer needed */
1215	free(buf);
1216
1217	/*
1218	 * If results are not in the original buffer provided,
1219	 * then check and process the new results buffer.
1220	 */
1221	if (door_arg.rbuf != (char *)(uintptr_t)&rv) {
1222
1223		/*
1224		 * First check that the buffer is valid.  Then check for
1225		 * the simple case where a short result code was sent.
1226		 * The last case is a packed nvlist was returned, which
1227		 * needs to be unpacked.
1228		 */
1229		if ((door_arg.rbuf == NULL) ||
1230		    (door_arg.data_size < sizeof (rv))) {
1231			i_hp_dprintf("i_hp_call_hotplugd: invalid results.\n");
1232			rv = EFAULT;
1233
1234		} else if (door_arg.data_size == sizeof (rv)) {
1235			rv = *(int *)(uintptr_t)door_arg.rbuf;
1236
1237		} else if ((rv = nvlist_unpack(door_arg.rbuf,
1238		    door_arg.data_size, &results, 0)) != 0) {
1239			i_hp_dprintf("i_hp_call_hotplugd: "
1240			    "cannot unpack results (%s).\n", strerror(rv));
1241			results = NULL;
1242			rv = EFAULT;
1243		}
1244
1245		/* Unmap the results buffer */
1246		if (door_arg.rbuf != NULL)
1247			(void) munmap(door_arg.rbuf, door_arg.rsize);
1248
1249		/*
1250		 * In the case of a packed nvlist, notify the daemon
1251		 * that it can free the result buffer from its heap.
1252		 */
1253		if ((results != NULL) &&
1254		    (nvlist_lookup_uint64(results, HPD_SEQNUM, &seqnum) == 0)) {
1255			door_arg.data_ptr = (char *)(uintptr_t)&seqnum;
1256			door_arg.data_size = sizeof (seqnum);
1257			door_arg.desc_ptr = NULL;
1258			door_arg.desc_num = 0;
1259			door_arg.rbuf = NULL;
1260			door_arg.rsize = 0;
1261			(void) door_call(door_fd, &door_arg);
1262			if (door_arg.rbuf != NULL)
1263				(void) munmap(door_arg.rbuf, door_arg.rsize);
1264		}
1265
1266		*resultsp = results;
1267	}
1268
1269	(void) close(door_fd);
1270	return (rv);
1271}
1272
1273/*
1274 * i_hp_set_args()
1275 *
1276 *	Construct an nvlist of arguments for a hotplugd door call.
1277 */
1278static nvlist_t *
1279i_hp_set_args(hp_cmd_t cmd, const char *path, const char *connection,
1280    uint_t flags, const char *options, int state)
1281{
1282	nvlist_t	*args;
1283
1284	/* Allocate a new nvlist */
1285	if (nvlist_alloc(&args, NV_UNIQUE_NAME_TYPE, 0) != 0)
1286		return (NULL);
1287
1288	/* Add common arguments */
1289	if ((nvlist_add_int32(args, HPD_CMD, cmd) != 0) ||
1290	    (nvlist_add_string(args, HPD_PATH, path) != 0)) {
1291		nvlist_free(args);
1292		return (NULL);
1293	}
1294
1295	/* Add connection, but only if defined */
1296	if ((connection != NULL) && (connection[0] != '\0') &&
1297	    (nvlist_add_string(args, HPD_CONNECTION, connection) != 0)) {
1298		nvlist_free(args);
1299		return (NULL);
1300	}
1301
1302	/* Add flags, but only if defined */
1303	if ((flags != 0) && (nvlist_add_uint32(args, HPD_FLAGS, flags) != 0)) {
1304		nvlist_free(args);
1305		return (NULL);
1306	}
1307
1308	/* Add options, but only if defined */
1309	if ((options != NULL) &&
1310	    (nvlist_add_string(args, HPD_OPTIONS, options) != 0)) {
1311		nvlist_free(args);
1312		return (NULL);
1313	}
1314
1315	/* Add state, but only for CHANGESTATE command */
1316	if ((cmd == HP_CMD_CHANGESTATE) &&
1317	    (nvlist_add_int32(args, HPD_STATE, state) != 0)) {
1318		nvlist_free(args);
1319		return (NULL);
1320	}
1321
1322	return (args);
1323}
1324
1325/*
1326 * i_hp_parse_results()
1327 *
1328 *	Parse out individual fields of an nvlist of results from
1329 *	a hotplugd door call.
1330 */
1331static int
1332i_hp_parse_results(nvlist_t *results, hp_node_t *rootp, char **optionsp)
1333{
1334	int	rv;
1335
1336	/* Parse an information snapshot */
1337	if (rootp) {
1338		char	*buf = NULL;
1339		size_t	len = 0;
1340
1341		*rootp = NULL;
1342		if (nvlist_lookup_byte_array(results, HPD_INFO,
1343		    (uchar_t **)&buf, (uint_t *)&len) == 0) {
1344			if ((rv = hp_unpack(buf, len, rootp)) != 0)
1345				return (rv);
1346		}
1347	}
1348
1349	/* Parse a bus private option string */
1350	if (optionsp) {
1351		char	*str;
1352
1353		*optionsp = NULL;
1354		if ((nvlist_lookup_string(results, HPD_OPTIONS, &str) == 0) &&
1355		    ((*optionsp = strdup(str)) == NULL)) {
1356			return (ENOMEM);
1357		}
1358	}
1359
1360	/* Parse result code of the operation */
1361	if (nvlist_lookup_int32(results, HPD_STATUS, &rv) != 0) {
1362		i_hp_dprintf("i_hp_call_hotplugd: missing status.\n");
1363		return (EFAULT);
1364	}
1365
1366	return (rv);
1367}
1368