xref: /illumos-gate/usr/src/cmd/rcm_daemon/common/network_rcm.c (revision d5dbd18d69de8954ab5ceb588e99d43fc9b21d46)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * This RCM module adds support to the RCM framework for an abstract
31  * namespace for network devices (DLPI providers).
32  */
33 #include <alloca.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <assert.h>
38 #include <string.h>
39 #include <synch.h>
40 #include <libintl.h>
41 #include <errno.h>
42 #include <libdevinfo.h>
43 #include <sys/types.h>
44 #include <net/if.h>
45 #include <liblaadm.h>
46 #include "rcm_module.h"
47 
48 /*
49  * Definitions
50  */
51 #ifndef	lint
52 #define	_(x)	gettext(x)
53 #else
54 #define	_(x)	x
55 #endif
56 
57 #define	CACHE_STALE	1	/* flags */
58 #define	CACHE_NEW	2	/* flags */
59 
60 /* operations */
61 #define	NET_OFFLINE	1
62 #define	NET_ONLINE	2
63 #define	NET_REMOVE	3
64 #define	NET_SUSPEND	4
65 #define	NET_RESUME	5
66 
67 /*
68  * PSARC decided that DLPI providers are not allowed to end in a digit.
69  * If this ever changes we could add a delimiter with this macro.
70  */
71 #define	NET_DELIMITER	""
72 
73 typedef struct net_cache
74 {
75 	char			*resource;
76 	char			*exported;
77 	char			*driver;
78 	int			ppa;
79 	int			flags;
80 	struct net_cache	*next;
81 	struct net_cache	*prev;
82 } net_cache_t;
83 
84 static net_cache_t	cache_head;
85 static net_cache_t	cache_tail;
86 static mutex_t		cache_lock;
87 static int		events_registered = 0;
88 
89 /* module interface routines */
90 static int net_register(rcm_handle_t *);
91 static int net_unregister(rcm_handle_t *);
92 static int net_getinfo(rcm_handle_t *, char *, id_t, uint_t, char **,
93     char **, nvlist_t *, rcm_info_t **);
94 static int net_suspend(rcm_handle_t *, char *, id_t, timespec_t *,
95     uint_t, char **, rcm_info_t **);
96 static int net_resume(rcm_handle_t *, char *, id_t, uint_t, char **,
97     rcm_info_t **);
98 static int net_offline(rcm_handle_t *, char *, id_t, uint_t, char **,
99     rcm_info_t **);
100 static int net_online(rcm_handle_t *, char *, id_t, uint_t, char **,
101     rcm_info_t **);
102 static int net_remove(rcm_handle_t *, char *, id_t, uint_t, char **,
103     rcm_info_t **);
104 static int net_notify_event(rcm_handle_t *, char *, id_t, uint_t,
105     char **, nvlist_t *, rcm_info_t **);
106 
107 /* module private routines */
108 static void free_cache(void);
109 static void update_cache(rcm_handle_t *hd);
110 static int devfs_entry(di_node_t node, di_minor_t minor, void *arg);
111 static void cache_remove(net_cache_t *node);
112 static net_cache_t *cache_lookup(const char *resource);
113 static void free_node(net_cache_t *);
114 static void cache_insert(net_cache_t *);
115 static boolean_t is_aggregated(char *driver, int ppa);
116 
117 /*
118  * Module-Private data
119  */
120 static struct rcm_mod_ops net_ops = {
121 	RCM_MOD_OPS_VERSION,
122 	net_register,
123 	net_unregister,
124 	net_getinfo,
125 	net_suspend,
126 	net_resume,
127 	net_offline,
128 	net_online,
129 	net_remove,
130 	NULL,		/* request_capacity_change */
131 	NULL,		/* notify_capacity_change */
132 	net_notify_event
133 };
134 
135 /*
136  * Module Interface Routines
137  */
138 
139 /*
140  * rcm_mod_init()
141  *
142  *	Update registrations, and return the ops structure.
143  */
144 struct rcm_mod_ops *
145 rcm_mod_init(void)
146 {
147 	cache_head.next = &cache_tail;
148 	cache_head.prev = NULL;
149 	cache_tail.prev = &cache_head;
150 	cache_tail.next = NULL;
151 	(void) mutex_init(&cache_lock, NULL, NULL);
152 
153 	/* Return the ops vectors */
154 	return (&net_ops);
155 }
156 
157 /*
158  * rcm_mod_info()
159  *
160  *	Return a string describing this module.
161  */
162 const char *
163 rcm_mod_info(void)
164 {
165 	return ("Network namespace module %I%");
166 }
167 
168 /*
169  * rcm_mod_fini()
170  *
171  *	Destroy the cache.
172  */
173 int
174 rcm_mod_fini(void)
175 {
176 	free_cache();
177 	(void) mutex_destroy(&cache_lock);
178 	return (RCM_SUCCESS);
179 }
180 
181 /*
182  * net_register()
183  *
184  *	Make sure the cache is properly sync'ed, and its registrations
185  *	are in order.
186  *
187  *	Locking: the cache is locked by update_cache, and is held
188  *	throughout update_cache's execution because it reads and
189  *	possibly modifies cache links continuously.
190  */
191 static int
192 net_register(rcm_handle_t *hd)
193 {
194 	if (events_registered == 0) {
195 		(void) rcm_register_event(hd, "SUNW_resource/new", 0, NULL);
196 		events_registered++;
197 	}
198 	update_cache(hd);
199 	return (RCM_SUCCESS);
200 }
201 
202 /*
203  * net_unregister()
204  *
205  *	Manually walk through the cache, unregistering all the networks.
206  *
207  *	Locking: the cache is locked throughout the execution of this routine
208  *	because it reads and modifies cache links continuously.
209  */
210 static int
211 net_unregister(rcm_handle_t *hd)
212 {
213 	net_cache_t *probe;
214 
215 	assert(hd != NULL);
216 
217 	/* Walk the cache, unregistering everything */
218 	(void) mutex_lock(&cache_lock);
219 	probe = cache_head.next;
220 	while (probe != &cache_tail) {
221 		(void) rcm_unregister_interest(hd, probe->resource, 0);
222 		cache_remove(probe);
223 		free_node(probe);
224 		probe = cache_head.next;
225 	}
226 	(void) mutex_unlock(&cache_lock);
227 	if (events_registered > 0) {
228 		(void) rcm_unregister_event(hd, "SUNW_resource/new", 0);
229 		events_registered--;
230 	}
231 	return (RCM_SUCCESS);
232 }
233 
234 /*
235  * Since all we do is pass operations thru, we provide a general
236  * routine for passing through operations.
237  */
238 /*ARGSUSED*/
239 static int
240 net_passthru(rcm_handle_t *hd, int op, const char *rsrc, uint_t flag,
241     char **reason, rcm_info_t **dependent_reason, void *arg)
242 {
243 	net_cache_t	*node;
244 	char		*exported;
245 	int		rv;
246 
247 	/*
248 	 * Lock the cache just long enough to extract information about this
249 	 * resource.
250 	 */
251 	(void) mutex_lock(&cache_lock);
252 	node = cache_lookup(rsrc);
253 	if (!node) {
254 		rcm_log_message(RCM_WARNING,
255 		    _("NET: unrecognized resource %s\n"), rsrc);
256 		(void) mutex_unlock(&cache_lock);
257 		return (RCM_SUCCESS);
258 	}
259 
260 	/*
261 	 * Since node->exported could be freed after we drop cache_lock,
262 	 * allocate a stack-local copy.  We don't use strdup() because some of
263 	 * the operations (such as NET_REMOVE) are not allowed to fail.  Note
264 	 * that node->exported is never more than MAXPATHLEN bytes.
265 	 */
266 	exported = alloca(strlen(node->exported) + 1);
267 	(void) strlcpy(exported, node->exported, strlen(node->exported) + 1);
268 
269 	/*
270 	 * Remove notifications are unconditional in the RCM state model,
271 	 * so it's safe to remove the node from the cache at this point.
272 	 * And we need to remove it so that we will recognize it as a new
273 	 * resource following the reattachment of the resource.
274 	 */
275 	if (op == NET_REMOVE) {
276 		cache_remove(node);
277 		free_node(node);
278 	}
279 	(void) mutex_unlock(&cache_lock);
280 
281 	switch (op) {
282 	case NET_SUSPEND:
283 		rv = rcm_request_suspend(hd, exported, flag,
284 		    (timespec_t *)arg, dependent_reason);
285 		break;
286 	case NET_OFFLINE:
287 		if (is_aggregated(node->driver, node->ppa)) {
288 			/* device is aggregated */
289 			*reason = strdup(gettext(
290 			    "Resource is in use by aggregation"));
291 			if (*reason == NULL) {
292 				rcm_log_message(RCM_ERROR,
293 				    gettext("NET: malloc failure"));
294 			}
295 			errno = EBUSY;
296 			return (RCM_FAILURE);
297 		}
298 
299 		rv = rcm_request_offline(hd, exported, flag, dependent_reason);
300 		break;
301 	case NET_ONLINE:
302 		rv = rcm_notify_online(hd, exported, flag, dependent_reason);
303 		break;
304 	case NET_REMOVE:
305 		rv = rcm_notify_remove(hd, exported, flag, dependent_reason);
306 		break;
307 	case NET_RESUME:
308 		rv = rcm_notify_resume(hd, exported, flag, dependent_reason);
309 		break;
310 	default:
311 		rcm_log_message(RCM_WARNING,
312 		    _("NET: bad RCM operation %1$d for %2$s\n"), op, exported);
313 		errno = EINVAL;
314 		return (RCM_FAILURE);
315 	}
316 
317 	if (rv != RCM_SUCCESS) {
318 		char format[256];
319 		(void) snprintf(format, sizeof (format),
320 		    _("RCM operation on dependent %s did not succeed"),
321 		    exported);
322 		rcm_log_message(RCM_WARNING, "NET: %s\n", format);
323 	}
324 
325 	return (rv);
326 }
327 
328 
329 /*
330  * net_offline()
331  *
332  *	Determine dependents of the resource being offlined, and offline
333  *	them all.
334  */
335 static int
336 net_offline(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flags,
337     char **reason, rcm_info_t **dependent_reason)
338 {
339 	assert(hd != NULL);
340 	assert(rsrc != NULL);
341 	assert(id == (id_t)0);
342 	assert(reason != NULL);
343 	assert(dependent_reason != NULL);
344 
345 	rcm_log_message(RCM_TRACE1, "NET: offline(%s)\n", rsrc);
346 
347 	return (net_passthru(hd, NET_OFFLINE, rsrc, flags, reason,
348 	    dependent_reason, NULL));
349 }
350 
351 /*
352  * net_online()
353  *
354  *	Remount the previously offlined filesystem, and online its dependents.
355  */
356 static int
357 net_online(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flag, char **reason,
358     rcm_info_t **dependent_reason)
359 {
360 	assert(hd != NULL);
361 	assert(rsrc != NULL);
362 	assert(id == (id_t)0);
363 
364 	rcm_log_message(RCM_TRACE1, "NET: online(%s)\n", rsrc);
365 
366 	return (net_passthru(hd, NET_ONLINE, rsrc, flag, reason,
367 	    dependent_reason, NULL));
368 }
369 
370 /*
371  * net_getinfo()
372  *
373  *	Gather usage information for this resource.
374  *
375  *	Locking: the cache is locked while this routine looks up the
376  *	resource and extracts copies of any piece of information it needs.
377  *	The cache is then unlocked, and this routine performs the rest of
378  *	its functions without touching any part of the cache.
379  */
380 /*ARGSUSED*/
381 static int
382 net_getinfo(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flag,
383     char **info, char **errstr, nvlist_t *proplist, rcm_info_t **depend_info)
384 {
385 	int		len;
386 	char		*exported;
387 	char		nic[64];
388 	const char	*info_fmt;
389 	net_cache_t	*node;
390 
391 	assert(hd != NULL);
392 	assert(rsrc != NULL);
393 	assert(id == (id_t)0);
394 	assert(info != NULL);
395 	assert(depend_info != NULL);
396 
397 	rcm_log_message(RCM_TRACE1, "NET: getinfo(%s)\n", rsrc);
398 
399 	info_fmt = _("Network interface %s");
400 
401 	(void) mutex_lock(&cache_lock);
402 	node = cache_lookup(rsrc);
403 	if (!node) {
404 		rcm_log_message(RCM_WARNING,
405 		    _("NET: unrecognized resource %s\n"), rsrc);
406 		(void) mutex_unlock(&cache_lock);
407 		errno = ENOENT;
408 		return (RCM_FAILURE);
409 	}
410 	exported = strdup(node->exported);
411 	if (!exported) {
412 		rcm_log_message(RCM_ERROR, _("NET: strdup failure"));
413 		(void) mutex_unlock(&cache_lock);
414 		return (RCM_FAILURE);
415 	}
416 
417 	(void) snprintf(nic, sizeof (nic), "%s%d", node->driver, node->ppa);
418 	(void) mutex_unlock(&cache_lock);
419 
420 	len = strlen(info_fmt) + strlen(nic) + 1;
421 	if ((*info = (char *)malloc(len)) == NULL) {
422 		rcm_log_message(RCM_ERROR, _("NET: malloc failure"));
423 		free(exported);
424 		return (RCM_FAILURE);
425 	}
426 
427 	/* Fill in the string */
428 	(void) snprintf(*info, len, info_fmt, nic);
429 
430 	/* Get dependent info if requested */
431 	if ((flag & RCM_INCLUDE_DEPENDENT) || (flag & RCM_INCLUDE_SUBTREE)) {
432 		(void) rcm_get_info(hd, exported, flag, depend_info);
433 	}
434 
435 	(void) nvlist_add_string(proplist, RCM_CLIENT_NAME, "SunOS");
436 	(void) nvlist_add_string_array(proplist, RCM_CLIENT_EXPORTS,
437 	    &exported, 1);
438 
439 	free(exported);
440 	return (RCM_SUCCESS);
441 }
442 
443 /*
444  * net_suspend()
445  *
446  *	Notify all dependents that the resource is being suspended.
447  *	Since no real operation is involved, QUERY or not doesn't matter.
448  *
449  *	Locking: the cache is only used to retrieve some information about
450  *	this resource, so it is only locked during that retrieval.
451  */
452 static int
453 net_suspend(rcm_handle_t *hd, char *rsrc, id_t id, timespec_t *interval,
454     uint_t flag, char **reason, rcm_info_t **dependent_reason)
455 {
456 	assert(hd != NULL);
457 	assert(rsrc != NULL);
458 	assert(id == (id_t)0);
459 	assert(interval != NULL);
460 	assert(reason != NULL);
461 	assert(dependent_reason != NULL);
462 
463 	rcm_log_message(RCM_TRACE1, "NET: suspend(%s)\n", rsrc);
464 
465 	return (net_passthru(hd, NET_SUSPEND, rsrc, flag, reason,
466 	    dependent_reason, (void *)interval));
467 }
468 
469 /*
470  * net_resume()
471  *
472  *	Resume all the dependents of a suspended network.
473  *
474  *	Locking: the cache is only used to retrieve some information about
475  *	this resource, so it is only locked during that retrieval.
476  */
477 static int
478 net_resume(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flag, char **info,
479     rcm_info_t **dependent_info)
480 {
481 	assert(hd != NULL);
482 	assert(rsrc != NULL);
483 	assert(id == (id_t)0);
484 	assert(info != NULL);
485 	assert(dependent_info != NULL);
486 
487 	rcm_log_message(RCM_TRACE1, "NET: resume(%s)\n", rsrc);
488 
489 	return (net_passthru(hd, NET_RESUME, rsrc, flag, info, dependent_info,
490 	    NULL));
491 }
492 
493 /*
494  * net_remove()
495  *
496  *	This is another NO-OP for us, we just passthru the information.  We
497  *	don't need to remove it from our cache.  We don't unregister
498  *	interest at this point either; the network device name is still
499  *	around.  This way we don't have to change this logic when we
500  *	gain the ability to learn about DR attach operations.
501  */
502 static int
503 net_remove(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flag, char **info,
504     rcm_info_t **dependent_info)
505 {
506 	assert(hd != NULL);
507 	assert(rsrc != NULL);
508 	assert(id == (id_t)0);
509 	assert(info != NULL);
510 	assert(dependent_info != NULL);
511 
512 	rcm_log_message(RCM_TRACE1, "NET: remove(%s)\n", rsrc);
513 
514 	return (net_passthru(hd, NET_REMOVE, rsrc, flag, info, dependent_info,
515 	    NULL));
516 }
517 
518 /*
519  * net_notify_event()
520  *
521  *	Receive new resource events.  If the resource is a network
522  *	device, then pass up a notify for it too.  No need to cache
523  *	it, though, since we'll do that in our register() routine the
524  *	next time we're called.
525  */
526 /*ARGSUSED*/
527 static int
528 net_notify_event(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flag,
529     char **errstr, nvlist_t *nvl, rcm_info_t **result)
530 {
531 	char		*devname = NULL, *nodetype, *driver, *kpath;
532 	char		ifname[MAXPATHLEN];
533 	di_node_t	node;
534 	di_minor_t	minor;
535 	nvlist_t	*nvlist;
536 	nvpair_t	*nvp = NULL;
537 	int		rv;
538 
539 	assert(hd != NULL);
540 	assert(rsrc != NULL);
541 	assert(id == (id_t)0);
542 	assert(nvl != NULL);
543 	assert(result != NULL);
544 
545 	rcm_log_message(RCM_TRACE1, "NET: notify_event(%s)\n", rsrc);
546 
547 	if (strcmp(rsrc, "SUNW_resource/new") != 0) {
548 		/* how did we get this?  we didn't ask for it! */
549 		rcm_log_message(RCM_WARNING,
550 		    _("NET: unrecognized event for %s\n"), rsrc);
551 		return (RCM_FAILURE);
552 	}
553 
554 	/* is it a /devices resource? */
555 	/*
556 	 * note: we'd like to use nvlist_lookup_string, but a bug in
557 	 * libnvpair breaks lookups, so we have to walk it ourself.
558 	 */
559 #ifdef NVLIST_LOOKUP_NOTBROKEN
560 	if (nvlist_lookup_string(nvl, RCM_RSRCNAME, &devname) != 0) {
561 		/* resource not found */
562 		rcm_log_message(RCM_WARNING,
563 		    _("NET: event without resource name\n"));
564 		return (RCM_FAILURE);
565 	}
566 #else
567 	while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
568 		if (strcmp(nvpair_name(nvp), RCM_RSRCNAME) == 0) {
569 			if (nvpair_value_string(nvp, &devname) != 0) {
570 				rcm_log_message(RCM_WARNING,
571 				    _("NET: cannot get event "
572 					"resource value\n"));
573 				return (RCM_FAILURE);
574 			}
575 			break;
576 		}
577 	}
578 	if (devname == NULL) {
579 		rcm_log_message(RCM_WARNING,
580 		    _("NET: event without resource name\n"));
581 		return (RCM_FAILURE);
582 	}
583 #endif
584 	rcm_log_message(RCM_TRACE1, "NET: new rsrc(%s)\n", devname);
585 	if (strncmp(devname, "/devices/", strlen("/devices/")) != 0) {
586 		/* not a /devices resource, we ignore it */
587 		rcm_log_message(RCM_TRACE1, "NET: %s not for us\n", devname);
588 		return (RCM_SUCCESS);
589 	}
590 	kpath = devname + strlen("/devices");
591 	if (strncmp(kpath, "/pseudo/", strlen("/pseudo/")) == 0) {
592 		/* pseudo device , not for us */
593 		rcm_log_message(RCM_TRACE1, "NET: ignoring pseudo %s\n",
594 		    devname);
595 		return (RCM_SUCCESS);
596 	}
597 
598 	/* just snapshot the specific tree we need */
599 	if ((node = di_init(kpath, DINFOMINOR)) == NULL) {
600 		rcm_log_message(RCM_ERROR,
601 		    _("NET: cannot initialize device tree\n"));
602 		return (RCM_FAILURE);
603 	}
604 
605 	/* network devices usually only have a single minor node */
606 	if ((minor = di_minor_next(node, DI_MINOR_NIL)) == DI_MINOR_NIL) {
607 		rcm_log_message(RCM_WARNING,
608 		    _("NET: cannot find minor for %s\n"),
609 		    devname);
610 		di_fini(node);
611 		return (RCM_FAILURE);
612 	}
613 
614 	nodetype = di_minor_nodetype(minor);
615 	if ((nodetype == NULL) || (strcmp(nodetype, DDI_NT_NET) != 0)) {
616 		/* doesn't look like a network device */
617 		rcm_log_message(RCM_TRACE1, "NET: %s not a NIC\n", devname);
618 		goto done;
619 	}
620 	if ((driver = di_driver_name(node)) == NULL) {
621 		rcm_log_message(RCM_TRACE1, "NET: no driver (%s)\n", devname);
622 		goto done;
623 	}
624 	(void) snprintf(ifname, sizeof (ifname), "SUNW_network/%s%s%d", driver,
625 	    NET_DELIMITER, di_instance(node));
626 
627 	rcm_log_message(RCM_TRACE1, "NET: notifying arrival of %s\n", ifname);
628 	/* build up our nvlist -- these shouldn't ever fail */
629 	if ((rv = nvlist_alloc(&nvlist, NV_UNIQUE_NAME, 0)) != 0) {
630 		rcm_log_message(RCM_TRACE1,
631 		    "NET: nvlist alloc failed %d, errno %d\n", rv, errno);
632 	}
633 
634 	if ((rv = nvlist_add_string(nvlist, RCM_RSRCNAME, ifname)) != 0) {
635 		rcm_log_message(RCM_TRACE1,
636 		    "NET: nvlist_add_string failed %d, errno %d\n", rv, errno);
637 	}
638 	/* now we need to do our own notification */
639 	rv = rcm_notify_event(hd, "SUNW_resource/new", 0, nvlist, result);
640 	if (rv != RCM_SUCCESS) {
641 		rcm_log_message(RCM_TRACE1,
642 		    "NET: notify_event failed: %s\n", strerror(errno));
643 	} else {
644 		rcm_log_message(RCM_TRACE1, "NET: notify_event succeeded\n");
645 	}
646 
647 	/* and clean up our nvlist */
648 	nvlist_free(nvlist);
649 
650 done:
651 	di_fini(node);
652 	return (RCM_SUCCESS);
653 }
654 
655 /*
656  * Cache management routines.  Note that the cache is implemented as a
657  * trivial linked list, and is only required because RCM doesn't
658  * provide enough state about our own registrations back to us.  This
659  * linked list implementation probably clobbers the CPU cache pretty
660  * well.
661  */
662 
663 /*
664  * cache_lookup()
665  *
666  * Get a cache node for a resource.  Call with cache lock held.
667  */
668 static net_cache_t *
669 cache_lookup(const char *resource)
670 {
671 	net_cache_t *probe;
672 	probe = cache_head.next;
673 	while (probe != &cache_tail) {
674 		if (probe->resource &&
675 		    (strcmp(resource, probe->resource) == 0)) {
676 			return (probe);
677 		}
678 		probe = probe->next;
679 	}
680 	return (NULL);
681 }
682 
683 /*
684  * free_node()
685  *
686  * Free a node.  Make sure it isn't in the list!
687  */
688 static void
689 free_node(net_cache_t *node)
690 {
691 	if (node) {
692 		free(node->resource);
693 		free(node->exported);
694 		free(node->driver);
695 		free(node);
696 	}
697 }
698 
699 /*
700  * cache_insert()
701  *
702  * Call with the cache_lock held.
703  */
704 static void
705 cache_insert(net_cache_t *node)
706 {
707 	/* insert at the head for best performance */
708 	node->next = cache_head.next;
709 	node->prev = &cache_head;
710 
711 	node->next->prev = node;
712 	node->prev->next = node;
713 }
714 
715 /*
716  * cache_remove()
717  *
718  * Call with the cache_lock held.
719  */
720 static void
721 cache_remove(net_cache_t *node)
722 {
723 	node->next->prev = node->prev;
724 	node->prev->next = node->next;
725 	node->next = NULL;
726 	node->prev = NULL;
727 }
728 
729 /*
730  * devfs_entry()
731  *
732  * Call with the cache_lock held.
733  */
734 /*ARGSUSED*/
735 static int
736 devfs_entry(di_node_t node, di_minor_t minor, void *arg)
737 {
738 	char		ifname [MAXPATHLEN];	/* should be big enough! */
739 	char		*devfspath;
740 	char		resource[MAXPATHLEN];
741 	char		*name;
742 	char		*cp;
743 	int		instance;
744 	net_cache_t	*probe;
745 
746 	cp = di_minor_nodetype(minor);
747 	if ((cp == NULL) || (strcmp(cp, DDI_NT_NET))) {
748 		/* doesn't look like a network device */
749 		return (DI_WALK_CONTINUE);
750 	}
751 
752 	name = di_driver_name(node);
753 	if (name == NULL) {
754 		/* what else can we do? */
755 		return (DI_WALK_CONTINUE);
756 	}
757 
758 	instance = di_instance(node);
759 
760 	(void) snprintf(ifname, sizeof (ifname), "SUNW_network/%s%s%d",
761 	    name, NET_DELIMITER, instance);
762 
763 	devfspath = di_devfs_path(node);
764 	if (!devfspath) {
765 		/* no devfs path?!? */
766 		rcm_log_message(RCM_DEBUG, "NET: missing devfs path\n");
767 		return (DI_WALK_CONTINUE);
768 	}
769 
770 	if (strncmp("/pseudo", devfspath, strlen("/pseudo")) == 0) {
771 		/* ignore pseudo devices, probably not really NICs */
772 		rcm_log_message(RCM_DEBUG, "NET: ignoring pseudo device %s\n",
773 		    devfspath);
774 		di_devfs_path_free(devfspath);
775 		return (DI_WALK_CONTINUE);
776 	}
777 
778 	(void) snprintf(resource, sizeof (resource), "/devices%s", devfspath);
779 	di_devfs_path_free(devfspath);
780 
781 	probe = cache_lookup(resource);
782 	if (probe != NULL) {
783 		rcm_log_message(RCM_DEBUG, "NET: %s already registered\n",
784 		    resource);
785 		probe->flags &= ~(CACHE_STALE);
786 	} else {
787 		rcm_log_message(RCM_DEBUG, "NET: %s is new resource\n",
788 		    resource);
789 		probe = calloc(1, sizeof (net_cache_t));
790 		if (!probe) {
791 			rcm_log_message(RCM_ERROR, _("NET: malloc failure"));
792 			return (DI_WALK_CONTINUE);
793 		}
794 
795 		probe->resource = strdup(resource);
796 		probe->ppa = instance;
797 		probe->driver = strdup(name);
798 		probe->exported = strdup(ifname);
799 
800 		if ((!probe->resource) || (!probe->exported) ||
801 		    (!probe->driver)) {
802 			free_node(probe);
803 			return (DI_WALK_CONTINUE);
804 		}
805 
806 		probe->flags |= CACHE_NEW;
807 		cache_insert(probe);
808 	}
809 
810 	return (DI_WALK_CONTINUE);
811 }
812 
813 /*
814  * update_cache()
815  *
816  * The devinfo tree walking code is lifted from ifconfig.c.
817  */
818 static void
819 update_cache(rcm_handle_t *hd)
820 {
821 	net_cache_t	*probe;
822 	di_node_t	root;
823 	int		rv;
824 
825 	(void) mutex_lock(&cache_lock);
826 
827 	/* first we walk the entire cache, marking each entry stale */
828 	probe = cache_head.next;
829 	while (probe != &cache_tail) {
830 		probe->flags |= CACHE_STALE;
831 		probe = probe->next;
832 	}
833 
834 	root = di_init("/", DINFOSUBTREE | DINFOMINOR);
835 	if (root == DI_NODE_NIL) {
836 		goto done;
837 	}
838 
839 	(void) di_walk_minor(root, DDI_NT_NET, DI_CHECK_ALIAS, NULL,
840 	    devfs_entry);
841 
842 	di_fini(root);
843 
844 	probe = cache_head.next;
845 	while (probe != &cache_tail) {
846 		net_cache_t *freeit;
847 		if (probe->flags & CACHE_STALE) {
848 			(void) rcm_unregister_interest(hd, probe->resource, 0);
849 			rcm_log_message(RCM_DEBUG, "NET: unregistered %s\n",
850 			    probe->resource);
851 			freeit = probe;
852 			probe = probe->next;
853 			cache_remove(freeit);
854 			free_node(freeit);
855 			continue;
856 		}
857 
858 		if (!(probe->flags & CACHE_NEW)) {
859 			probe = probe->next;
860 			continue;
861 		}
862 
863 		rcm_log_message(RCM_DEBUG, "NET: registering %s\n",
864 		    probe->resource);
865 		rv = rcm_register_interest(hd, probe->resource, 0, NULL);
866 		if (rv != RCM_SUCCESS) {
867 			rcm_log_message(RCM_ERROR,
868 			    _("NET: failed to register %s\n"),
869 			    probe->resource);
870 		} else {
871 			rcm_log_message(RCM_DEBUG,
872 			    "NET: registered %s (as %s)\n",
873 			    probe->resource, probe->exported);
874 			probe->flags &= ~(CACHE_NEW);
875 		}
876 		probe = probe->next;
877 	}
878 
879 done:
880 	(void) mutex_unlock(&cache_lock);
881 }
882 
883 /*
884  * free_cache()
885  */
886 static void
887 free_cache(void)
888 {
889 	net_cache_t *probe;
890 
891 	(void) mutex_lock(&cache_lock);
892 	probe = cache_head.next;
893 	while (probe != &cache_tail) {
894 		cache_remove(probe);
895 		free_node(probe);
896 		probe = cache_head.next;
897 	}
898 	(void) mutex_unlock(&cache_lock);
899 }
900 
901 /*
902  * is_aggregated() checks whether a NIC being removed is part of an
903  * aggregation.
904  */
905 
906 typedef struct aggr_walker_state_s {
907 	uint_t naggr;
908 	char dev_name[LIFNAMSIZ];
909 } aggr_walker_state_t;
910 
911 static int
912 aggr_walker(void *arg, laadm_grp_attr_sys_t *grp)
913 {
914 	aggr_walker_state_t *state = arg;
915 	laadm_port_attr_sys_t *port;
916 	int i;
917 
918 	for (i = 0; i < grp->lg_nports; i++) {
919 		port = &grp->lg_ports[i];
920 
921 		rcm_log_message(RCM_TRACE1, "MAC: aggr (%d) port %s/%d\n",
922 		    grp->lg_key, port->lp_devname, port->lp_port);
923 
924 		if (strcmp(port->lp_devname, state->dev_name) != 0)
925 			continue;
926 
927 		/* found matching MAC port */
928 		state->naggr++;
929 	}
930 
931 	return (0);
932 }
933 
934 static boolean_t
935 is_aggregated(char *driver, int ppa)
936 {
937 	aggr_walker_state_t state;
938 
939 	state.naggr = 0;
940 	(void) snprintf(state.dev_name, sizeof (state.dev_name), "%s%d",
941 	    driver, ppa);
942 
943 	if (laadm_walk_sys(aggr_walker, &state) != 0) {
944 		rcm_log_message(RCM_ERROR, gettext("NET: cannot walk "
945 		    "aggregations (%s)\n"), strerror(errno));
946 		return (B_FALSE);
947 	}
948 
949 	return (state.naggr > 0);
950 }
951