Commit cb3e3ebfac5edd8ff696eaa31913ea5812bd4b05

Kano 2013-01-23T20:37:28

USB system wide device locking on linux

diff --git a/usbutils.c b/usbutils.c
index 31489a0..2e758a0 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 <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/sem.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+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]);
 		}
 	}