summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/os/modctl.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/common/os/modctl.c')
-rw-r--r--usr/src/uts/common/os/modctl.c237
1 files changed, 185 insertions, 52 deletions
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);