Index: uspace/lib/usbhost/src/bus.c
===================================================================
--- uspace/lib/usbhost/src/bus.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/lib/usbhost/src/bus.c	(revision 95f1b8fb04a16c3932f15cfbed3adcd434b47bbb)
@@ -46,4 +46,5 @@
 #include <str_error.h>
 #include <usb/debug.h>
+#include <usb/dma_buffer.h>
 
 #include "endpoint.h"
@@ -389,4 +390,6 @@
 		endpoint_init(ep, device, desc);
 	}
+
+	assert((ep->required_transfer_buffer_policy & ~ep->transfer_buffer_policy) == 0);
 
 	/* Bus reference */
@@ -557,29 +560,39 @@
 
 /**
- * Initiate a transfer on the bus. Finds the target endpoint, creates
- * a transfer batch and schedules it.
- *
- * @param device Device for which to send the batch
- * @param target The target of the transfer.
- * @param direction A direction of the transfer.
- * @param data A pointer to the data buffer.
- * @param size Size of the data buffer.
- * @param setup_data Data to use in the setup stage (Control communication type)
- * @param on_complete Callback which is called after the batch is complete
- * @param arg Callback parameter.
- * @param name Communication identifier (for nicer output).
+ * Assert some conditions on transfer request. As the request is an entity of
+ * HC driver only, we can force these conditions harder. Invalid values from
+ * devices shall be caught on DDF interface already.
+ */
+static void check_request(const transfer_request_t *request)
+{
+	assert(usb_target_is_valid(&request->target));
+	assert(request->dir != USB_DIRECTION_BOTH);
+	/* Non-zero offset => size is non-zero */
+	assert(request->offset == 0 || request->size != 0);
+	/* Non-zero size => buffer is set */
+	assert(request->size == 0 || dma_buffer_is_set(&request->buffer));
+	/* Non-null arg => callback is set */
+	assert(request->arg == NULL || request->on_complete != NULL);
+	assert(request->name);
+}
+
+/**
+ * Initiate a transfer with given device.
+ *
  * @return Error code.
  */
