xref: /illumos-gate/usr/src/uts/sun4v/io/mdeg.c (revision 8d4e547db823a866b8f73efc0acdc423e2963caf)
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 2006 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 sychronize 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 sychronize 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.
257 	 */
258 	clnt = &mdeg.tbl[mdeg.maxclnts];
259 
260 	/* clean up the old table */
261 	kmem_free(mdeg.tbl, oldtblsz);
262 	mdeg.tbl = newtbl;
263 	mdeg.maxclnts = newmaxclnts;
264 
265 found:
266 	ASSERT(clnt->valid == 0);
267 
268 	clnt->hdl = MDEG_ALLOC_HDL(idx, MDEG_HDL2COUNT(clnt->hdl));
269 
270 	return (clnt);
271 }
272 
273 static mdeg_clnt_t *
274 mdeg_get_client(mdeg_handle_t hdl)
275 {
276 	int		idx;
277 	mdeg_clnt_t	*clnt;
278 
279 	idx = MDEG_HDL2IDX(hdl);
280 
281 	/* check if index is out of bounds */
282 	if ((idx < 0) || (idx >= mdeg.maxclnts)) {
283 		MDEG_DBG("mdeg_get_client: index out of bounds\n");
284 		return (NULL);
285 	}
286 
287 	clnt = &mdeg.tbl[idx];
288 
289 	/* check for a valid client */
290 	if (!clnt->valid) {
291 		MDEG_DBG("mdeg_get_client: client is not valid\n");
292 		return (NULL);
293 	}
294 
295 	/* make sure the handle is an exact match */
296 	if (clnt->hdl != hdl) {
297 		MDEG_DBG("mdeg_get_client: bad handle\n");
298 		return (NULL);
299 	}
300 
301 	if (clnt->magic != MDEG_MAGIC) {
302 		MDEG_DBG("mdeg_get_client: bad magic\n");
303 		return (NULL);
304 	}
305 
306 	return (clnt);
307 }
308 
309 /*
310  * Send a notification to a client immediately after it registers.
311  * The result_t is a list of all the nodes that match their specified
312  * nodes of interest, all returned on the added list. This serves
313  * as a base of reference to the client. All future MD updates are
314  * relative to this list.
315  */
316 static int
317 mdeg_notify_client_reg(mdeg_clnt_t *clnt)
318 {
319 	md_t			*mdp = NULL;
320 	mde_str_cookie_t	nname;
321 	mde_str_cookie_t	aname;
322 	mde_cookie_t		startnode;
323 	int			nnodes;
324 	int			nodechk;
325 	mde_cookie_t		*listp = NULL;
326 	mdeg_result_t		*mdeg_res = NULL;
327 	int			rv = MDEG_SUCCESS;
328 
329 	mutex_enter(&mdeg.lock);
330 
331 	/*
332 	 * Handle the special case where the node specification
333 	 * is NULL. In this case, call the client callback without
334 	 * any results. All processing is left to the client.
335 	 */
336 	if (clnt->pspec == NULL) {
337 		/* call the client callback */
338 		(*clnt->cb)(clnt->cb_arg, NULL);
339 		goto done;
340 	}
341 
342 	if ((mdp = md_get_handle()) == NULL) {
343 		cmn_err(CE_WARN, "unable to retrieve current MD");
344 		rv = MDEG_FAILURE;
345 		goto done;
346 	}
347 
348 	startnode = mdeg_find_start_node(mdp, clnt->pspec);
349 	if (startnode == MDE_INVAL_ELEM_COOKIE) {
350 		/* not much we can do */
351 		cmn_err(CE_WARN, "unable to match node specifier");
352 		rv = MDEG_FAILURE;
353 		goto done;
354 	}
355 
356 	/*
357 	 * Use zalloc to provide correct default values for the
358 	 * unused removed, match_prev, and match_curr lists.
359 	 */
360 	mdeg_res = kmem_zalloc(sizeof (mdeg_result_t), KM_SLEEP);
361 
362 	nname = md_find_name(mdp, clnt->nmatch->namep);
363 	aname = md_find_name(mdp, "fwd");
364 
365 	nnodes = md_scan_dag(mdp, startnode, nname, aname, NULL);
366 
367 	if (nnodes == 0) {
368 		MDEG_DBG("mdeg_notify_client_reg: no nodes of interest\n");
369 		rv = MDEG_SUCCESS;
370 		goto done;
371 	} else if (nnodes == -1) {
372 		MDEG_DBG("error scanning DAG\n");
373 		rv = MDEG_FAILURE;
374 		goto done;
375 	}
376 
377 	MDEG_DBG("mdeg_notify_client_reg: %d node%s of interest\n",
378 	    nnodes, (nnodes == 1) ? "" : "s");
379 
380 	/* get the list of nodes of interest */
381 	listp = kmem_alloc(sizeof (mde_cookie_t) * nnodes, KM_SLEEP);
382 	nodechk = md_scan_dag(mdp, startnode, nname, aname, listp);
383 
384 	ASSERT(nodechk == nnodes);
385 
386 	mdeg_res->added.mdp = mdp;
387 	mdeg_res->added.mdep = listp;
388 	mdeg_res->added.nelem = nnodes;
389 
390 	/* call the client callback */
391 	(*clnt->cb)(clnt->cb_arg, mdeg_res);
392 
393 done:
394 	mutex_exit(&mdeg.lock);
395 
396 	if (mdp)
397 		(void) md_fini_handle(mdp);
398 
399 	if (listp)
400 		kmem_free(listp, sizeof (mde_cookie_t) * nnodes);
401 
402 	if (mdeg_res)
403 		kmem_free(mdeg_res, sizeof (mdeg_result_t));
404 
405 	return (rv);
406 }
407 
408 /*
409  * Register to receive an event notification when the system
410  * machine description is updated.
411  *
412  * Passing NULL for the node specification parameter is valid
413  * as long as the match specification is also NULL. In this
414  * case, the client will receive a notification when the MD
415  * has been updated, but the callback will not include any
416  * information. The client is then responsible for obtaining
417  * its own copy of the system MD and performing any processing
418  * manually.
419  */
420 int
421 mdeg_register(mdeg_node_spec_t *pspecp, mdeg_node_match_t *nmatchp,
422     mdeg_cb_t cb, void *cb_arg, mdeg_handle_t *hdlp)
423 {
424 	mdeg_clnt_t	*clnt;
425 
426 	/*
427 	 * If the RW lock is held, a client is calling
428 	 * register from its own callback.
429 	 */
430 	if (RW_LOCK_HELD(&mdeg.rwlock)) {
431 		MDEG_DBG("mdeg_register: rwlock already held\n");
432 		return (MDEG_FAILURE);
433 	}
434 
435 	/* node spec and node match must both be valid, or both NULL */
436 	if (((pspecp != NULL) && (nmatchp == NULL)) ||
437 	    ((pspecp == NULL) && (nmatchp != NULL))) {
438 		MDEG_DBG("mdeg_register: invalid parameters\n");
439 		return (MDEG_FAILURE);
440 	}
441 
442 	rw_enter(&mdeg.rwlock, RW_WRITER);
443 
444 	clnt = mdeg_alloc_clnt();
445 
446 	ASSERT(clnt);
447 
448 	/*
449 	 * Fill in the rest of the data
450 	 */
451 	clnt->nmatch = nmatchp;
452 	clnt->pspec = pspecp;
453 	clnt->cb = cb;
454 	clnt->cb_arg = cb_arg;
455 	clnt->magic = MDEG_MAGIC;
456 
457 	/* do this last */
458 	clnt->valid = B_TRUE;
459 
460 	MDEG_DBG("client registered (0x%lx):\n", clnt->hdl);
461 	MDEG_DUMP_CLNT(clnt);
462 
463 	mdeg.nclnts++;
464 
465 	if (mdeg_notify_client_reg(clnt) != MDEG_SUCCESS) {
466 		bzero(clnt, sizeof (mdeg_clnt_t));
467 		rw_exit(&mdeg.rwlock);
468 		return (MDEG_FAILURE);
469 	}
470 
471 	rw_exit(&mdeg.rwlock);
472 
473 	*hdlp = clnt->hdl;
474 
475 	return (MDEG_SUCCESS);
476 }
477 
478 int
479 mdeg_unregister(mdeg_handle_t hdl)
480 {
481 	mdeg_clnt_t	*clnt;
482 	mdeg_handle_t	mdh;
483 
484 	/*
485 	 * If the RW lock is held, a client is calling
486 	 * unregister from its own callback.
487 	 */
488 	if (RW_LOCK_HELD(&mdeg.rwlock)) {
489 		MDEG_DBG("mdeg_unregister: rwlock already held\n");
490 		return (MDEG_FAILURE);
491 	}
492 
493 	/* lookup the client */
494 	if ((clnt = mdeg_get_client(hdl)) == NULL) {
495 		return (MDEG_FAILURE);
496 	}
497 
498 	rw_enter(&mdeg.rwlock, RW_WRITER);
499 
500 	MDEG_DBG("client unregistered (0x%lx):\n", hdl);
501 	MDEG_DUMP_CLNT(clnt);
502 
503 	/* save the handle to prevent reuse */
504 	mdh = clnt->hdl;
505 	bzero(clnt, sizeof (mdeg_clnt_t));
506 
507 	clnt->hdl = mdh;
508 
509 	mdeg.nclnts--;
510 
511 	rw_exit(&mdeg.rwlock);
512 
513 	return (MDEG_SUCCESS);
514 }
515 
516 /*
517  * Simple algorithm for now, grab the global lock and let all
518  * the clients update themselves in parallel. There is a lot of
519  * room for improvement here. We could eliminate some scans of
520  * the DAG by imcrementally scanning at lower levels of the DAG
521  * rather than having each client start its own scan from the root.
522  */
523 void
524 mdeg_notify_clients(void)
525 {
526 	md_t		*md_new;
527 	mdeg_clnt_t	*clnt;
528 	int		idx;
529 	int		nclnt;
530 
531 	rw_enter(&mdeg.rwlock, RW_READER);
532 	mutex_enter(&mdeg.lock);
533 
534 	/*
535 	 * Rotate the MDs
536 	 */
537 	if ((md_new = md_get_handle()) == NULL) {
538 		cmn_err(CE_WARN, "unable to retrieve new MD");
539 		goto done;
540 	}
541 
542 	if (mdeg.md_prev) {
543 		(void) md_fini_handle(mdeg.md_prev);
544 	}
545 
546 	mdeg.md_prev = mdeg.md_curr;
547 	mdeg.md_curr = md_new;
548 
549 	if (mdeg.nclnts == 0) {
550 		MDEG_DBG("mdeg_notify_clients: no clients registered\n");
551 		goto done;
552 	}
553 
554 	/* dispatch the update notification to all clients */
555 	for (idx = 0, nclnt = 0; idx < mdeg.maxclnts; idx++) {
556 		clnt = &mdeg.tbl[idx];
557 
558 		if (!clnt->valid)
559 			continue;
560 
561 		MDEG_DBG("notifying client 0x%lx (%d/%d)\n", clnt->hdl,
562 		    ++nclnt, mdeg.nclnts);
563 
564 		(void) taskq_dispatch(mdeg.taskq, mdeg_notify_client,
565 		    (void *)clnt, TQ_SLEEP);
566 	}
567 
568 	taskq_wait(mdeg.taskq);
569 
570 done:
571 	mutex_exit(&mdeg.lock);
572 	rw_exit(&mdeg.rwlock);
573 }
574 
575 static void
576 mdeg_notify_client(void *arg)
577 {
578 	mdeg_clnt_t		*clnt = (mdeg_clnt_t *)arg;
579 	md_diff_cookie_t	mdd = MD_INVAL_DIFF_COOKIE;
580 	mdeg_result_t		mdeg_res;
581 	mde_cookie_t		md_prev_start;
582 	mde_cookie_t		md_curr_start;
583 
584 	rw_enter(&mdeg.rwlock, RW_READER);
585 
586 	if (!mdeg.enabled) {
587 		/* trying to shutdown */
588 		MDEG_DBG("mdeg_notify_client: mdeg disabled, aborting\n");
589 		goto cleanup;
590 	}
591 
592 	/*
593 	 * Handle the special case where the node specification
594 	 * is NULL. In this case, call the client callback without
595 	 * any results. All processing is left to the client.
596 	 */
597 	if (clnt->pspec == NULL) {
598 		/* call the client callback */
599 		(*clnt->cb)(clnt->cb_arg, NULL);
600 
601 		MDEG_DBG("MDEG client callback done\n");
602 		goto cleanup;
603 	}
604 
605 	/* find our start nodes */
606 	md_prev_start = mdeg_find_start_node(mdeg.md_prev, clnt->pspec);
607 	if (md_prev_start == MDE_INVAL_ELEM_COOKIE) {
608 		goto cleanup;
609 	}
610 
611 	md_curr_start = mdeg_find_start_node(mdeg.md_curr, clnt->pspec);
612 	if (md_curr_start == MDE_INVAL_ELEM_COOKIE) {
613 		goto cleanup;
614 	}
615 
616 	/* diff the MDs */
617 	mdd = md_diff_init(mdeg.md_prev, md_prev_start, mdeg.md_curr,
618 	    md_curr_start, clnt->nmatch->namep, clnt->nmatch->matchp);
619 
620 	if (mdd == MD_INVAL_DIFF_COOKIE) {
621 		MDEG_DBG("unable to diff MDs\n");
622 		goto cleanup;
623 	}
624 
625 	/*
626 	 * Cache the results of the diff
627 	 */
628 	mdeg_get_diff_results(mdd, &mdeg_res);
629 
630 	/* call the client callback */
631 	(*clnt->cb)(clnt->cb_arg, &mdeg_res);
632 
633 	MDEG_DBG("MDEG client callback done\n");
634 
635 cleanup:
636 	rw_exit(&mdeg.rwlock);
637 
638 	if (mdd != MD_INVAL_DIFF_COOKIE)
639 		(void) md_diff_fini(mdd);
640 }
641 
642 static mde_cookie_t
643 mdeg_find_start_node(md_t *md, mdeg_node_spec_t *nspec)
644 {
645 	mde_cookie_t		*nodesp;
646 	mde_str_cookie_t	nname;
647 	mde_str_cookie_t	aname;
648 	int			nnodes;
649 	int			idx;
650 
651 	if ((md == NULL) || (nspec == NULL))
652 		return (MDE_INVAL_ELEM_COOKIE);
653 
654 	nname = md_find_name(md, nspec->namep);
655 	aname = md_find_name(md, "fwd");
656 
657 	nnodes = md_scan_dag(md, NULL, nname, aname, NULL);
658 	if (nnodes == 0)
659 		return (MDE_INVAL_ELEM_COOKIE);
660 
661 	nodesp = kmem_alloc(sizeof (mde_cookie_t) * nnodes, KM_SLEEP);
662 
663 	(void) md_scan_dag(md, NULL, nname, aname, nodesp);
664 
665 	for (idx = 0; idx < nnodes; idx++) {
666 
667 		if (mdeg_node_spec_match(md, nodesp[idx], nspec)) {
668 			mde_cookie_t res = nodesp[idx];
669 
670 			kmem_free(nodesp, sizeof (mde_cookie_t) * nnodes);
671 			return (res);
672 		}
673 	}
674 
675 	kmem_free(nodesp, sizeof (mde_cookie_t) * nnodes);
676 	return (MDE_INVAL_ELEM_COOKIE);
677 }
678 
679 static boolean_t
680 mdeg_node_spec_match(md_t *md, mde_cookie_t node, mdeg_node_spec_t *nspec)
681 {
682 	mdeg_prop_spec_t	*prop;
683 
684 	ASSERT(md && nspec);
685 	ASSERT(node != MDE_INVAL_ELEM_COOKIE);
686 
687 	prop = nspec->specp;
688 
689 	while (prop->type != MDET_LIST_END) {
690 
691 		switch (prop->type) {
692 		case MDET_PROP_VAL: {
693 			uint64_t val;
694 
695 			if (md_get_prop_val(md, node, prop->namep, &val) != 0)
696 				return (B_FALSE);
697 
698 			if (prop->ps_val != val)
699 				return (B_FALSE);
700 
701 			break;
702 		}
703 		case MDET_PROP_STR: {
704 			char	*str;
705 
706 			if (md_get_prop_str(md, node, prop->namep, &str) != 0)
707 				return (B_FALSE);
708 
709 			if (strcmp(prop->ps_str, str) != 0)
710 				return (B_FALSE);
711 
712 			break;
713 		}
714 
715 		default:
716 			return (B_FALSE);
717 		}
718 
719 		prop++;
720 	}
721 
722 	return (B_TRUE);
723 }
724 
725 static void
726 mdeg_get_diff_results(md_diff_cookie_t mdd, mdeg_result_t *res)
727 {
728 	/*
729 	 * Cache added nodes.
730 	 */
731 	res->added.mdp = mdeg.md_curr;
732 	res->added.nelem = md_diff_added(mdd, &(res->added.mdep));
733 
734 	if (res->added.nelem == -1) {
735 		bzero(&(res->added), sizeof (mdeg_diff_t));
736 	}
737 
738 	/*
739 	 * Cache removed nodes.
740 	 */
741 	res->removed.mdp = mdeg.md_prev;
742 	res->removed.nelem = md_diff_removed(mdd, &(res->removed.mdep));
743 
744 	if (res->removed.nelem == -1) {
745 		bzero(&(res->removed), sizeof (mdeg_diff_t));
746 	}
747 
748 	/*
749 	 * Cache matching node pairs.
750 	 */
751 	res->match_curr.mdp = mdeg.md_curr;
752 	res->match_prev.mdp = mdeg.md_prev;
753 	res->match_curr.nelem = md_diff_matched(mdd, &(res->match_prev.mdep),
754 	    &(res->match_curr.mdep));
755 	res->match_prev.nelem = res->match_curr.nelem;
756 
757 	if (res->match_prev.nelem == -1) {
758 		bzero(&(res->match_prev), sizeof (mdeg_diff_t));
759 		bzero(&(res->match_curr), sizeof (mdeg_diff_t));
760 	}
761 }
762 
763 #ifdef DEBUG
764 /*
765  * Generate a string that represents the node specifier
766  * structure. Clamp the string length if the specifier
767  * structure contains too much information.
768  *
769  *	General form:
770  *
771  *		<nodename>:{<propname>=<propval>,...}
772  *	e.g.
773  *		vdevice:{name=vsw,reg=0x0}
774  */
775 static void
776 mdeg_spec_str(mdeg_node_spec_t *spec, char *buf, int len)
777 {
778 	mdeg_prop_spec_t	*prop;
779 	int			offset;
780 	boolean_t		first = B_TRUE;
781 	char			*end = buf + len;
782 
783 	offset = snprintf(buf, len, "%s:{", spec->namep);
784 
785 	buf += offset;
786 	len -= offset;
787 	if (len <= 0)
788 		goto trunc;
789 
790 	prop = spec->specp;
791 
792 	while (prop->type != MDET_LIST_END) {
793 
794 		switch (prop->type) {
795 		case MDET_PROP_VAL:
796 			offset = snprintf(buf, len, "%s%s=0x%lx",
797 			    (first) ? "" : ",", prop->namep, prop->ps_val);
798 			buf += offset;
799 			len -= offset;
800 			if (len <= 0)
801 				goto trunc;
802 			break;
803 
804 		case MDET_PROP_STR:
805 			offset = snprintf(buf, len, "%s%s=%s",
806 			    (first) ? "" : ",", prop->namep, prop->ps_str);
807 			buf += offset;
808 			len -= offset;
809 			if (len <= 0)
810 				goto trunc;
811 			break;
812 
813 		default:
814 			(void) snprintf(buf, len, "}");
815 			return;
816 		}
817 
818 		if (first)
819 			first = B_FALSE;
820 		prop++;
821 	}
822 
823 	(void) snprintf(buf, len, "}");
824 	return;
825 
826 trunc:
827 	/* string too long, truncate it */
828 	buf = end - (strlen(trunc_str) + 1);
829 	(void) sprintf(buf, trunc_str);
830 }
831 
832 /*
833  * Generate a string that represents the match structure.
834  * Clamp the string length if the match structure contains
835  * too much information.
836  *
837  *	General form:
838  *
839  *		<nodename>:{<propname>,...}
840  *	e.g.
841  *		nmatch=vport:{reg}
842  */
843 static void
844 mdeg_match_str(mdeg_node_match_t *match, char *buf, int len)
845 {
846 	md_prop_match_t	*prop;
847 	int		offset;
848 	boolean_t	first = B_TRUE;
849 	char		*end = buf + len;
850 
851 	offset = snprintf(buf, len, "%s:{", match->namep);
852 
853 	buf += offset;
854 	len -= offset;
855 	if (len <= 0)
856 		goto trunc;
857 
858 	prop = match->matchp;
859 
860 	while (prop->type != MDET_LIST_END) {
861 		offset = snprintf(buf, len, "%s%s", (first) ? "" : ",",
862 		    prop->namep);
863 		buf += offset;
864 		len -= offset;
865 		if (len <= 0)
866 			goto trunc;
867 
868 		if (first)
869 			first = B_FALSE;
870 		prop++;
871 	}
872 
873 	(void) snprintf(buf, len, "}");
874 	return;
875 
876 trunc:
877 	/* string too long, truncate it */
878 	buf = end - (strlen(trunc_str) + 1);
879 	(void) sprintf(buf, trunc_str);
880 }
881 
882 #define	MAX_FIELD_STR	80
883 
884 static void
885 mdeg_dump_clnt(mdeg_clnt_t *clnt)
886 {
887 	char	str[MAX_FIELD_STR];
888 
889 	if (!clnt->valid) {
890 		MDEG_DBG("  valid=B_FALSE\n");
891 		return;
892 	}
893 
894 	mdeg_spec_str(clnt->pspec, str, MAX_FIELD_STR);
895 	MDEG_DBG("  pspecp=%s\n", str);
896 
897 	mdeg_match_str(clnt->nmatch, str, MAX_FIELD_STR);
898 	MDEG_DBG("  nmatch=%s\n", str);
899 }
900 
901 static void
902 mdeg_dump_table(void)
903 {
904 	int		idx;
905 	mdeg_clnt_t	*clnt;
906 
907 	for (idx = 0; idx < mdeg.maxclnts; idx++) {
908 		clnt = &(mdeg.tbl[idx]);
909 
910 		MDEG_DBG("client %d (0x%lx):\n", idx, clnt->hdl);
911 		mdeg_dump_clnt(clnt);
912 	}
913 }
914 #endif /* DEBUG */
915