diff options
| author | Jerry Gilliam <Jerry.Gilliam@Sun.COM> | 2009-02-16 12:06:27 -0800 |
|---|---|---|
| committer | Jerry Gilliam <Jerry.Gilliam@Sun.COM> | 2009-02-16 12:06:27 -0800 |
| commit | 6532b9600e063234d62bca681503353c01abad20 (patch) | |
| tree | 0635e04a2713cfe39a3bcf0eb7396722060a76ff | |
| parent | d35886f1bd1fe7bb59fc0efda96f064aba885f75 (diff) | |
| download | illumos-gate-6532b9600e063234d62bca681503353c01abad20.tar.gz | |
4854243 update_drv -d requires reboot
| -rw-r--r-- | usr/src/cmd/devfsadm/devfsadm.c | 43 | ||||
| -rw-r--r-- | usr/src/cmd/devfsadm/message.h | 5 | ||||
| -rw-r--r-- | usr/src/cmd/modload/addrem.h | 4 | ||||
| -rw-r--r-- | usr/src/cmd/modload/drvsubr.c | 201 | ||||
| -rw-r--r-- | usr/src/cmd/modload/errmsg.h | 2 | ||||
| -rw-r--r-- | usr/src/cmd/modload/update_drv.c | 59 | ||||
| -rw-r--r-- | usr/src/cmd/truss/print.c | 1 | ||||
| -rw-r--r-- | usr/src/uts/common/os/devcfg.c | 148 | ||||
| -rw-r--r-- | usr/src/uts/common/os/modctl.c | 237 | ||||
| -rw-r--r-- | usr/src/uts/common/os/modsubr.c | 158 | ||||
| -rw-r--r-- | usr/src/uts/common/sys/autoconf.h | 3 | ||||
| -rw-r--r-- | usr/src/uts/common/sys/ddi_implfuncs.h | 3 | ||||
| -rw-r--r-- | usr/src/uts/common/sys/modctl.h | 10 |
13 files changed, 661 insertions, 213 deletions
diff --git a/usr/src/cmd/devfsadm/devfsadm.c b/usr/src/cmd/devfsadm/devfsadm.c index 662f5de021..03e512a3f4 100644 --- a/usr/src/cmd/devfsadm/devfsadm.c +++ b/usr/src/cmd/devfsadm/devfsadm.c @@ -516,12 +516,16 @@ parse_args(int argc, char *argv[]) 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 @@ parse_args(int argc, char *argv[]) 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 @@ parse_args(int argc, char *argv[]) } 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 @@ parse_args(int argc, char *argv[]) * do nothing. */ break; + case 'f': + force_flag = TRUE; + break; case 'i': single_drv = TRUE; (void) strcpy(mc.drvname, optarg); @@ -672,6 +679,14 @@ parse_args(int argc, char *argv[]) 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 @@ parse_args(int argc, char *argv[]) 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*/ diff --git a/usr/src/cmd/devfsadm/message.h b/usr/src/cmd/devfsadm/message.h index d3edbb6dd3..1c5b3d8f02 100644 --- a/usr/src/cmd/devfsadm/message.h +++ b/usr/src/cmd/devfsadm/message.h @@ -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 @@ gettext("must specify major number and driver name when using the -b flag\n") #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") diff --git a/usr/src/cmd/modload/addrem.h b/usr/src/cmd/modload/addrem.h index 012feb734f..59a66eccad 100644 --- a/usr/src/cmd/modload/addrem.h +++ b/usr/src/cmd/modload/addrem.h @@ -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 update_name_to_major(char *, major_t *, int); 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 trim_duplicate_aliases(char *, char *, char **); 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); diff --git a/usr/src/cmd/modload/drvsubr.c b/usr/src/cmd/modload/drvsubr.c index d04d914e14..5347a3d32b 100644 --- a/usr/src/cmd/modload/drvsubr.c +++ b/usr/src/cmd/modload/drvsubr.c @@ -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 @@ exec_command(char *path, char *cmdline[MAX_CMD_LINE]) } /* - * 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 @@ config_driver( 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 @@ config_driver( } cmdline[n] = (char *)0; - exec_status = exec_command(DRVCONFIG_PATH, cmdline); + rv = exec_command(DRVCONFIG_PATH, cmdline); + if (rv == NOERR) + return (NOERR); + return (ERROR); +} - if (exec_status == NOERR) +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)); +} + +/* + * 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 @@ aliases_unique(char *aliases) 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 @@ aliases_unique(char *aliases) 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 @@ update_driver_aliases( } +/* + * 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 @@ unique_drv_alias(char *drv_alias) 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 @@ unique_drv_alias(char *drv_alias) 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 @@ unique_drv_alias(char *drv_alias) /* * 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) diff --git a/usr/src/cmd/modload/errmsg.h b/usr/src/cmd/modload/errmsg.h index 53f86a8f10..db8e931b81 100644 --- a/usr/src/cmd/modload/errmsg.h +++ b/usr/src/cmd/modload/errmsg.h @@ -76,12 +76,14 @@ extern "C" { #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" diff --git a/usr/src/cmd/modload/update_drv.c b/usr/src/cmd/modload/update_drv.c index 69651e2f2d..a0527a5787 100644 --- a/usr/src/cmd/modload/update_drv.c +++ b/usr/src/cmd/modload/update_drv.c @@ -343,11 +343,11 @@ main(int argc, char *argv[]) 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 @@ done: } 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) { diff --git a/usr/src/cmd/truss/print.c b/usr/src/cmd/truss/print.c index 8165f64f99..d3b095247a 100644 --- a/usr/src/cmd/truss/print.c +++ b/usr/src/cmd/truss/print.c @@ -1117,6 +1117,7 @@ prt_mod(private_t *pri, int raw, long val) /* print modctl() code */ s = "MODGETDEVFSPATH_MI_LEN"; break; case MODGETDEVFSPATH_MI: s = "MODGETDEVFSPATH_MI"; break; + case MODREMDRVALIAS: s = "MODREMDRVALIAS"; break; } } diff --git a/usr/src/uts/common/os/devcfg.c b/usr/src/uts/common/os/devcfg.c index 9e3ca0116b..de0f8837d3 100644 --- a/usr/src/uts/common/os/devcfg.c +++ b/usr/src/uts/common/os/devcfg.c @@ -4273,44 +4273,150 @@ i_ddi_bind_devs(void) 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) +unbind_children_by_alias(dev_info_t *dip, void *arg) { - int circ; - dev_info_t *cdip; - major_t major = (major_t)(uintptr_t)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); + 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); + } + } + 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. - * 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. + * 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. */ - 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; + 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); } - (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 + * 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); } /* diff --git a/usr/src/uts/common/os/modctl.c b/usr/src/uts/common/os/modctl.c index 4eda8d97ac..b8f4d61378 100644 --- a/usr/src/uts/common/os/modctl.c +++ b/usr/src/uts/common/os/modctl.c @@ -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 char sysbind[] = SYSBINDFILE; 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 @@ modctl_modreserve(modid_t id, int *data) return (0); } +/* 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) +{ + kmem_free(str, strlen(str)+1); +} + +/* Add/Remove driver and binding aliases */ static int -modctl_add_major(int *data) +modctl_update_driver_aliases(int add, int *data) { - struct modconfig mc; - int i, rv; - struct aliases alias; - struct aliases *ap; - char name[MAXMODCONFNAME]; - char cname[MAXMODCONFNAME]; - char *drvname; + 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 @@ modctl_add_major(int *data) #ifdef _SYSCALL32_IMPL else { struct modconfig32 modc32; - if (copyin(data, &modc32, sizeof (struct modconfig32)) != 0) return (EFAULT); else { @@ -506,6 +526,7 @@ modctl_add_major(int *data) 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 @@ modctl_add_major(int *data) * 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; + } - /* - * 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); - rv = make_devname(mc.drvname, mc.major); + /* + * 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 (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); + } + + /* + * Ensure that all nodes are bound to the most appropriate driver + * possible, attempting demotion and rebind when a more appropriate + * driver now exists. + */ + 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 @@ modctl_rem_major(major_t major) (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 @@ modctl_load_drvconf(major_t major) 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 @@ modctl_unload_drvconf(major_t major) if (ret != 0) return (ret); (void) i_ddi_unbind_devs(major); + i_ddi_bind_devs(); return (0); } @@ -2190,8 +2319,8 @@ modctl(int cmd, uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, 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 @@ modctl(int cmd, uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, 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); diff --git a/usr/src/uts/common/os/modsubr.c b/usr/src/uts/common/os/modsubr.c index 3466cedee4..46d0c8dbb4 100644 --- a/usr/src/uts/common/os/modsubr.c +++ b/usr/src/uts/common/os/modsubr.c @@ -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 @@ clear_binding_hash(struct bind **bhash) } } +/* 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; + + 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 */ - hashndx = nm_hash(name); - for (mb = hashtab[hashndx]; mb; mb = mb->b_next) { - if (strcmp(name, mb->b_name) == 0) + /* return if name matches */ + if (strcmp(name, mb->b_name) == 0) { break; + } } - return (mb); } @@ -507,85 +510,110 @@ find_mbind(char *name, struct bind **hashtab) * 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; - - 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; + struct bind *mb; + + for (mb = hashtab[nm_hash(name)]; mb; mb = mb->b_next) { + if ((mb->b_num >= 0) && (strcmp(name, mb->b_name) == 0)) { + /* delete by making b_num negative */ + if (moddebug & MODDEBUG_BINDING) { + cmn_err(CE_CONT, "mbind: %s %d deleted\n", + name, mb->b_num); } - 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; - - if ((mbind = find_mbind(name, mb_hashtab)) != NULL) + struct bind *mbind; + major_t maj; + + /* Search for non-deleted match. */ + if ((mbind = find_mbind(name, mb_hashtab, 0)) != NULL) { + if (moddebug & MODDEBUG_BINDING) { + if (find_mbind(name, mb_hashtab, 1)) + cmn_err(CE_CONT, + "'%s' has deleted match too\n", name); + } return ((major_t)mbind->b_num); + } + + /* + * Search for deleted match: We may find that we have dependencies + * on drivers that have been deleted (but the old driver may still + * be bound to a node). These callers should be converted to use + * ddi_driver_major(i.e. devi_major). + */ + if (moddebug & MODDEBUG_BINDING) { + if ((mbind = find_mbind(name, mb_hashtab, 1)) != NULL) { + maj = (major_t)(-(mbind->b_num)); + cmn_err(CE_CONT, "Reference to deleted alias '%s' %d\n", + name, maj); + } + } return (DDI_MAJOR_T_NONE); } @@ -742,7 +770,7 @@ mod_getsysnum(char *name) { 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); diff --git a/usr/src/uts/common/sys/autoconf.h b/usr/src/uts/common/sys/autoconf.h index babda9d22a..5aa9351981 100644 --- a/usr/src/uts/common/sys/autoconf.h +++ b/usr/src/uts/common/sys/autoconf.h @@ -43,6 +43,7 @@ extern "C" { #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 krwlock_t devinfo_tree_lock; /* obsolete */ 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) diff --git a/usr/src/uts/common/sys/ddi_implfuncs.h b/usr/src/uts/common/sys/ddi_implfuncs.h index 8a2bbca780..fc9a42f587 100644 --- a/usr/src/uts/common/sys/ddi_implfuncs.h +++ b/usr/src/uts/common/sys/ddi_implfuncs.h @@ -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 @@ void i_ddi_set_node_state(dev_info_t *, ddi_node_state_t); 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; diff --git a/usr/src/uts/common/sys/modctl.h b/usr/src/uts/common/sys/modctl.h index ed0811c580..592b3d11fe 100644 --- a/usr/src/uts/common/sys/modctl.h +++ b/usr/src/uts/common/sys/modctl.h @@ -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 @@ struct modlinkage { #define MODUNRETIRE 41 #define MODISRETIRED 42 #define MODDEVEMPTYDIR 43 +#define MODREMDRVALIAS 44 /* * sub cmds for MODEVENTS @@ -315,6 +316,7 @@ struct modconfig { char drvname[MAXMODCONFNAME]; char drvclass[MAXMODCONFNAME]; int major; + int flags; int num_aliases; struct aliases *ap; }; @@ -331,12 +333,16 @@ struct modconfig32 { 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 void mod_rele_dev_by_devi(dev_info_t *); 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 @@ extern int modctl(int, ...); #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 */ |
