Index: uspace/drv/bus/usb/ehci/ehci_batch.c
===================================================================
--- uspace/drv/bus/usb/ehci/ehci_batch.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/drv/bus/usb/ehci/ehci_batch.c	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -102,5 +102,5 @@
 		: 0;
 
-	const size_t size = ehci_batch->base.buffer_size;
+	const size_t size = ehci_batch->base.size;
 
 	/* Add TD left over by the previous transfer */
@@ -180,5 +180,5 @@
 
 	/* Assume all data got through */
-	ehci_batch->base.transferred_size = ehci_batch->base.buffer_size;
+	ehci_batch->base.transferred_size = ehci_batch->base.size;
 
 	/* Check all TDs */
@@ -216,5 +216,5 @@
 	}
 
-	assert(ehci_batch->base.transferred_size <= ehci_batch->base.buffer_size);
+	assert(ehci_batch->base.transferred_size <= ehci_batch->base.size);
 
 	/* Clear TD pointers */
@@ -281,5 +281,5 @@
 	/* Data stage */
 	unsigned td_current = 1;
-	size_t remain_size = ehci_batch->base.buffer_size;
+	size_t remain_size = ehci_batch->base.size;
 	uintptr_t buffer = dma_buffer_phys(&ehci_batch->base.dma_buffer,
 	    ehci_batch->data_buffer);
@@ -335,5 +335,5 @@
 
 	size_t td_current = 0;
-	size_t remain_size = ehci_batch->base.buffer_size;
+	size_t remain_size = ehci_batch->base.size;
 	uintptr_t buffer = dma_buffer_phys(&ehci_batch->base.dma_buffer,
 	    ehci_batch->data_buffer);
Index: uspace/drv/bus/usb/ehci/ehci_rh.c
===================================================================
--- uspace/drv/bus/usb/ehci/ehci_rh.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/drv/bus/usb/ehci/ehci_rh.c	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -147,5 +147,5 @@
 	batch->error = virthub_base_request(&instance->base, batch->target,
 	    batch->dir, (void*) batch->setup.buffer,
-	    batch->dma_buffer.virt, batch->buffer_size,
+	    batch->dma_buffer.virt, batch->size,
 	    &batch->transferred_size);
 	if (batch->error == ENAK) {
@@ -206,5 +206,5 @@
 		batch->error = virthub_base_request(&instance->base, batch->target,
 		    batch->dir, (void*) batch->setup.buffer,
-		    batch->dma_buffer.virt, batch->buffer_size,
+		    batch->dma_buffer.virt, batch->size,
 		    &batch->transferred_size);
 		usb_transfer_batch_finish(batch);
Index: uspace/drv/bus/usb/ohci/ohci_batch.c
===================================================================
--- uspace/drv/bus/usb/ohci/ohci_batch.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/drv/bus/usb/ohci/ohci_batch.c	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -96,5 +96,5 @@
 		return ENOTSUP;
 
-	ohci_batch->td_count = (usb_batch->buffer_size + OHCI_TD_MAX_TRANSFER - 1)
+	ohci_batch->td_count = (usb_batch->size + OHCI_TD_MAX_TRANSFER - 1)
 	    / OHCI_TD_MAX_TRANSFER;
 	/* Control transfer need Setup and Status stage */
@@ -166,5 +166,5 @@
 
 	/* Assume all data got through */
-	usb_batch->transferred_size = usb_batch->buffer_size;
+	usb_batch->transferred_size = usb_batch->size;
 
 	/* Check all TDs */
@@ -212,5 +212,5 @@
 		}
 	}
-	assert(usb_batch->transferred_size <= usb_batch->buffer_size);
+	assert(usb_batch->transferred_size <= usb_batch->size);
 
 	/* Make sure that we are leaving the right TD behind */
@@ -289,5 +289,5 @@
 	size_t td_current = 1;
 	const char* buffer = ohci_batch->data_buffer;
-	size_t remain_size = ohci_batch->base.buffer_size;
+	size_t remain_size = ohci_batch->base.size;
 	while (remain_size > 0) {
 		const size_t transfer_size =
@@ -343,5 +343,5 @@
 
 	size_t td_current = 0;
-	size_t remain_size = ohci_batch->base.buffer_size;
+	size_t remain_size = ohci_batch->base.size;
 	char *buffer = ohci_batch->data_buffer;
 	while (remain_size > 0) {
Index: uspace/drv/bus/usb/ohci/ohci_rh.c
===================================================================
--- uspace/drv/bus/usb/ohci/ohci_rh.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/drv/bus/usb/ohci/ohci_rh.c	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -182,5 +182,5 @@
 	batch->error = virthub_base_request(&instance->base, batch->target,
 	    batch->dir, &batch->setup.packet,
-	    batch->dma_buffer.virt, batch->buffer_size, &batch->transferred_size);
+	    batch->dma_buffer.virt, batch->size, &batch->transferred_size);
 	if (batch->error == ENAK) {
 		/* Lock the HC guard */
@@ -233,5 +233,5 @@
 		batch->error = virthub_base_request(&instance->base, batch->target,
 		    batch->dir, &batch->setup.packet,
-		    batch->dma_buffer.virt, batch->buffer_size, &batch->transferred_size);
+		    batch->dma_buffer.virt, batch->size, &batch->transferred_size);
 		usb_transfer_batch_finish(batch);
 	}
Index: uspace/drv/bus/usb/uhci/uhci_batch.c
===================================================================
--- uspace/drv/bus/usb/uhci/uhci_batch.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/drv/bus/usb/uhci/uhci_batch.c	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -98,5 +98,5 @@
 	usb_transfer_batch_t *usb_batch = &uhci_batch->base;
 
