Subject: list/suspend/resume w/ drvctl, plus bug fixes
To: None <current-users@netbsd.org>
From: David Young <dyoung@pobox.com>
List: current-users
Date: 12/21/2007 19:11:54
--bCsyhTFzCvuiizWE
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

Here are patches for drvctl(8) / drvctl(4) that let us list the children
of a device (drvctl -l device), suspend a device and its children (drvctl
-S device), resume a device (drvctl -R device), and resume a device and
its children (drvctl -Q device).

I have also made bus re-scanning/re-attachment work a bit better for
the case of cbb0 at pci0.

You may find this useful, as I have, for testing individual device
suspend/resume routines, for suspending/resuming device trees on embedded
systems, and for testing device detachment routines.

drvctl(8) will help test individual suspended drivers for their reaction
to ioctls, sysctls, and such.  It is a no-no for a driver to access
hardware that is suspended!

Dave

-- 
David Young             OJC Technologies
dyoung@ojctech.com      Urbana, IL * (217) 278-3933 ext 24

--bCsyhTFzCvuiizWE
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="drvctl.latest"

Index: sys/sys/drvctlio.h
===================================================================
RCS file: /cvsroot/src/sys/sys/drvctlio.h,v
retrieving revision 1.3
diff -p -u -u -p -r1.3 drvctlio.h
--- sys/sys/drvctlio.h	22 Sep 2006 04:37:37 -0000	1.3
+++ sys/sys/drvctlio.h	22 Dec 2007 00:19:13 -0000
@@ -50,6 +50,21 @@ struct devdetachargs {
 	char devname[16];
 };
 
