diff options
Diffstat (limited to 'usr/src/uts/common/os/instance.c')
-rw-r--r-- | usr/src/uts/common/os/instance.c | 1267 |
1 files changed, 1267 insertions, 0 deletions
diff --git a/usr/src/uts/common/os/instance.c b/usr/src/uts/common/os/instance.c new file mode 100644 index 0000000000..0144ec7c05 --- /dev/null +++ b/usr/src/uts/common/os/instance.c @@ -0,0 +1,1267 @@ +/* + * 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 2005 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Instance number assignment code + */ + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/errno.h> +#include <sys/systm.h> +#include <sys/kobj.h> +#include <sys/t_lock.h> +#include <sys/kmem.h> +#include <sys/cmn_err.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/autoconf.h> +#include <sys/systeminfo.h> +#include <sys/hwconf.h> +#include <sys/reboot.h> +#include <sys/ddi_impldefs.h> +#include <sys/instance.h> +#include <sys/debug.h> +#include <sys/sysevent.h> +#include <sys/modctl.h> +#include <sys/console.h> +#include <sys/cladm.h> + +static void in_preassign_instance(void); +static void i_log_devfs_instance_mod(void); +static int in_get_infile(char *); +static void in_removenode(struct devnames *dnp, in_node_t *mp, in_node_t *ap); +static in_node_t *in_alloc_node(char *name, char *addr); +static int in_eqstr(char *a, char *b); +static char *in_name_addr(char **cpp, char **addrp); +static in_node_t *in_devwalk(dev_info_t *dip, in_node_t **ap, char *addr); +static void in_dealloc_node(in_node_t *np); +static in_node_t *in_make_path(char *path); +static void in_enlist(in_node_t *ap, in_node_t *np); +static int in_inuse(int instance, char *name); +static void in_hashdrv(in_drv_t *dp); +static in_drv_t *in_drvwalk(in_node_t *np, char *binding_name); +static in_drv_t *in_alloc_drv(char *bindingname); +static void in_endrv(in_node_t *np, in_drv_t *dp); +static void in_dq_drv(in_drv_t *np); +static void in_removedrv(struct devnames *dnp, in_drv_t *mp); +static int in_pathin(char *cp, int instance, char *bname, struct bind **args); +static int in_next_instance(major_t); + +/* external functions */ +extern char *i_binding_to_drv_name(char *bname); + +/* + * This plus devnames defines the entire software state of the instance world. + */ +typedef struct in_softstate { + in_node_t *ins_root; /* the root of our instance tree */ + in_drv_t *ins_no_major; /* majorless drv entries */ + /* + * Used to serialize access to data structures + */ + void *ins_thread; + kmutex_t ins_serial; + kcondvar_t ins_serial_cv; + int ins_busy; + char ins_dirty; /* need flush */ +} in_softstate_t; + +static in_softstate_t e_ddi_inst_state; + +/* + * State transition information: + * e_ddi_inst_state contains, among other things, the root of a tree of + * device nodes used to track instance number assignments. + * Each device node may contain multiple driver bindings, represented + * by a linked list of in_drv_t nodes, each with an instance assignment + * (except for root node). Each in_drv node can be in one of 3 states, + * indicated by ind_state: + * + * IN_UNKNOWN: Each node created in this state. The instance number of + * this node is not known. ind_instance is set to -1. + * IN_PROVISIONAL: When a node is assigned an instance number in + * e_ddi_assign_instance(), its state is set to IN_PROVISIONAL. + * Subsequently, the framework will always call either + * e_ddi_keep_instance() which makes the node IN_PERMANENT, + * or e_ddi_free_instance(), which deletes the node. + * IN_PERMANENT: + * If e_ddi_keep_instance() is called on an IN_PROVISIONAL node, + * its state is set to IN_PERMANENT. + */ + +static char *instance_file = INSTANCE_FILE; +static char *instance_file_backup = INSTANCE_FILE INSTANCE_FILE_SUFFIX; + +/* + * Return values for in_get_infile(). + */ +#define PTI_FOUND 0 +#define PTI_NOT_FOUND 1 +#define PTI_REBUILD 2 + +/* + * Path to instance file magic string used for first time boot after + * an install. If this is the first string in the file we will + * automatically rebuild the file. + */ +#define PTI_MAGIC_STR "#path_to_inst_bootstrap_1" +#define PTI_MAGIC_STR_LEN (sizeof (PTI_MAGIC_STR) - 1) + +void +e_ddi_instance_init(void) +{ + char *file; + int rebuild = 1; + struct in_drv *dp; + + mutex_init(&e_ddi_inst_state.ins_serial, NULL, MUTEX_DEFAULT, NULL); + cv_init(&e_ddi_inst_state.ins_serial_cv, NULL, CV_DEFAULT, NULL); + + /* + * Only one thread is allowed to change the state of the instance + * number assignments on the system at any given time. + * Note that this is not really necessary, as we are single-threaded + * here, but it won't hurt, and it allows us to keep ASSERTS for + * our assumptions in the code. + */ + e_ddi_enter_instance(); + + /* + * Create the root node, instance zallocs to 0. + * The name and address of this node never get examined, we always + * start searching with its first child. + */ + ASSERT(e_ddi_inst_state.ins_root == NULL); + e_ddi_inst_state.ins_root = in_alloc_node(NULL, NULL); + dp = in_alloc_drv("rootnex"); + in_endrv(e_ddi_inst_state.ins_root, dp); + + file = instance_file; + switch (in_get_infile(file)) { + default: + case PTI_NOT_FOUND: + /* make sure path_to_inst is recreated */ + boothowto |= RB_RECONFIG; + + /* + * Something is wrong. First try the backup file. + * If not found, rebuild path_to_inst. Emit a + * message about the problem. + */ + cmn_err(CE_WARN, "%s empty or not found", file); + + file = instance_file_backup; + if (in_get_infile(file) != PTI_FOUND) { + cmn_err(CE_NOTE, "rebuilding device instance data"); + break; + } + cmn_err(CE_NOTE, "using backup instance data in %s", file); + /*FALLTHROUGH*/ + + case PTI_FOUND: + /* + * We've got a readable file + * parse the file into the instance tree + */ + (void) read_binding_file(file, NULL, in_pathin); + rebuild = 0; + break; + + case PTI_REBUILD: + cmn_err(CE_CONT, + "?Using default device instance data\n"); + break; + } + + /* + * The OBP device tree has been copied to the kernel and + * bound to drivers at this point. We walk the per-driver + * list to preassign instances. Since the bus addr is + * unknown at this point, we cannot place the instance + * number in the instance tree. This will be done at + * a later time. + */ + if (rebuild) + in_preassign_instance(); + + e_ddi_exit_instance(); +} + +static void +in_preassign_instance() +{ + major_t m; + extern major_t devcnt; + + for (m = 0; m < devcnt; m++) { + struct devnames *dnp = &devnamesp[m]; + dev_info_t *dip = dnp->dn_head; + while (dip) { + DEVI(dip)->devi_instance = dnp->dn_instance; + dnp->dn_instance++; + dip = ddi_get_next(dip); + } + } +} + +/* + * Checks to see if the /etc/path_to_inst file exists and whether or not + * it has the magic string in it. + * + * Returns one of the following: + * + * PTI_FOUND - We have found the /etc/path_to_inst file + * PTI_REBUILD - We have found the /etc/path_to_inst file and the + * first line was PTI_MAGIC_STR. + * PTI_NOT_FOUND - We did not find the /etc/path_to_inst file + * + */ +static int +in_get_infile(char *filename) +{ + intptr_t file; + int return_val; + char buf[PTI_MAGIC_STR_LEN]; + + /* + * Try to open the file. + */ + if ((file = kobj_open(filename)) == -1) { + return (PTI_NOT_FOUND); + } + return_val = PTI_FOUND; + + /* + * Read the first PTI_MAGIC_STR_LEN bytes from the file to see if + * it contains the magic string. If there aren't that many bytes + * in the file, then assume file is correct and no magic string + * and move on. + */ + switch (kobj_read(file, buf, PTI_MAGIC_STR_LEN, 0)) { + + case PTI_MAGIC_STR_LEN: + /* + * If the first PTI_MAGIC_STR_LEN bytes are the magic string + * then return PTI_REBUILD. + */ + if (strncmp(PTI_MAGIC_STR, buf, PTI_MAGIC_STR_LEN) == 0) + return_val = PTI_REBUILD; + break; + + case 0: + /* + * If the file is zero bytes in length, then consider the + * file to not be found + */ + return_val = PTI_NOT_FOUND; + + default: /* Do nothing we have a good file */ + break; + } + + kobj_close(file); + return (return_val); +} + +int +is_pseudo_device(dev_info_t *dip) +{ + dev_info_t *pdip; + + for (pdip = ddi_get_parent(dip); pdip && pdip != ddi_root_node(); + pdip = ddi_get_parent(pdip)) { + if (strcmp(ddi_get_name(pdip), DEVI_PSEUDO_NEXNAME) == 0) + return (1); + } + return (0); +} + + +static void +in_set_instance(dev_info_t *dip, in_drv_t *dp, major_t major) +{ + /* use preassigned instance if available */ + if (DEVI(dip)->devi_instance != -1) + dp->ind_instance = DEVI(dip)->devi_instance; + else + dp->ind_instance = in_next_instance(major); +} + +/* + * Look up an instance number for a dev_info node, and assign one if it does + * not have one (the dev_info node has devi_name and devi_addr already set). + */ +uint_t +e_ddi_assign_instance(dev_info_t *dip) +{ + char *name; + in_node_t *ap, *np; + in_drv_t *dp; + major_t major; + uint_t ret; + char *bname; + + /* + * Allow implementation to override + */ + if ((ret = impl_assign_instance(dip)) != (uint_t)-1) + return (ret); + + /* + * If this is a pseudo-device, use the instance number + * assigned by the pseudo nexus driver. The mutex is + * not needed since the instance tree is not used. + */ + if (is_pseudo_device(dip)) { + return (ddi_get_instance(dip)); + } + + /* + * Only one thread is allowed to change the state of the instance + * number assignments on the system at any given time. + */ + e_ddi_enter_instance(); + + /* + * Look for instance node, allocate one if not found + */ + np = in_devwalk(dip, &ap, NULL); + if (np == NULL) { + name = ddi_node_name(dip); + np = in_alloc_node(name, ddi_get_name_addr(dip)); + ASSERT(np != NULL); + in_enlist(ap, np); /* insert into tree */ + } + ASSERT(np == in_devwalk(dip, &ap, NULL)); + + /* + * Look for driver entry, allocate one if not found + */ + bname = (char *)ddi_driver_name(dip); + dp = in_drvwalk(np, bname); + if (dp == NULL) { + dp = in_alloc_drv(bname); + ASSERT(dp != NULL); + major = ddi_driver_major(dip); + ASSERT(major != (major_t)-1); + in_endrv(np, dp); + in_set_instance(dip, dp, major); + dp->ind_state = IN_PROVISIONAL; + in_hashdrv(dp); + } + + ret = dp->ind_instance; + + e_ddi_exit_instance(); + return (ret); +} + +static int +mkpathname(char *path, in_node_t *np, int len) +{ + int len_needed; + + if (np == e_ddi_inst_state.ins_root) + return (DDI_SUCCESS); + + if (mkpathname(path, np->in_parent, len) == DDI_FAILURE) + return (DDI_FAILURE); + + len_needed = strlen(path); + len_needed += strlen(np->in_node_name) + 1; /* for '/' */ + if (np->in_unit_addr) { + len_needed += strlen(np->in_unit_addr) + 1; /* for '@' */ + } + len_needed += 1; /* for '\0' */ + + /* + * XX complain + */ + if (len_needed > len) + return (DDI_FAILURE); + + if (np->in_unit_addr[0] == '\0') + (void) sprintf(path+strlen(path), "/%s", np->in_node_name); + else + (void) sprintf(path+strlen(path), "/%s@%s", np->in_node_name, + np->in_unit_addr); + + return (DDI_SUCCESS); +} + +/* + * produce the path to the given instance of a major number. + * path must hold MAXPATHLEN string + */ +int +e_ddi_instance_majorinstance_to_path(major_t major, uint_t inst, char *path) +{ + struct devnames *dnp; + in_drv_t *dp; + int ret; + + e_ddi_enter_instance(); + + /* look for the instance threaded off major */ + dnp = &devnamesp[major]; + for (dp = dnp->dn_inlist; dp != NULL; dp = dp->ind_next) + if (dp->ind_instance == inst) + break; + + /* produce path from the node that uses the instance */ + if (dp) { + *path = 0; + ret = mkpathname(path, dp->ind_node, MAXPATHLEN); + } else + ret = DDI_FAILURE; + + e_ddi_exit_instance(); + return (ret); +} + +/* + * This depends on the list being sorted in ascending instance number + * sequence. dn_instance contains the next available instance no. + * or IN_SEARCHME, indicating (a) hole(s) in the sequence. + */ +static int +in_next_instance(major_t major) +{ + unsigned int prev; + struct devnames *dnp; + in_drv_t *dp; + + dnp = &devnamesp[major]; + + ASSERT(major != (major_t)-1); + ASSERT(e_ddi_inst_state.ins_busy); + if (dnp->dn_instance != IN_SEARCHME) + return (dnp->dn_instance++); + dp = dnp->dn_inlist; + + /* no existing entries, assign instance 0 */ + if (dp == NULL) { + dnp->dn_instance = 1; + return (0); + } + + prev = dp->ind_instance; + if (prev != 0) /* hole at beginning of list */ + return (0); + /* search the list for a hole in the sequence */ + for (dp = dp->ind_next; dp; dp = dp->ind_next) { + if (dp->ind_instance != prev + 1) + return (prev + 1); + prev++; + } + /* + * If we got here, then the hole has been patched + */ + dnp->dn_instance = ++prev + 1; + + return (prev); +} + +/* + * This call causes us to *forget* the instance number we've generated + * for a given device if it was not permanent. + */ +void +e_ddi_free_instance(dev_info_t *dip, char *addr) +{ + char *name; + in_node_t *np; + in_node_t *ap; /* ancestor node */ + major_t major; + struct devnames *dnp; + in_drv_t *dp; /* in_drv entry */ + + /* + * Allow implementation override + */ + if (impl_free_instance(dip) == DDI_SUCCESS) + return; + + /* + * If this is a pseudo-device, no instance number + * was assigned. + */ + if (is_pseudo_device(dip)) { + return; + } + + name = (char *)ddi_driver_name(dip); + major = ddi_driver_major(dip); + ASSERT(major != (major_t)-1); + dnp = &devnamesp[major]; + /* + * Only one thread is allowed to change the state of the instance + * number assignments on the system at any given time. + */ + e_ddi_enter_instance(); + np = in_devwalk(dip, &ap, addr); + ASSERT(np); + dp = in_drvwalk(np, name); + ASSERT(dp); + if (dp->ind_state == IN_PROVISIONAL) { + in_removedrv(dnp, dp); + } + if (np->in_drivers == NULL) { + in_removenode(dnp, np, ap); + } + e_ddi_exit_instance(); +} + +/* + * This makes our memory of an instance assignment permanent + */ +void +e_ddi_keep_instance(dev_info_t *dip) +{ + in_node_t *np, *ap; + in_drv_t *dp; + + /* + * Allow implementation override + */ + if (impl_keep_instance(dip) == DDI_SUCCESS) + return; + + /* + * Nothing to do for pseudo devices. + */ + if (is_pseudo_device(dip)) + return; + + /* + * Only one thread is allowed to change the state of the instance + * number assignments on the system at any given time. + */ + e_ddi_enter_instance(); + np = in_devwalk(dip, &ap, NULL); + ASSERT(np); + dp = in_drvwalk(np, (char *)ddi_driver_name(dip)); + ASSERT(dp); + + mutex_enter(&e_ddi_inst_state.ins_serial); + if (dp->ind_state == IN_PROVISIONAL) { + dp->ind_state = IN_PERMANENT; + i_log_devfs_instance_mod(); + e_ddi_inst_state.ins_dirty = 1; + } + mutex_exit(&e_ddi_inst_state.ins_serial); + e_ddi_exit_instance(); +} + +/* + * A new major has been added to the system. Run through the orphan list + * and try to attach each one to a driver's list. + */ +void +e_ddi_unorphan_instance_nos() +{ + in_drv_t *dp, *ndp; + + /* + * disconnect the orphan list, and call in_hashdrv for each item + * on it + */ + + /* + * Only one thread is allowed to change the state of the instance + * number assignments on the system at any given time. + */ + e_ddi_enter_instance(); + if (e_ddi_inst_state.ins_no_major == NULL) { + e_ddi_exit_instance(); + return; + } + /* + * Hash instance list to devnames structure of major. + * Note that if there is not a valid major number for the + * node, in_hashdrv will put it back on the no_major list. + */ + dp = e_ddi_inst_state.ins_no_major; + e_ddi_inst_state.ins_no_major = NULL; + while (dp) { + ndp = dp->ind_next; + ASSERT(dp->ind_state != IN_UNKNOWN); + dp->ind_next = NULL; + in_hashdrv(dp); + dp = ndp; + } + e_ddi_exit_instance(); +} + +static void +in_removenode(struct devnames *dnp, in_node_t *mp, in_node_t *ap) +{ + in_node_t *np; + + ASSERT(e_ddi_inst_state.ins_busy); + /* + * Assertion: parents are always instantiated by the framework + * before their children, destroyed after them + */ + ASSERT(mp->in_child == NULL); + /* + * Assertion: drv entries are always removed before their owning nodes + */ + ASSERT(mp->in_drivers == NULL); + /* + * Take the node out of the tree + */ + if (ap->in_child == mp) { + ap->in_child = mp->in_sibling; + in_dealloc_node(mp); + return; + } else { + for (np = ap->in_child; np; np = np->in_sibling) { + if (np->in_sibling == mp) { + np->in_sibling = mp->in_sibling; + in_dealloc_node(mp); + return; + } + } + } + panic("in_removenode dnp %p mp %p", (void *)dnp, (void *)mp); +} + +/* + * Recursive ascent + * + * This now only does half the job. It finds the node, then the caller + * has to search the node for the binding name + */ +static in_node_t * +in_devwalk(dev_info_t *dip, in_node_t **ap, char *addr) +{ + in_node_t *np; + char *name; + + ASSERT(dip); + ASSERT(e_ddi_inst_state.ins_busy); + if (dip == ddi_root_node()) { + *ap = NULL; + return (e_ddi_inst_state.ins_root); + } + /* + * call up to find parent, then look through the list of kids + * for a match + */ + np = in_devwalk(ddi_get_parent(dip), ap, NULL); + if (np == NULL) + return (np); + *ap = np; + np = np->in_child; + name = ddi_node_name(dip); + if (addr == NULL) + addr = ddi_get_name_addr(dip); + + while (np) { + if (in_eqstr(np->in_node_name, name) && + in_eqstr(np->in_unit_addr, addr)) { + return (np); + } + np = np->in_sibling; + } + return (np); +} + +/* + * Create a node specified by cp and assign it the given instance no. + */ +static int +in_pathin(char *cp, int instance, char *bname, struct bind **args) +{ + in_node_t *np; + in_drv_t *dp; + char *name; + + ASSERT(e_ddi_inst_state.ins_busy); + ASSERT(args == NULL); + + /* + * Give a warning to the console. + * return value ignored + */ + if (cp[0] != '/' || instance == -1 || bname == NULL) { + cmn_err(CE_WARN, + "invalid instance file entry %s %d", + cp, instance); + + return (0); + } + + if ((name = i_binding_to_drv_name(bname)) != NULL) + bname = name; + + np = in_make_path(cp); + ASSERT(np); + if (in_inuse(instance, bname)) { + cmn_err(CE_WARN, + "instance already in use: %s %d", cp, instance); + return (0); + } + dp = in_drvwalk(np, bname); + if (dp != NULL) { + cmn_err(CE_WARN, + "multiple instance number assignments for " + "'%s' (driver %s), %d used", + cp, bname, dp->ind_instance); + return (0); + } + dp = in_alloc_drv(bname); + in_endrv(np, dp); + dp->ind_instance = instance; + dp->ind_state = IN_PERMANENT; + in_hashdrv(dp); + + return (0); +} + +/* + * Create (or find) the node named by path by recursively descending from the + * root's first child (we ignore the root, which is never named) + */ +static in_node_t * +in_make_path(char *path) +{ + in_node_t *ap; /* ancestor pointer */ + in_node_t *np; /* working node pointer */ + in_node_t *rp; /* return node pointer */ + char buf[MAXPATHLEN]; /* copy of string so we can change it */ + char *cp, *name, *addr; + + ASSERT(e_ddi_inst_state.ins_busy); + if (path == NULL || path[0] != '/') + return (NULL); + (void) snprintf(buf, sizeof (buf), "%s", path); + cp = buf + 1; /* skip over initial '/' in path */ + name = in_name_addr(&cp, &addr); + + /* + * In S9 and earlier releases, the path_to_inst file + * SunCluster was prepended with "/node@#". This was + * removed in S10. We skip the prefix if the prefix + * still exists in /etc/path_to_inst. It is needed for + * various forms of Solaris upgrade to work properly + * in the SunCluster environment. + */ + if ((cluster_bootflags & CLUSTER_CONFIGURED) && + (strcmp(name, "node") == 0)) + name = in_name_addr(&cp, &addr); + + ap = e_ddi_inst_state.ins_root; + rp = np = e_ddi_inst_state.ins_root->in_child; + while (name) { + while (name && np) { + if (in_eqstr(name, np->in_node_name) && + in_eqstr(addr, np->in_unit_addr)) { + name = in_name_addr(&cp, &addr); + if (name == NULL) + return (np); + ap = np; + np = np->in_child; + continue; + } else { + np = np->in_sibling; + } + } + np = in_alloc_node(name, addr); + in_enlist(ap, np); /* insert into tree */ + rp = np; /* value to return if we quit */ + ap = np; /* new parent */ + np = NULL; /* can have no children */ + name = in_name_addr(&cp, &addr); + } + return (rp); +} + +/* + * Insert node np into the tree as one of ap's children. + */ +static void +in_enlist(in_node_t *ap, in_node_t *np) +{ + in_node_t *mp; + ASSERT(e_ddi_inst_state.ins_busy); + /* + * Make this node some other node's child or child's sibling + */ + ASSERT(ap && np); + if (ap->in_child == NULL) { + ap->in_child = np; + } else { + for (mp = ap->in_child; mp; mp = mp->in_sibling) + if (mp->in_sibling == NULL) { + mp->in_sibling = np; + break; + } + } + np->in_parent = ap; +} + +/* + * Insert drv entry dp onto a node's driver list + */ +static void +in_endrv(in_node_t *np, in_drv_t *dp) +{ + in_drv_t *mp; + ASSERT(e_ddi_inst_state.ins_busy); + ASSERT(np && dp); + mp = np->in_drivers; + np->in_drivers = dp; + dp->ind_next_drv = mp; + dp->ind_node = np; +} + +/* + * Parse the next name out of the path, null terminate it and update cp. + * caller has copied string so we can mess with it. + * Upon return *cpp points to the next section to be parsed, *addrp points + * to the current address substring (or NULL if none) and we return the + * current name substring (or NULL if none). name and address substrings + * are null terminated in place. + */ + +static char * +in_name_addr(char **cpp, char **addrp) +{ + char *namep; /* return value holder */ + char *ap; /* pointer to '@' in string */ + char *sp; /* pointer to '/' in string */ + + if (*cpp == NULL || **cpp == '\0') { + *addrp = NULL; + return (NULL); + } + namep = *cpp; + sp = strchr(*cpp, '/'); + if (sp != NULL) { /* more to follow */ + *sp = '\0'; + *cpp = sp + 1; + } else { /* this is last component. */ + *cpp = NULL; + } + ap = strchr(namep, '@'); + if (ap == NULL) { + *addrp = NULL; + } else { + *ap = '\0'; /* terminate the name */ + *addrp = ap + 1; + } + return (namep); +} + +/* + * Allocate a node and storage for name and addr strings, and fill them in. + */ +static in_node_t * +in_alloc_node(char *name, char *addr) +{ + in_node_t *np; + char *cp; + size_t namelen; + + ASSERT(e_ddi_inst_state.ins_busy); + /* + * Has name or will become root + */ + ASSERT(name || e_ddi_inst_state.ins_root == NULL); + if (addr == NULL) + addr = ""; + if (name == NULL) + namelen = 0; + else + namelen = strlen(name) + 1; + cp = kmem_zalloc(sizeof (in_node_t) + namelen + strlen(addr) + 1, + KM_SLEEP); + np = (in_node_t *)cp; + if (name) { + np->in_node_name = cp + sizeof (in_node_t); + (void) strcpy(np->in_node_name, name); + } + np->in_unit_addr = cp + sizeof (in_node_t) + namelen; + (void) strcpy(np->in_unit_addr, addr); + return (np); +} + +/* + * Allocate a drv entry and storage for binding name string, and fill it in. + */ +static in_drv_t * +in_alloc_drv(char *bindingname) +{ + in_drv_t *dp; + char *cp; + size_t namelen; + + ASSERT(e_ddi_inst_state.ins_busy); + /* + * Has name or will become root + */ + ASSERT(bindingname || e_ddi_inst_state.ins_root == NULL); + if (bindingname == NULL) + namelen = 0; + else + namelen = strlen(bindingname) + 1; + cp = kmem_zalloc(sizeof (in_drv_t) + namelen, KM_SLEEP); + dp = (in_drv_t *)cp; + if (bindingname) { + dp->ind_driver_name = cp + sizeof (in_drv_t); + (void) strcpy(dp->ind_driver_name, bindingname); + } + dp->ind_state = IN_UNKNOWN; + dp->ind_instance = -1; + return (dp); +} + +static void +in_dealloc_node(in_node_t *np) +{ + /* + * The root node can never be de-allocated + */ + ASSERT(np->in_node_name && np->in_unit_addr); + ASSERT(e_ddi_inst_state.ins_busy); + kmem_free(np, sizeof (in_node_t) + strlen(np->in_node_name) + + strlen(np->in_unit_addr) + 2); +} + +static void +in_dealloc_drv(in_drv_t *dp) +{ + ASSERT(dp->ind_driver_name); + ASSERT(e_ddi_inst_state.ins_busy); + kmem_free(dp, sizeof (in_drv_t) + strlen(dp->ind_driver_name) + + 1); +} + +/* + * Handle the various possible versions of "no address" + */ +static int +in_eqstr(char *a, char *b) +{ + if (a == b) /* covers case where both are nulls */ + return (1); + if (a == NULL && *b == 0) + return (1); + if (b == NULL && *a == 0) + return (1); + if (a == NULL || b == NULL) + return (0); + return (strcmp(a, b) == 0); +} + +/* + * Returns true if instance no. is already in use by named driver + */ +static int +in_inuse(int instance, char *name) +{ + major_t major; + in_drv_t *dp; + struct devnames *dnp; + + ASSERT(e_ddi_inst_state.ins_busy); + /* + * For now, if we've never heard of this device we assume it is not + * in use, since we can't tell + * XXX could do the weaker search through the nomajor list checking + * XXX for the same name + */ + if ((major = ddi_name_to_major(name)) == (major_t)-1) + return (0); + dnp = &devnamesp[major]; + + dp = dnp->dn_inlist; + while (dp) { + if (dp->ind_instance == instance) + return (1); + dp = dp->ind_next; + } + return (0); +} + +static void +in_hashdrv(in_drv_t *dp) +{ + struct devnames *dnp; + in_drv_t *mp, *pp; + major_t major; + + /* hash to no major list */ + if ((major = ddi_name_to_major(dp->ind_driver_name)) == (major_t)-1) { + dp->ind_next = e_ddi_inst_state.ins_no_major; + e_ddi_inst_state.ins_no_major = dp; + return; + } + + /* + * dnp->dn_inlist is sorted by instance number. + * Adding a new instance entry may introduce holes, + * set dn_instance to IN_SEARCHME so the next instance + * assignment may fill in holes. + */ + dnp = &devnamesp[major]; + pp = mp = dnp->dn_inlist; + if (mp == NULL || dp->ind_instance < mp->ind_instance) { + /* prepend as the first entry, turn on IN_SEARCHME */ + dnp->dn_instance = IN_SEARCHME; + dp->ind_next = mp; + dnp->dn_inlist = dp; + return; + } + + ASSERT(mp->ind_instance != dp->ind_instance); + while (mp->ind_instance < dp->ind_instance && mp->ind_next) { + pp = mp; + mp = mp->ind_next; + ASSERT(mp->ind_instance != dp->ind_instance); + } + + if (mp->ind_instance < dp->ind_instance) { /* end of list */ + dp->ind_next = NULL; + mp->ind_next = dp; + } else { + ASSERT(dnp->dn_instance == IN_SEARCHME); + dp->ind_next = pp->ind_next; + pp->ind_next = dp; + } +} + +/* + * Remove a driver entry from the list, given a previous pointer + */ +static void +in_removedrv(struct devnames *dnp, in_drv_t *mp) +{ + in_drv_t *dp; + in_drv_t *prevp; + + if (dnp->dn_inlist == mp) { /* head of list */ + dnp->dn_inlist = mp->ind_next; + dnp->dn_instance = IN_SEARCHME; + in_dq_drv(mp); + in_dealloc_drv(mp); + return; + } + prevp = dnp->dn_inlist; + for (dp = prevp->ind_next; dp; dp = dp->ind_next) { + if (dp == mp) { /* found it */ + break; + } + prevp = dp; + } + + ASSERT(dp == mp); + dnp->dn_instance = IN_SEARCHME; + prevp->ind_next = mp->ind_next; + in_dq_drv(mp); + in_dealloc_drv(mp); +} + +static void +in_dq_drv(in_drv_t *mp) +{ + struct in_node *node = mp->ind_node; + in_drv_t *ptr, *prev; + + if (mp == node->in_drivers) { + node->in_drivers = mp->ind_next_drv; + return; + } + prev = node->in_drivers; + for (ptr = prev->ind_next_drv; ptr != (struct in_drv *)NULL; + ptr = ptr->ind_next_drv) { + if (ptr == mp) { + prev->ind_next_drv = ptr->ind_next_drv; + return; + } + } + panic("in_dq_drv: in_drv not found on node driver list"); +} + + +in_drv_t * +in_drvwalk(in_node_t *np, char *binding_name) +{ + char *name; + in_drv_t *dp = np->in_drivers; + while (dp) { + if ((name = i_binding_to_drv_name(dp->ind_driver_name)) + == NULL) { + name = dp->ind_driver_name; + } + if (strcmp(binding_name, name) == 0) { + break; + } + dp = dp->ind_next_drv; + } + return (dp); +} + + + +static void +i_log_devfs_instance_mod(void) +{ + sysevent_t *ev; + sysevent_id_t eid; + + /* + * Prevent unnecessary event generation. Do not need to generate + * events during boot. + */ + if (!i_ddi_io_initialized()) + return; + + ev = sysevent_alloc(EC_DEVFS, ESC_DEVFS_INSTANCE_MOD, EP_DDI, + SE_NOSLEEP); + if (ev == NULL) { + return; + } + if (log_sysevent(ev, SE_NOSLEEP, &eid) != 0) { + cmn_err(CE_WARN, "i_log_devfs_instance_mod: failed to post " + "event"); + } + sysevent_free(ev); +} + +void +e_ddi_enter_instance() +{ + mutex_enter(&e_ddi_inst_state.ins_serial); + if (e_ddi_inst_state.ins_thread == curthread) + e_ddi_inst_state.ins_busy++; + else { + while (e_ddi_inst_state.ins_busy) + cv_wait(&e_ddi_inst_state.ins_serial_cv, + &e_ddi_inst_state.ins_serial); + e_ddi_inst_state.ins_thread = curthread; + e_ddi_inst_state.ins_busy = 1; + } + mutex_exit(&e_ddi_inst_state.ins_serial); +} + +void +e_ddi_exit_instance() +{ + mutex_enter(&e_ddi_inst_state.ins_serial); + e_ddi_inst_state.ins_busy--; + if (e_ddi_inst_state.ins_busy == 0) { + cv_broadcast(&e_ddi_inst_state.ins_serial_cv); + e_ddi_inst_state.ins_thread = NULL; + } + mutex_exit(&e_ddi_inst_state.ins_serial); +} + +int +e_ddi_instance_is_clean() +{ + return (e_ddi_inst_state.ins_dirty == 0); +} + +void +e_ddi_instance_set_clean() +{ + e_ddi_inst_state.ins_dirty = 0; +} + +in_node_t * +e_ddi_instance_root() +{ + return (e_ddi_inst_state.ins_root); +} + +/* + * Visit a node in the instance tree + */ +static int +in_walk_instances(in_node_t *np, char *path, char *this, + int (*f)(const char *, in_node_t *, in_drv_t *, void *), void *arg) +{ + in_drv_t *dp; + int rval = INST_WALK_CONTINUE; + char *next; + + while (np != NULL) { + + if (np->in_unit_addr[0] == 0) + (void) sprintf(this, "/%s", np->in_node_name); + else + (void) sprintf(this, "/%s@%s", np->in_node_name, + np->in_unit_addr); + next = this + strlen(this); + + for (dp = np->in_drivers; dp; dp = dp->ind_next_drv) { + if (dp->ind_state == IN_PERMANENT) { + rval = (*f)(path, np, dp, arg); + if (rval == INST_WALK_TERMINATE) + break; + } + } + if (np->in_child) { + rval = in_walk_instances(np->in_child, + path, next, f, arg); + if (rval == INST_WALK_TERMINATE) + break; + } + + np = np->in_sibling; + } + + return (rval); +} + +/* + * A general interface for walking the instance tree, + * calling a user-supplied callback for each node. + */ +int +e_ddi_walk_instances(int (*f)(const char *, + in_node_t *, in_drv_t *, void *), void *arg) +{ + in_node_t *root; + int rval; + char *path; + + path = kmem_zalloc(MAXPATHLEN, KM_SLEEP); + + e_ddi_enter_instance(); + root = e_ddi_instance_root(); + rval = in_walk_instances(root->in_child, path, path, f, arg); + e_ddi_exit_instance(); + + kmem_free(path, MAXPATHLEN); + return (rval); +} |