-	uhci_batch->td_count = (usb_batch->buffer_size + usb_batch->ep->max_packet_size - 1)
+	uhci_batch->td_count = (usb_batch->size + usb_batch->ep->max_packet_size - 1)
 		/ usb_batch->ep->max_packet_size;
 
@@ -190,5 +190,5 @@
 	}
 
-	assert(batch->transferred_size <= batch->buffer_size);
+	assert(batch->transferred_size <= batch->size);
 
 	return true;
@@ -228,5 +228,5 @@
 
 	size_t td = 0;
-	size_t remain_size = uhci_batch->base.buffer_size;
+	size_t remain_size = uhci_batch->base.size;
 	char *buffer = uhci_transfer_batch_data_buffer(uhci_batch);
 
@@ -297,5 +297,5 @@
 	size_t td = 1;
 	unsigned toggle = 1;
-	size_t remain_size = uhci_batch->base.buffer_size;
+	size_t remain_size = uhci_batch->base.size;
 	char *buffer = uhci_transfer_batch_data_buffer(uhci_batch);
 
Index: uspace/drv/bus/usb/uhci/uhci_rh.c
===================================================================
--- uspace/drv/bus/usb/uhci/uhci_rh.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/drv/bus/usb/uhci/uhci_rh.c	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -107,5 +107,5 @@
 		batch->error = virthub_base_request(&instance->base, batch->target,
 		    batch->dir, (void*) batch->setup.buffer,
-		    batch->dma_buffer.virt, batch->buffer_size, &batch->transferred_size);
+		    batch->dma_buffer.virt, batch->size, &batch->transferred_size);
 		if (batch->error == ENAK)
 			async_usleep(instance->base.endpoint_descriptor.poll_interval * 1000);
Index: uspace/drv/bus/usb/vhc/transfer.c
===================================================================
--- uspace/drv/bus/usb/vhc/transfer.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/drv/bus/usb/vhc/transfer.c	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -72,5 +72,5 @@
 			rc = usbvirt_control_read(dev,
 			    batch->setup.buffer, USB_SETUP_PACKET_SIZE,
-			    batch->dma_buffer.virt, batch->buffer_size,
+			    batch->dma_buffer.virt, batch->size,
 			    actual_data_size);
 		} else {
@@ -78,5 +78,5 @@
 			rc = usbvirt_control_write(dev,
 			    batch->setup.buffer, USB_SETUP_PACKET_SIZE,
-			    batch->dma_buffer.virt, batch->buffer_size);
+			    batch->dma_buffer.virt, batch->size);
 		}
 	} else {
@@ -84,5 +84,5 @@
 			rc = usbvirt_data_in(dev, batch->ep->transfer_type,
 			    batch->ep->endpoint,
-			    batch->dma_buffer.virt, batch->buffer_size,
+			    batch->dma_buffer.virt, batch->size,
 			    actual_data_size);
 		} else {
@@ -90,5 +90,5 @@
 			rc = usbvirt_data_out(dev, batch->ep->transfer_type,
 			    batch->ep->endpoint,
-			    batch->dma_buffer.virt, batch->buffer_size);
+			    batch->dma_buffer.virt, batch->size);
 		}
 	}
@@ -108,5 +108,5 @@
 			rc = usbvirt_ipc_send_control_read(sess,
 			    batch->setup.buffer, USB_SETUP_PACKET_SIZE,
-			    batch->dma_buffer.virt, batch->buffer_size,
+			    batch->dma_buffer.virt, batch->size,
 			    actual_data_size);
 		} else {
@@ -114,5 +114,5 @@
 			rc = usbvirt_ipc_send_control_write(sess,
 			    batch->setup.buffer, USB_SETUP_PACKET_SIZE,
-			    batch->dma_buffer.virt, batch->buffer_size);
+			    batch->dma_buffer.virt, batch->size);
 		}
 	} else {
@@ -120,5 +120,5 @@
 			rc = usbvirt_ipc_send_data_in(sess, batch->ep->endpoint,
 			    batch->ep->transfer_type,
-			    batch->dma_buffer.virt, batch->buffer_size,
+			    batch->dma_buffer.virt, batch->size,
 			    actual_data_size);
 		} else {
@@ -126,5 +126,5 @@
 			rc = usbvirt_ipc_send_data_out(sess, batch->ep->endpoint,
 			    batch->ep->transfer_type,
-			    batch->dma_buffer.virt, batch->buffer_size);
+			    batch->dma_buffer.virt, batch->size);
 		}
 	}
Index: uspace/drv/bus/usb/xhci/commands.c
===================================================================
--- uspace/drv/bus/usb/xhci/commands.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/drv/bus/usb/xhci/commands.c	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -470,5 +470,6 @@
 	xhci_trb_clean(&cmd->_header.trb);
 
