Index: uspace/lib/usbhost/src/bus.c
===================================================================
--- uspace/lib/usbhost/src/bus.c	(revision 837d53df7e7cca6ce3ffd079108c600627b9c780)
+++ uspace/lib/usbhost/src/bus.c	(revision 32fb6bcec95f3a7fbb1b2fdb6528f0e04726a6b6)
@@ -46,13 +46,11 @@
  * Initializes the bus structure.
  */
-void bus_init(bus_t *bus, hcd_t *hcd, size_t device_size)
-{
-	assert(bus);
-	assert(hcd);
+void bus_init(bus_t *bus, size_t device_size)
+{
+	assert(bus);
 	assert(device_size >= sizeof(device_t));
 	memset(bus, 0, sizeof(bus_t));
 
 	fibril_mutex_initialize(&bus->guard);
-	bus->hcd = hcd;
 	bus->device_size = device_size;
 }
@@ -61,5 +59,4 @@
 {
 	assert(bus);
-	assert(bus->hcd);
 
 	memset(dev, 0, sizeof(*dev));
@@ -258,4 +255,88 @@
 }
 
+/** Prepare generic usb_transfer_batch and schedule it.
+ * @param device Device for which to send the batch
+ * @param target address and endpoint number.
+ * @param setup_data Data to use in setup stage (Control communication type)
+ * @param in Callback for device to host communication.
+ * @param out Callback for host to device communication.
+ * @param arg Callback parameter.
+ * @param name Communication identifier (for nicer output).
+ * @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);
+
+	/* Temporary reference */
+	endpoint_t *ep = bus_find_endpoint(device, target, direction);
+	if (ep == NULL) {
+		usb_log_error("Endpoint(%d:%d) not registered for %s.\n",
+		    device->address, target.endpoint, name);
+		return ENOENT;
+	}
+
+	assert(ep->device == device);
+
+	const int err = endpoint_send_batch(ep, target, direction, data, size, setup_data,
+	    on_complete, arg, name);
+
+	/* Temporary reference */
+	endpoint_del_ref(ep);
+
+	return err;
+}
+
+typedef struct {
+	fibril_mutex_t done_mtx;
+	fibril_condvar_t done_cv;
+	unsigned done;
+
+	size_t transfered_size;
+	int error;
+} sync_data_t;
+
+static int sync_transfer_complete(void *arg, int error, size_t transfered_size)
+{
+	sync_data_t *d = arg;
+	assert(d);
+	d->transfered_size = transfered_size;
+	d->error = error;
+	fibril_mutex_lock(&d->done_mtx);
+	d->done = 1;
+	fibril_condvar_broadcast(&d->done_cv);
+	fibril_mutex_unlock(&d->done_mtx);
+	return EOK;
+}
+
+ssize_t bus_device_send_batch_sync(device_t *device, usb_target_t target,
+    usb_direction_t direction, char *data, size_t size, uint64_t setup_data,
+    const char *name)
+{
+	sync_data_t sd = { .done = 0 };
+	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;
+
+	fibril_mutex_lock(&sd.done_mtx);
+	while (!sd.done) {
+		fibril_condvar_wait_timeout(&sd.done_cv, &sd.done_mtx, 3000000);
+		if (!sd.done)
+			usb_log_debug2("Still waiting...");
+	}
+	fibril_mutex_unlock(&sd.done_mtx);
+
+	return (sd.error == EOK)
+		? (ssize_t) sd.transfered_size
+		: (ssize_t) sd.error;
+}
+
 /**
  * @}
Index: uspace/lib/usbhost/src/ddf_helpers.c
===================================================================
--- uspace/lib/usbhost/src/ddf_helpers.c	(revision 837d53df7e7cca6ce3ffd079108c600627b9c780)
+++ uspace/lib/usbhost/src/ddf_helpers.c	(revision 32fb6bcec95f3a7fbb1b2fdb6528f0e04726a6b6)
@@ -53,26 +53,6 @@
 #include "ddf_helpers.h"
 
-typedef struct hc_dev {
-	ddf_fun_t *ctl_fun;
-	hcd_t hcd;
-} hc_dev_t;
-
-static hc_dev_t *dev_to_hc_dev(ddf_dev_t *dev)
-{
-	return ddf_dev_data_get(dev);
-}
-
-hcd_t *dev_to_hcd(ddf_dev_t *dev)
-{
-	hc_dev_t *hc_dev = dev_to_hc_dev(dev);
-	if (!hc_dev) {
-		usb_log_error("Invalid HCD device.\n");
-		return NULL;
-	}
-	return &hc_dev->hcd;
-}
-
-
-static int hcd_ddf_new_device(hcd_t *hcd, ddf_dev_t *hc, device_t *hub_dev, unsigned port);
+
+static int hcd_ddf_new_device(hc_device_t *hcd, ddf_dev_t *hc, device_t *hub_dev, unsigned port);
 static int hcd_ddf_remove_device(ddf_dev_t *device, device_t *hub, unsigned port);
 
@@ -89,5 +69,5 @@
 {
 	assert(fun);
-	hcd_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
+	hc_device_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
 	device_t *dev = ddf_fun_data_get(fun);
 	assert(hcd);
@@ -113,5 +93,5 @@
 {
 	assert(fun);
-	hcd_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
+	hc_device_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
 	device_t *dev = ddf_fun_data_get(fun);
 	assert(hcd);
@@ -138,5 +118,5 @@
 {
 	assert(fun);
-	hcd_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
+	hc_device_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
 	device_t *dev = ddf_fun_data_get(fun);
 	assert(hcd);
@@ -152,5 +132,5 @@
 {
 	assert(fun);
-	hcd_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
+	hc_device_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
 	device_t *dev = ddf_fun_data_get(fun);
 	assert(hcd);
@@ -167,5 +147,5 @@
 	ddf_dev_t *hc = ddf_fun_get_dev(fun);
 	assert(hc);
-	hcd_t *hcd = dev_to_hcd(hc);
+	hc_device_t *hcd = dev_to_hcd(hc);
 	assert(hcd);
 	device_t *hub = ddf_fun_data_get(fun);
@@ -218,5 +198,4 @@
 {
 	assert(fun);
-	hcd_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
 	device_t *dev = ddf_fun_data_get(fun);
 	assert(dev);
@@ -224,5 +203,5 @@
 	target.address = dev->address;
 
-	return hcd_send_batch(hcd, dev, target, USB_DIRECTION_IN,
+	return bus_device_send_batch(dev, target, USB_DIRECTION_IN,
 	    data, size, setup_data,
 	    callback, arg, "READ");
@@ -244,5 +223,4 @@
 {
 	assert(fun);
-	hcd_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
 	device_t *dev = ddf_fun_data_get(fun);
 	assert(dev);
@@ -250,5 +228,5 @@
 	target.address = dev->address;
 
-	return hcd_send_batch(hcd, dev, target, USB_DIRECTION_OUT,
+	return bus_device_send_batch(dev, target, USB_DIRECTION_OUT,
 	    (char *) data, size, setup_data,
 	    callback, arg, "WRITE");
@@ -337,10 +315,7 @@
 	assert(device);
 
-	hcd_t *hcd = dev_to_hcd(device);
+	hc_device_t *hcd = dev_to_hcd(device);
 	assert(hcd);
 	assert(hcd->bus);
-
-	hc_dev_t *hc_dev = dev_to_hc_dev(device);
-	assert(hc_dev);
 
 	fibril_mutex_lock(&hub->guard);
@@ -374,8 +349,8 @@
 }
 
-device_t *hcd_ddf_device_create(ddf_dev_t *hc, bus_t *bus)
+device_t *hcd_ddf_fun_create(hc_device_t *hc)
 {
 	/* Create DDF function for the new device */
