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