-	TRB_SET_ICTX(cmd->_header.trb, cmd->input_ctx.phys);
+	const uintptr_t phys = dma_buffer_phys_base(&cmd->input_ctx);
+	TRB_SET_ICTX(cmd->_header.trb, phys);
 
 	/**
@@ -496,5 +497,6 @@
 		assert(dma_buffer_is_set(&cmd->input_ctx));
 
-		TRB_SET_ICTX(cmd->_header.trb, cmd->input_ctx.phys);
+		const uintptr_t phys = dma_buffer_phys_base(&cmd->input_ctx);
+		TRB_SET_ICTX(cmd->_header.trb, phys);
 	}
 
@@ -520,5 +522,6 @@
 	xhci_trb_clean(&cmd->_header.trb);
 
-	TRB_SET_ICTX(cmd->_header.trb, cmd->input_ctx.phys);
+	const uintptr_t phys = dma_buffer_phys_base(&cmd->input_ctx);
+	TRB_SET_ICTX(cmd->_header.trb, phys);
 
 	TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_EVALUATE_CONTEXT_CMD);
@@ -594,5 +597,6 @@
 	xhci_trb_clean(&cmd->_header.trb);
 
-	TRB_SET_ICTX(cmd->_header.trb, cmd->bandwidth_ctx.phys);
+	const uintptr_t phys = dma_buffer_phys_base(&cmd->input_ctx);
+	TRB_SET_ICTX(cmd->_header.trb, phys);
 
 	TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_GET_PORT_BANDWIDTH_CMD);
Index: uspace/drv/bus/usb/xhci/endpoint.c
===================================================================
--- uspace/drv/bus/usb/xhci/endpoint.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/drv/bus/usb/xhci/endpoint.c	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -116,14 +116,16 @@
 		goto err;
 
-	/* Driver can handle non-contiguous buffers */
-	ep->transfer_buffer_policy &= ~DMA_POLICY_CONTIGUOUS;
-
-	/* Driver can handle buffers crossing boundaries */
-	ep->transfer_buffer_policy &= ~DMA_POLICY_NOT_CROSSING;
+	unsigned flags = -1U;
 
 	/* Some xHCs can handle 64-bit addresses */
 	xhci_bus_t *bus = bus_to_xhci_bus(ep->device->bus);
 	if (bus->hc->ac64)
-		ep->transfer_buffer_policy &= ~DMA_POLICY_4GiB;
+		flags &= ~DMA_POLICY_4GiB;
+
+	/* xHCI works best if it can fit 65k transfers in one TRB */
+	ep->transfer_buffer_policy = dma_policy_create(flags, 1 << 16);
+
+	/* But actualy can do full scatter-gather. */
+	ep->required_transfer_buffer_policy = dma_policy_create(flags, PAGE_SIZE);
 
 	return EOK;
Index: uspace/drv/bus/usb/xhci/hc.c
===================================================================
--- uspace/drv/bus/usb/xhci/hc.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/drv/bus/usb/xhci/hc.c	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -476,5 +476,6 @@
 		return ETIMEOUT;
 
-	XHCI_REG_WR(hc->op_regs, XHCI_OP_DCBAAP, hc->dcbaa_dma.phys);
+	uintptr_t dcbaa_phys = dma_buffer_phys_base(&hc->dcbaa_dma);
+	XHCI_REG_WR(hc->op_regs, XHCI_OP_DCBAAP, dcbaa_phys);
 	XHCI_REG_WR(hc->op_regs, XHCI_OP_MAX_SLOTS_EN, hc->max_slots);
 
@@ -490,5 +491,7 @@
 	XHCI_REG_WR(intr0, XHCI_INTR_ERSTSZ, hc->event_ring.segment_count);
 	XHCI_REG_WR(intr0, XHCI_INTR_ERDP, hc->event_ring.dequeue_ptr);
