Index: uspace/drv/bus/usb/xhci/Makefile
===================================================================
--- uspace/drv/bus/usb/xhci/Makefile	(revision 47930236198628981539314352b08f0a00aa2105)
+++ uspace/drv/bus/usb/xhci/Makefile	(revision 708d8fcd39276a71e62f9b40a631eab1814b52d7)
@@ -39,4 +39,5 @@
 	endpoint.c \
 	hc.c \
+	isoch.c \
 	main.c \
 	rh.c \
Index: uspace/drv/bus/usb/xhci/bus.c
===================================================================
--- uspace/drv/bus/usb/xhci/bus.c	(revision 47930236198628981539314352b08f0a00aa2105)
+++ uspace/drv/bus/usb/xhci/bus.c	(revision 708d8fcd39276a71e62f9b40a631eab1814b52d7)
@@ -58,11 +58,4 @@
 static endpoint_t *endpoint_create(device_t *, const usb_endpoint_descriptors_t *);
 
-/** Ops receive generic bus_t pointer. */
-static inline xhci_bus_t *bus_to_xhci_bus(bus_t *bus_base)
-{
-	assert(bus_base);
-	return (xhci_bus_t *) bus_base;
-}
-
 /**
  * Assign address and control endpoint to a new XHCI device. Once this function
Index: uspace/drv/bus/usb/xhci/bus.h
===================================================================
--- uspace/drv/bus/usb/xhci/bus.h	(revision 47930236198628981539314352b08f0a00aa2105)
+++ uspace/drv/bus/usb/xhci/bus.h	(revision 708d8fcd39276a71e62f9b40a631eab1814b52d7)
@@ -59,4 +59,10 @@
 int xhci_bus_remove_device(xhci_bus_t *, device_t *);
 
+static inline xhci_bus_t *bus_to_xhci_bus(bus_t *bus_base)
+{
+	assert(bus_base);
+	return (xhci_bus_t *) bus_base;
+}
+
 #endif
 /**
Index: uspace/drv/bus/usb/xhci/endpoint.c
===================================================================
--- uspace/drv/bus/usb/xhci/endpoint.c	(revision 47930236198628981539314352b08f0a00aa2105)
+++ uspace/drv/bus/usb/xhci/endpoint.c	(revision 708d8fcd39276a71e62f9b40a631eab1814b52d7)
@@ -84,5 +84,5 @@
 	 */
 	if (dev->speed >= USB_SPEED_HIGH || ep->transfer_type != USB_TRANSFER_INTERRUPT) {
-		xhci_ep->interval = desc->endpoint.poll_interval;
+		xhci_ep->interval = 1 << (xhci_ep->interval - 1);
 	}
 
@@ -92,18 +92,6 @@
 	}
 
-	if (xhci_ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS) {
-		xhci_ep->isoch->max_size = desc->companion.bytes_per_interval
-			? desc->companion.bytes_per_interval
-			: ep->max_transfer_size;
-		/* Technically there could be superspeed plus too. */
-
-		/* Allocate and setup isochronous-specific structures. */
-		xhci_ep->isoch->enqueue = 0;
-		xhci_ep->isoch->dequeue = 0;
-		xhci_ep->isoch->started = false;
-
-		fibril_mutex_initialize(&xhci_ep->isoch->guard);
-		fibril_condvar_initialize(&xhci_ep->isoch->avail);
-	}
+	if (xhci_ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS)
+		isoch_init(xhci_ep, desc);
 
 	if ((rc = alloc_transfer_ds(xhci_ep)))
@@ -272,30 +260,4 @@
 }
 
-/** TODO document this
- */
-static int xhci_isoch_alloc_transfers(xhci_endpoint_t *xhci_ep) {
-	int i = 0;
-	int err = EOK;
-	while (i < XHCI_ISOCH_BUFFER_COUNT) {
-		xhci_isoch_transfer_t *transfer = &xhci_ep->isoch->transfers[i];
-		if (dma_buffer_alloc(&transfer->data, xhci_ep->isoch->max_size)) {
-			err = ENOMEM;
-			break;
-		}
-		transfer->size = 0;
-		++i;
-	}
-
-	if (err) {
-		--i;
-		while(i >= 0) {
-			dma_buffer_free(&xhci_ep->isoch->transfers[i].data);
-			--i;
-		}
-	}
-
-	return err;
-}
-
 /** Allocate transfer data structures for XHCI endpoint.
  * @param[in] xhci_ep XHCI endpoint to allocate data structures for.
@@ -316,5 +278,5 @@
 
 	if (xhci_ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS) {
-		if ((err = xhci_isoch_alloc_transfers(xhci_ep))) {
+		if ((err = isoch_alloc_transfers(xhci_ep))) {
 			xhci_trb_ring_fini(&xhci_ep->ring);
 			return err;
@@ -427,5 +389,5 @@
 	XHCI_EP_TR_DPTR_SET(*ctx, ep->ring.dequeue);
 	XHCI_EP_DCS_SET(*ctx, 1);
-	XHCI_EP_INTERVAL_SET(*ctx, fnzb32(ep->interval) % 32 - 1);
+	XHCI_EP_INTERVAL_SET(*ctx, fnzb32(ep->interval) % 32);
 
 	XHCI_EP_MAX_ESIT_PAYLOAD_LO_SET(*ctx, ep->isoch->max_size & 0xFFFF);
@@ -446,5 +408,5 @@
 	XHCI_EP_TR_DPTR_SET(*ctx, ep->ring.dequeue);
 	XHCI_EP_DCS_SET(*ctx, 1);
-	XHCI_EP_INTERVAL_SET(*ctx, fnzb32(ep->interval) % 32 - 1);
+	XHCI_EP_INTERVAL_SET(*ctx, fnzb32(ep->interval) % 32);
 	// TODO: max ESIT payload
 }
Index: uspace/drv/bus/usb/xhci/endpoint.h
===================================================================
--- uspace/drv/bus/usb/xhci/endpoint.h	(revision 47930236198628981539314352b08f0a00aa2105)
+++ uspace/drv/bus/usb/xhci/endpoint.h	(revision 708d8fcd39276a71e62f9b40a631eab1814b52d7)
@@ -45,7 +45,7 @@
 #include <ddf/driver.h>
 
+#include "isoch.h"
+#include "transfers.h"
 #include "trb_ring.h"
-
-#include "transfers.h"
 
 typedef struct xhci_device xhci_device_t;
@@ -94,24 +94,5 @@
 
 	/** This field is a valid pointer for (and only for) isochronous transfers. */
