xref: /illumos-gate/usr/src/cmd/hotplugd/hotplugd_info.c (revision 63aa537723d4883425b44d96b6316b7ad14053fc)
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