-	XHCI_REG_WR(intr0, XHCI_INTR_ERSTBA, hc->event_ring.erst.phys);
+
+	const uintptr_t erstba_phys = dma_buffer_phys_base(&hc->event_ring.erst);
+	XHCI_REG_WR(intr0, XHCI_INTR_ERSTBA, erstba_phys);
 
 	if (hc->base.irq_cap > 0) {
@@ -799,5 +802,6 @@
 	if (err == EOK) {
 		dev->slot_id = cmd.slot_id;
-		hc->dcbaa[dev->slot_id] = host2xhci(64, dev->dev_ctx.phys);
+		hc->dcbaa[dev->slot_id] =
+		    host2xhci(64, dma_buffer_phys_base(&dev->dev_ctx));
 	}
 
Index: uspace/drv/bus/usb/xhci/isoch.c
===================================================================
--- uspace/drv/bus/usb/xhci/isoch.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/drv/bus/usb/xhci/isoch.c	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -176,5 +176,5 @@
 	xhci_trb_clean(&trb);
 
-	trb.parameter = it->data.phys;
+	trb.parameter = host2xhci(64, dma_buffer_phys_base(&it->data));
 	TRB_CTRL_SET_XFER_LEN(trb, it->size);
 	TRB_CTRL_SET_TD_SIZE(trb, 0);
@@ -481,5 +481,5 @@
 
 	/* This shall be already checked by endpoint */
-	assert(transfer->batch.buffer_size <= ep->base.max_transfer_size);
+	assert(transfer->batch.size <= ep->base.max_transfer_size);
 
 	fibril_mutex_lock(&isoch->guard);
@@ -521,5 +521,5 @@
 
 	/* Prepare the transfer. */
-	it->size = transfer->batch.buffer_size;
+	it->size = transfer->batch.size;
 	memcpy(it->data.virt, transfer->batch.dma_buffer.virt, it->size);
 	it->state = ISOCH_FILLED;
@@ -544,5 +544,5 @@
 	xhci_isoch_t * const isoch = ep->isoch;
 
-	if (transfer->batch.buffer_size < ep->base.max_transfer_size) {
+	if (transfer->batch.size < ep->base.max_transfer_size) {
 		usb_log_error("Cannot schedule an undersized isochronous transfer.");
 		return ELIMIT;
Index: uspace/drv/bus/usb/xhci/scratchpad.c
===================================================================
--- uspace/drv/bus/usb/xhci/scratchpad.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/drv/bus/usb/xhci/scratchpad.c	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -72,11 +72,13 @@
 	memset(hc->scratchpad_array.virt, 0, size);
 
-	uint64_t phys_begin = hc->scratchpad_array.phys + array_size;
+	const char *base = hc->scratchpad_array.virt + array_size;
 	uint64_t *array = hc->scratchpad_array.virt;
 
-	for (unsigned i = 0; i < num_bufs; ++i)
-		array[i] = host2xhci(64, phys_begin + i * PAGE_SIZE);
+	for (unsigned i = 0; i < num_bufs; ++i) {
+		array[i] = host2xhci(64, dma_buffer_phys(&hc->scratchpad_array,
+			    base + i * PAGE_SIZE));
+	}
 
-	hc->dcbaa[0] = host2xhci(64, hc->scratchpad_array.phys);
+	hc->dcbaa[0] = host2xhci(64, dma_buffer_phys_base(&hc->scratchpad_array));
 
 	usb_log_debug("Allocated %d scratchpad buffers.", num_bufs);
Index: uspace/drv/bus/usb/xhci/streams.c
===================================================================
--- uspace/drv/bus/usb/xhci/streams.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/drv/bus/usb/xhci/streams.c	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -239,5 +239,5 @@
 	data->secondary_stream_ctx_array = data->secondary_stream_ctx_dma.virt;
 
-	XHCI_STREAM_DEQ_PTR_SET(*ctx, data->secondary_stream_ctx_dma.phys);
+	XHCI_STREAM_DEQ_PTR_SET(*ctx, dma_buffer_phys_base(&data->secondary_stream_ctx_dma));
 	XHCI_STREAM_SCT_SET(*ctx, fnzb32(count) + 1);
 
@@ -283,5 +283,5 @@
 
 	XHCI_EP_MAX_P_STREAMS_SET(*ctx, pstreams);
-	XHCI_EP_TR_DPTR_SET(*ctx, xhci_ep->primary_stream_ctx_dma.phys);
+	XHCI_EP_TR_DPTR_SET(*ctx, dma_buffer_phys_base(&xhci_ep->primary_stream_ctx_dma));
 	XHCI_EP_LSA_SET(*ctx, lsa);
 }
Index: uspace/drv/bus/usb/xhci/transfers.c
===================================================================
--- uspace/drv/bus/usb/xhci/transfers.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/drv/bus/usb/xhci/transfers.c	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -126,5 +126,5 @@
 static int calculate_trb_count(xhci_transfer_t *transfer)
 {
-	const size_t size = transfer->batch.buffer_size;
+	const size_t size = transfer->batch.size;
 	return (size + PAGE_SIZE - 1 )/ PAGE_SIZE;
 }
@@ -184,5 +184,5 @@
 		int stage_dir = REQUEST_TYPE_IS_DEVICE_TO_HOST(setup->request_type)
 					? STAGE_IN : STAGE_OUT;
-		size_t remaining = transfer->batch.buffer_size;
+		size_t remaining = transfer->batch.size;
 
 		for (size_t i = 0; i < buffer_count; ++i) {
@@ -227,5 +227,5 @@
 		const size_t buffer_count = calculate_trb_count(transfer);
 		xhci_trb_t trbs[buffer_count];
-		size_t remaining = transfer->batch.buffer_size;
+		size_t remaining = transfer->batch.size;
 
 		for (size_t i = 0; i < buffer_count; ++i) {
@@ -254,5 +254,5 @@
 		const size_t buffer_count = calculate_trb_count(transfer);
 		xhci_trb_t trbs[buffer_count + 1];
-		size_t remaining = transfer->batch.buffer_size;
+		size_t remaining = transfer->batch.size;
 
 		for (size_t i = 0; i < buffer_count; ++i) {
@@ -278,5 +278,5 @@
 	const size_t buffer_count = calculate_trb_count(transfer);
 	xhci_trb_t trbs[buffer_count];
-	size_t remaining = transfer->batch.buffer_size;
+	size_t remaining = transfer->batch.size;
 
 	for (size_t i = 0; i < buffer_count; ++i) {
@@ -372,5 +372,5 @@
 		case XHCI_TRBC_SUCCESS:
 			batch->error = EOK;
-			batch->transferred_size = batch->buffer_size - TRB_TRANSFER_LENGTH(*trb);
+			batch->transferred_size = batch->size - TRB_TRANSFER_LENGTH(*trb);
 			break;
 
@@ -416,5 +416,5 @@
 	}
 
-	assert(batch->transferred_size <= batch->buffer_size);
+	assert(batch->transferred_size <= batch->size);
 
 	usb_transfer_batch_finish(batch);
Index: uspace/drv/bus/usb/xhci/trb_ring.c
===================================================================
--- uspace/drv/bus/usb/xhci/trb_ring.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/drv/bus/usb/xhci/trb_ring.c	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -89,13 +89,15 @@
 static errno_t trb_segment_alloc(trb_segment_t **segment)
 {
-	dma_buffer_t dbuf;
-
-	const errno_t err = dma_buffer_alloc(&dbuf, PAGE_SIZE);
+	*segment = AS_AREA_ANY;
+	uintptr_t phys;
+
+	const int err = dmamem_map_anonymous(PAGE_SIZE,
+	    DMAMEM_4GiB, AS_AREA_READ | AS_AREA_WRITE, 0,
+	    &phys, (void **) segment);
 	if (err)
 		return err;
 
-	*segment = dbuf.virt;
 	memset(*segment, 0, PAGE_SIZE);
-	(*segment)->phys = dbuf.phys;
+	(*segment)->phys = phys;
 	usb_log_debug("Allocated new ring segment.");
 	return EOK;
@@ -104,6 +106,5 @@
 static void trb_segment_free(trb_segment_t *segment)
 {
-	dma_buffer_t dbuf = { .virt = segment, .phys = segment->phys };
-	dma_buffer_free(&dbuf);
+	dmamem_unmap_anonymous(segment);
 }
 
Index: uspace/lib/drv/generic/remote_usbhc.c
===================================================================
--- uspace/lib/drv/generic/remote_usbhc.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/lib/drv/generic/remote_usbhc.c	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -210,5 +210,5 @@
 			? AS_AREA_WRITE : AS_AREA_READ;
 
-		const errno_t ret = async_share_out_start(exch, req->base, flags);
+		const errno_t ret = async_share_out_start(exch, req->buffer.virt, flags);
 		if (ret != EOK) {
 			async_forget(opening_request);
@@ -374,6 +374,6 @@
 		return;
 	}
-	if (trans->request.base != NULL) {
-		as_area_destroy(trans->request.base);
+	if (trans->request.buffer.virt != NULL) {
+		as_area_destroy(trans->request.buffer.virt);
 	}
 
@@ -422,19 +422,20 @@
 	}
 
-	if ((err = async_share_out_finalize(data_callid, &trans->request.base)))
+	if ((err = async_share_out_finalize(data_callid, &trans->request.buffer.virt)))
 		return err;
 
 	/*
-	 * As we're going to check the mapping, we must make sure the memory is
-	 * actually mapped. We must do it right now, because the area might be
-	 * read-only or write-only, and we may be unsure later.
+	 * As we're going to get physical addresses of the mapping, we must make
+	 * sure the memory is actually mapped. We must do it right now, because
+	 * the area might be read-only or write-only, and we may be unsure
+	 * later.
 	 */
 	if (flags & AS_AREA_READ) {
 		char foo = 0;
-		volatile const char *buf = trans->request.base + trans->request.offset;
+		volatile const char *buf = trans->request.buffer.virt + trans->request.offset;
 		for (size_t i = 0; i < size; i += PAGE_SIZE)
 			foo += buf[i];
 	} else {
-		volatile char *buf = trans->request.base + trans->request.offset;
+		volatile char *buf = trans->request.buffer.virt + trans->request.offset;
 		for (size_t i = 0; i < size; i += PAGE_SIZE)
 			buf[i] = 0xff;
@@ -482,5 +483,5 @@
 	} else {
 		/* The value was valid on the other side, for us, its garbage. */
-		trans->request.base = NULL;
+		trans->request.buffer.virt = NULL;
 	}
 
Index: uspace/lib/drv/include/usbhc_iface.h
===================================================================
--- uspace/lib/drv/include/usbhc_iface.h	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/lib/drv/include/usbhc_iface.h	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -104,5 +104,10 @@
 
 // FIXME: DMA buffers shall be part of libdrv anyway.
-typedef unsigned dma_policy_t;
+typedef uintptr_t dma_policy_t;
+
+typedef struct dma_buffer {
+	void *virt;
+	dma_policy_t policy;
+} dma_buffer_t;
 
 typedef struct usb_pipe_desc {
@@ -134,14 +139,11 @@
 
 	/**
-	 * Base address of the buffer to share. Must be at least offset + size
-	 * large. Is patched after being transmitted over IPC, so the pointer is
-	 * still valid.
-	 *
-	 * Note that offset might be actually more than PAGE_SIZE.
+	 * The DMA buffer to share. Must be at least offset + size large. Is
+	 * patched after being transmitted over IPC, so the pointer is still
+	 * valid.
 	 */
-	void *base;
+	dma_buffer_t buffer;
 	size_t offset;			/**< Offset to the buffer */
 	size_t size;			/**< Requested size. */
-	dma_policy_t buffer_policy;	/**< Properties of the buffer. */
 } usbhc_iface_transfer_request_t;
 
Index: uspace/lib/usb/include/usb/dma_buffer.h
===================================================================
--- uspace/lib/usb/include/usb/dma_buffer.h	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/lib/usb/include/usb/dma_buffer.h	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -37,45 +37,73 @@
  * shared through IPC).
  *
- * Note that although allocated memory is always page-aligned, the buffer itself
- * may be only a part of it, justifying the existence of page-alignment and
- * page-crossing flags.
+ * Currently, it is possible to allocate either completely contiguous buffers
+ * (with dma_map_anonymous) or arbitrary memory (with as_area_create). Shall the
+ * kernel be updated, this is a subject of major optimization of memory usage.
+ * The other way to do it without the kernel is building an userspace IO vector
+ * in a similar way how QEMU does it.
  *
- * Also, currently the buffers that are allocated are always contiguous and
- * page-aligned, regardless of whether the policy requires it. We blindly
- * believe this fact in dma_buffer_phys, which will yield wrong results if the
- * buffer is not contiguous.
+ * The structures themselves are defined in usbhc_iface, because they need to be
+ * passed through IPC.
  */
 #ifndef LIB_USB_DMA_BUFFER
 #define LIB_USB_DMA_BUFFER
 
+#include <as.h>
+#include <bitops.h>
+#include <errno.h>
 #include <stdint.h>
 #include <stdlib.h>
 #include <usbhc_iface.h>
-#include <errno.h>
 
-#define DMA_POLICY_4GiB		(1<<0)	/**< Must use only 32-bit addresses */
-#define DMA_POLICY_PAGE_ALIGNED	(1<<1)	/**< The first pointer must be page-aligned */
-#define DMA_POLICY_CONTIGUOUS	(1<<2)	/**< Pages must follow each other physically */
-#define DMA_POLICY_NOT_CROSSING	(1<<3)	/**< Buffer must not cross page boundary. (Implies buffer is no larger than page).  */
+/**
+ * The DMA policy describes properties of the buffer. It is used in two
+ * different contexts. Either it represents requirements, which shall be
+ * satisfied to avoid copying the buffer to a more strict one. Or, it is the
+ * actual property of the buffer, which can be more strict than requested. It
+ * always holds that more bits set means more restrictive policy, and that by
+ * computing a bitwise OR one gets the restriction that holds for both.
+ *
+ * The high bits of a DMA policy represent a physical contiguity. If bit i is
+ * set, it means that chunks of a size 2^(i+1) are contiguous in memory. It
+ * shall never happen that bit i > j is set when j is not.
+ *
+ * The previous applies for i >= PAGE_WIDTH. Lower bits are used as bit flags.
+ */
+#define DMA_POLICY_FLAGS_MASK		(PAGE_SIZE - 1)
+#define DMA_POLICY_CHUNK_SIZE_MASK	(~DMA_POLICY_FLAGS_MASK)
 
-#define DMA_POLICY_STRICT	(-1U)
-#define DMA_POLICY_DEFAULT	DMA_POLICY_STRICT
+#define DMA_POLICY_4GiB	(1<<0)		/**< Must use only 32-bit addresses */
 
-typedef struct dma_buffer {
-	void *virt;
-	uintptr_t phys;
-} dma_buffer_t;
+#define DMA_POLICY_STRICT		(-1UL)
+#define DMA_POLICY_DEFAULT		DMA_POLICY_STRICT
+
+extern dma_policy_t dma_policy_create(unsigned, size_t);
+
+/**
+ * Get mask which defines bits of offset in chunk.
+ */
+static inline size_t dma_policy_chunk_mask(const dma_policy_t policy)
+{
+	return policy | DMA_POLICY_FLAGS_MASK;
+}
 
 extern errno_t dma_buffer_alloc(dma_buffer_t *db, size_t size);
 extern errno_t dma_buffer_alloc_policy(dma_buffer_t *, size_t, dma_policy_t);
 extern void dma_buffer_free(dma_buffer_t *);
-extern uintptr_t dma_buffer_phys(const dma_buffer_t *, void *);
 
-extern bool dma_buffer_check_policy(const void *, size_t, const dma_policy_t);
+extern uintptr_t dma_buffer_phys(const dma_buffer_t *, const void *);
+
+static inline uintptr_t dma_buffer_phys_base(const dma_buffer_t *db)
+{
+	return dma_buffer_phys(db, db->virt);
+}
 
 extern errno_t dma_buffer_lock(dma_buffer_t *, void *, size_t);
 extern void dma_buffer_unlock(dma_buffer_t *, size_t);
 
-static inline int dma_buffer_is_set(dma_buffer_t *db)
+extern void dma_buffer_acquire(dma_buffer_t *);
+extern void dma_buffer_release(dma_buffer_t *);
+
+static inline bool dma_buffer_is_set(const dma_buffer_t *db)
 {
 	return !!db->virt;
Index: uspace/lib/usb/src/dma_buffer.c
===================================================================
--- uspace/lib/usb/src/dma_buffer.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/lib/usb/src/dma_buffer.c	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -39,9 +39,45 @@
 #include "usb/dma_buffer.h"
 
+dma_policy_t dma_policy_create(unsigned flags, size_t chunk_size)
+{
+	assert((chunk_size & (chunk_size - 1)) == 0); /* Check if power of 2 */
+	assert(chunk_size >= PAGE_SIZE || chunk_size == 0);
+
+	return ((chunk_size - 1) & DMA_POLICY_CHUNK_SIZE_MASK)
+		| (flags & DMA_POLICY_FLAGS_MASK);
+}
+
+/**
+ * As the driver is typically using only a few buffers at once, we cache the
+ * physical mapping to avoid calling the kernel unnecessarily often. This cache
+ * is global for a task.
+ *
+ * TODO: "few" is currently limited to one.
+ */
+static struct {
+	const void *last;
+	uintptr_t phys;
+} phys_mapping_cache = { 0 };
+
+static void cache_insert(const void *v, uintptr_t p)
+{
+	phys_mapping_cache.last = v;
+	phys_mapping_cache.phys = p;
+}
+
+static void cache_evict(const void *v)
+{
+	if (phys_mapping_cache.last == v)
+		phys_mapping_cache.last = NULL;
+}
+
+static bool cache_find(const void *v, uintptr_t *p)
+{
+	*p = phys_mapping_cache.phys;
+	return phys_mapping_cache.last == v;
+}
+
 /**
  * Allocate a DMA buffer.
- *
- * XXX: Currently cannot make much use of missing constraints, as it always
- * allocates page-aligned contiguous buffer. We rely on it in dma_buffer_phys.
  *
  * @param[in] db dma_buffer_t structure to fill
@@ -62,18 +98,20 @@
 	void *address = AS_AREA_ANY;
 
-	const int ret = dmamem_map_anonymous(real_size,
+	const int err = dmamem_map_anonymous(real_size,
 	    flags, AS_AREA_READ | AS_AREA_WRITE, 0,
 	    &phys, &address);
-
-	if (ret == EOK) {
-		/* Access the pages to force mapping */
-		volatile char *buf = address;
-		for (size_t i = 0; i < size; i += PAGE_SIZE)
-			buf[i] = 0xff;
-
-		db->virt = address;
-		db->phys = phys;
-	}
-	return ret;
+	if (err)
+		return err;
+
+	/* Access the pages to force mapping */
+	volatile char *buf = address;
+	for (size_t i = 0; i < size; i += PAGE_SIZE)
+		buf[i] = 0xff;
+
+	db->virt = address;
+	db->policy = dma_policy_create(policy, 0);
+	cache_insert(db->virt, phys);
+
+	return EOK;
 }
 
@@ -102,5 +140,5 @@
 		dmamem_unmap_anonymous(db->virt);
 		db->virt = NULL;
-		db->phys = 0;
+		db->policy = 0;
 	}
 }
@@ -112,73 +150,38 @@
  * @param[in] virt Pointer somewhere inside db
  */
-uintptr_t dma_buffer_phys(const dma_buffer_t *db, void *virt)
-{
-	return db->phys + (virt - db->virt);
-}
-
-/**
- * Check whether a memory area is compatible with a policy.
- *
- * Useful to skip copying when the buffer is already ready to be given to
- * hardware as is.
- *
- * Note that the "as_get_physical_mapping" fails when the page is not mapped
- * yet, and that the caller is responsible for forcing the mapping.
- */
-bool dma_buffer_check_policy(const void *buffer, size_t size, const dma_policy_t policy)
-{
-	uintptr_t addr = (uintptr_t) buffer;
-
-	const bool check_4gib       = !!(policy & DMA_POLICY_4GiB);
-	const bool check_crossing   = !!(policy & DMA_POLICY_NOT_CROSSING);
-	const bool check_alignment  = !!(policy & DMA_POLICY_PAGE_ALIGNED);
-	const bool check_contiguous = !!(policy & DMA_POLICY_CONTIGUOUS);
-
-	/* Check the two conditions that are easy */
-	if (check_crossing && (addr + size - 1) / PAGE_SIZE != addr / PAGE_SIZE)
-		goto violated;
-
-	if (check_alignment && ((uintptr_t) buffer) % PAGE_SIZE)
-		goto violated;
-
-	/*
-	 * For these conditions, we need to walk through pages and check
-	 * physical address of each one
-	 */
-	if (check_contiguous || check_4gib) {
-		const void *virt = buffer;
-		uintptr_t phys;
-
-		/* Get the mapping of the first page */
-		if (as_get_physical_mapping(virt, &phys))
-			goto error;
-
-		/* First page can already break 4GiB condition */
-		if (check_4gib && (phys & DMAMEM_4GiB) != 0)
-			goto violated;
-
-		while (size >= PAGE_SIZE) {
-			/* Move to the next page */
-			virt += PAGE_SIZE;
-			size -= PAGE_SIZE;
-
-			uintptr_t last_phys = phys;
-			if (as_get_physical_mapping(virt, &phys))
-				goto error;
-
-			if (check_contiguous && (phys - last_phys) != PAGE_SIZE)
-				goto violated;
-
-			if (check_4gib && (phys & DMAMEM_4GiB) != 0)
-				goto violated;
-		}
-	}
-
-	/* All checks passed */
+uintptr_t dma_buffer_phys(const dma_buffer_t *db, const void *virt)
+{
+	const size_t chunk_mask = dma_policy_chunk_mask(db->policy);
+	const uintptr_t offset = (virt - db->virt) & chunk_mask;
+	const void *chunk_base = virt - offset;
+
+	uintptr_t phys;
+
+	if (!cache_find(chunk_base, &phys)) {
+		if (as_get_physical_mapping(chunk_base, &phys))
+			return 0;
+		cache_insert(chunk_base, phys);
+	}
+
+	return phys + offset;
+}
+
+static bool dma_buffer_is_4gib(dma_buffer_t *db, size_t size)
+{
+	if (sizeof(uintptr_t) <= 32)
+		return true;
+
+	const size_t chunk_size = dma_policy_chunk_mask(db->policy) + 1;
+	const size_t chunks = chunk_size ? 1 : size / chunk_size;
+
+	for (size_t c = 0; c < chunks; c++) {
+		const void *addr = db->virt + (c * chunk_size);
+		const uintptr_t phys = dma_buffer_phys(db, addr);
+	
+		if ((phys & DMAMEM_4GiB) != 0)
+			return false;
+	}
+
 	return true;
-
-violated:
-error:
-	return false;
 }
 
@@ -192,6 +195,22 @@
 errno_t dma_buffer_lock(dma_buffer_t *db, void *virt, size_t size)
 {
+	assert(virt);
+
+	uintptr_t phys;
+
+	const errno_t err = dmamem_map(virt, size, 0, 0, &phys);
+	if (err)
+		return err;
+
 	db->virt = virt;
-	return dmamem_map(db->virt, size, 0, 0, &db->phys);
+	db->policy = dma_policy_create(0, PAGE_SIZE);
+	cache_insert(virt, phys);
+
+	unsigned flags = -1U;
+	if (!dma_buffer_is_4gib(db, size))
+		flags &= ~DMA_POLICY_4GiB;
+	db->policy = dma_policy_create(flags, PAGE_SIZE);
+
+	return EOK;
 }
 
@@ -204,6 +223,23 @@
 		dmamem_unmap(db->virt, size);
 		db->virt = NULL;
-		db->phys = 0;
-	}
+		db->policy = 0;
+	}
+}
+
+/**
+ * Must be called when the buffer is received over IPC. Clears potentially
+ * leftover value from different buffer mapped to the same virtual address.
+ */
+void dma_buffer_acquire(dma_buffer_t *db)
+{
+	cache_evict(db->virt);
+}
+
+/**
+ * Counterpart of acquire.
+ */
+void dma_buffer_release(dma_buffer_t *db)
+{
+	cache_evict(db->virt);
 }
 
