Index: uspace/drv/bus/usb/ohci/hw_struct/endpoint_descriptor.h
===================================================================
--- uspace/drv/bus/usb/ohci/hw_struct/endpoint_descriptor.h	(revision 3e6ff9a5d49f640854fbd4c5b5c21e928a71bc1d)
+++ uspace/drv/bus/usb/ohci/hw_struct/endpoint_descriptor.h	(revision e67c50ac9b5c850d056f789863dea7d4da95af3a)
@@ -165,4 +165,15 @@
 
 /**
+ * Set the HeadP of ED. Do not call unless the ED is Halted.
+ * @param instance ED
+ */
+static inline void ed_set_head_td(ed_t *instance, const td_t *td)
+{
+	assert(instance);
+	const uintptr_t pa = addr_to_phys(td);
+	OHCI_MEM32_WR(instance->td_head, pa & ED_TDHEAD_PTR_MASK);
+}
+
+/**
  * Set next ED in ED chain.
  * @param instance ED to modify
Index: uspace/drv/bus/usb/ohci/hw_struct/transfer_descriptor.c
===================================================================
--- uspace/drv/bus/usb/ohci/hw_struct/transfer_descriptor.c	(revision 3e6ff9a5d49f640854fbd4c5b5c21e928a71bc1d)
+++ uspace/drv/bus/usb/ohci/hw_struct/transfer_descriptor.c	(revision e67c50ac9b5c850d056f789863dea7d4da95af3a)
@@ -88,7 +88,12 @@
 	}
 
+	td_set_next(instance, next);
+}
+
+void td_set_next(td_t *instance, const td_t *next)
+{
 	OHCI_MEM32_WR(instance->next, addr_to_phys(next) & TD_NEXT_PTR_MASK);
+}
 
-}
 /**
  * @}
Index: uspace/drv/bus/usb/ohci/hw_struct/transfer_descriptor.h
===================================================================
--- uspace/drv/bus/usb/ohci/hw_struct/transfer_descriptor.h	(revision 3e6ff9a5d49f640854fbd4c5b5c21e928a71bc1d)
+++ uspace/drv/bus/usb/ohci/hw_struct/transfer_descriptor.h	(revision e67c50ac9b5c850d056f789863dea7d4da95af3a)
@@ -92,6 +92,6 @@
 } __attribute__((packed)) td_t;
 
-void td_init(td_t *instance, const td_t *next,
-    usb_direction_t dir, const void *buffer, size_t size, int toggle);
+void td_init(td_t *, const td_t *, usb_direction_t, const void *, size_t, int);
+void td_set_next(td_t *, const td_t *);
 
 /**
Index: uspace/drv/bus/usb/ohci/ohci_batch.c
===================================================================
--- uspace/drv/bus/usb/ohci/ohci_batch.c	(revision 3e6ff9a5d49f640854fbd4c5b5c21e928a71bc1d)
+++ uspace/drv/bus/usb/ohci/ohci_batch.c	(revision e67c50ac9b5c850d056f789863dea7d4da95af3a)
@@ -56,15 +56,5 @@
 {
 	assert(ohci_batch);
-	if (ohci_batch->tds) {
-		const ohci_endpoint_t *ohci_ep =
-		    ohci_endpoint_get(ohci_batch->base.ep);
-		assert(ohci_ep);
-		for (unsigned i = 0; i < ohci_batch->td_count; ++i) {
-			if (ohci_batch->tds[i] != ohci_ep->td)
-				free32(ohci_batch->tds[i]);
-		}
-		free(ohci_batch->tds);
-	}
-	free32(ohci_batch->device_buffer);
+	dma_buffer_free(&ohci_batch->ohci_dma_buffer);
 	free(ohci_batch);
 }
@@ -101,10 +91,8 @@
 {
 	assert(ohci_batch);
-
-	const size_t setup_size = (ohci_batch->base.ep->transfer_type == USB_TRANSFER_CONTROL)
-		? USB_SETUP_PACKET_SIZE
-		: 0;
-
 	usb_transfer_batch_t *usb_batch = &ohci_batch->base;
+
+	if (!batch_setup[usb_batch->ep->transfer_type])
+		return ENOTSUP;
 
 	ohci_batch->td_count = (usb_batch->buffer_size + OHCI_TD_MAX_TRANSFER - 1)
@@ -115,45 +103,34 @@
 	}
 
-	/* We need an extra place for TD that was left at ED */
-	ohci_batch->tds = calloc(ohci_batch->td_count + 1, sizeof(td_t*));
-	if (!ohci_batch->tds) {
-		usb_log_error("Failed to allocate OHCI transfer descriptors.");
+	/* Alloc one more to NULL terminate */
+	ohci_batch->tds = calloc(ohci_batch->td_count + 1, sizeof(td_t *));
+	if (!ohci_batch->tds)
 		return ENOMEM;
-	}
-
-	/* Add TD left over by the previous transfer */
-	ohci_batch->ed = ohci_endpoint_get(usb_batch->ep)->ed;
-	ohci_batch->tds[0] = ohci_endpoint_get(usb_batch->ep)->td;
-
-	for (unsigned i = 1; i <= ohci_batch->td_count; ++i) {
-		ohci_batch->tds[i] = malloc32(sizeof(td_t));
-		if (!ohci_batch->tds[i]) {
-			usb_log_error("Failed to allocate TD %d.", i);
-			return ENOMEM;
-		}
-	}
-
-	/* NOTE: OHCI is capable of handling buffer that crosses page boundaries
-	 * it is, however, not capable of handling buffer that occupies more
-	 * than two pages (the first page is computed using start pointer, the
-	 * other using the end pointer) */
-        if (setup_size + usb_batch->buffer_size > 0) {
-		/* Use one buffer for setup and data stage */
-		ohci_batch->device_buffer =
-		    malloc32(setup_size + usb_batch->buffer_size);
-		if (!ohci_batch->device_buffer) {
-			usb_log_error("Failed to allocate device buffer");
-			return ENOMEM;
-		}
-		/* Copy setup data */
-                memcpy(ohci_batch->device_buffer, usb_batch->setup.buffer, setup_size);
-		/* Copy generic data */
-		if (usb_batch->dir == USB_DIRECTION_OUT)
-			memcpy(
-			    ohci_batch->device_buffer + setup_size,
-			    usb_batch->buffer, usb_batch->buffer_size);
-        }
-
-	assert(batch_setup[usb_batch->ep->transfer_type]);
+
+	const size_t td_size = ohci_batch->td_count * sizeof(td_t);
+	const size_t setup_size = (usb_batch->ep->transfer_type == USB_TRANSFER_CONTROL)
+		? USB_SETUP_PACKET_SIZE
+		: 0;
+
+	if (dma_buffer_alloc(&ohci_batch->ohci_dma_buffer, td_size + setup_size + usb_batch->buffer_size)) {
+		usb_log_error("Failed to allocate OHCI DMA buffer.");
+		return ENOMEM;
+	}
+
+	td_t *tds = ohci_batch->ohci_dma_buffer.virt;
+
+	for (size_t i = 0; i < ohci_batch->td_count; i++)
+		ohci_batch->tds[i] = &tds[i];
+
+	/* Presence of this terminator makes TD initialization easier */
+	ohci_batch->tds[ohci_batch->td_count] = NULL;
+
+	ohci_batch->setup_buffer = (void *) (&tds[ohci_batch->td_count]);
+	memcpy(ohci_batch->setup_buffer, usb_batch->setup.buffer, setup_size);
+
+	ohci_batch->data_buffer = ohci_batch->setup_buffer + setup_size;
+	if (usb_batch->dir == USB_DIRECTION_OUT)
+		memcpy(ohci_batch->data_buffer, usb_batch->buffer, usb_batch->buffer_size);
+
 	batch_setup[usb_batch->ep->transfer_type](ohci_batch);
 
