--- a/usr/src/cmd/devfsadm/devfsadm.c Mon Feb 16 12:41:51 2009 -0600
+++ b/usr/src/cmd/devfsadm/devfsadm.c Mon Feb 16 12:06:27 2009 -0800
@@ -516,12 +516,16 @@
int num_aliases = 0;
int len;
int retval;
- int add_bind = FALSE;
+ int config = TRUE;
+ int bind = FALSE;
+ int force_flag = FALSE;
struct aliases *ap = NULL;
struct aliases *a_head = NULL;
struct aliases *a_tail = NULL;
struct modconfig mc;
+ (void) bzero(&mc, sizeof (mc));
+
if (strcmp(prog, DISKS) == 0) {
compat_class = "disk";
get_linkcompat_opts = TRUE;
@@ -596,7 +600,7 @@
build_dev = FALSE;
while ((opt =
- getopt(argc, argv, "a:bdc:i:m:np:R:r:svV:")) != EOF) {
+ getopt(argc, argv, "a:bcd:fi:m:np:R:r:suvV:")) != EOF) {
switch (opt) {
case 'a':
ap = calloc(sizeof (struct aliases), 1);
@@ -616,10 +620,10 @@
}
a_tail = ap;
num_aliases++;
- add_bind = TRUE;
+ bind = TRUE;
break;
case 'b':
- add_bind = TRUE;
+ bind = TRUE;
break;
case 'c':
(void) strcpy(mc.drvclass, optarg);
@@ -630,6 +634,9 @@
* do nothing.
*/
break;
+ case 'f':
+ force_flag = TRUE;
+ break;
case 'i':
single_drv = TRUE;
(void) strcpy(mc.drvname, optarg);
@@ -672,6 +679,14 @@
file_mods = FALSE;
flush_path_to_inst_enable = FALSE;
break;
+ case 'u':
+ /*
+ * Invoked via update_drv(1m) to update
+ * the kernel's driver/alias binding
+ * when removing one or more aliases.
+ */
+ config = FALSE;
+ break;
case 'v':
/* documented verbose flag */
add_verbose_id(VERBOSE_MID);
@@ -689,18 +704,20 @@
usage();
}
- if ((add_bind == TRUE) && (mc.major == -1 ||
- mc.drvname[0] == NULL)) {
- err_print(MAJOR_AND_B_FLAG);
- devfsadm_exit(1);
- /*NOTREACHED*/
- }
- if (add_bind == TRUE) {
+ if (bind == TRUE) {
+ if ((mc.major == -1) || (mc.drvname[0] == NULL)) {
+ err_print(MAJOR_AND_B_FLAG);
+ devfsadm_exit(1);
+ /*NOTREACHED*/
+ }
+ mc.flags = (force_flag) ? MOD_UNBIND_OVERRIDE : 0;
mc.num_aliases = num_aliases;
mc.ap = a_head;
- retval = modctl(MODADDMAJBIND, NULL, (caddr_t)&mc);
+ retval = modctl((config == TRUE) ? MODADDMAJBIND :
+ MODREMDRVALIAS, NULL, (caddr_t)&mc);
if (retval < 0) {
- err_print(MODCTL_ADDMAJBIND);
+ err_print((config == TRUE) ? MODCTL_ADDMAJBIND :
+ MODCTL_REMMAJBIND);
}
devfsadm_exit(retval);
/*NOTREACHED*/
--- a/usr/src/cmd/devfsadm/message.h Mon Feb 16 12:41:51 2009 -0600
+++ b/usr/src/cmd/devfsadm/message.h Mon Feb 16 12:06:27 2009 -0800
@@ -19,7 +19,7 @@
* CDDL HEADER END
*/
/*
- * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+ * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
@@ -62,6 +62,9 @@
#define MODCTL_ADDMAJBIND \
gettext("modctl failed to add major number binding.\n")
+#define MODCTL_REMMAJBIND \
+gettext("modctl failed to remove major number binding.\n")
+
#define DRIVER_FAILURE gettext("driver failed to attach: %s\n")
#define IS_EVENTD_RUNNING gettext("check to make sure syseventd is running\n")
--- a/usr/src/cmd/modload/addrem.h Mon Feb 16 12:41:51 2009 -0600
+++ b/usr/src/cmd/modload/addrem.h Mon Feb 16 12:06:27 2009 -0800
@@ -19,7 +19,7 @@
* CDDL HEADER END
*/
/*
- * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+ * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
@@ -136,6 +136,7 @@
extern int do_the_update(char *, char *);
extern int fill_n2m_array(char *, char **, int *);
extern int aliases_unique(char *);
+extern int aliases_exist(char *, char *);
extern int aliases_paths_exist(char *);
extern int update_driver_aliases(char *, char *);
extern int unique_driver_name(char *, char *, int *);
@@ -145,6 +146,7 @@
extern int get_max_major(char *);
extern void get_modid(char *, int *);
extern int config_driver(char *, major_t, char *, char *, int, int);
+extern int unconfig_driver(char *, major_t, char *, int, int);
extern void load_driver(char *, int);
extern int create_reconfig(char *);
extern void cleanup_moddir(void);
--- a/usr/src/cmd/modload/drvsubr.c Mon Feb 16 12:41:51 2009 -0600
+++ b/usr/src/cmd/modload/drvsubr.c Mon Feb 16 12:06:27 2009 -0800
@@ -19,7 +19,7 @@
* CDDL HEADER END
*/
/*
- * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+ * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
@@ -1005,19 +1005,19 @@
}
/*
- * check that major_num doesn't exceed maximum on this machine
- * do this here to support add_drv on server for diskless clients
+ * Exec devfsadm to perform driver config/unconfig operation,
+ * adding or removing aliases.
*/
-int
-config_driver(
+static int
+exec_devfsadm(
+ boolean_t config,
char *driver_name,
major_t major_num,
char *aliases,
char *classes,
- int cleanup_flag,
- int verbose_flag)
+ int verbose_flag,
+ int force_flag)
{
- int max_dev;
int n = 0;
char *cmdline[MAX_CMD_LINE];
char maj_num[128];
@@ -1025,23 +1025,15 @@
char *current;
int exec_status;
int len;
-
- if (modctl(MODRESERVED, NULL, &max_dev) < 0) {
- perror(NULL);
- (void) fprintf(stderr, gettext(ERR_MAX_MAJOR));
- return (ERROR);
- }
-
- if (major_num >= max_dev) {
- (void) fprintf(stderr, gettext(ERR_MAX_EXCEEDS),
- major_num, max_dev);
- return (ERROR);
- }
-
- /* bind major number and driver name */
+ int rv;
/* build command line */
cmdline[n++] = DRVCONFIG;
+ if (config == B_FALSE) {
+ cmdline[n++] = "-u"; /* unconfigure */
+ if (force_flag)
+ cmdline[n++] = "-f"; /* force if currently in use */
+ }
if (verbose_flag) {
cmdline[n++] = "-v";
}
@@ -1076,9 +1068,57 @@
}
cmdline[n] = (char *)0;
- exec_status = exec_command(DRVCONFIG_PATH, cmdline);
+ rv = exec_command(DRVCONFIG_PATH, cmdline);
+ if (rv == NOERR)
+ return (NOERR);
+ return (ERROR);
+}
+
+int
+unconfig_driver(
+ char *driver_name,
+ major_t major_num,
+ char *aliases,
+ int verbose_flag,
+ int force_flag)
+{
+ return (exec_devfsadm(B_FALSE, driver_name, major_num,
+ aliases, NULL, verbose_flag, force_flag));
+}
- if (exec_status == NOERR)
+/*
+ * check that major_num doesn't exceed maximum on this machine
+ * do this here to support add_drv on server for diskless clients
+ */
+int
+config_driver(
+ char *driver_name,
+ major_t major_num,
+ char *aliases,
+ char *classes,
+ int cleanup_flag,
+ int verbose_flag)
+{
+ int max_dev;
+ int rv;
+
+ if (modctl(MODRESERVED, NULL, &max_dev) < 0) {
+ perror(NULL);
+ (void) fprintf(stderr, gettext(ERR_MAX_MAJOR));
+ return (ERROR);
+ }
+
+ if (major_num >= max_dev) {
+ (void) fprintf(stderr, gettext(ERR_MAX_EXCEEDS),
+ major_num, max_dev);
+ return (ERROR);
+ }
+
+ /* bind major number and driver name */
+ rv = exec_devfsadm(B_TRUE, driver_name, major_num,
+ aliases, classes, verbose_flag, 0);
+
+ if (rv == NOERR)
return (NOERR);
perror(NULL);
remove_entry(cleanup_flag, driver_name);
@@ -1499,8 +1539,9 @@
char *current_head;
char *previous_head;
char *one_entry;
- int i, len;
+ int len;
int is_unique;
+ int err;
len = strlen(aliases);
@@ -1513,43 +1554,91 @@
previous_head = aliases;
do {
- for (i = 0; i <= len; i++)
- one_entry[i] = 0;
-
+ bzero(one_entry, len+1);
current_head = get_entry(previous_head, one_entry, ' ', 1);
previous_head = current_head;
if ((unique_driver_name(one_entry, name_to_major,
- &is_unique)) == ERROR) {
- free(one_entry);
- return (ERROR);
- }
+ &is_unique)) == ERROR)
+ goto err_out;
if (is_unique != UNIQUE) {
(void) fprintf(stderr, gettext(ERR_ALIAS_IN_NAM_MAJ),
one_entry);
- free(one_entry);
- return (ERROR);
+ goto err_out;
}
- if (unique_drv_alias(one_entry) != NOERR) {
- free(one_entry);
- return (ERROR);
+ if ((err = unique_drv_alias(one_entry)) != UNIQUE) {
+ if (err == NOT_UNIQUE) {
+ (void) fprintf(stderr,
+ gettext(ERR_ALIAS_IN_USE), one_entry);
+ }
+ goto err_out;
}
if (!is_token(one_entry)) {
(void) fprintf(stderr, gettext(ERR_BAD_TOK),
"-i", one_entry);
- free(one_entry);
- return (ERROR);
+ goto err_out;
}
} while (*current_head != '\0');
free(one_entry);
-
return (NOERR);
+err_out:
+ free(one_entry);
+ return (ERROR);
+}
+
+/*
+ * verify each alias :
+ * alias list members separated by white space and quoted
+ * exist as alias name in /etc/driver_aliases
+ */
+int
+aliases_exist(char *drvname, char *aliases)
+{
+ char *current_head;
+ char *previous_head;
+ char *one_entry;
+ int len;
+ int is_unique;
+ int err;
+
+ len = strlen(aliases);
+
+ one_entry = calloc(len + 1, 1);
+ if (one_entry == NULL) {
+ (void) fprintf(stderr, gettext(ERR_NO_MEM));
+ return (ERROR);
+ }
+
+ previous_head = aliases;
+
+ do {
+ bzero(one_entry, len+1);
+ current_head = get_entry(previous_head, one_entry, ' ', 1);
+ previous_head = current_head;
+
+ if ((err = unique_drv_alias(one_entry)) != NOT_UNIQUE)
+ goto err_out;
+
+ if (!is_token(one_entry)) {
+ (void) fprintf(stderr, gettext(ERR_BAD_TOK),
+ "-i", one_entry);
+ goto err_out;
+ }
+
+ } while (*current_head != '\0');
+
+ free(one_entry);
+ return (NOERR);
+
+err_out:
+ free(one_entry);
+ return (ERROR);
}
@@ -1616,6 +1705,14 @@
}
+/*
+ * Return:
+ * ERROR in case of memory or read error
+ * UNIQUE if there is no existing match to the supplied alias
+ * NOT_UNIQUE if there is a match
+ * An error message is emitted in the case of ERROR,
+ * up to the caller otherwise.
+ */
int
unique_drv_alias(char *drv_alias)
{
@@ -1624,13 +1721,13 @@
char line[MAX_N2M_ALIAS_LINE + 1], *cp;
char alias[FILENAME_MAX + 1];
char *a;
- int status = NOERR;
+ int status = UNIQUE;
fp = fopen(driver_aliases, "r");
if (fp != NULL) {
while ((fgets(line, sizeof (line), fp) != 0) &&
- status != ERROR) {
+ status == UNIQUE) {
/* cut off comments starting with '#' */
if ((cp = strchr(line, '#')) != NULL)
*cp = '\0';
@@ -1652,10 +1749,8 @@
if ((strcmp(drv_alias, drv) == 0) ||
(strcmp(drv_alias, a) == 0)) {
- (void) fprintf(stderr,
- gettext(ERR_ALIAS_IN_USE),
- drv_alias);
- status = ERROR;
+ status = NOT_UNIQUE;
+ break;
}
}
(void) fclose(fp);
@@ -1670,24 +1765,26 @@
/*
* search for driver_name in first field of file file_name
- * searching name_to_major and driver_aliases: name separated from rest of
- * line by blank
- * if there return
- * else return
+ * searching name_to_major and driver_aliases: name separated
+ * from the remainder of the line by white space.
*/
int
unique_driver_name(char *driver_name, char *file_name,
int *is_unique)
{
- int ret;
+ int ret, err;
if ((ret = get_major_no(driver_name, file_name)) == ERROR) {
(void) fprintf(stderr, gettext(ERR_CANT_ACCESS_FILE),
file_name);
} else {
- /* XXX */
/* check alias file for name collision */
- if (unique_drv_alias(driver_name) == ERROR) {
+ if ((err = unique_drv_alias(driver_name)) != UNIQUE) {
+ if (err == NOT_UNIQUE) {
+ (void) fprintf(stderr,
+ gettext(ERR_ALIAS_IN_USE),
+ driver_name);
+ }
ret = ERROR;
} else {
if (ret != UNIQUE)
--- a/usr/src/cmd/modload/errmsg.h Mon Feb 16 12:41:51 2009 -0600
+++ b/usr/src/cmd/modload/errmsg.h Mon Feb 16 12:06:27 2009 -0800
@@ -76,12 +76,14 @@
#define ERR_NO_MEM "Not enough memory\n"
#define ERR_DEL_ENTRY "Cannot delete entry for driver (%s) from file (%s).\n"
#define ERR_NO_ENTRY "No entry found for driver (%s) in file (%s).\n"
+#define ERR_DEV_IN_USE "One or more devices remain in use for driver %s.\n"
#define ERR_INT_UPDATE "Internal error updating (%s).\n"
#define ERR_NOMOD "Cannot find module (%s).\n"
#define ERR_MAX_MAJOR "Cannot get major device information.\n"
#define ERR_NO_FREE_MAJOR "No available major numbers.\n"
#define ERR_NOT_UNIQUE "Driver (%s) is already installed.\n"
#define ERR_NOT_INSTALLED "Driver (%s) not installed.\n"
+#define ERR_ALIAS_NOT_BOUND "Alias not bound to driver %s.\n"
#define ERR_UPDATE "Cannot update (%s).\n"
#define ERR_MAX_EXCEEDS "Major number (%d) exceeds maximum (%d).\n"
#define ERR_NO_CLEAN "Cannot update; check file %s and rem_drv %s by hand.\n"
--- a/usr/src/cmd/modload/update_drv.c Mon Feb 16 12:41:51 2009 -0600
+++ b/usr/src/cmd/modload/update_drv.c Mon Feb 16 12:06:27 2009 -0800
@@ -343,11 +343,11 @@
return (error);
}
- /* paranoia - if we crash whilst configuring */
- sync();
/* optionally update the running system - not -b */
if (update_conf) {
+ /* paranoia - if we crash whilst configuring */
+ sync();
cleanup_flag |= CLEAN_DRV_ALIAS;
if (config_driver(driver_name, major_num,
aliases2, NULL, cleanup_flag,
@@ -420,13 +420,64 @@
}
if (i_flag) {
- if ((error = delete_entry(driver_aliases,
- driver_name, ":", aliases)) != NOERR) {
+ found = get_major_no(driver_name, name_to_major);
+ if (found == ERROR) {
+ (void) fprintf(stderr, gettext(ERR_MAX_MAJOR),
+ name_to_major);
+ err_exit();
+ }
+
+ if (found == UNIQUE) {
+ (void) fprintf(stderr,
+ gettext(ERR_NOT_INSTALLED), driver_name);
+ err_exit();
+ }
+
+ major_num = (major_t)found;
+
+ /*
+ * verify that the aliases to be deleted exist
+ * before removal. With -f, failing to
+ * remove an alias is not an error so we
+ * can continue on to update the kernel.
+ */
+ error = NOERR;
+ rval = aliases_exist(driver_name, aliases);
+ if (rval == ERROR && (force_flag == 0)) {
+ (void) fprintf(stderr,
+ gettext(ERR_ALIAS_NOT_BOUND),
+ driver_name);
+ if (err != NOERR)
+ err = rval;
+ }
+ if (rval == NOERR)
+ error = delete_entry(driver_aliases,
+ driver_name, ":", aliases);
+ if (error != NOERR && (force_flag == 0)) {
(void) fprintf(stderr, gettext(ERR_NO_ENTRY),
driver_name, driver_aliases);
if (err != NOERR)
err = error;
}
+
+ /*
+ * optionally update the running system - not -b.
+ * Unless -f is specified, error if one or more
+ * devices remain bound to the alias.
+ */
+ if (err == NOERR && update_conf) {
+ /* paranoia - if we crash whilst configuring */
+ sync();
+ error = unconfig_driver(driver_name, major_num,
+ aliases, verbose_flag, force_flag);
+ if (error == ERROR && force_flag == 0) {
+ (void) fprintf(stderr,
+ gettext(ERR_DEV_IN_USE),
+ driver_name);
+ if (err != NOERR)
+ err = error;
+ }
+ }
}
if (priv != NULL) {
--- a/usr/src/cmd/truss/print.c Mon Feb 16 12:41:51 2009 -0600
+++ b/usr/src/cmd/truss/print.c Mon Feb 16 12:06:27 2009 -0800
@@ -1117,6 +1117,7 @@
s = "MODGETDEVFSPATH_MI_LEN"; break;
case MODGETDEVFSPATH_MI:
s = "MODGETDEVFSPATH_MI"; break;
+ case MODREMDRVALIAS: s = "MODREMDRVALIAS"; break;
}
}
--- a/usr/src/uts/common/os/devcfg.c Mon Feb 16 12:41:51 2009 -0600
+++ b/usr/src/uts/common/os/devcfg.c Mon Feb 16 12:06:27 2009 -0800
@@ -4273,44 +4273,150 @@
ddi_walk_devs(top_devinfo, bind_dip, (void *)NULL);
}
+/* callback data for unbind_children_by_alias() */
+typedef struct unbind_data {
+ major_t drv_major;
+ char *drv_alias;
+ int ndevs_bound;
+ int unbind_errors;
+} unbind_data_t;
+
+/*
+ * A utility function provided for testing and support convenience
+ * Called for each device during an upgrade_drv -d bound to the alias
+ * that cannot be unbound due to device in use.
+ */
+static void
+unbind_alias_dev_in_use(dev_info_t *dip, char *alias)
+{
+ if (moddebug & MODDEBUG_BINDING) {
+ cmn_err(CE_CONT, "%s%d: state %d: bound to %s\n",
+ ddi_driver_name(dip), ddi_get_instance(dip),
+ i_ddi_node_state(dip), alias);
+ }
+}
+
+/*
+ * walkdevs callback for unbind devices bound to specific driver
+ * and alias. Invoked within the context of update_drv -d <alias>.
+ */
static int
-unbind_children(dev_info_t *dip, void *arg)
-{
- int circ;
- dev_info_t *cdip;
- major_t major = (major_t)(uintptr_t)arg;
-
+unbind_children_by_alias(dev_info_t *dip, void *arg)
+{
+ int circ;
+ dev_info_t *cdip;
+ dev_info_t *next;
+ unbind_data_t *ub = (unbind_data_t *)(uintptr_t)arg;
+ int rv;
+
+ /*
+ * We are called from update_drv to try to unbind a specific
+ * set of aliases for a driver. Unbind what persistent nodes
+ * we can, and return the number of nodes which cannot be unbound.
+ * If not all nodes can be unbound, update_drv leaves the
+ * state of the driver binding files unchanged, except in
+ * the case of -f.
+ */
ndi_devi_enter(dip, &circ);
- cdip = ddi_get_child(dip);
- /*
- * We are called either from rem_drv or update_drv.
- * In both cases, we unbind persistent nodes and destroy
- * .conf nodes. In the case of rem_drv, this will be the
- * final state. In the case of update_drv, i_ddi_bind_devs()
- * will be invoked later to reenumerate (new) driver.conf
- * rebind persistent nodes.
- */
- while (cdip) {
- dev_info_t *next = ddi_get_next_sibling(cdip);
- if ((i_ddi_node_state(cdip) > DS_INITIALIZED) ||
- (ddi_driver_major(cdip) != major)) {
- cdip = next;
+ for (cdip = ddi_get_child(dip); cdip; cdip = next) {
+ next = ddi_get_next_sibling(cdip);
+ if ((ddi_driver_major(cdip) != ub->drv_major) ||
+ (strcmp(DEVI(cdip)->devi_node_name, ub->drv_alias) != 0))
continue;
+ if (i_ddi_node_state(cdip) >= DS_BOUND) {
+ rv = ndi_devi_unbind_driver(cdip);
+ if (rv != DDI_SUCCESS ||
+ (i_ddi_node_state(cdip) >= DS_BOUND)) {
+ unbind_alias_dev_in_use(cdip, ub->drv_alias);
+ ub->ndevs_bound++;
+ continue;
+ }
+ if (ndi_dev_is_persistent_node(cdip) == 0)
+ (void) ddi_remove_child(cdip, 0);
}
- (void) ndi_devi_unbind_driver(cdip);
- if (ndi_dev_is_persistent_node(cdip) == 0)
- (void) ddi_remove_child(cdip, 0);
- cdip = next;
}
ndi_devi_exit(dip, circ);
return (DDI_WALK_CONTINUE);
}
+/*
+ * Unbind devices by driver & alias
+ * Context: update_drv [-f] -d -i <alias> <driver>
+ */
+int
+i_ddi_unbind_devs_by_alias(major_t major, char *alias)
+{
+ unbind_data_t *ub;
+ int rv;
+
+ ub = kmem_zalloc(sizeof (*ub), KM_SLEEP);
+ ub->drv_major = major;
+ ub->drv_alias = alias;
+ ub->ndevs_bound = 0;
+ ub->unbind_errors = 0;
+
+ /* flush devfs so that ndi_devi_unbind_driver will work when possible */
+ devfs_clean(top_devinfo, NULL, 0);
+ ddi_walk_devs(top_devinfo, unbind_children_by_alias,
+ (void *)(uintptr_t)ub);
+
+ /* return the number of devices remaining bound to the alias */
+ rv = ub->ndevs_bound + ub->unbind_errors;
+ kmem_free(ub, sizeof (*ub));
+ return (rv);
+}
+
+/*
+ * walkdevs callback for unbind devices by driver
+ */
+static int
+unbind_children_by_driver(dev_info_t *dip, void *arg)
+{
+ int circ;
+ dev_info_t *cdip;
+ dev_info_t *next;
+ major_t major = (major_t)(uintptr_t)arg;
+ int rv;
+
+ /*
+ * We are called either from rem_drv or update_drv when reloading
+ * a driver.conf file. In either case, we unbind persistent nodes
+ * and destroy .conf nodes. In the case of rem_drv, this will be
+ * the final state. In the case of update_drv, i_ddi_bind_devs()
+ * may be invoked later to re-enumerate (new) driver.conf rebind
+ * persistent nodes.
+ */
+ ndi_devi_enter(dip, &circ);
+ for (cdip = ddi_get_child(dip); cdip; cdip = next) {
+ next = ddi_get_next_sibling(cdip);
+ if (ddi_driver_major(cdip) != major)
+ continue;
+ if (i_ddi_node_state(cdip) >= DS_BOUND) {
+ rv = ndi_devi_unbind_driver(cdip);
+ if (rv == DDI_FAILURE ||
+ (i_ddi_node_state(cdip) >= DS_BOUND))
+ continue;
+ if (ndi_dev_is_persistent_node(cdip) == 0)
+ (void) ddi_remove_child(cdip, 0);
+ }
+ }
+ ndi_devi_exit(dip, circ);
+
+ return (DDI_WALK_CONTINUE);
+}
+
+/*
+ * Unbind devices by driver
+ * Context: rem_drv or unload driver.conf
+ */
void
i_ddi_unbind_devs(major_t major)
{
- ddi_walk_devs(top_devinfo, unbind_children, (void *)(uintptr_t)major);
+ /* flush devfs so that ndi_devi_unbind_driver will work when possible */
+ devfs_clean(top_devinfo, NULL, 0);
+ ddi_walk_devs(top_devinfo, unbind_children_by_driver,
+ (void *)(uintptr_t)major);
}
/*
--- a/usr/src/uts/common/os/modctl.c Mon Feb 16 12:41:51 2009 -0600
+++ b/usr/src/uts/common/os/modctl.c Mon Feb 16 12:06:27 2009 -0800
@@ -20,7 +20,7 @@
*/
/*
- * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+ * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
@@ -153,7 +153,6 @@
static uint_t mod_autounload_key; /* for module autounload detection */
extern int obpdebug;
-extern int make_mbind(char *, int, char *, struct bind **);
#define DEBUGGER_PRESENT ((boothowto & RB_DEBUG) || (obpdebug != 0))
@@ -478,16 +477,38 @@
return (0);
}
-static int
-modctl_add_major(int *data)
+/* to be removed when Ed introduces these */
+static char *
+ddi_strdup(const char *str, int flag)
+{
+ char *rv;
+ int n = strlen(str) + 1;
+ rv = kmem_alloc(n, flag);
+ bcopy(str, rv, n);
+ return (rv);
+}
+static void
+strfree(char *str)
{
- struct modconfig mc;
- int i, rv;
- struct aliases alias;
- struct aliases *ap;
- char name[MAXMODCONFNAME];
- char cname[MAXMODCONFNAME];
- char *drvname;
+ kmem_free(str, strlen(str)+1);
+}
+
+/* Add/Remove driver and binding aliases */
+static int
+modctl_update_driver_aliases(int add, int *data)
+{
+ struct modconfig mc;
+ int i, n, rv = 0;
+ struct aliases alias;
+ struct aliases *ap;
+ char name[MAXMODCONFNAME];
+ char cname[MAXMODCONFNAME];
+ char *drvname;
+ int resid;
+ struct alias_info {
+ char *alias_name;
+ int alias_resid;
+ } *aliases, *aip;
bzero(&mc, sizeof (struct modconfig));
if (get_udatamodel() == DATAMODEL_NATIVE) {
@@ -497,7 +518,6 @@
#ifdef _SYSCALL32_IMPL
else {
struct modconfig32 modc32;
-
if (copyin(data, &modc32, sizeof (struct modconfig32)) != 0)
return (EFAULT);
else {
@@ -506,6 +526,7 @@
bcopy(modc32.drvclass, mc.drvclass,
sizeof (modc32.drvclass));
mc.major = modc32.major;
+ mc.flags = modc32.flags;
mc.num_aliases = modc32.num_aliases;
mc.ap = (struct aliases *)(uintptr_t)modc32.ap;
}
@@ -517,74 +538,173 @@
* doesn't match that driver's name, fail. Otherwise, pass, since
* we may be adding aliases.
*/
- if ((drvname = mod_major_to_name(mc.major)) != NULL &&
- strcmp(drvname, mc.drvname) != 0)
+ drvname = mod_major_to_name(mc.major);
+ if ((drvname != NULL) && strcmp(drvname, mc.drvname) != 0)
return (EINVAL);
/*
- * Add each supplied driver alias to mb_hashtab
+ * Precede alias removal by unbinding as many devices as possible.
+ */
+ if (add == 0) {
+ (void) i_ddi_unload_drvconf(mc.major);
+ i_ddi_unbind_devs(mc.major);
+ }
+
+ /*
+ * Add/remove each supplied driver alias to/from mb_hashtab
*/
ap = mc.ap;
+ if (mc.num_aliases > 0)
+ aliases = kmem_zalloc(
+ mc.num_aliases * sizeof (struct alias_info), KM_SLEEP);
+ aip = aliases;
for (i = 0; i < mc.num_aliases; i++) {
bzero(&alias, sizeof (struct aliases));
-
if (get_udatamodel() == DATAMODEL_NATIVE) {
- if (copyin(ap, &alias, sizeof (struct aliases)) != 0)
- return (EFAULT);
-
- if (alias.a_len > MAXMODCONFNAME)
- return (EINVAL);
-
- if (copyin(alias.a_name, name, alias.a_len) != 0)
- return (EFAULT);
-
- if (name[alias.a_len - 1] != '\0')
- return (EINVAL);
+ if (copyin(ap, &alias, sizeof (struct aliases)) != 0) {
+ rv = EFAULT;
+ goto error;
+ }
+ if (alias.a_len > MAXMODCONFNAME) {
+ rv = EINVAL;
+ goto error;
+ }
+ if (copyin(alias.a_name, name, alias.a_len) != 0) {
+ rv = EFAULT;
+ goto error;
+ }
+ if (name[alias.a_len - 1] != '\0') {
+ rv = EINVAL;
+ goto error;
+ }
}
#ifdef _SYSCALL32_IMPL
else {
struct aliases32 al32;
-
bzero(&al32, sizeof (struct aliases32));
- if (copyin(ap, &al32, sizeof (struct aliases32)) != 0)
- return (EFAULT);
-
- if (al32.a_len > MAXMODCONFNAME)
- return (EINVAL);
-
+ if (copyin(ap, &al32, sizeof (struct aliases32)) != 0) {
+ rv = EFAULT;
+ goto error;
+ }
+ if (al32.a_len > MAXMODCONFNAME) {
+ rv = EINVAL;
+ goto error;
+ }
if (copyin((void *)(uintptr_t)al32.a_name,
- name, al32.a_len) != 0)
- return (EFAULT);
-
- if (name[al32.a_len - 1] != '\0')
- return (EINVAL);
-
+ name, al32.a_len) != 0) {
+ rv = EFAULT;
+ goto error;
+ }
+ if (name[al32.a_len - 1] != '\0') {
+ rv = EINVAL;
+ goto error;
+ }
alias.a_next = (void *)(uintptr_t)al32.a_next;
}
#endif
check_esc_sequences(name, cname);
- (void) make_mbind(cname, mc.major, NULL, mb_hashtab);
+ aip->alias_name = ddi_strdup(cname, KM_SLEEP);
ap = alias.a_next;
+ aip++;
+ }
+
+ if (add == 0) {
+ ap = mc.ap;
+ resid = 0;
+ aip = aliases;
+ /* attempt to unbind all devices bound to each alias */
+ for (i = 0; i < mc.num_aliases; i++) {
+ n = i_ddi_unbind_devs_by_alias(
+ mc.major, aip->alias_name);
+ resid += n;
+ aip->alias_resid = n;
+ }
+
+ /*
+ * If some device bound to an alias remains in use,
+ * and override wasn't specified, no change is made to
+ * the binding state and we fail the operation.
+ */
+ if (resid > 0 && ((mc.flags & MOD_UNBIND_OVERRIDE) == 0)) {
+ rv = EBUSY;
+ goto error;
+ }
+
+ /*
+ * No device remains bound of any of the aliases,
+ * or force was requested. Mark each alias as
+ * inactive via delete_mbind so no future binds
+ * to this alias take place and that a new
+ * binding can be established.
+ */
+ aip = aliases;
+ for (i = 0; i < mc.num_aliases; i++) {
+ if (moddebug & MODDEBUG_BINDING)
+ cmn_err(CE_CONT, "Removing binding for %s "
+ "(%d active references)\n",
+ aip->alias_name, aip->alias_resid);
+ delete_mbind(aip->alias_name, mb_hashtab);
+ aip++;
+ }
+ rv = 0;
+ } else {
+ aip = aliases;
+ for (i = 0; i < mc.num_aliases; i++) {
+ if (moddebug & MODDEBUG_BINDING)
+ cmn_err(CE_NOTE, "Adding binding for '%s'\n",
+ aip->alias_name);
+ (void) make_mbind(aip->alias_name,
+ mc.major, NULL, mb_hashtab);
+ aip++;
+ }
+ /*
+ * Try to establish an mbinding for mc.drvname, and add it to
+ * devnames. Add class if any after establishing the major
+ * number.
+ */
+ (void) make_mbind(mc.drvname, mc.major, NULL, mb_hashtab);
+ if ((rv = make_devname(mc.drvname, mc.major)) != 0)
+ goto error;
+
+ if (mc.drvclass[0] != '\0')
+ add_class(mc.drvname, mc.drvclass);
+ (void) i_ddi_load_drvconf(mc.major);
}
/*
- * Try to establish an mbinding for mc.drvname, and add it to devnames.
- * Add class if any after establishing the major number
+ * Ensure that all nodes are bound to the most appropriate driver
+ * possible, attempting demotion and rebind when a more appropriate
+ * driver now exists.
*/
- (void) make_mbind(mc.drvname, mc.major, NULL, mb_hashtab);
- rv = make_devname(mc.drvname, mc.major);
-
- if (rv == 0) {
- if (mc.drvclass[0] != '\0')
- add_class(mc.drvname, mc.drvclass);
- (void) i_ddi_load_drvconf(mc.major);
- i_ddi_bind_devs();
- i_ddi_di_cache_invalidate(KM_SLEEP);
+ i_ddi_bind_devs();
+ i_ddi_di_cache_invalidate(KM_SLEEP);
+
+error:
+ if (mc.num_aliases > 0) {
+ aip = aliases;
+ for (i = 0; i < mc.num_aliases; i++) {
+ if (aip->alias_name != NULL)
+ strfree(aip->alias_name);
+ aip++;
+ }
+ kmem_free(aliases, mc.num_aliases * sizeof (struct alias_info));
}
return (rv);
}
static int
+modctl_add_driver_aliases(int *data)
+{
+ return (modctl_update_driver_aliases(1, data));
+}
+
+static int
+modctl_remove_driver_aliases(int *data)
+{
+ return (modctl_update_driver_aliases(0, data));
+}
+
+static int
modctl_rem_major(major_t major)
{
struct devnames *dnp;
@@ -606,7 +726,11 @@
(void) i_ddi_unload_drvconf(major);
i_ddi_unbind_devs(major);
+ i_ddi_bind_devs();
i_ddi_di_cache_invalidate(KM_SLEEP);
+
+ /* purge all the bindings to this driver */
+ purge_mbind(major, mb_hashtab);
return (0);
}
@@ -707,6 +831,10 @@
return (0);
}
+/*
+ * Unload driver.conf file and follow up by attempting
+ * to rebind devices to more appropriate driver.
+ */
static int
modctl_unload_drvconf(major_t major)
{
@@ -719,6 +847,7 @@
if (ret != 0)
return (ret);
(void) i_ddi_unbind_devs(major);
+ i_ddi_bind_devs();
return (0);
}
@@ -2190,8 +2319,8 @@
error = 0;
break;
- case MODADDMAJBIND: /* read major binding file */
- error = modctl_add_major((int *)a2);
+ case MODADDMAJBIND: /* add major / driver alias bindings */
+ error = modctl_add_driver_aliases((int *)a2);
break;
case MODGETPATHLEN: /* get modpath length */
@@ -2339,6 +2468,10 @@
error = modctl_rem_major((major_t)a1);
break;
+ case MODREMDRVALIAS: /* remove a major/alias binding */
+ error = modctl_remove_driver_aliases((int *)a2);
+ break;
+
case MODDEVID2PATHS: /* get paths given devid */
error = modctl_devid2paths((ddi_devid_t)a1, (char *)a2,
(uint_t)a3, (size_t *)a4, (char *)a5);
--- a/usr/src/uts/common/os/modsubr.c Mon Feb 16 12:41:51 2009 -0600
+++ b/usr/src/uts/common/os/modsubr.c Mon Feb 16 12:06:27 2009 -0800
@@ -19,12 +19,10 @@
* CDDL HEADER END
*/
/*
- * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+ * Copyright 2009 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>
@@ -483,18 +481,23 @@
}
}
+/* Find an mbind by name match (caller can ask for deleted match) */
static struct bind *
-find_mbind(char *name, struct bind **hashtab)
+find_mbind(char *name, struct bind **hashtab, int deleted)
{
- int hashndx;
- struct bind *mb;
+ struct bind *mb;
- hashndx = nm_hash(name);
- for (mb = hashtab[hashndx]; mb; mb = mb->b_next) {
- if (strcmp(name, mb->b_name) == 0)
+ 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);
}
@@ -507,85 +510,110 @@
* externally provided locking.
*/
int
-make_mbind(char *name, int major, char *bind_name, struct bind **hashtab)
+make_mbind(char *name, int num, char *bind_name, struct bind **hashtab)
{
- struct bind *bp;
- int hashndx;
+ struct bind *mb;
+ struct bind **pmb;
ASSERT(hashtab != NULL);
+ ASSERT(num >= 0);
- /*
- * Fail if the key being added is already in the hash table
- */
- if (find_mbind(name, hashtab) != NULL)
+ /* Fail if the key being added is already established */
+ if (find_mbind(name, hashtab, 0) != 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;
+ /* 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.
- *
- * Does not provide synchronization, so use only during boot or with
- * externally provided locking.
+ * 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)
{
- int hashndx;
- struct bind *b, *bparent = NULL;
- struct bind *t = NULL; /* target to delete */
-
- hashndx = nm_hash(name);
-
- if (hashtab[hashndx] == NULL)
- return;
+ struct bind *mb;
- 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;
+ 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);
}
- bparent = b;
+ mb->b_num = -mb->b_num;
+ break;
}
}
-
- 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));
- }
}
+/*
+ * 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;
+ 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);
+ }
- if ((mbind = find_mbind(name, mb_hashtab)) != NULL)
- 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);
}
@@ -742,7 +770,7 @@
{
struct bind *mbind;
- if ((mbind = find_mbind(name, sb_hashtab)) != NULL)
+ if ((mbind = find_mbind(name, sb_hashtab, 0)) != NULL)
return (mbind->b_num);
return (-1);
--- a/usr/src/uts/common/sys/autoconf.h Mon Feb 16 12:41:51 2009 -0600
+++ b/usr/src/uts/common/sys/autoconf.h Mon Feb 16 12:06:27 2009 -0800
@@ -43,6 +43,7 @@
#include <sys/thread.h>
#include <sys/obpdefs.h>
#include <sys/systm.h>
+#include <sys/hwconf.h>
struct devnames {
char *dn_name; /* Name of this driver */
@@ -234,9 +235,9 @@
extern void impl_rem_dev_props(dev_info_t *);
extern void add_class(char *, char *);
-struct bind;
extern int make_mbind(char *, int, char *, struct bind **);
extern void delete_mbind(char *, struct bind **);
+extern void purge_mbind(int, struct bind **);
extern void configure(void);
#if defined(__sparc)
--- a/usr/src/uts/common/sys/ddi_implfuncs.h Mon Feb 16 12:41:51 2009 -0600
+++ b/usr/src/uts/common/sys/ddi_implfuncs.h Mon Feb 16 12:06:27 2009 -0800
@@ -20,7 +20,7 @@
*/
/*
- * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+ * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
@@ -234,6 +234,7 @@
int i_ddi_detach_installed_driver(major_t, int);
void i_ddi_set_binding_name(dev_info_t *, char *);
void i_ddi_bind_devs();
+int i_ddi_unbind_devs_by_alias(major_t, char *);
void i_ddi_unbind_devs(major_t);
ddi_prop_list_t *i_ddi_prop_list_create(ddi_prop_t *);
struct devnames;
--- a/usr/src/uts/common/sys/modctl.h Mon Feb 16 12:41:51 2009 -0600
+++ b/usr/src/uts/common/sys/modctl.h Mon Feb 16 12:06:27 2009 -0800
@@ -19,7 +19,7 @@
* CDDL HEADER END
*/
/*
- * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+ * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
@@ -276,6 +276,7 @@
#define MODUNRETIRE 41
#define MODISRETIRED 42
#define MODDEVEMPTYDIR 43
+#define MODREMDRVALIAS 44
/*
* sub cmds for MODEVENTS
@@ -315,6 +316,7 @@
char drvname[MAXMODCONFNAME];
char drvclass[MAXMODCONFNAME];
int major;
+ int flags;
int num_aliases;
struct aliases *ap;
};
@@ -331,12 +333,16 @@
char drvname[MAXMODCONFNAME];
char drvclass[MAXMODCONFNAME];
int32_t major;
+ int32_t flags;
int32_t num_aliases;
caddr32_t ap;
};
#endif /* _SYSCALL32 */
+/* flags for modconfig */
+#define MOD_UNBIND_OVERRIDE 0x01 /* fail unbind if in use */
+
/*
* Max module path length
*/
@@ -592,7 +598,6 @@
extern int make_devname(char *, major_t);
extern int gmatch(const char *, const char *);
-struct bind;
extern void make_aliases(struct bind **);
extern int read_binding_file(char *, struct bind **,
int (*line_parser)(char *, int, char *, struct bind **));
@@ -661,6 +666,7 @@
#define MODDEBUG_ERRMSG 0x40000000 /* print detailed error msgs */
#define MODDEBUG_LOADMSG2 0x20000000 /* print 2nd level msgs */
#define MODDEBUG_RETIRE 0x10000000 /* print retire msgs */
+#define MODDEBUG_BINDING 0x00040000 /* driver/alias binding */
#define MODDEBUG_FINI_EBUSY 0x00020000 /* pretend fini returns EBUSY */
#define MODDEBUG_NOAUL_IPP 0x00010000 /* no Autounloading ipp mods */
#define MODDEBUG_NOAUL_DACF 0x00008000 /* no Autounloading dacf mods */