[LWN Logo]
[Timeline]
Date: Fri, 3 Nov 2000 04:21:45 -0800
From: "Adam J. Richter" <adam@yggdrasil.com>
To: sailer@ife.ee.ethz.ch, linux-usb-devel@lists.sourceforge.net
Subject: [linux-usb-devel] Patch/Announce: usbmodules program


--ikeVEW9yuYc//A+q
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline


	usbmodules is an analog to my pcimodules program.  It prints
the list of kernel modules that are relevant to a particular USB
device specified, like so:

		usbmodules --device /proc/bus/usb/NNN/NNN

	(NNN/NNN are the bus and device number).  

	It is intended to replace the part of /etc/usb/policy that
parses /lib/modules/<version>/modules.usbmap.  That code would be
repaced with something like:

		for module in $(usbmodules --device $DEVICE) ; do
			modprobe -s -k $module
		done

	(The DEVICE environment variable is passed to hotplug and
then to /etc/usb/policy by the kernel.)

	usbmodules has the advantage that it examines *all* interface
descriptors to determine a complete list of potentially relevant
modules, not just the one associated with interface 0, alternate
setting 0.  usbmodules lists all modules that have expressed an
interest in the device through their DEVICE_MODULE_TABLE entry.

	usbmodules will eventually enable deletion of a section of
code in drivers/usb/usb.c that passes a lot of additional information
in environment variables to hotplug.

	The one big disadvantage of usbmodules right now is that
the ioctl that it relies on is flakey, at least under the two
uhci controllers that I tried.  On one machine, I was able to make
the problems go away by having usbioctl retry up to one hundred
times (this does not take long).  On a notebook that I have, which
appears to have an IRQ problem, this was not sufficient.  However,
once the uhci problems are resolved, I expect this to be used in
/etc/usb/policy.

	In the meantime, I am submitting it for inclusion in usbutils
if you will accept it, Thomas.  And, by the way, thank you for writing
such useful stuff as usbutils.  To write this program, I started with
lsusb.c and edited away at it.

-- 
Adam J. Richter     __     ______________   4880 Stevens Creek Blvd, Suite 104
adam@yggdrasil.com     \ /                  San Jose, California 95129-1034
+1 408 261-6630         | g g d r a s i l   United States of America
fax +1 408 261-6631      "Free Software For The Rest Of Us."

--ikeVEW9yuYc//A+q
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="diffs.usbutils"

diff -u -r --new-file usbutils-0.6/Makefile.am usbutils/Makefile.am
--- usbutils-0.6/Makefile.am	Wed Dec 22 07:12:37 1999
+++ usbutils/Makefile.am	Fri Nov  3 04:03:25 2000
@@ -4,12 +4,12 @@
 
 sbin_PROGRAMS = lsusb
 
-noinst_HEADERS = names.h usb.h devtree.h list.h
+noinst_HEADERS = names.h usb.h devtree.h list.h usbmodules.h
 
 lsusb_SOURCES = lsusb.c names.c devtree.c
 
 data_DATA = usb.ids
 
-man_MANS = lsusb.8
+man_MANS = lsusb.8 usbmodules.8
 
 EXTRA_DIST = $(man_MANS) $(data_DATA) usbutils.spec
