xref: /illumos-gate/usr/src/uts/sun4v/io/mdeg.c (revision bdf0047c)
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 /*
23  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * MD Event Generator (MDEG) Module
29  */
30 
31 #include <sys/machsystm.h>
32 #include <sys/taskq.h>
33 #include <sys/disp.h>
34 #include <sys/cmn_err.h>
35 #include <sys/note.h>
36 
37 #include <sys/mdeg.h>
38 #include <sys/mach_descrip.h>
39 #include <sys/mdesc.h>
40 
41 /*
42  * A single client registration
43  */
44 typedef struct mdeg_clnt {
45 	boolean_t		valid;		/* structure is in active use */
46 	mdeg_node_match_t	*nmatch;	/* node match filter */
47 	mdeg_node_spec_t	*pspec;		/* parent match filter */
48 	mdeg_cb_t		cb;		/* the client callback */
49 	caddr_t			cb_arg;		/* argument to the callback */
50 	uint64_t		magic;		/* sanity checking magic */
51 	mdeg_handle_t		hdl;		/* handle assigned by MDEG */
52 } mdeg_clnt_t;
53 
54 /*
55  * Global MDEG data
56  *
57  * Locking Strategy:
58  *
59  *   mdeg.lock - lock used to synchronize system-wide MD updates. An
60  *	MD update must be treated as an atomic event. The lock is
61  *	taken when notification that a new MD is available and held
62  *	until all clients have been notified.
63  *
64  *   mdeg.rwlock - lock used to synchronize access to the table of
65  *	registered clients. The reader lock must be held when looking
66  *	up client information in the table. The writer lock must be
67  *	held when modifying any client information.
68  */
69 static struct mdeg {
70 	taskq_t 	*taskq;		/* for internal processing */
71 	boolean_t	enabled;	/* enable/disable taskq processing */
72 	kmutex_t	lock;		/* synchronize MD updates */
73 	md_t		*md_prev;	/* previous MD */
74 	md_t		*md_curr;	/* current MD */
75 	mdeg_clnt_t	*tbl;		/* table of registered clients */
76 	krwlock_t	rwlock;		/* client table lock */
77 	uint_t		maxclnts;	/* client table size */
78 	uint_t		nclnts;		/* current number of clients */
79 } mdeg;
80 
81 /*
82  * Debugging routines
83  */
84 #ifdef DEBUG
85 uint_t mdeg_debug = 0x0;
86 
87 static void mdeg_dump_clnt(mdeg_clnt_t *clnt);
88 static void mdeg_dump_table(void);
89 
90 #define	MDEG_DBG		if (mdeg_debug) printf
91 #define	MDEG_DUMP_CLNT		mdeg_dump_clnt
92 #define	MDEG_DUMP_TABLE		mdeg_dump_table
93 
94 #else /* DEBUG */
95 
96 #define	MDEG_DBG		_NOTE(CONSTCOND) if (0) printf
97 #define	MDEG_DUMP_CLNT
98 #define	MDEG_DUMP_TABLE()
99 
100 #endif /* DEBUG */
101 
102 /*
103  * Global constants
104  */
105 #define	MDEG_MAX_TASKQ_THR	512	/* maximum number of taskq threads */
106 #define	MDEG_MAX_CLNTS_INIT	64	/* initial client table size */
107 
108 #define	MDEG_MAGIC		0x4D4445475F48444Cull	/* 'MDEG_HDL' */
109 
110 /*
111  * A client handle is a 64 bit value with two pieces of
112  * information encoded in it. The upper 32 bits are the
113  * index into the table of a particular client structure.
114  * The lower 32 bits are a counter that is incremented
115  * each time a client structure is reused.
116  */
117 #define	MDEG_IDX_SHIFT			32
118 #define	MDEG_COUNT_MASK			0xfffffffful
119 
120 #define	MDEG_ALLOC_HDL(_idx, _count)	(((uint64_t)_idx << MDEG_IDX_SHIFT) | \
121 					((uint64_t)(_count + 1) &	      \
122 					MDEG_COUNT_MASK))
123 #define	MDEG_HDL2IDX(hdl)		(hdl >> MDEG_IDX_SHIFT)
124 #define	MDEG_HDL2COUNT(hdl)		(hdl & MDEG_COUNT_MASK)
125 
126 static const char trunc_str[] = " ... }";
127 
128 /*
129  * Utility routines
130  */
131 static mdeg_clnt_t *mdeg_alloc_clnt(void);
132 static void mdeg_notify_client(void *);
133 static mde_cookie_t mdeg_find_start_node(md_t *, mdeg_node_spec_t *);
134 static boolean_t mdeg_node_spec_match(md_t *, mde_cookie_t, mdeg_node_spec_t *);
135 static void mdeg_get_diff_results(md_diff_cookie_t, mdeg_result_t *);
136 
137 int
138 mdeg_init(void)
139 {
140 	int	tblsz;
141 
142 	/*
143 	 * Grab the current MD
144 	 */
145 	if ((mdeg.md_curr = md_get_handle()) == NULL) {
146 		cmn_err(CE_WARN, "unable to cache snapshot of MD");
147 		return (-1);
148 	}
149 
150 	/*
151 	 * Initialize table of registered clients
152 	 */
153 	mdeg.maxclnts = MDEG_MAX_CLNTS_INIT;
154 
155 	tblsz = mdeg.maxclnts * sizeof (mdeg_clnt_t);
156 	mdeg.tbl = kmem_zalloc(tblsz, KM_SLEEP);
157 
158 	rw_init(&mdeg.rwlock, NULL, RW_DRIVER, NULL);
159 
160 	mdeg.nclnts = 0;
161 
162 	/*
163 	 * Initialize global lock
164 	 */
165 	mutex_init(&mdeg.lock, NULL, MUTEX_DRIVER, NULL);
166 
167 	/*
168 	 * Initialize the task queue
169 	 */
170 	mdeg.taskq = taskq_create("mdeg_taskq", 1, minclsyspri, 1,
171 	    MDEG_MAX_TASKQ_THR, TASKQ_PREPOPULATE | TASKQ_DYNAMIC);
172 
173 	/* ready to begin handling clients */
174 	mdeg.enabled = B_TRUE;
175 
176 	return (0);
177 }
178 
179 void
180 mdeg_fini(void)
181 {
182 	/*
183 	 * Flip the enabled switch off to make sure that
184 	 * no events get dispatched while things are being
185 	 * torn down.
186 	 */
187 	mdeg.enabled = B_FALSE;
188 
189 	/* destroy the task queue */
190 	taskq_destroy(mdeg.taskq);
191 
192 	/*
193 	 * Deallocate the table of registered clients
194 	 */
195 	kmem_free(mdeg.tbl, mdeg.maxclnts * sizeof (mdeg_clnt_t));
196 	rw_destroy(&mdeg.rwlock);
197 
198 	/*
199 	 * Free up the cached MDs.
200 	 */
201 	if (mdeg.md_curr)
202 		(void) md_fini_handle(mdeg.md_curr);
203 
204 	if (mdeg.md_prev)
205 		(void) md_fini_handle(mdeg.md_prev);
206 
207 	mutex_destroy(&mdeg.lock);
208 }
209 
210 static mdeg_clnt_t *
211 mdeg_alloc_clnt(void)
212 {
213 	mdeg_clnt_t	*clnt;
214 	int		idx;
215 	mdeg_clnt_t	*newtbl;
216 	uint_t		newmaxclnts;
217 	uint_t		newtblsz;
218 	uint_t		oldtblsz;
219 
220 	ASSERT(RW_WRITE_HELD(&mdeg.rwlock));
221 
222 	/* search for an unused slot in the table */
223 	for (idx = 0; idx < mdeg.maxclnts; idx++) {
224 		clnt = &mdeg.tbl[idx];
225 		if (!clnt->valid) {
226 			break;
227 		}
228 	}
229 
230 	/* found any empty slot */
231 	if (idx != mdeg.maxclnts) {
232 		goto found;
233 	}
234 
235 	/*
236 	 * There was no free space in the table. Grow
237 	 * the table to double its current size.
238 	 */
239 
240 	MDEG_DBG("client table full:\n");
241 	MDEG_DUMP_TABLE();
242 
243 	newmaxclnts = mdeg.maxclnts * 2;
244 	newtblsz = newmaxclnts * sizeof (mdeg_clnt_t);
245 
246 	newtbl = kmem_zalloc(newtblsz, KM_SLEEP);
247 
248 	/* copy old table data to the new table */
249 	oldtblsz = mdeg.maxclnts * sizeof (mdeg_clnt_t);
250 	bcopy(mdeg.tbl, newtbl, oldtblsz);
251 
252 	/*
253 	 * Since the old table was full, the first free entry
254 	 * will be just past the end of the old table data in
255 	 * the new table.
256 	 */
257 	clnt = &newtbl[mdeg.maxclnts];
258 
259 	/* clean up the old table */
260 	kmem_free(mdeg.tbl, oldtblsz);
261 	mdeg.tbl = newtbl;
262 	mdeg.maxclnts = newmaxclnts;
263 
264 found:
265 	ASSERT(clnt->valid == 0);
266 
267 	clnt->hdl = MDEG_ALLOC_HDL(idx, MDEG_HDL2COUNT(clnt->hdl));
268 
269 	return (clnt);
270 }
271 
272 static mdeg_clnt_t *
273 mdeg_get_client(mdeg_handle_t hdl)
274 {
275 	int		idx;
276 	mdeg_clnt_t	*clnt;
277 
278 	idx = MDEG_HDL2IDX(hdl);
279 
280 	/* check if index is out of bounds */
281 	if ((idx < 0) || (idx >= mdeg.maxclnts)) {
282 		MDEG_DBG("mdeg_get_client: index out of bounds\n");
283 		return (NULL);
284 	}
285 
286 	clnt = &mdeg.tbl[idx];
287 
288 	/* check for a valid client */
289 	if (!clnt->valid) {
290 		MDEG_DBG("mdeg_get_client: client is not valid\n");
291 		return (NULL);
292 	}
293 
294 	/* make sure the handle is an exact match */
295 	if (clnt->hdl != hdl) {
296 		MDEG_DBG("mdeg_get_client: bad handle\n");
297 		return (NULL);
298 	}
299 
300 	if (clnt->magic != MDEG_MAGIC) {
301 		MDEG_DBG("mdeg_get_client: bad magic\n");
302 		return (NULL);
303 	}
304 
305 	return (clnt);
306 }
307 
308 /*
309  * Send a notification to a client immediately after it registers.
310  * The result_t is a list of all the nodes that match their specified
311  * nodes of interest, all returned on the added list. This serves
312  * as a base of reference to the client. All future MD updates are
313  * relative to this list.
314  */
315 static int
316 mdeg_notify_client_reg(mdeg_clnt_t *clnt)
317 {
318 	md_t			*mdp = NULL;
319 	mde_str_cookie_t	nname;
320 	mde_str_cookie_t	aname;
321 	mde_cookie_t		startnode;
322 	int			nnodes;
323 	int			nodechk;
324 	mde_cookie_t		*listp = NULL;
325 	mdeg_result_t		*mdeg_res = NULL;
326 	int			rv = MDEG_SUCCESS;
327 
328 	mutex_enter(&mdeg.lock);
329 
330 	/*
331 	 * Handle the special case where the node specification
332 	 * is NULL. In this case, call the client callback without
333 	 * any results. All processing is left to the client.
334 	 */
335 	if (clnt->pspec == NULL) {
336 		/* call the client callback */
337 		(*clnt->cb)(clnt->cb_arg, NULL);
338 		goto done;
339 	}
340 
341 	if ((mdp = md_get_handle()) == NULL) {
342 		cmn_err(CE_WARN, "unable to retrieve current MD");
343 		rv = MDEG_FAILURE;
344 		goto done;
345 	}
346 
347 	startnode = mdeg_find_start_node(mdp, clnt->pspec);
348 	if (startnode == MDE_INVAL_ELEM_COOKIE) {
349 		/* not much we can do */
350 		cmn_err(CE_WARN, "unable to match node specifier");
351 		rv = MDEG_FAILURE;
352 		goto done;
353 	}
354 
355 	/*
356 	 * Use zalloc to provide correct default values for the
357 	 * unused removed, match_prev, and match_curr lists.
358 	 */
359 	mdeg_res = kmem_zalloc(sizeof (mdeg_result_t), KM_SLEEP);
360 
361 	nname = md_find_name(mdp, clnt->nmatch->namep);
362 	aname = md_find_name(mdp, "fwd");
363 
364 	nnodes = md_scan_dag(mdp, startnode, nname, aname, NULL);
365 
366 	if (nnodes == 0) {
367 		MDEG_DBG("mdeg_notify_client_reg: no nodes of interest\n");
368 		rv = MDEG_SUCCESS;
369 		goto done;
370 	} else if (nnodes == -1) {
371 		MDEG_DBG("error scanning DAG\n");
372 		rv = MDEG_FAILURE;
373 		goto done;
374 	}
375 
376 	MDEG_DBG("mdeg_notify_client_reg: %d node%s of interest\n",
377 	    nnodes, (nnodes == 1) ? "" : "s");
378 
379 	/* get the list of nodes of interest */
380 	listp = kmem_alloc(sizeof (mde_cookie_t) * nnodes, KM_SLEEP);
381 	nodechk = md_scan_dag(mdp, startnode, nname, aname, listp);
382 
383 	ASSERT(nodechk == nnodes);
384 
385 	mdeg_res->added.mdp = mdp;
386 	mdeg_res->added.mdep = listp;
387 	mdeg_res->added.nelem = nnodes;
388 
389 	/* call the client callback */
390 	(*clnt->cb)(clnt->cb_arg, mdeg_res);
391 
392 done:
393 	mutex_exit(&mdeg.lock);
394 
395 	if (mdp)
396 		(void) md_fini_handle(mdp);
397 
398 	if (listp)
399 		kmem_free(listp, sizeof (mde_cookie_t) * nnodes);
400 
401 	if (mdeg_res)
402 		kmem_free(mdeg_res, sizeof (mdeg_result_t));
403 
404 	return (rv);
405 }
406 
407 /*
408  * Register to receive an event notification when the system
409  * machine description is updated.
410  *
411  * Passing NULL for the node specification parameter is valid
412  * as long as the match specification is also NULL. In this
413  * case, the client will receive a notification when the MD
414  * has been updated, but the callback will not include any
415  * information. The client is then responsible for obtaining
416  * its own copy of the system MD and performing any processing
417  * manually.
418  */
419 int
420 mdeg_register(mdeg_node_spec_t *pspecp, mdeg_node_match_t *nmatchp,
421     mdeg_cb_t cb, void *cb_arg, mdeg_handle_t *hdlp)
422 {
423 	mdeg_clnt_t	*clnt;
424 
425 	/* should never be called from a callback */
426 	ASSERT(!taskq_member(mdeg.taskq, curthread));
427 
428 	/* node spec and node match must both be valid, or both NULL */
429 	if (((pspecp != NULL) && (nmatchp == NULL)) ||
430 	    ((pspecp == NULL) && (nmatchp != NULL))) {
431 		MDEG_DBG("mdeg_register: invalid parameters\n");
432 		return (MDEG_FAILURE);
433 	}
434 
435 	rw_enter(&mdeg.rwlock, RW_WRITER);
436 
437 	clnt = mdeg_alloc_clnt();
438 
439 	ASSERT(clnt);
440 
441 	/*
442 	 * Fill in the rest of the data
443 	 */
444 	clnt->nmatch = nmatchp;
445 	clnt->pspec = pspecp;
446 	clnt->cb = cb;
447 	clnt->cb_arg = cb_arg;
448 	clnt->magic = MDEG_MAGIC;
449 
450 	/* do this last */
451 	clnt->valid = B_TRUE;
452 
453 	MDEG_DBG("client registered (0x%lx):\n", clnt->hdl);
454 	MDEG_DUMP_CLNT(clnt);
455 
456 	mdeg.nclnts++;
457 
458 	if (mdeg_notify_client_reg(clnt) != MDEG_SUCCESS) {
459 		bzero(clnt, sizeof (mdeg_clnt_t));
460 		rw_exit(&mdeg.rwlock);
461 		return (MDEG_FAILURE);
462 	}
463 
464 	rw_exit(&mdeg.rwlock);
465 
466 	*hdlp = clnt->hdl;
467 
468 	return (MDEG_SUCCESS);
469 }
470 
471 int
472 mdeg_unregister(mdeg_handle_t hdl)
473 {
474 	mdeg_clnt_t	*clnt;
475 	mdeg_handle_t	mdh;
476 
477 	/* should never be called from a callback */
478 	ASSERT(!taskq_member(mdeg.taskq, curthread));
479 
480 	rw_enter(&mdeg.rwlock, RW_WRITER);
481 
482 	/* lookup the client */
483 	if ((clnt = mdeg_get_client(hdl)) == NULL) {
484 		rw_exit(&mdeg.rwlock);
485 		return (MDEG_FAILURE);
486 	}
487 
488 	MDEG_DBG("client unregistered (0x%lx):\n", hdl);
489 	MDEG_DUMP_CLNT(clnt);
490 
491 	/* save the handle to prevent reuse */
492 	mdh = clnt->hdl;
493 	bzero(clnt, sizeof (mdeg_clnt_t));
494 
495 	clnt->hdl = mdh;
496 
497 	mdeg.nclnts--;
498 
499 	rw_exit(&mdeg.rwlock);
500 
501 	return (MDEG_SUCCESS);
502 }
503 
504 /*
505  * Simple algorithm for now, grab the global lock and let all
506  * the clients update themselves in parallel. There is a lot of
507  * room for improvement here. We could eliminate some scans of
508  * the DAG by incrementally scanning at lower levels of the DAG
509  * rather than having each client start its own scan from the root.
510  */
511 void
512 mdeg_notify_clients(void)
513 {
514 	md_t		*md_new;
515 	mdeg_clnt_t	*clnt;
516 	int		idx;
517 	int		nclnt;
518 
519 	rw_enter(&mdeg.rwlock, RW_READER);
520 	mutex_enter(&mdeg.lock);
521 
522 	/*
523 	 * Rotate the MDs
524 	 */
525 	if ((md_new = md_get_handle()) == NULL) {
526 		cmn_err(CE_WARN, "unable to retrieve new MD");
527 		goto done;
528 	}
529 
530 	if (mdeg.md_prev) {
531 		(void) md_fini_handle(mdeg.md_prev);
532 	}
533 
534 	mdeg.md_prev = mdeg.md_curr;
535 	mdeg.md_curr = md_new;
536 
537 	if (mdeg.nclnts == 0) {
538 		MDEG_DBG("mdeg_notify_clients: no clients registered\n");
539 		goto done;
540 	}
541 
542 	/* dispatch the update notification to all clients */
543 	for (idx = 0, nclnt = 0; idx < mdeg.maxclnts; idx++) {
544 		clnt = &mdeg.tbl[idx];
545 
546 		if (!clnt->valid)
547 			continue;
548 
549 		MDEG_DBG("notifying client 0x%lx (%d/%d)\n", clnt->hdl,
550 		    ++nclnt, mdeg.nclnts);
551 
552 		(void) taskq_dispatch(mdeg.taskq, mdeg_notify_client,
553 		    (void *)clnt, TQ_SLEEP);
554 	}
555 
556 	taskq_wait(mdeg.taskq);
557 
558 done:
559 	mutex_exit(&mdeg.lock);
560 	rw_exit(&mdeg.rwlock);
561 }
562 
563 static void
564 mdeg_notify_client(void *arg)
565 {
566 	mdeg_clnt_t		*clnt = (mdeg_clnt_t *)arg;
567 	md_diff_cookie_t	mdd = MD_INVAL_DIFF_COOKIE;
568 	mdeg_result_t		mdeg_res;
569 	mde_cookie_t		md_prev_start;
570 	mde_cookie_t		md_curr_start;
571 
572 	rw_enter(&mdeg.rwlock, RW_READER);
573 
574 	if (!mdeg.enabled) {
575 		/* trying to shutdown */
576 		MDEG_DBG("mdeg_notify_client: mdeg disabled, aborting\n");
577 		goto cleanup;
578 	}
579 
580 	/*
581 	 * Handle the special case where the node specification
582 	 * is NULL. In this case, call the client callback without
583 	 * any results. All processing is left to the client.
584 	 */
585 	if (clnt->pspec == NULL) {
586 		/* call the client callback */
587 		(*clnt->cb)(clnt->cb_arg, NULL);
588 
589 		MDEG_DBG("MDEG client callback done\n");
590 		goto cleanup;
591 	}
592 
593 	/* find our start nodes */
594 	md_prev_start = mdeg_find_start_node(mdeg.md_prev, clnt->pspec);
595 	if (md_prev_start == MDE_INVAL_ELEM_COOKIE) {
596 		goto cleanup;
597 	}
598 
599 	md_curr_start = mdeg_find_start_node(mdeg.md_curr, clnt->pspec);
600 	if (md_curr_start == MDE_INVAL_ELEM_COOKIE) {
601 		goto cleanup;
602 	}
603 
604 	/* diff the MDs */
605 	mdd = md_diff_init(mdeg.md_prev, md_prev_start, mdeg.md_curr,
606 	    md_curr_start, clnt->nmatch->namep, clnt->nmatch->matchp);
607 
608 	if (mdd == MD_INVAL_DIFF_COOKIE) {
609 		MDEG_DBG("unable to diff MDs\n");
610 		goto cleanup;
611 	}
612 
613 	/*
614 	 * Cache the results of the diff
615 	 */
616 	mdeg_get_diff_results(mdd, &mdeg_res);
617 
618 	/* call the client callback */
619 	(*clnt->cb)(clnt->cb_arg, &mdeg_res);
620 
621 	MDEG_DBG("MDEG client callback done\n");
622 
623 cleanup:
624 	rw_exit(&mdeg.rwlock);
625 
626 	if (mdd != MD_INVAL_DIFF_COOKIE)
627 		(void) md_diff_fini(mdd);
628 }
629 
630 static mde_cookie_t
631 mdeg_find_start_node(md_t *md, mdeg_node_spec_t *nspec)
632 {
633 	mde_cookie_t		*nodesp;
634 	mde_str_cookie_t	nname;
635 	mde_str_cookie_t	aname;
636 	int			nnodes;
637 	int			idx;
638 
639 	if ((md == NULL) || (nspec == NULL))
640 		return (MDE_INVAL_ELEM_COOKIE);
641 
642 	nname = md_find_name(md, nspec->namep);
643 	aname = md_find_name(md, "fwd");
644 
645 	nnodes = md_scan_dag(md, NULL, nname, aname, NULL);
646 	if (nnodes == 0)
647 		return (MDE_INVAL_ELEM_COOKIE);
648 
649 	nodesp = kmem_alloc(sizeof (mde_cookie_t) * nnodes, KM_SLEEP);
650 
651 	(void) md_scan_dag(md, NULL, nname, aname, nodesp);
652 
653 	for (idx = 0; idx < nnodes; idx++) {
654 
655 		if (mdeg_node_spec_match(md, nodesp[idx], nspec)) {
656 			mde_cookie_t res = nodesp[idx];
657 
658 			kmem_free(nodesp, sizeof (mde_cookie_t) * nnodes);
659 			return (res);
660 		}
661 	}
662 
663 	kmem_free(nodesp, sizeof (mde_cookie_t) * nnodes);
664 	return (MDE_INVAL_ELEM_COOKIE);
665 }
666 
667 static boolean_t
668 mdeg_node_spec_match(md_t *md, mde_cookie_t node, mdeg_node_spec_t *nspec)
669 {
670 	mdeg_prop_spec_t	*prop;
671 
672 	ASSERT(md && nspec);
673 	ASSERT(node != MDE_INVAL_ELEM_COOKIE);
674 
675 	prop = nspec->specp;
676 
677 	while (prop->type != MDET_LIST_END) {
678 
679 		switch (prop->type) {
680 		case MDET_PROP_VAL: {
681 			uint64_t val;
682 
683 			if (md_get_prop_val(md, node, prop->namep, &val) != 0)
684 				return (B_FALSE);
685 
686 			if (prop->ps_val != val)
687 				return (B_FALSE);
688 
689 			break;
690 		}
691 		case MDET_PROP_STR: {
692 			char	*str;
693 
694 			if (md_get_prop_str(md, node, prop->namep, &str) != 0)
695 				return (B_FALSE);
696 
697 			if (strcmp(prop->ps_str, str) != 0)
698 				return (B_FALSE);
699 
700 			break;
701 		}
702 
703 		default:
704 			return (B_FALSE);
705 		}
706 
707 		prop++;
708 	}
709 
710 	return (B_TRUE);
711 }
712 
713 static void
714 mdeg_get_diff_results(md_diff_cookie_t mdd, mdeg_result_t *res)
715 {
716 	/*
717 	 * Cache added nodes.
718 	 */
719 	res->added.mdp = mdeg.md_curr;
720 	res->added.nelem = md_diff_added(mdd, &(res->added.mdep));
721 
722 	if (res->added.nelem == -1) {
723 		bzero(&(res->added), sizeof (mdeg_diff_t));
724 	}
725 
726 	/*
727 	 * Cache removed nodes.
728 	 */
729 	res->removed.mdp = mdeg.md_prev;
730 	res->removed.nelem = md_diff_removed(mdd, &(res->removed.mdep));
731 
732 	if (res->removed.nelem == -1) {
733 		bzero(&(res->removed), sizeof (mdeg_diff_t));
734 	}
735 
736 	/*
737 	 * Cache matching node pairs.
738 	 */
739 	res->match_curr.mdp = mdeg.md_curr;
740 	res->match_prev.mdp = mdeg.md_prev;
741 	res->match_curr.nelem = md_diff_matched(mdd, &(res->match_prev.mdep),
742 	    &(res->match_curr.mdep));
743 	res->match_prev.nelem = res->match_curr.nelem;
744 
745 	if (res->match_prev.nelem == -1) {
746 		bzero(&(res->match_prev), sizeof (mdeg_diff_t));
747 		bzero(&(res->match_curr), sizeof (mdeg_diff_t));
748 	}
749 }
750 
751 #ifdef DEBUG
752 /*
753  * Generate a string that represents the node specifier
754  * structure. Clamp the string length if the specifier
755  * structure contains too much information.
756  *
757  *	General form:
758  *
759  *		<nodename>:{<propname>=<propval>,...}
760  *	e.g.
761  *		vdevice:{name=vsw,reg=0x0}
762  */
763 static void
764 mdeg_spec_str(mdeg_node_spec_t *spec, char *buf, int len)
765 {
766 	mdeg_prop_spec_t	*prop;
767 	int			offset;
768 	boolean_t		first = B_TRUE;
769 	char			*end = buf + len;
770 
771 	offset = snprintf(buf, len, "%s:{", spec->namep);
772 
773 	buf += offset;
774 	len -= offset;
775 	if (len <= 0)
776 		goto trunc;
777 
778 	prop = spec->specp;
779 
780 	while (prop->type != MDET_LIST_END) {
781 
782 		switch (prop->type) {
783 		case MDET_PROP_VAL:
784 			offset = snprintf(buf, len, "%s%s=0x%lx",
785 			    (first) ? "" : ",", prop->namep, prop->ps_val);
786 			buf += offset;
787 			len -= offset;
788 			if (len <= 0)
789 				goto trunc;
790 			break;
791 
792 		case MDET_PROP_STR:
793 			offset = snprintf(buf, len, "%s%s=%s",
794 			    (first) ? "" : ",", prop->namep, prop->ps_str);
795 			buf += offset;
796 			len -= offset;
797 			if (len <= 0)
798 				goto trunc;
799 			break;
800 
801 		default:
802 			(void) snprintf(buf, len, "}");
803 			return;
804 		}
805 
806 		if (first)
807 			first = B_FALSE;
808 		prop++;
809 	}
810 
811 	(void) snprintf(buf, len, "}");
812 	return;
813 
814 trunc:
815 	/* string too long, truncate it */
816 	buf = end - (strlen(trunc_str) + 1);
817 	(void) sprintf(buf, trunc_str);
818 }
819 
820 /*
821  * Generate a string that represents the match structure.
822  * Clamp the string length if the match structure contains
823  * too much information.
824  *
825  *	General form:
826  *
827  *		<nodename>:{<propname>,...}
828  *	e.g.
829  *		nmatch=vport:{reg}
830  */
831 static void
832 mdeg_match_str(mdeg_node_match_t *match, char *buf, int len)
833 {
834 	md_prop_match_t	*prop;
835 	int		offset;
836 	boolean_t	first = B_TRUE;
837 	char		*end = buf + len;
838 
839 	offset = snprintf(buf, len, "%s:{", match->namep);
840 
841 	buf += offset;
842 	len -= offset;
843 	if (len <= 0)
844 		goto trunc;
845 
846 	prop = match->matchp;
847 
848 	while (prop->type != MDET_LIST_END) {
849 		offset = snprintf(buf, len, "%s%s", (first) ? "" : ",",
850 		    prop->namep);
851 		buf += offset;
852 		len -= offset;
853 		if (len <= 0)
854 			goto trunc;
855 
856 		if (first)
857 			first = B_FALSE;
858 		prop++;
859 	}
860 
861 	(void) snprintf(buf, len, "}");
862 	return;
863 
864 trunc:
865 	/* string too long, truncate it */
866 	buf = end - (strlen(trunc_str) + 1);
867 	(void) sprintf(buf, trunc_str);
868 }
869 
870 #define	MAX_FIELD_STR	80
871 
872 static void
873 mdeg_dump_clnt(mdeg_clnt_t *clnt)
874 {
875 	char	str[MAX_FIELD_STR] = "";
876 
877 	if (!clnt->valid) {
878 		MDEG_DBG("  valid=B_FALSE\n");
879 		return;
880 	}
881 
882 	if (clnt->pspec) {
883 		mdeg_spec_str(clnt->pspec, str, MAX_FIELD_STR);
884 		MDEG_DBG("  pspecp=%s\n", str);
885 	}
886 
887 	if (clnt->nmatch) {
888 		mdeg_match_str(clnt->nmatch, str, MAX_FIELD_STR);
889 		MDEG_DBG("  nmatch=%s\n", str);
890 	}
891 }
892 
893 static void
894 mdeg_dump_table(void)
895 {
896 	int		idx;
897 	mdeg_clnt_t	*clnt;
898 
899 	for (idx = 0; idx < mdeg.maxclnts; idx++) {
900 		clnt = &(mdeg.tbl[idx]);
901 
902 		MDEG_DBG("client %d (0x%lx):\n", idx, clnt->hdl);
903 		mdeg_dump_clnt(clnt);
904 	}
905 }
906 #endif /* DEBUG */
907