@@ -174,11 +151,14 @@
 	assert(ohci_batch);
 
+	ohci_endpoint_t *ohci_ep = ohci_endpoint_get(ohci_batch->base.ep);
+	assert(ohci_ep);
+
 	usb_log_debug("Batch %p checking %zu td(s) for completion.",
 	    &ohci_batch->base, ohci_batch->td_count);
 	usb_log_debug2("ED: %08x:%08x:%08x:%08x.",
-	    ohci_batch->ed->status, ohci_batch->ed->td_head,
-	    ohci_batch->ed->td_tail, ohci_batch->ed->next);
-
-	if (!ed_inactive(ohci_batch->ed) && ed_transfer_pending(ohci_batch->ed))
+	    ohci_ep->ed->status, ohci_ep->ed->td_head,
+	    ohci_ep->ed->td_tail, ohci_ep->ed->next);
+
+	if (!ed_inactive(ohci_ep->ed) && ed_transfer_pending(ohci_ep->ed))
 		return false;
 
@@ -188,7 +168,4 @@
 	/* Assume all data got through */
 	ohci_batch->base.transferred_size = ohci_batch->base.buffer_size;
-
-	/* Assume we will leave the last(unused) TD behind */
-	unsigned leave_td = ohci_batch->td_count;
 
 	/* Check all TDs */
