xref: /illumos-gate/usr/src/uts/sun4v/io/mdeg.c (revision 46b592853d0f4f11781b6b0a7533f267c6aee132)
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 2009 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 	/*
426 	 * If the RW lock is held, a client is calling
427 	 * register from its own callback.
428 	 */
429 	if (RW_LOCK_HELD(&mdeg.rwlock)) {
430 		MDEG_DBG("mdeg_register: rwlock already held\n");
431 		return (MDEG_FAILURE);
432 	}
433 
434 	/* node spec and node match must both be valid, or both NULL */
435 	if (((pspecp != NULL) && (nmatchp == NULL)) ||
436 	    ((pspecp == NULL) && (nmatchp != NULL))) {
437 		MDEG_DBG("mdeg_register: invalid parameters\n");
438 		return (MDEG_FAILURE);
439 	}
440 
441 	rw_enter(&mdeg.rwlock, RW_WRITER);
442 
443 	clnt = mdeg_alloc_clnt();
444 
445 	ASSERT(clnt);
446 
447 	/*
448 	 * Fill in the rest of the data
449 	 */
450 	clnt->nmatch = nmatchp;
451 	clnt->pspec = pspecp;
452 	clnt->cb = cb;
453 	clnt->cb_arg = cb_arg;
454 	clnt->magic = MDEG_MAGIC;
455 
456 	/* do this last */
457 	clnt->valid = B_TRUE;
458 
459 	MDEG_DBG("client registered (0x%lx):\n", clnt->hdl);
460 	MDEG_DUMP_CLNT(clnt);
461 
462 	mdeg.nclnts++;
463 
464 	if (mdeg_notify_client_reg(clnt) != MDEG_SUCCESS) {
465 		bzero(clnt, sizeof (mdeg_clnt_t));
466 		rw_exit(&mdeg.rwlock);
467 		return (MDEG_FAILURE);
468 	}
469 
470 	rw_exit(&mdeg.rwlock);
471 
472 	*hdlp = clnt->hdl;
473 
474 	return (MDEG_SUCCESS);
475 }
476 
477 int
478 mdeg_unregister(mdeg_handle_t hdl)
479 {
480 	mdeg_clnt_t	*clnt;
481 	mdeg_handle_t	mdh;
482 
483 	/*
484 	 * If the RW lock is held, a client is calling
485 	 * unregister from its own callback.
486 	 */
487 	if (RW_LOCK_HELD(&mdeg.rwlock)) {
488 		MDEG_DBG("mdeg_unregister: rwlock already held\n");
489 		return (MDEG_FAILURE);
490 	}
491 
492 	/* lookup the client */
493 	if ((clnt = mdeg_get_client(hdl)) == NULL) {
494 		return (MDEG_FAILURE);
495 	}
496 
497 	rw_enter(&mdeg.rwlock, RW_WRITER);
498 
499 	MDEG_DBG("client unregistered (0x%lx):\n", hdl);
500 	MDEG_DUMP_CLNT(clnt);
501 
502 	/* save the handle to prevent reuse */
503 	mdh = clnt->hdl;
504 	bzero(clnt, sizeof (mdeg_clnt_t));
505 
506 	clnt->hdl = mdh;
507 
508 	mdeg.nclnts--;
509 
510 	rw_exit(&mdeg.rwlock);
511 
512 	return (MDEG_SUCCESS);
513 }
514 
515 /*
516  * Simple algorithm for now, grab the global lock and let all
517  * the clients update themselves in parallel. There is a lot of
518  * room for improvement here. We could eliminate some scans of
519  * the DAG by incrementally scanning at lower levels of the DAG
520  * rather than having each client start its own scan from the root.
521  */
522 void
523 mdeg_notify_clients(void)
524 {
525 	md_t		*md_new;
526 	mdeg_clnt_t	*clnt;
527 	int		idx;
528 	int		nclnt;
529 
530 	rw_enter(&mdeg.rwlock, RW_READER);
531 	mutex_enter(&mdeg.lock);
532 
533 	/*
534 	 * Rotate the MDs
535 	 */
536 	if ((md_new = md_get_handle()) == NULL) {
537 		cmn_err(CE_WARN, "unable to retrieve new MD");
538 		goto done;
539 	}
540 
541 	if (mdeg.md_prev) {
542 		(void) md_fini_handle(mdeg.md_prev);
543 	}
544 
545 	mdeg.md_prev = mdeg.md_curr;
546 	mdeg.md_curr = md_new;
547 
548 	if (mdeg.nclnts == 0) {
549 		MDEG_DBG("mdeg_notify_clients: no clients registered\n");
550 		goto done;
551 	}
552 
553 	/* dispatch the update notification to all clients */
554 	for (idx = 0, nclnt = 0; idx < mdeg.maxclnts; idx++) {
555 		clnt = &mdeg.tbl[idx];
556 
557 		if (!clnt->valid)
558 			continue;
559 
560 		MDEG_DBG("notifying client 0x%lx (%d/%d)\n", clnt->hdl,
561 		    ++nclnt, mdeg.nclnts);
562 
563 		(void) taskq_dispatch(mdeg.taskq, mdeg_notify_client,
564 		    (void *)clnt, TQ_SLEEP);
565 	}
566 
567 	taskq_wait(mdeg.taskq);
568 
569 done:
570 	mutex_exit(&mdeg.lock);
571 	rw_exit(&mdeg.rwlock);
572 }
573 
574 static void
575 mdeg_notify_client(void *arg)
576 {
577 	mdeg_clnt_t		*clnt = (mdeg_clnt_t *)arg;
578 	md_diff_cookie_t	mdd = MD_INVAL_DIFF_COOKIE;
579 	mdeg_result_t		mdeg_res;
580 	mde_cookie_t		md_prev_start;
581 	mde_cookie_t		md_curr_start;
582 
583 	rw_enter(&mdeg.rwlock, RW_READER);
584 
585 	if (!mdeg.enabled) {
586 		/* trying to shutdown */
587 		MDEG_DBG("mdeg_notify_client: mdeg disabled, aborting\n");
588 		goto cleanup;
589 	}
590 
591 	/*
592 	 * Handle the special case where the node specification
593 	 * is NULL. In this case, call the client callback without
594 	 * any results. All processing is left to the client.
595 	 */
596 	if (clnt->pspec == NULL) {
597 		/* call the client callback */
598 		(*clnt->cb)(clnt->cb_arg, NULL);
599 
600 		MDEG_DBG("MDEG client callback done\n");
601 		goto cleanup;
602 	}
603 
604 	/* find our start nodes */
605 	md_prev_start = mdeg_find_start_node(mdeg.md_prev, clnt->pspec);
606 	if (md_prev_start == MDE_INVAL_ELEM_COOKIE) {
607 		goto cleanup;
608 	}
609 
610 	md_curr_start = mdeg_find_start_node(mdeg.md_curr, clnt->pspec);
611 	if (md_curr_start == MDE_INVAL_ELEM_COOKIE) {
612 		goto cleanup;
613 	}
614 
615 	/* diff the MDs */
616 	mdd = md_diff_init(mdeg.md_prev, md_prev_start, mdeg.md_curr,
617 	    md_curr_start, clnt->nmatch->namep, clnt->nmatch->matchp);
618 
619 	if (mdd == MD_INVAL_DIFF_COOKIE) {
620 		MDEG_DBG("unable to diff MDs\n");
621 		goto cleanup;
622 	}
623 
624 	/*
625 	 * Cache the results of the diff
626 	 */
627 	mdeg_get_diff_results(mdd, &mdeg_res);
628 
629 	/* call the client callback */
630 	(*clnt->cb)(clnt->cb_arg, &mdeg_res);
631 
632 	MDEG_DBG("MDEG client callback done\n");
633 
634 cleanup:
635 	rw_exit(&mdeg.rwlock);
636 
637 	if (mdd != MD_INVAL_DIFF_COOKIE)
638 		(void) md_diff_fini(mdd);
639 }
640 
641 static mde_cookie_t
642 mdeg_find_start_node(md_t *md, mdeg_node_spec_t *nspec)
643 {
644 	mde_cookie_t		*nodesp;
645 	mde_str_cookie_t	nname;
646 	mde_str_cookie_t	aname;
647 	int			nnodes;
648 	int			idx;
649 
650 	if ((md == NULL) || (nspec == NULL))
651 		return (MDE_INVAL_ELEM_COOKIE);
652 
653 	nname = md_find_name(md, nspec->namep);
654 	aname = md_find_name(md, "fwd");
655 
656 	nnodes = md_scan_dag(md, NULL, nname, aname, NULL);
657 	if (nnodes == 0)
658 		return (MDE_INVAL_ELEM_COOKIE);
659 
660 	nodesp = kmem_alloc(sizeof (mde_cookie_t) * nnodes, KM_SLEEP);
661 
662 	(void) md_scan_dag(md, NULL, nname, aname, nodesp);
663 
664 	for (idx = 0; idx < nnodes; idx++) {
665 
666 		if (mdeg_node_spec_match(md, nodesp[idx], nspec)) {
667 			mde_cookie_t res = nodesp[idx];
668 
669 			kmem_free(nodesp, sizeof (mde_cookie_t) * nnodes);
670 			return (res);
671 		}
672 	}
673 
674 	kmem_free(nodesp, sizeof (mde_cookie_t) * nnodes);
675 	return (MDE_INVAL_ELEM_COOKIE);
676 }
677 
678 static boolean_t
679 mdeg_node_spec_match(md_t *md, mde_cookie_t node, mdeg_node_spec_t *nspec)
680 {
681 	mdeg_prop_spec_t	*prop;
682 
683 	ASSERT(md && nspec);
684 	ASSERT(node != MDE_INVAL_ELEM_COOKIE);
685 
686 	prop = nspec->specp;
687 
688 	while (prop->type != MDET_LIST_END) {
689 
690 		switch (prop->type) {
691 		case MDET_PROP_VAL: {
692 			uint64_t val;
693 
694 			if (md_get_prop_val(md, node, prop->namep, &val) != 0)
695 				return (B_FALSE);
696 
697 			if (prop->ps_val != val)
698 				return (B_FALSE);
699 
700 			break;
701 		}
702 		case MDET_PROP_STR: {
703 			char	*str;
704 
705 			if (md_get_prop_str(md, node, prop->namep, &str) != 0)
706 				return (B_FALSE);
707 
708 			if (strcmp(prop->ps_str, str) != 0)
709 				return (B_FALSE);
710 
711 			break;
712 		}
713 
714 		default:
715 			return (B_FALSE);
716 		}
717 
718 		prop++;
719 	}
720 
721 	return (B_TRUE);
722 }
723 
724 static void
725 mdeg_get_diff_results(md_diff_cookie_t mdd, mdeg_result_t *res)
726 {
727 	/*
728 	 * Cache added nodes.
729 	 */
730 	res->added.mdp = mdeg.md_curr;
731 	res->added.nelem = md_diff_added(mdd, &(res->added.mdep));
732 
733 	if (res->added.nelem == -1) {
734 		bzero(&(res->added), sizeof (mdeg_diff_t));
735 	}
736 
737 	/*
738 	 * Cache removed nodes.
739 	 */
740 	res->removed.mdp = mdeg.md_prev;
741 	res->removed.nelem = md_diff_removed(mdd, &(res->removed.mdep));
742 
743 	if (res->removed.nelem == -1) {
744 		bzero(&(res->removed), sizeof (mdeg_diff_t));
745 	}
746 
747 	/*
748 	 * Cache matching node pairs.
749 	 */
750 	res->match_curr.mdp = mdeg.md_curr;
751 	res->match_prev.mdp = mdeg.md_prev;
752 	res->match_curr.nelem = md_diff_matched(mdd, &(res->match_prev.mdep),
753 	    &(res->match_curr.mdep));
754 	res->match_prev.nelem = res->match_curr.nelem;
755 
756 	if (res->match_prev.nelem == -1) {
757 		bzero(&(res->match_prev), sizeof (mdeg_diff_t));
758 		bzero(&(res->match_curr), sizeof (mdeg_diff_t));
759 	}
760 }
761 
762 #ifdef DEBUG
763 /*
764  * Generate a string that represents the node specifier
765  * structure. Clamp the string length if the specifier
766  * structure contains too much information.
767  *
768  *	General form:
769  *
770  *		<nodename>:{<propname>=<propval>,...}
771  *	e.g.
772  *		vdevice:{name=vsw,reg=0x0}
773  */
774 static void
775 mdeg_spec_str(mdeg_node_spec_t *spec, char *buf, int len)
776 {
777 	mdeg_prop_spec_t	*prop;
778 	int			offset;
779 	boolean_t		first = B_TRUE;
780 	char			*end = buf + len;
781 
782 	offset = snprintf(buf, len, "%s:{", spec->namep);
783 
784 	buf += offset;
785 	len -= offset;
786 	if (len <= 0)
787 		goto trunc;
788 
789 	prop = spec->specp;
790 
791 	while (prop->type != MDET_LIST_END) {
792 
793 		switch (prop->type) {
794 		case MDET_PROP_VAL:
795 			offset = snprintf(buf, len, "%s%s=0x%lx",
796 			    (first) ? "" : ",", prop->namep, prop->ps_val);
797 			buf += offset;
798 			len -= offset;
799 			if (len <= 0)
800 				goto trunc;
801 			break;
802 
803 		case MDET_PROP_STR:
804 			offset = snprintf(buf, len, "%s%s=%s",
805 			    (first) ? "" : ",", prop->namep, prop->ps_str);
806 			buf += offset;
807 			len -= offset;
808 			if (len <= 0)
809 				goto trunc;
810 			break;
811 
812 		default:
813 			(void) snprintf(buf, len, "}");
814 			return;
815 		}
816 
817 		if (first)
818 			first = B_FALSE;
819 		prop++;
820 	}
821 
822 	(void) snprintf(buf, len, "}");
823 	return;
824 
825 trunc:
826 	/* string too long, truncate it */
827 	buf = end - (strlen(trunc_str) + 1);
828 	(void) sprintf(buf, trunc_str);
829 }
830 
831 /*
832  * Generate a string that represents the match structure.
833  * Clamp the string length if the match structure contains
834  * too much information.
835  *
836  *	General form:
837  *
838  *		<nodename>:{<propname>,...}
839  *	e.g.
840  *		nmatch=vport:{reg}
841  */
842 static void
843 mdeg_match_str(mdeg_node_match_t *match, char *buf, int len)
844 {
845 	md_prop_match_t	*prop;
846 	int		offset;
847 	boolean_t	first = B_TRUE;
848 	char		*end = buf + len;
849 
850 	offset = snprintf(buf, len, "%s:{", match->namep);
851 
852 	buf += offset;
853 	len -= offset;
854 	if (len <= 0)
855 		goto trunc;
856 
857 	prop = match->matchp;
858 
859 	while (prop->type != MDET_LIST_END) {
860 		offset = snprintf(buf, len, "%s%s", (first) ? "" : ",",
861 		    prop->namep);
862 		buf += offset;
863 		len -= offset;
864 		if (len <= 0)
865 			goto trunc;
866 
867 		if (first)
868 			first = B_FALSE;
869 		prop++;
870 	}
871 
872 	(void) snprintf(buf, len, "}");
873 	return;
874 
875 trunc:
876 	/* string too long, truncate it */
877 	buf = end - (strlen(trunc_str) + 1);
878 	(void) sprintf(buf, trunc_str);
879 }
880 
881 #define	MAX_FIELD_STR	80
882 
883 static void
884 mdeg_dump_clnt(mdeg_clnt_t *clnt)
885 {
886 	char	str[MAX_FIELD_STR] = "";
887 
888 	if (!clnt->valid) {
889 		MDEG_DBG("  valid=B_FALSE\n");
890 		return;
891 	}
892 
893 	if (clnt->pspec) {
894 		mdeg_spec_str(clnt->pspec, str, MAX_FIELD_STR);
895 		MDEG_DBG("  pspecp=%s\n", str);
896 	}
897 
898 	if (clnt->nmatch) {
899 		mdeg_match_str(clnt->nmatch, str, MAX_FIELD_STR);
900 		MDEG_DBG("  nmatch=%s\n", str);
901 	}
902 }
903 
904 static void
905 mdeg_dump_table(void)
906 {
907 	int		idx;
908 	mdeg_clnt_t	*clnt;
909 
910 	for (idx = 0; idx < mdeg.maxclnts; idx++) {
911 		clnt = &(mdeg.tbl[idx]);
912 
913 		MDEG_DBG("client %d (0x%lx):\n", idx, clnt->hdl);
914 		mdeg_dump_clnt(clnt);
915 	}
916 }
917 #endif /* DEBUG */
918