$NetBSD$

Support asynchronous transfers by doing each one in a new thread.

This is expensive, but there does not appear to be a good alternative
with NetBSD's ugen(4) as is.

--- libusb/os/netbsd_usb.c.orig	2014-04-22 12:31:42.000000000 +0000
+++ libusb/os/netbsd_usb.c
@@ -21,6 +21,7 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <pthread.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -42,6 +43,13 @@ struct device_priv {
 struct handle_priv {
 	int pipe[2];				/* for event notification */
 	int endpoints[USB_MAX_ENDPOINTS];
+	pthread_mutex_t lock;			/* serialize endpoints */
+};
+
+struct transfer_priv {
+	pthread_t thread;
+	int (*transfer_method)(struct usbi_transfer *);
+	enum libusb_transfer_status status;
 };
 
 /*
@@ -86,6 +94,9 @@ static int _cache_active_config_descript
 static int _sync_control_transfer(struct usbi_transfer *);
 static int _sync_gen_transfer(struct usbi_transfer *);
 static int _access_endpoint(struct libusb_transfer *);
+static int _async_transfer(struct usbi_transfer *,
+    int (*)(struct usbi_transfer *));
+static void *_async_transfer_thread(void *);
 
 const struct usbi_os_backend netbsd_backend = {
 	"Synchronous NetBSD backend",
@@ -130,7 +141,7 @@ const struct usbi_os_backend netbsd_back
 	netbsd_clock_gettime,
 	sizeof(struct device_priv),
 	sizeof(struct handle_priv),
-	0,				/* transfer_priv_size */
+	sizeof(struct transfer_priv),
 	0,				/* add_iso_packet_size */
 };
 
