Index: uspace/drv/bus/usb/ehci/ehci_rh.c
===================================================================
--- uspace/drv/bus/usb/ehci/ehci_rh.c	(revision 047fbc82a9a6f1939379d03723a2abc9fdf13295)
+++ uspace/drv/bus/usb/ehci/ehci_rh.c	(revision a94cbfa1045ccc51f38426d1ef32bc75b14c5d5d)
@@ -127,5 +127,5 @@
 
 	ehci_rh_hub_desc_init(instance, EHCI_RD(caps->hcsparams));
-	instance->unfinished_interrupt_transfer = NULL;
+	instance->status_change_endpoint = NULL;
 
 	return virthub_base_init(&instance->base, name, &ops, instance,
@@ -150,9 +150,22 @@
 		usb_log_debug("RH(%p): BATCH(%p) adding as unfinished",
 		    instance, batch);
-		/* This is safe because only status change interrupt transfers
-		 * return NAK. The assertion holds true because the batch
-		 * existence prevents communication with that ep */
-		assert(instance->unfinished_interrupt_transfer == NULL);
-		instance->unfinished_interrupt_transfer = batch;
+
+		/* Lock the HC guard */
+		fibril_mutex_lock(batch->ep->guard);
+		const int err = endpoint_activate_locked(batch->ep, batch);
+		if (err) {
+			fibril_mutex_unlock(batch->ep->guard);
+			return err;
+		}
+
+		/*
+		 * Asserting that the HC do not run two instances of the status
+		 * change endpoint - shall be true.
+		 */
+		assert(!instance->status_change_endpoint);
+
+		endpoint_add_ref(batch->ep);
+		instance->status_change_endpoint = batch->ep;
+		fibril_mutex_unlock(batch->ep->guard);
 	} else {
 		usb_log_debug("RH(%p): BATCH(%p) virtual request complete: %s",
@@ -172,10 +185,19 @@
 int ehci_rh_interrupt(ehci_rh_t *instance)
 {
-	//TODO atomic swap needed
-	usb_transfer_batch_t *batch = instance->unfinished_interrupt_transfer;
-	instance->unfinished_interrupt_transfer = NULL;
-	usb_log_debug2("RH(%p): Interrupt. Processing batch: %p",
-	    instance, batch);
+	endpoint_t *ep = instance->status_change_endpoint;
+	if (!ep)
+		return EOK;
+
+	fibril_mutex_lock(ep->guard);
+	usb_transfer_batch_t * const batch = ep->active_batch;
+	endpoint_deactivate_locked(ep);
+	instance->status_change_endpoint = NULL;
+	fibril_mutex_unlock(ep->guard);
+
+	endpoint_del_ref(ep);
+
 	if (batch) {
+		usb_log_debug2("RH(%p): Interrupt. Processing batch: %p",
+		    instance, batch);
 		batch->error = virthub_base_request(&instance->base, batch->target,
 		    batch->dir, (void*) batch->setup.buffer,
Index: uspace/drv/bus/usb/ehci/ehci_rh.h
===================================================================
--- uspace/drv/bus/usb/ehci/ehci_rh.h	(revision 047fbc82a9a6f1939379d03723a2abc9fdf13295)
+++ uspace/drv/bus/usb/ehci/ehci_rh.h	(revision a94cbfa1045ccc51f38426d1ef32bc75b14c5d5d)
@@ -61,8 +61,17 @@
 		uint8_t rempow[STATUS_BYTES(EHCI_MAX_PORTS) * 2];
 	} __attribute__((packed)) hub_descriptor;
-	/** interrupt transfer waiting for an actual interrupt to occur */
-	usb_transfer_batch_t *unfinished_interrupt_transfer;
 	bool reset_flag[EHCI_MAX_PORTS];
 	bool resume_flag[EHCI_MAX_PORTS];
+
+	/*
+	 * This is sort of hacky, but better than duplicating functionality.
+	 * We cannot simply store a pointer to a transfer in-progress, in order
+	 * to allow it to be aborted. We can however store a reference to the
+	 * Status Change Endpoint. Note that this is mixing two worlds together
+	 * - otherwise, the RH is "a device" and have no clue about HC, apart
+	 * from accessing its registers.
+	 */
+	endpoint_t *status_change_endpoint;
+
 } ehci_rh_t;
 
Index: uspace/drv/bus/usb/ohci/ohci_rh.c
===================================================================
--- uspace/drv/bus/usb/ohci/ohci_rh.c	(revision 047fbc82a9a6f1939379d03723a2abc9fdf13295)
+++ uspace/drv/bus/usb/ohci/ohci_rh.c	(revision a94cbfa1045ccc51f38426d1ef32bc75b14c5d5d)
@@ -162,5 +162,5 @@
 
 	ohci_rh_hub_desc_init(instance);
-	instance->unfinished_interrupt_transfer = NULL;
+	instance->status_change_endpoint = NULL;
 	return virthub_base_init(&instance->base, name, &ops, instance,
 	    NULL, &instance->hub_descriptor.header, HUB_STATUS_CHANGE_PIPE);
@@ -182,9 +182,21 @@
 	    batch->buffer, batch->buffer_size, &batch->transferred_size);
 	if (batch->error == ENAK) {
-		/* This is safe because only status change interrupt transfers
-		 * return NAK. The assertion holds true because the batch
-		 * existence prevents communication with that ep */
-		assert(instance->unfinished_interrupt_transfer == NULL);
-		instance->unfinished_interrupt_transfer = batch;
+		/* Lock the HC guard */
+		fibril_mutex_lock(batch->ep->guard);
+		const int err = endpoint_activate_locked(batch->ep, batch);
+		if (err) {
+			fibril_mutex_unlock(batch->ep->guard);
+			return err;
+		}
+
+		/*
+		 * Asserting that the HC do not run two instances of the status
+		 * change endpoint - shall be true.
+		 */
+		assert(!instance->status_change_endpoint);
+
+		endpoint_add_ref(batch->ep);
+		instance->status_change_endpoint = batch->ep;
+		fibril_mutex_unlock(batch->ep->guard);
 	} else {
 		usb_transfer_batch_finish(batch);
@@ -202,7 +214,16 @@
 int ohci_rh_interrupt(ohci_rh_t *instance)
 {
-	//TODO atomic swap needed
-	usb_transfer_batch_t *batch = instance->unfinished_interrupt_transfer;
-	instance->unfinished_interrupt_transfer = NULL;
+	endpoint_t *ep = instance->status_change_endpoint;
+	if (!ep)
+		return EOK;
+
+	fibril_mutex_lock(ep->guard);
+	usb_transfer_batch_t * const batch = ep->active_batch;
+	endpoint_deactivate_locked(ep);
+	instance->status_change_endpoint = NULL;
+	fibril_mutex_unlock(ep->guard);
+
+	endpoint_del_ref(ep);
+
 	if (batch) {
 		batch->error = virthub_base_request(&instance->base, batch->target,
Index: uspace/drv/bus/usb/ohci/ohci_rh.h
===================================================================
--- uspace/drv/bus/usb/ohci/ohci_rh.h	(revision 047fbc82a9a6f1939379d03723a2abc9fdf13295)
+++ uspace/drv/bus/usb/ohci/ohci_rh.h	(revision a94cbfa1045ccc51f38426d1ef32bc75b14c5d5d)
@@ -61,6 +61,6 @@
 		uint8_t rempow[STATUS_BYTES(OHCI_MAX_PORTS) * 2];
 	} __attribute__((packed)) hub_descriptor;
-	/** interrupt transfer waiting for an actual interrupt to occur */
-	usb_transfer_batch_t *unfinished_interrupt_transfer;
+	/** A hacky way to emulate interrupts over polling. See ehci_rh. */
+	endpoint_t *status_change_endpoint;
 } ohci_rh_t;
 
