Add event queue handling. It triggers activities on events read from

/dev/usb. The actions are specified in the file /etc/usbd.conf.

usbd.c:
   - Add event queue (/dev/usb) handling.
   - Add comments
   - Clean up code some more

usbd.8:
   - Update manpage for the new command line flags
   - Remove a duplicate FreeBSD tag from it).

usbd.conf, usbd.conf.5, Makefile:
   - Add the usbd.conf configuration file and the man page for it.

NOTE: MAKEDEV already creates the /dev/usb device tree node, no change
needed there anymore.
This commit is contained in:
Nick Hibma 1999-11-21 17:44:43 +00:00
parent 3cc1fd63ff
commit 7183c3f848
5 changed files with 984 additions and 59 deletions

35
etc/usbd.conf Normal file
View File

@ -0,0 +1,35 @@
# Configuration file the USB daemon.
#
# See usbd.conf(5) for the description of the format of the file.
# Firmware download into the ActiveWire board. After the firmware download is done
# the device detaches and reappears as something new and shiny.
#
device "ActiveWire board, firmware download"
product 0x0100
vendor 0x0854
release 0x0000
attach "/usr/local/bin/ezdownload -f /usr/local/share/usb/firmware/0854.0100.0_01.hex"
# The piece below has to be copied for every drive. It does not work for the
# generic case, the umass storage class uses interface drivers. The info for
# the interfaces is not yet exported.
#
device "USB Zip drive"
product 0x0001
vendor 0x059b
release 0x0100
attach "/usr/bin/camcontrol rescan bus 0"
# The entry below is for the Logitech mouse. Replace the product and vendor
# id (and the device name of course) with the data for your mouse.
#
device "Logitech N48 USB mouse"
product 0xc001
vendor 0x046d
attach "/usr/sbin/moused -p /dev/ums0 -I /var/run/moused.ums0.pid"
# The fallthrough entry: Nothing is specified, nothing is done. And it isn't
# necessary at all :-). Just for pretty printing in debugging mode.
#
device "USB device"

View File

@ -2,6 +2,7 @@
PROG= usbd
MAN8= usbd.8
MAN5= usbd.conf.5
CFLAGS+=-I${.CURDIR}/../../sys
.include <bsd.prog.mk>

View File

@ -1,5 +1,4 @@
.\" $NetBSD: usbd.8,v 1.2 1998/07/13 11:01:50 augustss Exp $
.\" $FreeBSD$
.\" Copyright (c) 1998 The NetBSD Foundation, Inc.
.\" All rights reserved.
.\"
@ -53,6 +52,9 @@ handles the USB device attachment and detachment.
.Pp
The options are as follows:
.Bl -tag -width Ds
.It Fl c Ar filename
Name of configuration file. The default is
.Pa /etc/usbd.conf.
.It Fl d
Enable debugging to the standard output,
and do not disassociate from the controlling terminal.
@ -62,22 +64,47 @@ Do one device tree exploration and then exit.
Specify the pathname of a USB controller device file.
The flag may be repeated to watch more than one USB controller.
The default is
.Pa /dev/usb0 ,
.Pa /dev/usb1 ,
.Pa /dev/usb2 ,
and
.Pa /dev/usb0
through
.Pa /dev/usb3 .
Do not specify the device
.Pa /dev/usb
here. It is used for events only.
.It Fl n
Do not handle the event queue on /dev/usb.
.It Fl t Ar timeout
Set the timeout interval (in seconds) before an exploration happens
without being triggered by a connect or disconnect.
A timeout of 0 means that there is no timeout. The default is 30.
.It Fl v
Be verbose.
Be verbose. Repeating the flag makes
.Nm usbd
more verbose.
.El
.Sh FILES
.Bl -tag -width /etc/usbd.conf -compact
.It Pa /etc/usbd.conf
.It /dev/usb
.It /dev/usb0
.It /dev/usb1
.It etc.
.Sh SEE ALSO
.Xr usb 4
.Xr usb 4 ,
.Xr usbd.conf 8
.Sh HISTORY
The
.Nm
command appeared in
.Nx 1.4 .
.Nx 4.0 .
.Sh AUTHORS
The
.Nm
driver was written by
.An Lennart Augustsson Aq augustss@carlstedt.se
for the
.Nx
project. The event queue handling in
.Nm usbd
was added by
.An Nick Hibma Aq n_hibma@freebsd.org .