@@ -212,20 +223,44 @@ netbsd_open(struct libusb_device_handle 
 {
 	struct handle_priv *hpriv = (struct handle_priv *)handle->os_priv;
 	struct device_priv *dpriv = (struct device_priv *)handle->dev->os_priv;
+	int err, ptherr;
 
 	dpriv->fd = open(dpriv->devnode, O_RDWR);
 	if (dpriv->fd < 0) {
 		dpriv->fd = open(dpriv->devnode, O_RDONLY);
-		if (dpriv->fd < 0)
-			return _errno_to_libusb(errno);
+		if (dpriv->fd < 0) {
+			err = _errno_to_libusb(errno);
+			goto fail0;
+		}
 	}
 
 	usbi_dbg("open %s: fd %d", dpriv->devnode, dpriv->fd);
 
-	if (pipe(hpriv->pipe) < 0)
-		return _errno_to_libusb(errno);
+	if (pipe(hpriv->pipe) < 0) {
+		err = _errno_to_libusb(errno);
+		goto fail1;
+	}
+
+	ptherr = pthread_mutex_init(&hpriv->lock, NULL);
+	if (ptherr) {
+		err = _errno_to_libusb(ptherr);
+		goto fail2;
+	}
+
+	err = usbi_add_pollfd(HANDLE_CTX(handle), hpriv->pipe[0], POLLIN);
+	if (err)
+		goto fail3;
+
+	return (0);
 
-	return usbi_add_pollfd(HANDLE_CTX(handle), hpriv->pipe[0], POLLIN);
+fail3:	pthread_mutex_destroy(&hpriv->lock);
+fail2:	close(hpriv->pipe[0]);
+	close(hpriv->pipe[1]);
+	hpriv->pipe[0] = -1;
+	hpriv->pipe[1] = -1;
+fail1:	close(dpriv->fd);
+	dpriv->fd = -1;
+fail0:	return (err);
 }
 
 void
@@ -236,13 +271,17 @@ netbsd_close(struct libusb_device_handle
 
 	usbi_dbg("close: fd %d", dpriv->fd);
 
-	close(dpriv->fd);
-	dpriv->fd = -1;
-
 	usbi_remove_pollfd(HANDLE_CTX(handle), hpriv->pipe[0]);
 
+	pthread_mutex_destroy(&hpriv->lock);
+
 	close(hpriv->pipe[0]);
 	close(hpriv->pipe[1]);
+	hpriv->pipe[0] = -1;
+	hpriv->pipe[1] = -1;
+
+	close(dpriv->fd);
+	dpriv->fd = -1;
 }
 
 int
@@ -442,7 +481,7 @@ netbsd_submit_transfer(struct usbi_trans
 
 	switch (transfer->type) {
 	case LIBUSB_TRANSFER_TYPE_CONTROL:
-		err = _sync_control_transfer(itransfer);
+		err = _async_transfer(itransfer, _sync_control_transfer);
 		break;
 	case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
 		if (IS_XFEROUT(transfer)) {
@@ -450,7 +489,7 @@ netbsd_submit_transfer(struct usbi_trans
 			err = LIBUSB_ERROR_NOT_SUPPORTED;
 			break;
 		}
-		err = _sync_gen_transfer(itransfer);
+		err = _async_transfer(itransfer, _sync_gen_transfer);
 		break;
 	case LIBUSB_TRANSFER_TYPE_BULK:
 	case LIBUSB_TRANSFER_TYPE_INTERRUPT:
@@ -459,7 +498,7 @@ netbsd_submit_transfer(struct usbi_trans
 			err = LIBUSB_ERROR_NOT_SUPPORTED;
 			break;
 		}
-		err = _sync_gen_transfer(itransfer);
+		err = _async_transfer(itransfer, _sync_gen_transfer);
 		break;
 	case LIBUSB_TRANSFER_TYPE_BULK_STREAM:
 		err = LIBUSB_ERROR_NOT_SUPPORTED;
@@ -469,9 +508,6 @@ netbsd_submit_transfer(struct usbi_trans
 	if (err)
 		return (err);
 
-	if (write(hpriv->pipe[1], &itransfer, sizeof(itransfer)) < 0)
-		return _errno_to_libusb(errno);
-
 	return (LIBUSB_SUCCESS);
 }
 
@@ -498,6 +534,7 @@ netbsd_handle_events(struct libusb_conte
 	struct libusb_device_handle *handle;
 	struct handle_priv *hpriv = NULL;
 	struct usbi_transfer *itransfer;
+	struct transfer_priv *tpriv;
 	struct pollfd *pollfd;
 	int i, err = 0;
 
@@ -524,7 +561,7 @@ netbsd_handle_events(struct libusb_conte
 
 		if (NULL == hpriv) {
 			usbi_dbg("fd %d is not an event pipe!", pollfd->fd);
-			err = ENOENT;
+			err = _errno_to_libusb(ENOENT);
 			break;
 		}
 
@@ -535,20 +572,18 @@ netbsd_handle_events(struct libusb_conte
 		}
 
 		if (read(hpriv->pipe[0], &itransfer, sizeof(itransfer)) < 0) {
-			err = errno;
+			err = _errno_to_libusb(errno);
 			break;
 		}
 
+		tpriv = usbi_transfer_get_os_priv(itransfer);
 		if ((err = usbi_handle_transfer_completion(itransfer,
-		    LIBUSB_TRANSFER_COMPLETED)))
+			    tpriv->status)) != 0)
 			break;
 	}
 	pthread_mutex_unlock(&ctx->open_devs_lock);
 
-	if (err)
-		return _errno_to_libusb(err);
-
-	return (LIBUSB_SUCCESS);
+	return (err);
 }
 
 int
@@ -625,7 +660,7 @@ _cache_active_config_descriptor(struct l
 	return (0);
 }
 
-int
+static int
 _sync_control_transfer(struct usbi_transfer *itransfer)
 {
 	struct libusb_transfer *transfer;
@@ -646,9 +681,9 @@ _sync_control_transfer(struct usbi_trans
 	req.ucr_request.bmRequestType = setup->bmRequestType;
 	req.ucr_request.bRequest = setup->bRequest;
 	/* Don't use USETW, libusb already deals with the endianness */
-	(*(uint16_t *)req.ucr_request.wValue) = setup->wValue;
-	(*(uint16_t *)req.ucr_request.wIndex) = setup->wIndex;
-	(*(uint16_t *)req.ucr_request.wLength) = setup->wLength;
+	memcpy(req.ucr_request.wValue, &setup->wValue, 2);
+	memcpy(req.ucr_request.wIndex, &setup->wIndex, 2);
+	memcpy(req.ucr_request.wLength, &setup->wLength, 2);
 	req.ucr_data = transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE;
 
 	if ((transfer->flags & LIBUSB_TRANSFER_SHORT_NOT_OK) == 0)
@@ -684,6 +719,10 @@ _access_endpoint(struct libusb_transfer 
 
 	usbi_dbg("endpoint %d mode %d", endpt, mode);
 
+	if (hpriv->endpoints[endpt] >= 0)
+		return hpriv->endpoints[endpt];
+
+	pthread_mutex_lock(&hpriv->lock);
 	if (hpriv->endpoints[endpt] < 0) {
 		/* Pick the right node given the control one */
 		strlcpy(devnode, dpriv->devnode, sizeof(devnode));
@@ -697,11 +736,12 @@ _access_endpoint(struct libusb_transfer 
 
 		hpriv->endpoints[endpt] = fd;
 	}
+	pthread_mutex_unlock(&hpriv->lock);
 
 	return (hpriv->endpoints[endpt]);
 }
 
-int
+static int
 _sync_gen_transfer(struct usbi_transfer *itransfer)
 {
 	struct libusb_transfer *transfer;
@@ -736,3 +776,63 @@ _sync_gen_transfer(struct usbi_transfer 
 
 	return (0);
 }
+
+static int
+_async_transfer(struct usbi_transfer *itransfer,
+    int (*transfer_method)(struct usbi_transfer *))
+{
+	pthread_attr_t attr;
+	struct transfer_priv *tpriv;
+	int ptherr;
+
+	tpriv = usbi_transfer_get_os_priv(itransfer);
+
+	/* Create a thread to do the transfer.  */
+	tpriv->transfer_method = transfer_method;
+	ptherr = pthread_attr_init(&attr);
+	if (ptherr)
+		goto fail0;
+	ptherr = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+	if (ptherr)
+		goto fail1;
+	ptherr = pthread_create(&tpriv->thread, &attr, _async_transfer_thread,
+	    itransfer);
+	if (ptherr)
+		goto fail1;
+
+	/* Success!  */
+	pthread_attr_destroy(&attr);
+	return (0);
+
+fail1:	pthread_attr_destroy(&attr);
+fail0:	return _errno_to_libusb(ptherr);
+}
+
+static void *
+_async_transfer_thread(void *cookie)
+{
+	struct usbi_transfer *itransfer = cookie;
+	struct libusb_transfer *transfer;
+	struct transfer_priv *tpriv;
+	struct handle_priv *hpriv;
+	int err;
+
+	transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
+	tpriv = usbi_transfer_get_os_priv(itransfer);
+	hpriv = (struct handle_priv *)transfer->dev_handle->os_priv;
+
+	/* Attempt the transfer.  */
+	err = (*tpriv->transfer_method)(itransfer);
+	if (err) {
+		tpriv->status = LIBUSB_TRANSFER_ERROR;
+		goto out;
+	}
+	tpriv->status = LIBUSB_TRANSFER_COMPLETED;
+
+out:
+	/* Can't do anything if this fails.  */
+	if (write(hpriv->pipe[1], &itransfer, sizeof(itransfer)) < 0)
+		usbi_err(TRANSFER_CTX(transfer), "write failed (%d): %s",
+		    errno, strerror(errno));
+	return (NULL);
+}