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
getinfo(const char * path,const char * connection,uint_t flags,hp_node_t * retp)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
copy_devinfo(const char * path,const char * connection,uint_t flags,hp_node_t * rootp)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
copy_devices(hp_node_t parent,di_node_t dev,uint_t flags,hp_node_t * rootp)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
copy_hotplug(hp_node_t parent,di_node_t dev,const char * connection,uint_t flags,hp_node_t * retp)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 *
base_path(const char * path)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
search_cb(di_node_t node,void * arg)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
check_search(di_node_t dev,uint_t flags)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
node_list_add(hp_node_list_t * listp,hp_node_t node)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
new_device_node(hp_node_t parent,di_node_t dev)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
new_hotplug_node(hp_node_t parent,di_hp_t hp)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