Files
Arquivotheca.Solaris-2.5/uts/common/os/instance.c
seta75D 7c4988eac0 Init
2021-10-11 19:38:01 -03:00

1086 lines
28 KiB
C
Executable File

/*
* Copyright (c) 1992-1993, by Sun Microsystems Inc.
*/
#pragma ident "@(#)instance.c 1.15 94/03/31 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/hwconf.h> /* XXX wrong name here */
#include <sys/reboot.h>
#include <sys/ddi_impldefs.h>
#include <sys/instance.h>
#include <sys/debug.h>
static int in_get_infile(int);
static u_int in_next_instance(struct devnames *dnp);
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);
static void in_dealloc_node(in_node_t *np);
static void in_hash_walk(in_node_t *np);
static void in_pathin(char *cp, u_int instance);
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(u_int instance, char *name);
static void in_hashin(in_node_t *np);
static int in_ask_rebuild(void);
in_softstate_t e_ddi_inst_state;
/*
* State transition information:
* e_ddi_inst_state contains, among other things, the root of a tree of
* nodes used to track instance number assignment.
* Each node can be in one of 3 states, indicated by in_state:
* IN_UNKNOWN: Each node is created in this state. The instance number of
* this node is not known. in_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.
*
* During the processing of the /etc/path_to_inst file in
* e_ddi_instance_init(), after all nodes that have been explicitly
* assigned instance numbers in path_to_inst have been processed,
* all inferred nodes (nexus nodes that have only been encountered in
* the path to an explicitly assigned node) are assigned instance
* numbers in in_hashin() and their state changed from IN_UNKNOWN
* to IN_PERMANENT.
*/
/*
* This can be undefined (and the code removed as appropriate)
* when the real devfs is done.
*/
#define INSTANCE_TRANSITION_MODE
#ifdef INSTANCE_TRANSITION_MODE
/*
* This flag generates behaviour identical to that before persistent
* instance numbers, allowing us to boot without having installed a
* correct /etc/path_to_inst file.
*/
static int in_transition_mode;
#endif /* INSTANCE_TRANSITION_MODE */
/*
* Someday when we are convinced that no one has set path_to_inst_bootstrap
* in their /etc/system file, we can undefine this.
*/
#define PTI_TRANSITION_MODE
#ifdef PTI_TRANSITION_MODE
/*
* path_to_inst_bootstrap is no longer used. We leave it here so that there
* won't be a booting problem with systems that get upgraded but don't remove
* this variable from their /etc/system files. In e_ddi_instance_init, we
* reset this variable to 0 and print out a message to the user if they had
* set it to something other than 0 in their /etc/system file.
*/
int path_to_inst_bootstrap = 0;
#endif /* PTI_TRANSITION_MODE */
static char *instance_file = INSTANCE_FILE;
/*
* XXX this is copied from sun/os/modsubr.c--it should be moved out into
* XXX an include file and included both places (modctl.h?)
* XXX or better yet, it should be allocated and freed in read_binding_file
*/
#define HASHTABSIZE 64
#define HASHMASK (HASHTABSIZE-1)
/*
* 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 25
void
e_ddi_instance_init(void)
{
int hshndx;
struct bind *bp, *bp1;
struct bind **in_hashtab = NULL;
struct in_node *np, *nnp;
extern int read_binding_file(char *, struct bind **);
mutex_init(&e_ddi_inst_state.in_serial, "instance serial",
MUTEX_DEFAULT, NULL);
cv_init(&e_ddi_inst_state.in_serial_cv, "instance serial",
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.
*/
mutex_enter(&e_ddi_inst_state.in_serial);
while (e_ddi_inst_state.in_busy)
cv_wait(&e_ddi_inst_state.in_serial_cv,
&e_ddi_inst_state.in_serial);
e_ddi_inst_state.in_busy = 1;
mutex_exit(&e_ddi_inst_state.in_serial);
#ifdef PTI_TRANSITION_MODE
/*
* Check to see if path_to_inst_bootstrap was set in the /etc/system
* file. If path_to_inst_bootstrap != 0, then it was set. So set it
* back to 0 since we don't use it anymore and print out a message
*/
if (path_to_inst_bootstrap != 0) {
cmn_err(CE_WARN, "path_to_inst_bootstrap, set in /etc/system"
" no longer used.");
path_to_inst_bootstrap = 0;
}
#endif /* PTI_TRANSITION_MODE */
/*
* 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.in_root == NULL);
e_ddi_inst_state.in_root = in_alloc_node(NULL, NULL);
switch (in_get_infile((boothowto & RB_ASKNAME))) {
case PTI_REBUILD:
/*
* The file does not exist, but user booted -a and wants
* to rebuild the file, or this is the first boot after
* an install and the magic string is all that is in the
* file.
*/
#ifdef INSTANCE_TRANSITION_MODE
in_transition_mode = 1;
#endif /* INSTANCE_TRANSITION_MODE */
/*
* Need to set the reconfigure flag so that the /devices
* and /dev directories gets rebuild.
*/
boothowto |= RB_RECONFIG;
cmn_err(CE_CONT,
"?Using default device instance data\n");
break;
case PTI_FOUND:
/*
* Assertion:
* We've got a readable file
*/
in_hashtab = kmem_zalloc(sizeof (struct bind) * HASHTABSIZE,
KM_SLEEP);
(void) read_binding_file(instance_file, in_hashtab);
/*
* This really needs to be a call into the mod code
*/
for (hshndx = 0; hshndx < HASHTABSIZE; hshndx++) {
bp = in_hashtab[hshndx];
while (bp) {
if (*bp->b_name == '/')
in_pathin(bp->b_name, bp->b_num);
else
cmn_err(CE_WARN,
"invalid instance file entry %s %d",
bp->b_name, bp->b_num);
bp1 = bp;
bp = bp->b_next;
kmem_free(bp1->b_name, strlen(bp1->b_name) + 1);
kmem_free(bp1, sizeof (struct bind));
}
}
kmem_free(in_hashtab, sizeof (struct bind) * HASHTABSIZE);
/*
* Walk the tree, calling in_hashin() on every node that
* already has an instance number assigned
*/
in_hash_walk(e_ddi_inst_state.in_root->in_child);
/*
* Now do the ones that did not
*/
np = e_ddi_inst_state.in_no_instance;
e_ddi_inst_state.in_no_instance = NULL;
while (np) {
nnp = np->in_next;
np->in_next = NULL;
in_hashin(np);
np = nnp;
}
break;
default:
case PTI_NOT_FOUND:
/*
* .. else something is terribly wrong.
*
* We're paranoid here because the loss of this file
* is potentially damaging to user data e.g. a
* controller slips and we swap on someones database..
* Oops.
*
* This is the rather vicious and cruel version
* favoured by some. If you can't find path_to_inst
* and you're not booting with '-a' then just halt
* the system.
*
*/
cmn_err(CE_CONT, "Cannot open '%s'\n", instance_file);
halt((char *)NULL);
/*NOTREACHED*/
break;
}
mutex_enter(&e_ddi_inst_state.in_serial);
e_ddi_inst_state.in_busy = 0;
cv_broadcast(&e_ddi_inst_state.in_serial_cv);
mutex_exit(&e_ddi_inst_state.in_serial);
}
/*
* 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 did not find the /etc/path_to_inst file, but
* the user has booted with -a and wants to rebuild it
* or 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(int ask_rebuild)
{
int file;
int return_val;
char buf[PTI_MAGIC_STR_LEN];
/*
* Try to open the file. If the user booted -a (ask_rebuild == TRUE if
* the user booted -a) and the file was not found, then ask the
* user if they want to rebuild the path_to_inst file. If not,
* then return file not found, else return rebuild.
*/
if ((file = kobj_open(instance_file)) == -1) {
if (ask_rebuild && in_ask_rebuild())
return (PTI_REBUILD);
else
return (PTI_NOT_FOUND);
} else {
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 and ask the user if they want to
* rebuild it (if ask_rebuild is true).
*/
if (ask_rebuild && in_ask_rebuild())
return_val = PTI_REBUILD;
else
return_val = PTI_NOT_FOUND;
break;
default: /* Do nothing we have a good file */
break;
}
kobj_close(file);
return (return_val);
}
static int
in_ask_rebuild(void)
{
char answer[32];
extern void gets(char *);
do {
answer[0] = '\0';
printf(
"\nThe %s on your system does not exist or is empty.\n"
"Do you want to rebuild this file [n]? ",
instance_file);
gets(answer);
if ((answer[0] == 'y') || (answer[0] == 'Y'))
return (1);
} while ((answer[0] != 'n') && (answer[0] != 'N') &&
(answer[0] != '\0'));
return (0);
}
static void
in_hash_walk(in_node_t *np)
{
while (np) {
if (np->in_state == IN_UNKNOWN) {
np->in_next = e_ddi_inst_state.in_no_instance;
e_ddi_inst_state.in_no_instance = np;
} else
in_hashin(np);
if (np->in_child)
in_hash_walk(np->in_child);
np = np->in_sibling;
}
}
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);
}
/*
* 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).
*/
u_int
e_ddi_assign_instance(dev_info_t *dip)
{
char *name;
in_node_t *ap, *np;
u_int major;
struct devnames *dnp;
u_int 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.
*/
mutex_enter(&e_ddi_inst_state.in_serial);
while (e_ddi_inst_state.in_busy)
cv_wait(&e_ddi_inst_state.in_serial_cv,
&e_ddi_inst_state.in_serial);
e_ddi_inst_state.in_busy = 1;
mutex_exit(&e_ddi_inst_state.in_serial);
np = in_devwalk(dip, &ap);
if (np) {
/*
* found a matching instance node, we're done
*/
ret = np->in_instance;
mutex_enter(&e_ddi_inst_state.in_serial);
e_ddi_inst_state.in_busy = 0;
cv_broadcast(&e_ddi_inst_state.in_serial_cv);
mutex_exit(&e_ddi_inst_state.in_serial);
return (ret);
}
name = ddi_get_name(dip);
major = ddi_name_to_major(name);
ASSERT(major != (u_int) -1);
dnp = &devnamesp[major];
np = in_alloc_node(name, ddi_get_name_addr(dip));
if (ap == NULL)
cmn_err(CE_PANIC, "instance initialization");
in_enlist(ap, np); /* insert into tree */
#ifdef INSTANCE_TRANSITION_MODE
if (in_transition_mode) {
/*
* If a hint is provided by .conf file processing and it does
* not conflict with a previous assignment, use it
*/
if (DEVI(dip)->devi_instance != -1 &&
!in_inuse(DEVI(dip)->devi_instance, name))
np->in_instance = DEVI(dip)->devi_instance;
else
np->in_instance = in_next_instance(dnp);
} else {
np->in_instance = in_next_instance(dnp);
}
#else /* INSTANCE_TRANSITION_MODE */
np->in_instance = in_next_instance(dnp);
#endif /* INSTANCE_TRANSITION_MODE */
np->in_state = IN_PROVISIONAL;
in_hashin(np);
ASSERT(np == in_devwalk(dip, &ap));
ret = np->in_instance;
mutex_enter(&e_ddi_inst_state.in_serial);
e_ddi_inst_state.in_busy = 0;
cv_broadcast(&e_ddi_inst_state.in_serial_cv);
mutex_exit(&e_ddi_inst_state.in_serial);
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 u_int
in_next_instance(struct devnames *dnp)
{
in_node_t *np;
u_int prev;
ASSERT(e_ddi_inst_state.in_busy);
if (dnp->dn_instance != IN_SEARCHME)
return (dnp->dn_instance++);
np = dnp->dn_inlist;
if (np == NULL) {
dnp->dn_instance = 1;
return (0);
}
if (np->in_next == NULL) {
if (np->in_instance != 0)
return (0);
else {
dnp->dn_instance = 2;
return (1);
}
}
prev = np->in_instance;
if (prev != 0) /* hole at beginning of list */
return (0);
/* search the list for a hole in the sequence */
for (np = np->in_next; np; np = np->in_next) {
if (np->in_instance != prev + 1)
return (prev + 1);
else
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 *name;
in_node_t *np;
in_node_t *ap;
u_int major;
struct devnames *dnp;
name = ddi_get_name(dip);
major = ddi_name_to_major(name);
ASSERT(major != (u_int) -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.
*/
mutex_enter(&e_ddi_inst_state.in_serial);
while (e_ddi_inst_state.in_busy)
cv_wait(&e_ddi_inst_state.in_serial_cv,
&e_ddi_inst_state.in_serial);
e_ddi_inst_state.in_busy = 1;
mutex_exit(&e_ddi_inst_state.in_serial);
np = in_devwalk(dip, &ap);
ASSERT(np);
if (np->in_state == IN_PROVISIONAL) {
in_removenode(dnp, np, ap);
}
mutex_enter(&e_ddi_inst_state.in_serial);
e_ddi_inst_state.in_busy = 0;
cv_broadcast(&e_ddi_inst_state.in_serial_cv);
mutex_exit(&e_ddi_inst_state.in_serial);
}
/*
* This makes our memory of an instance assignment permanent
*/
void
e_ddi_keep_instance(dev_info_t *dip)
{
in_node_t *np;
in_node_t *ap;
/*
* 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.
*/
mutex_enter(&e_ddi_inst_state.in_serial);
while (e_ddi_inst_state.in_busy)
cv_wait(&e_ddi_inst_state.in_serial_cv,
&e_ddi_inst_state.in_serial);
e_ddi_inst_state.in_busy = 1;
mutex_exit(&e_ddi_inst_state.in_serial);
np = in_devwalk(dip, &ap);
ASSERT(np);
if (np->in_state == IN_PROVISIONAL)
np->in_state = IN_PERMANENT;
mutex_enter(&e_ddi_inst_state.in_serial);
e_ddi_inst_state.in_busy = 0;
cv_broadcast(&e_ddi_inst_state.in_serial_cv);
mutex_exit(&e_ddi_inst_state.in_serial);
}
/*
* The devnames struct for this driver is about to vanish.
* Put the instance tracking nodes on the orphan list
*/
void
e_ddi_orphan_instance_nos(in_node_t *np)
{
in_node_t *nnp;
/*
* Only one thread is allowed to change the state of the instance
* number assignments on the system at any given time.
*/
mutex_enter(&e_ddi_inst_state.in_serial);
while (e_ddi_inst_state.in_busy)
cv_wait(&e_ddi_inst_state.in_serial_cv,
&e_ddi_inst_state.in_serial);
e_ddi_inst_state.in_busy = 1;
mutex_exit(&e_ddi_inst_state.in_serial);
while (np) {
nnp = np->in_next;
np->in_next = e_ddi_inst_state.in_no_major;
e_ddi_inst_state.in_no_major = np;
np = nnp;
}
mutex_enter(&e_ddi_inst_state.in_serial);
e_ddi_inst_state.in_busy = 0;
cv_broadcast(&e_ddi_inst_state.in_serial_cv);
mutex_exit(&e_ddi_inst_state.in_serial);
}
/*
* 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_node_t *np, *nnp;
/*
* disconnect the orphan list, and call in_hashin 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.
*/
mutex_enter(&e_ddi_inst_state.in_serial);
while (e_ddi_inst_state.in_busy)
cv_wait(&e_ddi_inst_state.in_serial_cv,
&e_ddi_inst_state.in_serial);
e_ddi_inst_state.in_busy = 1;
mutex_exit(&e_ddi_inst_state.in_serial);
if (e_ddi_inst_state.in_no_major == NULL) {
mutex_enter(&e_ddi_inst_state.in_serial);
e_ddi_inst_state.in_busy = 0;
cv_broadcast(&e_ddi_inst_state.in_serial_cv);
mutex_exit(&e_ddi_inst_state.in_serial);
return;
}
/*
* Make two passes through the data, skipping over those without
* instance no assignments the first time, then making the
* assignments the second time. List should be shorter second
* time. Note that if there is not a valid major number for the
* node, in_hashin will put it back on the no_major list without
* assigning an instance number.
*/
np = e_ddi_inst_state.in_no_major;
e_ddi_inst_state.in_no_major = NULL;
while (np) {
nnp = np->in_next;
if (np->in_state == IN_UNKNOWN) {
np->in_next = e_ddi_inst_state.in_no_instance;
e_ddi_inst_state.in_no_instance = np;
} else {
np->in_next = NULL;
in_hashin(np);
}
np = nnp;
}
np = e_ddi_inst_state.in_no_instance;
e_ddi_inst_state.in_no_instance = NULL;
while (np) {
nnp = np->in_next;
np->in_next = NULL;
in_hashin(np);
np = nnp;
}
mutex_enter(&e_ddi_inst_state.in_serial);
e_ddi_inst_state.in_busy = 0;
cv_broadcast(&e_ddi_inst_state.in_serial_cv);
mutex_exit(&e_ddi_inst_state.in_serial);
}
static void
in_removenode(struct devnames *dnp, in_node_t *mp, in_node_t *ap)
{
in_node_t *prevp, *np;
ASSERT(e_ddi_inst_state.in_busy);
/*
* Assertion: parents are always instantiated by the framework
* before their children, destroyed after them
*/
ASSERT(mp->in_child == NULL);
/*
* Take the node out of the tree
*/
if (ap->in_child == mp)
ap->in_child = mp->in_sibling;
else {
for (np = ap->in_child; np; np = np->in_sibling) {
if (np->in_sibling == mp) {
np->in_sibling = mp->in_sibling;
break;
}
}
}
/*
* Take the node out of the instance list
*/
if (dnp->dn_inlist == mp) { /* head of list */
dnp->dn_inlist = mp->in_next;
dnp->dn_instance = IN_SEARCHME;
in_dealloc_node(mp);
return;
}
prevp = dnp->dn_inlist;
for (np = prevp->in_next; np; np = np->in_next) {
if (np == mp) { /* found it */
dnp->dn_instance = IN_SEARCHME;
prevp->in_next = mp->in_next;
in_dealloc_node(mp);
return;
}
prevp = np;
}
cmn_err(CE_PANIC, "in_removenode dnp %x, mp %x", (int)dnp, (int)mp);
}
/*
* Recursive ascent
*/
static in_node_t *
in_devwalk(dev_info_t *dip, in_node_t **ap)
{
in_node_t *np;
char *name;
char *addr;
ASSERT(dip);
ASSERT(e_ddi_inst_state.in_busy);
if (dip == ddi_root_node()) {
*ap = NULL;
return (e_ddi_inst_state.in_root);
}
/*
* call up to find parent, then look through the list of kids
* for a match
*/
np = in_devwalk(ddi_get_parent(dip), ap);
if (np == NULL)
return (np);
*ap = np;
np = np->in_child;
name = ddi_get_name(dip);
addr = ddi_get_name_addr(dip);
while (np) {
if (in_eqstr(np->in_name, name) &&
in_eqstr(np->in_addr, addr)) {
return (np);
}
np = np->in_sibling;
}
return (np);
}
/*
* Create a node specified by cp and assign it the given instance no.
*/
static void
in_pathin(char *cp, u_int instance)
{
in_node_t *np;
ASSERT(e_ddi_inst_state.in_busy);
#define IGNORE_STORED_PSEUDO_INSTANCES
#ifdef IGNORE_STORED_PSEUDO_INSTANCES
/*
* We should prevent pseudo devices from being placed in the
* instance tree by omitting all names beginning with /pseudo/.
* If they aren't removed, a new kernel with an old path_to_inst
* file will contain unnecessary entries in the instance tree,
* wasting memory, and they will be written out when the
* system is reconfigured (thus never going away). Upgrading the
* system might cause this to happen. Functionally, this isn't
* a problem, since the instance tree is never examined for
* pseudo devices.
*
* This uses ANSI C string concatenation to add the slashes
* to the nexus name, so DEVI_PSEUDO_NEXNAME better always be
* a macro for a constant string until this is no longer needed.
*/
if (strncmp(cp, "/" DEVI_PSEUDO_NEXNAME "/",
strlen(DEVI_PSEUDO_NEXNAME) + 2) == 0) {
return;
}
#endif
np = in_make_path(cp);
if (in_inuse(instance, np->in_name)) {
cmn_err(CE_WARN,
"instance %d already in use, cannot be assigned to '%s'",
instance, cp);
return;
}
if (np->in_state == IN_PERMANENT) {
cmn_err(CE_WARN,
"multiple instance number assignments for '%s', %d used",
cp, np->in_instance);
} else {
np->in_instance = instance;
np->in_state = IN_PERMANENT;
}
}
/*
* Create (or find) the node named by path by recursively decending 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.in_busy);
if (path == NULL || path[0] != '/')
return (NULL);
strcpy(buf, path);
cp = buf + 1; /* skip over initial '/' in path */
name = in_name_addr(&cp, &addr);
ap = e_ddi_inst_state.in_root;
rp = np = e_ddi_inst_state.in_root->in_child;
while (name) {
while (name && np) {
if (in_eqstr(name, np->in_name) &&
in_eqstr(addr, np->in_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.in_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;
}
}
}
/*
* 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);
}
/*
* Put this node on the "by instance number" list
* sorted in increasing instance number order.
* This assumes that it will be called with nodes of type IN_UNKNOWN
* only after all the other type nodes for a given major have been processed.
* We do not assign instance numbers to nodes without a major number because
* we depend on major numbers to handle driver aliases.
*/
void
in_hashin(in_node_t *np)
{
struct devnames *dnp;
in_node_t *mp, *pp;
int major;
ASSERT(e_ddi_inst_state.in_busy);
major = ddi_name_to_major(np->in_name);
if (major == -1) {
np->in_next = e_ddi_inst_state.in_no_major;
e_ddi_inst_state.in_no_major = np;
return;
}
dnp = &devnamesp[major];
if (np->in_state == IN_UNKNOWN) {
np->in_instance = in_next_instance(dnp);
np->in_state = IN_PERMANENT;
}
dnp->dn_instance = IN_SEARCHME;
pp = mp = dnp->dn_inlist;
if (mp == NULL || np->in_instance < mp->in_instance) {
np->in_next = mp;
dnp->dn_inlist = np;
} else {
ASSERT(mp->in_instance != np->in_instance);
while (mp->in_instance < np->in_instance && mp->in_next) {
pp = mp;
mp = mp->in_next;
ASSERT(mp->in_instance != np->in_instance);
}
if (mp->in_instance < np->in_instance) { /* end of list */
np->in_next = NULL;
mp->in_next = np;
} else {
np->in_next = pp->in_next;
pp->in_next = np;
}
}
}
/*
* 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;
u_int namelen;
ASSERT(e_ddi_inst_state.in_busy);
/*
* Has name or will become root
*/
ASSERT(name || e_ddi_inst_state.in_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_name = cp + sizeof (in_node_t);
strcpy(np->in_name, name);
}
np->in_addr = cp + sizeof (in_node_t) + namelen;
strcpy(np->in_addr, addr);
np->in_state = IN_UNKNOWN;
np->in_instance = -1;
return (np);
}
static void
in_dealloc_node(in_node_t *np)
{
/*
* The root node can never be de-allocated
*/
ASSERT(np->in_name && np->in_addr);
ASSERT(e_ddi_inst_state.in_busy);
kmem_free(np, sizeof (in_node_t) + strlen(np->in_name)
+ strlen(np->in_addr) + 2);
}
/*
* 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(u_int instance, char *name)
{
int major;
in_node_t *np;
struct devnames *dnp;
ASSERT(e_ddi_inst_state.in_busy);
major = ddi_name_to_major(name);
/*
* For now, if we've never heard of this device we assume it is not
* in use, since we can't tell
* XXX could to the weaker search through the nomajor list checking
* XXX for the same name
*/
if (major == -1)
return (0);
dnp = &devnamesp[major];
np = dnp->dn_inlist;
while (np) {
if (np->in_instance == instance)
return (1);
np = np->in_next;
}
return (0);
}