+struct devlistargs {
+	char l_devname[16];
+	char (*l_childname)[16];
+	size_t l_children;
+};
+
+enum devpmflags {
+	DEVPM_F_SUBTREE = 0x1
+};
+
+struct devpmargs {
+	char devname[16];
+	uint32_t flags;
+};
+
 struct devrescanargs {
 	char busname[16];
 	char ifattr[16];
@@ -59,6 +74,9 @@ struct devrescanargs {
 
 #define DRVDETACHDEV _IOW('D', 123, struct devdetachargs)
 #define DRVRESCANBUS _IOW('D', 124, struct devrescanargs)
+#define DRVSUSPENDDEV _IOW('D', 125, struct devpmargs)
+#define DRVRESUMEDEV _IOW('D', 126, struct devpmargs)
+#define DRVLISTDEV _IOWR('D', 127, struct devlistargs)
 
 /*
  * Generic ioctl that takes a dictionary as an argument (specifies the
Index: sys/kern/kern_drvctl.c
===================================================================
RCS file: /cvsroot/src/sys/kern/kern_drvctl.c,v
retrieving revision 1.11
diff -p -u -u -p -r1.11 kern_drvctl.c
--- sys/kern/kern_drvctl.c	3 Apr 2007 23:02:39 -0000	1.11
+++ sys/kern/kern_drvctl.c	22 Dec 2007 00:19:10 -0000
@@ -48,41 +48,113 @@ const struct cdevsw drvctl_cdevsw = {
 };
 
 void drvctlattach(int);
+static struct device *devbyname(const char *);
 
 #define MAXLOCATORS 100
 
 static int drvctl_command(struct lwp *, struct plistref *, u_long, int flag);
 
+static struct device *
+devbyname(const char *devname)
+{
+	struct device *d;
+
+	TAILQ_FOREACH(d, &alldevs, dv_list) {
+		if (strcmp(devname, device_xname(d)) == 0)
+			break;
+	}
+	return d;
+}
+
+static int
+pmdevbyname(int cmd, struct devpmargs *a)
+{
+	struct device *d;
+
+	if ((d = devbyname(a->devname)) == NULL)
+		return ENXIO;
+
+	switch (cmd) {
+	case DRVSUSPENDDEV:
+		return pmf_device_recursive_suspend(d) ? 0 : EBUSY;
+	case DRVRESUMEDEV:
+		if (a->flags & DEVPM_F_SUBTREE)
+			return pmf_device_resume_subtree(d) ? 0 : EBUSY;
+		else
+			return pmf_device_recursive_resume(d) ? 0 : EBUSY;
+	default:
+		return EPASSTHROUGH;
+	}
+}
+
+struct listdevchild_arg {
+	int			la_error;
+	int			la_idx;
+	struct devlistargs	*la_l;
+};
+
+static bool
+listdevchild(device_t child, void *arg)
+{
+	struct listdevchild_arg *la = arg;
+	struct devlistargs *l = la->la_l;
+	int idx;
+	
+	idx = la->la_idx++;
+
+	aprint_debug_dev(child, "idx %d\n", idx);
+
+	if (l->l_childname == NULL || idx >= l->l_children)
+		return true;
+
+	la->la_error = copyoutstr(device_xname(child), l->l_childname[idx],
+	    sizeof(l->l_childname[idx]), NULL);
+
+	return la->la_error == 0;
+}
+
+static int
+listdevbyname(struct devlistargs *l)
+{
+	struct device *d;
+	struct listdevchild_arg la = {.la_idx = 0, .la_l = l, .la_error = 0};
+
+	if ((d = devbyname(l->l_devname)) == NULL)
+		return ENXIO;
+
+	if (!device_foreach_child(d, listdevchild, &la))
+		return la.la_error;
+
+	l->l_children = la.la_idx;
+	return 0;
+}
+
 static int
 detachdevbyname(const char *devname)
 {
 	struct device *d;
 
-	TAILQ_FOREACH(d, &alldevs, dv_list) {
-		if (!strcmp(devname, d->dv_xname)) {
+	if ((d = devbyname(devname)) == NULL)
+		return ENXIO;
+
 #ifndef XXXFULLRISK
-			/*
-			 * If the parent cannot be notified, it might keep
-			 * pointers to the detached device.
-			 * There might be a private notification mechanism,
-			 * but better play save here.
-			 */
-			if (d->dv_parent &&
-			    !d->dv_parent->dv_cfattach->ca_childdetached)
-				return (ENOTSUP);
+	/*
+	 * If the parent cannot be notified, it might keep
+	 * pointers to the detached device.
+	 * There might be a private notification mechanism,
+	 * but better play save here.
+	 */
+	if (d->dv_parent && !d->dv_parent->dv_cfattach->ca_childdetached)
+		return (ENOTSUP);
 #endif
-			return (config_detach(d, 0));
-		}
-	}
-
-	return (ENXIO);
+	return (config_detach(d, 0));
 }
 
 static int
 rescanbus(const char *busname, const char *ifattr,
 	  int numlocators, const int *locators)
 {
-	int i;
+	int i, rc;
 	struct device *d;
 	const struct cfiattrdata * const *ap;
 
@@ -95,35 +167,34 @@ rescanbus(const char *busname, const cha
 	for (i = 0; i < numlocators;i++)
 		locs[i] = locators[i];
 
-	TAILQ_FOREACH(d, &alldevs, dv_list) {
-		if (!strcmp(busname, d->dv_xname)) {
-			/*
-			 * must support rescan, and must have something
-			 * to attach to
-			 */
-			if (!d->dv_cfattach->ca_rescan ||
-			    !d->dv_cfdriver->cd_attrs)
-				return (ENODEV);
-
-			/* allow to omit attribute if there is exactly one */
-			if (!ifattr) {
-				if (d->dv_cfdriver->cd_attrs[1])
-					return (EINVAL);
-				ifattr = d->dv_cfdriver->cd_attrs[0]->ci_name;
-			} else {
-				/* check for valid attribute passed */
-				for (ap = d->dv_cfdriver->cd_attrs; *ap; ap++)
-					if (!strcmp((*ap)->ci_name, ifattr))
-						break;
-				if (!*ap)
-					return (EINVAL);
-			}
+	if ((d = devbyname(busname)) == NULL)
+		return ENXIO;
 
-			return (*d->dv_cfattach->ca_rescan)(d, ifattr, locs);
-		}
+	/*
+	 * must support rescan, and must have something
+	 * to attach to
+	 */
+	if (!d->dv_cfattach->ca_rescan ||
+	    !d->dv_cfdriver->cd_attrs)
+		return (ENODEV);
+
+	/* allow to omit attribute if there is exactly one */
+	if (!ifattr) {
+		if (d->dv_cfdriver->cd_attrs[1])
+			return (EINVAL);
+		ifattr = d->dv_cfdriver->cd_attrs[0]->ci_name;
+	} else {
+		/* check for valid attribute passed */
+		for (ap = d->dv_cfdriver->cd_attrs; *ap; ap++)
+			if (!strcmp((*ap)->ci_name, ifattr))
+				break;
+		if (!*ap)
+			return (EINVAL);
 	}
 
-	return (ENXIO);
+	rc = (*d->dv_cfattach->ca_rescan)(d, ifattr, locs);
+	config_deferred(NULL);
+	return rc;
 }
 
 int
@@ -134,6 +205,15 @@ drvctlioctl(dev_t dev, u_long cmd, void 
 	int *locs;
 
 	switch (cmd) {
+	case DRVSUSPENDDEV:
+	case DRVRESUMEDEV:
+#define d ((struct devpmargs *)data)
+		res = pmdevbyname(cmd, d);
+#undef d
+		break;
+	case DRVLISTDEV:
+		res = listdevbyname((struct devlistargs *)data);
+		break;
 	case DRVDETACHDEV:
 #define d ((struct devdetachargs *)data)
 		res = detachdevbyname(d->devname);
Index: sbin/drvctl/drvctl.c
===================================================================
RCS file: /cvsroot/src/sbin/drvctl/drvctl.c,v
retrieving revision 1.5
diff -p -u -u -p -r1.5 drvctl.c
--- sbin/drvctl/drvctl.c	22 Sep 2006 04:37:36 -0000	1.5
+++ sbin/drvctl/drvctl.c	22 Dec 2007 00:18:56 -0000
@@ -35,7 +35,7 @@
 #include <sys/ioctl.h>
 #include <sys/drvctlio.h>
 
-#define OPTS "dra:p"
+#define OPTS "QRSa:dlpr"
 
 #define	OPEN_MODE(mode)							\
 	(((mode) == 'd' || (mode) == 'r') ? O_RDWR			\
@@ -49,7 +49,12 @@ usage(void)
 
 	fprintf(stderr, "Usage: %s -r [-a attribute] busdevice [locator ...]\n"
 	    "       %s -d device\n"
-	    "       %s -p device\n",
+	    "       %s -l device\n"
+	    "       %s -p device\n"
+	    "       %s -Q device\n"
+	    "       %s -R device\n"
+	    "       %s -S device\n",
+	    getprogname(), getprogname(), getprogname(), getprogname(),
 	    getprogname(), getprogname(), getprogname());
 	exit(1);
 }
@@ -62,16 +67,29 @@ main(int argc, char **argv)
 	extern char *optarg;
 	extern int optind;
 	int fd, res;
+	size_t children;
+	struct devpmargs paa = {.devname = "", .flags = 0};
+	struct devlistargs laa = {.l_devname = "", .l_childname = NULL,
+				  .l_children = 0};
 	struct devdetachargs daa;
 	struct devrescanargs raa;
 	int *locs, i;
+	prop_dictionary_t command_dict, args_dict, results_dict,
+			  data_dict;
+	prop_string_t string;
+	prop_number_t number;
+	char *xml;
 
 	mode = 0;
 	while ((c = getopt(argc, argv, OPTS)) != -1) {
 		switch (c) {
+		case 'Q':
+		case 'R':
+		case 'S':
 		case 'd':
-		case 'r':
+		case 'l':
 		case 'p':
+		case 'r':
 			mode = c;
 			break;
 		case 'a':
@@ -93,13 +111,48 @@ main(int argc, char **argv)
 	if (fd < 0)
 		err(2, "open %s", DRVCTLDEV);
 
-	if (mode == 'd') {
+	switch (mode) {
+	case 'Q':
+		paa.flags = DEVPM_F_SUBTREE;
+		/*FALLTHROUGH*/
+	case 'R':
+		strlcpy(paa.devname, argv[0], sizeof(paa.devname));
+
+		if (ioctl(fd, DRVRESUMEDEV, &paa) == -1)
+			err(3, "DRVRESUMEDEV");
+		break;
+	case 'S':
+		strlcpy(paa.devname, argv[0], sizeof(paa.devname));
+
+		if (ioctl(fd, DRVSUSPENDDEV, &paa) == -1)
+			err(3, "DRVSUSPENDDEV");
+		break;
+	case 'd':
 		strlcpy(daa.devname, argv[0], sizeof(daa.devname));
 
-		res = ioctl(fd, DRVDETACHDEV, &daa);
-		if (res)
+		if (ioctl(fd, DRVDETACHDEV, &daa) == -1)
 			err(3, "DRVDETACHDEV");
-	} else if (mode == 'r') {
+		break;
+	case 'l':
+		strlcpy(laa.l_devname, argv[0], sizeof(laa.l_devname));
+
+		if (ioctl(fd, DRVLISTDEV, &laa) == -1)
+			err(3, "DRVLISTDEV");
+
+		children = laa.l_children;
+
+		laa.l_childname = malloc(children * sizeof(laa.l_childname[0]));
+		if (laa.l_childname == NULL)
+			err(5, "DRVLISTDEV");
+		if (ioctl(fd, DRVLISTDEV, &laa) == -1)
+			err(3, "DRVLISTDEV");
+		if (laa.l_children > children)
+			err(6, "DRVLISTDEV: number of children grew");
+
+		for (i = 0; i < laa.l_children; i++)
+			printf("%s %s\n", laa.l_devname, laa.l_childname[i]);
+		break;
+	case 'r':
 		memset(&raa, 0, sizeof(raa));
 		strlcpy(raa.busname, argv[0], sizeof(raa.busname));
 		if (attr)
@@ -114,15 +167,10 @@ main(int argc, char **argv)
 			raa.locators = locs;
 		}
 
-		res = ioctl(fd, DRVRESCANBUS, &raa);
-		if (res)
+		if (ioctl(fd, DRVRESCANBUS, &raa) == -1)
 			err(3, "DRVRESCANBUS");
-	} else if (mode == 'p') {
-		prop_dictionary_t command_dict, args_dict, results_dict,
-				  data_dict;
-		prop_string_t string;
-		prop_number_t number;
-		char *xml;
+		break;
+	case 'p':
 
 		command_dict = prop_dictionary_create();
 		args_dict = prop_dictionary_create();
@@ -164,8 +212,10 @@ main(int argc, char **argv)
 		printf("Properties for device `%s':\n%s",
 		       argv[0], xml);
 		free(xml);
-	} else
+		break;
+	default:
 		errx(4, "unknown command");
+	}
 
 	return (0);
 }


--bCsyhTFzCvuiizWE--