From cb3e3ebfac5edd8ff696eaa31913ea5812bd4b05 Mon Sep 17 00:00:00 2001 From: Kano Date: Wed, 23 Jan 2013 20:37:28 +1100 Subject: [PATCH] USB system wide device locking on linux --- usbutils.c | 286 +++++++++++++++++++++++++++-------------------------- 1 file changed, 144 insertions(+), 142 deletions(-) diff --git a/usbutils.c b/usbutils.c index 31489a08..2e758a07 100644 --- a/usbutils.c +++ b/usbutils.c @@ -110,24 +110,10 @@ extern struct device_drv icarus_drv; extern struct device_drv modminer_drv; #endif -/* - * Our own internal list of used USB devices - * So two drivers or a single driver searching - * can't touch the same device during detection - */ -struct usb_list { - uint8_t bus_number; - uint8_t device_address; - uint8_t filler[2]; - struct usb_list *prev; - struct usb_list *next; -}; - #define STRBUFLEN 256 static const char *BLANK = ""; -static pthread_mutex_t *list_lock = NULL; -static struct usb_list *usb_head = NULL; +static bool stats_initialised = false; struct cg_usb_stats_item { uint64_t count; @@ -537,10 +523,7 @@ static void cgusb_check_init() { mutex_lock(&cgusb_lock); - if (list_lock == NULL) { - list_lock = calloc(1, sizeof(*list_lock)); - mutex_init(list_lock); - + if (stats_initialised == false) { // N.B. environment LIBUSB_DEBUG also sets libusb_set_debug() if (opt_usbdump >= 0) { libusb_set_debug(NULL, opt_usbdump); @@ -592,134 +575,163 @@ static void cgusb_check_init() mutex_unlock(&cgusb_lock); } -static bool in_use(libusb_device *dev, bool lock) -{ - struct usb_list *usb_tmp; - bool used = false; - uint8_t bus_number; - uint8_t device_address; - - bus_number = libusb_get_bus_number(dev); - device_address = libusb_get_device_address(dev); - - if (lock) - mutex_lock(list_lock); - - if ((usb_tmp = usb_head)) - do { - if (bus_number == usb_tmp->bus_number - && device_address == usb_tmp->device_address) { - used = true; - break; - } - - usb_tmp = usb_tmp->next; - - } while (usb_tmp != usb_head); - - if (lock) - mutex_unlock(list_lock); - - return used; -} +#ifdef WIN32 +#else +#include +#include +#include +#include +#include +#include +#include +#include + +union semun { + int sem; + struct semid_ds *seminfo; + ushort *all; +}; +#endif -static void add_used(libusb_device *dev, bool lock) +// Any errors should always be printed since they will rarely if ever occur +// and thus it is best to always display them +static bool cgminer_usb_lock_bd(struct device_drv *drv, uint8_t bus_number, uint8_t device_address) { - struct usb_list *usb_tmp; - char buf[128]; - uint8_t bus_number; - uint8_t device_address; - - bus_number = libusb_get_bus_number(dev); - device_address = libusb_get_device_address(dev); - - if (lock) - mutex_lock(list_lock); +#ifdef WIN32 +#else + struct semid_ds seminfo; + union semun opt; + char name[64]; + key_t key; + int fd, sem, count; + + sprintf(name, "/tmp/cgminer-usb-%d-%d", (int)bus_number, (int)device_address); + fd = open(name, O_CREAT|O_RDONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); + if (fd == -1) { + applog(LOG_ERR, + "SEM: %s USB open failed '%s' err (%d) %s", + drv->dname, name, errno, strerror(errno)); + return false; + } + close(fd); + key = ftok(name, 'K'); + sem = semget(key, 1, IPC_CREAT | IPC_EXCL | 438); + if (sem < 0) { + if (errno != EEXIST) { + applog(LOG_ERR, + "SEM: %s USB failed to get '%s' err (%d) %s", + drv->dname, name, errno, strerror(errno)); + return false; + } - if (in_use(dev, false)) { - if (lock) - mutex_unlock(list_lock); + sem = semget(key, 1, 0); + if (sem < 0) { + applog(LOG_ERR, + "SEM: %s USB failed to access '%s' err (%d) %s", + drv->dname, name, errno, strerror(errno)); + return false; + } - sprintf(buf, "add_used() duplicate bus_number %d device_address %d", - (int)bus_number, (int)device_address); - quit(1, buf); + opt.seminfo = &seminfo; + count = 0; + while (++count) { + // Should NEVER take 100ms + if (count > 99) { + applog(LOG_ERR, + "SEM: %s USB timeout waiting for (%d) '%s'", + drv->dname, sem, name); + return false; + } + if (semctl(sem, 0, IPC_STAT, opt) == -1) { + applog(LOG_ERR, + "SEM: %s USB failed to wait for (%d) '%s' count %d err (%d) %s", + drv->dname, sem, name, count, errno, strerror(errno)); + return false; + } + if (opt.seminfo->sem_otime != 0) + break; + nmsleep(1); + } } - usb_tmp = malloc(sizeof(*usb_tmp)); - - usb_tmp->bus_number = bus_number; - usb_tmp->device_address = device_address; - - if (usb_head) { - // add to end - usb_tmp->prev = usb_head->prev; - usb_tmp->next = usb_head; - usb_head->prev = usb_tmp; - usb_tmp->prev->next = usb_tmp; - } else { - usb_tmp->prev = usb_tmp; - usb_tmp->next = usb_tmp; - usb_head = usb_tmp; + struct sembuf sops[] = { + { 0, 0, IPC_NOWAIT | SEM_UNDO }, + { 0, 1, IPC_NOWAIT | SEM_UNDO } + }; + + if (semop(sem, sops, 2)) { + if (errno == EAGAIN) { + applog(LOG_WARNING, + "SEM: %s USB failed to get (%d) '%s' - device in use", + drv->dname, sem, name); + } else { + applog(LOG_DEBUG, + "SEM: %s USB failed to get (%d) '%s' err(%d) %s", + drv->dname, sem, name, errno, strerror(errno)); + } + return false; } - if (lock) - mutex_unlock(list_lock); + return true; +#endif } -static void release(uint8_t bus_number, uint8_t device_address, bool lock) +static bool cgminer_usb_lock(struct device_drv *drv, libusb_device *dev) { - struct usb_list *usb_tmp; - bool found = false; - char buf[128]; - - if (lock) - mutex_lock(list_lock); - - if ((usb_tmp = usb_head)) - do { - if (bus_number == usb_tmp->bus_number - && device_address == usb_tmp->device_address) { - found = true; - break; - } - - usb_tmp = usb_tmp->next; + return cgminer_usb_lock_bd(drv, libusb_get_bus_number(dev), libusb_get_device_address(dev)); +} - } while (usb_tmp != usb_head); +// Any errors should always be printed since they will rarely if ever occur +// and thus it is best to always display them +static void cgminer_usb_unlock_bd(struct device_drv *drv, uint8_t bus_number, uint8_t device_address) +{ +#ifdef WIN32 +#else + char name[64]; + key_t key; + int fd, sem; + + sprintf(name, "/tmp/cgminer-usb-%d-%d", (int)bus_number, (int)device_address); + fd = open(name, O_CREAT|O_RDONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); + if (fd == -1) { + applog(LOG_ERR, + "SEM: %s USB open failed '%s' for release err (%d) %s", + drv->dname, name, errno, strerror(errno)); + return; + } + close(fd); + key = ftok(name, 'K'); + + sem = semget(key, 1, 0); + if (sem < 0) { + applog(LOG_ERR, + "SEM: %s USB failed to get '%s' for release err (%d) %s", + drv->dname, name, errno, strerror(errno)); + return; + } - if (!found) { - if (lock) - mutex_unlock(list_lock); + struct sembuf sops[] = { + { 0, -1, SEM_UNDO } + }; - sprintf(buf, "release() unknown: bus_number %d device_address %d", - (int)bus_number, (int)device_address); - quit(1, buf); - } + // Allow a 10ms timeout + // exceeding this timeout means it would probably never succeed anyway + struct timespec timeout = { 0, 10000000 }; - if (usb_tmp->next == usb_tmp) { - usb_head = NULL; - } else { - if (usb_head == usb_tmp) - usb_head = usb_tmp->next; - usb_tmp->next->prev = usb_tmp->prev; - usb_tmp->prev->next = usb_tmp->next; + // Wait forever since we shoud be the one who has it + if (semtimedop(sem, sops, 1, &timeout)) { + applog(LOG_ERR, + "SEM: %d USB failed to release '%s' err (%d) %s", + drv->dname, name, errno, strerror(errno)); } - if (lock) - mutex_unlock(list_lock); - - free(usb_tmp); + return; +#endif } -static void release_dev(libusb_device *dev, bool lock) +static void cgminer_usb_unlock(struct device_drv *drv, libusb_device *dev) { - uint8_t bus_number; - uint8_t device_address; - - bus_number = libusb_get_bus_number(dev); - device_address = libusb_get_device_address(dev); - - release(bus_number, device_address, lock); + cgminer_usb_unlock_bd(drv, libusb_get_bus_number(dev), libusb_get_device_address(dev)); } static struct cg_usb_device *free_cgusb(struct cg_usb_device *cgusb) @@ -771,7 +783,7 @@ void release_cgpu(struct cgpu_info *cgpu) usb_uninit(cgpu); - release(cgpu->usbinfo.bus_number, cgpu->usbinfo.device_address, true); + cgminer_usb_unlock_bd(cgpu->drv, cgpu->usbinfo.bus_number, cgpu->usbinfo.device_address); } bool usb_init(struct cgpu_info *cgpu, struct libusb_device *dev, struct usb_find_devices *found) @@ -1053,21 +1065,11 @@ void usb_detect(struct device_drv *drv, bool (*device_detect)(struct libusb_devi applog(LOG_DEBUG, "USB scan devices: found no devices"); for (i = 0; i < count; i++) { - mutex_lock(list_lock); - - if (in_use(list[i], false)) - mutex_unlock(list_lock); - else { - add_used(list[i], false); - - mutex_unlock(list_lock); - - found = usb_check(drv, list[i]); - if (!found) - release_dev(list[i], true); - else + found = usb_check(drv, list[i]); + if (found) { + if (cgminer_usb_lock(drv, list[i]) == true) if (!device_detect(list[i], found)) - release_dev(list[i], true); + cgminer_usb_unlock(drv, list[i]); } }