/* * 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 (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 *); int major_valid(major_t major) { return (major != DDI_MAJOR_T_NONE && (major >= 0 && major < devcnt)); } int driver_installed(major_t major) { return (major_valid(major) && devnamesp[major].dn_name != NULL); } int driver_active(major_t major) { return (driver_installed(major) && !(devnamesp[major].dn_flags & (DN_DRIVER_REMOVED|DN_DRIVER_INACTIVE))); } struct dev_ops * mod_hold_dev_by_major(major_t major) { struct dev_ops **devopspp, *ops; int loaded; char *drvname; if (!driver_active(major)) 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 (!driver_active(major)) 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)) == DDI_MAJOR_T_NONE) 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)) == DDI_MAJOR_T_NONE) 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; if (i_ddi_prop_search(DDI_DEV_T_ANY, DDI_OPEN_RETURNS_EINTR, DDI_PROP_TYPE_INT, &props)) dnp->dn_flags |= DN_OPEN_RETURNS_EINTR; if (i_ddi_prop_search(DDI_DEV_T_ANY, "scsi-size-clean", DDI_PROP_TYPE_INT, &props)) dnp->dn_flags |= DN_SCSI_SIZE_CLEAN; } if (i_ddi_prop_search(DDI_DEV_T_ANY, DDI_VHCI_CLASS, DDI_PROP_TYPE_STRING, &props)) dnp->dn_flags |= DN_PHCI_DRIVER; if (i_ddi_prop_search(DDI_DEV_T_ANY, DDI_DEVID_REGISTRANT, DDI_PROP_TYPE_INT, &props)) { dnp->dn_flags |= DN_DEVID_REGISTRANT; } 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; } } /* Find an mbind by name match (caller can ask for deleted match) */ static struct bind * find_mbind(char *name, struct bind **hashtab, int deleted) { struct bind *mb; for (mb = hashtab[nm_hash(name)]; mb; mb = mb->b_next) { if (deleted && (mb->b_num >= 0)) continue; /* skip active */ if (!deleted && (mb->b_num < 0)) continue; /* skip deleted */ /* return if name matches */ 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 num, char *bind_name, struct bind **hashtab) { struct bind *mb; struct bind **pmb; ASSERT(hashtab != NULL); ASSERT(num >= 0); /* Fail if the key being added is already established */ if (find_mbind(name, hashtab, 0) != NULL) return (-1); /* Allocate new mbind */ mb = kmem_zalloc(sizeof (struct bind), KM_SLEEP); mb->b_name = i_ddi_strdup(name, KM_SLEEP); mb->b_num = num; if (bind_name != NULL) mb->b_bind_name = i_ddi_strdup(bind_name, KM_SLEEP); /* Insert at head of hash */ pmb = &hashtab[nm_hash(name)]; mb->b_next = *pmb; *pmb = mb; return (0); } /* * Delete a binding from a binding-hash. Since there is no locking we * delete an mbind by making its b_num negative. We also support find_mbind * of deleted entries, so we still need deleted items on the list. */ void delete_mbind(char *name, struct bind **hashtab) { struct bind *mb; for (mb = hashtab[nm_hash(name)]; mb; mb = mb->b_next) { if ((mb->b_num >= 0) && (strcmp(name, mb->b_name) == 0)) { /* delete by making b_num negative */ if (moddebug & MODDEBUG_BINDING) { cmn_err(CE_CONT, "mbind: %s %d deleted\n", name, mb->b_num); } mb->b_num = -mb->b_num; break; } } } /* * Delete all items in an mbind associated with specified num. * An example would be rem_drv deleting all aliases associated with a * driver major number. */ void purge_mbind(int num, struct bind **hashtab) { int i; struct bind *mb; /* search all hash lists for items that associated with 'num' */ for (i = 0; i < MOD_BIND_HASHSIZE; i++) { for (mb = hashtab[i]; mb; mb = mb->b_next) { if (mb->b_num == num) { if (moddebug & MODDEBUG_BINDING) cmn_err(CE_CONT, "mbind: %s %d purged\n", mb->b_name, num); /* purge by changing the sign */ mb->b_num = -num; } } } } major_t mod_name_to_major(char *name) { struct bind *mbind; major_t maj; /* Search for non-deleted match. */ if ((mbind = find_mbind(name, mb_hashtab, 0)) != NULL) { if (moddebug & MODDEBUG_BINDING) { if (find_mbind(name, mb_hashtab, 1)) cmn_err(CE_CONT, "'%s' has deleted match too\n", name); } return ((major_t)mbind->b_num); } /* * Search for deleted match: We may find that we have dependencies * on drivers that have been deleted (but the old driver may still * be bound to a node). These callers should be converted to use * ddi_driver_major(i.e. devi_major). */ if (moddebug & MODDEBUG_BINDING) { if ((mbind = find_mbind(name, mb_hashtab, 1)) != NULL) { maj = (major_t)(-(mbind->b_num)); cmn_err(CE_CONT, "Reference to deleted alias '%s' %d\n", name, maj); } } return (DDI_MAJOR_T_NONE); } char * mod_major_to_name(major_t major) { if (!driver_installed(major)) 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) != 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, int dn_flags) { 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; dnp->dn_flags |= dn_flags; 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 = dn_flags; 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) { if (bp->b_num < 0 || bp->b_num >= size) { cmn_err(CE_WARN, "!Couldn't add system call \"%s %d\". " "Value out of range (0..%d) in " "/etc/name_to_sysnum.", bp->b_name, bp->b_num, size - 1); continue; } 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, 0)) != 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, "" 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 (""); } /* * 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 != DDI_MAJOR_T_NONE) && (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); }