diff options
Diffstat (limited to 'usr/src/uts/common/os/modsubr.c')
-rw-r--r-- | usr/src/uts/common/os/modsubr.c | 1083 |
1 files changed, 1083 insertions, 0 deletions
diff --git a/usr/src/uts/common/os/modsubr.c b/usr/src/uts/common/os/modsubr.c new file mode 100644 index 0000000000..46ad16799b --- /dev/null +++ b/usr/src/uts/common/os/modsubr.c @@ -0,0 +1,1083 @@ +/* + * 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" + +#include <sys/param.h> +#include <sys/modctl.h> +#include <sys/modhash.h> +#include <sys/open.h> +#include <sys/conf.h> +#include <sys/errno.h> +#include <sys/sysmacros.h> +#include <sys/kmem.h> +#include <sys/cmn_err.h> +#include <sys/stat.h> +#include <sys/mode.h> +#include <sys/pathname.h> +#include <sys/vnode.h> +#include <sys/ddi_impldefs.h> +#include <sys/ddi_implfuncs.h> +#include <sys/esunddi.h> +#include <sys/sunddi.h> +#include <sys/sunndi.h> +#include <sys/systeminfo.h> +#include <sys/hwconf.h> +#include <sys/file.h> +#include <sys/varargs.h> +#include <sys/thread.h> +#include <sys/cred.h> +#include <sys/autoconf.h> +#include <sys/kobj.h> +#include <sys/consdev.h> +#include <sys/systm.h> +#include <sys/debug.h> +#include <sys/atomic.h> + +extern struct dev_ops nodev_ops; +extern struct dev_ops mod_nodev_ops; + +struct mod_noload { + struct mod_noload *mn_next; + char *mn_name; +}; + +/* + * Function prototypes + */ +static int init_stubs(struct modctl *, struct mod_modinfo *); +static int nm_hash(char *); +static void make_syscallname(char *, int); +static void hwc_hash_init(); +static void hwc_hash(struct hwc_spec *, major_t); +static void hwc_unhash(struct hwc_spec *); + +struct dev_ops * +mod_hold_dev_by_major(major_t major) +{ + struct dev_ops **devopspp, *ops; + int loaded; + char *drvname; + + if (major >= devcnt) + return (NULL); + + LOCK_DEV_OPS(&(devnamesp[major].dn_lock)); + devopspp = &devopsp[major]; + loaded = 1; + while (loaded && !CB_DRV_INSTALLED(*devopspp)) { + UNLOCK_DEV_OPS(&(devnamesp[major].dn_lock)); + drvname = mod_major_to_name(major); + if (drvname == NULL) + return (NULL); + loaded = (modload("drv", drvname) != -1); + LOCK_DEV_OPS(&(devnamesp[major].dn_lock)); + } + if (loaded) { + INCR_DEV_OPS_REF(*devopspp); + ops = *devopspp; + } else { + ops = NULL; + } + UNLOCK_DEV_OPS(&(devnamesp[major].dn_lock)); + return (ops); +} + +#ifdef DEBUG_RELE +static int mod_rele_pause = DEBUG_RELE; +#endif /* DEBUG_RELE */ + +void +mod_rele_dev_by_major(major_t major) +{ + struct dev_ops *ops; + struct devnames *dnp; + + if (major >= devcnt) + return; + + dnp = &devnamesp[major]; + LOCK_DEV_OPS(&dnp->dn_lock); + ops = devopsp[major]; + ASSERT(CB_DRV_INSTALLED(ops)); + +#ifdef DEBUG_RELE + if (!DEV_OPS_HELD(ops)) { + char *s; + static char *msg = "mod_rele_dev_by_major: unheld driver!"; + + printf("mod_rele_dev_by_major: Major dev <%u>, name <%s>\n", + (uint_t)major, + (s = mod_major_to_name(major)) ? s : "unknown"); + if (mod_rele_pause) + debug_enter(msg); + else + printf("%s\n", msg); + UNLOCK_DEV_OPS(&dnp->dn_lock); + return; /* XXX: Note changed behavior */ + } + +#endif /* DEBUG_RELE */ + + if (!DEV_OPS_HELD(ops)) { + cmn_err(CE_PANIC, + "mod_rele_dev_by_major: Unheld driver: major number <%u>", + (uint_t)major); + } + DECR_DEV_OPS_REF(ops); + UNLOCK_DEV_OPS(&dnp->dn_lock); +} + +struct dev_ops * +mod_hold_dev_by_devi(dev_info_t *devi) +{ + major_t major; + char *name; + + name = ddi_get_name(devi); + if ((major = mod_name_to_major(name)) == (major_t)-1) + return (NULL); + return (mod_hold_dev_by_major(major)); +} + +void +mod_rele_dev_by_devi(dev_info_t *devi) +{ + major_t major; + char *name; + + name = ddi_get_name(devi); + if ((major = mod_name_to_major(name)) == (major_t)-1) + return; + mod_rele_dev_by_major(major); +} + +int +nomod_zero() +{ + return (0); +} + +int +nomod_minus_one() +{ + return (-1); +} + +int +nomod_einval() +{ + return (EINVAL); +} + +void +nomod_void() +{ + /* nothing */ +} + +/* + * Install all the stubs for a module. + * Return zero if there were no errors or an errno value. + */ +int +install_stubs_by_name(struct modctl *modp, char *name) +{ + char *p; + char *filenamep; + char namebuf[MODMAXNAMELEN + 12]; + struct mod_modinfo *mp; + + p = name; + filenamep = name; + + while (*p) + if (*p++ == '/') + filenamep = p; + + /* + * Concatenate "name" with "_modname" then look up this symbol + * in the kernel. If not found, we're done. + * If found, then find the "mod" info structure and call init_stubs(). + */ + p = namebuf; + + while (*filenamep && *filenamep != '.') + *p++ = *filenamep++; + + (void) strcpy(p, "_modinfo"); + + if ((mp = (struct mod_modinfo *)modgetsymvalue(namebuf, 1)) != 0) + return (init_stubs(modp, mp)); + else + return (0); +} + +static int +init_stubs(struct modctl *modp, struct mod_modinfo *mp) +{ + struct mod_stub_info *sp; + int i; + ulong_t offset; + uintptr_t funcadr; + char *funcname; + + modp->mod_modinfo = mp; + + /* + * Fill in all stubs for this module. We can't be lazy, since + * some calls could come in from interrupt level, and we + * can't modlookup then (symbols may be paged out). + */ + sp = mp->modm_stubs; + for (i = 0; sp->mods_func_adr; i++, sp++) { + funcname = modgetsymname(sp->mods_stub_adr, &offset); + if (funcname == NULL) { + printf("init_stubs: couldn't find symbol in module %s\n", + mp->modm_module_name); + return (EFAULT); + } + funcadr = kobj_lookup(modp->mod_mp, funcname); + + if (kobj_addrcheck(modp->mod_mp, (caddr_t)funcadr)) { + printf("%s:%s() not defined properly\n", + mp->modm_module_name, funcname); + return (EFAULT); + } + sp->mods_func_adr = funcadr; + } + mp->mp = modp; + return (0); +} + +/* + * modp->mod_modinfo has to be checked in these functions before + * mod_stub_info is accessed because it's not guranteed that all + * modules define mod_stub_info structures. + */ +void +install_stubs(struct modctl *modp) +{ + struct mod_stub_info *stub; + + if (modp->mod_modinfo) { + membar_producer(); + for (stub = modp->mod_modinfo->modm_stubs; + stub->mods_func_adr; stub++) { + stub->mods_flag |= MODS_INSTALLED; + } + membar_producer(); + } +} + +void +uninstall_stubs(struct modctl *modp) +{ + struct mod_stub_info *stub; + + if (modp->mod_modinfo) { + membar_producer(); + for (stub = modp->mod_modinfo->modm_stubs; + stub->mods_func_adr; stub++) { + stub->mods_flag &= ~MODS_INSTALLED; + } + membar_producer(); + } +} + +void +reset_stubs(struct modctl *modp) +{ + struct mod_stub_info *stub; + + if (modp->mod_modinfo) { + for (stub = modp->mod_modinfo->modm_stubs; + stub->mods_func_adr; stub++) { + if (stub->mods_flag & (MODS_WEAK | MODS_NOUNLOAD)) + stub->mods_func_adr = + (uintptr_t)stub->mods_errfcn; + else + stub->mods_func_adr = + (uintptr_t)mod_hold_stub; + } + modp->mod_modinfo->mp = NULL; + } +} + +struct modctl * +mod_getctl(struct modlinkage *modlp) +{ + struct modctl *modp; + + mutex_enter(&mod_lock); + modp = &modules; + do { + if (modp->mod_linkage == modlp) { + mutex_exit(&mod_lock); + return (modp); + } + } while ((modp = modp->mod_next) != &modules); + mutex_exit(&mod_lock); + return (NULL); +} + + +/* + * Attach driver.conf info to devnames for a driver + */ +struct par_list * +impl_make_parlist(major_t major) +{ + int err; + struct par_list *pl = NULL, *tmp; + ddi_prop_t *props = NULL; + char *confname, *drvname; + struct devnames *dnp; + + dnp = &devnamesp[major]; + + ASSERT(mutex_owned(&dnp->dn_lock)); + + /* + * If .conf file already parsed or driver removed, just return. + * May return NULL. + */ + if (dnp->dn_flags & (DN_CONF_PARSED | DN_DRIVER_REMOVED)) + return (dnp->dn_pl); + + drvname = mod_major_to_name(major); + if (drvname == NULL) + return (NULL); + + confname = kmem_alloc(MAXNAMELEN, KM_SLEEP); + (void) snprintf(confname, MAXNAMELEN, "drv/%s.conf", drvname); + err = hwc_parse(confname, &pl, &props); + kmem_free(confname, MAXNAMELEN); + if (err) /* file doesn't exist */ + return (NULL); + + /* + * If there are global properties, reference it from dnp. + */ + if (props) + dnp->dn_global_prop_ptr = i_ddi_prop_list_create(props); + + /* + * Hash specs to be looked up by nexus drivers + */ + tmp = pl; + while (tmp) { + (void) hwc_hash(tmp->par_specs, major); + tmp = tmp->par_next; + } + + if (!i_ddi_io_initialized()) { + if (i_ddi_prop_search(DDI_DEV_T_ANY, DDI_FORCEATTACH, + DDI_PROP_TYPE_INT, &props)) + dnp->dn_flags |= DN_FORCE_ATTACH; + } + dnp->dn_flags |= DN_CONF_PARSED; + dnp->dn_pl = pl; + return (pl); +} + +/* + * Destroy driver.conf info in devnames array for a driver + */ +int +impl_free_parlist(major_t major) +{ + struct par_list *pl; + struct devnames *dnp = &devnamesp[major]; + + /* + * Unref driver global property list. Don't destroy it + * because some instances may still be referencing it. + * The property list will be freed when the last ref + * goes away. + */ + if (dnp->dn_global_prop_ptr) { + i_ddi_prop_list_rele(dnp->dn_global_prop_ptr, dnp); + dnp->dn_global_prop_ptr = NULL; + } + + /* + * remove specs from hash table + */ + for (pl = dnp->dn_pl; pl; pl = pl->par_next) + hwc_unhash(pl->par_specs); + + impl_delete_par_list(dnp->dn_pl); + dnp->dn_pl = NULL; + dnp->dn_flags &= ~DN_CONF_PARSED; + return (0); +} + +struct bind *mb_hashtab[MOD_BIND_HASHSIZE]; +struct bind *sb_hashtab[MOD_BIND_HASHSIZE]; + +static int +nm_hash(char *name) +{ + char c; + int hash = 0; + + for (c = *name++; c; c = *name++) + hash ^= c; + + return (hash & MOD_BIND_HASHMASK); +} + +void +clear_binding_hash(struct bind **bhash) +{ + int i; + struct bind *bp, *bp1; + + for (i = 0; i < MOD_BIND_HASHSIZE; i++) { + bp = bhash[i]; + while (bp != NULL) { + kmem_free(bp->b_name, strlen(bp->b_name) + 1); + if (bp->b_bind_name) { + kmem_free(bp->b_bind_name, + strlen(bp->b_bind_name) + 1); + } + bp1 = bp; + bp = bp->b_next; + kmem_free(bp1, sizeof (struct bind)); + } + bhash[i] = NULL; + } +} + +static struct bind * +find_mbind(char *name, struct bind **hashtab) +{ + int hashndx; + struct bind *mb; + + hashndx = nm_hash(name); + for (mb = hashtab[hashndx]; mb; mb = mb->b_next) { + if (strcmp(name, mb->b_name) == 0) + break; + } + + return (mb); +} + +/* + * Create an entry for the given (name, major, bind_name) tuple in the + * hash table supplied. Reject the attempt to do so if 'name' is already + * in the hash table. + * + * Does not provide synchronization, so use only during boot or with + * externally provided locking. + */ +int +make_mbind(char *name, int major, char *bind_name, struct bind **hashtab) +{ + struct bind *bp; + int hashndx; + + ASSERT(hashtab != NULL); + + /* + * Fail if the key being added is already in the hash table + */ + if (find_mbind(name, hashtab) != NULL) + return (-1); + + bp = kmem_zalloc(sizeof (struct bind), KM_SLEEP); + bp->b_name = kmem_alloc(strlen(name) + 1, KM_SLEEP); + (void) strcpy(bp->b_name, name); + bp->b_num = major; + if (bind_name != NULL) { + bp->b_bind_name = kmem_alloc(strlen(bind_name) + 1, KM_SLEEP); + (void) strcpy(bp->b_bind_name, bind_name); + } + hashndx = nm_hash(name); + bp->b_next = hashtab[hashndx]; + hashtab[hashndx] = bp; + + return (0); +} + +/* + * Delete a binding from a binding-hash. + * + * Does not provide synchronization, so use only during boot or with + * externally provided locking. + */ +void +delete_mbind(char *name, struct bind **hashtab) +{ + int hashndx; + struct bind *b, *bparent = NULL; + struct bind *t = NULL; /* target to delete */ + + hashndx = nm_hash(name); + + if (hashtab[hashndx] == NULL) + return; + + b = hashtab[hashndx]; + if (strcmp(name, b->b_name) == 0) { /* special case first elem. */ + hashtab[hashndx] = b->b_next; + t = b; + } else { + for (b = hashtab[hashndx]; b; b = b->b_next) { + if (strcmp(name, b->b_name) == 0) { + ASSERT(bparent); + t = b; + bparent->b_next = b->b_next; + break; + } + bparent = b; + } + } + + if (t != NULL) { /* delete the target */ + ASSERT(t->b_name); + kmem_free(t->b_name, strlen(t->b_name) + 1); + if (t->b_bind_name) + kmem_free(t->b_bind_name, strlen(t->b_bind_name) + 1); + kmem_free(t, sizeof (struct bind)); + } +} + + +major_t +mod_name_to_major(char *name) +{ + struct bind *mbind; + + if ((mbind = find_mbind(name, mb_hashtab)) != NULL) + return ((major_t)mbind->b_num); + + return ((major_t)-1); +} + +char * +mod_major_to_name(major_t major) +{ + if (major >= devcnt) + return (NULL); + return ((&devnamesp[major])->dn_name); +} + +/* + * Set up the devnames array. Error check for duplicate entries. + */ +void +init_devnamesp(int size) +{ + int hshndx; + struct bind *bp; + static char dupwarn[] = + "!Device entry \"%s %d\" conflicts with previous entry \"%s %d\" " + "in /etc/name_to_major."; + static char badmaj[] = "The major number %u is invalid."; + + ASSERT(size <= L_MAXMAJ32 && size > 0); + + /* + * Allocate the devnames array. All mutexes and cv's will be + * automagically initialized. + */ + devnamesp = kobj_zalloc(size * sizeof (struct devnames), KM_SLEEP); + + /* + * Stick the contents of mb_hashtab into the devnames array. Warn if + * two hash entries correspond to the same major number, or if a + * major number is out of range. + */ + for (hshndx = 0; hshndx < MOD_BIND_HASHSIZE; hshndx++) { + for (bp = mb_hashtab[hshndx]; bp; bp = bp->b_next) { + if (make_devname(bp->b_name, (major_t)bp->b_num) != 0) { + /* + * If there is not an entry at b_num already, + * then this must be a bad major number. + */ + char *nm = mod_major_to_name(bp->b_num); + if (nm == NULL) { + cmn_err(CE_WARN, badmaj, + (uint_t)bp->b_num); + } else { + cmn_err(CE_WARN, dupwarn, bp->b_name, + bp->b_num, nm, bp->b_num); + } + } + } + } + + /* Initialize hash table for hwc_spec's */ + hwc_hash_init(); +} + +int +make_devname(char *name, major_t major) +{ + struct devnames *dnp; + char *copy; + + /* + * Until on-disk support for major nums > 14 bits arrives, fail + * any major numbers that are too big. + */ + if (major > L_MAXMAJ32) + return (EINVAL); + + dnp = &devnamesp[major]; + LOCK_DEV_OPS(&dnp->dn_lock); + if (dnp->dn_name) { + if (strcmp(dnp->dn_name, name) != 0) { + /* Another driver already here */ + UNLOCK_DEV_OPS(&dnp->dn_lock); + return (EINVAL); + } + /* Adding back a removed driver */ + dnp->dn_flags &= ~DN_DRIVER_REMOVED; + UNLOCK_DEV_OPS(&dnp->dn_lock); + return (0); + } + + /* + * Check if flag is taken by getudev() + */ + if (dnp->dn_flags & DN_TAKEN_GETUDEV) { + UNLOCK_DEV_OPS(&dnp->dn_lock); + return (EINVAL); + } + + copy = kmem_alloc(strlen(name) + 1, KM_SLEEP); + (void) strcpy(copy, name); + + /* Make sure string is copied before setting dn_name */ + membar_producer(); + dnp->dn_name = copy; + dnp->dn_flags = 0; + UNLOCK_DEV_OPS(&dnp->dn_lock); + return (0); +} + +/* + * Set up the syscallnames array. + */ +void +init_syscallnames(int size) +{ + int hshndx; + struct bind *bp; + + syscallnames = kobj_zalloc(size * sizeof (char *), KM_SLEEP); + + for (hshndx = 0; hshndx < MOD_BIND_HASHSIZE; hshndx++) { + for (bp = sb_hashtab[hshndx]; bp; bp = bp->b_next) { + make_syscallname(bp->b_name, bp->b_num); + } + } +} + +static void +make_syscallname(char *name, int sysno) +{ + char **cp = &syscallnames[sysno]; + + if (*cp != NULL) { + cmn_err(CE_WARN, "!Couldn't add system call \"%s %d\". " + "It conflicts with \"%s %d\" in /etc/name_to_sysnum.", + name, sysno, *cp, sysno); + return; + } + *cp = kmem_alloc(strlen(name) + 1, KM_SLEEP); + (void) strcpy(*cp, name); +} + +/* + * Given a system call name, get its number. + */ +int +mod_getsysnum(char *name) +{ + struct bind *mbind; + + if ((mbind = find_mbind(name, sb_hashtab)) != NULL) + return (mbind->b_num); + + return (-1); +} + +/* + * Given a system call number, get the system call name. + */ +char * +mod_getsysname(int sysnum) +{ + return (syscallnames[sysnum]); +} + +/* + * Find the name of the module containing the specified pc. + * Returns the name on success, "<unknown>" on failure. + * No mod_lock locking is required because things are never deleted from + * the &modules list. + */ +char * +mod_containing_pc(caddr_t pc) +{ + struct modctl *mcp = &modules; + + do { + if (mcp->mod_mp != NULL && + (size_t)pc - (size_t)mcp->mod_text < mcp->mod_text_size) + return (mcp->mod_modname); + } while ((mcp = mcp->mod_next) != &modules); + return ("<unknown>"); +} + +/* + * Hash tables for hwc_spec + * + * The purpose of these hash tables are to allow the framework to discover + * all possible .conf children for a given nexus. There are two hash tables. + * One is hashed based on parent name, the on the class name. Each + * driver.conf file translates to a list of hwc_spec's. Adding and + * removing the entire list is an atomic operation, protected by + * the hwc_hash_lock. + * + * What we get from all the hashing is the function hwc_get_child_spec(). + */ +#define HWC_SPEC_HASHSIZE (1 << 6) /* 64 */ + +static mod_hash_t *hwc_par_hash; /* hash by parent name */ +static mod_hash_t *hwc_class_hash; /* hash by class name */ +static kmutex_t hwc_hash_lock; /* lock protecting hwc hashes */ + +/* + * Initialize hash tables for parent and class specs + */ +static void +hwc_hash_init() +{ + hwc_par_hash = mod_hash_create_strhash("hwc parent spec hash", + HWC_SPEC_HASHSIZE, mod_hash_null_valdtor); + hwc_class_hash = mod_hash_create_strhash("hwc class spec hash", + HWC_SPEC_HASHSIZE, mod_hash_null_valdtor); +} + +/* + * Insert a spec into hash table. hwc_hash_lock must be held + */ +static void +hwc_hash_insert(struct hwc_spec *spec, char *name, mod_hash_t *hash) +{ + mod_hash_key_t key; + struct hwc_spec *entry = NULL; + + ASSERT(name != NULL); + + if (mod_hash_find(hash, (mod_hash_key_t)name, + (mod_hash_val_t)&entry) != 0) { + /* Name doesn't exist, insert a new key */ + key = kmem_alloc(strlen(name) + 1, KM_SLEEP); + (void) strcpy((char *)key, name); + if (mod_hash_insert(hash, key, (mod_hash_val_t)spec) != 0) { + kmem_free(key, strlen(name) + 1); + cmn_err(CE_WARN, "hwc hash state inconsistent"); + } + return; + } + + /* + * Name is already present, append spec to the list. + * This is the case when driver.conf specifies multiple + * nodes under a single parent or class. + */ + while (entry->hwc_hash_next) + entry = entry->hwc_hash_next; + entry->hwc_hash_next = spec; +} + +/* + * Remove a spec entry from spec hash table, the spec itself is + * destroyed external to this function. + */ +static void +hwc_hash_remove(struct hwc_spec *spec, char *name, mod_hash_t *hash) +{ + char *key; + struct hwc_spec *entry; + + ASSERT(name != NULL); + + if (mod_hash_find(hash, (mod_hash_key_t)name, + (mod_hash_val_t)&entry) != 0) { + return; /* name not found in hash */ + } + + /* + * If the head is the spec to be removed, either destroy the + * entry or replace it with the remaining list. + */ + if (entry == spec) { + if (spec->hwc_hash_next == NULL) { + (void) mod_hash_destroy(hash, (mod_hash_key_t)name); + return; + } + key = kmem_alloc(strlen(name) + 1, KM_SLEEP); + (void) strcpy(key, name); + (void) mod_hash_replace(hash, (mod_hash_key_t)key, + (mod_hash_val_t)spec->hwc_hash_next); + spec->hwc_hash_next = NULL; + return; + } + + /* + * If the head is not the one, look for the spec in the + * hwc_hash_next linkage. + */ + while (entry->hwc_hash_next && (entry->hwc_hash_next != spec)) + entry = entry->hwc_hash_next; + + if (entry->hwc_hash_next) { + entry->hwc_hash_next = spec->hwc_hash_next; + spec->hwc_hash_next = NULL; + } +} + +/* + * Hash a list of specs based on either parent name or class name + */ +static void +hwc_hash(struct hwc_spec *spec_list, major_t major) +{ + struct hwc_spec *spec = spec_list; + + mutex_enter(&hwc_hash_lock); + while (spec) { + /* Put driver major here so parent can find it */ + spec->hwc_major = major; + + if (spec->hwc_parent_name != NULL) { + hwc_hash_insert(spec, spec->hwc_parent_name, + hwc_par_hash); + } else if (spec->hwc_class_name != NULL) { + hwc_hash_insert(spec, spec->hwc_class_name, + hwc_class_hash); + } else { + cmn_err(CE_WARN, + "hwc_hash: No class or parent specified"); + } + spec = spec->hwc_next; + } + mutex_exit(&hwc_hash_lock); +} + +/* + * Remove a list of specs from hash tables. Don't destroy the specs yet. + */ +static void +hwc_unhash(struct hwc_spec *spec_list) +{ + struct hwc_spec *spec = spec_list; + + mutex_enter(&hwc_hash_lock); + while (spec) { + if (spec->hwc_parent_name != NULL) { + hwc_hash_remove(spec, spec->hwc_parent_name, + hwc_par_hash); + } else if (spec->hwc_class_name != NULL) { + hwc_hash_remove(spec, spec->hwc_class_name, + hwc_class_hash); + } else { + cmn_err(CE_WARN, + "hwc_unhash: No class or parent specified"); + } + spec = spec->hwc_next; + } + mutex_exit(&hwc_hash_lock); +} + +/* + * Make a copy of specs in a hash entry and add to the end of listp. + * Called by nexus to locate a list of child specs. + * + * entry is a list of hwc_spec chained together with hwc_hash_next. + * listp points to list chained together with hwc_next. + */ +static void +hwc_spec_add(struct hwc_spec **listp, struct hwc_spec *entry, + major_t match_major) +{ + /* Find the tail of the list */ + while (*listp) + listp = &(*listp)->hwc_next; + + while (entry) { + struct hwc_spec *spec; + + if ((match_major != (major_t)-1) && + (match_major != entry->hwc_major)) { + entry = entry->hwc_hash_next; + continue; + } + + /* + * Allocate spec and copy the content of entry. + * No need to copy class/parent name since caller + * already knows the parent dip. + */ + spec = kmem_zalloc(sizeof (*spec), KM_SLEEP); + spec->hwc_devi_name = i_ddi_strdup( + entry->hwc_devi_name, KM_SLEEP); + spec->hwc_major = entry->hwc_major; + spec->hwc_devi_sys_prop_ptr = i_ddi_prop_list_dup( + entry->hwc_devi_sys_prop_ptr, KM_SLEEP); + + *listp = spec; + listp = &spec->hwc_next; + entry = entry->hwc_hash_next; + } +} + +/* + * Given a dip, find the list of child .conf specs from most specific + * (parent pathname) to least specific (class name). + * + * This function allows top-down loading to be implemented without + * changing the format of driver.conf file. + */ +struct hwc_spec * +hwc_get_child_spec(dev_info_t *dip, major_t match_major) +{ + extern char *i_ddi_parname(dev_info_t *, char *); + extern int i_ddi_get_exported_classes(dev_info_t *, char ***); + extern void i_ddi_free_exported_classes(char **, int); + + int i, nclass; + char **classes; + struct hwc_spec *list = NULL; + mod_hash_val_t val; + char *parname, *parname_buf; + char *deviname, *deviname_buf; + char *pathname, *pathname_buf; + char *bindname; + char *drvname; + + pathname_buf = kmem_alloc(3 * MAXPATHLEN, KM_SLEEP); + deviname_buf = pathname_buf + MAXPATHLEN; + parname_buf = pathname_buf + (2 * MAXPATHLEN); + + mutex_enter(&hwc_hash_lock); + + /* + * Lookup based on full path. + * In the case of root node, ddi_pathname would return + * null string so just skip calling it. + * As the pathname always begins with /, no simpler + * name can duplicate it. + */ + pathname = (dip == ddi_root_node()) ? "/" : + ddi_pathname(dip, pathname_buf); + ASSERT(pathname != NULL); + ASSERT(*pathname == '/'); + + if (mod_hash_find(hwc_par_hash, (mod_hash_key_t)pathname, &val) == 0) { + hwc_spec_add(&list, (struct hwc_spec *)val, match_major); + } + + /* + * Lookup nodename@address. + * Note deviname cannot match pathname. + */ + deviname = ddi_deviname(dip, deviname_buf); + if (*deviname != '\0') { + /* + * Skip leading / returned by ddi_deviname. + */ + ASSERT(*deviname == '/'); + deviname++; + if ((*deviname != '\0') && + (mod_hash_find(hwc_par_hash, + (mod_hash_key_t)deviname, &val) == 0)) + hwc_spec_add(&list, + (struct hwc_spec *)val, match_major); + } + + /* + * Lookup bindingname@address. + * Take care not to perform duplicate lookups. + */ + parname = i_ddi_parname(dip, parname_buf); + if (*parname != '\0') { + ASSERT(*parname != '/'); + if ((strcmp(parname, deviname) != 0) && + (mod_hash_find(hwc_par_hash, + (mod_hash_key_t)parname, &val) == 0)) { + hwc_spec_add(&list, + (struct hwc_spec *)val, match_major); + } + } + + /* + * Lookup driver binding name + */ + bindname = ddi_binding_name(dip); + ASSERT(*bindname != '/'); + if ((strcmp(bindname, parname) != 0) && + (strcmp(bindname, deviname) != 0) && + (mod_hash_find(hwc_par_hash, (mod_hash_key_t)bindname, &val) == 0)) + hwc_spec_add(&list, (struct hwc_spec *)val, match_major); + + /* + * Lookup driver name + */ + drvname = (char *)ddi_driver_name(dip); + ASSERT(*drvname != '/'); + if ((strcmp(drvname, bindname) != 0) && + (strcmp(drvname, parname) != 0) && + (strcmp(drvname, deviname) != 0) && + (mod_hash_find(hwc_par_hash, (mod_hash_key_t)drvname, &val) == 0)) + hwc_spec_add(&list, (struct hwc_spec *)val, match_major); + + kmem_free(pathname_buf, 3 * MAXPATHLEN); + + /* + * Lookup classes exported by this node and lookup the + * class hash table for all .conf specs + */ + nclass = i_ddi_get_exported_classes(dip, &classes); + for (i = 0; i < nclass; i++) { + if (mod_hash_find(hwc_class_hash, (mod_hash_key_t)classes[i], + &val) == 0) + hwc_spec_add(&list, (struct hwc_spec *)val, + match_major); + } + i_ddi_free_exported_classes(classes, nclass); + + mutex_exit(&hwc_hash_lock); + return (list); +} |