4854243 update_drv -d requires reboot
authorJerry Gilliam <Jerry.Gilliam@Sun.COM>
Mon, 16 Feb 2009 12:06:27 -0800
changeset 8831 4b58222260b7
parent 8830 f089baed734b
child 8832 dbf480d38c0d
4854243 update_drv -d requires reboot
usr/src/cmd/devfsadm/devfsadm.c
usr/src/cmd/devfsadm/message.h
usr/src/cmd/modload/addrem.h
usr/src/cmd/modload/drvsubr.c
usr/src/cmd/modload/errmsg.h
usr/src/cmd/modload/update_drv.c
usr/src/cmd/truss/print.c
usr/src/uts/common/os/devcfg.c
usr/src/uts/common/os/modctl.c
usr/src/uts/common/os/modsubr.c
usr/src/uts/common/sys/autoconf.h
usr/src/uts/common/sys/ddi_implfuncs.h
usr/src/uts/common/sys/modctl.h
--- 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 */