Index: uspace/lib/usbdev/src/pipes.c
===================================================================
--- uspace/lib/usbdev/src/pipes.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/lib/usbdev/src/pipes.c	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -83,10 +83,9 @@
 
 	/* Only control writes make sense without buffer */
-	if ((t->dir != USB_DIRECTION_OUT || !t->is_control)
-	    && (t->req.base == NULL || t->req.size == 0))
+	if ((t->dir != USB_DIRECTION_OUT || !t->is_control) && t->req.size == 0)
 		return EINVAL;
 
 	/* Nonzero size requires buffer */
-	if (t->req.base == NULL && t->req.size != 0)
+	if (!dma_buffer_is_set(&t->req.buffer) && t->req.size != 0)
 		return EINVAL;
 
@@ -119,11 +118,14 @@
 /**
  * Setup the transfer request inside transfer according to dma buffer provided.
+ *
+ * TODO: The buffer could have been allocated as a more strict one. Currently,
+ * we assume that the policy is just the requested one.
  */
 static void setup_dma_buffer(transfer_t *t, void *base, void *ptr, size_t size)
 {
-	t->req.base = base;
+	t->req.buffer.virt = base;
+	t->req.buffer.policy = t->pipe->desc.transfer_buffer_policy;
 	t->req.offset = ptr - base;
 	t->req.size = size;
-	t->req.buffer_policy = t->pipe->desc.transfer_buffer_policy;
 }
 
