Index: uspace/lib/usbdev/src/pipes.c
===================================================================
--- uspace/lib/usbdev/src/pipes.c	(revision fdc2253b3ae7378b237bd037a2075887d6336b2d)
+++ uspace/lib/usbdev/src/pipes.c	(revision af16ebece2cb3bc7592cffdd6e1a6da6698dc8db)
@@ -61,4 +61,90 @@
 }
 
+typedef struct {
+	usb_pipe_t *pipe;
+	usb_direction_t dir;
+	bool is_control;
+	uint64_t setup;
+	void *buffer;
+	size_t buffer_size;
+	size_t transferred_size;
+} transfer_t;
+
+/**
+ * Issue a transfer in a separate exchange.
+ */
+static errno_t transfer_common(transfer_t *t)
+{
+	async_exch_t *exch = async_exchange_begin(t->pipe->bus_session);
+	if (!exch)
+		return ENOMEM;
+
+	const errno_t rc = usbhc_transfer(exch, t->pipe->desc.endpoint_no,
+	    t->dir, t->setup, t->buffer, t->buffer_size, &t->transferred_size);
+
+	async_exchange_end(exch);
+
+	if (rc == ESTALL)
+		clear_self_endpoint_halt(t->pipe);
+
+	return rc;
+}
+
+/**
+ * Compatibility wrapper for reads/writes without preallocated buffer.
+ */
+static errno_t transfer_wrap_dma(transfer_t *t)
+{
+	void *orig_buffer = t->buffer;
+	t->buffer = usb_pipe_alloc_buffer(t->pipe, t->buffer_size);
+
+	if (t->dir == USB_DIRECTION_OUT)
+		memcpy(t->buffer, orig_buffer, t->buffer_size);
+
+	const errno_t err = transfer_common(t);
+
+	if (!err && t->dir == USB_DIRECTION_IN)
+		memcpy(orig_buffer, t->buffer, t->transferred_size);
+
+	usb_pipe_free_buffer(t->pipe, t->buffer);
+	t->buffer = orig_buffer;
+	return err;
+}
+
+static errno_t transfer_check(const transfer_t *t)
+{
+	if (!t->pipe)
+		return EBADMEM;
+
+	/* Only control writes make sense without buffer */
+	if ((t->dir != USB_DIRECTION_OUT || !t->is_control)
+	    && (t->buffer == NULL || t->buffer_size == 0))
+		return EINVAL;
+
+	/* Nonzero size requires buffer */
+	if (t->buffer == NULL && t->buffer_size != 0)
+		return EINVAL;
+
+	/* Check expected direction */
+	if (t->pipe->desc.direction != USB_DIRECTION_BOTH &&
+	    t->pipe->desc.direction != t->dir)
+		return EBADF;
+
+	/* Check expected transfer type */
+	if ((t->pipe->desc.transfer_type == USB_TRANSFER_CONTROL) != t->is_control)
+		return EBADF;
+
+	return EOK;
+}
+
+static errno_t prepare_control(transfer_t *t, const void *setup, size_t setup_size)
+{
+	if ((setup == NULL) || (setup_size != 8))
+		return EINVAL;
+	
+	memcpy(&t->setup, setup, 8);
+	return EOK;
+}
+
 /** Request a control read transfer on an endpoint pipe.
  *
@@ -78,37 +164,26 @@
     void *buffer, size_t buffer_size, size_t *transferred_size)
 {
-	assert(pipe);
-
-	if ((setup_buffer == NULL) || (setup_buffer_size != 8)) {
-		return EINVAL;
-	}
-
-	if ((buffer == NULL) || (buffer_size == 0)) {
-		return EINVAL;
-	}
-
-	if ((pipe->desc.direction != USB_DIRECTION_BOTH)
-	    || (pipe->desc.transfer_type != USB_TRANSFER_CONTROL)) {
-		return EBADF;
-	}
-
-	uint64_t setup_packet;
-	memcpy(&setup_packet, setup_buffer, 8);
-
-	async_exch_t *exch = async_exchange_begin(pipe->bus_session);
-	size_t act_size = 0;
-	const errno_t rc = usbhc_read(exch, pipe->desc.endpoint_no, setup_packet, buffer,
-	    buffer_size, &act_size);
-	async_exchange_end(exch);
-
-	if (rc == ESTALL) {
-		clear_self_endpoint_halt(pipe);
-	}
-
-	if (rc == EOK && transferred_size != NULL) {
-		*transferred_size = act_size;
-	}
-
-	return rc;
+	errno_t err;
+	transfer_t transfer = {
+		.pipe = pipe,
+		.dir = USB_DIRECTION_IN,
+		.is_control = true,
+		.buffer = buffer,
+		.buffer_size = buffer_size
+	};
+
+	if ((err = transfer_check(&transfer)))
+		return err;
+
+	if ((err = prepare_control(&transfer, setup_buffer, setup_buffer_size)))
+		return err;
+
+	if ((err = transfer_wrap_dma(&transfer)))
+		return err;
+
+	if (transferred_size)
+		*transferred_size = transfer.transferred_size;
+
+	return EOK;
 }
 
@@ -129,35 +204,20 @@
 {
 	assert(pipe);
-
-	if ((setup_buffer == NULL) || (setup_buffer_size != 8)) {
-		return EINVAL;
-	}
-
-	if ((buffer == NULL) && (buffer_size > 0)) {
-		return EINVAL;
-	}
-
-	if ((buffer != NULL) && (buffer_size == 0)) {
-		return EINVAL;
-	}
-
-	if ((pipe->desc.direction != USB_DIRECTION_BOTH)
-	    || (pipe->desc.transfer_type != USB_TRANSFER_CONTROL)) {
-		return EBADF;
-	}
-
-	uint64_t setup_packet;
-	memcpy(&setup_packet, setup_buffer, 8);
-
-	async_exch_t *exch = async_exchange_begin(pipe->bus_session);
-	const errno_t rc = usbhc_write(exch,
-	    pipe->desc.endpoint_no, setup_packet, buffer, buffer_size);
-	async_exchange_end(exch);
-
-	if (rc == ESTALL) {
-		clear_self_endpoint_halt(pipe);
-	}
-
-	return rc;
+	errno_t err;
+	transfer_t transfer = {
+		.pipe = pipe,
+		.dir = USB_DIRECTION_OUT,
+		.is_control = true,
+		.buffer = (void *) buffer,
+		.buffer_size = buffer_size
+	};
+
+	if ((err = transfer_check(&transfer)))
+		return err;
+
+	if ((err = prepare_control(&transfer, setup_buffer, setup_buffer_size)))
+		return err;
+
+	return transfer_wrap_dma(&transfer);
 }
 
@@ -197,32 +257,22 @@
 {
 	assert(pipe);
-
-	if (buffer == NULL) {
-		return EINVAL;
-	}
-
-	if (size == 0) {
-		return EINVAL;
-	}
-
-	if (pipe->desc.direction != USB_DIRECTION_IN) {
-		return EBADF;
-	}
-
-	if (pipe->desc.transfer_type == USB_TRANSFER_CONTROL) {
-		return EBADF;
-	}
-
-	async_exch_t *exch = async_exchange_begin(pipe->bus_session);
-	size_t act_size = 0;
-	const errno_t rc =
-	    usbhc_read(exch, pipe->desc.endpoint_no, 0, buffer, size, &act_size);
-	async_exchange_end(exch);
-
-	if (rc == EOK && size_transferred != NULL) {
-		*size_transferred = act_size;
-	}
-
-	return rc;
+	errno_t err;
+	transfer_t transfer = {
+		.pipe = pipe,
+		.dir = USB_DIRECTION_IN,
+		.buffer = buffer,
+		.buffer_size = size,
+	};
+
+	if ((err = transfer_check(&transfer)))
+		return err;
+
+	if ((err = transfer_wrap_dma(&transfer)))
+		return err;
+
+	if (size_transferred)
+		*size_transferred = transfer.transferred_size;
+
+	return EOK;
 }
 
@@ -237,21 +287,19 @@
 {
 	assert(pipe);
-
-	if (buffer == NULL || size == 0) {
-		return EINVAL;
-	}
-
-	if (pipe->desc.direction != USB_DIRECTION_OUT) {
-		return EBADF;
-	}
-
-	if (pipe->desc.transfer_type == USB_TRANSFER_CONTROL) {
-		return EBADF;
-	}
-
-	async_exch_t *exch = async_exchange_begin(pipe->bus_session);
-	const errno_t rc = usbhc_write(exch, pipe->desc.endpoint_no, 0, buffer, size);
-	async_exchange_end(exch);
-	return rc;
+	errno_t err;
+	transfer_t transfer = {
+		.pipe = pipe,
+		.dir = USB_DIRECTION_OUT,
+		.buffer = (void *) buffer,
+		.buffer_size = size
+	};
+
+	if ((err = transfer_check(&transfer)))
+		return err;
+
+	if ((err = transfer_wrap_dma(&transfer)))
+		return err;
+
+	return EOK;
 }
 
@@ -270,20 +318,22 @@
 {
 	assert(pipe);
-
-	if (buffer == NULL || size == 0)
-		return EINVAL;
-
-	if (pipe->desc.direction != USB_DIRECTION_IN
-	    || pipe->desc.transfer_type == USB_TRANSFER_CONTROL)
-		return EBADF;
-
-	async_exch_t *exch = async_exchange_begin(pipe->bus_session);
-	if (!exch)
-		return ENOMEM;
-
-	const errno_t rc = usbhc_transfer(exch, pipe->desc.endpoint_no,
-	    USB_DIRECTION_IN, 0, buffer, size, size_transferred);
-	async_exchange_end(exch);
-	return rc;
+	errno_t err;
+	transfer_t transfer = {
+		.pipe = pipe,
+		.dir = USB_DIRECTION_IN,
+		.buffer = buffer,
+		.buffer_size = size
+	};
+
+	if ((err = transfer_check(&transfer)))
+		return err;
+
+	if ((err = transfer_common(&transfer)))
+		return err;
+
+	if (size_transferred)
+		*size_transferred = transfer.transferred_size;
+
+	return EOK;
 }
 
@@ -300,20 +350,19 @@
 {
 	assert(pipe);
-
-	if (buffer == NULL || size == 0) {
-		return EINVAL;
-	}
-
-	if (pipe->desc.direction != USB_DIRECTION_OUT
-	    || pipe->desc.transfer_type == USB_TRANSFER_CONTROL)
-		return EBADF;
-
-	async_exch_t *exch = async_exchange_begin(pipe->bus_session);
-	if (!exch)
-		return ENOMEM;
-
-	const errno_t rc = usbhc_transfer(exch, pipe->desc.endpoint_no, USB_DIRECTION_OUT, 0, buffer, size, NULL);
-	async_exchange_end(exch);
-	return rc;
+	errno_t err;
+	transfer_t transfer = {
+		.pipe = pipe,
+		.dir = USB_DIRECTION_OUT,
+		.buffer = buffer,
+		.buffer_size = size
+	};
+
+	if ((err = transfer_check(&transfer)))
+		return err;
+
+	if ((err = transfer_common(&transfer)))
+		return err;
+
+	return EOK;
 }
 
