diff options
author | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
---|---|---|
committer | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
commit | 7c478bd95313f5f23a4c958a745db2134aa03244 (patch) | |
tree | c871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/uts/common/os/dacf.c | |
download | illumos-gate-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz |
OpenSolaris Launch
Diffstat (limited to 'usr/src/uts/common/os/dacf.c')
-rw-r--r-- | usr/src/uts/common/os/dacf.c | 1292 |
1 files changed, 1292 insertions, 0 deletions
diff --git a/usr/src/uts/common/os/dacf.c b/usr/src/uts/common/os/dacf.c new file mode 100644 index 0000000000..91e34dabbf --- /dev/null +++ b/usr/src/uts/common/os/dacf.c @@ -0,0 +1,1292 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (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 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * DACF: device autoconfiguration support + * + * DACF provides a fast, lightweight policy engine for the I/O subsystem. + * This policy engine provides a mechanism for auto-configuring and + * auto-unconfiguring devices. + * + * After a device is attach(9E)ed, additional configuration may be needed in + * order to make the device available for use by the system. For example, + * STREAMS modules may need to be pushed atop the driver in order to create + * a STREAMS stack. If the device is to be removed from the system, these + * configuration operations need to be undone, and the device prepared for + * detach(9E). + * + * It is desirable to move the implementation of such policies outside of the + * kernel proper, since such operations are typically infrequent. To this end, + * DACF manages kernel modules in (module_path)/dacf directories. These adhere + * to the api defined in sys/dacf.h, and register sets of configuration + * operations. The kernel loads these modules when the operations they + * implement are needed, and can unload them at any time thereafter. + * Implementing configuration operations in external modules can also increase + * code reuse. + * + * DACF provides a policy database which associates + * + * (device descr., kernel action) --> (configuration operation, parameters) + * + * - Device description is matching rule, for example: + * minor-nodetype="ddi_keyboard" + * - Kernel action is a reference to a dacf kernel hook. + * currently supported are "post-attach" and "pre-detach" + * - Configuration action is a reference to a module and a set of operations + * within the module, for example: consconfig:kbd_config + * - Parameters is a list of name="value" parameters to be passed to the + * configuration operation when invoked. + * + * The contents of the rules database are loaded from /etc/dacf.conf upon boot. + * + * DACF kernel hooks are comprised of a call into the rule-matching engine, + * using parameters from the hook in order find a matching rule. If one is + * found, the framework can invoke the configuration operation immediately, or + * defer doing so until later, by putting the rule on a 'reservation list.' + */ + +#include <sys/param.h> +#include <sys/modctl.h> +#include <sys/sysmacros.h> +#include <sys/kmem.h> +#include <sys/cmn_err.h> +#include <sys/pathname.h> +#include <sys/ddi_impldefs.h> +#include <sys/sunddi.h> +#include <sys/autoconf.h> +#include <sys/modhash.h> +#include <sys/dacf.h> +#include <sys/dacf_impl.h> +#include <sys/systm.h> +#include <sys/varargs.h> +#include <sys/debug.h> +#include <sys/log.h> +#include <sys/fs/snode.h> + +/* + * Enumeration of the ops exported by the dacf framework. + * + * To add a new op to the framework, add it to this list, update dacf.h, + * (don't miss DACF_NUM_OPIDS) and modify dacf_rule_matrix. + * + */ +typedef struct dacf_opmap { + const char *name; + dacf_opid_t id; +} dacf_opmap_t; + +static dacf_opmap_t dacf_ops[] = { + { "post-attach", DACF_OPID_POSTATTACH }, + { "pre-detach", DACF_OPID_PREDETACH }, + { NULL, 0 }, +}; + +/* + * Enumeration of the options exported by the dacf framework (currently none). + * + * To add a new option, add it to this array. + */ +typedef struct dacf_opt { + const char *optname; + uint_t optmask; +} dacf_opt_t; + +static dacf_opt_t dacf_options[] = { +#ifdef DEBUG + { "testopt", 1 }, + { "testopt2", 2 }, +#endif + { NULL, 0 }, +}; + +static char kmod_name[] = "__kernel"; + +/* + * Enumeration of the device specifiers exported by the dacf framework. + * + * To add a new devspec to the framework, add it to this list, update dacf.h, + * (don't miss DACF_NUM_DEVSPECS), modify dacf_rule_matrix, and modify + * dacf_match(). + */ +typedef struct dacf_ds { + const char *name; + dacf_devspec_t id; +} dacf_ds_t; + +static dacf_ds_t dacf_devspecs[] = { + { "minor-nodetype", DACF_DS_MIN_NT }, + { "driver-minorname", DACF_DS_DRV_MNAME }, + { "device-path", DACF_DS_DEV_PATH }, + { NULL, NULL }, +}; + +mod_hash_t *posta_mntype, *posta_mname, *posta_devname; /* post-attach */ +mod_hash_t *pred_mntype, *pred_mname, *pred_devname; /* pre-detach */ + +mod_hash_t *dacf_module_hash; +mod_hash_t *dacf_info_hash; + +/* + * This is the lookup table for the hash tables that dacf manages. Given an + * op id and devspec type, one can obtain the hash for that type of data. + */ +mod_hash_t **dacf_rule_matrix[DACF_NUM_OPIDS][DACF_NUM_DEVSPECS] = { + { &posta_mntype, &posta_mname, &posta_devname }, + { &pred_mntype, &pred_mname, &pred_devname }, +}; + +kmutex_t dacf_lock; +kmutex_t dacf_module_lock; + +int dacfdebug = 0; + +static dacf_rule_t *dacf_rule_ctor(char *, char *, char *, dacf_opid_t, + uint_t, dacf_arg_t *); +static mod_hash_t *dacf_get_op_hash(dacf_opid_t, dacf_devspec_t); +static void dacf_rule_val_dtor(mod_hash_val_t); +static void dacf_destroy_opsets(dacf_module_t *module); +static void dacf_opset_copy(dacf_opset_t *dst, dacf_opset_t *src); +static void dprintf(const char *, ...) __KPRINTFLIKE(1); + +/*PRINTFLIKE1*/ +static void +dprintf(const char *format, ...) +{ + va_list alist; + char dp_buf[256], *dpbp; + if (dacfdebug & DACF_DBG_MSGS) { + va_start(alist, format); + /* + * sprintf up the string that is 'dacf debug: <the message>' + */ + (void) sprintf(dp_buf, "dacf debug: "); + dpbp = &(dp_buf[strlen(dp_buf)]); + (void) vsnprintf(dpbp, sizeof (dp_buf) - strlen(dp_buf), + format, alist); + printf(dp_buf); + va_end(alist); + } +} + +/* + * dacf_init() + * initialize the dacf framework by creating the various hash tables. + */ +void +dacf_init() +{ + int i, j; + char hbuf[40]; + + mutex_enter(&dacf_lock); + + dprintf("dacf_init: creating hashmatrix\n"); + +#ifdef DEBUG + /* + * Sanity check that DACF_NUM_DEVSPECS and the devspecs are in sync + */ + for (i = 0; dacf_devspecs[i].name != NULL; i++) + continue; + ASSERT(i == DACF_NUM_DEVSPECS); + + /* + * Sanity check that DACF_NUM_OPIDS and the dacf_ops are in sync + */ + for (i = 0; dacf_ops[i].name != NULL; i++) + continue; + ASSERT(i == DACF_NUM_OPIDS); +#endif + + for (i = 0; i < DACF_NUM_OPIDS; i++) { + for (j = 0; j < DACF_NUM_DEVSPECS; j++) { + if (dacf_rule_matrix[i][j] == NULL) { + continue; + } + /* + * Set up a hash table with no key destructor. The + * keys are carried in the rule_t, so the val_dtor + * will take care of the key as well. + */ + (void) snprintf(hbuf, sizeof (hbuf), + "dacf hashmatrix [%d][%d]", i, j); + *(dacf_rule_matrix[i][j]) = mod_hash_create_extended( + hbuf, /* hash name */ + DACF_RULE_HASHSIZE, /* # hash elems */ + mod_hash_null_keydtor, /* key dtor */ + dacf_rule_val_dtor, /* value dtor */ + mod_hash_bystr, NULL, /* hash alg & data */ + mod_hash_strkey_cmp, /* key comparator */ + KM_SLEEP); + } + } + + dprintf("dacf_init: creating module_hash\n"); + /* + * dacf_module_hash stores the currently registered dacf modules + * by name. + */ + dacf_module_hash = mod_hash_create_strhash("dacf module hash", + DACF_MODULE_HASHSIZE, mod_hash_null_valdtor); + + dprintf("dacf_init: creating info_hash\n"); + /* + * dacf_info_hash stores pointers to data that modules can associate + * on a per minornode basis. The type of data stored is opaque to the + * framework-- thus there is no destructor supplied. + */ + dacf_info_hash = mod_hash_create_ptrhash("dacf info hash", + DACF_INFO_HASHSIZE, mod_hash_null_valdtor, + sizeof (struct ddi_minor_data)); + + mutex_exit(&dacf_lock); + + /* + * Register the '__kernel' module. + * + * These are operations that are provided by the kernel, not by a + * module. We just feed the framework a dacfsw structure; it will get + * marked as 'loaded' by dacf_module_register(), and will always be + * available. + */ + (void) dacf_module_register(kmod_name, &kmod_dacfsw); + + (void) read_dacf_binding_file(NULL); + + dprintf("dacf_init: dacf is ready\n"); +} + +/* + * dacf_clear_rules() + * clear the dacf rule database. This is typically done in advance of + * rereading the dacf binding file. + */ +void +dacf_clear_rules() +{ + int i, j; + ASSERT(MUTEX_HELD(&dacf_lock)); + + for (i = 0; i < DACF_NUM_OPIDS; i++) { + for (j = 0; j < DACF_NUM_DEVSPECS; j++) { + if ((dacf_rule_matrix[i][j] != NULL) && + (*(dacf_rule_matrix[i][j]) != NULL)) { + mod_hash_clear(*(dacf_rule_matrix[i][j])); + } + } + } +} + +/* + * dacf_rule_insert() + * Create an entry in the dacf rule database. + * If 'module' is null, the kernel is the 'module'. (see dacf_rule_ctor()). + */ +int +dacf_rule_insert(dacf_devspec_t devspec_type, char *devspec_data, + char *module, char *opset, dacf_opid_t opid, uint_t opts, + dacf_arg_t *op_args) +{ + dacf_rule_t *rule; + mod_hash_t *hash; + + ASSERT(devspec_type != DACF_DS_ERROR); + ASSERT(devspec_data); + ASSERT(opset); + ASSERT(MUTEX_HELD(&dacf_lock)); + + dprintf("dacf_rule_insert called: %s=\"%s\", %s:%s, %s\n", + dacf_devspec_to_str(devspec_type), devspec_data, + module ? module : "[kernel]", opset, dacf_opid_to_str(opid)); + + /* + * Fetch the hash table associated with this op-name and devspec-type. + * Some ops may not support all devspec-types, since they may be + * meaningless, so hash may be null. + */ + hash = dacf_get_op_hash(opid, devspec_type); + if (hash == NULL) { + cmn_err(CE_WARN, "!dacf dev-spec '%s' does not support op '%s'", + dacf_devspec_to_str(devspec_type), dacf_opid_to_str(opid)); + return (-1); + } + + /* + * Allocate a rule and fill it in, take a hold on it. + */ + rule = dacf_rule_ctor(devspec_data, module, opset, opid, opts, + op_args); + dacf_rule_hold(rule); + + if (mod_hash_insert(hash, (mod_hash_key_t)rule->r_devspec_data, + (mod_hash_val_t)rule) != 0) { + /* + * We failed, so release hold. This will cause the rule and + * associated data to get nuked. + */ + dacf_rule_rele(rule); + + cmn_err(CE_WARN, "!dacf rule %s='%s' %s:%s %s duplicates " + "another rule, ignored", dacf_devspec_to_str(devspec_type), + devspec_data, module, opset, dacf_opid_to_str(opid)); + return (-1); + } + return (0); +} + +/* + * dacf_rule_ctor() + * Allocate and fill out entries in a dacf_rule_t. + */ +static dacf_rule_t * +dacf_rule_ctor(char *device_spec, char *module, char *opset, dacf_opid_t opid, + uint_t opts, dacf_arg_t *op_args) +{ + dacf_rule_t *rule; + dacf_arg_t *p; + + rule = kmem_alloc(sizeof (dacf_rule_t), KM_SLEEP); + + /* + * Fill in the entries + */ + rule->r_devspec_data = kmem_alloc(strlen(device_spec) + 1, KM_SLEEP); + (void) strcpy(rule->r_devspec_data, device_spec); + + /* + * If module is 'null' we set it to __kernel, meaning that this op + * is implemented by the kernel. + */ + if (module == NULL) { + module = kmod_name; + } + + rule->r_module = kmem_alloc(strlen(module) + 1, KM_SLEEP); + (void) strcpy(rule->r_module, module); + + rule->r_opset = kmem_alloc(strlen(opset) + 1, KM_SLEEP); + (void) strcpy(rule->r_opset, opset); + + rule->r_refs = 0; /* no refs yet */ + rule->r_opts = opts; + rule->r_opid = opid; + + rule->r_args = NULL; + p = op_args; + while (p != NULL) { + ASSERT(p->arg_name); + ASSERT(p->arg_val); + /* + * dacf_arg_insert() should always succeed, since we're copying + * another (already duplicate-free) list. + */ + (void) dacf_arg_insert(&rule->r_args, p->arg_name, p->arg_val); + p = p->arg_next; + } + + return (rule); +} + +/* + * dacf_rule_val_dtor() + * This is the destructor for dacf_rule_t's in the rule database. It + * simply does a dacf_rule_rele() on the rule. This function will take + * care of destroying the rule if its ref count has dropped to 0. + */ +static void +dacf_rule_val_dtor(mod_hash_val_t val) +{ + ASSERT((void *)val != NULL); + dacf_rule_rele((dacf_rule_t *)val); +} + +/* + * dacf_rule_destroy() + * destroy a dacf_rule_t + */ +void +dacf_rule_destroy(dacf_rule_t *rule) +{ + ASSERT(rule->r_refs == 0); + /* + * Free arguments. + */ + dacf_arglist_delete(&(rule->r_args)); + kmem_free(rule->r_devspec_data, strlen(rule->r_devspec_data) + 1); + /* + * Module may be null for a kernel-managed op-set + */ + kmem_free(rule->r_module, strlen(rule->r_module) + 1); + kmem_free(rule->r_opset, strlen(rule->r_opset) + 1); + kmem_free(rule, sizeof (dacf_rule_t)); +} + +/* + * dacf_rule_hold() + * dacf rules are ref-counted. This function increases the reference + * count on an rule. + */ +void +dacf_rule_hold(dacf_rule_t *rule) +{ + ASSERT(MUTEX_HELD(&dacf_lock)); + + rule->r_refs++; +} + +/* + * dacf_rule_rele() + * drop the ref count on an rule, and destroy the rule if its + * ref count drops to 0. + */ +void +dacf_rule_rele(dacf_rule_t *rule) +{ + ASSERT(MUTEX_HELD(&dacf_lock)); + ASSERT(rule->r_refs > 0); + + rule->r_refs--; + if (rule->r_refs == 0) { + dacf_rule_destroy(rule); + } +} + +/* + * dacf_rsrv_make() + * add an rule to a reservation list to be processed later. + */ +void +dacf_rsrv_make(dacf_rsrvlist_t *rsrv, dacf_rule_t *rule, void *info, + dacf_rsrvlist_t **list) +{ + dacf_infohdl_t ihdl = info; + ASSERT(MUTEX_HELD(&dacf_lock)); + ASSERT(info && rule && list); + + /* + * Bump the ref count on rule, so it won't get freed as long as it's on + * this reservation list. + */ + dacf_rule_hold(rule); + + rsrv->rsrv_rule = rule; + rsrv->rsrv_ihdl = ihdl; + rsrv->rsrv_result = DDI_SUCCESS; + rsrv->rsrv_next = *list; + *list = rsrv; + + dprintf("dacf: reservation made\n"); +} + +/* + * dacf_clr_rsrvs() + * clear reservation list of operations of type 'op' + */ +void +dacf_clr_rsrvs(dev_info_t *devi, dacf_opid_t op) +{ + dacf_process_rsrvs(&(DEVI(devi)->devi_dacf_tasks), op, DACF_PROC_RELE); +} + +/* + * dacf_process_rsrvs() + * iterate across a locked reservation list, processing each element + * which matches 'op' according to 'flags'. + * + * if DACF_PROC_INVOKE is specified, the elements that match 'op' + * will have their operations invoked. The return value from that + * operation is placed in the rsrv_result field of the dacf_rsrvlist_t + */ +void +dacf_process_rsrvs(dacf_rsrvlist_t **list, dacf_opid_t op, int flags) +{ + dacf_rsrvlist_t *p, *dp; + dacf_rsrvlist_t **prevptr; + + ASSERT(MUTEX_HELD(&dacf_lock)); + ASSERT(list); + ASSERT(flags != 0); + + if (*list == NULL) + return; + + dprintf("dacf_process_rsrvs: opid = %d, flags = 0x%x\n", op, flags); + + /* + * Walk the list, finding rules whose opid's match op, and performing + * the work described by 'flags'. + */ + prevptr = list; + for (p = *list; p != NULL; ) { + + if (p->rsrv_rule->r_opid != op) { + prevptr = &(p->rsrv_next); + p = p->rsrv_next; + continue; + } + + if (flags & DACF_PROC_INVOKE) { + p->rsrv_result = dacf_op_invoke(p->rsrv_rule, + p->rsrv_ihdl, 0); + } + + if (flags & DACF_PROC_RELE) { + *prevptr = p->rsrv_next; + dp = p; + p = p->rsrv_next; + dacf_rule_rele(dp->rsrv_rule); + kmem_free(dp, sizeof (dacf_rsrvlist_t)); + } else { + prevptr = &(p->rsrv_next); + p = p->rsrv_next; + } + } +} + +/* + * dacf_get_op_hash() + * Given an op name, (i.e. "post-attach" or "pre-detach") and a + * devspec-type, return the hash that represents that op indexed + * by that devspec. + */ +static mod_hash_t * +dacf_get_op_hash(dacf_opid_t op, dacf_devspec_t ds_type) +{ + ASSERT(op <= DACF_NUM_OPIDS && op > 0); + ASSERT(ds_type <= DACF_NUM_DEVSPECS && ds_type > 0); + + /* + * dacf_rule_matrix is an array of pointers to pointers to hashes. + */ + if (dacf_rule_matrix[op - 1][ds_type - 1] == NULL) { + return (NULL); + } + return (*(dacf_rule_matrix[op - 1][ds_type - 1])); +} + +/* + * dacf_arg_insert() + * Create and insert an entry in an argument list. + * Returns -1 if the argument name is a duplicate of another already + * present in the hash. + */ +int +dacf_arg_insert(dacf_arg_t **list, char *name, char *val) +{ + dacf_arg_t *arg; + + /* + * Don't allow duplicates. + */ + for (arg = *list; arg != NULL; arg = arg->arg_next) { + if (strcmp(arg->arg_name, name) == 0) { + return (-1); + } + } + + arg = kmem_alloc(sizeof (dacf_arg_t), KM_SLEEP); + arg->arg_name = kmem_alloc(strlen(name) + 1, KM_SLEEP); + (void) strcpy(arg->arg_name, name); + arg->arg_val = kmem_alloc(strlen(val) + 1, KM_SLEEP); + (void) strcpy(arg->arg_val, val); + + arg->arg_next = *list; + *list = arg; + + return (0); +} + +/* + * dacf_arglist_delete() + * free all the elements of a list of dacf_arg_t's. + */ +void +dacf_arglist_delete(dacf_arg_t **list) +{ + dacf_arg_t *arg, *narg; + arg = *list; + while (arg != NULL) { + narg = arg->arg_next; + kmem_free(arg->arg_name, strlen(arg->arg_name) + 1); + kmem_free(arg->arg_val, strlen(arg->arg_val) + 1); + kmem_free(arg, sizeof (dacf_arg_t)); + arg = narg; + } + *list = NULL; +} + +/* + * dacf_match() + * Match a device-spec to a rule. + */ +dacf_rule_t * +dacf_match(dacf_opid_t op, dacf_devspec_t ds, void *match_info) +{ + dacf_rule_t *rule; + + ASSERT(MUTEX_HELD(&dacf_lock)); + + if (mod_hash_find(dacf_get_op_hash(op, ds), (mod_hash_key_t)match_info, + (mod_hash_val_t *)&rule) == 0) { + return (rule); + } + + return (NULL); /* Not Found */ +} + +/* + * dacf_module_register() + * register a module with the framework. Use when a module gets loaded, + * or for the kernel to register a "virtual" module (i.e. a "module" + * which the kernel provides). Makes a copy of the interface description + * provided by the module. + */ +int +dacf_module_register(char *mod_name, struct dacfsw *sw) +{ + char *str; + size_t i, nelems; + dacf_module_t *module; + dacf_opset_t *opsarray; + + if (sw == NULL) { + return (EINVAL); + } + + if (sw->dacf_rev != DACF_MODREV_1) { + cmn_err(CE_WARN, "dacf: module '%s' exports unsupported " + "version %d interface, not registered\n", mod_name, + sw->dacf_rev); + return (EINVAL); + } + + /* + * count how many opsets are provided. + */ + for (nelems = 0; sw->dacf_opsets[nelems].opset_name != NULL; nelems++) + ; + + dprintf("dacf_module_register: found %lu opsets\n", nelems); + + /* + * Temporary: It's ok for the kernel dacf_sw to have no opsets, since + * we don't have any opsets to export yet (in NON-DEBUG). + */ + if ((nelems == 0) && (sw != &kmod_dacfsw)) { + cmn_err(CE_WARN, "dacf module %s exports no opsets, " + "not registered.\n", mod_name); + return (EINVAL); + } + + /* + * Look to see if the module has been previously registered with the + * framework. If so, we can fail with EBUSY. + */ + if (mod_hash_find(dacf_module_hash, (mod_hash_key_t)mod_name, + (mod_hash_val_t)&module) == 0) { + /* + * See if it is loaded currently + */ + rw_enter(&module->dm_lock, RW_WRITER); + if (module->dm_loaded) { + rw_exit(&module->dm_lock); + cmn_err(CE_WARN, "dacf module '%s' is " + "already registered.", mod_name); + return (EBUSY); + } + } else { + /* + * This is the first time we've ever seen the module; stick + * it into the module hash. If that fails, we've had a + * race between two threads, both trying to insert the same + * new module. It's safe to stick the module into the + * hash only partly filled in, since dm_lock protects the + * structure, and we've got that write-locked. + */ + module = kmem_zalloc(sizeof (dacf_module_t), KM_SLEEP); + str = kmem_alloc(strlen(mod_name) + 1, KM_SLEEP); + (void) strcpy(str, mod_name); + rw_enter(&module->dm_lock, RW_WRITER); + + if (mod_hash_insert(dacf_module_hash, (mod_hash_key_t)str, + (mod_hash_val_t)module) != 0) { + rw_exit(&module->dm_lock); + kmem_free(str, strlen(str) + 1); + kmem_free(module, sizeof (dacf_module_t)); + cmn_err(CE_WARN, "dacf module '%s' is " + "already registered.", mod_name); + return (EBUSY); + } + } + /* + * In either case (first time we've seen it or not), the module is + * not loaded, and we hold it write-locked. + */ + ASSERT(RW_WRITE_HELD(&module->dm_lock)); + + /* + * Alloc array of opsets for this module. Add one for the final + * NULL entry + */ + opsarray = kmem_zalloc(sizeof (dacf_opset_t) * (nelems + 1), KM_SLEEP); + + for (i = 0; i < nelems; i++) { + dacf_opset_copy(&(opsarray[i]), &(sw->dacf_opsets[i])); + ASSERT(opsarray[i].opset_name != NULL); + ASSERT(opsarray[i].opset_ops != NULL); + } + opsarray[nelems].opset_name = NULL; + opsarray[nelems].opset_ops = NULL; + + ASSERT(module->dm_opsets == NULL); /* see dacf_destroy_opsets() */ + module->dm_opsets = opsarray; + + if (dacfdebug & DACF_DBG_MSGS) { + dprintf("%s registered.\n", mod_name); + for (i = 0; i < nelems; i++) { + dprintf("registered %s\n", opsarray[i].opset_name); + } + } + + module->dm_loaded = 1; + rw_exit(&module->dm_lock); + + return (0); +} + +/* + * dacf_module_unregister() + * remove a module from the framework, and free framework-allocated + * resources. + */ +int +dacf_module_unregister(char *mod_name) +{ + dacf_module_t *module; + + /* + * Can't unregister __kernel, since there is no real way to get it + * back-- Once it gets marked with dm_loaded == 0, the kernel will + * try to modload() if it is ever needed, which will fail utterly, + * and send op_invoke into a loop in it's modload logic + * + * If this is behavior is ever needed in the future, we can just + * add a flag indicating that this module is really a fake. + */ + ASSERT(strcmp(mod_name, kmod_name) != 0); + + dprintf("dacf_module_unregister: called for '%s'!\n", mod_name); + + /* + * If NOAUL_DACF is set, or we try to get a write-lock on dm_lock and + * that fails, return EBUSY, and fail to unregister. + */ + if (mod_hash_find(dacf_module_hash, (mod_hash_key_t)mod_name, + (mod_hash_val_t)&module) == 0) { + if ((moddebug & MODDEBUG_NOAUL_DACF) || + !rw_tryenter(&module->dm_lock, RW_WRITER)) { + return (EBUSY); + } + } else { + return (EINVAL); + } + + ASSERT(RW_WRITE_HELD(&module->dm_lock)); + dacf_destroy_opsets(module); + module->dm_loaded = 0; + rw_exit(&module->dm_lock); + return (0); +} + +/* + * dacf_destroy_opsets() + * given a module, destroy all of it's associated op-sets. + */ +static void +dacf_destroy_opsets(dacf_module_t *module) +{ + dacf_opset_t *array = module->dm_opsets; + dacf_opset_t *p; + int i; + size_t nelems; + + ASSERT(RW_WRITE_HELD(&module->dm_lock)); + ASSERT(module->dm_loaded == 1); + + for (i = 0; array[i].opset_name != NULL; i++) { + p = &(array[i]); + kmem_free(p->opset_name, strlen(p->opset_name) + 1); + /* + * count nelems in opset_ops + */ + for (nelems = 0; ; nelems++) { + if (p->opset_ops[nelems].op_id == DACF_OPID_END) { + break; + } + } + /* + * Free the array of op ptrs. + */ + kmem_free(p->opset_ops, sizeof (dacf_op_t) * (nelems + 1)); + } + + /* + * i has counted how big array is; +1 to account for the last element. + */ + kmem_free(array, (sizeof (dacf_opset_t)) * (i + 1)); + module->dm_opsets = NULL; +} + +/* + * dacf_opset_copy() + * makes a copy of a dacf_opset_t. + */ +static void +dacf_opset_copy(dacf_opset_t *dst, dacf_opset_t *src) +{ + size_t nelems, i; + ASSERT(src && dst); + + dprintf("dacf_opset_copy: called\n"); + + dst->opset_name = kmem_alloc(strlen(src->opset_name) + 1, KM_SLEEP); + (void) strcpy(dst->opset_name, src->opset_name); + + dprintf("dacf_opset_copy: counting ops\n"); + + for (nelems = 0; ; nelems++) { + if ((src->opset_ops[nelems].op_id == DACF_OPID_END) || + (src->opset_ops[nelems].op_func == NULL)) { + break; + } + } + + dprintf("dacf_opset_copy: found %lu ops\n", nelems); + + dst->opset_ops = kmem_alloc(sizeof (dacf_op_t) * (nelems + 1), + KM_SLEEP); + + dprintf("dacf_opset_copy: copying ops\n"); + for (i = 0; i < nelems; i++) { + dst->opset_ops[i].op_id = src->opset_ops[i].op_id; + dst->opset_ops[i].op_func = src->opset_ops[i].op_func; + } + dst->opset_ops[nelems].op_id = DACF_OPID_END; + dst->opset_ops[nelems].op_func = NULL; + + dprintf("dacf_opset_copy: done copying ops\n"); +} + +int dacf_modload_laps = 0; /* just a diagnostic aid */ + +/* + * dacf_op_invoke() + * Invoke a op in a opset in a module given the rule to invoke. + * + * If the return value of dacf_op_invoke is 0, then rval contains the + * return value of the _op_ being invoked. Otherwise, dacf_op_invoke's + * return value indicates why the op invocation failed. + */ +int +dacf_op_invoke(dacf_rule_t *rule, dacf_infohdl_t info_hdl, int flags) +{ + dacf_module_t *module; + dacf_opset_t *opsarray; + dacf_opset_t *opset; + dacf_op_t *op = NULL; + dacf_opid_t op_id; + dacf_arghdl_t arg_hdl; + dev_info_t *dip; + int i, rval = -1; + + ASSERT(rule); + ASSERT(MUTEX_HELD(&dacf_lock)); + + op_id = rule->r_opid; + dprintf("dacf_op_invoke: opid=%d\n", op_id); + + /* + * Take laps, trying to load the dacf module. For the case of kernel- + * provided operations, __kernel will be found in the hash table, and + * no modload will be needed. + */ + for (;;) { + if (mod_hash_find(dacf_module_hash, + (mod_hash_key_t)rule->r_module, + (mod_hash_val_t *)&module) == 0) { + rw_enter(&module->dm_lock, RW_READER); + /* + * Found the module, and it is loaded. + */ + if (module->dm_loaded != 0) { + break; + } + rw_exit(&module->dm_lock); + } + + /* + * If we're here, either: 1) it's not in the hash, or 2) it is, + * but dm_loaded is 0, meaning the module needs to be loaded. + */ + dprintf("dacf_op_invoke: calling modload\n"); + if (modload("dacf", rule->r_module) < 0) { + return (DACF_ERR_MOD_NOTFOUND); + } + dacf_modload_laps++; + } + + ASSERT(RW_READ_HELD(&module->dm_lock)); + + opsarray = module->dm_opsets; + + /* + * Loop through the opsets exported by this module, and find the one + * we care about. + */ + opset = NULL; + for (i = 0; opsarray[i].opset_name != NULL; i++) { + if (strcmp(opsarray[i].opset_name, rule->r_opset) == 0) { + opset = &opsarray[i]; + break; + } + } + + if (opset == NULL) { + cmn_err(CE_WARN, "!dacf: couldn't invoke op, opset '%s' not " + "found in module '%s'", rule->r_opset, rule->r_module); + rw_exit(&module->dm_lock); + return (DACF_ERR_OPSET_NOTFOUND); + } + + arg_hdl = (dacf_arghdl_t)rule->r_args; + + /* + * Call the appropriate routine in the target by looping across the + * ops until we find the one whose id matches opid. + */ + op = NULL; + for (i = 0; opset->opset_ops[i].op_id != DACF_OPID_END; i++) { + if (opset->opset_ops[i].op_id == op_id) { + op = &(opset->opset_ops[i]); + break; + } + } + + if (op == NULL) { + cmn_err(CE_WARN, "!dacf: couldn't invoke op, op '%s' not found " + "in opset '%s' in module '%s'", dacf_opid_to_str(op_id), + rule->r_opset, rule->r_module); + rw_exit(&module->dm_lock); + return (DACF_ERR_OP_NOTFOUND); + } + + dprintf("dacf_op_invoke: found op, invoking...\n"); + + /* + * Drop dacf_lock here, so that op_func's that cause drivers to + * get loaded don't wedge the system when they try to acquire dacf_lock + * to do matching. + * + * Mark that an invoke is happening to prevent recursive invokes + */ + dip = ((struct ddi_minor_data *)info_hdl)->dip; + + mutex_enter(&(DEVI(dip)->devi_lock)); + DEVI_SET_INVOKING_DACF(dip); + mutex_exit(&(DEVI(dip)->devi_lock)); + + mutex_exit(&dacf_lock); + + rval = op->op_func(info_hdl, arg_hdl, flags); + + mutex_enter(&dacf_lock); + + /* + * Completed the invocation against module, so let go of it. + */ + mutex_enter(&(DEVI(dip)->devi_lock)); + DEVI_CLR_INVOKING_DACF(dip); + mutex_exit(&(DEVI(dip)->devi_lock)); + + /* + * Drop our r-lock on the module, now that we no longer need the module + * to stay loaded. + */ + rw_exit(&module->dm_lock); + + if (rval == DACF_SUCCESS) { + return (DACF_SUCCESS); + } else { + return (DACF_ERR_OP_FAILED); + } +} + +/* + * dacf_get_devspec() + * given a devspec-type as a string, return a corresponding dacf_devspec_t + */ +dacf_devspec_t +dacf_get_devspec(char *name) +{ + dacf_ds_t *p = &dacf_devspecs[0]; + + while (p->name != NULL) { + if (strcmp(p->name, name) == 0) { + return (p->id); + } + p++; + } + return (DACF_DS_ERROR); +} + +/* + * dacf_devspec_to_str() + * given a dacf_devspec_t, return a pointer to the human readable string + * representation of that device specifier. + */ +const char * +dacf_devspec_to_str(dacf_devspec_t ds) +{ + dacf_ds_t *p = &dacf_devspecs[0]; + + while (p->name != NULL) { + if (p->id == ds) { + return (p->name); + } + p++; + } + return (NULL); +} + +/* + * dacf_get_op() + * given a op name, returns the corresponding dacf_opid_t. + */ +dacf_opid_t +dacf_get_op(char *name) +{ + dacf_opmap_t *p = &dacf_ops[0]; + + while (p->name != NULL) { + if (strcmp(p->name, name) == 0) { + return (p->id); + } + p++; + } + return (DACF_OPID_ERROR); +} + +/* + * dacf_opid_to_str() + * given a dacf_opid_t, return the human-readable op-name. + */ +const char * +dacf_opid_to_str(dacf_opid_t tid) +{ + dacf_opmap_t *p = &dacf_ops[0]; + + while (p->name != NULL) { + if (p->id == tid) { + return (p->name); + } + p++; + } + return (NULL); +} + +/* + * dacf_getopt() + * given an option specified as a string, add it to the bit-field of + * options given. Returns -1 if the option is unrecognized. + */ +int +dacf_getopt(char *opt_str, uint_t *opts) +{ + dacf_opt_t *p = &dacf_options[0]; + + /* + * Look through the list for the option given + */ + while (p->optname != NULL) { + if (strcmp(opt_str, p->optname) == 0) { + *opts |= p->optmask; + return (0); + } + p++; + } + return (-1); +} + + + +/* + * This family of functions forms the dacf interface which is exported to + * kernel/dacf modules. Modules _should_not_ use any dacf_* functions + * presented above this point. + * + * Note: These routines use a dacf_infohdl_t to struct ddi_minor_data * and + * assume that the resulting pointer is not to an alias node. That is true + * because dacf_op_invoke guarantees it by first resolving the alias. + */ + +/* + * dacf_minor_name() + * given a dacf_infohdl_t, obtain the minor name of the device instance + * being configured. + */ +const char * +dacf_minor_name(dacf_infohdl_t info_hdl) +{ + struct ddi_minor_data *dmdp = (struct ddi_minor_data *)info_hdl; + + return (dmdp->ddm_name); +} + +/* + * dacf_minor_number() + * given a dacf_infohdl_t, obtain the device minor number of the instance + * being configured. + */ +minor_t +dacf_minor_number(dacf_infohdl_t info_hdl) +{ + struct ddi_minor_data *dmdp = (struct ddi_minor_data *)info_hdl; + + return (getminor(dmdp->ddm_dev)); +} + +/* + * dacf_driver_name() + * given a dacf_infohdl_t, obtain the device driver name of the device + * instance being configured. + */ +const char * +dacf_driver_name(dacf_infohdl_t info_hdl) +{ + struct ddi_minor_data *dmdp = (struct ddi_minor_data *)info_hdl; + + return (ddi_driver_name(dmdp->dip)); +} + +/* + * dacf_devinfo_node() + * given a dacf_infohdl_t, obtain the dev_info_t of the device instance + * being configured. + */ +dev_info_t * +dacf_devinfo_node(dacf_infohdl_t info_hdl) +{ + struct ddi_minor_data *dmdp = (struct ddi_minor_data *)info_hdl; + + return (dmdp->dip); +} + +/* + * dacf_get_arg() + * given the dacf_arghdl_t passed to a op and the name of an argument, + * return the value of that argument. + * + * returns NULL if the argument is not found. + */ +const char * +dacf_get_arg(dacf_arghdl_t arghdl, char *arg_name) +{ + dacf_arg_t *arg_list = (dacf_arg_t *)arghdl; + ASSERT(arg_name); + + while (arg_list != NULL) { + if (strcmp(arg_list->arg_name, arg_name) == 0) { + return (arg_list->arg_val); + } + arg_list = arg_list->arg_next; + } + + return (NULL); +} + +/* + * dacf_store_info() + * associate instance-specific data with a device instance. Future + * configuration ops invoked for this instance can retrieve this data using + * dacf_retrieve_info() below. Modules are responsible for cleaning up + * this data as appropriate, and should store NULL as the value of 'data' + * when the data is no longer valid. + */ +void +dacf_store_info(dacf_infohdl_t info_hdl, void *data) +{ + struct ddi_minor_data *dmdp = (struct ddi_minor_data *)info_hdl; + + /* + * If the client is 'storing NULL' we can represent that by blowing + * the info entry out of the hash. + */ + if (data == NULL) { + (void) mod_hash_destroy(dacf_info_hash, (mod_hash_key_t)dmdp); + } else { + /* + * mod_hash_replace can only fail on out of memory, but we sleep + * for memory in this hash, so it is safe to ignore the retval. + */ + (void) mod_hash_replace(dacf_info_hash, (mod_hash_key_t)dmdp, + (mod_hash_val_t)data); + } +} + +/* + * dacf_retrieve_info() + * retrieve instance-specific data associated with a device instance. + */ +void * +dacf_retrieve_info(dacf_infohdl_t info_hdl) +{ + struct ddi_minor_data *dmdp = (struct ddi_minor_data *)info_hdl; + void *data; + + if (mod_hash_find(dacf_info_hash, (mod_hash_key_t)dmdp, + (mod_hash_val_t *)&data) != 0) { + return (NULL); + } + + return (data); +} + +/* + * dacf_makevp() + * make a vnode for the specified dacf_infohdl_t. + */ +struct vnode * +dacf_makevp(dacf_infohdl_t info_hdl) +{ + struct ddi_minor_data *dmdp = (struct ddi_minor_data *)info_hdl; + struct vnode *vp; + + vp = makespecvp(dmdp->ddm_dev, VCHR); + spec_assoc_vp_with_devi(vp, dmdp->dip); + return (vp); +} |