-	ddf_fun_t *fun = ddf_fun_create(hc, fun_inner, NULL);
+	ddf_fun_t *fun = ddf_fun_create(hc->ddf_dev, fun_inner, NULL);
 	if (!fun)
 		return NULL;
@@ -384,5 +359,5 @@
 
 	/* Create USB device node for the new device */
-	device_t *dev = ddf_fun_data_alloc(fun, bus->device_size);
+	device_t *dev = ddf_fun_data_alloc(fun, hc->bus->device_size);
 	if (!dev) {
 		ddf_fun_destroy(fun);
@@ -390,10 +365,10 @@
 	}
 
-	bus_device_init(dev, bus);
+	bus_device_init(dev, hc->bus);
 	dev->fun = fun;
 	return dev;
 }
 
-void hcd_ddf_device_destroy(device_t *dev)
+void hcd_ddf_fun_destroy(device_t *dev)
 {
 	assert(dev);
@@ -402,5 +377,5 @@
 }
 
-int hcd_ddf_device_explore(device_t *device)
+int hcd_device_explore(device_t *device)
 {
 	int err;
@@ -421,5 +396,5 @@
 	usb_log_debug("Device(%d): Requesting full device descriptor.",
 	    device->address);
-	ssize_t got = hcd_send_batch_sync(device->bus->hcd, device, control_ep, USB_DIRECTION_IN,
+	ssize_t got = bus_device_send_batch_sync(device, control_ep, USB_DIRECTION_IN,
 	    (char *) &desc, sizeof(desc), *(uint64_t *)&get_device_desc,
 	    "read device descriptor");
@@ -447,33 +422,5 @@
 }
 
-int hcd_ddf_device_online(ddf_fun_t *fun)
-{
-	assert(fun);
-
-	hcd_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
-	device_t *dev = ddf_fun_data_get(fun);
-	assert(dev);
-	assert(hcd->bus);
-
-	usb_log_info("Device(%d): Requested to be brought online.", dev->address);
-
-	return bus_device_online(dev);
-}
-
-int hcd_ddf_device_offline(ddf_fun_t *fun)
-{
-	assert(fun);
-
-	hcd_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
-	device_t *dev = ddf_fun_data_get(fun);
-	assert(dev);
-	assert(hcd->bus);
-
-	usb_log_info("Device(%d): Requested to be taken offline.", dev->address);
-
-	return bus_device_offline(dev);
-}
-
-static int hcd_ddf_new_device(hcd_t *hcd, ddf_dev_t *hc, device_t *hub, unsigned port)
+static int hcd_ddf_new_device(hc_device_t *hcd, ddf_dev_t *hc, device_t *hub, unsigned port)
 {
 	int err;
@@ -483,5 +430,5 @@
 	assert(hc);
 
-	device_t *dev = hcd_ddf_device_create(hc, hcd->bus);
+	device_t *dev = hcd_ddf_fun_create(hcd);
 	if (!dev) {
 		usb_log_error("Failed to create USB device function.");
@@ -516,5 +463,5 @@
 
 err_usb_dev:
-	hcd_ddf_device_destroy(dev);
+	hcd_ddf_fun_destroy(dev);
 	return err;
 }
@@ -525,11 +472,9 @@
  * @return Error code
  */
-int hcd_setup_virtual_root_hub(hcd_t *hcd, ddf_dev_t *hc)
+int hcd_setup_virtual_root_hub(hc_device_t *hcd)
 {
 	int err;
 
-	assert(hc);
-	assert(hcd);
-	assert(hcd->bus);
+	assert(hcd);
 
 	if ((err = bus_reserve_default_address(hcd->bus, USB_SPEED_MAX))) {
@@ -538,5 +483,5 @@
 	}
 
-	device_t *dev = hcd_ddf_device_create(hc, hcd->bus);
+	device_t *dev = hcd_ddf_fun_create(hcd);
 	if (!dev) {
 		usb_log_error("Failed to create function for the root hub.");
@@ -561,5 +506,5 @@
 
 err_usb_dev:
-	hcd_ddf_device_destroy(dev);
+	hcd_ddf_fun_destroy(dev);
 err_default_address:
 	bus_release_default_address(hcd->bus);
@@ -577,14 +522,14 @@
  * This function does all the ddf work for hc driver.
  */
-int hcd_ddf_setup_hc(ddf_dev_t *device)
+int hcd_ddf_setup_hc(ddf_dev_t *device, size_t size)
 {
 	assert(device);
 
-	hc_dev_t *instance = ddf_dev_data_alloc(device, sizeof(hc_dev_t));
+	hc_device_t *instance = ddf_dev_data_alloc(device, size);
 	if (instance == NULL) {
 		usb_log_error("Failed to allocate HCD ddf structure.\n");
 		return ENOMEM;
 	}
-	hcd_init(&instance->hcd);
+	instance->ddf_dev = device;
 
 	int ret = ENOMEM;
@@ -618,15 +563,10 @@
 }
 
-void hcd_ddf_clean_hc(ddf_dev_t *device)
-{
-	assert(device);
-	hc_dev_t *hc = dev_to_hc_dev(device);
-	assert(hc);
-	const int ret = ddf_fun_unbind(hc->ctl_fun);
-	if (ret == EOK)
-		ddf_fun_destroy(hc->ctl_fun);
-}
-
-//TODO: Cache parent session in HCD
+void hcd_ddf_clean_hc(hc_device_t *hcd)
+{
+	if (ddf_fun_unbind(hcd->ctl_fun) == EOK)
+		ddf_fun_destroy(hcd->ctl_fun);
+}
+
 /** Call the parent driver with a request to enable interrupt
  *
@@ -635,7 +575,7 @@
  * @return Error code.
  */
-int hcd_ddf_enable_interrupt(ddf_dev_t *device, int inum)
-{
-	async_sess_t *parent_sess = ddf_dev_parent_sess_get(device);
+int hcd_ddf_enable_interrupt(hc_device_t *hcd, int inum)
+{
+	async_sess_t *parent_sess = ddf_dev_parent_sess_get(hcd->ddf_dev);
 	if (parent_sess == NULL)
 		return EIO;
@@ -644,8 +584,7 @@
 }
 
-//TODO: Cache parent session in HCD
-int hcd_ddf_get_registers(ddf_dev_t *device, hw_res_list_parsed_t *hw_res)
-{
-	async_sess_t *parent_sess = ddf_dev_parent_sess_get(device);
+int hcd_ddf_get_registers(hc_device_t *hcd, hw_res_list_parsed_t *hw_res)
+{
+	async_sess_t *parent_sess = ddf_dev_parent_sess_get(hcd->ddf_dev);
 	if (parent_sess == NULL)
 		return EIO;
@@ -658,229 +597,4 @@
 }
 
-// TODO: move this someplace else
-static inline void irq_code_clean(irq_code_t *code)
-{
-	if (code) {
-		free(code->ranges);
-		free(code->cmds);
-		code->ranges = NULL;
-		code->cmds = NULL;
-		code->rangecount = 0;
-		code->cmdcount = 0;
-	}
-}
-
-/** Register interrupt handler
- *
- * @param[in] device Host controller DDF device
- * @param[in] regs Register range
- * @param[in] irq Interrupt number
- * @paran[in] handler Interrupt handler
- * @param[in] gen_irq_code IRQ code generator.
- *
- * @return IRQ capability handle on success.
- * @return Negative error code.
- */
-int hcd_ddf_setup_interrupts(ddf_dev_t *device,
-    const hw_res_list_parsed_t *hw_res,
-    interrupt_handler_t handler,
-    irq_code_gen_t gen_irq_code)
-{
-	assert(device);
-
-	hcd_t *hcd = dev_to_hcd(device);
-
-	if (!handler || !gen_irq_code)
-		return ENOTSUP;
-
-	irq_code_t irq_code = {0};
-
-	const int irq = gen_irq_code(&irq_code, hcd, hw_res);
-	if (irq < 0) {
-		usb_log_error("Failed to generate IRQ code: %s.\n",
-		    str_error(irq));
-		return irq;
-	}
-
-	/* Register handler to avoid interrupt lockup */
-	const int irq_cap = register_interrupt_handler(device, irq, handler,
-	    &irq_code);
-	irq_code_clean(&irq_code);
-	if (irq_cap < 0) {
-		usb_log_error("Failed to register interrupt handler: %s.\n",
-		    str_error(irq_cap));
-		return irq_cap;
-	}
-
-	/* Enable interrupts */
-	int ret = hcd_ddf_enable_interrupt(device, irq);
-	if (ret != EOK) {
-		usb_log_error("Failed to enable interrupts: %s.\n",
-		    str_error(ret));
-		unregister_interrupt_handler(device, irq_cap);
-		return ret;
-	}
-	return irq_cap;
-}
-
-/** IRQ handling callback, forward status from call to diver structure.
- *
- * @param[in] dev DDF instance of the device to use.
- * @param[in] iid (Unused).
- * @param[in] call Pointer to the call from kernel.
- */
-void ddf_hcd_gen_irq_handler(ipc_callid_t iid, ipc_call_t *call, ddf_dev_t *dev)
-{
-	assert(dev);
-	hcd_t *hcd = dev_to_hcd(dev);
-	if (!hcd || !hcd->ops.irq_hook) {
-		usb_log_error("Interrupt on not yet initialized device.\n");
-		return;
-	}
-	const uint32_t status = IPC_GET_ARG1(*call);
-	hcd->ops.irq_hook(hcd, status);
-}
-
-static int interrupt_polling(void *arg)
-{
-	hcd_t *hcd = arg;
-	assert(hcd);
-	if (!hcd->ops.status_hook || !hcd->ops.irq_hook)
-		return ENOTSUP;
-	uint32_t status = 0;
-	while (hcd->ops.status_hook(hcd, &status) == EOK) {
-		hcd->ops.irq_hook(hcd, status);
-		status = 0;
-		/* We should wait 1 frame - 1ms here, but this polling is a
-		 * lame crutch anyway so don't hog the system. 10ms is still
-		 * good enough for emergency mode */
-		async_usleep(10000);
-	}
-	return EOK;
-}
-
-/** Initialize hc and rh DDF structures and their respective drivers.
- *
- * @param device DDF instance of the device to use
- * @param speed Maximum supported speed
- * @param bw Available bandwidth (arbitrary units)
- * @param bw_count Bandwidth computing function
- * @param irq_handler IRQ handling function
- * @param gen_irq_code Function to generate IRQ pseudocode
- *                     (it needs to return used irq number)
- * @param driver_init Function to initialize HC driver
- * @param driver_fini Function to cleanup HC driver
- * @return Error code
- *
- * This function does all the preparatory work for hc and rh drivers:
- *  - gets device's hw resources
- *  - attempts to enable interrupts
- *  - registers interrupt handler
- *  - calls driver specific initialization
- *  - registers root hub
- */
-int hcd_ddf_add_hc(ddf_dev_t *device, const ddf_hc_driver_t *driver)
-{
-	assert(driver);
-
-	int ret = EOK;
-
-	hw_res_list_parsed_t hw_res;
-	ret = hcd_ddf_get_registers(device, &hw_res);
-	if (ret != EOK) {
-		usb_log_error("Failed to get register memory addresses "
-		    "for `%s': %s.\n", ddf_dev_get_name(device),
-		    str_error(ret));
-		return ret;
-	}
-
-	ret = hcd_ddf_setup_hc(device);
-	if (ret != EOK) {
-		usb_log_error("Failed to setup generic HCD.\n");
-		goto err_hw_res;
-	}
-
-	hcd_t *hcd = dev_to_hcd(device);
-
-	if (driver->init)
-		ret = driver->init(hcd, &hw_res, device);
-	if (ret != EOK) {
-		usb_log_error("Failed to init HCD.\n");
-		goto err_hcd;
-	}
-
-	/* Setup interrupts  */
-	interrupt_handler_t *irq_handler =
-	    driver->irq_handler ? driver->irq_handler : ddf_hcd_gen_irq_handler;
-	const int irq_cap = hcd_ddf_setup_interrupts(device, &hw_res,
-	    irq_handler, driver->irq_code_gen);
-	bool irqs_enabled = !(irq_cap < 0);
-	if (irqs_enabled) {
-		usb_log_debug("Hw interrupts enabled.\n");
-	}
-
-	/* Claim the device from BIOS */
-	if (driver->claim)
-		ret = driver->claim(hcd, device);
-	if (ret != EOK) {
-		usb_log_error("Failed to claim `%s' for driver `%s': %s",
-		    ddf_dev_get_name(device), driver->name, str_error(ret));
-		goto err_irq;
-	}
-
-	/* Start hw driver */
-	if (driver->start)
-		ret = driver->start(hcd, irqs_enabled);
-	if (ret != EOK) {
-		usb_log_error("Failed to start HCD: %s.\n", str_error(ret));
-		goto err_irq;
-	}
-
-	/* Need working irq replacement to setup root hub */
-	if (!irqs_enabled && hcd->ops.status_hook) {
-		hcd->polling_fibril = fibril_create(interrupt_polling, hcd);
-		if (hcd->polling_fibril == 0) {
-			usb_log_error("Failed to create polling fibril\n");
-			ret = ENOMEM;
-			goto err_started;
-		}
-		fibril_add_ready(hcd->polling_fibril);
-		usb_log_warning("Failed to enable interrupts: %s."
-		    " Falling back to polling.\n", str_error(irq_cap));
-	}
-
-	/*
-	 * Creating root hub registers a new USB device so HC
-	 * needs to be ready at this time.
-	 */
-	if (driver->setup_root_hub)
-		ret = driver->setup_root_hub(hcd, device);
-	if (ret != EOK) {
-		usb_log_error("Failed to setup HC root hub: %s.\n",
-		    str_error(ret));
-		goto err_polling;
-	}
-
-	usb_log_info("Controlling new `%s' device `%s'.\n",
-	    driver->name, ddf_dev_get_name(device));
-	return EOK;
-
-err_polling:
-	// TODO: Stop the polling fibril (refactor the interrupt_polling func)
-	//
-err_started:
-	if (driver->stop)
-		driver->stop(hcd);
-err_irq:
-	unregister_interrupt_handler(device, irq_cap);
-	if (driver->fini)
-		driver->fini(hcd);
-err_hcd:
-	hcd_ddf_clean_hc(device);
-err_hw_res:
-	hw_res_list_parsed_clean(&hw_res);
-	return ret;
-}
-
 /**
  * @}
Index: uspace/lib/usbhost/src/endpoint.c
===================================================================
--- uspace/lib/usbhost/src/endpoint.c	(revision 837d53df7e7cca6ce3ffd079108c600627b9c780)
+++ uspace/lib/usbhost/src/endpoint.c	(revision 32fb6bcec95f3a7fbb1b2fdb6528f0e04726a6b6)
@@ -39,4 +39,7 @@
 #include <mem.h>
 #include <stdlib.h>
+#include <str_error.h>
+#include <usb/debug.h>
+#include <usb/host/hcd.h>
 
 #include "usb_transfer_batch.h"
@@ -187,4 +190,65 @@
 }
 
+/** Prepare generic usb_transfer_batch and schedule it.
+ * @param ep Endpoint for which the batch shall be created.
+ * @param target address and endpoint number.
+ * @param setup_data Data to use in setup stage (Control communication type)
+ * @param in Callback for device to host communication.
+ * @param out Callback for host to device communication.
+ * @param arg Callback parameter.
+ * @param name Communication identifier (for nicer output).
+ * @return Error code.
+ */
+int 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)
+{
+	usb_log_debug2("%s %d:%d %zu(%zu).\n",
+	    name, target.address, target.endpoint, size, ep->max_packet_size);
+
+	bus_t *bus = endpoint_get_bus(ep);
+	const bus_ops_t *ops = BUS_OPS_LOOKUP(bus->ops, batch_schedule);
+	if (!ops) {
+		usb_log_error("HCD does not implement scheduler.\n");
+		return ENOTSUP;
+	}
+
+	const size_t bw = endpoint_count_bw(ep, size);
+	/* Check if we have enough bandwidth reserved */
+	if (ep->bandwidth < bw) {
+		usb_log_error("Endpoint(%d:%d) %s needs %zu bw "
+		    "but only %zu is reserved.\n",
+		    ep->device->address, ep->endpoint, name, bw, ep->bandwidth);
+		return ENOSPC;
+	}
+
+	usb_transfer_batch_t *batch = usb_transfer_batch_create(ep);
+	if (!batch) {
+		usb_log_error("Failed to create transfer batch.\n");
+		return ENOMEM;
+	}
+
+	batch->target = target;
+	batch->buffer = data;
+	batch->buffer_size = size;
+	batch->setup.packed = setup_data;
+	batch->dir = direction;
+	batch->on_complete = on_complete;
+	batch->on_complete_data = arg;
+
+	/* Check for commands that reset toggle bit */
+	if (ep->transfer_type == USB_TRANSFER_CONTROL)
+		batch->toggle_reset_mode
+			= hcd_get_request_toggle_reset_mode(&batch->setup.packet);
+
+	const int ret = ops->batch_schedule(batch);
+	if (ret != EOK) {
+		usb_log_warning("Batch %p failed to schedule: %s", batch, str_error(ret));
+		usb_transfer_batch_destroy(batch);
+	}
+
+	return ret;
+}
+
 /**
  * @}
Index: uspace/lib/usbhost/src/hcd.c
===================================================================
--- uspace/lib/usbhost/src/hcd.c	(revision 837d53df7e7cca6ce3ffd079108c600627b9c780)
+++ uspace/lib/usbhost/src/hcd.c	(revision 32fb6bcec95f3a7fbb1b2fdb6528f0e04726a6b6)
@@ -36,4 +36,5 @@
 #include <assert.h>
 #include <async.h>
+#include <ddf/interrupt.h>
 #include <errno.h>
 #include <macros.h>
@@ -45,4 +46,5 @@
 
 #include "bus.h"
+#include "ddf_helpers.h"
 #include "endpoint.h"
 #include "usb_transfer_batch.h"
@@ -50,17 +52,307 @@
 #include "hcd.h"
 
-
-/** Initialize hcd_t structure.
- * Initializes device and endpoint managers. Sets data and hook pointer to NULL.
- *
- * @param hcd hcd_t structure to initialize, non-null.
- * @param max_speed Maximum supported USB speed (full, high).
- * @param bandwidth Available bandwidth, passed to endpoint manager.
- * @param bw_count Bandwidth compute function, passed to endpoint manager.
- */
-void hcd_init(hcd_t *hcd) {
+int hc_dev_add(ddf_dev_t *);
+int hc_dev_remove(ddf_dev_t *);
+int hc_dev_gone(ddf_dev_t *);
+int hc_fun_online(ddf_fun_t *);
+int hc_fun_offline(ddf_fun_t *);
+
+static driver_ops_t hc_driver_ops = {
+	.dev_add = hc_dev_add,
+	.dev_remove = hc_dev_remove,
+	.dev_gone = hc_dev_gone,
+	.fun_online = hc_fun_offline,
+	.fun_offline = hc_fun_offline,
+};
+
+static const hc_driver_t *hc_driver;
+
+int hc_driver_main(const hc_driver_t *driver)
+{
+	driver_t ddf_driver = {
+		.name = driver->name,
+		.driver_ops = &hc_driver_ops,
+	};
+
+	/* Remember ops to call. */
+	hc_driver = driver;
+
+	return ddf_driver_main(&ddf_driver);
+}
+
+/** IRQ handling callback, forward status from call to diver structure.
+ *
+ * @param[in] dev DDF instance of the device to use.
+ * @param[in] iid (Unused).
+ * @param[in] call Pointer to the call from kernel.
+ */
+static void irq_handler(ipc_callid_t iid, ipc_call_t *call, ddf_dev_t *dev)
+{
+	assert(dev);
+	hc_device_t *hcd = dev_to_hcd(dev);
+
+	const bus_ops_t *ops = BUS_OPS_LOOKUP(hcd->bus->ops, interrupt);
+	assert(ops);
+
+	const uint32_t status = IPC_GET_ARG1(*call);
+	ops->interrupt(hcd->bus, status);
+}
+
+/** Worker for the HW interrupt replacement fibril.
+ */
+static int interrupt_polling(void *arg)
+{
+	hc_device_t *hcd = arg;
 	assert(hcd);
-
-	hcd_set_implementation(hcd, NULL, NULL, NULL);
+	bus_t *bus = hcd->bus;
+
+	const bus_ops_t *interrupt_ops = BUS_OPS_LOOKUP(bus->ops, interrupt);
+	const bus_ops_t *status_ops = BUS_OPS_LOOKUP(bus->ops, status);
+	if (!interrupt_ops || !status_ops)
+		return ENOTSUP;
+
+	uint32_t status = 0;
+	while (status_ops->status(bus, &status) == EOK) {
+		interrupt_ops->interrupt(bus, status);
+		status = 0;
+		/* We should wait 1 frame - 1ms here, but this polling is a
+		 * lame crutch anyway so don't hog the system. 10ms is still
+		 * good enough for emergency mode */
+		async_usleep(10000);
+	}
+	return EOK;
+}
+
+static inline void irq_code_clean(irq_code_t *code)
+{
+	if (code) {
+		free(code->ranges);
+		free(code->cmds);
+		code->ranges = NULL;
+		code->cmds = NULL;
+		code->rangecount = 0;
+		code->cmdcount = 0;
+	}
+}
+
+/** Register interrupt handler
+ *
+ * @param[in] device Host controller DDF device
+ * @param[in] regs Register range
+ * @param[in] irq Interrupt number
+ * @paran[in] handler Interrupt handler
+ * @param[in] gen_irq_code IRQ code generator.
+ *
+ * @return IRQ capability handle on success.
+ * @return Negative error code.
+ */
+static int hcd_ddf_setup_interrupts(hc_device_t *hcd, const hw_res_list_parsed_t *hw_res)
+{
+	assert(hcd);
+	irq_code_t irq_code = {0};
+
+	if (!hc_driver->irq_code_gen)
+		return ENOTSUP;
+
+	const int irq = hc_driver->irq_code_gen(&irq_code, hcd, hw_res);
+	if (irq < 0) {
+		usb_log_error("Failed to generate IRQ code: %s.\n",
+		    str_error(irq));
+		return irq;
+	}
+
+	/* Register handler to avoid interrupt lockup */
+	const int irq_cap = register_interrupt_handler(hcd->ddf_dev, irq, irq_handler, &irq_code);
+	irq_code_clean(&irq_code);
+	if (irq_cap < 0) {
+		usb_log_error("Failed to register interrupt handler: %s.\n",
+		    str_error(irq_cap));
+		return irq_cap;
+	}
+
+	/* Enable interrupts */
+	int ret = hcd_ddf_enable_interrupt(hcd, irq);
+	if (ret != EOK) {
+		usb_log_error("Failed to enable interrupts: %s.\n",
+		    str_error(ret));
+		unregister_interrupt_handler(hcd->ddf_dev, irq_cap);
+		return ret;
+	}
+	return irq_cap;
+}
+
+/** Initialize HC in memory of the driver.
+ *
+ * @param device DDF instance of the device to use
+ * @return Error code
+ *
+ * This function does all the preparatory work for hc and rh drivers:
+ *  - gets device's hw resources
+ *  - attempts to enable interrupts
+ *  - registers interrupt handler
+ *  - calls driver specific initialization
+ *  - registers root hub
+ */
+int hc_dev_add(ddf_dev_t *device)
+{
+	int ret = EOK;
+	assert(device);
+
+	if (!hc_driver->hc_add) {
+		usb_log_error("Driver '%s' does not support adding devices.", hc_driver->name);
+		return ENOTSUP;
+	}
+
+	ret = hcd_ddf_setup_hc(device, hc_driver->hc_device_size);
+	if (ret != EOK) {
+		usb_log_error("Failed to setup HC device.\n");
+		return ret;
+	}
+
+	hc_device_t *hcd = dev_to_hcd(device);
+
+	hw_res_list_parsed_t hw_res;
+	ret = hcd_ddf_get_registers(hcd, &hw_res);
+	if (ret != EOK) {
+		usb_log_error("Failed to get register memory addresses "
+		    "for `%s': %s.\n", ddf_dev_get_name(device),
+		    str_error(ret));
+		goto err_hcd;
+	}
+
+	ret = hc_driver->hc_add(hcd, &hw_res);
+	if (ret != EOK) {
+		usb_log_error("Failed to init HCD.\n");
+		goto err_hw_res;
+	}
+
+	assert(hcd->bus);
+
+	/* Setup interrupts  */
+	hcd->irq_cap = hcd_ddf_setup_interrupts(hcd, &hw_res);
+	if (hcd->irq_cap >= 0) {
+		usb_log_debug("Hw interrupts enabled.\n");
+	}
+
+	/* Claim the device from BIOS */
+	if (hc_driver->claim)
+		ret = hc_driver->claim(hcd);
+	if (ret != EOK) {
+		usb_log_error("Failed to claim `%s' for `%s': %s",
+		    ddf_dev_get_name(device), hc_driver->name, str_error(ret));
+		goto err_irq;
+	}
+
+	/* Start hw */
+	if (hc_driver->start)
+		ret = hc_driver->start(hcd);
+	if (ret != EOK) {
+		usb_log_error("Failed to start HCD: %s.\n", str_error(ret));
+		goto err_irq;
+	}
+
+	const bus_ops_t *ops = BUS_OPS_LOOKUP(hcd->bus->ops, status);
+
+	/* Need working irq replacement to setup root hub */
+	if (hcd->irq_cap < 0 && ops) {
+		hcd->polling_fibril = fibril_create(interrupt_polling, hcd->bus);
+		if (!hcd->polling_fibril) {
+			usb_log_error("Failed to create polling fibril\n");
+			ret = ENOMEM;
+			goto err_started;
+		}
+		fibril_add_ready(hcd->polling_fibril);
+		usb_log_warning("Failed to enable interrupts: %s."
+		    " Falling back to polling.\n", str_error(hcd->irq_cap));
+	}
+
+	/*
+	 * Creating root hub registers a new USB device so HC
+	 * needs to be ready at this time.
+	 */
+	if (hc_driver->setup_root_hub)
+		ret = hc_driver->setup_root_hub(hcd);
+	if (ret != EOK) {
+		usb_log_error("Failed to setup HC root hub: %s.\n",
+		    str_error(ret));
+		goto err_polling;
+	}
+
+	usb_log_info("Controlling new `%s' device `%s'.\n",
+	   hc_driver->name, ddf_dev_get_name(device));
+	return EOK;
+
+err_polling:
+	// TODO: Stop the polling fibril (refactor the interrupt_polling func)
+	//
+err_started:
+	if (hc_driver->stop)
+		hc_driver->stop(hcd);
+err_irq:
+	unregister_interrupt_handler(device, hcd->irq_cap);
+	if (hc_driver->hc_remove)
+		hc_driver->hc_remove(hcd);
+err_hw_res:
+	hw_res_list_parsed_clean(&hw_res);
+err_hcd:
+	hcd_ddf_clean_hc(hcd);
+	return ret;
+}
+
+int hc_dev_remove(ddf_dev_t *dev)
+{
+	int err;
+	hc_device_t *hcd = dev_to_hcd(dev);
+
+	if (hc_driver->stop)
+		if ((err = hc_driver->stop(hcd)))
+			return err;
+
+	unregister_interrupt_handler(dev, hcd->irq_cap);
+
+	if (hc_driver->hc_remove)
+		if ((err = hc_driver->hc_remove(hcd)))
+			return err;
+
+	hcd_ddf_clean_hc(hcd);
+
+	// TODO probably not complete
+
+	return EOK;
+}
+
+int hc_dev_gone(ddf_dev_t *dev)
+{
+	int err = ENOTSUP;
+	hc_device_t *hcd = dev_to_hcd(dev);
+
+	if (hc_driver->hc_gone)
+		err = hc_driver->hc_gone(hcd);
+
+	hcd_ddf_clean_hc(hcd);
+
+	return err;
+}
+
+int hc_fun_online(ddf_fun_t *fun)
+{
+	assert(fun);
+
+	device_t *dev = ddf_fun_data_get(fun);
+	assert(dev);
+
+	usb_log_info("Device(%d): Requested to be brought online.", dev->address);
+	return bus_device_online(dev);
+}
+
+int hc_fun_offline(ddf_fun_t *fun)
+{
+	assert(fun);
+
+	device_t *dev = ddf_fun_data_get(fun);
+	assert(dev);
+
+	usb_log_info("Device(%d): Requested to be taken offline.", dev->address);
+	return bus_device_offline(dev);
 }
 
@@ -72,5 +364,5 @@
  * @return Max packet size for EP 0
  */
-int hcd_get_ep0_max_packet_size(uint16_t *mps, hcd_t *hcd, device_t *dev)
+int hcd_get_ep0_max_packet_size(uint16_t *mps, bus_t *bus, device_t *dev)
 {
 	assert(mps);
@@ -97,5 +389,5 @@
 
 	usb_log_debug("Requesting first 8B of device descriptor to determine MPS.");
-	ssize_t got = hcd_send_batch_sync(hcd, dev, control_ep, USB_DIRECTION_IN,
+	ssize_t got = bus_device_send_batch_sync(dev, control_ep, USB_DIRECTION_IN,
 	    (char *) &desc, CTRL_PIPE_MIN_PACKET_SIZE, *(uint64_t *)&get_device_desc_8,
 	    "read first 8 bytes of dev descriptor");
@@ -156,5 +448,5 @@
  *
  */
-static toggle_reset_mode_t hcd_get_request_toggle_reset_mode(
+toggle_reset_mode_t hcd_get_request_toggle_reset_mode(
     const usb_device_request_setup_packet_t *request)
 {
@@ -186,126 +478,4 @@
 }
 
-/** Prepare generic usb_transfer_batch and schedule it.
- * @param hcd Host controller driver.
- * @param target address and endpoint number.
- * @param setup_data Data to use in setup stage (Control communication type)
- * @param in Callback for device to host communication.
- * @param out Callback for host to device communication.
- * @param arg Callback parameter.
- * @param name Communication identifier (for nicer output).
- * @return Error code.
- */
-int hcd_send_batch(hcd_t *hcd, 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(hcd);
-	assert(device->address == target.address);
-
-	if (!hcd->ops.schedule) {
-		usb_log_error("HCD does not implement scheduler.\n");
-		return ENOTSUP;
-	}
-
-	endpoint_t *ep = bus_find_endpoint(device, target, direction);
-	if (ep == NULL) {
-		usb_log_error("Endpoint(%d:%d) not registered for %s.\n",
-		    device->address, target.endpoint, name);
-		return ENOENT;
-	}
-
-	// TODO cut here aka provide helper to call with instance of endpoint_t in hand
-	assert(ep->device == device);
-
-	usb_log_debug2("%s %d:%d %zu(%zu).\n",
-	    name, target.address, target.endpoint, size, ep->max_packet_size);
-
-	const size_t bw = endpoint_count_bw(ep, size);
-	/* Check if we have enough bandwidth reserved */
-	if (ep->bandwidth < bw) {
-		usb_log_error("Endpoint(%d:%d) %s needs %zu bw "
-		    "but only %zu is reserved.\n",
-		    device->address, ep->endpoint, name, bw, ep->bandwidth);
-		return ENOSPC;
-	}
-
-	usb_transfer_batch_t *batch = usb_transfer_batch_create(ep);
-	if (!batch) {
-		usb_log_error("Failed to create transfer batch.\n");
-		return ENOMEM;
-	}
-
-	batch->target = target;
-	batch->buffer = data;
-	batch->buffer_size = size;
-	batch->setup.packed = setup_data;
-	batch->dir = direction;
-	batch->on_complete = on_complete;
-	batch->on_complete_data = arg;
-
-	/* Check for commands that reset toggle bit */
-	if (ep->transfer_type == USB_TRANSFER_CONTROL)
-		batch->toggle_reset_mode
-			= hcd_get_request_toggle_reset_mode(&batch->setup.packet);
-
-	const int ret = hcd->ops.schedule(hcd, batch);
-	if (ret != EOK) {
-		usb_log_warning("Batch %p failed to schedule: %s", batch, str_error(ret));
-		usb_transfer_batch_destroy(batch);
-	}
-
-	/* Drop our own reference to ep. */
-	endpoint_del_ref(ep);
-
-	return ret;
-}
-
-typedef struct {
-	fibril_mutex_t done_mtx;
-	fibril_condvar_t done_cv;
-	unsigned done;
-
-	size_t transfered_size;
-	int error;
-} sync_data_t;
-
-static int sync_transfer_complete(void *arg, int error, size_t transfered_size)
-{
-	sync_data_t *d = arg;
-	assert(d);
-	d->transfered_size = transfered_size;
-	d->error = error;
-	fibril_mutex_lock(&d->done_mtx);
-	d->done = 1;
-	fibril_condvar_broadcast(&d->done_cv);
-	fibril_mutex_unlock(&d->done_mtx);
-	return EOK;
-}
-
-ssize_t hcd_send_batch_sync(hcd_t *hcd, device_t *device, usb_target_t target,
-    usb_direction_t direction, char *data, size_t size, uint64_t setup_data,
-    const char *name)
-{
-	assert(hcd);
-	sync_data_t sd = { .done = 0 };
-	fibril_mutex_initialize(&sd.done_mtx);
-	fibril_condvar_initialize(&sd.done_cv);
-
-	const int ret = hcd_send_batch(hcd, device, target, direction,
-	    data, size, setup_data,
-	    sync_transfer_complete, &sd, name);
-	if (ret != EOK)
-		return ret;
-
-	fibril_mutex_lock(&sd.done_mtx);
-	while (!sd.done)
-		fibril_condvar_wait(&sd.done_cv, &sd.done_mtx);
-	fibril_mutex_unlock(&sd.done_mtx);
-
-	return (sd.error == EOK)
-		? (ssize_t) sd.transfered_size
-		: (ssize_t) sd.error;
-}
-
 
 /**
Index: uspace/lib/usbhost/src/usb2_bus.c
===================================================================
--- uspace/lib/usbhost/src/usb2_bus.c	(revision 837d53df7e7cca6ce3ffd079108c600627b9c780)
+++ uspace/lib/usbhost/src/usb2_bus.c	(revision 32fb6bcec95f3a7fbb1b2fdb6528f0e04726a6b6)
@@ -205,5 +205,4 @@
 
 	usb2_bus_t *bus = (usb2_bus_t *) dev->bus;
-	hcd_t *hcd = (hcd_t *) bus->base.hcd;
 
 	/* The default address is currently reserved for this device */
@@ -231,5 +230,5 @@
 
 	uint16_t max_packet_size;
-	if ((err = hcd_get_ep0_max_packet_size(&max_packet_size, hcd, dev)))
+	if ((err = hcd_get_ep0_max_packet_size(&max_packet_size, &bus->base, dev)))
 		goto err_address;
 
@@ -238,5 +237,5 @@
 
 	usb_log_debug("Device(%d): Setting USB address.", address);
-	err = hcd_send_batch_sync(hcd, dev, usb2_default_target, USB_DIRECTION_OUT,
+	err = bus_device_send_batch_sync(dev, usb2_default_target, USB_DIRECTION_OUT,
 	    NULL, 0, *(uint64_t *)&set_address, "set address");
 	if (err != 0) {
@@ -315,5 +314,5 @@
 
 	/* Read the device descriptor, derive the match ids */
-	if ((err = hcd_ddf_device_explore(dev))) {
+	if ((err = hcd_device_explore(dev))) {
 		usb_log_error("Device(%d): Failed to explore device: %s", dev->address, str_error(err));
 		release_address(bus, dev->address);
@@ -463,9 +462,9 @@
  * @return Error code.
  */
-int usb2_bus_init(usb2_bus_t *bus, hcd_t *hcd, size_t available_bandwidth)
+int usb2_bus_init(usb2_bus_t *bus, size_t available_bandwidth)
 {
 	assert(bus);
 
-	bus_init(&bus->base, hcd, sizeof(device_t));
+	bus_init(&bus->base, sizeof(device_t));
 	bus->base.ops = &usb2_bus_ops;
 
