#include #include #include #include #include #include #include #include #include #include #include "usb_driver.h" #include "proto.h" #define SERVICE_BINARY "/bin/service" #define DEVMAN_TYPE_NAME "dev_type" #define PATH_LEN 256 #define INVAL_MAJOR -1 #define MAX_CONFIG_DIRS 4 static void main_loop(); static void handle_event(); static void cleanup(); static void parse_config(); static void display_usage(); static enum dev_type determine_type(char *path); static int get_major(); static void create_pid_file(); static void put_major(int major); static struct devmand_usb_driver* match_usb_driver(struct usb_device_id *id); static struct devmand_driver_instance *find_instance(int dev_id); #define dbg(fmt, ... ) \ if (args.verbose) \ printf("%8s:%4d: %13s()| "fmt"\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__ ) static LIST_HEAD(usb_driver_head, devmand_usb_driver) drivers = LIST_HEAD_INITIALIZER(drivers); static LIST_HEAD(usb_driver_inst_head, devmand_driver_instance) instances = LIST_HEAD_INITIALIZER(instances); static int _run = 1; struct global_args { char *path; char *config_dirs[MAX_CONFIG_DIRS]; int config_dir_count ; int major_offset; int verbose; int check_config; }; enum dev_type { DEV_TYPE_USB_DEVICE, DEV_TYPE_USB_INTF, DEV_TYPE_UNKOWN }; extern FILE *yyin; static struct global_args args = { .path = NULL, .config_dirs = {NULL,NULL,NULL,NULL}, .config_dir_count = 0, .major_offset = 25, .verbose = 0, .check_config = 0}; static struct option options[] = { {"dir" , required_argument, NULL, 'd'}, {"path", required_argument, NULL, 'p'}, {"verbose", required_argument, NULL, 'v'}, {"check-config", no_argument, NULL, 'x'}, {0,0,0,0} /* terminating entry */ }; static char major_bitmap[16]; /* can store up to 128 major number states */ /*===========================================================================* * run_upscript * *===========================================================================*/ int run_upscript(struct devmand_driver_instance *inst) { char cmdl[1024]; cmdl[0] = 0; int ret; snprintf(cmdl, 1024, "%s up %s %d %d", inst->drv->upscript, inst->label, inst->major, inst->dev_id); dbg("Running Upscript: \"%s\"", cmdl); ret = system(cmdl); if (ret != 0) { return EINVAL; } return 0; } /*===========================================================================* * run_cleanscript * *===========================================================================*/ int run_cleanscript(struct devmand_usb_driver *drv) { char cmdl[1024]; cmdl[0] = 0; int ret; snprintf(cmdl, 1024, "%s clean %s ", drv->upscript, drv->devprefix); dbg("Running Upscript: \"%s\"", cmdl); ret = system(cmdl); if (ret != 0) { return EINVAL; } return 0; } /*===========================================================================* * run_downscript * *===========================================================================*/ int run_downscript(struct devmand_driver_instance *inst) { char cmdl[1024]; cmdl[0] = 0; int ret; snprintf(cmdl, 1024, "%s down %s %d", inst->drv->downscript, inst->label, inst->major); dbg("Running Upscript: \"%s\"", cmdl); ret = system(cmdl); if (ret != 0) { return EINVAL; } return 0; } /*===========================================================================* * stop_driver * *===========================================================================*/ int stop_driver(struct devmand_driver_instance *inst) { char cmdl[1024]; cmdl[0] = 0; int ret; snprintf(cmdl, 1024, "%s down %s %d", SERVICE_BINARY, inst->label, inst->dev_id); dbg("executing service: \"%s\"", cmdl); ret = system(cmdl); if (ret != 0) { return EINVAL; } printf("Stopped driver %s with label %s for device %d.\n", inst->drv->binary, inst->label, inst->dev_id); return 0; } /*===========================================================================* * start_driver * *===========================================================================*/ int start_driver(struct devmand_driver_instance *inst) { char cmdl[1024]; cmdl[0] = 0; int ret; /* generate label */ ret = snprintf(inst->label, 32, "%s%d", inst->drv->devprefix, inst->dev_id); if (ret < 0 || ret > DEVMAND_DRIVER_LABEL_LEN) { dbg("label too long"); return ENOMEM; } snprintf(cmdl, 1024, "%s up %s -major %d -devid %d -label %s", SERVICE_BINARY, inst->drv->binary, inst->major, inst->dev_id, inst->label); dbg("executing service: \"%s\"", cmdl); ret = system(cmdl); if (ret != 0) { return EINVAL; } printf("Started driver %s with label %s for device %d.\n", inst->drv->binary, inst->label, inst->dev_id); return 0; } /*===========================================================================* * find_instance * *===========================================================================*/ static struct devmand_driver_instance * find_instance(int dev_id) { struct devmand_driver_instance *inst; LIST_FOREACH(inst, &instances, list) { if (inst->dev_id == dev_id) { return inst; } } return NULL; } /*===========================================================================* * match_usb_driver * *===========================================================================*/ static int match_usb_id(struct devmand_usb_match_id *mid, struct usb_device_id *id) { int res = 1; unsigned long match = mid->match_flags; struct usb_device_id *_id = &mid->match_id; if (match & USB_MATCH_ID_VENDOR) if (id->idVendor != _id->idVendor) res = 0; if (match & USB_MATCH_ID_PRODUCT) if (id->idProduct != _id->idProduct) res = 0; if (match & USB_MATCH_BCD_DEVICE) if (id->bcdDevice != _id->bcdDevice) res = 0; if (match & USB_MATCH_DEVICE_PROTOCOL) if (id->bDeviceProtocol != _id->bDeviceProtocol) res = 0; if (match & USB_MATCH_DEVICE_SUBCLASS) if (id->bDeviceSubClass != _id->bDeviceSubClass) res = 0; if (match & USB_MATCH_DEVICE_PROTOCOL) if (id->bDeviceProtocol != _id->bDeviceProtocol) res = 0; if (match & USB_MATCH_INTERFACE_CLASS) if (id->bInterfaceClass != _id->bInterfaceClass) res = 0; if (match & USB_MATCH_INTERFACE_SUBCLASS) if (id->bInterfaceSubClass != _id->bInterfaceSubClass) res = 0; if (match & USB_MATCH_INTERFACE_PROTOCOL) if (id->bInterfaceProtocol != _id->bInterfaceProtocol) res = 0; if (match == 0UL) { res = 0; } return res; } /*===========================================================================* * match_usb_driver * *===========================================================================*/ static struct devmand_usb_driver* match_usb_driver(struct usb_device_id *id) { struct devmand_usb_driver *driver; struct devmand_usb_match_id *mid; LIST_FOREACH(driver, &drivers, list) { LIST_FOREACH(mid, &driver->ids, list) { if (match_usb_id(mid, id)) { return driver; } } } return NULL; } /*===========================================================================* * add_usb_match_id * *===========================================================================*/ struct devmand_usb_driver * add_usb_driver(char *name) { struct devmand_usb_driver *udrv = (struct devmand_usb_driver*) malloc(sizeof(struct devmand_usb_driver)); LIST_INSERT_HEAD(&drivers, udrv, list); LIST_INIT(&udrv->ids); udrv->name = name; return udrv; } /*===========================================================================* * add_usb_match_id * *===========================================================================*/ struct devmand_usb_match_id * add_usb_match_id (struct devmand_usb_driver *drv) { struct devmand_usb_match_id *id = (struct devmand_usb_match_id*) malloc(sizeof(struct devmand_usb_match_id)); memset(id, 0, sizeof(struct devmand_usb_match_id)); LIST_INSERT_HEAD(&drv->ids, id, list); return id; } /*===========================================================================* * parse_config * *===========================================================================*/ static void parse_config() { int i, status, error; struct stat stats; char * dirname; DIR * dir; struct dirent entry; struct dirent *result; char config_file[PATH_MAX]; dbg("Parsing configuration directories... "); /* Next parse the configuration directories */ for(i=0; i < args.config_dir_count; i++){ dirname = args.config_dirs[i]; dbg("Parsing config dir %s ", dirname); status = stat(dirname,&stats); if (status == -1){ error = errno; dbg("Failed to read directory '%s':%s (skipping) \n", dirname,strerror(error)); continue; } if (!S_ISDIR(stats.st_mode)){ dbg("Parse configuration skipping %s " "(not a directory) \n",dirname); continue; } dir = opendir(dirname); if (dir == NULL){ error = errno; dbg("Parse configuration failed to read dir '%s'" "(skipping) :%s\n",dirname, strerror(error)); continue; } while( (status = readdir_r(dir,&entry,&result)) == 0 ){ if (result == NULL){ /* last entry */ closedir(dir); break; } /* concatenate dir and file name to open it */ snprintf(config_file,PATH_MAX, "%s/%s", dirname,entry.d_name); status = stat(config_file, &stats); if (status == -1){ error = errno; dbg("Parse configuration Failed to stat file " "'%s': %s (skipping)\n", config_file, strerror(error)); } if (S_ISREG(stats.st_mode)){ dbg("Parsing file %s",config_file); yyin = fopen(config_file, "r"); if (yyin < 0) { dbg("Can not open config file:" " %d.\n", errno); } yyparse(); dbg("Done."); fclose(yyin); } } } dbg("Parsing configuration directories done... "); } /*===========================================================================* * cleanup * *===========================================================================*/ static void cleanup() { struct devmand_driver_instance *inst; /* destroy fifo */ dbg("cleaning up... "); /* quit all running drivers */ LIST_FOREACH(inst, &instances, list) { dbg("stopping driver %s", inst->label); run_downscript (inst); stop_driver(inst); } unlink("/var/run/devmand.pid"); } static void sig_int(int sig) { dbg("devman: Received SIGINT... cleaning up."); _run = 0; } /*===========================================================================* * create_pid_file * *===========================================================================*/ void create_pid_file() { FILE *fd; fd = fopen("/var/run/devmand.pid", "r"); if(fd) { fprintf(stderr, "devmand: /var/run/devmand.pid exists... " "another devmand running?\n"); fclose(fd); exit(1); } else { fd = fopen("/var/run/devmand.pid","w"); fprintf(fd, "%d", getpid()); fclose(fd); } } /*===========================================================================* * main * *===========================================================================*/ int main(int argc, char *argv[]) { int opt, optindex; struct devmand_usb_driver *driver; /* get command line arguments */ while ((opt = getopt_long(argc, argv, "d:p:vxh?", options, &optindex)) != -1) { switch (opt) { case 'd':/* config directory */ if (args.config_dir_count >= MAX_CONFIG_DIRS){ fprintf(stderr,"Parse arguments: Maximum" " of %i configuration directories" " reached skipping directory '%s'\n" , MAX_CONFIG_DIRS, optarg); break; } args.config_dirs[args.config_dir_count] = optarg; args.config_dir_count++; break; case 'p': /* sysfs path */ args.path = optarg; break; case 'v': /* verbose */ args.verbose = 1; break; case 'x': /* check config */ args.check_config = 1; break; case 'h': /* help */ case '?': /* help */ default: display_usage(argv[0]); return 0; } } /* is path set? */ if (args.path == NULL) { args.path = "/sys/"; } /* is the configuration directory set? */ if (args.config_dir_count == 0) { dbg("Using default configuration directory"); args.config_dirs[0] = "/etc/devmand"; args.config_dir_count = 1; } /* If we only check the configuration run and exit imediately */ if (args.check_config == 1){ fprintf(stdout, "Only parsing configuration\n"); parse_config(); exit(0); } create_pid_file(); parse_config(); LIST_FOREACH(driver, &drivers, list) { run_cleanscript(driver); } signal(SIGINT, sig_int); main_loop(); cleanup(); return 0; } /*===========================================================================* * determine_type * *===========================================================================*/ static enum dev_type determine_type (char *path) { FILE * fd; char *mypath; char buf[256]; int res; mypath = (char *) calloc(1, strlen(path)+strlen(DEVMAN_TYPE_NAME)+1); if (mypath == NULL) { fprintf(stderr, "ERROR: out of mem\n"); exit(1); } strcat(mypath, path); strcat(mypath, DEVMAN_TYPE_NAME); fd = fopen(mypath, "r"); free(mypath); if (fd == NULL) { fprintf(stderr, "WARN: could not open %s\n", mypath); return DEV_TYPE_UNKOWN; } res = fscanf(fd , "%s\n", buf); fclose(fd); if (res != 1) { fprintf(stderr, "WARN: could not parse %s\n", mypath); return DEV_TYPE_UNKOWN; } if (strcmp(buf, "USB_DEV") == 0) { return DEV_TYPE_USB_DEVICE; } else if (strcmp(buf, "USB_INTF") == 0) { return DEV_TYPE_USB_INTF; } return DEV_TYPE_UNKOWN; } /*===========================================================================* * read_hex_uint * *===========================================================================*/ static int read_hex_uint(char *base_path, char *name, unsigned int* val ) { char my_path[PATH_LEN]; FILE *fd; memset(my_path,0,PATH_LEN); int ret = 0; strcat(my_path, base_path); strcat(my_path, name); fd = fopen(my_path, "r"); if (fd == NULL) { fprintf(stderr, "WARN: could not open %s\n", my_path); return EEXIST; } else if (fscanf(fd, "0x%x\n", val ) != 1) { fprintf(stderr, "WARN: could not parse %s\n", my_path); ret = EINVAL; } fclose(fd); return ret; } /*===========================================================================* * get_major * *===========================================================================*/ static int get_major() { int i, ret = args.major_offset; for (i=0; i < 16; i++) { int j; for (j = 0; j < 8; j++ ) { if ((major_bitmap[i] & (1 << j))) { major_bitmap[i] &= !(1 << j); return ret; } ret++; } } return INVAL_MAJOR; } /*===========================================================================* * put_major * *===========================================================================*/ static void put_major(int major) { int i; major -= args.major_offset; assert(major >= 0); for (i=0; i < 16; i++) { int j; for (j = 0; j < 8; j++ ) { if (major==0) { assert(!(major_bitmap[i] & (1 <idVendor = val; res = read_hex_uint(path, "../idProduct", &val); if (res) goto err; ret->idProduct = val; #if 0 res = read_hex_uint(path, "../bcdDevice", &val); if (res) goto err; ret->bcdDevice = val; #endif res = read_hex_uint(path, "../bDeviceClass", &val); if (res) goto err; ret->bDeviceClass = val; res = read_hex_uint(path, "../bDeviceSubClass", &val); if (res) goto err; ret->bDeviceSubClass = val; res = read_hex_uint(path, "../bDeviceProtocol", &val); if (res) goto err; ret->bDeviceProtocol = val; res = read_hex_uint(path, "/bInterfaceClass", &val); if (res) goto err; ret->bInterfaceClass = val; res = read_hex_uint(path, "/bInterfaceSubClass", &val); if (res) goto err; ret->bInterfaceSubClass = val; res = read_hex_uint(path, "/bInterfaceProtocol", &val); if (res) goto err; ret->bInterfaceProtocol = val; } return ret; err: free(ret); return NULL; } /*===========================================================================* * usb_intf_add_even * *===========================================================================*/ static void usb_intf_add_event(char *path, int dev_id) { struct usb_device_id *id; struct devmand_usb_driver *drv; struct devmand_driver_instance *drv_inst; int major, ret; /* generate usb_match_id */ id = generate_usb_device_id(path,TRUE); if (id == NULL) { fprintf(stderr, "WARN: could not create usb_device id...\n" " ommiting event\n"); free(id); return; } /* find suitable driver */ drv = match_usb_driver(id); free (id); if (drv == NULL) { dbg("INFO: could not find a suitable driver for %s", path); return; } /* create instance */ drv_inst = (struct devmand_driver_instance *) calloc(1,sizeof(struct devmand_driver_instance)); if (drv_inst == NULL) { fprintf(stderr, "ERROR: out of memory"); return; /* maybe better quit here. */ } /* allocate inode number, if device files needed */ major = get_major(); if (major == INVAL_MAJOR) { fprintf(stderr, "WARN: ran out of major numbers\n" " cannot start driver %s for %s\n", drv->name, path); return; } drv_inst->major = major; drv_inst->drv = drv; drv_inst->dev_id = dev_id; /* start driver (invoke service) */ start_driver(drv_inst); /* * run the up action * * An up action can be any executable. Before running it devmand * will set certain environment variables so the script can configure * the device (or generate device files, etc). See up_action() for that. */ if (drv->upscript) { ret = run_upscript(drv_inst); if (ret) { stop_driver(drv_inst); fprintf(stderr, "devmand: warning, could not run up_action\n"); free(drv_inst); return; } } LIST_INSERT_HEAD(&instances,drv_inst,list); } /*===========================================================================* * usb_intf_remove_event * *===========================================================================*/ static void usb_intf_remove_event(char *path, int dev_id) { struct devmand_driver_instance *inst; struct devmand_usb_driver *drv; int ret; /* find the driver instance */ inst = find_instance(dev_id); if (inst == NULL) { dbg("No driver running for id: %d", dev_id); return; } drv = inst->drv; /* run the down script */ if (drv->downscript) { ret = run_downscript(inst); if (ret) { fprintf(stderr, "WARN: error running up_action"); } } /* stop the driver */ stop_driver(inst); /* free major */ put_major(inst->major); /* free instance */ LIST_REMOVE(inst,list); free(inst); } /*===========================================================================* * handle_event * *===========================================================================*/ static void handle_event(char *event) { enum dev_type type; char path[PATH_LEN]; char tmp_path[PATH_LEN]; int dev_id, res; path[0]=0; if (strncmp("ADD ", event, 4) == 0) { /* read data from event */ res = sscanf(event, "ADD %s 0x%x", tmp_path, &dev_id); if (res != 2) { fprintf(stderr, "WARN: could not parse event: %s", event); fprintf(stderr, "WARN: omitting event: %s", event); } strcpy(path, args.path); strcat(path, tmp_path); /* what kind of device is added? */ type = determine_type(path); switch (type) { case DEV_TYPE_USB_DEVICE: dbg("USB device added: ommited...."); /* ommit usb devices for now */ break; case DEV_TYPE_USB_INTF: dbg("USB interface added: (%s, devid: = %d)",path, dev_id); usb_intf_add_event(path, dev_id); return; default: dbg("default"); fprintf(stderr, "WARN: ommiting event\n"); } } else if (strncmp("REMOVE ", event, 7) == 0) { /* read data from event */ res = sscanf(event,"REMOVE %s 0x%x", tmp_path, &dev_id); if (res != 2) { fprintf(stderr, "WARN: could not parse event: %s", event); fprintf(stderr, "WARN: omitting event: %s", event); } usb_intf_remove_event(path, dev_id); #if 0 strcpy(path, args.path); strcat(path, tmp_path); /* what kind of device is added? */ type = determine_type(path); switch (type) { case DEV_TYPE_USB_DEVICE: /* ommit usb devices for now */ break; case DEV_TYPE_USB_INTF: usb_intf_remove_event(path, dev_id); return; default: fprintf(stderr, "WARN: ommiting event\n"); } #endif } } /*===========================================================================* * main_loop * *===========================================================================*/ static void main_loop() { char ev_path[128]; char buf[256]; int len; FILE* fd; len = strlen(args.path); /* init major numbers */ memset(&major_bitmap, 0xff, 16); if (len > 128 - 7 /*len of "events" */) { fprintf(stderr, "pathname to long\n"); exit(1); } strcpy(ev_path, args.path); strcat(ev_path, "events"); while (_run) { char *res; fd = fopen(ev_path, "r"); if (fd == NULL) { fprintf(stderr, "ERROR: could not open event file...\n"); exit(1); } res = fgets(buf, 256, fd); fclose(fd); if (res == NULL) { usleep(50000); continue; } dbg("handle_event: %s", buf); handle_event(buf); } } /*===========================================================================* * display_usage * *===========================================================================*/ static void display_usage(const char *name) { printf("Usage: %s [{-p|--pathname} PATH_TO_SYS}" " [{-d|--config-dir} CONFIG_DIR] [-v|--verbose]" " [[x||--check-config]\n", name); }