View File

@ -37,55 +37,730 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <err.h>
/* USBD creates 'threads' in the kernel, used for doing discovery when a
* device has attached or detached. This functionality should be removed
* once kernel threads have been added to the kernel.
* It also handles the event queue, and executing commands based on those
* events.
*
* See usbd(8).
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>
#include <signal.h>
#include <paths.h>
#include <sys/types.h>
#include <sys/time.h>
#if defined(__FreeBSD__)
#include <sys/ioctl.h>
#endif
#include <sys/errno.h>
#include <sys/queue.h>
#include <sys/wait.h>
#include <dev/usb/usb.h>
/* default name of configuration file
*/
#define CONFIGFILE "/etc/usbd.conf"
/* the name of the device spitting out usb attach/detach events as well as
* the prefix for the individual busses (used as a semi kernel thread).
*/
#define USBDEV "/dev/usb"
#define USBDEV "/dev/usb"
/* Maximum number of USB busses expected to be in a system
* XXX should be replaced by dynamic allocation.
*/
#define MAXUSBDEV 4
#define MAXUSBDEV 4
/*
* Sometimes a device does not respond in time for interrupt
/* Sometimes a device does not respond in time for interrupt
* driven explore to find it. Therefore we run an exploration
* at regular intervals to catch those.
*/
#define TIMEOUT 30
#define TIMEOUT 30
/* The wildcard used in actions for strings and integers
*/
#define WILDCARD_STRING NULL
#define WILDCARD_INT -1
extern char *__progname;
extern char *__progname; /* name of program */
char *configfile = CONFIGFILE; /* name of configuration file */
char *devs[MAXUSBDEV]; /* device names */
int fds[MAXUSBDEV]; /* file descriptors for USBDEV\d+ */
int ndevs = 0; /* number of entries in fds / devs */
int fd = -1; /* file descriptor for USBDEV */
int lineno;
int verbose = 0; /* print message on what it is doing */
typedef struct event_name_s {
int type; /* event number (from usb.h) */
char *name;
} event_name_t;
event_name_t event_names[] = {
{USB_EVENT_ATTACH, "attach"},
{USB_EVENT_DETACH, "detach"},
{0, NULL} /* NULL indicates end of list, not 0 */
};
#define DEVICE_FIELD 0 /* descriptive field */
#define PRODUCT_FIELD 1 /* selective fields */
#define VENDOR_FIELD 2
#define RELEASE_FIELD 3
#define CLASS_FIELD 4
#define SUBCLASS_FIELD 5
#define PROTOCOL_FIELD 6
#define ATTACH_FIELD 8 /* command fields */
#define DETACH_FIELD 9
typedef struct action_s {
char *name; /* descriptive string */
int product; /* selection criteria */
int vendor;
int release;
int class;
int subclass;
int protocol;
char *attach; /* commands to execute */
char *detach;
STAILQ_ENTRY(action_s) next;
} action_t;
STAILQ_HEAD(action_list, action_s) actions = STAILQ_HEAD_INITIALIZER(actions);
/* the function returns 0 for failure, 1 for all arguments found and 2 for
* arguments left over in trail.
*/
typedef int (*config_field_fn) __P((action_t *action, char *args,
char **trail));
int set_device_field(action_t *action, char *args, char **trail);
int set_product_field(action_t *action, char *args, char **trail);
int set_vendor_field(action_t *action, char *args, char **trail);
int set_release_field(action_t *action, char *args, char **trail);
int set_class_field(action_t *action, char *args, char **trail);
int set_subclass_field(action_t *action, char *args, char **trail);
int set_protocol_field(action_t *action, char *args, char **trail);
int set_attach_field(action_t *action, char *args, char **trail);
int set_detach_field(action_t *action, char *args, char **trail);
/* the list of fields supported in an entry */
typedef struct config_field_s {
int event;
char *name;
config_field_fn function;
} config_field_t;
config_field_t config_fields[] = {
{DEVICE_FIELD, "device", set_device_field},
{PRODUCT_FIELD, "product", set_product_field},
{VENDOR_FIELD, "vendor", set_vendor_field},
{RELEASE_FIELD, "release", set_release_field},
{CLASS_FIELD, "class", set_class_field},
{SUBCLASS_FIELD, "subclass", set_subclass_field},
{PROTOCOL_FIELD, "protocol", set_protocol_field},
{ATTACH_FIELD, "attach", set_attach_field},
{DETACH_FIELD, "detach", set_detach_field},
{0, NULL, NULL} /* NULL is EOL marker, not the 0 */
};
/* prototypes for some functions */
void print_event __P((struct usb_event *event));
void print_action __P((action_t *action, int i));
void print_actions __P((void));
action_t *find_action __P((struct usb_device_info *devinfo));
void usage(void);
void
usage(void)
{
fprintf(stderr, "Usage: %s [-d] [-e] [-f dev] [-t timeout] [-v]\n",
fprintf(stderr, "Usage: %s [-d] [-v] [-t timeout] [-e] [-f dev]\n"
" [-n] [-c config]\n",
__progname);
fprintf(stderr, " -d for debugging\n");
fprintf(stderr, " -e only do 1 explore\n");
fprintf(stderr, " -f dev for example /dev/usb0, "
"and can be specified multiple times.");
fprintf(stderr, " -t timeout timeout between explores\n");
fprintf(stderr, " -v verbose output\n");
exit(1);
}
/* generic helper functions for the functions to set the fields of actions */
int
get_string(char *src, char **rdst, char **rsrc)
{
/* Takes the first string from src, taking quoting into account.
* rsrc (if not NULL) is set to the first byte not included in the
* string returned in rdst.
*
* Input is:
* src = 'fir"st \'par"t second part';
* Returned is:
* *dst = 'hello \'world';
* if (rsrc != NULL)
* *rsrc = 'second part';
*
* Notice the fact that the single quote enclosed in double quotes is
* returned. Also notice that before second part there is more than
* one space, which is removed in rsrc.
*
* The string in src is not modified.
*/
char *dst; /* destination string */
int i; /* index into src */
int j; /* index into dst */
int quoted = 0; /* 1 for single, 2 for double quoted */
dst = malloc(strlen(src)); /* XXX allocation is too big, realloc?*/
if (dst == NULL) { /* should not happen, really */
fprintf(stderr, "%s:%d: Out of memory\n", configfile, lineno);
exit(2);
}
/* find the end of the current string. If quotes are found the search
* continues until the corresponding quote is found.
* So,
* hel'lo" "wor'ld
* represents the string
* hello" "world
* and not (hello world).
*/
for (i = 0, j = 0; i < strlen(src); i++) {
if (src[i] == '\'' && (quoted == 0 || quoted == 1)) {
quoted = (quoted? 0:1);
} else if (src[i] == '"' && (quoted == 0 || quoted == 2)) {
quoted = (quoted? 0:2);
} else if (isspace(src[i]) && !quoted) {
/* found a space outside quotes -> terminates src */
break;
} else {
dst[j++] = src[i]; /* copy character */
}
}
/* quotes being left open? */
if (quoted) {
fprintf(stderr, "%s:%d: Missing %s quote at end of '%s'\n",
configfile, lineno,
(quoted == 1? "single":"double"), src);
exit(2);
}
/* skip whitespace for second part */
for (/*i is set*/; i < strlen(src) && isspace(src[i]); i++)
; /* nop */
dst[j] = '\0'; /* make sure it's NULL terminated */
*rdst = dst; /* and return the pointers */
if (rsrc != NULL) /* if info wanted */
*rsrc = &src[i];
if (*dst == '\0') { /* empty string */
return 0;
} else if (src[i] == '\0') { /* completely used (1 argument) */
return 1;
} else { /* 2 or more args, *rsrc is rest */
return 2;
}
}
int
get_integer(char *src, int *dst, char **rsrc)
{
char *endptr;
/* Converts str to a number. If one argument was found in
* str, 1 is returned and *dst is set to the value of the integer.
* If 2 or more arguments were presented, 2 is returned,
* *dst is set to the converted value and rsrc, if not null, points
* at the start of the next argument (whitespace skipped).
* Else 0 is returned and nothing else is valid.
*/
if (src == NULL || *src == '\0') /* empty src */
return(0);
*dst = (int) strtol(src, &endptr, 0);
/* skip over whitespace of second argument */
while (isspace(*endptr))
endptr++;
if (rsrc)
*rsrc = endptr;
if (isspace(*endptr)) { /* partial match, 2 or more arguments */
return(2);
} else if (*endptr == '\0') { /* full match, 1 argument */
return(1);
} else { /* invalid src, no match */
return(0);
}
}
/* functions to set the fields of the actions appropriately */
int
set_device_field(action_t *action, char *args, char **trail)
{
return(get_string(args, &action->name, trail));
}
int
set_product_field(action_t *action, char *args, char **trail)
{
return(get_integer(args, &action->product, trail));
}
int
set_vendor_field(action_t *action, char *args, char **trail)
{
return(get_integer(args, &action->vendor, trail));
}
int
set_release_field(action_t *action, char *args, char **trail)
{
return(get_integer(args, &action->release, trail));
}
int
set_class_field(action_t *action, char *args, char **trail)
{
return(get_integer(args, &action->class, trail));
}
int
set_subclass_field(action_t *action, char *args, char **trail)
{
return(get_integer(args, &action->subclass, trail));
}
int
set_protocol_field(action_t *action, char *args, char **trail)
{
return(get_integer(args, &action->protocol, trail));
}
int
set_attach_field(action_t *action, char *args, char **trail)
{
return(get_string(args, &action->attach, trail));
}
int
set_detach_field(action_t *action, char *args, char **trail)
{
return(get_string(args, &action->detach, trail));
}
void
read_configuration(void)
{
FILE *file; /* file descriptor */
char *line; /* current line */
char *linez; /* current line, NULL terminated */
char *field; /* first part, the field name */
char *args; /* second part, arguments */
char *trail; /* remaining part after parsing, should be '' */
int len; /* length of current line */
int i,j; /* loop counters */
action_t *action = NULL; /* current action */
file = fopen(configfile, "r");
if (file == NULL) {
fprintf(stderr, "%s: Could not open for reading, %s\n",
configfile, strerror(errno));
exit(2);
}
for (lineno = 1; /* nop */;lineno++) {
line = fgetln(file, &len);
if (line == NULL) {
if (feof(file)) /* EOF */
break;
if (ferror(file)) {
fprintf(stderr, "%s:%d: Could not read, %s\n",
configfile, lineno, strerror(errno));
exit(2);
}
}
/* skip initial spaces */
while (*line != '\0' && isspace(*line)) {
line++;
len--;
}
if (len == 0) /* empty line */
continue;
if (line[0] == '#') /* comment line */
continue;
/* make a NULL terminated copy of the string */
linez = malloc(len+1);
if (linez == NULL) {
fprintf(stderr, "%s:%d: Out of memory\n",
configfile, lineno);
exit(2);
}
strncpy(linez, line, len);
linez[len+1] = '\0';
/* find the end of the current word (is field), that's the
* start of the arguments
*/
field = linez;
args = linez;
while (*args != '\0' && !isspace(*args))
args++;
/* If arguments is not the empty string, NULL terminate the
* field and move the argument pointer to the first character
* of the arguments.
* If arguments is the empty string field and arguments both
* are terminated (strlen(field) >= 0, strlen(arguments) == 0).
*/
if (*args != '\0') {
*args = '\0';
args++;
}
/* Skip initial spaces */
while (*args != '\0' && isspace(*args))
args++;
/* Cut off trailing whitespace */
for (i = 0, j = 0; args[i] != '\0'; i++)
if (!isspace(args[i]))
j = i+1;
args[j] = '\0';
/* We now have the field and the argument separated into
* two strings that are NULL terminated
*/
/* If the field is 'device' we have to start a new action. */
if (strcmp(field, "device") == 0) {
/* Allocate a new action and set defaults */
action = malloc(sizeof(*action));
if (action == NULL) {
fprintf(stderr, "%s:%d: Out of memory\n",
configfile, lineno);
exit(2);
}
memset(action, 0, sizeof(*action));
action->product = WILDCARD_INT;
action->vendor = WILDCARD_INT;
action->release = WILDCARD_INT;
action->class = WILDCARD_INT;
action->subclass = WILDCARD_INT;
action->protocol = WILDCARD_INT;
/* Add it to the end of the list to preserve order */
STAILQ_INSERT_TAIL(&actions, action, next);
}
if (action == NULL) {
line[len] = '\0'; /* XXX zero terminate */
fprintf(stderr, "%s:%d: Doesn't start with 'device' "
"but '%s'\n", configfile, lineno, field);
exit(2);
}
for (i = 0; config_fields[i].name ; i++) {
/* does the field name match? */
if (strcmp(config_fields[i].name, field) == 0) {
/* execute corresponding set-field function */
if ((config_fields[i].function)(action, args,
&trail)
!= 1) {
fprintf(stderr,"%s:%d: "
"Syntax error in '%s'\n",
configfile, lineno, linez);
exit(2);
}
break;
}
}
if (config_fields[i].name == NULL) { /* Reached end of list*/
fprintf(stderr, "%s:%d: Unknown field '%s'\n",
configfile, lineno, field);
exit(2);
}
}
fclose(file);
if (verbose >= 2)
print_actions();
}
void
print_event(struct usb_event *event)
{
int i;
struct timespec *timespec = &event->ue_time;
struct usb_device_info *devinfo = &event->ue_device;
printf("%s: ", __progname);
for (i = 0; event_names[i].name != NULL; i++) {
if (event->ue_type == event_names[i].type) {
printf("%s event", event_names[i].name);
break;
}
}
if (event_names[i].name == NULL)
printf("unknown event %d", event->ue_type);
printf(" at %ld.%09ld, %s, %s:\n",
timespec->tv_sec, timespec->tv_nsec,
devinfo->product, devinfo->vendor);
printf(" prdct=0x%04x vndr=0x%04x rlse=0x%04x "
"clss=0x%04x subclss=0x%04x prtcl=0x%04x\n",
devinfo->productNo, devinfo->vendorNo, devinfo->releaseNo,
devinfo->class, devinfo->subclass, devinfo->protocol);
}
void
print_action(action_t *action, int i)
{
if (action == NULL)
return;
printf("%s: action %d: %s\n",
__progname, i,
(action->name? action->name:""));
if (action->product != WILDCARD_INT ||
action->vendor != WILDCARD_INT ||
action->release != WILDCARD_INT ||
action->class != WILDCARD_INT ||
action->subclass != WILDCARD_INT ||
action->protocol != WILDCARD_INT)
printf(" ");
if (action->product != WILDCARD_INT)
printf(" prdct=0x%04x", action->product);
if (action->vendor != WILDCARD_INT)
printf(" vndr=0x%04x", action->vendor);
if (action->release != WILDCARD_INT)
printf(" rlse=0x%04x", action->release);
if (action->class != WILDCARD_INT)
printf(" clss=0x%04x", action->class);
if (action->subclass != WILDCARD_INT)
printf(" subclss=0x%04x", action->subclass);
if (action->protocol != WILDCARD_INT)
printf(" prtcl=0x%04x", action->protocol);
if (action->product != WILDCARD_INT ||
action->vendor != WILDCARD_INT ||
action->release != WILDCARD_INT ||
action->class != WILDCARD_INT ||
action->subclass != WILDCARD_INT ||
action->protocol != WILDCARD_INT)
printf("\n");
if (action->attach != NULL)
printf("%s: attach='%s'\n",
__progname, action->attach);
if (action->detach != NULL)
printf("%s: detach='%s'\n",
__progname, action->detach);
}
void
print_actions()
{
int i = 0;
action_t *action;
STAILQ_FOREACH(action, &actions, next)
print_action(action, ++i);
printf("%s: %d action%s\n", __progname, i, (i == 1? "":"s"));
}
action_t *
find_action(struct usb_device_info *devinfo)
{
action_t *action;
STAILQ_FOREACH(action, &actions, next) {
if ((action->product == WILDCARD_INT ||
action->product == devinfo->productNo) &&
(action->vendor == WILDCARD_INT ||
action->vendor == devinfo->vendorNo) &&
(action->release == WILDCARD_INT ||
action->release == devinfo->releaseNo) &&
(action->class == WILDCARD_INT ||
action->class == devinfo->class) &&
(action->subclass == WILDCARD_INT ||
action->subclass == devinfo->subclass) &&
(action->protocol == WILDCARD_INT ||
action->protocol == devinfo->protocol)) {
/* found match !*/
break;
}
}
if (verbose)
printf("%s: Found action '%s' for %s, %s\n",
__progname, action->name,
devinfo->product, devinfo->vendor);
return(action);
}
void
execute_command(char *cmd)
{
pid_t pid;
int pstat;
struct sigaction ign, intact, quitact;
sigset_t newsigblock, oldsigblock;
int status;
int i;
if (verbose)
printf("%s: Executing '%s'\n", __progname, cmd);
if (cmd == NULL)
return;
/* The code below is directly taken from the system(3) call.
* Added to it is the closing of open file descriptors.
*/
/*
* Ignore SIGINT and SIGQUIT, block SIGCHLD. Remember to save
* existing signal dispositions.
*/
ign.sa_handler = SIG_IGN;
(void) sigemptyset(&ign.sa_mask);
ign.sa_flags = 0;
(void) sigaction(SIGINT, &ign, &intact);
(void) sigaction(SIGQUIT, &ign, &quitact);
(void) sigemptyset(&newsigblock);
(void) sigaddset(&newsigblock, SIGCHLD);
(void) sigprocmask(SIG_BLOCK, &newsigblock, &oldsigblock);
pid = fork();
if (pid == -1) {
fprintf(stderr, "%s: fork failed, %s\n",
__progname, strerror(errno));
} else if (pid == 0) {
/* child here */
/* close all open file handles for USBDEV\d* devices */
for (i = 0; i < ndevs; i++)
close(fds[i]); /* USBDEV\d+ */
close(fd); /* USBDEV */
/* Restore original signal dispositions and exec the command. */
(void) sigaction(SIGINT, &intact, NULL);
(void) sigaction(SIGQUIT, &quitact, NULL);
(void) sigprocmask(SIG_SETMASK, &oldsigblock, NULL);
execl(_PATH_BSHELL, "sh", "-c", cmd, (char *)NULL);
exit(127);
} else {
/* parent here */
do {
pid = waitpid(pid, &pstat, 0);
} while (pid == -1 && errno == EINTR);
}
(void) sigaction(SIGINT, &intact, NULL);
(void) sigaction(SIGQUIT, &quitact, NULL);
(void) sigprocmask(SIG_SETMASK, &oldsigblock, NULL);
if (pid == -1) {
fprintf(stderr, "%s: waitpid returned: %s\n",
__progname, strerror(errno));
} else if (pid == 0) {
fprintf(stderr, "%s: waitpid returned 0 ?!\n",
__progname);
} else {
if (status == -1) {
fprintf(stderr, "%s: Could not start '%s'\n",
__progname, cmd);
} else if (status == 127) {
fprintf(stderr, "%s: Shell failed for '%s'\n",
__progname, cmd);
} else if (WIFEXITED(status)) {
fprintf(stderr, "%s: '%s' returned %d\n",
__progname, cmd, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
fprintf(stderr, "%s: '%s' caught signal %d\n",
__progname, cmd, WTERMSIG(status));
} else if (verbose) {
printf("%s: '%s' is done\n", __progname, cmd);
}
}
}
void
process_event_queue(int fd)
{
struct usb_event event;
int len;
action_t *action;
for (;;) {
len = read(fd, &event, sizeof(event));
if (len == -1) {
if (errno == EWOULDBLOCK) {
/* no more events */
break;
} else {
fprintf(stderr,"%s: Could not read event, %s\n",
__progname, strerror(errno));
exit(1);
}
}
if (len == 0)
break;
if (len != sizeof(event)) {
fprintf(stderr, "partial read on %s\n", USBDEV);
exit(1);
}
/* we seem to have gotten a valid event */
if (verbose)
print_event(&event);
/* handle the event appropriately */
switch (event.ue_type) {
case USB_EVENT_ATTACH:
case USB_EVENT_DETACH:
action = find_action(&event.ue_device);
if (action == NULL)
break;
else if (verbose >= 2)
print_action(action, 0);
if (event.ue_type == USB_EVENT_ATTACH && action->attach)
execute_command(action->attach);
if (event.ue_type == USB_EVENT_DETACH && action->detach)
execute_command(action->detach);
break;
default:
printf("Unknown USB event %d\n", event.ue_type);
}
}
}
int
main(int argc, char **argv)
{
@ -93,32 +768,35 @@ main(int argc, char **argv)
int ch; /* getopt option */
extern char *optarg; /* from getopt */
extern int optind; /* from getopt */
char *devs[MAXUSBDEV]; /* device names */
int ndevs = 0; /* number of devices found */
int verbose = 0; /* print message on what it is doing */
int debug = 0; /* print debugging output */
int explore = 0; /* don't do only explore */
int explore_once = 0; /* don't do only explore */
int handle_events = 1; /* do handle the event queue */
int maxfd; /* maximum fd in use */
char buf[50]; /* for creation of the filename */
int fds[MAXUSBDEV]; /* open filedescriptors */
fd_set fdset;
int itimo = TIMEOUT; /* timeout for select */
struct timeval timo;
fd_set r,w;
int itimeout = TIMEOUT; /* timeout for select */
struct timeval tv;
while ((ch = getopt(argc, argv, "def:t:v")) != -1) {
while ((ch = getopt(argc, argv, "c:def:nt:v")) != -1) {
switch(ch) {
case 'c':
configfile = strdup(optarg);
break;
case 'd':
debug++;
break;
case 'e':
explore++;
explore_once = 1;
break;
case 'f':
if (ndevs < MAXUSBDEV)
devs[ndevs++] = optarg;
break;
case 'n':
handle_events = 0;
break;
case 't':
itimo = atoi(optarg);
itimeout = atoi(optarg);
break;
case 'v':
verbose++;
@ -131,16 +809,16 @@ main(int argc, char **argv)
argc -= optind;
argv += optind;
/* open all the files /dev/usb\d+ or specified with -f */
maxfd = 0;
if (ndevs == 0) {
/* open all the USBDEVS\d+ devices */
for (i = 0; i < MAXUSBDEV; i++) {
sprintf(buf, "%s%d", USBDEV, i);
fds[ndevs] = open(buf, O_RDWR);
if (fds[ndevs] >= 0) {
devs[ndevs] = strdup(buf);
if (verbose)
printf("%s: opening %s\n",
printf("%s: opened %s\n",
__progname, devs[ndevs]);
if (fds[ndevs] > maxfd)
maxfd = fds[ndevs];
@ -148,14 +826,23 @@ main(int argc, char **argv)
}
}
} else {
/* open all the files specified with -f */
for (i = 0; i < ndevs; i++) {
fds[i] = open(devs[i], O_RDWR);
if (fds[i] < 0)
err(1, "%s", devs[i]);
else if (fds[i] > maxfd)
maxfd = fds[i];
if (fds[i] < 0) {
fprintf(stderr, "%s: Could not open %s, %s\n",
__progname, devs[i], strerror(errno));
exit(1);
} else {
if (verbose)
printf("%s: opened %s\n",
__progname, devs[i]);
if (fds[i] > maxfd)
maxfd = fds[i];
}
}
}
if (ndevs == 0) {
fprintf(stderr, "No USB host controllers found\n");
exit(1);
@ -163,37 +850,84 @@ main(int argc, char **argv)
/* Do the explore once and exit */
if (explore) {
if (explore_once) {
for (i = 0; i < ndevs; i++) {
error = ioctl(fds[i], USB_DISCOVER);
if (error < 0)
err(1, "USB_DISCOVER");
if (error < 0) {
fprintf(stderr, "%s: ioctl(%s, USB_DISCOVER) "
"failed, %s\n",
__progname, devs[i], strerror(errno));
exit(1);
}
}
exit(0);
}
if (handle_events) {
if (verbose)
printf("%s: reading configuration file %s\n",
__progname, configfile);
read_configuration();
fd = open(USBDEV, O_RDONLY | O_NONBLOCK);
if (fd < 0) {
fprintf(stderr, "%s: Could not open %s, %s\n",
__progname, USBDEV, strerror(errno));
exit(1);
}
if (verbose)
printf("%s: opened %s\n", __progname, USBDEV);
if (fd > maxfd)
maxfd = fd;
process_event_queue(fd); /* dequeue the initial events */
}
/* move to the background */
if (!debug)
daemon(0, 0);
/* start select on all the open file descriptors */
for (;;) {
FD_ZERO(&fdset);
FD_ZERO(&r);
FD_ZERO(&w);
if (handle_events)
FD_SET(fd, &r); /* device USBDEV */
for (i = 0; i < ndevs; i++)
FD_SET(fds[i], &fdset);
timo.tv_usec = 0;
timo.tv_sec = itimo;
error = select(maxfd+1, 0, &fdset, 0, itimo ? &timo : 0);
if (error < 0)
warn("select failed\n");
for (i = 0; i < ndevs; i++)
if (error == 0 || FD_ISSET(fds[i], &fdset)) {
if (verbose)
FD_SET(fds[i], &w); /* device USBDEV\d+ */
tv.tv_usec = 0;
tv.tv_sec = itimeout;
error = select(maxfd+1, &r, &w, 0, itimeout ? &tv : 0);
if (error < 0) {
fprintf(stderr, "%s: Select failed, %s\n",
__progname, strerror(errno));
exit(1);
}
/* USBDEV\d+ devices have signaled change, do a usb_discover */
for (i = 0; i < ndevs; i++) {
if (error == 0 || FD_ISSET(fds[i], &w)) {
if (verbose >= 2)
printf("%s: doing %sdiscovery on %s\n",
__progname,
(error? "":"timeout "), devs[i]);
if (ioctl(fds[i], USB_DISCOVER) < 0)
err(1, "USB_DISCOVER");
if (ioctl(fds[i], USB_DISCOVER) < 0) {
fprintf(stderr, "%s: ioctl(%s, "
"USB_DISCOVER) failed, %s\n",
__progname, devs[i],
strerror(errno));
exit(1);
}
}
}
/* check the event queue */
if (handle_events && (FD_ISSET(fd, &r) || error == 0)) {
if (verbose >= 2)
printf("%s: processing event queue %son %s\n",
__progname,
(error? "":"due to timeout "), USBDEV);
process_event_queue(fd);
}
}
}

128
usr.sbin/usbd/usbd.conf.5 Normal file
View File

@ -0,0 +1,128 @@
.\"
.\" Copyright (c) 1999 Nick Hibma. All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\" 3. The name of the author may not be used to endorse or promote products
.\" derived from this software without specific prior written permission.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
.\" $FreeBSD$
.\"
.\" Many parts of this manual have been snarfed from the pccard.conf (5) man
.\" page, copyright by Andrew McRae.
.\"
.Dd November 19, 1999
.Dt USBD.CONF 5
.Os FreeBSD
.Sh NAME
.Nm usbd.conf
.Nd
.Xr usbd 8
configuration file
.Sh DESCRIPTION
The
.Nm
file is the configuration file for the
.Xr usbd 8
daemon. It provides information to allow execution of userland commands
on events reported by the
.Xr usb 4
subsystem in the kernel. Currently the only events are device attach and
detach, but could in the future be extended to include power management
functions.
.Pp
The configuration file consists of a sorted list of entries. Each entry
describes a set of criteria commands. When an event occurs, the criteria
are checked and if met, the commands for that event are executed through
a shell. The list is sorted and scanned from top to bottom. The first
matching entry is used for an event.
.Pp
Each entry contains a number of fields. There are 3 types of fields:
descriptive fields, selection criteria and commands to execute on
events. The field name is case sensitive and should be all lower case.
Each field can have one or more arguments.
.Pp
The following fields are available:
.Bl -tag -width devicename\ <Id>
.It device Ar string
Start a new entry.
.Ar string
is an arbitrary string used for pretty printing.
.It product Ar id
Product Id
.It vendor Ar id
Vendor Id
.It release Ar id
Release Id, also called revision Id sometimes.
.It class Ar id
Device Class
.It subclass Ar id
Device Subclass
.It protocol Ar id
Device Protocol
.It devicenames Ar string
Device name, for example umass2, or ums0.
.El
.Pp
String arguments may be quoted. If a string argument contains a space or
tab character it needs to be enclosed in single or double quotes. If an
argument contains a single or double quote, that quote needs to be
enclosed in double or single quotes respectively. See below for
examples.
.Pp
Numeric arguments can either be specified in decimal (42), octal (052) or
hexadeximal (0x2a).
.Pp
The values for the fields
.Li product , vendor , release, class , subclass
and
.Li protocol
can be retrieved by killing the
.Nm usbd
daemon and running it with the
.Fl -d
and
.Fl -v
flags.
.Pp
.Sh EXAMPLES
A sample entry to rescan the SCSI bus on connection of a
.Tn "Iomega USB Zip Drive" .
.Bd -literal
device "USB Zip drive"
product 0x0001
vendor 0x059b
release 0x0100
attach "/usr/bin/camcontrol rescan bus 0"
.Ed
.Sh FILES
.Bl -tag -width /etc/pccard.conf -compact
.It Pa /etc/usbd.conf
The
.Nm usbd
configuration file.
.El
.Sh SEE ALSO
.Xr usb 4 ,
.Xr usbd 8 ,
.Xr usbdevs 8
.Sh AUTHORS
The man page for the usbd configuration file was written by
.An Nick Hibma Aq n_hibma@freebsd.org .