-int bus_device_send_batch(device_t *device, usb_target_t target,
-    usb_direction_t direction, char *data, size_t size, uint64_t setup_data,
-    usbhc_iface_transfer_callback_t on_complete, void *arg, const char *name)
-{
-	assert(device->address == target.address);
+int bus_issue_transfer(device_t *device, const transfer_request_t *request)
+{
+	assert(device);
+	assert(request);
+
+	check_request(request);
+	assert(device->address == request->target.address);
 
 	/* Temporary reference */
-	endpoint_t *ep = bus_find_endpoint(device, target.endpoint, direction);
+	endpoint_t *ep = bus_find_endpoint(device, request->target.endpoint, request->dir);
 	if (ep == NULL) {
 		usb_log_error("Endpoint(%d:%d) not registered for %s.",
-		    device->address, target.endpoint, name);
+		    device->address, request->target.endpoint, request->name);
 		return ENOENT;
 	}
@@ -587,17 +600,5 @@
 	assert(ep->device == device);
 
-	/*
-	 * This method is already callable from HC only, so we can force these
-	 * conditions harder.
-	 * Invalid values from devices shall be caught on DDF interface already.
-	 */
-	assert(usb_target_is_valid(&target));
-	assert(direction != USB_DIRECTION_BOTH);
-	assert(size == 0 || data != NULL);
-	assert(arg == NULL || on_complete != NULL);
-	assert(name);
-
-	const int err = endpoint_send_batch(ep, target, direction,
-	    data, size, setup_data, on_complete, arg, name);
+	const int err = endpoint_send_batch(ep, request);
 
 	/* Temporary reference */
@@ -650,12 +651,28 @@
     const char *name, size_t *transferred_size)
 {
+	int err;
 	sync_data_t sd = { .done = false };
 	fibril_mutex_initialize(&sd.done_mtx);
 	fibril_condvar_initialize(&sd.done_cv);
 
-	const int ret = bus_device_send_batch(device, target, direction,
-	    data, size, setup_data, sync_transfer_complete, &sd, name);
-	if (ret != EOK)
-		return ret;
+	transfer_request_t request = {
+		.target = target,
+		.dir = direction,
+		.offset = ((uintptr_t) data) % PAGE_SIZE,
+		.size = size,
+		.setup = setup_data,
+		.on_complete = sync_transfer_complete,
+		.arg = &sd,
+		.name = name,
+	};
+
+	if (data &&
+	    (err = dma_buffer_lock(&request.buffer, data - request.offset, size)))
+		return err;
+
+	if ((err = bus_issue_transfer(device, &request))) {
+		dma_buffer_unlock(&request.buffer, size);
+		return err;
+	}
 
 	/*
@@ -668,4 +685,6 @@
 	fibril_mutex_unlock(&sd.done_mtx);
 
+	dma_buffer_unlock(&request.buffer, size);
+
 	if (transferred_size)
 		*transferred_size = sd.transferred_size;
Index: uspace/lib/usbhost/src/ddf_helpers.c
===================================================================
--- uspace/lib/usbhost/src/ddf_helpers.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/lib/usbhost/src/ddf_helpers.c	(revision 95f1b8fb04a16c3932f15cfbed3adcd434b47bbb)
@@ -46,4 +46,5 @@
 #include <usb/descriptor.h>
 #include <usb/usb.h>
+#include <usb/dma_buffer.h>
 #include <usb_iface.h>
 #include <usbhc_iface.h>
@@ -271,5 +272,6 @@
  * @return Error code.
  */
-static errno_t transfer(ddf_fun_t *fun, const usbhc_iface_transfer_request_t *req,
+static errno_t transfer(ddf_fun_t *fun,
+    const usbhc_iface_transfer_request_t *ifreq,
     usbhc_iface_transfer_callback_t callback, void *arg)
 {
@@ -280,6 +282,6 @@
 	const usb_target_t target = {{
 		.address = dev->address,
-		.endpoint = req->endpoint,
-		.stream = req->stream,
+		.endpoint = ifreq->endpoint,
+		.stream = ifreq->stream,
 	}};
 
@@ -287,5 +289,8 @@
 		return EINVAL;
 
-	if (req->size > 0 && req->base == NULL)
+	if (ifreq->offset > 0 && ifreq->size == 0)
+		return EINVAL;
+
+	if (ifreq->size > 0 && !dma_buffer_is_set(&ifreq->buffer))
 		return EBADMEM;
 
@@ -293,11 +298,17 @@
 		return EBADMEM;
 
-	const char *name = (req->dir == USB_DIRECTION_IN) ? "READ" : "WRITE";
-
-	char *buffer = req->base + req->offset;
-
-	return bus_device_send_batch(dev, target, req->dir,
-	    buffer, req->size, req->setup,
-	    callback, arg, name);
+	const transfer_request_t request = {
+		.target = target,
+		.dir = ifreq->dir,
+		.buffer = ifreq->buffer,
+		.offset = ifreq->offset,
+		.size = ifreq->size,
+		.setup = ifreq->setup,
+		.on_complete = callback,
+		.arg = arg,
+		.name = (ifreq->dir == USB_DIRECTION_IN) ? "READ" : "WRITE",
+	};
+
+	return bus_issue_transfer(dev, &request);
 }
 
Index: uspace/lib/usbhost/src/endpoint.c
===================================================================
--- uspace/lib/usbhost/src/endpoint.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/lib/usbhost/src/endpoint.c	(revision 95f1b8fb04a16c3932f15cfbed3adcd434b47bbb)
@@ -75,4 +75,5 @@
 	ep->max_transfer_size = ep->max_packet_size * ep->packets_per_uframe;
 	ep->transfer_buffer_policy = DMA_POLICY_STRICT;
+	ep->required_transfer_buffer_policy = DMA_POLICY_STRICT;
 }
 
@@ -207,27 +208,19 @@
  *
  * @param endpoint Endpoint for which to send the batch
- * @param target The target of the transfer.
- * @param direction A direction of the transfer.
- * @param data A pointer to the data buffer.
- * @param size Size of the data buffer.
- * @param setup_data Data to use in the setup stage (Control communication type)
- * @param on_complete Callback which is called after the batch is complete
- * @param arg Callback parameter.
- * @param name Communication identifier (for nicer output).
- */
-errno_t endpoint_send_batch(endpoint_t *ep, usb_target_t target,
-    usb_direction_t direction, char *data, size_t size, uint64_t setup_data,
-    usbhc_iface_transfer_callback_t on_complete, void *arg, const char *name)
-{
-	if (!ep)
-		return EBADMEM;
+ */
+errno_t endpoint_send_batch(endpoint_t *ep, const transfer_request_t *req)
+{
+	assert(ep);
+	assert(req);
 
 	if (ep->transfer_type == USB_TRANSFER_CONTROL) {
-		usb_log_debug("%s %d:%d %zu/%zuB, setup %#016" PRIx64, name,
-		    target.address, target.endpoint, size, ep->max_packet_size,
-		    setup_data);
+		usb_log_debug("%s %d:%d %zu/%zuB, setup %#016" PRIx64, req->name,
+		    req->target.address, req->target.endpoint,
+		    req->size, ep->max_packet_size,
+		    req->setup);
 	} else {
-		usb_log_debug("%s %d:%d %zu/%zuB", name, target.address,
-		    target.endpoint, size, ep->max_packet_size);
+		usb_log_debug("%s %d:%d %zu/%zuB", req->name,
+		    req->target.address, req->target.endpoint,
+		    req->size, ep->max_packet_size);
 	}
 
@@ -244,14 +237,16 @@
 	}
 
+	size_t size = req->size;
 	/*
 	 * Limit transfers with reserved bandwidth to the amount reserved.
 	 * OUT transfers are rejected, IN can be just trimmed in advance.
 	 */
-	if ((ep->transfer_type == USB_TRANSFER_INTERRUPT || ep->transfer_type == USB_TRANSFER_ISOCHRONOUS) && size > ep->max_transfer_size) {
-		if (direction == USB_DIRECTION_OUT)
+	if (size > ep->max_transfer_size &&
+	    (ep->transfer_type == USB_TRANSFER_INTERRUPT
+	     || ep->transfer_type == USB_TRANSFER_ISOCHRONOUS)) {
+		if (req->dir == USB_DIRECTION_OUT)
 			return ENOSPC;
 		else
 			size = ep->max_transfer_size;
-
 	}
 
@@ -266,18 +261,23 @@
 	}
 
-	batch->target = target;
-	batch->setup.packed = setup_data;
-	batch->dir = direction;
-	batch->buffer_size = size;
-
-	errno_t err;
-	if ((err = usb_transfer_batch_prepare_buffer(batch, data))) {
-		usb_log_warning("Failed to prepare buffer for batch: %s", str_error(err));
-		usb_transfer_batch_destroy(batch);
-		return err;
-	}
-
-	batch->on_complete = on_complete;
-	batch->on_complete_data = arg;
+	batch->target = req->target;
+	batch->setup.packed = req->setup;
+	batch->dir = req->dir;
+	batch->size = size;
+	batch->offset = req->offset;
+	batch->dma_buffer = req->buffer;
+
+	dma_buffer_acquire(&batch->dma_buffer);
+
+	if (batch->offset != 0) {
+		usb_log_debug("A transfer with nonzero offset requested.");
+		usb_transfer_batch_bounce(batch);
+	}
+
+	if (usb_transfer_batch_bounce_required(batch))
+		usb_transfer_batch_bounce(batch);
+
+	batch->on_complete = req->on_complete;
+	batch->on_complete_data = req->arg;
 
 	const int ret = ops->batch_schedule(batch);
Index: uspace/lib/usbhost/src/usb_transfer_batch.c
===================================================================
--- uspace/lib/usbhost/src/usb_transfer_batch.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/lib/usbhost/src/usb_transfer_batch.c	(revision 95f1b8fb04a16c3932f15cfbed3adcd434b47bbb)
@@ -103,4 +103,34 @@
 }
 
+bool usb_transfer_batch_bounce_required(usb_transfer_batch_t *batch)
+{
+	if (!batch->size)
+		return false;
+
+	unsigned flags = batch->dma_buffer.policy & DMA_POLICY_FLAGS_MASK;
+	unsigned required_flags =
+	    batch->ep->required_transfer_buffer_policy & DMA_POLICY_FLAGS_MASK;
+
+	if (required_flags & ~flags)
+		return true;
+
+	size_t chunk_mask = dma_policy_chunk_mask(batch->dma_buffer.policy);
+	size_t required_chunk_mask =
+	     dma_policy_chunk_mask(batch->ep->required_transfer_buffer_policy);
+
+	/* If the chunks are at least as large as required, we're good */
+	if ((required_chunk_mask & ~chunk_mask) == 0)
+		return false;
+
+	size_t start_chunk = batch->offset & ~chunk_mask;
+	size_t end_chunk = (batch->offset + batch->size - 1) & ~chunk_mask;
+
+	/* The requested area crosses a chunk boundary */
+	if (start_chunk != end_chunk)
+		return true;
+
+	return false;
+}
+
 errno_t usb_transfer_batch_bounce(usb_transfer_batch_t *batch)
 {
@@ -108,6 +138,7 @@
 	assert(!batch->is_bounced);
 
-	if (dma_buffer_is_set(&batch->dma_buffer))
-		dma_buffer_unlock(&batch->dma_buffer, batch->buffer_size);
+	dma_buffer_release(&batch->dma_buffer);
+
+	batch->original_buffer = batch->dma_buffer.virt + batch->offset;
 
 	usb_log_debug("Batch(%p): Buffer cannot be used directly, "
@@ -115,5 +146,5 @@
 
 	const errno_t err = dma_buffer_alloc_policy(&batch->dma_buffer,
-	    batch->buffer_size, batch->ep->transfer_buffer_policy);
+	    batch->size, batch->ep->transfer_buffer_policy);
 	if (err)
 		return err;
@@ -121,35 +152,12 @@
 	/* Copy the data out */
 	if (batch->dir == USB_DIRECTION_OUT)
-		memcpy(batch->dma_buffer.virt, batch->buffer, batch->buffer_size);
+		memcpy(batch->dma_buffer.virt,
+		    batch->original_buffer,
+		    batch->size);
 
 	batch->is_bounced = true;
+	batch->offset = 0;
+
 	return err;
-}
-
-/**
- * Prepare a DMA buffer according to endpoint policy.
- *
- * If the buffer is suitable to be used directly, it is. Otherwise, a bounce
- * buffer is created.
- */
-errno_t usb_transfer_batch_prepare_buffer(usb_transfer_batch_t *batch, char *buf)
-{
-	/* Empty transfers do not need a buffer */
-	if (batch->buffer_size == 0)
-		return EOK;
-
-	batch->buffer = buf;
-
-	const dma_policy_t policy = batch->ep->transfer_buffer_policy;
-
-	/*
-	 * We don't have enough information (yet, WIP) to know if we can skip
-	 * the bounce, so check the conditions carefully.
-	 */
-	if (!dma_buffer_check_policy(buf, batch->buffer_size, policy))
-		return usb_transfer_batch_bounce(batch);
-
-	/* Fill the buffer with virtual address and lock it for DMA */
-	return dma_buffer_lock(&batch->dma_buffer, buf, batch->buffer_size);
 }
 
@@ -167,18 +175,16 @@
 	    batch, USB_TRANSFER_BATCH_ARGS(*batch));
 
-	if (batch->error == EOK && batch->buffer_size > 0) {
-		if (!batch->is_bounced) {
-			/* Unlock the buffer for DMA */
-			dma_buffer_unlock(&batch->dma_buffer,
-			    batch->buffer_size);
+	if (batch->error == EOK && batch->size > 0) {
+		if (batch->is_bounced) {
+			/* We we're forced to use bounce buffer, copy it back */
+			if (batch->dir == USB_DIRECTION_IN)
+			memcpy(batch->original_buffer,
+			    batch->dma_buffer.virt,
+			    batch->transferred_size);
+
+			dma_buffer_free(&batch->dma_buffer);
 		}
 		else {
-			/* We we're forced to use bounce buffer, copy it back */
-			if (batch->dir == USB_DIRECTION_IN)
-				memcpy(batch->buffer,
-				    batch->dma_buffer.virt,
-				    batch->transferred_size);
-
-			dma_buffer_free(&batch->dma_buffer);
+			dma_buffer_release(&batch->dma_buffer);
 		}
 	}