diff -u -r --new-file usbutils-0.6/usbmodules.8 usbutils/usbmodules.8
--- usbutils-0.6/usbmodules.8	Wed Dec 31 16:00:00 1969
+++ usbutils/usbmodules.8	Fri Nov  3 03:58:25 2000
@@ -0,0 +1,78 @@
+.TH usbmodules 8 "03 November 2000" "usbutils-0.6" "Linux USB Utilities"
+.IX usbmodules
+.SH NAME
+usbmodules \- List kernel driver modules available for all currently plugged
+in USB devices
+.SH SYNOPSIS
+.B usbmodules
+.RB [ --device /proc/bus/bus/NNN/NNN ]
+.RB [ --help ]
+.SH DESCRIPTION
+.B usbmodules
+lists all driver modules for all currently plugged in USB devices.
+.B usbmodules
+should be run at by /sbin/hotplug of /etc/usb/policy, and whenever
+a USB device is "hot plugged" into the system.  This can be done by
+the following Bourne shell syntax:
+.IP
+	for module in $(usbmodules --device $DEVICE) ; do
+.IP
+		modprobe -s -k "$module"
+.IP
+	done
+.PP
+The DEVICE environment variable is passed from the kernel to /sbin/hotplug.
+.PP
+When a USB device is removed from the system, the Linux kernel will
+decrement a usage count on USB driver module.  If this count drops
+to zero (i.e., there are no clients for the USB device driver), then the
+.B modprobe -r
+process that is normally configured to run from cron every few minutes
+will eventually remove the unneeded module.
+.PP
+.SH OPTIONS
+.TP
+.B --device /proc/bus/usb/NNN/NNN
+Selects which device
+.B usbmodules
+will examine.  The argument is currently mandatory.
+.SH FILES
+.TP
+.B --help, -h
+Print a help message
+.SH FILES
+.TP
+.B /lib/modules/<kernel-version>/modules.usbmap
+This file is automatically generated by
+.B depmod,
+and used by
+.B usbmodules
+to determine which modules correspond to which USB ID's.
+.TP
+.B /proc/bus/usb
+An interface to USB bus configuration space provided by the post-2.1.82 Linux
+kernels. Contains per-bus subdirectories with per-card config space files and a
+.I devices
+file containing a list of all USB devices.
+
+.SH SEE ALSO
+.BR lsusb (8)
+
+.SH AUTHOR
+.B usbmodules
+was written by Adam J. Richter <adam@yggdrasil.com>, and is
+based partly on
+.B
+lsusb,
+which was written by Thomas Sailer <sailer@ife.ee.ethz.ch>.
+
+
+.SH COPYRIGHT
+.B usbmodules
+is copyright 2000, Yggdrasil Computing, Incorporated, and
+copyright 1999, Thomas Sailer.
+.B usbmodules
+may
+may be copied under the terms and conditions of version 2 of the GNU
+General Public License as published by the Free Software Foundation
+(Cambrige, Massachusetts, United States of America).
diff -u -r --new-file usbutils-0.6/usbmodules.c usbutils/usbmodules.c
--- usbutils-0.6/usbmodules.c	Wed Dec 31 16:00:00 1969
+++ usbutils/usbmodules.c	Fri Nov  3 03:57:19 2000
@@ -0,0 +1,386 @@
+/*****************************************************************************/
+
+/*
+ *      usmodules.c  --  pcimodules like utility for the USB bus
+ *	
+ *	Written by primarily by Adam J. Richter.  lspci.c is derived from:
+ *      lsusb.c, written by Thomas Sailer, and pcimodules.c, which is
+ *	also written by Adam J. Richter.  usbmodules.h is derived from
+ *	linux-2.4.0-test10/include/linux/usb.h (exact authorship unknown,
+ *	probably Randy Dunlap).
+ *
+ *      Copyright (C) 2000  Yggdrasil Computing, Inc.
+ *      Copyright (C) 1999  Thomas Sailer (sailer@ife.ee.ethz.ch)
+ *
+ *      This program is free software; you can redistribute it and/or modify
+ *      it under the terms of the GNU General Public License as published by
+ *      the Free Software Foundation; either version 2 of the License, or
+ *      (at your option) any later version.
+ *
+ *      This program is distributed in the hope that it will be useful,
+ *      but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *      GNU General Public License for more details.
+ *
+ *      You should have received a copy of the GNU General Public License
+ *      along with this program; if not, write to the Free Software
+ *      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *
+ */
+
+/*****************************************************************************/
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/param.h>
+#include <sys/utsname.h>
+
+#ifdef HAVE_LINUX_USB_H
+#include <linux/usb.h>
+#else
+#include "usb.h"
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "usbmodules.h"
+#include "names.h"
+#include "devtree.h"
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#define OPT_STRING "d:h"
+static struct option long_options[] = {
+	{"help",	no_argument, 		NULL,	'h'},
+	{"device",	required_argument,	NULL,	'd'},
+	{ 0, 		0,			NULL,	0}
+};
+
+#define MODDIR	"/lib/modules"
+#define USBMAP	"modules.usbmap"
+
+#define LINELENGTH	8000 
+
+
+#define USB_DT_CS_DEVICE	       	0x21
+#define USB_DT_CS_CONFIG	       	0x22
+#define USB_DT_CS_STRING		0x23
+#define USB_DT_CS_INTERFACE		0x24
+#define USB_DT_CS_ENDPOINT		0x25
+
+static const char *procbususb = "/proc/bus/usb";
+
+static int idVendor;
+static int idProduct;
+static int bcdDevice;
+static int bDeviceClass;
+static int bDeviceSubClass;
+static int bDeviceProtocol;
+struct usbmap_entry *usbmap_list;
+
+static void *
+xmalloc(unsigned int size) {
+	void *result = malloc(size);
+	if (result == NULL) {
+		fprintf(stderr, "Memory allocation failure.\n");
+		exit(1);
+	}
+}
+
+void
+read_modules_usbmap(void)
+{
+	struct utsname utsname;
+	char filename[MAXPATHLEN];
+	FILE *usbmap_file;
+	char line[LINELENGTH];
+	struct usbmap_entry *prev;
+	struct usbmap_entry *entry;
+	unsigned int driver_data;
+	char name[LINELENGTH];
+
+	if (uname(&utsname) < 0) {
+		perror("uname");
+		exit(1);
+	}
+	sprintf(filename, "%s/%s/%s", MODDIR, utsname.release, USBMAP);
+	if ((usbmap_file = fopen(filename, "r")) == NULL) {
+		perror(filename);
+		exit(1);
+	}
+
+	prev = NULL;
+	while(fgets(line, LINELENGTH, usbmap_file) != NULL) {
+		if (line[0] == '#')
+			continue;
+
+		entry = xmalloc(sizeof(struct usbmap_entry));
+
+		if (sscanf(line, "%s 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x",
+			   name,
+			   &entry->idVendor,
+			   &entry->idProduct,
+			   &entry->bcdDevice_lo,
+			   &entry->bcdDevice_hi,
+			   &entry->bDeviceClass,
+			   &entry->bDeviceSubClass,
+			   &entry->bDeviceProtocol,
+			   &entry->bInterfaceClass,
+			   &entry->bInterfaceSubClass,
+			   &entry->bInterfaceProtocol) != 10) {
+			fprintf (stderr,
+				"modules.usbmap unparsable line: %s.\n", line);
+			free(entry);
+			continue;
+		}
+
+		/* Optimize memory allocation a bit, in case someday we
+		   have Linux systems with ~100,000 modules.  It also
+		   allows us to just compare pointers to avoid trying
+		   to load a module twice. */
+		if (prev == NULL || strcmp(name, prev->name) != 0) {
+			entry->name = xmalloc(strlen(name)+1);
+			strcpy(entry->name, name);
+			entry->selected_ptr = &entry->selected;
+			entry->selected = 0;
+			prev = entry;
+		} else {
+			entry->name = prev->name;
+			entry->selected_ptr = prev->selected_ptr;
+		}
+		entry->next = usbmap_list;
+		usbmap_list = entry;
+	}
+	fclose(usbmap_file);
+}
+
+/* Match modules is called once per interface.  We know that
+   each device has at least one interface, because, according
+   to the USB 2.0 Specification, section 9.6.3, "A USB device has
+   one or more configuration descriptors.  Each configuration has
+   one or more interfaces and each interface has zero or more endpoints."
+   So, there must be at least one interface on a device.
+*/
+
+static void
+match_modules(int bInterfaceClass,
+	     int bInterfaceSubClass,
+	     int bInterfaceProtocol)
+{
+	struct usbmap_entry *mod;
+
+	for (mod = usbmap_list; mod != NULL; mod = mod->next) {
+
+		if (mod->idVendor &&
+		    mod->idVendor != idVendor)
+			continue;
+
+	    	if (mod->idProduct &&
+        	    mod->idProduct != idProduct)
+			continue;
+
+		/* No need to test mod->bcdDevice_lo != 0, since 0 is never
+		   greater than any unsigned number. */
+		if (mod->bcdDevice_lo > bcdDevice)
+			continue;
+
+		if (mod->bcdDevice_hi &&
+		    mod->bcdDevice_hi < bcdDevice)
+			continue;
+
+	    	if (mod->bDeviceClass &&
+		    mod->bDeviceClass != bDeviceClass)
+			continue;
+
+		if (mod->bDeviceSubClass &&
+		    mod->bDeviceSubClass!= bDeviceClass)
+			continue;
+
+		if (mod->bDeviceProtocol &&
+		    mod->bDeviceProtocol != bDeviceProtocol)
+			continue;
+
+		if (mod->bInterfaceClass
+		    && mod->bInterfaceClass != bInterfaceClass)
+			continue;
+
+		if (mod->bInterfaceSubClass &&
+		    mod->bInterfaceSubClass != bInterfaceSubClass)
+		    continue;
+
+		if (mod->bInterfaceProtocol
+		    && mod->bInterfaceProtocol != bInterfaceProtocol)
+		    continue;
+
+		if (!(*mod->selected_ptr)) {
+			*(mod->selected_ptr) = 1;
+			printf ("%s\n", mod->name);
+		}
+	}
+}
+
+static int usb_control_msg(int fd,
+			   u_int8_t requesttype,
+			   u_int8_t request,
+			   u_int16_t value,
+			   u_int16_t index,
+			   unsigned int size,
+			   void *data)
+{
+	int result;
+	int try;
+
+	struct usb_proc_ctrltransfer ctrl;
+
+	ctrl.requesttype = requesttype;
+	ctrl.request = request;
+	ctrl.value = value;
+	ctrl.index = index;
+	ctrl.length = size;
+	ctrl.data = data;
+
+	/* At least on UHCI controllers, this ioctl gets a lot of
+	   ETIMEDOUT errors which can often be retried with success
+	   one is persistent enough.  So, we try 100 times, which work
+  	   on one machine, but not on my notebook computer.
+	   --Adam J. Richter (adam@yggdrasil.com) 2000 November 03. */
+
+	try = 0;
+	do {
+	  	result = ioctl(fd, USB_PROC_CONTROL, &ctrl);
+		try++;
+	} while (try < 100 && result == -1 && errno == ETIMEDOUT);
+	if (result < 0)
+		perror("USB_PROC_CONTROL");
+	return result;
+}
+
+
+static void do_config(int fd, unsigned int nr)
+{
+	unsigned char buf[1024], *p;
+	unsigned int sz;
+	int bInterfaceClass;
+	int bInterfaceSubClass;
+	int bInterfaceProtocol;
+
+	if (usb_control_msg(fd, USB_DIR_IN, USB_REQ_GET_DESCRIPTOR,
+			    (USB_DT_CONFIG << 8) | nr,
+			    0, USB_DT_CONFIG_SIZE, buf) < 0) {
+		fprintf(stderr ,"cannot get config descriptor %d, %s (%d)\n",
+			nr, strerror(errno), errno);
+		return;
+	}
+	if (buf[0] < USB_DT_CONFIG_SIZE || buf[1] != USB_DT_CONFIG)
+		fprintf(stderr, "Warning: invalid config descriptor\n");
+	sz = buf[2] | buf[3] << 8;
+	if (sz > sizeof(buf)) {
+		fprintf(stderr,
+			"Config %d descriptor too long, truncating\n", nr);
+		sz = sizeof(buf);
+	}
+	if (usb_control_msg(fd, USB_DIR_IN, USB_REQ_GET_DESCRIPTOR,
+			    (USB_DT_CONFIG << 8) | nr, 0, sz, buf) < 0) {
+		fprintf(stderr, "cannot get config descriptor %d, %s (%d)\n",
+		        nr, strerror(errno), errno);
+		return;
+	}
+	p = buf;
+	while (sz >= 2 && p[0] >= 2 && p[0] < sz) {
+		if (p[1] == USB_DT_INTERFACE) {
+			const int intClass = p[5];
+			const int intSubClass = p[6];
+			const int intProto = p[7];
+			match_modules(intClass, intSubClass, intProto);
+		}
+		sz -= p[0];
+		p += p[0];
+	}
+}
+
+static void process_device(const char *path)
+{
+        unsigned char buf[USB_DT_DEVICE_SIZE];
+	unsigned int vid, pid;
+	char vendor[128], product[128];
+        int fd;
+ 	unsigned int i, maxcfg;
+
+	if ((fd = open(path, O_RDWR)) == -1) {
+		fprintf(stderr, "cannot open %s, %s (%d)\n",
+			path, strerror(errno), errno);
+		return;
+	}
+	if (usb_control_msg(fd, USB_DIR_IN, USB_REQ_GET_DESCRIPTOR,
+			    (USB_DT_DEVICE << 8), 0, USB_DT_DEVICE_SIZE, buf)
+	    < 0) {
+		perror("cannot get config descriptor");
+		goto err;
+	}
+	bDeviceClass = buf[4];
+	bDeviceSubClass = buf[5];
+	bDeviceProtocol = buf[6];
+	idVendor = buf[8] | (buf[9] << 8);
+	idProduct = buf[10] | (buf[11] << 8);
+	bcdDevice = buf[12] | (buf[13] << 8);
+
+	maxcfg = buf[17];
+	if (buf[0] < 18 || buf[1] != USB_DT_DEVICE)
+		maxcfg = 1;
+
+	for (i = 0; i < maxcfg; i++)
+		do_config(fd, i);
+ err:
+	close(fd);
+}
+
+
+int
+main (int argc, char **argv)
+{
+	int opt_index = 0;
+	int opt;
+	char *device = NULL;
+
+	while ((opt = getopt_long(argc, argv, OPT_STRING, long_options,
+		           &opt_index)) != -1) {
+		switch(opt) {
+			case 'd':
+				device = optarg;
+				break;
+			case 'h':
+				printf ("Usage: pcimodules [--help]\n"
+	"  Lists kernel modules corresponding to PCI devices currently plugged"
+					"  into the computer.\n");
+				return 0;
+			default:
+				fprintf(stderr,
+					"Unknown argument character \"%c\".\n",
+					opt);
+				return 1;
+		}
+	}	
+
+	if (device == NULL) {
+		fprintf (stderr,
+			 "You must specify a device with something like:\n"
+			 "\tusbmodules --device /proc/bus/usb/001/009\n");
+		return 1;
+	}
+	read_modules_usbmap();
+	process_device(device);
+	return 0;
+}
diff -u -r --new-file usbutils-0.6/usbmodules.h usbutils/usbmodules.h
--- usbutils-0.6/usbmodules.h	Wed Dec 31 16:00:00 1969
+++ usbutils/usbmodules.h	Fri Nov  3 02:09:39 2000
@@ -0,0 +1,39 @@
+/* Declaring the usb_device_id fields as unsigned int simplifies
+   the sscanf call. */
+
+struct usbmap_entry {
+	/*
+	 * vendor/product codes are checked, if vendor is nonzero
+	 * Range is for device revision (bcdDevice), inclusive;
+	 * zero values here mean range isn't considered
+	 */
+	unsigned int		idVendor;
+	unsigned int		idProduct;
+	unsigned int		bcdDevice_lo, bcdDevice_hi;
+
+	/*
+	 * if device class != 0, these can be match criteria;
+	 * but only if this bDeviceClass value is nonzero
+	 */
+	unsigned int		bDeviceClass;
+	unsigned int		bDeviceSubClass;
+	unsigned int		bDeviceProtocol;
+
+	/*
+	 * if interface class != 0, these can be match criteria;
+	 * but only if this bUnsigned LongerfaceClass value is nonzero
+	 */
+	unsigned int		bInterfaceClass;
+	unsigned int		bInterfaceSubClass;
+	unsigned int		bInterfaceProtocol;
+
+	/*
+	 * for driver's use; not involved in driver matching.
+	 */
+	unsigned long	driver_info;
+
+	char *name;
+	int selected;
+	int *selected_ptr;
+	struct usbmap_entry *next;
+};

--ikeVEW9yuYc//A+q--
_______________________________________________
linux-usb-devel@lists.sourceforge.net
To unsubscribe, use the last form field at:
http://lists.sourceforge.net/mailman/listinfo/linux-usb-devel