-	struct {
-		/** The maximum size of an isochronous transfer and therefore the size of buffers */
-		size_t max_size;
-
-		/** Isochronous scheduled transfers with respective buffers */
-		#define XHCI_ISOCH_BUFFER_COUNT 4
-		xhci_isoch_transfer_t transfers[XHCI_ISOCH_BUFFER_COUNT];
-
-		/** Indices to transfers */
-		size_t dequeue, enqueue;
-
-		/** Are isochronous transfers started? */
-		bool started;
-
-		/** Protects common buffers. */
-		fibril_mutex_t guard;
-
-		/** Signals filled buffer. */
-		fibril_condvar_t avail;
-	} isoch [0];
+	xhci_isoch_t isoch [0];
 } xhci_endpoint_t;
 
Index: uspace/drv/bus/usb/xhci/hc.c
===================================================================
--- uspace/drv/bus/usb/xhci/hc.c	(revision 47930236198628981539314352b08f0a00aa2105)
+++ uspace/drv/bus/usb/xhci/hc.c	(revision 708d8fcd39276a71e62f9b40a631eab1814b52d7)
@@ -192,4 +192,6 @@
 	hc->ac64 = XHCI_REG_RD(hc->cap_regs, XHCI_CAP_AC64);
 	hc->max_slots = XHCI_REG_RD(hc->cap_regs, XHCI_CAP_MAX_SLOTS);
+	unsigned ist = XHCI_REG_RD(hc->cap_regs, XHCI_CAP_IST);
+	hc->ist = (ist & 0x10 >> 1) * (ist & 0xf);
 
 	if ((err = hc_parse_ec(hc))) {
@@ -611,5 +613,5 @@
  * Ring a xHC Doorbell. Implements section 4.7.
  */
-int hc_ring_doorbell(xhci_hc_t *hc, unsigned doorbell, unsigned target)
+void hc_ring_doorbell(xhci_hc_t *hc, unsigned doorbell, unsigned target)
 {
 	assert(hc);
@@ -617,5 +619,4 @@
 	pio_write_32(&hc->db_arry[doorbell], v);
 	usb_log_debug2("Ringing doorbell %d (target: %d)", doorbell, target);
-	return EOK;
 }
 
Index: uspace/drv/bus/usb/xhci/hc.h
===================================================================
--- uspace/drv/bus/usb/xhci/hc.h	(revision 47930236198628981539314352b08f0a00aa2105)
+++ uspace/drv/bus/usb/xhci/hc.h	(revision 708d8fcd39276a71e62f9b40a631eab1814b52d7)
@@ -84,4 +84,5 @@
 	unsigned max_slots;
 	bool ac64;
+	unsigned ist;			/**< IST in microframes */
 
 	/** Port speed mapping */
@@ -105,5 +106,5 @@
 int hc_start(xhci_hc_t *, bool);
 void hc_fini(xhci_hc_t *);
-int hc_ring_doorbell(xhci_hc_t *, unsigned, unsigned);
+void hc_ring_doorbell(xhci_hc_t *, unsigned, unsigned);
 int hc_enable_slot(xhci_hc_t *, uint32_t *);
 int hc_disable_slot(xhci_hc_t *, xhci_device_t *);
Index: uspace/drv/bus/usb/xhci/hw_struct/regs.h
===================================================================
--- uspace/drv/bus/usb/xhci/hw_struct/regs.h	(revision 47930236198628981539314352b08f0a00aa2105)
+++ uspace/drv/bus/usb/xhci/hw_struct/regs.h	(revision 708d8fcd39276a71e62f9b40a631eab1814b52d7)
@@ -481,5 +481,6 @@
 } xhci_rt_regs_t;
 