@@ -217,28 +194,20 @@
 		} else {
 			usb_log_debug("Batch %p found error TD(%zu):%08x.",
-			    &ohci_batch->base, i,
-			    ohci_batch->tds[i]->status);
+			    &ohci_batch->base, i, ohci_batch->tds[i]->status);
 
 			/* ED should be stopped because of errors */
-			assert((ohci_batch->ed->td_head & ED_TDHEAD_HALTED_FLAG) != 0);
-
-			/* Now we have a problem: we don't know what TD
-			 * the head pointer points to, the retiring rules
-			 * described in specs say it should be the one after
-			 * the failed one so set the tail pointer accordingly.
-			 * It will be the one TD we leave behind.
+			assert((ohci_ep->ed->td_head & ED_TDHEAD_HALTED_FLAG) != 0);
+
+			/* We don't care where the processing stopped, we just 
+			 * need to make sure it's not using any of the TDs owned
+			 * by the transfer.
+			 *
+			 * As the chain is terminated by a TD in ownership of
+			 * the EP, set it.
 			 */
-			leave_td = i + 1;
-
-			/* Check TD assumption */
-			assert(ed_head_td(ohci_batch->ed) ==
-			    addr_to_phys(ohci_batch->tds[leave_td]));
-
-			/* Set tail to the same TD */
-			ed_set_tail_td(ohci_batch->ed,
-			    ohci_batch->tds[leave_td]);
-
-			/* Clear possible ED HALT */
-			ed_clear_halt(ohci_batch->ed);
+			ed_set_head_td(ohci_ep->ed, ohci_ep->tds[0]);
+
+			/* Clear the halted condition for the next transfer */
+			ed_clear_halt(ohci_ep->ed);
 			break;
 		}
@@ -247,33 +216,46 @@
 	    ohci_batch->base.buffer_size);
 
-	const size_t setup_size = (ohci_batch->base.ep->transfer_type == USB_TRANSFER_CONTROL)
-		? USB_SETUP_PACKET_SIZE
-		: 0;
-
 	if (ohci_batch->base.dir == USB_DIRECTION_IN)
 		memcpy(ohci_batch->base.buffer,
-		    ohci_batch->device_buffer + setup_size,
+		    ohci_batch->data_buffer,
 		    ohci_batch->base.transferred_size);
 
-	/* Store the remaining TD */
+	/* Make sure that we are leaving the right TD behind */
+	assert(addr_to_phys(ohci_ep->tds[0]) == ed_tail_td(ohci_ep->ed));
+	assert(ed_tail_td(ohci_ep->ed) == ed_head_td(ohci_ep->ed));
+
+	return true;
+}
+
+/** Starts execution of the TD list
+ *
+ * @param[in] ohci_batch Batch structure to use
+ */
+void ohci_transfer_batch_commit(const ohci_transfer_batch_t *ohci_batch)
+{
+	assert(ohci_batch);
+
 	ohci_endpoint_t *ohci_ep = ohci_endpoint_get(ohci_batch->base.ep);
-	assert(ohci_ep);
-	ohci_ep->td = ohci_batch->tds[leave_td];
-
-	/* Make sure that we are leaving the right TD behind */
-	assert(addr_to_phys(ohci_ep->td) == ed_head_td(ohci_batch->ed));
-	assert(addr_to_phys(ohci_ep->td) == ed_tail_td(ohci_batch->ed));
-
-	return true;
-}
-
-/** Starts execution of the TD list
- *
- * @param[in] ohci_batch Batch structure to use
- */
-void ohci_transfer_batch_commit(const ohci_transfer_batch_t *ohci_batch)
-{
-	assert(ohci_batch);
-	ed_set_tail_td(ohci_batch->ed, ohci_batch->tds[ohci_batch->td_count]);
+
+	usb_log_debug("Using ED(%p): %08x:%08x:%08x:%08x.", ohci_ep->ed,
+	    ohci_ep->ed->status, ohci_ep->ed->td_tail,
+	    ohci_ep->ed->td_head, ohci_ep->ed->next);
+
+	/*
+	 * According to spec, we need to copy the first TD to the currently
+	 * enqueued one.
+	 */
+	memcpy(ohci_ep->tds[0], ohci_batch->tds[0], sizeof(td_t));
+	ohci_batch->tds[0] = ohci_ep->tds[0];
+
+	td_t *last = ohci_batch->tds[ohci_batch->td_count - 1];
+	td_set_next(last, ohci_ep->tds[1]);
+
+	ed_set_tail_td(ohci_ep->ed, ohci_ep->tds[1]);
+
+	/* Swap the EP TDs for the next transfer */
+	td_t *tmp = ohci_ep->tds[0];
+	ohci_ep->tds[0] = ohci_ep->tds[1];
+	ohci_ep->tds[1] = tmp;
 }
 
