/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * MD Event Generator (MDEG) Module */ #include #include #include #include #include #include #include #include /* * A single client registration */ typedef struct mdeg_clnt { boolean_t valid; /* structure is in active use */ mdeg_node_match_t *nmatch; /* node match filter */ mdeg_node_spec_t *pspec; /* parent match filter */ mdeg_cb_t cb; /* the client callback */ caddr_t cb_arg; /* argument to the callback */ uint64_t magic; /* sanity checking magic */ mdeg_handle_t hdl; /* handle assigned by MDEG */ } mdeg_clnt_t; /* * Global MDEG data * * Locking Strategy: * * mdeg.lock - lock used to synchronize system-wide MD updates. An * MD update must be treated as an atomic event. The lock is * taken when notification that a new MD is available and held * until all clients have been notified. * * mdeg.rwlock - lock used to synchronize access to the table of * registered clients. The reader lock must be held when looking * up client information in the table. The writer lock must be * held when modifying any client information. */ static struct mdeg { taskq_t *taskq; /* for internal processing */ boolean_t enabled; /* enable/disable taskq processing */ kmutex_t lock; /* synchronize MD updates */ md_t *md_prev; /* previous MD */ md_t *md_curr; /* current MD */ mdeg_clnt_t *tbl; /* table of registered clients */ krwlock_t rwlock; /* client table lock */ uint_t maxclnts; /* client table size */ uint_t nclnts; /* current number of clients */ } mdeg; /* * Debugging routines */ #ifdef DEBUG uint_t mdeg_debug = 0x0; static void mdeg_dump_clnt(mdeg_clnt_t *clnt); static void mdeg_dump_table(void); #define MDEG_DBG if (mdeg_debug) printf #define MDEG_DUMP_CLNT mdeg_dump_clnt #define MDEG_DUMP_TABLE mdeg_dump_table #else /* DEBUG */ #define MDEG_DBG _NOTE(CONSTCOND) if (0) printf #define MDEG_DUMP_CLNT(...) #define MDEG_DUMP_TABLE(...) #endif /* DEBUG */ /* * Global constants */ #define MDEG_MAX_TASKQ_THR 512 /* maximum number of taskq threads */ #define MDEG_MAX_CLNTS_INIT 64 /* initial client table size */ #define MDEG_MAGIC 0x4D4445475F48444Cull /* 'MDEG_HDL' */ /* * A client handle is a 64 bit value with two pieces of * information encoded in it. The upper 32 bits are the * index into the table of a particular client structure. * The lower 32 bits are a counter that is incremented * each time a client structure is reused. */ #define MDEG_IDX_SHIFT 32 #define MDEG_COUNT_MASK 0xfffffffful #define MDEG_ALLOC_HDL(_idx, _count) (((uint64_t)_idx << MDEG_IDX_SHIFT) | \ ((uint64_t)(_count + 1) & \ MDEG_COUNT_MASK)) #define MDEG_HDL2IDX(hdl) (hdl >> MDEG_IDX_SHIFT) #define MDEG_HDL2COUNT(hdl) (hdl & MDEG_COUNT_MASK) static const char trunc_str[] = " ... }"; /* * Utility routines */ static mdeg_clnt_t *mdeg_alloc_clnt(void); static void mdeg_notify_client(void *); static mde_cookie_t mdeg_find_start_node(md_t *, mdeg_node_spec_t *); static boolean_t mdeg_node_spec_match(md_t *, mde_cookie_t, mdeg_node_spec_t *); static void mdeg_get_diff_results(md_diff_cookie_t, mdeg_result_t *); int mdeg_init(void) { int tblsz; /* * Grab the current MD */ if ((mdeg.md_curr = md_get_handle()) == NULL) { cmn_err(CE_WARN, "unable to cache snapshot of MD"); return (-1); } /* * Initialize table of registered clients */ mdeg.maxclnts = MDEG_MAX_CLNTS_INIT; tblsz = mdeg.maxclnts * sizeof (mdeg_clnt_t); mdeg.tbl = kmem_zalloc(tblsz, KM_SLEEP); rw_init(&mdeg.rwlock, NULL, RW_DRIVER, NULL); mdeg.nclnts = 0; /* * Initialize global lock */ mutex_init(&mdeg.lock, NULL, MUTEX_DRIVER, NULL); /* * Initialize the task queue */ mdeg.taskq = taskq_create("mdeg_taskq", 1, minclsyspri, 1, MDEG_MAX_TASKQ_THR, TASKQ_PREPOPULATE | TASKQ_DYNAMIC); /* ready to begin handling clients */ mdeg.enabled = B_TRUE; return (0); } void mdeg_fini(void) { /* * Flip the enabled switch off to make sure that * no events get dispatched while things are being * torn down. */ mdeg.enabled = B_FALSE; /* destroy the task queue */ taskq_destroy(mdeg.taskq); /* * Deallocate the table of registered clients */ kmem_free(mdeg.tbl, mdeg.maxclnts * sizeof (mdeg_clnt_t)); rw_destroy(&mdeg.rwlock); /* * Free up the cached MDs. */ if (mdeg.md_curr) (void) md_fini_handle(mdeg.md_curr); if (mdeg.md_prev) (void) md_fini_handle(mdeg.md_prev); mutex_destroy(&mdeg.lock); } static mdeg_clnt_t * mdeg_alloc_clnt(void) { mdeg_clnt_t *clnt; int idx; mdeg_clnt_t *newtbl; uint_t newmaxclnts; uint_t newtblsz; uint_t oldtblsz; ASSERT(RW_WRITE_HELD(&mdeg.rwlock)); /* search for an unused slot in the table */ for (idx = 0; idx < mdeg.maxclnts; idx++) { clnt = &mdeg.tbl[idx]; if (!clnt->valid) { break; } } /* found any empty slot */ if (idx != mdeg.maxclnts) { goto found; } /* * There was no free space in the table. Grow * the table to double its current size. */ MDEG_DBG("client table full:\n"); MDEG_DUMP_TABLE(); newmaxclnts = mdeg.maxclnts * 2; newtblsz = newmaxclnts * sizeof (mdeg_clnt_t); newtbl = kmem_zalloc(newtblsz, KM_SLEEP); /* copy old table data to the new table */ oldtblsz = mdeg.maxclnts * sizeof (mdeg_clnt_t); bcopy(mdeg.tbl, newtbl, oldtblsz); /* * Since the old table was full, the first free entry * will be just past the end of the old table data in * the new table. */ clnt = &newtbl[mdeg.maxclnts]; /* clean up the old table */ kmem_free(mdeg.tbl, oldtblsz); mdeg.tbl = newtbl; mdeg.maxclnts = newmaxclnts; found: ASSERT(clnt->valid == 0); clnt->hdl = MDEG_ALLOC_HDL(idx, MDEG_HDL2COUNT(clnt->hdl)); return (clnt); } static mdeg_clnt_t * mdeg_get_client(mdeg_handle_t hdl) { int idx; mdeg_clnt_t *clnt; idx = MDEG_HDL2IDX(hdl); /* check if index is out of bounds */ if ((idx < 0) || (idx >= mdeg.maxclnts)) { MDEG_DBG("mdeg_get_client: index out of bounds\n"); return (NULL); } clnt = &mdeg.tbl[idx]; /* check for a valid client */ if (!clnt->valid) { MDEG_DBG("mdeg_get_client: client is not valid\n"); return (NULL); } /* make sure the handle is an exact match */ if (clnt->hdl != hdl) { MDEG_DBG("mdeg_get_client: bad handle\n"); return (NULL); } if (clnt->magic != MDEG_MAGIC) { MDEG_DBG("mdeg_get_client: bad magic\n"); return (NULL); } return (clnt); } /* * Send a notification to a client immediately after it registers. * The result_t is a list of all the nodes that match their specified * nodes of interest, all returned on the added list. This serves * as a base of reference to the client. All future MD updates are * relative to this list. */ static int mdeg_notify_client_reg(mdeg_clnt_t *clnt) { md_t *mdp = NULL; mde_str_cookie_t nname; mde_str_cookie_t aname; mde_cookie_t startnode; int nnodes; int nodechk; mde_cookie_t *listp = NULL; mdeg_result_t *mdeg_res = NULL; int rv = MDEG_SUCCESS; mutex_enter(&mdeg.lock); /* * Handle the special case where the node specification * is NULL. In this case, call the client callback without * any results. All processing is left to the client. */ if (clnt->pspec == NULL) { /* call the client callback */ (void) (*clnt->cb)(clnt->cb_arg, NULL); goto done; } if ((mdp = md_get_handle()) == NULL) { cmn_err(CE_WARN, "unable to retrieve current MD"); rv = MDEG_FAILURE; goto done; } startnode = mdeg_find_start_node(mdp, clnt->pspec); if (startnode == MDE_INVAL_ELEM_COOKIE) { /* not much we can do */ cmn_err(CE_WARN, "unable to match node specifier"); rv = MDEG_FAILURE; goto done; } /* * Use zalloc to provide correct default values for the * unused removed, match_prev, and match_curr lists. */ mdeg_res = kmem_zalloc(sizeof (mdeg_result_t), KM_SLEEP); nname = md_find_name(mdp, clnt->nmatch->namep); aname = md_find_name(mdp, "fwd"); nnodes = md_scan_dag(mdp, startnode, nname, aname, NULL); if (nnodes == 0) { MDEG_DBG("mdeg_notify_client_reg: no nodes of interest\n"); rv = MDEG_SUCCESS; goto done; } else if (nnodes == -1) { MDEG_DBG("error scanning DAG\n"); rv = MDEG_FAILURE; goto done; } MDEG_DBG("mdeg_notify_client_reg: %d node%s of interest\n", nnodes, (nnodes == 1) ? "" : "s"); /* get the list of nodes of interest */ listp = kmem_alloc(sizeof (mde_cookie_t) * nnodes, KM_SLEEP); nodechk = md_scan_dag(mdp, startnode, nname, aname, listp); ASSERT(nodechk == nnodes); mdeg_res->added.mdp = mdp; mdeg_res->added.mdep = listp; mdeg_res->added.nelem = nnodes; /* call the client callback */ (void) (*clnt->cb)(clnt->cb_arg, mdeg_res); done: mutex_exit(&mdeg.lock); if (mdp) (void) md_fini_handle(mdp); if (listp) kmem_free(listp, sizeof (mde_cookie_t) * nnodes); if (mdeg_res) kmem_free(mdeg_res, sizeof (mdeg_result_t)); return (rv); } /* * Register to receive an event notification when the system * machine description is updated. * * Passing NULL for the node specification parameter is valid * as long as the match specification is also NULL. In this * case, the client will receive a notification when the MD * has been updated, but the callback will not include any * information. The client is then responsible for obtaining * its own copy of the system MD and performing any processing * manually. */ int mdeg_register(mdeg_node_spec_t *pspecp, mdeg_node_match_t *nmatchp, mdeg_cb_t cb, void *cb_arg, mdeg_handle_t *hdlp) { mdeg_clnt_t *clnt; /* should never be called from a callback */ ASSERT(!taskq_member(mdeg.taskq, curthread)); /* node spec and node match must both be valid, or both NULL */ if (((pspecp != NULL) && (nmatchp == NULL)) || ((pspecp == NULL) && (nmatchp != NULL))) { MDEG_DBG("mdeg_register: invalid parameters\n"); return (MDEG_FAILURE); } rw_enter(&mdeg.rwlock, RW_WRITER); clnt = mdeg_alloc_clnt(); ASSERT(clnt); /* * Fill in the rest of the data */ clnt->nmatch = nmatchp; clnt->pspec = pspecp; clnt->cb = cb; clnt->cb_arg = cb_arg; clnt->magic = MDEG_MAGIC; /* do this last */ clnt->valid = B_TRUE; MDEG_DBG("client registered (0x%lx):\n", clnt->hdl); MDEG_DUMP_CLNT(clnt); mdeg.nclnts++; if (mdeg_notify_client_reg(clnt) != MDEG_SUCCESS) { bzero(clnt, sizeof (mdeg_clnt_t)); rw_exit(&mdeg.rwlock); return (MDEG_FAILURE); } rw_exit(&mdeg.rwlock); *hdlp = clnt->hdl; return (MDEG_SUCCESS); } int mdeg_unregister(mdeg_handle_t hdl) { mdeg_clnt_t *clnt; mdeg_handle_t mdh; /* should never be called from a callback */ ASSERT(!taskq_member(mdeg.taskq, curthread)); rw_enter(&mdeg.rwlock, RW_WRITER); /* lookup the client */ if ((clnt = mdeg_get_client(hdl)) == NULL) { rw_exit(&mdeg.rwlock); return (MDEG_FAILURE); } MDEG_DBG("client unregistered (0x%lx):\n", hdl); MDEG_DUMP_CLNT(clnt); /* save the handle to prevent reuse */ mdh = clnt->hdl; bzero(clnt, sizeof (mdeg_clnt_t)); clnt->hdl = mdh; mdeg.nclnts--; rw_exit(&mdeg.rwlock); return (MDEG_SUCCESS); } /* * Simple algorithm for now, grab the global lock and let all * the clients update themselves in parallel. There is a lot of * room for improvement here. We could eliminate some scans of * the DAG by incrementally scanning at lower levels of the DAG * rather than having each client start its own scan from the root. */ void mdeg_notify_clients(void) { md_t *md_new; mdeg_clnt_t *clnt; int idx; int nclnt; rw_enter(&mdeg.rwlock, RW_READER); mutex_enter(&mdeg.lock); /* * Rotate the MDs */ if ((md_new = md_get_handle()) == NULL) { cmn_err(CE_WARN, "unable to retrieve new MD"); goto done; } if (mdeg.md_prev) { (void) md_fini_handle(mdeg.md_prev); } mdeg.md_prev = mdeg.md_curr; mdeg.md_curr = md_new; if (mdeg.nclnts == 0) { MDEG_DBG("mdeg_notify_clients: no clients registered\n"); goto done; } /* dispatch the update notification to all clients */ for (idx = 0, nclnt = 0; idx < mdeg.maxclnts; idx++) { clnt = &mdeg.tbl[idx]; if (!clnt->valid) continue; MDEG_DBG("notifying client 0x%lx (%d/%d)\n", clnt->hdl, ++nclnt, mdeg.nclnts); (void) taskq_dispatch(mdeg.taskq, mdeg_notify_client, (void *)clnt, TQ_SLEEP); } /* * Wait for all mdeg_notify_client notifications to * finish while we are still holding mdeg.rwlock. */ taskq_wait(mdeg.taskq); done: mutex_exit(&mdeg.lock); rw_exit(&mdeg.rwlock); } static void mdeg_notify_client(void *arg) { mdeg_clnt_t *clnt = (mdeg_clnt_t *)arg; md_diff_cookie_t mdd = MD_INVAL_DIFF_COOKIE; mdeg_result_t mdeg_res; mde_cookie_t md_prev_start; mde_cookie_t md_curr_start; /* * mdeg.rwlock must be held as a reader while this function * executes. However, we do not need to acquire the lock as a * reader here because it is held as a reader by the thread * executing mdeg_notify_clients which triggers the execution * of this function from a taskq. Since mdeg_notify_clients * holds the lock as a reader until the taskq callbacks have * completed, it will be held for the life of this function call. * Furthermore, we must not attempt to acquire the lock as a * reader with rw_enter because if there is a pending writer, * we will block, creating a circular deadlock with this function, * the writer, and mdeg_notify_clients. Since we do not need * to acquire the lock, just assert that it is held. */ ASSERT(RW_READ_HELD(&mdeg.rwlock)); if (!mdeg.enabled) { /* trying to shutdown */ MDEG_DBG("mdeg_notify_client: mdeg disabled, aborting\n"); goto cleanup; } /* * Handle the special case where the node specification * is NULL. In this case, call the client callback without * any results. All processing is left to the client. */ if (clnt->pspec == NULL) { /* call the client callback */ (void) (*clnt->cb)(clnt->cb_arg, NULL); MDEG_DBG("MDEG client callback done\n"); goto cleanup; } /* find our start nodes */ md_prev_start = mdeg_find_start_node(mdeg.md_prev, clnt->pspec); if (md_prev_start == MDE_INVAL_ELEM_COOKIE) { goto cleanup; } md_curr_start = mdeg_find_start_node(mdeg.md_curr, clnt->pspec); if (md_curr_start == MDE_INVAL_ELEM_COOKIE) { goto cleanup; } /* diff the MDs */ mdd = md_diff_init(mdeg.md_prev, md_prev_start, mdeg.md_curr, md_curr_start, clnt->nmatch->namep, clnt->nmatch->matchp); if (mdd == MD_INVAL_DIFF_COOKIE) { MDEG_DBG("unable to diff MDs\n"); goto cleanup; } /* * Cache the results of the diff */ mdeg_get_diff_results(mdd, &mdeg_res); /* call the client callback */ (void) (*clnt->cb)(clnt->cb_arg, &mdeg_res); MDEG_DBG("MDEG client callback done\n"); cleanup: if (mdd != MD_INVAL_DIFF_COOKIE) (void) md_diff_fini(mdd); } static mde_cookie_t mdeg_find_start_node(md_t *md, mdeg_node_spec_t *nspec) { mde_cookie_t *nodesp; mde_str_cookie_t nname; mde_str_cookie_t aname; int nnodes; int idx; if ((md == NULL) || (nspec == NULL)) return (MDE_INVAL_ELEM_COOKIE); nname = md_find_name(md, nspec->namep); aname = md_find_name(md, "fwd"); nnodes = md_scan_dag(md, 0, nname, aname, NULL); if (nnodes == 0) return (MDE_INVAL_ELEM_COOKIE); nodesp = kmem_alloc(sizeof (mde_cookie_t) * nnodes, KM_SLEEP); (void) md_scan_dag(md, 0, nname, aname, nodesp); for (idx = 0; idx < nnodes; idx++) { if (mdeg_node_spec_match(md, nodesp[idx], nspec)) { mde_cookie_t res = nodesp[idx]; kmem_free(nodesp, sizeof (mde_cookie_t) * nnodes); return (res); } } kmem_free(nodesp, sizeof (mde_cookie_t) * nnodes); return (MDE_INVAL_ELEM_COOKIE); } static boolean_t mdeg_node_spec_match(md_t *md, mde_cookie_t node, mdeg_node_spec_t *nspec) { mdeg_prop_spec_t *prop; ASSERT(md && nspec); ASSERT(node != MDE_INVAL_ELEM_COOKIE); prop = nspec->specp; while (prop->type != MDET_LIST_END) { switch (prop->type) { case MDET_PROP_VAL: { uint64_t val; if (md_get_prop_val(md, node, prop->namep, &val) != 0) return (B_FALSE); if (prop->ps_val != val) return (B_FALSE); break; } case MDET_PROP_STR: { char *str; if (md_get_prop_str(md, node, prop->namep, &str) != 0) return (B_FALSE); if (strcmp(prop->ps_str, str) != 0) return (B_FALSE); break; } default: return (B_FALSE); } prop++; } return (B_TRUE); } static void mdeg_get_diff_results(md_diff_cookie_t mdd, mdeg_result_t *res) { /* * Cache added nodes. */ res->added.mdp = mdeg.md_curr; res->added.nelem = md_diff_added(mdd, &(res->added.mdep)); if (res->added.nelem == -1) { bzero(&(res->added), sizeof (mdeg_diff_t)); } /* * Cache removed nodes. */ res->removed.mdp = mdeg.md_prev; res->removed.nelem = md_diff_removed(mdd, &(res->removed.mdep)); if (res->removed.nelem == -1) { bzero(&(res->removed), sizeof (mdeg_diff_t)); } /* * Cache matching node pairs. */ res->match_curr.mdp = mdeg.md_curr; res->match_prev.mdp = mdeg.md_prev; res->match_curr.nelem = md_diff_matched(mdd, &(res->match_prev.mdep), &(res->match_curr.mdep)); res->match_prev.nelem = res->match_curr.nelem; if (res->match_prev.nelem == -1) { bzero(&(res->match_prev), sizeof (mdeg_diff_t)); bzero(&(res->match_curr), sizeof (mdeg_diff_t)); } } #ifdef DEBUG /* * Generate a string that represents the node specifier * structure. Clamp the string length if the specifier * structure contains too much information. * * General form: * * :{=,...} * e.g. * vdevice:{name=vsw,reg=0x0} */ static void mdeg_spec_str(mdeg_node_spec_t *spec, char *buf, int len) { mdeg_prop_spec_t *prop; int offset; boolean_t first = B_TRUE; char *end = buf + len; offset = snprintf(buf, len, "%s:{", spec->namep); buf += offset; len -= offset; if (len <= 0) goto trunc; prop = spec->specp; while (prop->type != MDET_LIST_END) { switch (prop->type) { case MDET_PROP_VAL: offset = snprintf(buf, len, "%s%s=0x%lx", (first) ? "" : ",", prop->namep, prop->ps_val); buf += offset; len -= offset; if (len <= 0) goto trunc; break; case MDET_PROP_STR: offset = snprintf(buf, len, "%s%s=%s", (first) ? "" : ",", prop->namep, prop->ps_str); buf += offset; len -= offset; if (len <= 0) goto trunc; break; default: (void) snprintf(buf, len, "}"); return; } if (first) first = B_FALSE; prop++; } (void) snprintf(buf, len, "}"); return; trunc: /* string too long, truncate it */ buf = end - (strlen(trunc_str) + 1); (void) sprintf(buf, trunc_str); } /* * Generate a string that represents the match structure. * Clamp the string length if the match structure contains * too much information. * * General form: * * :{,...} * e.g. * nmatch=vport:{reg} */ static void mdeg_match_str(mdeg_node_match_t *match, char *buf, int len) { md_prop_match_t *prop; int offset; boolean_t first = B_TRUE; char *end = buf + len; offset = snprintf(buf, len, "%s:{", match->namep); buf += offset; len -= offset; if (len <= 0) goto trunc; prop = match->matchp; while (prop->type != MDET_LIST_END) { offset = snprintf(buf, len, "%s%s", (first) ? "" : ",", prop->namep); buf += offset; len -= offset; if (len <= 0) goto trunc; if (first) first = B_FALSE; prop++; } (void) snprintf(buf, len, "}"); return; trunc: /* string too long, truncate it */ buf = end - (strlen(trunc_str) + 1); (void) sprintf(buf, trunc_str); } #define MAX_FIELD_STR 80 static void mdeg_dump_clnt(mdeg_clnt_t *clnt) { char str[MAX_FIELD_STR] = ""; if (!clnt->valid) { MDEG_DBG(" valid=B_FALSE\n"); return; } if (clnt->pspec) { mdeg_spec_str(clnt->pspec, str, MAX_FIELD_STR); MDEG_DBG(" pspecp=%s\n", str); } if (clnt->nmatch) { mdeg_match_str(clnt->nmatch, str, MAX_FIELD_STR); MDEG_DBG(" nmatch=%s\n", str); } } static void mdeg_dump_table(void) { int idx; mdeg_clnt_t *clnt; for (idx = 0; idx < mdeg.maxclnts; idx++) { clnt = &(mdeg.tbl[idx]); MDEG_DBG("client %d (0x%lx):\n", idx, clnt->hdl); mdeg_dump_clnt(clnt); } } #endif /* DEBUG */