-#define XHCI_RT_MFINDEX        mfindex, 32, FIELD
+#define XHCI_RT_MFINDEX        mfindex, 32, RANGE, 13, 0
+#define XHCI_MFINDEX_MAX	(1 << 14)
 
 /**
Index: uspace/drv/bus/usb/xhci/hw_struct/trb.h
===================================================================
--- uspace/drv/bus/usb/xhci/hw_struct/trb.h	(revision 47930236198628981539314352b08f0a00aa2105)
+++ uspace/drv/bus/usb/xhci/hw_struct/trb.h	(revision 708d8fcd39276a71e62f9b40a631eab1814b52d7)
@@ -147,9 +147,11 @@
 	xhci_dword_set_bits(&(trb).control, val, 17, 16)
 
-#define TRB_CTRL_SET_TBC(trb, val) \
+#define TRB_ISOCH_SET_TBC(trb, val) \
 	xhci_dword_set_bits(&(trb).control, val, 8, 7)
-#define TRB_CTRL_SET_TLBPC(trb, val) \
+#define TRB_ISOCH_SET_TLBPC(trb, val) \
 	xhci_dword_set_bits(&(trb).control, val, 19, 16)
-#define TRB_CTRL_SET_SIA(trb, val) \
+#define TRB_ISOCH_SET_FRAMEID(trb, val) \
+	xhci_dword_set_bits(&(trb).control, val, 30, 20)
+#define TRB_ISOCH_SET_SIA(trb, val) \
 	xhci_dword_set_bits(&(trb).control, val, 31, 31)
 
Index: uspace/drv/bus/usb/xhci/isoch.c
===================================================================
--- uspace/drv/bus/usb/xhci/isoch.c	(revision 708d8fcd39276a71e62f9b40a631eab1814b52d7)
+++ uspace/drv/bus/usb/xhci/isoch.c	(revision 708d8fcd39276a71e62f9b40a631eab1814b52d7)
@@ -0,0 +1,593 @@
+/*
+ * Copyright (c) 2017 HelUSB3 team
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup drvusbxhci
+ * @{
+ */
+/** @file
+ * @brief The host controller endpoint management.
+ */
+
+#include <str_error.h>
+
+#include "endpoint.h"
+#include "hw_struct/trb.h"
+#include "hw_struct/regs.h"
+#include "trb_ring.h"
+#include "hc.h"
+#include "bus.h"
+
+#include "isoch.h"
+
+void isoch_init(xhci_endpoint_t *ep, const usb_endpoint_descriptors_t *desc)
+{
+	assert(ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS);
+	xhci_isoch_t * const isoch = ep->isoch;
+
+	fibril_mutex_initialize(&isoch->guard);
+	fibril_condvar_initialize(&isoch->avail);
+
+	isoch->max_size = desc->companion.bytes_per_interval
+		? desc->companion.bytes_per_interval
+		: ep->base.max_transfer_size;
+	/* Technically there could be superspeed plus too. */
+}
+
+static void isoch_reset(xhci_endpoint_t *ep)
+{
+	xhci_isoch_t * const isoch = ep->isoch;
+	assert(fibril_mutex_is_locked(&isoch->guard));
+
+	isoch->dequeue = isoch->enqueue = isoch->hw_enqueue = 0;
+
+	for (size_t i = 0; i < XHCI_ISOCH_BUFFER_COUNT; ++i) {
+		isoch->transfers[i].state = ISOCH_EMPTY;
+	}
+
+	fibril_timer_clear_locked(isoch->feeding_timer);
+	isoch->last_mfindex = -1U;
+	usb_log_info("[isoch] Endpoint" XHCI_EP_FMT ": Data flow reset.", XHCI_EP_ARGS(*ep));
+}
+
+void isoch_fini(xhci_endpoint_t *ep)
+{
+	assert(ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS);
+	xhci_isoch_t * const isoch = ep->isoch;
+
+	if (isoch->feeding_timer) {
+		fibril_timer_clear(isoch->feeding_timer);
+		fibril_timer_destroy(isoch->feeding_timer);
+	}
+
+	for (size_t i = 0; i < XHCI_ISOCH_BUFFER_COUNT; ++i)
+		dma_buffer_free(&isoch->transfers[i].data);
+}
+
+/**
+ * Allocate isochronous buffers. Create the feeding timer.
+ */
+int isoch_alloc_transfers(xhci_endpoint_t *ep) {
+	assert(ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS);
+	xhci_isoch_t * const isoch = ep->isoch;
+
+	isoch->feeding_timer = fibril_timer_create(&isoch->guard);
+	if (!isoch->feeding_timer)
+		return ENOMEM;
+
+	for (size_t i = 0; i < XHCI_ISOCH_BUFFER_COUNT; ++i) {
+		xhci_isoch_transfer_t *transfer = &isoch->transfers[i];
+		if (dma_buffer_alloc(&transfer->data, isoch->max_size)) {
+			isoch_fini(ep);
+			return ENOMEM;
+		}
+	}
+
+	fibril_mutex_lock(&isoch->guard);
+	isoch_reset(ep);
+	fibril_mutex_unlock(&isoch->guard);
+
+	return EOK;
+}
+
+static int schedule_isochronous_trb(xhci_endpoint_t *ep, xhci_isoch_transfer_t *it)
+{
+	xhci_trb_t trb;
+	xhci_trb_clean(&trb);
+
+	trb.parameter = it->data.phys;
+	TRB_CTRL_SET_XFER_LEN(trb, it->size);
+	TRB_CTRL_SET_TD_SIZE(trb, 0);
+	TRB_CTRL_SET_IOC(trb, 1);
+	TRB_CTRL_SET_TRB_TYPE(trb, XHCI_TRB_TYPE_ISOCH);
+
+	// see 4.14.1 and 4.11.2.3 for the explanation, how to calculate those
+	size_t tdpc = it->size / 1024 + ((it->size % 1024) ? 1 : 0);
+	size_t tbc = tdpc / ep->max_burst;
+	if (!tdpc % ep->max_burst) --tbc;
+	size_t bsp = tdpc % ep->max_burst;
+	size_t tlbpc = (bsp ? bsp : ep->max_burst) - 1;
+
+	TRB_ISOCH_SET_TBC(trb, tbc);
+	TRB_ISOCH_SET_TLBPC(trb, tlbpc);
+	TRB_ISOCH_SET_FRAMEID(trb, (it->mfindex / 8) % 2048);
+
+	const int err = xhci_trb_ring_enqueue(&ep->ring, &trb, &it->interrupt_trb_phys);
+	return err;
+}
+
+static inline void calc_next_mfindex(xhci_endpoint_t *ep, xhci_isoch_transfer_t *it)
+{
+	xhci_isoch_t * const isoch = ep->isoch;
+	if (isoch->last_mfindex == -1U) {
+		const xhci_bus_t *bus = bus_to_xhci_bus(ep->base.device->bus);
+		const xhci_hc_t *hc = bus->hc;
+
+		/* Choose some number, give us a little time to prepare the
+		 * buffers */
+		it->mfindex = XHCI_REG_RD(hc->rt_regs, XHCI_RT_MFINDEX) + 1
+		       + XHCI_ISOCH_BUFFER_COUNT * ep->interval
+		       + hc->ist;
+
+		// Align to ESIT start boundary
+		it->mfindex += ep->interval - 1;
+		it->mfindex &= ~(ep->interval - 1);
+	} else {
+		it->mfindex = (isoch->last_mfindex + ep->interval) % XHCI_MFINDEX_MAX;
+	}
+}
+
+/** 825 ms in uframes */
+#define END_FRAME_DELAY   (895000 / 125)
+
+typedef enum {
+	WINDOW_TOO_SOON,
+	WINDOW_INSIDE,
+	WINDOW_TOO_LATE,
+} window_position_t;
+
+typedef struct {
+	window_position_t position;
+	uint32_t offset;
+} window_decision_t;
+
+/**
+ * Decide on the position of mfindex relatively to the window specified by
+ * Start Frame ID and End Frame ID. The resulting structure contains the
+ * decision, and in case of the mfindex being outside, also the number of
+ * uframes it's off.
+ */
+static inline void window_decide(window_decision_t *res, xhci_hc_t *hc, uint32_t mfindex)
+{
+	uint32_t current_mfindex = XHCI_REG_RD(hc->rt_regs, XHCI_RT_MFINDEX) + 1;
+
+	/*
+	 * In your mind, rotate the clock so the window is at its beginning.
+	 * The length of the window is always the same, and by rotating the
+	 * mfindex too, we can decide by the value of it easily.
+	 */
+	mfindex = (mfindex - current_mfindex - hc->ist + XHCI_MFINDEX_MAX) % XHCI_MFINDEX_MAX;
+	const uint32_t end = END_FRAME_DELAY - hc->ist;
+	const uint32_t threshold = (XHCI_MFINDEX_MAX + end) / 2;
+
+	if (mfindex <= end) {
+		res->position = WINDOW_INSIDE;
+	} else if (mfindex > threshold) {
+		res->position = WINDOW_TOO_LATE;
+		res->offset = XHCI_MFINDEX_MAX - mfindex;
+	} else {
+		res->position = WINDOW_TOO_SOON;
+		res->offset = mfindex - end;
+	}
+	/*
+	 * TODO: The "size" of the clock is too low. We have to scale it a bit
+	 * to ensure correct scheduling of transfers, that are
+	 * XHCI_ISOCH_BUFFER_COUNT * interval away from now.
+	 * Maximum interval is 8 seconds, which means we need a size of 
+	 * 16 seconds. The size of MFIINDEX is 2 seconds only.
+	 *
+	 * A plan is to create a thin abstraction at HC, which would return
+	 * a time from 32-bit clock, having its high bits updated by the
+	 * MFINDEX Wrap Event, and low bits from the MFINDEX register. Using
+	 * this 32-bit clock, one can plan 6 days ahead.
+	 */
+}
+
+static void isoch_feed_out_timer(void *);
+static void isoch_feed_in_timer(void *);
+
+/**
+ * Schedule TRBs with filled buffers to HW. Takes filled isoch transfers and
+ * pushes their TRBs to the ring.
+ *
+ * According to 4.11.2.5, we can't just push all TRBs we have. We must not do
+ * it too late, but also not too soon.
+ */
+static void isoch_feed_out(xhci_endpoint_t *ep)
+{
+	assert(ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS);
+	xhci_isoch_t * const isoch = ep->isoch;
+	assert(fibril_mutex_is_locked(&isoch->guard));
+
+	xhci_bus_t *bus = bus_to_xhci_bus(ep->base.device->bus);
+	xhci_hc_t *hc = bus->hc;
+
+	bool fed = false;
+
+	while (isoch->hw_enqueue != isoch->enqueue) {
+		xhci_isoch_transfer_t * const it = &isoch->transfers[isoch->hw_enqueue];
+
+		assert(it->state == ISOCH_FILLED);
+
+		window_decision_t wd;
+		window_decide(&wd, hc, it->mfindex);
+
+		switch (wd.position) {
+		case WINDOW_TOO_SOON: {
+			const suseconds_t delay = wd.offset * 125;
+			usb_log_debug2("[isoch] delaying feeding buffer %lu for %ldus",
+				it - isoch->transfers, delay);
+			fibril_timer_set_locked(isoch->feeding_timer, delay,
+			    isoch_feed_out_timer, ep);
+			break;
+		}
+
+		case WINDOW_INSIDE:
+			usb_log_debug2("[isoch] feeding buffer %lu at 0x%x",
+			    it - isoch->transfers, it->mfindex);
+			it->error = schedule_isochronous_trb(ep, it);
+			if (it->error) {
+				it->state = ISOCH_COMPLETE;
+			} else {
+				it->state = ISOCH_FED;
+				fed = true;
+			}
+
+			isoch->hw_enqueue = (isoch->hw_enqueue + 1) % XHCI_ISOCH_BUFFER_COUNT;
+			break;
+
+		case WINDOW_TOO_LATE:
+			/* Missed the opportunity to schedule. Just mark this transfer as skipped. */
+			usb_log_debug2("[isoch] missed feeding buffer %lu at 0x%x by %u uframes",
+			    it - isoch->transfers, it->mfindex, wd.offset);
+			it->state = ISOCH_COMPLETE;
+			it->error = EOK;
+			it->size = 0;
+
+			isoch->hw_enqueue = (isoch->hw_enqueue + 1) % XHCI_ISOCH_BUFFER_COUNT;
+			break;
+		}
+	}
+
+	if (fed) {
+		const uint8_t slot_id = xhci_device_get(ep->base.device)->slot_id;
+		const uint8_t target = xhci_endpoint_index(ep) + 1; /* EP Doorbells start at 1 */
+		hc_ring_doorbell(hc, slot_id, target);
+	}
+
+}
+
+static void isoch_feed_out_timer(void *ep)
+{
+	xhci_isoch_t * const isoch = xhci_endpoint_get(ep)->isoch;
+	fibril_mutex_lock(&isoch->guard);
+	isoch_feed_out(ep);
+	fibril_mutex_unlock(&isoch->guard);
+}
+
+/**
+ * Schedule TRBs with empty, withdrawn buffers to HW. Takes empty isoch
+ * transfers and pushes their TRBs to the ring.
+ *
+ * According to 4.11.2.5, we can't just push all TRBs we have. We must not do
+ * it too late, but also not too soon.
+ */
+static void isoch_feed_in(xhci_endpoint_t *ep)
+{
+	assert(ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS);
+	xhci_isoch_t * const isoch = ep->isoch;
+	assert(fibril_mutex_is_locked(&isoch->guard));
+
+	xhci_bus_t *bus = bus_to_xhci_bus(ep->base.device->bus);
+	xhci_hc_t *hc = bus->hc;
+
+	bool fed = false;
+
+	while (isoch->transfers[isoch->enqueue].state <= ISOCH_FILLED) {
+		xhci_isoch_transfer_t * const it = &isoch->transfers[isoch->enqueue];
+
+		/* IN buffers are "filled" with free space */
+		if (it->state == ISOCH_EMPTY) {
+			it->size = isoch->max_size;
+			it->state = ISOCH_FILLED;
+			calc_next_mfindex(ep, it);
+		}
+
+		window_decision_t wd;
+		window_decide(&wd, hc, it->mfindex);
+
+		switch (wd.position) {
+		case WINDOW_TOO_SOON: {
+			/* Not allowed to feed yet. Defer to later. */
+			const suseconds_t delay = wd.offset * 125;
+			usb_log_debug2("[isoch] delaying feeding buffer %lu for %ldus",
+			    it - isoch->transfers, delay);
+			fibril_timer_set_locked(isoch->feeding_timer, delay,
+			    isoch_feed_in_timer, ep);
+			break;
+		}
+
+		case WINDOW_TOO_LATE:
+			usb_log_debug2("[isoch] missed feeding buffer %lu at 0x%x by %u uframes",
+			    it - isoch->transfers, it->mfindex, wd.offset);
+			/* Missed the opportunity to schedule. Schedule ASAP. */
+			it->mfindex += wd.offset;
+			// Align to ESIT start boundary
+			it->mfindex += ep->interval - 1;
+			it->mfindex &= ~(ep->interval - 1);
+
+			/* fallthrough */
+		case WINDOW_INSIDE:
+			isoch->enqueue = (isoch->enqueue + 1) % XHCI_ISOCH_BUFFER_COUNT;
+			isoch->last_mfindex = it->mfindex;
+
+			usb_log_debug2("[isoch] feeding buffer %lu at 0x%x",
+			    it - isoch->transfers, it->mfindex);
+
+			it->error = schedule_isochronous_trb(ep, it);
+			if (it->error) {
+				it->state = ISOCH_COMPLETE;
+			} else {
+				it->state = ISOCH_FED;
+				fed = true;
+			}
+			break;
+		}
+	}
+
+	if (fed) {
+		const uint8_t slot_id = xhci_device_get(ep->base.device)->slot_id;
+		const uint8_t target = xhci_endpoint_index(ep) + 1; /* EP Doorbells start at 1 */
+		hc_ring_doorbell(hc, slot_id, target);
+	}
+}
+
+static void isoch_feed_in_timer(void *ep)
+{
+	xhci_isoch_t * const isoch = xhci_endpoint_get(ep)->isoch;
+	fibril_mutex_lock(&isoch->guard);
+	isoch_feed_in(ep);
+	fibril_mutex_unlock(&isoch->guard);
+}
+
+/**
+ * First, withdraw all (at least one) results left by previous transfers to
+ * make room in the ring. Stop on first error.
+ *
+ * When there is at least one buffer free, fill it with data. Then try to feed
+ * it to the xHC.
+ */
+int isoch_schedule_out(xhci_transfer_t *transfer)
+{
+	int err = EOK;
+
+	xhci_endpoint_t *ep = xhci_endpoint_get(transfer->batch.ep);
+	assert(ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS);
+	xhci_isoch_t * const isoch = ep->isoch;
+
+	if (transfer->batch.buffer_size > isoch->max_size) {
+		usb_log_error("Cannot schedule an oversized isochronous transfer.");
+		return ELIMIT;
+	}
+
+	fibril_mutex_lock(&isoch->guard);
+
+	/* Get the buffer to write to */
+	xhci_isoch_transfer_t *it = &isoch->transfers[isoch->enqueue];
+
+	/* Wait for the buffer to be completed */
+	while (it->state == ISOCH_FED || it->state == ISOCH_FILLED) {
+		fibril_condvar_wait(&isoch->avail, &isoch->guard);
+		/* The enqueue ptr may have changed while sleeping */
+		it = &isoch->transfers[isoch->enqueue];
+	}
+
+	isoch->enqueue = (isoch->enqueue + 1) % XHCI_ISOCH_BUFFER_COUNT;
+
+	/* Withdraw results from previous transfers. */
+	transfer->batch.transfered_size = 0;
+	xhci_isoch_transfer_t *res = &isoch->transfers[isoch->dequeue];
+	while (res->state == ISOCH_COMPLETE) {
+		isoch->dequeue = (isoch->dequeue + 1) % XHCI_ISOCH_BUFFER_COUNT;
+
+		res->state = ISOCH_EMPTY;
+		transfer->batch.transfered_size += res->size;
+		transfer->batch.error = res->error;
+		if (res->error)
+			break; // Announce one error at a time
+
+		res = &isoch->transfers[isoch->dequeue];
+	}
+
+	assert(it->state == ISOCH_EMPTY);
+
+	/* Calculate when to schedule next transfer */
+	calc_next_mfindex(ep, it);
+	isoch->last_mfindex = it->mfindex;
+	usb_log_debug2("[isoch] buffer %zu will be on schedule at 0x%x", it - isoch->transfers, it->mfindex);
+
+	/* Prepare the transfer. */
+	it->size = transfer->batch.buffer_size;
+	memcpy(it->data.virt, transfer->batch.buffer, it->size);
+	it->state = ISOCH_FILLED;
+
+	fibril_timer_clear_locked(isoch->feeding_timer);
+	isoch_feed_out(ep);
+
+	fibril_mutex_unlock(&isoch->guard);
+
+	usb_transfer_batch_finish(&transfer->batch);
+	return err;
+}
+
+/**
+ * IN is in fact easier than OUT. Our responsibility is just to feed all empty
+ * buffers, and fetch one filled buffer from the ring.
+ */
+int isoch_schedule_in(xhci_transfer_t *transfer)
+{
+	xhci_endpoint_t *ep = xhci_endpoint_get(transfer->batch.ep);
+	assert(ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS);
+	xhci_isoch_t * const isoch = ep->isoch;
+
+	if (transfer->batch.buffer_size < isoch->max_size) {
+		usb_log_error("Cannot schedule an undersized isochronous transfer.");
+		return ELIMIT;
+	}
+
+	fibril_mutex_lock(&isoch->guard);
+
+	xhci_isoch_transfer_t *it = &isoch->transfers[isoch->dequeue];
+
+	/* Wait for at least one transfer to complete. */
+	while (it->state != ISOCH_COMPLETE) {
+		/* First, make sure we will have something to read. */
+		fibril_timer_clear_locked(isoch->feeding_timer);
+		isoch_feed_in(ep);
+
+		usb_log_debug2("[isoch] waiting for buffer %zu to be completed", it - isoch->transfers);
+		fibril_condvar_wait(&isoch->avail, &isoch->guard);
+
+		/* The enqueue ptr may have changed while sleeping */
+		it = &isoch->transfers[isoch->dequeue];
+	}
+
+	isoch->dequeue = (isoch->dequeue + 1) % XHCI_ISOCH_BUFFER_COUNT;
+
+	/* Withdraw results from previous transfer. */
+	if (!it->error) {
+		memcpy(transfer->batch.buffer, it->data.virt, it->size);
+		transfer->batch.transfered_size = it->size;
+		transfer->batch.error = it->error;
+	}
+
+	/* Prepare the empty buffer */
+	it->state = ISOCH_EMPTY;
+
+	fibril_mutex_unlock(&isoch->guard);
+	usb_transfer_batch_finish(&transfer->batch);
+
+	return EOK;
+}
+
+int isoch_handle_transfer_event(xhci_hc_t *hc, xhci_endpoint_t *ep, xhci_trb_t *trb)
+{
+	assert(ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS);
+	xhci_isoch_t * const isoch = ep->isoch;
+
+	fibril_mutex_lock(&ep->isoch->guard);
+
+	int err;
+	const xhci_trb_completion_code_t completion_code = TRB_COMPLETION_CODE(*trb);
+
+	switch (completion_code) {
+		case XHCI_TRBC_RING_OVERRUN:
+		case XHCI_TRBC_RING_UNDERRUN:
+			/* For OUT, there was nothing to process */
+			/* For IN, the buffer has overfilled, we empty the buffers and readd TRBs */
+			usb_log_warning("Ring over/underrun.");
+			isoch_reset(ep);
+			fibril_condvar_broadcast(&ep->isoch->avail);
+			fibril_mutex_unlock(&ep->isoch->guard);
+			return EOK;
+		case XHCI_TRBC_SHORT_PACKET:
+		case XHCI_TRBC_SUCCESS:
+			err = EOK;
+			break;
+		default:
+			usb_log_warning("Transfer not successfull: %u", completion_code);
+			err = EIO;
+			break;
+	}
+
+	bool found_mine = false;
+	bool found_incomplete = false;
+
+	/*
+	 * The order of delivering events is not necessarily the one we would
+	 * expect. It is safer to walk the list of our 4 transfers and check
+	 * which one it is.
+	 */
+	for (size_t i = 0; i < XHCI_ISOCH_BUFFER_COUNT; ++i) {
+		xhci_isoch_transfer_t * const it = &isoch->transfers[i];
+
+		switch (it->state) {
+		case ISOCH_FILLED:
+			found_incomplete = true;
+			break;
+
+		case ISOCH_FED:
+			if (it->interrupt_trb_phys != trb->parameter) {
+				found_incomplete = true;
+				break;
+			}
+
+			usb_log_debug2("[isoch] buffer %zu completed", it - isoch->transfers);
+			it->state = ISOCH_COMPLETE;
+			it->size -= TRB_TRANSFER_LENGTH(*trb);
+			it->error = err;
+			found_mine = true;
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (!found_mine) {
+		usb_log_warning("[isoch] A transfer event occured for unknown transfer.");
+	}
+
+	/*
+	 * It may happen that the driver already stopped reading (writing),
+	 * and our buffers are filled (empty). As QEMU (and possibly others)
+	 * does not send RING_UNDERRUN (OVERRUN) event, detect it here.
+	 */
+	if (!found_incomplete) {
+		usb_log_warning("[isoch] Endpoint" XHCI_EP_FMT ": Detected "
+		    "isochronous ring %s.", XHCI_EP_ARGS(*ep),
+		    (ep->base.direction == USB_DIRECTION_IN) ? "underrun" : "overrun");
+		isoch_reset(ep);
+	}
+
+	fibril_condvar_broadcast(&ep->isoch->avail);
+	fibril_mutex_unlock(&ep->isoch->guard);
+	return EOK;
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/isoch.h
===================================================================
--- uspace/drv/bus/usb/xhci/isoch.h	(revision 708d8fcd39276a71e62f9b40a631eab1814b52d7)
+++ uspace/drv/bus/usb/xhci/isoch.h	(revision 708d8fcd39276a71e62f9b40a631eab1814b52d7)
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2017 HelUSB3 team
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup drvusbxhci
+ * @{
+ */
+/** @file
+ * @brief Data structures and functions related to isochronous transfers.
+ */
+
+#ifndef XHCI_ISOCH_H
+#define XHCI_ISOCH_H
+
+#include <usb/host/dma_buffer.h>
+
+#include "trb_ring.h"
+
+#include "transfers.h"
+
+typedef struct xhci_endpoint xhci_endpoint_t;
+
+typedef enum {
+	ISOCH_EMPTY,	/* Unused yet */
+	ISOCH_FILLED,	/* The data buffer is valid */
+	ISOCH_FED,	/* The data buffer is in possession of xHC */
+	ISOCH_COMPLETE,	/* The error code is valid */
+} xhci_isoch_transfer_state_t;
+
+typedef struct {
+	/** Buffer with data */
+	dma_buffer_t data;
+	/** Used buffer size */
+	size_t size;
+
+	/** Current state */
+	xhci_isoch_transfer_state_t state;
+
+	/** Microframe to which to schedule */
+	uint32_t mfindex;
+
+	/** Physical address of enqueued TRB */
+	uintptr_t interrupt_trb_phys;
+
+	/** Result of the transfer. Valid only if status == ISOCH_COMPLETE. */
+	int error;
+} xhci_isoch_transfer_t;
+
+typedef struct {
+	/** Protects common buffers. */
+	fibril_mutex_t guard;
+
+	/** Signals filled buffer. */
+	fibril_condvar_t avail;
+
+	/** Defers handing buffers to the HC. */
+	fibril_timer_t *feeding_timer;
+
+	/** The maximum size of an isochronous transfer and therefore the size of buffers */
+	size_t max_size;
+
+	/** The MFINDEX at which the last TRB was scheduled. */
+	uint32_t last_mfindex;
+
+	/** Isochronous scheduled transfers with respective buffers */
+	#define XHCI_ISOCH_BUFFER_COUNT 4
+	xhci_isoch_transfer_t transfers[XHCI_ISOCH_BUFFER_COUNT];
+
+	/**
+	 * Out: Next buffer that will be handed to HW.
+	 * In:  Invalid. Hidden inside HC.
+	 */
+	size_t hw_enqueue;
+
+	/**
+	 * Out: Next buffer that will be used for writing.
+	 * In:  Next buffer that will be enqueued to be written by the HC
+	 */
+	size_t enqueue;
+
+	/**
+	 * Out: First buffer that will be checked for completion
+	 * In:  Next buffer to be read from, when valid.
+	 */
+	size_t dequeue;
+} xhci_isoch_t;
+
+typedef struct usb_endpoint_descriptors usb_endpoint_descriptors_t;
+
+void isoch_init(xhci_endpoint_t *, const usb_endpoint_descriptors_t *);
+void isoch_fini(xhci_endpoint_t *);
+int isoch_alloc_transfers(xhci_endpoint_t *);
+
+int isoch_schedule_out(xhci_transfer_t *);
+int isoch_schedule_in(xhci_transfer_t *);
+int isoch_handle_transfer_event(xhci_hc_t *, xhci_endpoint_t *, xhci_trb_t *);
+
+#endif
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/transfers.c
===================================================================
--- uspace/drv/bus/usb/xhci/transfers.c	(revision 47930236198628981539314352b08f0a00aa2105)
+++ uspace/drv/bus/usb/xhci/transfers.c	(revision 708d8fcd39276a71e62f9b40a631eab1814b52d7)
@@ -236,233 +236,11 @@
 }
 
-static xhci_isoch_transfer_t* isoch_transfer_get_enqueue(xhci_endpoint_t *ep) {
-	if (((ep->isoch->enqueue + 1) % XHCI_ISOCH_BUFFER_COUNT) == ep->isoch->dequeue) {
-		/* None ready */
-		return NULL;
-	}
-	xhci_isoch_transfer_t *isoch_transfer = &ep->isoch->transfers[ep->isoch->enqueue];
-	ep->isoch->enqueue = (ep->isoch->enqueue + 1) % XHCI_ISOCH_BUFFER_COUNT;
-	return isoch_transfer;
-}
-
-static xhci_isoch_transfer_t* isoch_transfer_get_dequeue(xhci_endpoint_t *ep) {
-	xhci_isoch_transfer_t *isoch_transfer = &ep->isoch->transfers[ep->isoch->dequeue];
-	ep->isoch->dequeue = (ep->isoch->dequeue + 1) % XHCI_ISOCH_BUFFER_COUNT;
-	return isoch_transfer;
-}
-
-static int schedule_isochronous_trb(xhci_trb_ring_t *ring, xhci_endpoint_t *ep, xhci_trb_t *trb,
-	const size_t len, uintptr_t *trb_phys)
-{
-	TRB_CTRL_SET_XFER_LEN(*trb, len);
-	// FIXME: TD size 4.11.2.4 (there is no next TRB, so 0?)
-	TRB_CTRL_SET_TD_SIZE(*trb, 0);
-	TRB_CTRL_SET_IOC(*trb, 1);
-	TRB_CTRL_SET_TRB_TYPE(*trb, XHCI_TRB_TYPE_ISOCH);
-
-	// see 4.14.1 and 4.11.2.3 for the explanation, how to calculate those
-	size_t tdpc = len / 1024 + ((len % 1024) ? 1 : 0);
-	size_t tbc = tdpc / ep->max_burst;
-	if (!tdpc % ep->max_burst) --tbc;
-	size_t bsp = tdpc % ep->max_burst;
-	size_t tlbpc = (bsp ? bsp : ep->max_burst) - 1;
-
-	TRB_CTRL_SET_TBC(*trb, tbc);
-	TRB_CTRL_SET_TLBPC(*trb, tlbpc);
-
-	// FIXME: do we want this? 6.4.1.3, p 366 (also possibly frame id?)
-	TRB_CTRL_SET_SIA(*trb, 1);
-
-	return xhci_trb_ring_enqueue(ring, trb, trb_phys);
-}
-
-static int schedule_isochronous_out(xhci_hc_t* hc, xhci_transfer_t* transfer, xhci_endpoint_t *xhci_ep,
-	xhci_device_t *xhci_dev)
-{
-	xhci_trb_t trb;
-	xhci_trb_clean(&trb);
-
-	fibril_mutex_lock(&xhci_ep->isoch->guard);
-	xhci_isoch_transfer_t *isoch_transfer = isoch_transfer_get_enqueue(xhci_ep);
-	while (!isoch_transfer) {
-		fibril_condvar_wait(&xhci_ep->isoch->avail, &xhci_ep->isoch->guard);
-		isoch_transfer = isoch_transfer_get_enqueue(xhci_ep);
-	}
-
-	isoch_transfer->size = transfer->batch.buffer_size;
-	if (isoch_transfer->size > 0) {
-		memcpy(isoch_transfer->data.virt, transfer->batch.buffer, isoch_transfer->size);
-	}
-
-	trb.parameter = isoch_transfer->data.phys;
-
-	xhci_trb_ring_t *ring = get_ring(hc, transfer);
-	int err = schedule_isochronous_trb(ring, xhci_ep, &trb, isoch_transfer->size,
-		&isoch_transfer->interrupt_trb_phys);
-	if (err) {
-		fibril_mutex_unlock(&xhci_ep->isoch->guard);
-		return err;
-	}
-
-	/* If not yet started, start the isochronous endpoint transfers - after buffer count - 1 writes */
-	/* The -1 is there because of the enqueue != dequeue check. The buffer must have at least 2 transfers. */
-	if (((xhci_ep->isoch->enqueue + 1) % XHCI_ISOCH_BUFFER_COUNT) == xhci_ep->isoch->dequeue && !xhci_ep->isoch->started) {
-		const uint8_t slot_id = xhci_dev->slot_id;
-		const uint8_t target = xhci_endpoint_index(xhci_ep) + 1; /* EP Doorbells start at 1 */
-		err = hc_ring_doorbell(hc, slot_id, target);
-		xhci_ep->isoch->started = true;
-	}
-	fibril_mutex_unlock(&xhci_ep->isoch->guard);
-	if (err) {
-		return err;
-	}
-
-	/* Isochronous transfers don't handle errors, they skip them all. */
-	transfer->batch.error = EOK;
-	transfer->batch.transfered_size = transfer->batch.buffer_size;
-	usb_transfer_batch_finish(&transfer->batch);
-	return EOK;
-}
-
-static int schedule_isochronous_in_trbs(xhci_endpoint_t *xhci_ep, xhci_trb_ring_t *ring) {
-	xhci_trb_t trb;
-	xhci_isoch_transfer_t *isoch_transfer;
-	while ((isoch_transfer = isoch_transfer_get_enqueue(xhci_ep)) != NULL) {
-		xhci_trb_clean(&trb);
-		trb.parameter = isoch_transfer->data.phys;
-		isoch_transfer->size = xhci_ep->isoch->max_size;
-
-		int err = schedule_isochronous_trb(ring, xhci_ep, &trb, isoch_transfer->size,
-			&isoch_transfer->interrupt_trb_phys);
-		if (err)
-			return err;
-	}
-	return EOK;
-}
-
-static int schedule_isochronous_in(xhci_hc_t* hc, xhci_transfer_t* transfer, xhci_endpoint_t *xhci_ep,
-	xhci_device_t *xhci_dev)
-{
-	fibril_mutex_lock(&xhci_ep->isoch->guard);
-	/* If not yet started, start the isochronous endpoint transfers - before first read */
-	if (!xhci_ep->isoch->started) {
-		xhci_trb_ring_t *ring = get_ring(hc, transfer);
-		/* Fill the TRB ring. */
-		int err = schedule_isochronous_in_trbs(xhci_ep, ring);
-		if (err) {
-			fibril_mutex_unlock(&xhci_ep->isoch->guard);
-			return err;
-		}
-		/* Ring the doorbell to start it. */
-		const uint8_t slot_id = xhci_dev->slot_id;
-		const uint8_t target = xhci_endpoint_index(xhci_ep) + 1; /* EP Doorbells start at 1 */
-		err = hc_ring_doorbell(hc, slot_id, target);
-		if (err) {
-			fibril_mutex_unlock(&xhci_ep->isoch->guard);
-			return err;
-		}
-		xhci_ep->isoch->started = true;
-	}
-
-	xhci_isoch_transfer_t *isoch_transfer = isoch_transfer_get_enqueue(xhci_ep);
-	while(!isoch_transfer) {
-		fibril_condvar_wait(&xhci_ep->isoch->avail, &xhci_ep->isoch->guard);
-		isoch_transfer = isoch_transfer_get_enqueue(xhci_ep);
-	}
-
-	isoch_transfer->size = transfer->batch.buffer_size;
-	if (transfer->batch.buffer_size <= isoch_transfer->size) {
-		if (transfer->batch.buffer_size > 0) {
-			memcpy(transfer->batch.buffer, isoch_transfer->data.virt, transfer->batch.buffer_size);
-		}
-		if (transfer->batch.buffer_size < isoch_transfer->size) {
-			// FIXME: somehow notify that buffer was too small, probably batch error code
-		}
-		transfer->batch.transfered_size = transfer->batch.buffer_size;
-	}
-	else {
-		memcpy(transfer->batch.buffer, isoch_transfer->data.virt, isoch_transfer->size);
-		transfer->batch.transfered_size = isoch_transfer->size;
-	}
-
-	// Clear and requeue the transfer with new TRB
-	xhci_trb_t trb;
-	xhci_trb_clean(&trb);
-
-	trb.parameter = isoch_transfer->data.phys;
-	isoch_transfer->size = xhci_ep->isoch->max_size;
-
-	xhci_trb_ring_t *ring = get_ring(hc, transfer);
-	int err = schedule_isochronous_trb(ring, xhci_ep, &trb, isoch_transfer->size,
-		&isoch_transfer->interrupt_trb_phys);
-	fibril_mutex_unlock(&xhci_ep->isoch->guard);
-
-	if (err) {
-		return err;
-	}
-
-	/* Isochronous transfers don't handle errors, they skip them all. */
-	transfer->batch.error = EOK;
-	usb_transfer_batch_finish(&transfer->batch);
-	return EOK;
-}
-
-static int schedule_isochronous(xhci_hc_t* hc, xhci_transfer_t* transfer, xhci_endpoint_t *xhci_ep,
-	xhci_device_t *xhci_dev)
-{
-	if (transfer->batch.buffer_size > xhci_ep->isoch->max_size) {
-		usb_log_error("Cannot schedule an oversized isochronous transfer.");
-		return EINVAL;
-	}
-
-	if (xhci_ep->base.direction == USB_DIRECTION_OUT) {
-		return schedule_isochronous_out(hc, transfer, xhci_ep, xhci_dev);
-	}
-	else {
-		return schedule_isochronous_in(hc, transfer, xhci_ep, xhci_dev);
-	}
-}
-
-static int handle_isochronous_transfer_event(xhci_hc_t *hc, xhci_trb_t *trb, xhci_endpoint_t *ep) {
-	fibril_mutex_lock(&ep->isoch->guard);
-
-	int err = EOK;
-
-	const xhci_trb_completion_code_t completion_code = TRB_COMPLETION_CODE(*trb);
-	switch (completion_code) {
-		case XHCI_TRBC_RING_OVERRUN:
-		case XHCI_TRBC_RING_UNDERRUN:
-			/* Rings are unscheduled by xHC now */
-			ep->isoch->started = false;
-			/* For OUT, there was nothing to process */
-			/* For IN, the buffer has overfilled, we empty the buffers and readd TRBs */
-			ep->isoch->enqueue = ep->isoch->dequeue = 0;
-			err = EIO;
-			break;
-		case XHCI_TRBC_SHORT_PACKET:
-			usb_log_debug("Short transfer.");
-			/* fallthrough */
-		case XHCI_TRBC_SUCCESS:
-			break;
-		default:
-			usb_log_warning("Transfer not successfull: %u", completion_code);
-			err = EIO;
-	}
-
-	xhci_isoch_transfer_t *isoch_transfer = isoch_transfer_get_dequeue(ep);
-	if (isoch_transfer->interrupt_trb_phys != trb->parameter) {
-		usb_log_error("Non-matching trb to isochronous transfer, skipping.");
-		// FIXME: what to do? probably just kill the whole endpoint
-		err = ENOENT;
-	}
-
-	if (ep->base.direction == USB_DIRECTION_IN) {
-		// We may have received less data, that's fine
-		isoch_transfer->size -= TRB_TRANSFER_LENGTH(*trb);
-	}
-
-	fibril_condvar_signal(&ep->isoch->avail);
-	fibril_mutex_unlock(&ep->isoch->guard);
-	return err;
+static int schedule_isochronous(xhci_transfer_t* transfer)
+{
+	endpoint_t *ep = transfer->batch.ep;
+
+	return ep->direction == USB_DIRECTION_OUT
+		? isoch_schedule_out(transfer)
+		: isoch_schedule_in(transfer);
 }
 
@@ -486,4 +264,5 @@
 		return ENOENT;
 	}
+	// No need to add reference for endpoint, it is held by the transfer batch.
 
 	/* FIXME: This is racy. Do we care? */
@@ -491,5 +270,5 @@
 
 	if (ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS) {
-		return handle_isochronous_transfer_event(hc, trb, ep);
+		return isoch_handle_transfer_event(hc, ep, trb);
 	}
 
@@ -558,5 +337,5 @@
 	// Isochronous transfer needs to be handled differently
 	if (batch->ep->transfer_type == USB_TRANSFER_ISOCHRONOUS) {
-		return schedule_isochronous(hc, transfer, xhci_ep, xhci_dev);
+		return schedule_isochronous(transfer);
 	}
 
@@ -591,4 +370,5 @@
 	const uint8_t slot_id = xhci_dev->slot_id;
 	const uint8_t target = xhci_endpoint_index(xhci_ep) + 1; /* EP Doorbells start at 1 */
-	return hc_ring_doorbell(hc, slot_id, target);
-}
+	hc_ring_doorbell(hc, slot_id, target);
+	return EOK;
+}
Index: uspace/drv/bus/usb/xhci/transfers.h
===================================================================
--- uspace/drv/bus/usb/xhci/transfers.h	(revision 47930236198628981539314352b08f0a00aa2105)
+++ uspace/drv/bus/usb/xhci/transfers.h	(revision 708d8fcd39276a71e62f9b40a631eab1814b52d7)
@@ -55,13 +55,4 @@
 } xhci_transfer_t;
 
-typedef struct {
-	/* Used buffer size */
-	uint64_t size;
-	/* Buffer with data */
-	dma_buffer_t data;
-	/* Physical address of enqueued TRB */
-	uintptr_t interrupt_trb_phys;
-} xhci_isoch_transfer_t;
-
 usb_transfer_batch_t* xhci_transfer_create(endpoint_t *);
 int xhci_transfer_schedule(xhci_hc_t *, usb_transfer_batch_t *);
Index: uspace/lib/drv/include/usbhc_iface.h
===================================================================
--- uspace/lib/drv/include/usbhc_iface.h	(revision 47930236198628981539314352b08f0a00aa2105)
+++ uspace/lib/drv/include/usbhc_iface.h	(revision 708d8fcd39276a71e62f9b40a631eab1814b52d7)
@@ -64,5 +64,5 @@
  * Callers shall fill it with bare contents of respective descriptors (in usb endianity).
  */
-typedef struct {
+typedef struct usb_endpoint_descriptors {
 	struct {
 		uint8_t endpoint_address;