@@ -294,7 +276,4 @@
 	assert(dir == USB_DIRECTION_IN || dir == USB_DIRECTION_OUT);
 
-	usb_log_debug("Using ED(%p): %08x:%08x:%08x:%08x.", ohci_batch->ed,
-	    ohci_batch->ed->status, ohci_batch->ed->td_tail,
-	    ohci_batch->ed->td_head, ohci_batch->ed->next);
 	static const usb_direction_t reverse_dir[] = {
 		[USB_DIRECTION_IN]  = USB_DIRECTION_OUT,
@@ -303,5 +282,4 @@
 
 	int toggle = 0;
-	const char* buffer = ohci_batch->device_buffer;
 	const usb_direction_t data_dir = dir;
 	const usb_direction_t status_dir = reverse_dir[dir];
@@ -310,12 +288,12 @@
 	td_init(
 	    ohci_batch->tds[0], ohci_batch->tds[1], USB_DIRECTION_BOTH,
-	    buffer, USB_SETUP_PACKET_SIZE, toggle);
+	    ohci_batch->setup_buffer, USB_SETUP_PACKET_SIZE, toggle);
 	usb_log_debug("Created CONTROL SETUP TD: %08x:%08x:%08x:%08x.",
 	    ohci_batch->tds[0]->status, ohci_batch->tds[0]->cbp,
 	    ohci_batch->tds[0]->next, ohci_batch->tds[0]->be);
-	buffer += USB_SETUP_PACKET_SIZE;
 
 	/* Data stage */
 	size_t td_current = 1;