@@ -133,4 +135,9 @@
 static errno_t transfer_wrap_dma(transfer_t *t, void *buf, size_t size)
 {
+	if (size == 0) {
+		setup_dma_buffer(t, NULL, NULL, 0);
+		return transfer_common(t);
+	}
+
 	void *dma_buf = usb_pipe_alloc_buffer(t->pipe, size);
 	setup_dma_buffer(t, dma_buf, dma_buf, size);
@@ -364,4 +371,5 @@
 	.direction = USB_DIRECTION_BOTH,
 	.max_transfer_size = CTRL_PIPE_MIN_PACKET_SIZE,
+	.transfer_buffer_policy = DMA_POLICY_STRICT,
 };
 
Index: uspace/lib/usbhost/include/usb/host/bus.h
===================================================================
--- uspace/lib/usbhost/include/usb/host/bus.h	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/lib/usbhost/include/usb/host/bus.h	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -153,7 +153,24 @@
 int bus_device_offline(device_t *);
 
-int bus_device_send_batch(device_t *, usb_target_t,
-    usb_direction_t direction, char *, size_t, uint64_t,
-    usbhc_iface_transfer_callback_t, void *, const char *);
+/**
+ * A proforma to USB transfer batch. As opposed to transfer batch, which is
+ * supposed to be a dynamic structrure, this one is static and descriptive only.
+ * Its fields are copied to the final batch.
+ */
+typedef struct transfer_request {
+	usb_target_t target;
+	usb_direction_t dir;
+
+	dma_buffer_t buffer;
+	size_t offset, size;
+	uint64_t setup;
+
+	usbhc_iface_transfer_callback_t on_complete;
+	void *arg;
+
+	const char *name;
+} transfer_request_t;
+
+int bus_issue_transfer(device_t *, const transfer_request_t *);
 
 errno_t bus_device_send_batch_sync(device_t *, usb_target_t,
Index: uspace/lib/usbhost/include/usb/host/endpoint.h
===================================================================
--- uspace/lib/usbhost/include/usb/host/endpoint.h	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/lib/usbhost/include/usb/host/endpoint.h	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -51,4 +51,5 @@
 typedef struct bus bus_t;
 typedef struct device device_t;
+typedef struct transfer_request transfer_request_t;
 typedef struct usb_transfer_batch usb_transfer_batch_t;
 
@@ -98,6 +99,8 @@
 	/** Maximum size of one transfer */
 	size_t max_transfer_size;
-	/** Policy for transfer buffers */
-	dma_policy_t transfer_buffer_policy;
+
+	/* Policies for transfer buffers */
+	dma_policy_t transfer_buffer_policy;		/**< A hint for optimal performance. */
+	dma_policy_t required_transfer_buffer_policy;	/**< Enforced by the library. */
 
 	/**
@@ -122,7 +125,5 @@
 extern void endpoint_deactivate_locked(endpoint_t *);
 
-int endpoint_send_batch(endpoint_t *, usb_target_t, usb_direction_t,
-    char *, size_t, uint64_t, usbhc_iface_transfer_callback_t, void *,
-    const char *);
+int endpoint_send_batch(endpoint_t *, const transfer_request_t *);
 
 static inline bus_t *endpoint_get_bus(endpoint_t *ep)
Index: uspace/lib/usbhost/include/usb/host/usb_transfer_batch.h
===================================================================
--- uspace/lib/usbhost/include/usb/host/usb_transfer_batch.h	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/lib/usbhost/include/usb/host/usb_transfer_batch.h	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -70,14 +70,14 @@
 	} setup;
 
+	/** DMA buffer with enforced policy */
+	dma_buffer_t dma_buffer;
+	/** Size of memory buffer */
+	size_t offset, size;
+
 	/**
 	 * In case a bounce buffer is allocated, the original buffer must to be
 	 * stored to be filled after the IN transaction is finished.
 	 */
-	char *buffer;
-	/** Size of memory buffer */
-	size_t buffer_size;
-
-	/** DMA buffer with enforced policy */
-	dma_buffer_t dma_buffer;
+	char *original_buffer;
 	bool is_bounced;
 
@@ -107,5 +107,5 @@
 	usb_str_transfer_type_short((batch).ep->transfer_type), \
 	usb_str_direction((batch).dir), \
-	(batch).buffer_size, (batch).ep->max_packet_size
+	(batch).size, (batch).ep->max_packet_size
 
 /** Wrapper for bus operation. */
@@ -115,7 +115,7 @@
 void usb_transfer_batch_init(usb_transfer_batch_t *, endpoint_t *);
 
+/** Buffer handling */
+bool usb_transfer_batch_bounce_required(usb_transfer_batch_t *);
 errno_t usb_transfer_batch_bounce(usb_transfer_batch_t *);
-/** Buffer preparation */
-errno_t usb_transfer_batch_prepare_buffer(usb_transfer_batch_t *, char *);
 
 /** Batch finalization. */
Index: uspace/lib/usbhost/src/bus.c
===================================================================
--- uspace/lib/usbhost/src/bus.c	(revision 2f762a7c6e72f686c37eb21ceb35942d89b918e5)
+++ uspace/lib/usbhost/src/bus.c	(revision 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -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 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -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 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -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 1d758fc6d12dfedeea03efca816537794a056af2)
@@ -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);
 		}
 	}
