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.
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.
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]
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
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"
38 * Define a list of hotplug nodes.
39 * (Only used within this module.)
49 static int copy_devinfo(const char *, const char *, uint_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
,
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
);
64 * Build a hotplug information snapshot. The path, connection,
65 * and flags indicate what information should be included.
68 getinfo(const char *path
, const char *connection
, uint_t flags
, hp_node_t
*retp
)
70 hp_node_t root
= NULL
;
75 if ((path
== NULL
) || (retp
== NULL
))
78 dprintf("getinfo: path=%s, connection=%s, flags=0x%x\n", path
,
79 (connection
== NULL
) ? "NULL" : connection
, flags
);
81 /* Allocate the base path */
82 if ((basepath
= base_path(path
)) == NULL
)
85 /* Copy in device and hotplug nodes from libdevinfo */
86 if ((rv
= copy_devinfo(basepath
, connection
, flags
, &root
)) != 0) {
92 /* Check if there were no connections */
94 dprintf("getinfo: no hotplug connections.\n");
99 /* Special case: exclude root nexus from snapshot */
100 if (strcmp(basepath
, "/") == 0) {
101 child
= root
->hp_child
;
102 if (root
->hp_name
!= NULL
)
106 for (child
= root
; child
; child
= child
->hp_sibling
)
107 child
->hp_parent
= NULL
;
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
;
114 /* Copy in usage information from RCM */
115 if (flags
& HPINFOUSAGE
) {
116 if ((rv
= copy_usage(root
)) != 0) {
117 (void) hp_fini(root
);
129 * Copy information about device and hotplug nodes from libdevinfo.
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.
136 copy_devinfo(const char *path
, const char *connection
, uint_t flags
,
139 hp_node_t hp_root
= NULL
;
143 /* Get libdevinfo snapshot */
144 if ((di_root
= di_init(path
, DINFOSUBTREE
| DINFOHP
)) == DI_NODE_NIL
)
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
);
154 * If a connection is specified, just copy immediate hotplug info.
155 * Else, copy the device tree normally.
157 if (connection
!= NULL
)
158 rv
= copy_hotplug(NULL
, di_root
, connection
, flags
, &hp_root
);
160 rv
= copy_devices(NULL
, di_root
, flags
, &hp_root
);
162 /* Destroy devinfo snapshot */
165 *rootp
= (rv
== 0) ? hp_root
: NULL
;
172 * Copy a full branch of device nodes. Used by copy_devinfo() and
176 copy_devices(hp_node_t parent
, di_node_t dev
, uint_t flags
, hp_node_t
*rootp
)
178 hp_node_list_t children
;
179 hp_node_t self
, branch
;
183 /* Initialize results */
186 /* Enforce search semantics */
187 if (check_search(dev
, flags
) == 0)
190 /* Allocate new node for current device */
191 if ((self
= new_device_node(parent
, dev
)) == NULL
)
195 * If the device has hotplug nodes, then use copy_hotplug()
196 * instead to build the branch associated with current device.
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) {
209 * The device does not have hotplug nodes. Use normal
210 * approach of iterating through its child device nodes.
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
)) {
216 if ((rv
= copy_devices(self
, child
, flags
, &branch
)) != 0) {
217 (void) hp_fini(children
.head
);
222 node_list_add(&children
, branch
);
224 self
->hp_child
= children
.head
;
234 * Copy a full branch of hotplug nodes. Used by copy_devinfo()
235 * and copy_devices().
237 * If a connection is specified, the results are limited only
238 * to the branch associated with that specific connection.
241 copy_hotplug(hp_node_t parent
, di_node_t dev
, const char *connection
,
242 uint_t flags
, hp_node_t
*retp
)
244 hp_node_list_t connections
, ports
;
245 hp_node_t node
, port_node
;
251 /* Stop implementing the HPINFOSEARCH flag */
252 child_flags
= flags
& ~(HPINFOSEARCH
);
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
));
259 * Scan virtual ports.
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.
266 for (hp
= DI_HP_NIL
; (hp
= di_hp_next(dev
, hp
)) != DI_HP_NIL
; ) {
268 /* Ignore connectors */
269 if (di_hp_type(hp
) != DDI_HP_CN_TYPE_VIRTUAL_PORT
)
273 * Ignore ports associated with connectors, unless
274 * a specific connection is being sought.
276 if ((connection
== NULL
) && (di_hp_depends_on(hp
) != -1))
279 /* If a connection is specified, ignore non-matching ports */
280 if ((connection
!= NULL
) &&
281 (strcmp(di_hp_name(hp
), connection
) != 0))
284 /* Create a new port node */
285 if ((node
= new_hotplug_node(parent
, hp
)) == NULL
) {
290 /* Add port node to connection list */
291 node_list_add(&connections
, node
);
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)
301 * Scan physical connectors.
303 * If a connection is specified, the results will be limited
304 * only to the branch associated with that connection.
306 for (hp
= DI_HP_NIL
; (hp
= di_hp_next(dev
, hp
)) != DI_HP_NIL
; ) {
309 if (di_hp_type(hp
) == DDI_HP_CN_TYPE_VIRTUAL_PORT
)
312 /* If a connection is specified, ignore non-matching ports */
313 if ((connection
!= NULL
) &&
314 (strcmp(di_hp_name(hp
), connection
) != 0))
317 /* Create a new connector node */
318 if ((node
= new_hotplug_node(parent
, hp
)) == NULL
) {
323 /* Add connector node to connection list */
324 node_list_add(&connections
, node
);
326 /* Add branches of associated port nodes */
327 physnum
= di_hp_connection(hp
);
329 while ((port_hp
= di_hp_next(dev
, port_hp
)) != DI_HP_NIL
) {
331 /* Ignore irrelevant connections */
332 if (di_hp_depends_on(port_hp
) != physnum
)
335 /* Add new port node to port list */
336 if ((port_node
= new_hotplug_node(node
,
341 node_list_add(&ports
, port_node
);
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)
350 node
->hp_child
= ports
.head
;
351 (void) memset(&ports
, 0, sizeof (hp_node_list_t
));
354 if (connections
.head
== NULL
)
356 *retp
= connections
.head
;
360 (void) hp_fini(ports
.head
);
361 (void) hp_fini(connections
.head
);
368 * Normalize the base path of a hotplug information snapshot.
369 * The caller must free the string that is allocated.
372 base_path(const char *path
)
377 devices_len
= strlen(S_DEVICES
);
379 if (strncmp(path
, S_DEVICES
, devices_len
) == 0)
380 base_path
= strdup(&path
[devices_len
]);
382 base_path
= strdup(path
);
390 * Callback function used by di_walk_node() to search for branches
391 * of the libdevinfo snapshot that contain hotplug nodes.
395 search_cb(di_node_t node
, void *arg
)
400 (void) di_node_private_set(node
, (void *)(uintptr_t)0);
402 if (di_hp_next(node
, DI_HP_NIL
) == DI_HP_NIL
)
403 return (DI_WALK_CONTINUE
);
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
);
412 return (DI_WALK_CONTINUE
);
418 * Check if a device node was marked by an initial search pass.
421 check_search(di_node_t dev
, uint_t flags
)
425 if (flags
& HPINFOSEARCH
) {
426 dev_flags
= (uint_t
)(uintptr_t)di_node_private_get(dev
);
427 if ((dev_flags
& HPINFOSEARCH
) == 0)
437 * Utility function to append one node to a list of hotplug nodes.
440 node_list_add(hp_node_list_t
*listp
, hp_node_t node
)
442 if (listp
->prev
!= NULL
)
443 listp
->prev
->hp_sibling
= node
;
453 * Build a new hotplug node based on a specified devinfo node.
456 new_device_node(hp_node_t parent
, di_node_t dev
)
459 char *node_name
, *bus_addr
;
460 char name
[MAXPATHLEN
];
462 node
= (hp_node_t
)calloc(1, sizeof (struct hp_node
));
465 node
->hp_parent
= parent
;
466 node
->hp_type
= HP_NODE_DEVICE
;
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");
477 node
->hp_name
= strdup(name
);
479 node
->hp_name
= strdup(node_name
);
488 * Build a new hotplug node based on a specified devinfo hotplug node.
491 new_hotplug_node(hp_node_t parent
, di_hp_t hp
)
496 node
= (hp_node_t
)calloc(1, sizeof (struct hp_node
));
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
;
509 node
->hp_type
= HP_NODE_CONNECTOR
;