+	const char* buffer = ohci_batch->data_buffer;
 	size_t remain_size = ohci_batch->base.buffer_size;
 	while (remain_size > 0) {
@@ -370,11 +348,8 @@
 	usb_direction_t dir = ohci_batch->base.dir;
 	assert(dir == USB_DIRECTION_IN || dir == USB_DIRECTION_OUT);
-	usb_log_debug("Using ED(%p): %08x:%08x:%08x:%08x.", ohci_batch->ed,
-	    ohci_batch->ed->status, ohci_batch->ed->td_tail,
-	    ohci_batch->ed->td_head, ohci_batch->ed->next);
 
 	size_t td_current = 0;
 	size_t remain_size = ohci_batch->base.buffer_size;
-	char *buffer = ohci_batch->device_buffer;
+	char *buffer = ohci_batch->data_buffer;
 	while (remain_size > 0) {
 		const size_t transfer_size = remain_size > OHCI_TD_MAX_TRANSFER
Index: uspace/drv/bus/usb/ohci/ohci_batch.h
===================================================================
--- uspace/drv/bus/usb/ohci/ohci_batch.h	(revision 3e6ff9a5d49f640854fbd4c5b5c21e928a71bc1d)
+++ uspace/drv/bus/usb/ohci/ohci_batch.h	(revision e67c50ac9b5c850d056f789863dea7d4da95af3a)
@@ -38,4 +38,5 @@
 #include <assert.h>
 #include <stdbool.h>
+#include <usb/dma_buffer.h>
 #include <usb/host/usb_transfer_batch.h>
 
@@ -47,12 +48,18 @@
 	usb_transfer_batch_t base;
 
-	/** Endpoint descriptor of the target endpoint. */
-	ed_t *ed;
-	/** List of TDs needed for the transfer */
-	td_t **tds;
 	/** Number of TDs used by the transfer */
 	size_t td_count;
-	/** Data buffer, must be accessible by the OHCI hw. */
-	char *device_buffer;
+
+	/**
+	 * List of TDs needed for the transfer - together with setup data
+	 * backed by the dma buffer. Note that the TD pointers are pointing to
+	 * the DMA buffer initially, but as the scheduling must use the first TD
+	 * from EP, it is replaced.
+	 */
+	td_t **tds;
+	char *setup_buffer;
+	char *data_buffer;
+
+	dma_buffer_t ohci_dma_buffer;
 } ohci_transfer_batch_t;
 
Index: uspace/drv/bus/usb/ohci/ohci_bus.c
===================================================================
--- uspace/drv/bus/usb/ohci/ohci_bus.c	(revision 3e6ff9a5d49f640854fbd4c5b5c21e928a71bc1d)
+++ uspace/drv/bus/usb/ohci/ohci_bus.c	(revision e67c50ac9b5c850d056f789863dea7d4da95af3a)
@@ -75,5 +75,5 @@
 	assert(dev);
 
-	ohci_endpoint_t *ohci_ep = malloc(sizeof(ohci_endpoint_t));
+	ohci_endpoint_t *ohci_ep = calloc(1, sizeof(ohci_endpoint_t));
 	if (ohci_ep == NULL)
 		return NULL;
@@ -81,16 +81,14 @@
 	endpoint_init(&ohci_ep->base, dev, desc);
 
-	ohci_ep->ed = malloc32(sizeof(ed_t));
-	if (ohci_ep->ed == NULL) {
+	const errno_t err = dma_buffer_alloc(&ohci_ep->dma_buffer, sizeof(ed_t) + 2 * sizeof(td_t));
+	if (err) {
 		free(ohci_ep);
 		return NULL;
 	}
 
-	ohci_ep->td = malloc32(sizeof(td_t));
-	if (ohci_ep->td == NULL) {
-		free32(ohci_ep->ed);
-		free(ohci_ep);
-		return NULL;
-	}
+	ohci_ep->ed = ohci_ep->dma_buffer.virt;
+
+	ohci_ep->tds[0] = (td_t *) ohci_ep->ed + 1;
+	ohci_ep->tds[1] = ohci_ep->tds[0] + 1;
 
 	link_initialize(&ohci_ep->eplist_link);
@@ -109,6 +107,5 @@
 	ohci_endpoint_t *instance = ohci_endpoint_get(ep);
 
-	free32(instance->ed);
-	free32(instance->td);
+	dma_buffer_free(&instance->dma_buffer);
 	free(instance);
 }
@@ -125,5 +122,5 @@
 		return err;
 
-	ed_init(ohci_ep->ed, ep, ohci_ep->td);
+	ed_init(ohci_ep->ed, ep, ohci_ep->tds[0]);
 	hc_enqueue_endpoint(bus->hc, ep);
 	endpoint_set_online(ep, &bus->hc->guard);
Index: uspace/drv/bus/usb/ohci/ohci_bus.h
===================================================================
--- uspace/drv/bus/usb/ohci/ohci_bus.h	(revision 3e6ff9a5d49f640854fbd4c5b5c21e928a71bc1d)
+++ uspace/drv/bus/usb/ohci/ohci_bus.h	(revision e67c50ac9b5c850d056f789863dea7d4da95af3a)
@@ -38,4 +38,5 @@
 #include <assert.h>
 #include <adt/list.h>
+#include <usb/dma_buffer.h>
 #include <usb/host/usb2_bus.h>
 
@@ -43,5 +44,12 @@
 #include "hw_struct/transfer_descriptor.h"
 
-/** Connector structure linking ED to to prepared TD. */
+/**
+ * Connector structure linking ED to to prepared TD.
+ *
+ * OHCI requires new transfers to be appended at the end of a queue. But it has
+ * a weird semantics of a leftover TD, which serves as a placeholder. This left
+ * TD is overwritten with first TD of a new transfer, and the spare one is used
+ * as the next placeholder. Then the two are swapped for the next transaction.
+ */
 typedef struct ohci_endpoint {
 	endpoint_t base;
@@ -49,6 +57,10 @@
 	/** OHCI endpoint descriptor */
 	ed_t *ed;
-	/** Currently enqueued transfer descriptor */
-	td_t *td;
+	/** TDs to be used at the beginning and end of transaction */
+	td_t *tds [2];
+
+	/** Buffer to back ED + 2 TD */
+	dma_buffer_t dma_buffer;
+
 	/** Link in endpoint_list*/
 	link_t eplist_link;
