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.c225
1 files changed, 223 insertions, 2 deletions
diff --git a/usr/src/uts/common/os/modctl.c b/usr/src/uts/common/os/modctl.c
index 31108c215b..1f821fef85 100644
--- a/usr/src/uts/common/os/modctl.c
+++ b/usr/src/uts/common/os/modctl.c
@@ -161,8 +161,6 @@ extern int make_mbind(char *, int, char *, struct bind **);
static int minorperm_loaded = 0;
-
-
void
mod_setup(void)
{
@@ -798,6 +796,217 @@ modctl_getmaj(char *uname, uint_t ulen, int *umajorp)
return (0);
}
+static char **
+convert_constraint_string(char *constraints, size_t len)
+{
+ int i;
+ int n;
+ char *p;
+ char **array;
+
+ ASSERT(constraints != NULL);
+ ASSERT(len > 0);
+
+ for (i = 0, p = constraints; strlen(p) > 0; i++, p += strlen(p) + 1);
+
+ n = i;
+
+ if (n == 0) {
+ kmem_free(constraints, len);
+ return (NULL);
+ }
+
+ array = kmem_alloc((n + 1) * sizeof (char *), KM_SLEEP);
+
+ for (i = 0, p = constraints; i < n; i++, p += strlen(p) + 1) {
+ array[i] = i_ddi_strdup(p, KM_SLEEP);
+ }
+ array[n] = NULL;
+
+ kmem_free(constraints, len);
+
+ return (array);
+}
+/*ARGSUSED*/
+static int
+modctl_retire(char *path, char *uconstraints, size_t ulen)
+{
+ char *pathbuf;
+ char *devpath;
+ size_t pathsz;
+ int retval;
+ char *constraints;
+ char **cons_array;
+
+ if (path == NULL)
+ return (EINVAL);
+
+ if ((uconstraints == NULL) ^ (ulen == 0))
+ return (EINVAL);
+
+ pathbuf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+ retval = copyinstr(path, pathbuf, MAXPATHLEN, &pathsz);
+ if (retval != 0) {
+ kmem_free(pathbuf, MAXPATHLEN);
+ return (retval);
+ }
+ devpath = i_ddi_strdup(pathbuf, KM_SLEEP);
+ kmem_free(pathbuf, MAXPATHLEN);
+
+ /*
+ * First check if the device is already retired.
+ * If it is, this becomes a NOP
+ */
+ if (e_ddi_device_retired(devpath)) {
+ cmn_err(CE_NOTE, "Device: already retired: %s", devpath);
+ kmem_free(devpath, strlen(devpath) + 1);
+ return (0);
+ }
+
+ cons_array = NULL;
+ if (uconstraints) {
+ constraints = kmem_alloc(ulen, KM_SLEEP);
+ if (copyin(uconstraints, constraints, ulen)) {
+ kmem_free(constraints, ulen);
+ kmem_free(devpath, strlen(devpath) + 1);
+ return (EFAULT);
+ }
+ cons_array = convert_constraint_string(constraints, ulen);
+ }
+
+ /*
+ * Try to retire the device first. The following
+ * routine will return an error only if the device
+ * is not retireable i.e. retire constraints forbid
+ * a retire. A return of success from this routine
+ * indicates that device is retireable.
+ */
+ retval = e_ddi_retire_device(devpath, cons_array);
+ if (retval != DDI_SUCCESS) {
+ cmn_err(CE_WARN, "constraints forbid retire: %s", devpath);
+ kmem_free(devpath, strlen(devpath) + 1);
+ return (ENOTSUP);
+ }
+
+ /*
+ * Ok, the retire succeeded. Persist the retire.
+ * If retiring a nexus, we need to only persist the
+ * nexus retire. Any children of a retired nexus
+ * are automatically covered by the retire store
+ * code.
+ */
+ retval = e_ddi_retire_persist(devpath);
+ if (retval != 0) {
+ cmn_err(CE_WARN, "Failed to persist device retire: error %d: "
+ "%s", retval, devpath);
+ kmem_free(devpath, strlen(devpath) + 1);
+ return (retval);
+ }
+ if (moddebug & MODDEBUG_RETIRE)
+ cmn_err(CE_NOTE, "Persisted retire of device: %s", devpath);
+
+ kmem_free(devpath, strlen(devpath) + 1);
+ return (0);
+}
+
+static int
+modctl_is_retired(char *path, int *statep)
+{
+ char *pathbuf;
+ char *devpath;
+ size_t pathsz;
+ int error;
+ int status;
+
+ if (path == NULL || statep == NULL)
+ return (EINVAL);
+
+ pathbuf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+ error = copyinstr(path, pathbuf, MAXPATHLEN, &pathsz);
+ if (error != 0) {
+ kmem_free(pathbuf, MAXPATHLEN);
+ return (error);
+ }
+ devpath = i_ddi_strdup(pathbuf, KM_SLEEP);
+ kmem_free(pathbuf, MAXPATHLEN);
+
+ if (e_ddi_device_retired(devpath))
+ status = 1;
+ else
+ status = 0;
+ kmem_free(devpath, strlen(devpath) + 1);
+
+ return (copyout(&status, statep, sizeof (status)) ? EFAULT : 0);
+}
+
+static int
+modctl_unretire(char *path)
+{
+ char *pathbuf;
+ char *devpath;
+ size_t pathsz;
+ int retired;
+ int retval;
+
+ if (path == NULL)
+ return (EINVAL);
+
+ pathbuf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+ retval = copyinstr(path, pathbuf, MAXPATHLEN, &pathsz);
+ if (retval != 0) {
+ kmem_free(pathbuf, MAXPATHLEN);
+ return (retval);
+ }
+ devpath = i_ddi_strdup(pathbuf, KM_SLEEP);
+ kmem_free(pathbuf, MAXPATHLEN);
+
+ /*
+ * We check if a device is retired (first) before
+ * unpersisting the retire, because we use the
+ * retire store to determine if a device is retired.
+ * If we unpersist first, the device will always appear
+ * to be unretired. For the rationale behind unpersisting
+ * a device that is not retired, see the next comment.
+ */
+ retired = e_ddi_device_retired(devpath);
+
+ /*
+ * We call unpersist unconditionally because the lookup
+ * for retired devices (e_ddi_device_retired()), skips "bypassed"
+ * devices. We still want to be able remove "bypassed" entries
+ * from the persistent store, so we unpersist unconditionally
+ * i.e. whether or not the entry is found on a lookup.
+ *
+ * e_ddi_retire_unpersist() returns 1 if it found and cleared
+ * an entry from the retire store or 0 otherwise.
+ */
+ if (e_ddi_retire_unpersist(devpath))
+ if (moddebug & MODDEBUG_RETIRE) {
+ cmn_err(CE_NOTE, "Unpersisted retire of device: %s",
+ devpath);
+ }
+
+ /*
+ * Check if the device is already unretired. If so,
+ * the unretire becomes a NOP
+ */
+ if (!retired) {
+ cmn_err(CE_NOTE, "Not retired: %s", devpath);
+ kmem_free(devpath, strlen(devpath) + 1);
+ return (0);
+ }
+
+ retval = e_ddi_unretire_device(devpath);
+ if (retval != 0) {
+ cmn_err(CE_WARN, "cannot unretire device: error %d, path %s\n",
+ retval, devpath);
+ }
+
+ kmem_free(devpath, strlen(devpath) + 1);
+
+ return (retval);
+}
+
static int
modctl_getname(char *uname, uint_t ulen, int *umajorp)
{
@@ -2069,6 +2278,18 @@ modctl(int cmd, uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4,
error = modctl_moddevname((int)a1, a2, a3);
break;
+ case MODRETIRE: /* retire device named by physpath a1 */
+ error = modctl_retire((char *)a1, (char *)a2, (size_t)a3);
+ break;
+
+ case MODISRETIRED: /* check if a device is retired. */
+ error = modctl_is_retired((char *)a1, (int *)a2);
+ break;
+
+ case MODUNRETIRE: /* unretire device named by physpath a1 */
+ error = modctl_unretire((char *)a1);
+ break;
+
default:
error = EINVAL;
break;