Index: uspace/Makefile
===================================================================
--- uspace/Makefile	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/Makefile	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -74,4 +74,5 @@
 	app/testwrit \
 	app/tetris \
+	app/tmon \
 	app/trace \
 	app/top \
@@ -151,8 +152,10 @@
 	drv/bus/usb/ohci \
 	drv/bus/usb/uhci \
+	drv/bus/usb/usbdiag \
 	drv/bus/usb/usbflbk \
 	drv/bus/usb/usbhub \
 	drv/bus/usb/usbmid \
 	drv/bus/usb/vhc \
+	drv/bus/usb/xhci \
 	drv/char/i8042 \
 	drv/char/msim-con \
@@ -282,3 +285,2 @@
 
 -include $(DEPS)
-
Index: uspace/app/tmon/Makefile
===================================================================
--- uspace/app/tmon/Makefile	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/app/tmon/Makefile	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,41 @@
+#
+# Copyright (c) 2017 Petr Manek
+# 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 thgis 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.
+#
+
+USPACE_PREFIX = ../..
+BINARY = tmon
+
+LIBS = drv
+
+SOURCES = \
+	main.c\
+	list.c\
+	tf.c\
+	burst_tests.c\
+	resolve.c
+
+include $(USPACE_PREFIX)/Makefile.common
Index: uspace/app/tmon/burst_tests.c
===================================================================
--- uspace/app/tmon/burst_tests.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/app/tmon/burst_tests.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,446 @@
+/*
+ * Copyright (c) 2017 Petr Manek
+ * 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 tmon
+ * @{
+ */
+/**
+ * @file
+ * USB burst tests.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <str.h>
+#include <str_error.h>
+#include <getopt.h>
+#include <usbdiag_iface.h>
+#include <macros.h>
+#include "commands.h"
+#include "tf.h"
+
+#define NAME   "tmon"
+#define INDENT "      "
+
+/** Generic burst test parameters. */
+typedef struct tmon_burst_test_params {
+	/** Inherited base. */
+	tmon_test_params_t base;
+	/** The count of reads/writes to perform. */
+	uint32_t cycles;
+	/** Size of single read/write. */
+	size_t size;
+} tmon_burst_test_params_t;
+
+/** Static array of long options, from which test parameters are parsed. */
+static struct option long_options[] = {
+	{"cycles", required_argument, NULL, 'n'},
+	{"size", required_argument, NULL, 's'},
+	{0, 0, NULL, 0}
+};
+
+/** String of short options, from which test parameters are parsed. */
+static const char *short_options = "n:s:";
+
+/** Common option parser for all burst tests.
+ * @param[in] argc Number of arguments.
+ * @param[in] argv Argument values. Must point to exactly `argc` strings.
+ * @param[out] params Parsed test parameters (if successful).
+ *
+ * @return EOK if successful (in such case caller becomes the owner of `params`).
+ */
+static int read_params(int argc, char *argv[], tmon_test_params_t **params)
+{
+	int rc;
+	tmon_burst_test_params_t *p = (tmon_burst_test_params_t *) malloc(sizeof(tmon_burst_test_params_t));
+	if (!p)
+		return ENOMEM;
+
+	// Default values.
+	p->cycles = 256;
+	p->size = 1024;
+
+	// Parse other than default values.
+	int c;
+	for (c = 0, optreset = 1, optind = 0; c != -1;) {
+		c = getopt_long(argc, argv, short_options, long_options, NULL);
+		switch (c) {
+		case -1:
+			break;
+		case 'n':
+			if (!optarg || str_uint32_t(optarg, NULL, 10, false, &p->cycles) != EOK) {
+				puts(NAME ": Invalid number of cycles.\n");
+				rc = EINVAL;
+				goto err_malloc;
+			}
+			break;
+		case 's':
+			if (!optarg || str_size_t(optarg, NULL, 10, false, &p->size) != EOK) {
+				puts(NAME ": Invalid data size.\n");
+				rc = EINVAL;
+				goto err_malloc;
+			}
+			break;
+		}
+	}
+
+	*params = (tmon_test_params_t *) p;
+	return EOK;
+
+err_malloc:
+	free(p);
+	*params = NULL;
+	return rc;
+}
+
+/** Unit of quantity used for pretty formatting. */
+typedef struct tmon_unit {
+	/** Prefix letter, which is printed before the actual unit. */
+	char prefix;
+	/** Factor of the unit. */
+	uint64_t factor;
+} tmon_unit_t;
+
+/** Static array of units with decreasing factors. */
+static const tmon_unit_t units[] = {
+	{ .prefix = 'E', .factor = ((uint64_t) 1) << 60 },
+	{ .prefix = 'P', .factor = ((uint64_t) 1) << 50 },
+	{ .prefix = 'T', .factor = ((uint64_t) 1) << 40 },
+	{ .prefix = 'G', .factor = ((uint64_t) 1) << 30 },
+	{ .prefix = 'M', .factor = ((uint64_t) 1) << 20 },
+	{ .prefix = 'k', .factor = ((uint64_t) 1) << 10 }
+};
+
+/** Format size in bytes for human reading.
+ * @param[in] size The size to format.
+ * @param[in] fmt Format string. Must include one double and char.
+ *
+ * @return Heap-allocated string if successful (caller becomes its owner), NULL otherwise.
+ */
+static char * format_size(double size, const char *fmt)
+{
+	// Figure out the "tightest" unit.
+	unsigned i;
+	for (i = 0; i < ARRAY_SIZE(units); ++i) {
+		if (units[i].factor <= size)
+			break;
+	}
+
+	char prefix[] = { '\0', '\0' };
+	double factor = 1;
+
+	if (i < ARRAY_SIZE(units)) {
+		prefix[0] = units[i].prefix;
+		factor = units[i].factor;
+	}
+
+	// Format the size.
+	const double div_size = size / factor;
+
+	char *out = NULL;
+	asprintf(&out, fmt, div_size, prefix);
+
+	return out;
+}
+
+/** Print burst test parameters.
+ * @param[in] params Test parameters to print.
+ */
+static void print_params(const tmon_burst_test_params_t *params)
+{
+	printf(INDENT "Number of cycles: %d\n", params->cycles);
+
+	char *str_size = format_size(params->size, "%.3f %sB");
+	printf(INDENT "Data size: %s\n", str_size);
+	free(str_size);
+}
+
+/** Print burst test results.
+ * @param[in] params Test parameters.
+ * @param[in] duration Duration of the burst test.
+ */
+static void print_results(const tmon_burst_test_params_t *params, usbdiag_dur_t duration)
+{
+	printf(INDENT "Total duration: %ld ms\n", duration);
+
+	const double dur_per_cycle = (double) duration / (double) params->cycles;
+	printf(INDENT "Duration per cycle: %.3f ms\n", dur_per_cycle);
+
+	const size_t total_size = params->size * params->cycles;
+	char *str_total_size = format_size(total_size, "%.3f %sB");
+	printf(INDENT "Total size: %s\n", str_total_size);
+	free(str_total_size);
+
+	const double speed = 1000.0 * (double) total_size / (double) duration;
+	char *str_speed = format_size(speed, "%.3f %sB/s");
+	printf(INDENT "Average speed: %s\n", str_speed);
+	free(str_speed);
+}
+
+/** Run "interrupt in" burst test.
+ * @param[in] exch Open async exchange with the diagnostic device.
+ * @param[in] generic_params Test parameters. Must point to 'tmon_burst_test_params_t'.
+ *
+ * @return Exit code
+ */
+static int run_intr_in(async_exch_t *exch, const tmon_test_params_t *generic_params)
+{
+	const tmon_burst_test_params_t *params = (tmon_burst_test_params_t *) generic_params;
+	puts("Reading data from interrupt endpoint.\n");
+	print_params(params);
+
+	usbdiag_dur_t duration;
+	int rc = usbdiag_burst_intr_in(exch, params->cycles, params->size, &duration);
+	if (rc) {
+		printf(NAME ": Test failed with error: %s\n", str_error(rc));
+		return 1;
+	}
+
+	puts("Test succeeded.\n");
+	print_results(params, duration);
+	return 0;
+}
+
+/** Run "interrupt out" burst test.
+ * @param[in] exch Open async exchange with the diagnostic device.
+ * @param[in] generic_params Test parameters. Must point to 'tmon_burst_test_params_t'.
+ *
+ * @return Exit code
+ */
+static int run_intr_out(async_exch_t *exch, const tmon_test_params_t *generic_params)
+{
+	const tmon_burst_test_params_t *params = (tmon_burst_test_params_t *) generic_params;
+	puts("Writing data to interrupt endpoint.\n");
+	print_params(params);
+
+	usbdiag_dur_t duration;
+	int rc = usbdiag_burst_intr_out(exch, params->cycles, params->size, &duration);
+	if (rc) {
+		printf(NAME ": Test failed with error: %s\n", str_error(rc));
+		return 1;
+	}
+
+	puts("Test succeeded.\n");
+	print_results(params, duration);
+	return 0;
+}
+
+/** Run "bulk in" burst test.
+ * @param[in] exch Open async exchange with the diagnostic device.
+ * @param[in] generic_params Test parameters. Must point to 'tmon_burst_test_params_t'.
+ *
+ * @return Exit code
+ */
+static int run_bulk_in(async_exch_t *exch, const tmon_test_params_t *generic_params)
+{
+	const tmon_burst_test_params_t *params = (tmon_burst_test_params_t *) generic_params;
+	puts("Reading data from bulk endpoint.\n");
+	print_params(params);
+
+	usbdiag_dur_t duration;
+	int rc = usbdiag_burst_bulk_in(exch, params->cycles, params->size, &duration);
+	if (rc) {
+		printf(NAME ": Test failed with error: %s\n", str_error(rc));
+		return 1;
+	}
+
+	puts("Test succeeded.\n");
+	print_results(params, duration);
+	return 0;
+}
+
+/** Run "bulk out" burst test.
+ * @param[in] exch Open async exchange with the diagnostic device.
+ * @param[in] generic_params Test parameters. Must point to 'tmon_burst_test_params_t'.
+ *
+ * @return Exit code
+ */
+static int run_bulk_out(async_exch_t *exch, const tmon_test_params_t *generic_params)
+{
+	const tmon_burst_test_params_t *params = (tmon_burst_test_params_t *) generic_params;
+	puts("Writing data to bulk endpoint.\n");
+	print_params(params);
+
+	usbdiag_dur_t duration;
+	int rc = usbdiag_burst_bulk_out(exch, params->cycles, params->size, &duration);
+	if (rc) {
+		printf(NAME ": Test failed with error: %s\n", str_error(rc));
+		return 1;
+	}
+
+	puts("Test succeeded.\n");
+	print_results(params, duration);
+	return 0;
+}
+
+/** Run "isochronous in" burst test.
+ * @param[in] exch Open async exchange with the diagnostic device.
+ * @param[in] generic_params Test parameters. Must point to 'tmon_burst_test_params_t'.
+ *
+ * @return Exit code
+ */
+static int run_isoch_in(async_exch_t *exch, const tmon_test_params_t *generic_params)
+{
+	const tmon_burst_test_params_t *params = (tmon_burst_test_params_t *) generic_params;
+	puts("Reading data from isochronous endpoint.\n");
+	print_params(params);
+
+	usbdiag_dur_t duration;
+	int rc = usbdiag_burst_isoch_in(exch, params->cycles, params->size, &duration);
+	if (rc) {
+		printf(NAME ": Test failed with error: %s\n", str_error(rc));
+		return 1;
+	}
+
+	puts("Test succeeded.\n");
+	print_results(params, duration);
+	return 0;
+}
+
+/** Run "isochronous out" burst test.
+ * @param[in] exch Open async exchange with the diagnostic device.
+ * @param[in] generic_params Test parameters. Must point to 'tmon_burst_test_params_t'.
+ *
+ * @return Exit code
+ */
+static int run_isoch_out(async_exch_t *exch, const tmon_test_params_t *generic_params)
+{
+	const tmon_burst_test_params_t *params = (tmon_burst_test_params_t *) generic_params;
+	puts("Writing data to isochronous endpoint.\n");
+	print_params(params);
+
+	usbdiag_dur_t duration;
+	int rc = usbdiag_burst_isoch_out(exch, params->cycles, params->size, &duration);
+	if (rc) {
+		printf(NAME ": Test failed with error: %s\n", str_error(rc));
+		return 1;
+	}
+
+	puts("Test succeeded.\n");
+	print_results(params, duration);
+	return 0;
+}
+
+/** Interrupt in burst test command handler.
+ * @param[in] argc Number of arguments.
+ * @param[in] argv Argument values. Must point to exactly `argc` strings.
+ *
+ * @return Exit code
+ */
+int tmon_burst_intr_in(int argc, char *argv[])
+{
+	static const tmon_test_ops_t ops = {
+		.run = run_intr_in,
+		.read_params = read_params
+	};
+
+	return tmon_test_main(argc, argv, &ops);
+}
+
+/** Interrupt out burst test command handler.
+ * @param[in] argc Number of arguments.
+ * @param[in] argv Argument values. Must point to exactly `argc` strings.
+ *
+ * @return Exit code
+ */
+int tmon_burst_intr_out(int argc, char *argv[])
+{
+	static const tmon_test_ops_t ops = {
+		.run = run_intr_out,
+		.read_params = read_params
+	};
+
+	return tmon_test_main(argc, argv, &ops);
+}
+
+/** Interrupt bulk burst test command handler.
+ * @param[in] argc Number of arguments.
+ * @param[in] argv Argument values. Must point to exactly `argc` strings.
+ *
+ * @return Exit code
+ */
+int tmon_burst_bulk_in(int argc, char *argv[])
+{
+	static const tmon_test_ops_t ops = {
+		.run = run_bulk_in,
+		.read_params = read_params
+	};
+
+	return tmon_test_main(argc, argv, &ops);
+}
+
+/** Bulk out burst test command handler.
+ * @param[in] argc Number of arguments.
+ * @param[in] argv Argument values. Must point to exactly `argc` strings.
+ *
+ * @return Exit code
+ */
+int tmon_burst_bulk_out(int argc, char *argv[])
+{
+	static const tmon_test_ops_t ops = {
+		.run = run_bulk_out,
+		.read_params = read_params
+	};
+
+	return tmon_test_main(argc, argv, &ops);
+}
+
+/** Isochronous in burst test command handler.
+ * @param[in] argc Number of arguments.
+ * @param[in] argv Argument values. Must point to exactly `argc` strings.
+ *
+ * @return Exit code
+ */
+int tmon_burst_isoch_in(int argc, char *argv[])
+{
+	static const tmon_test_ops_t ops = {
+		.run = run_isoch_in,
+		.read_params = read_params
+	};
+
+	return tmon_test_main(argc, argv, &ops);
+}
+
+/** Isochronous out burst test command handler.
+ * @param[in] argc Number of arguments.
+ * @param[in] argv Argument values. Must point to exactly `argc` strings.
+ *
+ * @return Exit code
+ */
+int tmon_burst_isoch_out(int argc, char *argv[])
+{
+	static const tmon_test_ops_t ops = {
+		.run = run_isoch_out,
+		.read_params = read_params
+	};
+
+	return tmon_test_main(argc, argv, &ops);
+}
+
+/** @}
+ */
Index: uspace/app/tmon/commands.h
===================================================================
--- uspace/app/tmon/commands.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/app/tmon/commands.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2017 Petr Manek
+ * 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 tmon
+ * @{
+ */
+/**
+ * @file USB diagnostic device commands.
+ */
+
+#ifndef TMON_COMMANDS_H_
+#define TMON_COMMANDS_H_
+
+/* All commands are just versions of int main(int, char **). */
+
+/* List command just prints compatible devices. */
+int tmon_list(int, char **);
+
+/* Burst tests read/write into endpoints as fast as possible. */
+int tmon_burst_intr_in(int, char **);
+int tmon_burst_intr_out(int, char **);
+int tmon_burst_bulk_in(int, char **);
+int tmon_burst_bulk_out(int, char **);
+int tmon_burst_isoch_in(int, char **);
+int tmon_burst_isoch_out(int, char **);
+
+#endif /* TMON_COMMANDS_H_ */
+
+/** @}
+ */
Index: uspace/app/tmon/list.c
===================================================================
--- uspace/app/tmon/list.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/app/tmon/list.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2017 Petr Manek
+ * 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 tmon
+ * @{
+ */
+/**
+ * @file
+ * USB transfer debugging.
+ */
+
+#include <stdio.h>
+#include <devman.h>
+#include <loc.h>
+#include <usbdiag_iface.h>
+#include "commands.h"
+
+#define NAME "tmon"
+#define MAX_PATH_LENGTH 1024
+
+/** Print a single item of the device list.
+ * @param[in] svc Service ID of the devman function.
+ */
+static void print_list_item(service_id_t svc)
+{
+	int rc;
+	devman_handle_t diag_handle = 0;
+
+	if ((rc = devman_fun_sid_to_handle(svc, &diag_handle))) {
+		printf(NAME ": Error resolving handle of device with SID %" PRIun ", skipping.\n", svc);
+		return;
+	}
+
+	char path[MAX_PATH_LENGTH];
+	if ((rc = devman_fun_get_path(diag_handle, path, sizeof(path)))) {
+		printf(NAME ": Error resolving path of device with SID %" PRIun ", skipping.\n", svc);
+		return;
+	}
+
+	printf("%s\n", path);
+}
+
+/** List command handler.
+ * @param[in] argc Number of arguments.
+ * @param[in] argv Argument values. Must point to exactly `argc` strings.
+ *
+ * @return Exit code
+ */
+int tmon_list(int argc, char *argv[])
+{
+	category_id_t diag_cat;
+	service_id_t *svcs;
+	size_t count;
+	int rc;
+
+	if ((rc = loc_category_get_id(USBDIAG_CATEGORY, &diag_cat, 0))) {
+		printf(NAME ": Error resolving category '%s'", USBDIAG_CATEGORY);
+		return 1;
+	}
+
+	if ((rc = loc_category_get_svcs(diag_cat, &svcs, &count))) {
+		printf(NAME ": Error getting list of diagnostic devices.\n");
+		return 1;
+	}
+
+	for (unsigned i = 0; i < count; ++i) {
+		print_list_item(svcs[i]);
+	}
+
+	free(svcs);
+	return 0;
+}
+
+/** @}
+ */
Index: uspace/app/tmon/main.c
===================================================================
--- uspace/app/tmon/main.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/app/tmon/main.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2017 Petr Manek
+ * 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 tmon
+ * @{
+ */
+/**
+ * @file
+ * USB transfer debugging.
+ */
+
+#include <stdio.h>
+#include <stddef.h>
+#include <str.h>
+#include <macros.h>
+#include "commands.h"
+
+#define NAME   "tmon"
+#define INDENT "      "
+
+/** Command which is executed by tmon. */
+typedef struct tmon_cmd {
+	/** Unique name, by which the command is executed. */
+	const char *name;
+	/** Description of the command, which is displayed in the usage string. */
+	const char *description;
+	/** Function, which executes the command. Same as int main(int, char**). */
+	int (*action)(int, char **);
+} tmon_cmd_t;
+
+/** Static array of commands supported by tmon. */
+static tmon_cmd_t commands[] = {
+	{
+		.name = "list",
+		.description = "Print a list of connected diagnostic devices.",
+		.action = tmon_list,
+	},
+	{
+		.name = "test-intr-in",
+		.description = "Read from interrupt endpoint as fast as possible.",
+		.action = tmon_burst_intr_in,
+	},
+	{
+		.name = "test-intr-out",
+		.description = "Write to interrupt endpoint as fast as possible.",
+		.action = tmon_burst_intr_out,
+	},
+	{
+		.name = "test-bulk-in",
+		.description = "Read from bulk endpoint as fast as possible.",
+		.action = tmon_burst_bulk_in,
+	},
+	{
+		.name = "test-bulk-out",
+		.description = "Write to bulk endpoint as fast as possible.",
+		.action = tmon_burst_bulk_out,
+	},
+	{
+		.name = "test-isoch-in",
+		.description = "Read from isochronous endpoint as fast as possible.",
+		.action = tmon_burst_isoch_in,
+	},
+	{
+		.name = "test-isoch-out",
+		.description = "Write to isochronous endpoint as fast as possible.",
+		.action = tmon_burst_isoch_out,
+	}
+};
+
+/** Option shown in the usage string. */
+typedef struct tmon_opt {
+	/** Long name of the option without "--" prefix. */
+	const char *long_name;
+	/** Short name of the option without "-" prefix. */
+	char short_name;
+	/** Description of the option displayed in the usage string. */
+	const char *description;
+} tmon_opt_t;
+
+/** Static array of options displayed in the tmon usage string. */
+static tmon_opt_t options[] = {
+	{
+		.long_name = "cycles",
+		.short_name = 'n',
+		.description = "Set the number of read/write cycles."
+	},
+	{
+		.long_name = "size",
+		.short_name = 's',
+		.description = "Set the data size transferred in a single cycle."
+	}
+};
+
+/** Print usage string.
+ * @param[in] app_name Name to print in the invocation example.
+ */
+static void print_usage(char *app_name)
+{
+	puts(NAME ": benchmark USB diagnostic device\n\n");
+	printf("Usage: %s command [device] [options]\n\n", app_name);
+
+	for (unsigned i = 0; i < ARRAY_SIZE(commands); ++i) {
+		printf(INDENT "%s - %s\n", commands[i].name, commands[i].description);
+	}
+
+	puts("\n");
+	for (unsigned i = 0; i < ARRAY_SIZE(options); ++i) {
+		printf(INDENT "-%c --%s\n" INDENT INDENT "%s\n", options[i].short_name, options[i].long_name, options[i].description);
+	}
+
+	puts("\nIf no device is specified, the first device is used provided that it is the only one connected. Otherwise, the command fails.\n\n");
+}
+
+/** Main tmon entry point.
+ * @param[in] argc Number of arguments.
+ * @param[in] argv Argument values. Must point to exactly `argc` strings.
+ *
+ * @return Exit code
+ */
+int main(int argc, char *argv[])
+{
+	// Find a command to execute.
+	tmon_cmd_t *cmd = NULL;
+	for (unsigned i = 0; argc > 1 && i < ARRAY_SIZE(commands); ++i) {
+		if (str_cmp(argv[1], commands[i].name) == 0) {
+			cmd = commands + i;
+			break;
+		}
+	}
+
+	if (!cmd) {
+		print_usage(argv[0]);
+		return -1;
+	}
+
+	return cmd->action(argc - 1, argv + 1);
+}
+
+/** @}
+ */
Index: uspace/app/tmon/resolve.c
===================================================================
--- uspace/app/tmon/resolve.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/app/tmon/resolve.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2017 Petr Manek
+ * 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 tmon
+ * @{
+ */
+/**
+ * @file
+ * USB diagnostic device resolving.
+ */
+
+#include <loc.h>
+#include <errno.h>
+#include <str_error.h>
+#include <stdio.h>
+#include <usbdiag_iface.h>
+#include "resolve.h"
+
+#define NAME "tmon"
+
+/** Resolve a single function by its class (fail if there is more/less than 1).
+ * @param[out] fun Resolved function handle (if found).
+ *
+ * @return EOK if the function was resolved successfully.
+ */
+int tmon_resolve_default(devman_handle_t *fun)
+{
+	category_id_t diag_cat;
+	service_id_t *svcs;
+	size_t count;
+	int rc;
+
+	if ((rc = loc_category_get_id(USBDIAG_CATEGORY, &diag_cat, 0))) {
+		printf(NAME ": Error resolving category '%s'", USBDIAG_CATEGORY);
+		return rc;
+	}
+
+	if ((rc = loc_category_get_svcs(diag_cat, &svcs, &count))) {
+		printf(NAME ": Error getting list of diagnostic devices.\n");
+		return rc;
+	}
+
+	// There must be exactly one diagnostic device for this to work.
+	if (count != 1) {
+		if (count) {
+			printf(NAME ": Found %zu devices. Please specify which to use.\n", count);
+		} else {
+			printf(NAME ": No diagnostic devices found.\n");
+		}
+		return ENOENT;
+	}
+
+	if ((rc = devman_fun_sid_to_handle(svcs[0], fun))) {
+		printf(NAME ": Error resolving handle of device with "
+		    "SID %" PRIun ".\n", svcs[0]);
+		return rc;
+	}
+
+	return EOK;
+}
+
+/** Resolve a function by its name or device path.
+ * @param[in] dev_path Name or device path (see `devman_fun_get_handle` for possible values).
+ * @param[out] fun Resolved function handle (if found).
+ *
+ * @return EOK if the function was resolved successfully.
+ */
+int tmon_resolve_named(const char *dev_path, devman_handle_t *fun)
+{
+	int rc;
+	if ((rc = devman_fun_get_handle(dev_path, fun, 0))) {
+		printf(NAME ": Error resolving handle of device - %s.\n", str_error(rc));
+		return rc;
+	}
+
+	return EOK;
+}
+
+/** @}
+ */
Index: uspace/app/tmon/resolve.h
===================================================================
--- uspace/app/tmon/resolve.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/app/tmon/resolve.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2017 Petr Manek
+ * 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 tmon
+ * @{
+ */
+/**
+ * @file USB diagnostic device resolving.
+ */
+
+#ifndef TMON_RESOLVE_H_
+#define TMON_RESOLVE_H_
+
+#include <devman.h>
+
+int tmon_resolve_default(devman_handle_t *);
+int tmon_resolve_named(const char *, devman_handle_t *);
+
+#endif /* TMON_RESOLVE_H_ */
+
+/** @}
+ */
Index: uspace/app/tmon/tf.c
===================================================================
--- uspace/app/tmon/tf.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/app/tmon/tf.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2017 Petr Manek
+ * 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 tmon
+ * @{
+ */
+/**
+ * @file
+ * Testing framework.
+ */
+
+#include <stdio.h>
+#include <devman.h>
+#include <str_error.h>
+#include <usbdiag_iface.h>
+#include "resolve.h"
+#include "tf.h"
+
+#define NAME "tmon"
+#define MAX_PATH_LENGTH 1024
+
+/** Common command handler for all test commands.
+ * @param[in] argc Number of arguments.
+ * @param[in] argv Argument values. Must point to exactly `argc` strings.
+ *
+ * @return Exit code
+ */
+int tmon_test_main(int argc, char *argv[], const tmon_test_ops_t *ops)
+{
+	// Resolve device function.
+	devman_handle_t fun = -1;
+
+	if (argc >= 2 && *argv[1] != '-') {
+		// Assume that the first argument is device path.
+		if (tmon_resolve_named(argv[1], &fun))
+			return 1;
+	} else {
+		// The first argument is either an option or not present.
+		if (tmon_resolve_default(&fun))
+			return 1;
+	}
+
+	int rc, ec;
+	char path[MAX_PATH_LENGTH];
+	if ((rc = devman_fun_get_path(fun, path, sizeof(path)))) {
+		printf(NAME ": Error resolving path of device with handle "
+		    "%" PRIun ". %s\n", fun, str_error(rc));
+		return 1;
+	}
+
+	printf("Using device: %s\n", path);
+
+	// Read test parameters from options.
+	tmon_test_params_t *params = NULL;
+	if ((rc = ops->read_params(argc, argv, &params))) {
+		printf(NAME ": Reading test parameters failed. %s\n", str_error(rc));
+		return 1;
+	}
+
+	// Run the test body.
+	async_sess_t *sess = usbdiag_connect(fun);
+	if (!sess) {
+		printf(NAME ": Could not connect to the device.\n");
+		return 1;
+	}
+
+	async_exch_t *exch = async_exchange_begin(sess);
+	if (!exch) {
+		printf(NAME ": Could not start exchange with the device.\n");
+		ec = 1;
+		goto err_sess;
+	}
+
+	ec = ops->run(exch, params);
+	async_exchange_end(exch);
+
+err_sess:
+	usbdiag_disconnect(sess);
+	free(params);
+	return ec;
+}
+
+/** @}
+ */
Index: uspace/app/tmon/tf.h
===================================================================
--- uspace/app/tmon/tf.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/app/tmon/tf.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2017 Petr Manek
+ * 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 tmon
+ * @{
+ */
+/**
+ * @file Testing framework definitions.
+ */
+
+#ifndef TMON_TF_H_
+#define TMON_TF_H_
+
+#include <async.h>
+
+/** Parameters common for all tests. */
+typedef struct tmon_test_params {
+	/* Nothing here. */
+} tmon_test_params_t;
+
+/** Operations to implement by all tests. */
+typedef struct tmon_test_ops {
+	int (*run)(async_exch_t *, const tmon_test_params_t *);
+	int (*read_params)(int, char **, tmon_test_params_t **);
+} tmon_test_ops_t;
+
+int tmon_test_main(int, char **, const tmon_test_ops_t *);
+
+#endif /* TMON_TF_H_ */
+
+/** @}
+ */
Index: uspace/app/usbinfo/dump.c
===================================================================
--- uspace/app/usbinfo/dump.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/app/usbinfo/dump.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -61,5 +61,6 @@
 		INDENT INDENT INDENT,
 		INDENT INDENT INDENT INDENT,
-		INDENT INDENT INDENT INDENT INDENT
+		INDENT INDENT INDENT INDENT INDENT,
+		INDENT INDENT INDENT INDENT INDENT INDENT,
 	};
 	static size_t indents_count = sizeof(indents)/sizeof(indents[0]);
Index: uspace/app/usbinfo/hid.c
===================================================================
--- uspace/app/usbinfo/hid.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/app/usbinfo/hid.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -141,5 +141,5 @@
 	    raw_report, report_size, &actual_report_size);
 	if (rc != EOK) {
-		usb_log_error("Failed to retrieve HID report descriptor: %s.\n",
+		usb_log_error("Failed to retrieve HID report descriptor: %s.",
 		    str_error(rc));
 		free(raw_report);
@@ -150,5 +150,5 @@
 	rc = usb_hid_parse_report_descriptor(&report, raw_report, report_size);
 	if (rc != EOK) {
-		usb_log_error("Failed to part report descriptor: %s.\n",
+		usb_log_error("Failed to part report descriptor: %s.",
 		    str_error(rc));
 	}
Index: uspace/app/usbinfo/info.c
===================================================================
--- uspace/app/usbinfo/info.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/app/usbinfo/info.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -151,4 +151,10 @@
 }
 
+static void dump_descriptor_tree_brief_superspeed_endpoint_companion(const char *prefix,
+    usb_superspeed_endpoint_companion_descriptor_t *descriptor)
+{
+	printf("%sSuperspeed endpoint companion\n", prefix);
+}
+
 static void dump_descriptor_tree_brief_hid(const char *prefix,
     usb_standard_hid_descriptor_t *descriptor)
@@ -206,4 +212,7 @@
 		    usb_standard_endpoint_descriptor_t,
 		    dump_descriptor_tree_brief_endpoint);
+		_BRANCH(USB_DESCTYPE_SSPEED_EP_COMPANION,
+		    usb_superspeed_endpoint_companion_descriptor_t,
+		    dump_descriptor_tree_brief_superspeed_endpoint_companion);
 		_BRANCH(USB_DESCTYPE_HID,
 		    usb_standard_hid_descriptor_t,
Index: uspace/app/vuhid/hids/bootkbd.c
===================================================================
--- uspace/app/vuhid/hids/bootkbd.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/app/vuhid/hids/bootkbd.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -110,5 +110,5 @@
 #define _GET_LED(index, signature) \
 	(((leds) & (1 << index)) ? (signature) : '-')
-	usb_log_info("%s: LEDs = %c%c%c%c%c\n",
+	usb_log_info("%s: LEDs = %c%c%c%c%c",
 	    iface->name,
 	    _GET_LED(0, '0'), _GET_LED(1, 'A'), _GET_LED(2, 's'),
Index: uspace/app/vuhid/life.c
===================================================================
--- uspace/app/vuhid/life.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/app/vuhid/life.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -45,5 +45,5 @@
 	data->data_in_last_pos = (size_t) -1;
 	async_usleep(1000 * 1000 * 5);
-	usb_log_debug("%s\n", data->msg_born);
+	usb_log_debug("%s", data->msg_born);
 	while (data->data_in_pos < data->data_in_count) {
 		async_usleep(1000 * data->data_in_pos_change_delay);
@@ -51,5 +51,5 @@
 		data->data_in_pos++;
 	}
-	usb_log_debug("%s\n", data->msg_die);
+	usb_log_debug("%s", data->msg_die);
 }
 
Index: uspace/app/vuhid/main.c
===================================================================
--- uspace/app/vuhid/main.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/app/vuhid/main.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -227,5 +227,5 @@
 
 	for (int i = 0; i < (int) hid_dev.descriptors->configuration->extra_count; i++) {
-		usb_log_debug("Found extra descriptor: %s.\n",
+		usb_log_debug("Found extra descriptor: %s.",
 		    usb_debug_str_buffer(
 		        hid_dev.descriptors->configuration->extra[i].data,
Index: uspace/app/vuhid/virthid.h
===================================================================
--- uspace/app/vuhid/virthid.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/app/vuhid/virthid.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -40,5 +40,5 @@
 #include <fibril_synch.h>
 
-#define VUHID_ENDPOINT_MAX USB11_ENDPOINT_MAX
+#define VUHID_ENDPOINT_MAX USB_ENDPOINT_MAX
 #define VUHID_INTERFACE_MAX 8
 
Index: uspace/drv/block/usbmast/bo_trans.c
===================================================================
--- uspace/drv/block/usbmast/bo_trans.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/block/usbmast/bo_trans.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -90,5 +90,5 @@
 	    str_error(rc));
 	if (rc != EOK) {
-		usb_log_error("Bulk out write failed: %s\n", str_error(rc));
+		usb_log_error("Bulk out write failed: %s", str_error(rc));
 		return EIO;
 	}
@@ -154,12 +154,12 @@
 	case cbs_failed:
 		cmd->status = CMDS_FAILED;
-		usb_log_error("CBS Failed.\n");
+		usb_log_error("CBS Failed.");
 		break;
 	case cbs_phase_error:
-		usb_log_error("CBS phase error.\n");
+		usb_log_error("CBS phase error.");
 		rc = EIO;
 		break;
 	default:
-		usb_log_error("CBS other error.\n");
+		usb_log_error("CBS other error.");
 		rc = EIO;
 		break;
@@ -168,5 +168,5 @@
 	const size_t residue = uint32_usb2host(csw.dCSWDataResidue);
 	if (residue > dbuf_size) {
-		usb_log_error("Residue > buffer size (%zu > %zu).\n",
+		usb_log_error("Residue > buffer size (%zu > %zu).",
 		    residue, dbuf_size);
 		return EIO;
Index: uspace/drv/block/usbmast/main.c
===================================================================
--- uspace/drv/block/usbmast/main.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/block/usbmast/main.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -156,5 +156,5 @@
 	    usb_device_get_mapped_ep_desc(dev, &bulk_out_ep);
 	if (!epm_in || !epm_out || !epm_in->present || !epm_out->present) {
-		usb_log_error("Required EPs were not mapped.\n");
+		usb_log_error("Required EPs were not mapped.");
 		return ENOENT;
 	}
@@ -163,5 +163,5 @@
 	mdev = usb_device_data_alloc(dev, sizeof(usbmast_dev_t));
 	if (mdev == NULL) {
-		usb_log_error("Failed allocating softstate.\n");
+		usb_log_error("Failed allocating softstate.");
 		return ENOMEM;
 	}
@@ -169,18 +169,17 @@
 	mdev->usb_dev = dev;
 
-	usb_log_info("Initializing mass storage `%s'.\n",
+	usb_log_info("Initializing mass storage `%s'.",
 	    usb_device_get_name(dev));
-	usb_log_debug("Bulk in endpoint: %d [%zuB].\n",
-	    epm_in->pipe.endpoint_no, epm_in->pipe.max_packet_size);
-	usb_log_debug("Bulk out endpoint: %d [%zuB].\n",
-	    epm_out->pipe.endpoint_no, epm_out->pipe.max_packet_size);
-
-	usb_log_debug("Get LUN count...\n");
+	usb_log_debug("Bulk in endpoint: %d [%zuB].",
+	    epm_in->pipe.desc.endpoint_no, epm_in->pipe.desc.max_transfer_size);
+	usb_log_debug("Bulk out endpoint: %d [%zuB].",
+	    epm_out->pipe.desc.endpoint_no, epm_out->pipe.desc.max_transfer_size);
+
+	usb_log_debug("Get LUN count...");
 	mdev->lun_count = usb_masstor_get_lun_count(mdev);
 	mdev->luns = calloc(mdev->lun_count, sizeof(ddf_fun_t*));
 	if (mdev->luns == NULL) {
-		rc = ENOMEM;
-		usb_log_error("Failed allocating luns table.\n");
-		goto error;
+		usb_log_error("Failed allocating luns table.");
+		return ENOMEM;
 	}
 
@@ -226,5 +225,5 @@
 
 	if (asprintf(&fun_name, "l%u", lun) < 0) {
-		usb_log_error("Out of memory.\n");
+		usb_log_error("Out of memory.");
 		rc = ENOMEM;
 		goto error;
@@ -233,5 +232,5 @@
 	fun = usb_device_ddf_fun_create(mdev->usb_dev, fun_exposed, fun_name);
 	if (fun == NULL) {
-		usb_log_error("Failed to create DDF function %s.\n", fun_name);
+		usb_log_error("Failed to create DDF function %s.", fun_name);
 		rc = ENOMEM;
 		goto error;
@@ -241,5 +240,5 @@
 	mfun = ddf_fun_data_alloc(fun, sizeof(usbmast_fun_t));
 	if (mfun == NULL) {
-		usb_log_error("Failed allocating softstate.\n");
+		usb_log_error("Failed allocating softstate.");
 		rc = ENOMEM;
 		goto error;
@@ -257,9 +256,9 @@
 	ddf_fun_set_conn_handler(fun, usbmast_bd_connection);
 
-	usb_log_debug("Inquire...\n");
+	usb_log_debug("Inquire...");
 	usbmast_inquiry_data_t inquiry;
 	rc = usbmast_inquiry(mfun, &inquiry);
 	if (rc != EOK) {
-		usb_log_warning("Failed to inquire device `%s': %s.\n",
+		usb_log_warning("Failed to inquire device `%s': %s.",
 		    usb_device_get_name(mdev->usb_dev), str_error(rc));
 		rc = EIO;
@@ -281,5 +280,5 @@
 	rc = usbmast_read_capacity(mfun, &nblocks, &block_size);
 	if (rc != EOK) {
-		usb_log_warning("Failed to read capacity, device `%s': %s.\n",
+		usb_log_warning("Failed to read capacity, device `%s': %s.",
 		    usb_device_get_name(mdev->usb_dev), str_error(rc));
 		rc = EIO;
@@ -295,5 +294,5 @@
 	rc = ddf_fun_bind(fun);
 	if (rc != EOK) {
-		usb_log_error("Failed to bind DDF function %s: %s.\n",
+		usb_log_error("Failed to bind DDF function %s: %s.",
 		    fun_name, str_error(rc));
 		goto error;
@@ -390,5 +389,5 @@
 static const usb_driver_ops_t usbmast_driver_ops = {
 	.device_add = usbmast_device_add,
-	.device_rem = usbmast_device_remove,
+	.device_remove = usbmast_device_remove,
 	.device_gone = usbmast_device_gone,
 };
Index: uspace/drv/block/usbmast/scsi_ms.c
===================================================================
--- uspace/drv/block/usbmast/scsi_ms.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/block/usbmast/scsi_ms.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -116,5 +116,5 @@
 		rc = usb_massstor_unit_ready(mfun);
 		if (rc != EOK) {
-			usb_log_error("Inquiry transport failed, device %s: %s.\n",
+			usb_log_error("Inquiry transport failed, device %s: %s.",
 			   usb_device_get_name(mfun->mdev->usb_dev), str_error(rc));
 			return rc;
@@ -123,5 +123,5 @@
 		rc = usb_massstor_cmd(mfun, 0xDEADBEEF, cmd);
 		if (rc != EOK) {
-			usb_log_error("Inquiry transport failed, device %s: %s.\n",
+			usb_log_error("Inquiry transport failed, device %s: %s.",
 			   usb_device_get_name(mfun->mdev->usb_dev), str_error(rc));
 			return rc;
@@ -131,10 +131,10 @@
 			return EOK;
 
-		usb_log_error("SCSI command failed, device %s.\n",
+		usb_log_error("SCSI command failed, device %s.",
 		    usb_device_get_name(mfun->mdev->usb_dev));
 
 		rc = usbmast_request_sense(mfun, &sense_buf, sizeof(sense_buf));
 		if (rc != EOK) {
-			usb_log_error("Failed to read sense data.\n");
+			usb_log_error("Failed to read sense data.");
 			return EIO;
 		}
@@ -182,5 +182,5 @@
 
 	if (rc != EOK) {
-		usb_log_error("Inquiry transport failed, device %s: %s.\n",
+		usb_log_error("Inquiry transport failed, device %s: %s.",
 		   usb_device_get_name(mfun->mdev->usb_dev), str_error(rc));
 		return rc;
@@ -188,5 +188,5 @@
 
 	if (cmd.status != CMDS_GOOD) {
-		usb_log_error("Inquiry command failed, device %s.\n",
+		usb_log_error("Inquiry command failed, device %s.",
 		   usb_device_get_name(mfun->mdev->usb_dev));
 		return EIO;
@@ -194,5 +194,5 @@
 
 	if (cmd.rcvd_size < SCSI_STD_INQUIRY_DATA_MIN_SIZE) {
-		usb_log_error("SCSI Inquiry response too short (%zu).\n",
+		usb_log_error("SCSI Inquiry response too short (%zu).",
 		    cmd.rcvd_size);
 		return EIO;
@@ -250,5 +250,5 @@
 
         if (rc != EOK || cmd.status != CMDS_GOOD) {
-		usb_log_error("Request Sense failed, device %s: %s.\n",
+		usb_log_error("Request Sense failed, device %s: %s.",
 		   usb_device_get_name(mfun->mdev->usb_dev), str_error(rc));
 		return rc;
@@ -292,5 +292,5 @@
 
         if (rc != EOK) {
-		usb_log_error("Read Capacity (10) transport failed, device %s: %s.\n",
+		usb_log_error("Read Capacity (10) transport failed, device %s: %s.",
 		   usb_device_get_name(mfun->mdev->usb_dev), str_error(rc));
 		return rc;
@@ -298,5 +298,5 @@
 
 	if (cmd.status != CMDS_GOOD) {
-		usb_log_error("Read Capacity (10) command failed, device %s.\n",
+		usb_log_error("Read Capacity (10) command failed, device %s.",
 		   usb_device_get_name(mfun->mdev->usb_dev));
 		return EIO;
@@ -304,5 +304,5 @@
 
 	if (cmd.rcvd_size < sizeof(data)) {
-		usb_log_error("SCSI Read Capacity response too short (%zu).\n",
+		usb_log_error("SCSI Read Capacity response too short (%zu).",
 		    cmd.rcvd_size);
 		return EIO;
@@ -349,5 +349,5 @@
 
         if (rc != EOK) {
-		usb_log_error("Read (10) transport failed, device %s: %s.\n",
+		usb_log_error("Read (10) transport failed, device %s: %s.",
 		   usb_device_get_name(mfun->mdev->usb_dev), str_error(rc));
 		return rc;
@@ -355,5 +355,5 @@
 
 	if (cmd.status != CMDS_GOOD) {
-		usb_log_error("Read (10) command failed, device %s.\n",
+		usb_log_error("Read (10) command failed, device %s.",
 		   usb_device_get_name(mfun->mdev->usb_dev));
 		return EIO;
@@ -361,5 +361,5 @@
 
 	if (cmd.rcvd_size < nblocks * mfun->block_size) {
-		usb_log_error("SCSI Read response too short (%zu).\n",
+		usb_log_error("SCSI Read response too short (%zu).",
 		    cmd.rcvd_size);
 		return EIO;
@@ -405,5 +405,5 @@
 
         if (rc != EOK) {
-		usb_log_error("Write (10) transport failed, device %s: %s.\n",
+		usb_log_error("Write (10) transport failed, device %s: %s.",
 		   usb_device_get_name(mfun->mdev->usb_dev), str_error(rc));
 		return rc;
@@ -411,5 +411,5 @@
 
 	if (cmd.status != CMDS_GOOD) {
-		usb_log_error("Write (10) command failed, device %s.\n",
+		usb_log_error("Write (10) command failed, device %s.",
 		   usb_device_get_name(mfun->mdev->usb_dev));
 		return EIO;
@@ -450,5 +450,5 @@
 
         if (rc != EOK) {
-		usb_log_error("Synchronize Cache (10) transport failed, device %s: %s.\n",
+		usb_log_error("Synchronize Cache (10) transport failed, device %s: %s.",
 		   usb_device_get_name(mfun->mdev->usb_dev), str_error(rc));
 		return rc;
@@ -456,5 +456,5 @@
 
 	if (cmd.status != CMDS_GOOD) {
-		usb_log_error("Synchronize Cache (10) command failed, device %s.\n",
+		usb_log_error("Synchronize Cache (10) command failed, device %s.",
 		   usb_device_get_name(mfun->mdev->usb_dev));
 		return EIO;
Index: uspace/drv/bus/usb/ehci/Makefile
===================================================================
--- uspace/drv/bus/usb/ehci/Makefile	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ehci/Makefile	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -35,5 +35,5 @@
 SOURCES = \
 	ehci_batch.c \
-	ehci_endpoint.c \
+	ehci_bus.c \
 	ehci_rh.c \
 	endpoint_list.c \
Index: uspace/drv/bus/usb/ehci/ehci_batch.c
===================================================================
--- uspace/drv/bus/usb/ehci/ehci_batch.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ehci/ehci_batch.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -42,8 +42,7 @@
 #include <usb/usb.h>
 #include <usb/debug.h>
-#include <usb/host/utils/malloc32.h>
 
 #include "ehci_batch.h"
-#include "ehci_endpoint.h"
+#include "ehci_bus.h"
 
 /* The buffer pointer list in the qTD is long enough to support a maximum
@@ -53,5 +52,5 @@
 #define EHCI_TD_MAX_TRANSFER   (16 * 1024)
 
-static void (*const batch_setup[])(ehci_transfer_batch_t*, usb_direction_t);
+static void (*const batch_setup[])(ehci_transfer_batch_t*);
 
 /** Safely destructs ehci_transfer_batch_t structure
@@ -59,31 +58,10 @@
  * @param[in] ehci_batch Instance to destroy.
  */
-static void ehci_transfer_batch_dispose(ehci_transfer_batch_t *ehci_batch)
-{
-	if (!ehci_batch)
-		return;
-	if (ehci_batch->tds) {
-		for (size_t i = 0; i < ehci_batch->td_count; ++i) {
-			free32(ehci_batch->tds[i]);
-		}
-		free(ehci_batch->tds);
-	}
-	usb_transfer_batch_destroy(ehci_batch->usb_batch);
-	free32(ehci_batch->device_buffer);
+void ehci_transfer_batch_destroy(ehci_transfer_batch_t *ehci_batch)
+{
+	assert(ehci_batch);
+	dma_buffer_free(&ehci_batch->dma_buffer);
+	usb_log_debug2("Batch(%p): disposed", ehci_batch);
 	free(ehci_batch);
-	usb_log_debug2("Batch(%p): disposed", ehci_batch);
-}
-
-/** Finishes usb_transfer_batch and destroys the structure.
- *
- * @param[in] uhci_batch Instance to finish and destroy.
- */
-void ehci_transfer_batch_finish_dispose(ehci_transfer_batch_t *ehci_batch)
-{
-	assert(ehci_batch);
-	assert(ehci_batch->usb_batch);
-	usb_transfer_batch_finish(ehci_batch->usb_batch,
-	    ehci_batch->device_buffer + ehci_batch->usb_batch->setup_size);
-	ehci_transfer_batch_dispose(ehci_batch);
 }
 
@@ -94,83 +72,79 @@
  * NULL otherwise.
  *
+ */
+ehci_transfer_batch_t * ehci_transfer_batch_create(endpoint_t *ep)
+{
+	assert(ep);
+
+	ehci_transfer_batch_t *ehci_batch = calloc(1, sizeof(ehci_transfer_batch_t));
+	if (!ehci_batch) {
+		usb_log_error("Failed to allocate EHCI batch data.");
+		return NULL;
+	}
+
+	usb_transfer_batch_init(&ehci_batch->base, ep);
+
+	return ehci_batch;
+}
+
+/** Prepares a batch to be sent.
+ *
  * Determines the number of needed transfer descriptors (TDs).
  * Prepares a transport buffer (that is accessible by the hardware).
  * Initializes parameters needed for the transfer and callback.
  */
-ehci_transfer_batch_t * ehci_transfer_batch_get(usb_transfer_batch_t *usb_batch)
-{
-	assert(usb_batch);
-
-	ehci_transfer_batch_t *ehci_batch =
-	    calloc(1, sizeof(ehci_transfer_batch_t));
-	if (!ehci_batch) {
-		usb_log_error("Batch %p: Failed to allocate EHCI batch data.",
-		    usb_batch);
-		goto dispose;
-	}
-	link_initialize(&ehci_batch->link);
-	ehci_batch->td_count =
-	    (usb_batch->buffer_size + EHCI_TD_MAX_TRANSFER - 1)
-	    / EHCI_TD_MAX_TRANSFER;
+int ehci_transfer_batch_prepare(ehci_transfer_batch_t *ehci_batch)
+{
+	assert(ehci_batch);
+
+	const size_t setup_size = (ehci_batch->base.ep->transfer_type == USB_TRANSFER_CONTROL)
+		? USB_SETUP_PACKET_SIZE
+		: 0;
+
+	const size_t size = ehci_batch->base.buffer_size;
+
+	/* Add TD left over by the previous transfer */
+	ehci_batch->qh = ehci_endpoint_get(ehci_batch->base.ep)->qh;
+
+	/* Determine number of TDs needed */
+	ehci_batch->td_count = (size + EHCI_TD_MAX_TRANSFER - 1)
+		/ EHCI_TD_MAX_TRANSFER;
 
 	/* Control transfer need Setup and Status stage */
-	if (usb_batch->ep->transfer_type == USB_TRANSFER_CONTROL) {
+	if (ehci_batch->base.ep->transfer_type == USB_TRANSFER_CONTROL) {
 		ehci_batch->td_count += 2;
 	}
 
-	ehci_batch->tds = calloc(ehci_batch->td_count, sizeof(td_t*));
-	if (!ehci_batch->tds) {
-		usb_log_error("Batch %p: Failed to allocate EHCI transfer "
-		    "descriptors.", usb_batch);
-		goto dispose;
-	}
-
-	/* Add TD left over by the previous transfer */
-	ehci_batch->qh = ehci_endpoint_get(usb_batch->ep)->qh;
-
-	for (unsigned i = 0; i < ehci_batch->td_count; ++i) {
-		ehci_batch->tds[i] = malloc32(sizeof(td_t));
-		if (!ehci_batch->tds[i]) {
-			usb_log_error("Batch %p: Failed to allocate TD %d.",
-			    usb_batch, i);
-			goto dispose;
+	const size_t tds_size = ehci_batch->td_count * sizeof(td_t);
+
+	/* Mix setup stage, data and TDs together, we have enough space */
+	if (size + setup_size + tds_size > 0) {
+		if (dma_buffer_alloc(&ehci_batch->dma_buffer, tds_size + setup_size + size)) {
+			usb_log_error("Batch %p: Failed to allocate device "
+			    "buffer", ehci_batch);
+			return ENOMEM;
 		}
-		memset(ehci_batch->tds[i], 0, sizeof(td_t));
-	}
-
-
-	/* Mix setup stage and data together, we have enough space */
-        if (usb_batch->setup_size + usb_batch->buffer_size > 0) {
-		/* Use one buffer for setup and data stage */
-		ehci_batch->device_buffer =
-		    malloc32(usb_batch->setup_size + usb_batch->buffer_size);
-		if (!ehci_batch->device_buffer) {
-			usb_log_error("Batch %p: Failed to allocate device "
-			    "buffer", usb_batch);
-			goto dispose;
-		}
+		/* Clean TDs */
+		ehci_batch->tds = ehci_batch->dma_buffer.virt;
+		memset(ehci_batch->tds, 0, tds_size);
 		/* Copy setup data */
-                memcpy(ehci_batch->device_buffer, usb_batch->setup_buffer,
-		    usb_batch->setup_size);
+		ehci_batch->setup_buffer = ehci_batch->dma_buffer.virt + tds_size;
+                memcpy(ehci_batch->setup_buffer, ehci_batch->base.setup.buffer, setup_size);
 		/* Copy generic data */
-		if (usb_batch->ep->direction != USB_DIRECTION_IN)
-			memcpy(
-			    ehci_batch->device_buffer + usb_batch->setup_size,
-			    usb_batch->buffer, usb_batch->buffer_size);
-        }
-	ehci_batch->usb_batch = usb_batch;
-
-	const usb_direction_t dir = usb_transfer_batch_direction(usb_batch);
-	assert(batch_setup[usb_batch->ep->transfer_type]);
-	batch_setup[usb_batch->ep->transfer_type](ehci_batch, dir);
-
-	usb_log_debug("Batch %p %s " USB_TRANSFER_BATCH_FMT " initialized.\n",
-	    usb_batch, usb_str_direction(dir),
-	    USB_TRANSFER_BATCH_ARGS(*usb_batch));
-
-	return ehci_batch;
-dispose:
-	ehci_transfer_batch_dispose(ehci_batch);
-	return NULL;
+		ehci_batch->data_buffer = ehci_batch->setup_buffer + setup_size;
+		if (ehci_batch->base.dir != USB_DIRECTION_IN)
+			memcpy(ehci_batch->data_buffer,
+			    ehci_batch->base.buffer,
+			    ehci_batch->base.buffer_size);
+	}
+
+	assert(batch_setup[ehci_batch->base.ep->transfer_type]);
+	batch_setup[ehci_batch->base.ep->transfer_type](ehci_batch);
+
+	usb_log_debug("Batch %p %s " USB_TRANSFER_BATCH_FMT " initialized.",
+	    ehci_batch, usb_str_direction(ehci_batch->base.dir),
+	    USB_TRANSFER_BATCH_ARGS(ehci_batch->base));
+
+	return EOK;
 }
 
@@ -184,14 +158,13 @@
  * completes with the last TD.
  */
-bool ehci_transfer_batch_is_complete(const ehci_transfer_batch_t *ehci_batch)
-{
-	assert(ehci_batch);
-	assert(ehci_batch->usb_batch);
-
-	usb_log_debug("Batch %p: checking %zu td(s) for completion.\n",
-	    ehci_batch->usb_batch, ehci_batch->td_count);
-
-	usb_log_debug2("Batch %p: QH: %08x:%08x:%08x:%08x:%08x:%08x.\n",
-	    ehci_batch->usb_batch,
+bool ehci_transfer_batch_check_completed(ehci_transfer_batch_t *ehci_batch)
+{
+	assert(ehci_batch);
+
+	usb_log_debug("Batch %p: checking %zu td(s) for completion.",
+	    ehci_batch, ehci_batch->td_count);
+
+	usb_log_debug2("Batch %p: QH: %08x:%08x:%08x:%08x:%08x:%08x.",
+	    ehci_batch,
 	    ehci_batch->qh->ep_char, ehci_batch->qh->ep_cap,
 	    ehci_batch->qh->status, ehci_batch->qh->current,
@@ -206,17 +179,15 @@
 
 	/* Assume all data got through */
-	ehci_batch->usb_batch->transfered_size =
-	    ehci_batch->usb_batch->buffer_size;
+	ehci_batch->base.transferred_size = ehci_batch->base.buffer_size;
 
 	/* Check all TDs */
 	for (size_t i = 0; i < ehci_batch->td_count; ++i) {
-		assert(ehci_batch->tds[i] != NULL);
 		usb_log_debug("Batch %p: TD %zu: %08x:%08x:%08x.",
-		    ehci_batch->usb_batch, i,
-		    ehci_batch->tds[i]->status, ehci_batch->tds[i]->next,
-		    ehci_batch->tds[i]->alternate);
-
-		ehci_batch->usb_batch->error = td_error(ehci_batch->tds[i]);
-		if (ehci_batch->usb_batch->error == EOK) {
+		    ehci_batch, i,
+		    ehci_batch->tds[i].status, ehci_batch->tds[i].next,
+		    ehci_batch->tds[i].alternate);
+
+		ehci_batch->base.error = td_error(&ehci_batch->tds[i]);
+		if (ehci_batch->base.error == EOK) {
 			/* If the TD got all its data through, it will report
 			 * 0 bytes remain, the sole exception is INPUT with
@@ -231,11 +202,11 @@
 			 * we leave the very last(unused) TD behind.
 			 */
-			ehci_batch->usb_batch->transfered_size
-			    -= td_remain_size(ehci_batch->tds[i]);
+			ehci_batch->base.transferred_size
+			    -= td_remain_size(&ehci_batch->tds[i]);
 		} else {
 			usb_log_debug("Batch %p found error TD(%zu):%08x: %s.",
-			    ehci_batch->usb_batch, i,
-			    ehci_batch->tds[i]->status,
-			    str_error_name(ehci_batch->usb_batch->error));
+			    ehci_batch, i,
+			    ehci_batch->tds[i].status,
+			    str_error_name(ehci_batch->base.error));
 			/* Clear possible ED HALT */
 			qh_clear_halt(ehci_batch->qh);
@@ -244,11 +215,16 @@
 	}
 
-	assert(ehci_batch->usb_batch->transfered_size <=
-	    ehci_batch->usb_batch->buffer_size);
+	assert(ehci_batch->base.transferred_size <= ehci_batch->base.buffer_size);
+
+	if (ehci_batch->base.dir == USB_DIRECTION_IN)
+		memcpy(ehci_batch->base.buffer,
+		    ehci_batch->data_buffer,
+		    ehci_batch->base.transferred_size);
+
 	/* Clear TD pointers */
 	ehci_batch->qh->next = LINK_POINTER_TERM;
 	ehci_batch->qh->current = LINK_POINTER_TERM;
-	usb_log_debug("Batch %p complete: %s", ehci_batch->usb_batch,
-	    str_error(ehci_batch->usb_batch->error));
+	usb_log_debug("Batch %p complete: %s", ehci_batch,
+	    str_error(ehci_batch->base.error));
 
 	return true;
@@ -262,5 +238,5 @@
 {
 	assert(ehci_batch);
-	qh_set_next_td(ehci_batch->qh, ehci_batch->tds[0]);
+	qh_set_next_td(ehci_batch->qh, dma_buffer_phys(&ehci_batch->dma_buffer, &ehci_batch->tds[0]));
 }
 
@@ -268,19 +244,19 @@
  *
  * @param[in] ehci_batch Batch structure to use.
- * @param[in] dir Communication direction
  *
  * Setup stage with toggle 0 and direction BOTH(SETUP_PID)
- * Data stage with alternating toggle and direction supplied by parameter.
- * Status stage with toggle 1 and direction supplied by parameter.
- */
-static void batch_control(ehci_transfer_batch_t *ehci_batch, usb_direction_t dir)
-{
-	assert(ehci_batch);
-	assert(ehci_batch->usb_batch);
+ * Data stage with alternating toggle and direction
+ * Status stage with toggle 1 and direction
+ */
+static void batch_control(ehci_transfer_batch_t *ehci_batch)
+{
+	assert(ehci_batch);
+
+	usb_direction_t dir = ehci_batch->base.dir;
 	assert(dir == USB_DIRECTION_IN || dir == USB_DIRECTION_OUT);
 
-	usb_log_debug2("Batch %p: Control QH(%"PRIxn"): "
-	    "%08x:%08x:%08x:%08x:%08x:%08x", ehci_batch->usb_batch,
-	    addr_to_phys(ehci_batch->qh),
+	usb_log_debug2("Batch %p: Control QH(%p): "
+	    "%08x:%08x:%08x:%08x:%08x:%08x", ehci_batch,
+	    ehci_batch->qh,
 	    ehci_batch->qh->ep_char, ehci_batch->qh->ep_cap,
 	    ehci_batch->qh->status, ehci_batch->qh->current,
@@ -292,35 +268,35 @@
 
 	int toggle = 0;
-	const char* buffer = ehci_batch->device_buffer;
 	const usb_direction_t data_dir = dir;
 	const usb_direction_t status_dir = reverse_dir[dir];
 
 	/* Setup stage */
-	td_init(ehci_batch->tds[0], ehci_batch->tds[1], USB_DIRECTION_BOTH,
-	    buffer, ehci_batch->usb_batch->setup_size, toggle, false);
+	td_init(&ehci_batch->tds[0],
+	    dma_buffer_phys(&ehci_batch->dma_buffer, &ehci_batch->tds[1]),
+	    dma_buffer_phys(&ehci_batch->dma_buffer, ehci_batch->setup_buffer),
+	    USB_DIRECTION_BOTH, USB_SETUP_PACKET_SIZE, toggle, false);
 	usb_log_debug2("Batch %p: Created CONTROL SETUP TD(%"PRIxn"): "
-	    "%08x:%08x:%08x", ehci_batch->usb_batch,
-	    addr_to_phys(ehci_batch->tds[0]),
-	    ehci_batch->tds[0]->status, ehci_batch->tds[0]->next,
-	    ehci_batch->tds[0]->alternate);
-	buffer += ehci_batch->usb_batch->setup_size;
+	    "%08x:%08x:%08x", ehci_batch,
+	    dma_buffer_phys(&ehci_batch->dma_buffer, &ehci_batch->tds[0]),
+	    ehci_batch->tds[0].status, ehci_batch->tds[0].next,
+	    ehci_batch->tds[0].alternate);
 
 	/* Data stage */
-	size_t td_current = 1;
-	size_t remain_size = ehci_batch->usb_batch->buffer_size;
+	unsigned td_current = 1;
+	size_t remain_size = ehci_batch->base.buffer_size;
+	uintptr_t buffer = dma_buffer_phys(&ehci_batch->dma_buffer, ehci_batch->data_buffer);
 	while (remain_size > 0) {
-		const size_t transfer_size =
-		    min(remain_size, EHCI_TD_MAX_TRANSFER);
+		const size_t transfer_size = min(remain_size, EHCI_TD_MAX_TRANSFER);
 		toggle = 1 - toggle;
 
-		td_init(ehci_batch->tds[td_current],
-		    ehci_batch->tds[td_current + 1], data_dir, buffer,
-		    transfer_size, toggle, false);
+		td_init(&ehci_batch->tds[td_current],
+		    dma_buffer_phys(&ehci_batch->dma_buffer, &ehci_batch->tds[td_current + 1]),
+		    buffer, data_dir, transfer_size, toggle, false);
 		usb_log_debug2("Batch %p: Created CONTROL DATA TD(%"PRIxn"): "
-		    "%08x:%08x:%08x", ehci_batch->usb_batch,
-		    addr_to_phys(ehci_batch->tds[td_current]),
-		    ehci_batch->tds[td_current]->status,
-		    ehci_batch->tds[td_current]->next,
-		    ehci_batch->tds[td_current]->alternate);
+		    "%08x:%08x:%08x", ehci_batch,
+		    dma_buffer_phys(&ehci_batch->dma_buffer, &ehci_batch->tds[td_current]),
+		    ehci_batch->tds[td_current].status,
+		    ehci_batch->tds[td_current].next,
+		    ehci_batch->tds[td_current].alternate);
 
 		buffer += transfer_size;
@@ -332,11 +308,11 @@
 	/* Status stage */
 	assert(td_current == ehci_batch->td_count - 1);
-	td_init(ehci_batch->tds[td_current], NULL, status_dir, NULL, 0, 1, true);
-	usb_log_debug2("Batch %p: Created CONTROL STATUS TD(%"PRIxn"): "
-	    "%08x:%08x:%08x", ehci_batch->usb_batch,
-	    addr_to_phys(ehci_batch->tds[td_current]),
-	    ehci_batch->tds[td_current]->status,
-	    ehci_batch->tds[td_current]->next,
-	    ehci_batch->tds[td_current]->alternate);
+	td_init(&ehci_batch->tds[td_current], 0, 0, status_dir, 0, 1, true);
+	usb_log_debug2("Batch %p: Created CONTROL STATUS TD %d(%"PRIxn"): "
+	    "%08x:%08x:%08x", ehci_batch, td_current,
+	    dma_buffer_phys(&ehci_batch->dma_buffer, &ehci_batch->tds[td_current]),
+	    ehci_batch->tds[td_current].status,
+	    ehci_batch->tds[td_current].next,
+	    ehci_batch->tds[td_current].alternate);
 }
 
@@ -349,13 +325,11 @@
  * EHCI hw in ED.
  */
-static void batch_data(ehci_transfer_batch_t *ehci_batch, usb_direction_t dir)
-{
-	assert(ehci_batch);
-	assert(ehci_batch->usb_batch);
-	assert(dir == USB_DIRECTION_IN || dir == USB_DIRECTION_OUT);
-
-	usb_log_debug2("Batch %p: Data QH(%"PRIxn"): "
-	    "%08x:%08x:%08x:%08x:%08x:%08x", ehci_batch->usb_batch,
-	    addr_to_phys(ehci_batch->qh),
+static void batch_data(ehci_transfer_batch_t *ehci_batch)
+{
+	assert(ehci_batch);
+
+	usb_log_debug2("Batch %p: Data QH(%p): "
+	    "%08x:%08x:%08x:%08x:%08x:%08x", ehci_batch,
+	    ehci_batch->qh,
 	    ehci_batch->qh->ep_char, ehci_batch->qh->ep_cap,
 	    ehci_batch->qh->status, ehci_batch->qh->current,
@@ -363,6 +337,6 @@
 
 	size_t td_current = 0;
-	size_t remain_size = ehci_batch->usb_batch->buffer_size;
-	char *buffer = ehci_batch->device_buffer;
+	size_t remain_size = ehci_batch->base.buffer_size;
+	uintptr_t buffer = dma_buffer_phys(&ehci_batch->dma_buffer, ehci_batch->data_buffer);
 	while (remain_size > 0) {
 		const size_t transfer_size = remain_size > EHCI_TD_MAX_TRANSFER
@@ -370,15 +344,14 @@
 
 		const bool last = (remain_size == transfer_size);
-		td_init(
-		    ehci_batch->tds[td_current],
-		    last ? NULL : ehci_batch->tds[td_current + 1],
-		    dir, buffer, transfer_size, -1, last);
+		td_init(&ehci_batch->tds[td_current],
+		    last ? 0 : dma_buffer_phys(&ehci_batch->dma_buffer, &ehci_batch->tds[td_current + 1]),
+		    buffer, ehci_batch->base.dir, transfer_size, -1, last);
 
 		usb_log_debug2("Batch %p: DATA TD(%"PRIxn": %08x:%08x:%08x",
-		    ehci_batch->usb_batch,
-		    addr_to_phys(ehci_batch->tds[td_current]),
-		    ehci_batch->tds[td_current]->status,
-		    ehci_batch->tds[td_current]->next,
-		    ehci_batch->tds[td_current]->alternate);
+		    ehci_batch,
+		    dma_buffer_phys(&ehci_batch->dma_buffer, &ehci_batch->tds[td_current]),
+		    ehci_batch->tds[td_current].status,
+		    ehci_batch->tds[td_current].next,
+		    ehci_batch->tds[td_current].alternate);
 
 		buffer += transfer_size;
@@ -390,5 +363,5 @@
 
 /** Transfer setup table. */
-static void (*const batch_setup[])(ehci_transfer_batch_t*, usb_direction_t) =
+static void (*const batch_setup[])(ehci_transfer_batch_t*) =
 {
 	[USB_TRANSFER_CONTROL] = batch_control,
Index: uspace/drv/bus/usb/ehci/ehci_batch.h
===================================================================
--- uspace/drv/bus/usb/ehci/ehci_batch.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ehci/ehci_batch.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -39,4 +39,5 @@
 #include <stdbool.h>
 #include <usb/host/usb_transfer_batch.h>
+#include <usb/host/dma_buffer.h>
 
 #include "hw_struct/queue_head.h"
@@ -45,28 +46,33 @@
 /** EHCI specific data required for USB transfer */
 typedef struct ehci_transfer_batch {
-	/** Link */
-	link_t link;
+	usb_transfer_batch_t base;
+	/** Number of TDs used by the transfer */
+	size_t td_count;
 	/** Endpoint descriptor of the target endpoint. */
 	qh_t *qh;
-	/** 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 EHCI hw. */
-	char *device_buffer;
+	dma_buffer_t dma_buffer;
+	/** List of TDs needed for the transfer - backed by dma_buffer */
+	td_t *tds;
+	/** Data buffers - backed by dma_buffer */
+	void *setup_buffer;
+	void *data_buffer;
 	/** Generic USB transfer structure */
 	usb_transfer_batch_t *usb_batch;
 } ehci_transfer_batch_t;
 
-ehci_transfer_batch_t * ehci_transfer_batch_get(usb_transfer_batch_t *batch);
-bool ehci_transfer_batch_is_complete(const ehci_transfer_batch_t *batch);
+ehci_transfer_batch_t * ehci_transfer_batch_create(endpoint_t *ep);
+int ehci_transfer_batch_prepare(ehci_transfer_batch_t *batch);
 void ehci_transfer_batch_commit(const ehci_transfer_batch_t *batch);
-void ehci_transfer_batch_finish_dispose(ehci_transfer_batch_t *batch);
+bool ehci_transfer_batch_check_completed(ehci_transfer_batch_t *batch);
+void ehci_transfer_batch_destroy(ehci_transfer_batch_t *batch);
 
-static inline ehci_transfer_batch_t *ehci_transfer_batch_from_link(link_t *l)
+static inline ehci_transfer_batch_t * ehci_transfer_batch_get(usb_transfer_batch_t *usb_batch)
 {
-	assert(l);
-	return list_get_instance(l, ehci_transfer_batch_t, link);
+	assert(usb_batch);
+
+	return (ehci_transfer_batch_t *) usb_batch;
 }
+
 #endif
 /**
Index: uspace/drv/bus/usb/ehci/ehci_bus.c
===================================================================
--- uspace/drv/bus/usb/ehci/ehci_bus.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/ehci/ehci_bus.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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 drvusbehci
+ * @{
+ */
+/** @file
+ * @brief EHCI driver
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <usb/host/bandwidth.h>
+#include <usb/debug.h>
+
+#include "ehci_bus.h"
+#include "ehci_batch.h"
+#include "hc.h"
+
+/**
+ * Callback to set toggle on ED.
+ *
+ * @param[in] hcd_ep hcd endpoint structure
+ * @param[in] toggle new value of toggle bit
+ */
+void ehci_ep_toggle_reset(endpoint_t *ep)
+{
+	ehci_endpoint_t *instance = ehci_endpoint_get(ep);
+	if (qh_toggle_from_td(instance->qh))
+		usb_log_warning("EP(%p): Resetting toggle bit for transfer directed EP", instance);
+	qh_toggle_set(instance->qh, 0);
+}
+
+static int ehci_device_enumerate(device_t *dev)
+{
+	ehci_bus_t *bus = (ehci_bus_t *) dev->bus;
+	return usb2_bus_device_enumerate(&bus->helper, dev);
+}
+
+static void ehci_device_gone(device_t *dev)
+{
+	ehci_bus_t *bus = (ehci_bus_t *) dev->bus;
+	usb2_bus_device_gone(&bus->helper, dev);
+}
+
+/** Creates new hcd endpoint representation.
+ */
+static endpoint_t *ehci_endpoint_create(device_t *dev, const usb_endpoint_descriptors_t *desc)
+{
+	assert(dev);
+
+	ehci_endpoint_t *ehci_ep = malloc(sizeof(ehci_endpoint_t));
+	if (ehci_ep == NULL)
+		return NULL;
+
+	endpoint_init(&ehci_ep->base, dev, desc);
+
+	if (dma_buffer_alloc(&ehci_ep->dma_buffer, sizeof(qh_t)))
+		return NULL;
+
+	ehci_ep->qh = ehci_ep->dma_buffer.virt;
+
+	link_initialize(&ehci_ep->eplist_link);
+	link_initialize(&ehci_ep->pending_link);
+	return &ehci_ep->base;
+}
+
+/** Disposes hcd endpoint structure
+ *
+ * @param[in] hcd driver using this instance.
+ * @param[in] ep endpoint structure.
+ */
+static void ehci_endpoint_destroy(endpoint_t *ep)
+{
+	assert(ep);
+	ehci_endpoint_t *instance = ehci_endpoint_get(ep);
+
+	dma_buffer_free(&instance->dma_buffer);
+	free(instance);
+}
+
+
+static int ehci_register_ep(endpoint_t *ep)
+{
+	bus_t *bus_base = endpoint_get_bus(ep);
+	ehci_bus_t *bus = (ehci_bus_t *) bus_base;
+	ehci_endpoint_t *ehci_ep = ehci_endpoint_get(ep);
+
+	const int err = usb2_bus_endpoint_register(&bus->helper, ep);
+	if (err)
+		return err;
+
+	qh_init(ehci_ep->qh, ep);
+	hc_enqueue_endpoint(bus->hc, ep);
+	endpoint_set_online(ep, &bus->hc->guard);
+	return EOK;
+}
+
+static void ehci_unregister_ep(endpoint_t *ep)
+{
+	bus_t *bus_base = endpoint_get_bus(ep);
+	ehci_bus_t *bus = (ehci_bus_t *) bus_base;
+	hc_t *hc = bus->hc;
+	assert(bus);
+	assert(ep);
+
+	usb2_bus_endpoint_unregister(&bus->helper, ep);
+	hc_dequeue_endpoint(hc, ep);
+	/*
+	 * Now we can be sure the active transfer will not be completed,
+	 * as it's out of the schedule, and HC acknowledged it.
+	 */
+
+	ehci_endpoint_t *ehci_ep = ehci_endpoint_get(ep);
+
+	fibril_mutex_lock(&hc->guard);
+	endpoint_set_offline_locked(ep);
+	list_remove(&ehci_ep->pending_link);
+	usb_transfer_batch_t * const batch = ep->active_batch;
+	endpoint_deactivate_locked(ep);
+	fibril_mutex_unlock(&hc->guard);
+
+	if (batch) {
+		batch->error = EINTR;
+		batch->transferred_size = 0;
+		usb_transfer_batch_finish(batch);
+	}
+}
+
+static usb_transfer_batch_t *ehci_create_batch(endpoint_t *ep)
+{
+	ehci_transfer_batch_t *batch = ehci_transfer_batch_create(ep);
+	return &batch->base;
+}
+
+static void ehci_destroy_batch(usb_transfer_batch_t *batch)
+{
+	ehci_transfer_batch_destroy(ehci_transfer_batch_get(batch));
+}
+
+static const bus_ops_t ehci_bus_ops = {
+	.interrupt = ehci_hc_interrupt,
+	.status = ehci_hc_status,
+
+	.device_enumerate = ehci_device_enumerate,
+	.device_gone = ehci_device_gone,
+
+	.endpoint_destroy = ehci_endpoint_destroy,
+	.endpoint_create = ehci_endpoint_create,
+	.endpoint_register = ehci_register_ep,
+	.endpoint_unregister = ehci_unregister_ep,
+
+	.batch_create = ehci_create_batch,
+	.batch_destroy = ehci_destroy_batch,
+	.batch_schedule = ehci_hc_schedule,
+};
+
+int ehci_bus_init(ehci_bus_t *bus, hc_t *hc)
+{
+	assert(hc);
+	assert(bus);
+
+	bus_t *bus_base = (bus_t *) bus;
+	bus_init(bus_base, sizeof(device_t));
+	bus_base->ops = &ehci_bus_ops;
+
+	usb2_bus_helper_init(&bus->helper, &bandwidth_accounting_usb2);
+
+	bus->hc = hc;
+
+	return EOK;
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/ehci/ehci_bus.h
===================================================================
--- uspace/drv/bus/usb/ehci/ehci_bus.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/ehci/ehci_bus.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * Copyright (c) 2017 Ondrej Hlavaty <aearsis@eideo.cz>
+ * 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 drvusbehci
+ * @{
+ */
+/** @file
+ * @brief EHCI driver
+ */
+#ifndef DRV_EHCI_HCD_BUS_H
+#define DRV_EHCI_HCD_BUS_H
+
+#include <assert.h>
+#include <adt/list.h>
+#include <usb/host/usb2_bus.h>
+#include <usb/host/endpoint.h>
+#include <usb/host/dma_buffer.h>
+
+#include "hw_struct/queue_head.h"
+
+/** Connector structure linking ED to to prepared TD. */
+typedef struct ehci_endpoint {
+	/* Inheritance */
+	endpoint_t base;
+
+	/** EHCI endpoint descriptor, backed by dma_buffer */
+	qh_t *qh;
+	
+	dma_buffer_t dma_buffer;
+
+	/** Link in endpoint_list */
+	link_t eplist_link;
+
+	/** Link in pending_endpoints */
+	link_t pending_link;
+} ehci_endpoint_t;
+
+typedef struct hc hc_t;
+
+typedef struct {
+	bus_t base;
+	usb2_bus_helper_t helper;
+	hc_t *hc;
+} ehci_bus_t;
+
+void ehci_ep_toggle_reset(endpoint_t *);
+void ehci_bus_prepare_ops(void);
+
+errno_t ehci_bus_init(ehci_bus_t *, hc_t *);
+
+/** Get and convert assigned ehci_endpoint_t structure
+ * @param[in] ep USBD endpoint structure.
+ * @return Pointer to assigned ehci endpoint structure
+ */
+static inline ehci_endpoint_t * ehci_endpoint_get(const endpoint_t *ep)
+{
+	assert(ep);
+	return (ehci_endpoint_t *) ep;
+}
+
+static inline ehci_endpoint_t * ehci_endpoint_list_instance(link_t *l)
+{
+	return list_get_instance(l, ehci_endpoint_t, eplist_link);
+}
+
+#endif
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/ehci/ehci_endpoint.c
===================================================================
--- uspace/drv/bus/usb/ehci/ehci_endpoint.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ 	(revision )
@@ -1,126 +1,0 @@
-/*
- * Copyright (c) 2014 Jan Vesely
- * 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 drvusbehci
- * @{
- */
-/** @file
- * @brief EHCI driver
- */
-
-#include <assert.h>
-#include <stdlib.h>
-#include <usb/debug.h>
-#include <usb/host/utils/malloc32.h>
-
-#include "ehci_endpoint.h"
-#include "hc.h"
-
-/** Callback to set toggle on ED.
- *
- * @param[in] hcd_ep hcd endpoint structure
- * @param[in] toggle new value of toggle bit
- */
-static void ehci_ep_toggle_set(void *ehci_ep, int toggle)
-{
-	ehci_endpoint_t *instance = ehci_ep;
-	assert(instance);
-	assert(instance->qh);
-	if (qh_toggle_from_td(instance->qh))
-		usb_log_warning("EP(%p): Setting toggle bit for transfer "
-		    "directed EP", instance);
-	qh_toggle_set(instance->qh, toggle);
-}
-
-/** Callback to get value of toggle bit.
- *
- * @param[in] hcd_ep hcd endpoint structure
- * @return Current value of toggle bit.
- */
-static int ehci_ep_toggle_get(void *ehci_ep)
-{
-	ehci_endpoint_t *instance = ehci_ep;
-	assert(instance);
-	assert(instance->qh);
-	if (qh_toggle_from_td(instance->qh))
-		usb_log_warning("EP(%p): Reading useless toggle bit", instance);
-	return qh_toggle_get(instance->qh);
-}
-
-/** Creates new hcd endpoint representation.
- *
- * @param[in] ep USBD endpoint structure
- * @return Error code.
- */
-errno_t ehci_endpoint_init(hcd_t *hcd, endpoint_t *ep)
-{
-	assert(ep);
-	hc_t *hc = hcd_get_driver_data(hcd);
-
-	ehci_endpoint_t *ehci_ep = malloc(sizeof(ehci_endpoint_t));
-	if (ehci_ep == NULL)
-		return ENOMEM;
-
-	ehci_ep->qh = malloc32(sizeof(qh_t));
-	if (ehci_ep->qh == NULL) {
-		free(ehci_ep);
-		return ENOMEM;
-	}
-
-	usb_log_debug2("EP(%p): Creating for %p", ehci_ep, ep);
-	link_initialize(&ehci_ep->link);
-	qh_init(ehci_ep->qh, ep);
-	endpoint_set_hc_data(
-	    ep, ehci_ep, ehci_ep_toggle_get, ehci_ep_toggle_set);
-	hc_enqueue_endpoint(hc, ep);
-	return EOK;
-}
-
-/** Disposes hcd endpoint structure
- *
- * @param[in] hcd driver using this instance.
- * @param[in] ep endpoint structure.
- */
-void ehci_endpoint_fini(hcd_t *hcd, endpoint_t *ep)
-{
-	assert(hcd);
-	assert(ep);
-	hc_t *hc = hcd_get_driver_data(hcd);
-
-	ehci_endpoint_t *instance = ehci_endpoint_get(ep);
-	hc_dequeue_endpoint(hc, ep);
-	endpoint_clear_hc_data(ep);
-	usb_log_debug2("EP(%p): Destroying for %p", instance, ep);
-	if (instance) {
-		free32(instance->qh);
-		free(instance);
-	}
-}
-/**
- * @}
- */
-
Index: uspace/drv/bus/usb/ehci/ehci_endpoint.h
===================================================================
--- uspace/drv/bus/usb/ehci/ehci_endpoint.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ 	(revision )
@@ -1,75 +1,0 @@
-/*
- * Copyright (c) 2011 Jan Vesely
- * 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 drvusbehci
- * @{
- */
-/** @file
- * @brief EHCI driver
- */
-#ifndef DRV_EHCI_HCD_ENDPOINT_H
-#define DRV_EHCI_HCD_ENDPOINT_H
-
-#include <assert.h>
-#include <adt/list.h>
-#include <usb/host/endpoint.h>
-#include <usb/host/hcd.h>
-
-#include "hw_struct/queue_head.h"
-#include "hw_struct/transfer_descriptor.h"
-
-/** Connector structure linking ED to to prepared TD. */
-typedef struct ehci_endpoint {
-	/** EHCI endpoint descriptor */
-	qh_t *qh;
-	/** Linked list used by driver software */
-	link_t link;
-} ehci_endpoint_t;
-
-errno_t ehci_endpoint_init(hcd_t *hcd, endpoint_t *ep);
-void ehci_endpoint_fini(hcd_t *hcd, endpoint_t *ep);
-
-/** Get and convert assigned ehci_endpoint_t structure
- * @param[in] ep USBD endpoint structure.
- * @return Pointer to assigned hcd endpoint structure
- */
-static inline ehci_endpoint_t * ehci_endpoint_get(const endpoint_t *ep)
-{
-	assert(ep);
-	return ep->hc_data.data;
-}
-
-static inline ehci_endpoint_t * ehci_endpoint_list_instance(link_t *l)
-{
-	return list_get_instance(l, ehci_endpoint_t, link);
-}
-
-#endif
-/**
- * @}
- */
-
Index: uspace/drv/bus/usb/ehci/ehci_rh.c
===================================================================
--- uspace/drv/bus/usb/ehci/ehci_rh.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ehci/ehci_rh.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -100,5 +100,5 @@
  */
 errno_t ehci_rh_init(ehci_rh_t *instance, ehci_caps_regs_t *caps, ehci_regs_t *regs,
-    const char *name)
+    fibril_mutex_t *guard, const char *name)
 {
 	assert(instance);
@@ -107,7 +107,7 @@
 	    (EHCI_RD(caps->hcsparams) >> EHCI_CAPS_HCS_N_PORTS_SHIFT) &
 	    EHCI_CAPS_HCS_N_PORTS_MASK;
-	usb_log_debug2("RH(%p): hcsparams: %x.\n", instance,
+	usb_log_debug2("RH(%p): hcsparams: %x.", instance,
 	    EHCI_RD(caps->hcsparams));
-	usb_log_info("RH(%p): Found %u ports.\n", instance,
+	usb_log_info("RH(%p): Found %u ports.", instance,
 	    instance->port_count);
 
@@ -127,5 +127,6 @@
 
 	ehci_rh_hub_desc_init(instance, EHCI_RD(caps->hcsparams));
-	instance->unfinished_interrupt_transfer = NULL;
+	instance->guard = guard;
+	instance->status_change_endpoint = NULL;
 
 	return virthub_base_init(&instance->base, name, &ops, instance,
@@ -144,24 +145,32 @@
 	assert(instance);
 	assert(batch);
-	const usb_target_t target = {{
-		.address = batch->ep->address,
-		.endpoint = batch->ep->endpoint,
-	}};
-	batch->error = virthub_base_request(&instance->base, target,
-	    usb_transfer_batch_direction(batch), (void*)batch->setup_buffer,
-	    batch->buffer, batch->buffer_size, &batch->transfered_size);
+	batch->error = virthub_base_request(&instance->base, batch->target,
+	    batch->dir, (void*) batch->setup.buffer,
+	    batch->buffer, batch->buffer_size, &batch->transferred_size);
 	if (batch->error == ENAK) {
 		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(instance->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(instance->guard);
 	} else {
-		usb_transfer_batch_finish(batch, NULL);
-		usb_transfer_batch_destroy(batch);
 		usb_log_debug("RH(%p): BATCH(%p) virtual request complete: %s",
 		    instance, batch, str_error(batch->error));
+		usb_transfer_batch_finish(batch);
 	}
 	return EOK;
@@ -177,20 +186,25 @@
 errno_t 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);
+	fibril_mutex_lock(instance->guard);
+	endpoint_t *ep = instance->status_change_endpoint;
+	if (!ep) {
+		fibril_mutex_unlock(instance->guard);
+		return EOK;
+	}
+
+	usb_transfer_batch_t * const batch = ep->active_batch;
+	endpoint_deactivate_locked(ep);
+	instance->status_change_endpoint = NULL;
+	fibril_mutex_unlock(instance->guard);
+
+	endpoint_del_ref(ep);
+
 	if (batch) {
-		const usb_target_t target = {{
-			.address = batch->ep->address,
-			.endpoint = batch->ep->endpoint,
-		}};
-		batch->error = virthub_base_request(&instance->base, target,
-		    usb_transfer_batch_direction(batch),
-		    (void*)batch->setup_buffer,
-		    batch->buffer, batch->buffer_size, &batch->transfered_size);
-		usb_transfer_batch_finish(batch, NULL);
-		usb_transfer_batch_destroy(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,
+		    batch->buffer, batch->buffer_size, &batch->transferred_size);
+		usb_transfer_batch_finish(batch);
 	}
 	return EOK;
@@ -258,5 +272,5 @@
 
 #define BIT_VAL(val, bit)   ((val & bit) ? 1 : 0)
-#define EHCI2USB(val, bit, feat)   (BIT_VAL(val, bit) << feat)
+#define EHCI2USB(val, bit, mask)   (BIT_VAL(val, bit) ? mask : 0)
 
 /** Port status request handler.
@@ -280,20 +294,20 @@
 	const uint32_t reg = EHCI_RD(hub->registers->portsc[port]);
 	const uint32_t status = uint32_host2usb(
-	    EHCI2USB(reg, USB_PORTSC_CONNECT_FLAG, USB_HUB_FEATURE_PORT_CONNECTION) |
-	    EHCI2USB(reg, USB_PORTSC_ENABLED_FLAG, USB_HUB_FEATURE_PORT_ENABLE) |
-	    EHCI2USB(reg, USB_PORTSC_SUSPEND_FLAG, USB_HUB_FEATURE_PORT_SUSPEND) |
-	    EHCI2USB(reg, USB_PORTSC_OC_ACTIVE_FLAG, USB_HUB_FEATURE_PORT_OVER_CURRENT) |
-	    EHCI2USB(reg, USB_PORTSC_PORT_RESET_FLAG, USB_HUB_FEATURE_PORT_RESET) |
-	    EHCI2USB(reg, USB_PORTSC_PORT_POWER_FLAG, USB_HUB_FEATURE_PORT_POWER) |
+	    EHCI2USB(reg, USB_PORTSC_CONNECT_FLAG, USB_HUB_PORT_STATUS_CONNECTION) |
+	    EHCI2USB(reg, USB_PORTSC_ENABLED_FLAG, USB_HUB_PORT_STATUS_ENABLE) |
+	    EHCI2USB(reg, USB_PORTSC_SUSPEND_FLAG, USB2_HUB_PORT_STATUS_SUSPEND) |
+	    EHCI2USB(reg, USB_PORTSC_OC_ACTIVE_FLAG, USB_HUB_PORT_STATUS_OC) |
+	    EHCI2USB(reg, USB_PORTSC_PORT_RESET_FLAG, USB_HUB_PORT_STATUS_RESET) |
+	    EHCI2USB(reg, USB_PORTSC_PORT_POWER_FLAG, USB2_HUB_PORT_STATUS_POWER) |
 	    (((reg & USB_PORTSC_LINE_STATUS_MASK) == USB_PORTSC_LINE_STATUS_K) ?
-	        (1 << USB_HUB_FEATURE_PORT_LOW_SPEED) : 0) |
-	    ((reg & USB_PORTSC_PORT_OWNER_FLAG) ? 0 : (1 << USB_HUB_FEATURE_PORT_HIGH_SPEED)) |
-	    EHCI2USB(reg, USB_PORTSC_PORT_TEST_MASK, 11) |
-	    EHCI2USB(reg, USB_PORTSC_INDICATOR_MASK, 12) |
-	    EHCI2USB(reg, USB_PORTSC_CONNECT_CH_FLAG, USB_HUB_FEATURE_C_PORT_CONNECTION) |
-	    EHCI2USB(reg, USB_PORTSC_EN_CHANGE_FLAG, USB_HUB_FEATURE_C_PORT_ENABLE) |
-	    (hub->resume_flag[port] ? (1 << USB_HUB_FEATURE_C_PORT_SUSPEND) : 0) |
-	    EHCI2USB(reg, USB_PORTSC_OC_CHANGE_FLAG, USB_HUB_FEATURE_C_PORT_OVER_CURRENT) |
-	    (hub->reset_flag[port] ? (1 << USB_HUB_FEATURE_C_PORT_RESET): 0)
+	        (USB2_HUB_PORT_STATUS_LOW_SPEED) : 0) |
+	    ((reg & USB_PORTSC_PORT_OWNER_FLAG) ? 0 : USB2_HUB_PORT_STATUS_HIGH_SPEED) |
+	    EHCI2USB(reg, USB_PORTSC_PORT_TEST_MASK, USB2_HUB_PORT_STATUS_TEST) |
+	    EHCI2USB(reg, USB_PORTSC_INDICATOR_MASK, USB2_HUB_PORT_STATUS_INDICATOR) |
+	    EHCI2USB(reg, USB_PORTSC_CONNECT_CH_FLAG, USB_HUB_PORT_STATUS_C_CONNECTION) |
+	    EHCI2USB(reg, USB_PORTSC_EN_CHANGE_FLAG, USB2_HUB_PORT_STATUS_C_ENABLE) |
+	    (hub->resume_flag[port] ? USB2_HUB_PORT_STATUS_C_SUSPEND : 0) |
+	    EHCI2USB(reg, USB_PORTSC_OC_CHANGE_FLAG, USB_HUB_PORT_STATUS_C_OC) |
+	    (hub->reset_flag[port] ? USB_HUB_PORT_STATUS_C_RESET: 0)
 	);
 	/* Note feature numbers for test and indicator feature do not
@@ -396,5 +410,5 @@
 		return EOK;
 
-	case USB_HUB_FEATURE_PORT_ENABLE:         /*1*/
+	case USB2_HUB_FEATURE_PORT_ENABLE:         /*1*/
 		usb_log_debug2("RH(%p-%u): Clear port enable.", hub, port);
 		EHCI_CLR(hub->registers->portsc[port],
@@ -402,5 +416,5 @@
 		return EOK;
 
-	case USB_HUB_FEATURE_PORT_SUSPEND:        /*2*/
+	case USB2_HUB_FEATURE_PORT_SUSPEND:        /*2*/
 		usb_log_debug2("RH(%p-%u): Clear port suspend.", hub, port);
 		/* If not in suspend it's noop */
@@ -420,5 +434,5 @@
 		    USB_PORTSC_CONNECT_CH_FLAG);
 		return EOK;
-	case USB_HUB_FEATURE_C_PORT_ENABLE:       /*17*/
+	case USB2_HUB_FEATURE_C_PORT_ENABLE:       /*17*/
 		usb_log_debug2("RH(%p-%u): Clear port enable change.",
 		    hub, port);
@@ -432,5 +446,5 @@
 		    USB_PORTSC_OC_CHANGE_FLAG);
 		return EOK;
-	case USB_HUB_FEATURE_C_PORT_SUSPEND:      /*18*/
+	case USB2_HUB_FEATURE_C_PORT_SUSPEND:      /*18*/
 		usb_log_debug2("RH(%p-%u): Clear port suspend change.",
 		    hub, port);
@@ -467,10 +481,10 @@
 	const unsigned feature = uint16_usb2host(setup_packet->value);
 	switch (feature) {
-	case USB_HUB_FEATURE_PORT_ENABLE:  /*1*/
+	case USB2_HUB_FEATURE_PORT_ENABLE:  /*1*/
 		usb_log_debug2("RH(%p-%u): Set port enable.", hub, port);
 		EHCI_SET(hub->registers->portsc[port],
 		    USB_PORTSC_ENABLED_FLAG);
 		return EOK;
-	case USB_HUB_FEATURE_PORT_SUSPEND: /*2*/
+	case USB2_HUB_FEATURE_PORT_SUSPEND: /*2*/
 		usb_log_debug2("RH(%p-%u): Set port suspend.", hub, port);
 		EHCI_SET(hub->registers->portsc[port],
Index: uspace/drv/bus/usb/ehci/ehci_rh.h
===================================================================
--- uspace/drv/bus/usb/ehci/ehci_rh.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ehci/ehci_rh.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -61,12 +61,24 @@
 		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];
+
+	/* HC guard */
+	fibril_mutex_t *guard;
+
+	/*
+	 * 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;
 
 errno_t ehci_rh_init(ehci_rh_t *instance, ehci_caps_regs_t *caps, ehci_regs_t *regs,
-    const char *name);
+    fibril_mutex_t *guard, const char *name);
 errno_t ehci_rh_schedule(ehci_rh_t *instance, usb_transfer_batch_t *batch);
 errno_t ehci_rh_interrupt(ehci_rh_t *instance);
Index: uspace/drv/bus/usb/ehci/endpoint_list.c
===================================================================
--- uspace/drv/bus/usb/ehci/endpoint_list.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ehci/endpoint_list.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -54,10 +54,10 @@
 	assert(instance);
 	instance->name = name;
-	instance->list_head = malloc32(sizeof(qh_t));
-	if (!instance->list_head) {
+	if (dma_buffer_alloc(&instance->dma_buffer, sizeof(qh_t))) {
 		usb_log_error("EPL(%p-%s): Failed to allocate list head.",
 		    instance, name);
 		return ENOMEM;
 	}
+	instance->list_head = instance->dma_buffer.virt;
 	qh_init(instance->list_head, NULL);
 
@@ -95,7 +95,8 @@
 {
 	assert(instance);
+	assert(instance->list_head);
 	assert(ep);
 	assert(ep->qh);
-	usb_log_debug2("EPL(%p-%s): Append endpoint(%p).\n",
+	usb_log_debug2("EPL(%p-%s): Append endpoint(%p).",
 	    instance, instance->name, ep);
 
@@ -124,12 +125,12 @@
 	write_barrier();
 	/* Add to the sw list */
-	list_append(&ep->link, &instance->endpoint_list);
+	list_append(&ep->eplist_link, &instance->endpoint_list);
 
 	ehci_endpoint_t *first = ehci_endpoint_list_instance(
 	    list_first(&instance->endpoint_list));
-	usb_log_debug("EPL(%p-%s): EP(%p) added to list, first is %p(%p).\n",
+	usb_log_debug("EPL(%p-%s): EP(%p) added to list, first is %p(%p).",
 	    instance, instance->name, ep, first, first->qh);
 	if (last_qh == instance->list_head) {
-		usb_log_debug2("EPL(%p-%s): head EP(%p-%"PRIxn"): %x:%x.\n",
+		usb_log_debug2("EPL(%p-%s): head EP(%p-%"PRIxn"): %x:%x.",
 		    instance, instance->name, last_qh,
 		    addr_to_phys(instance->list_head),
@@ -153,5 +154,5 @@
 	fibril_mutex_lock(&instance->guard);
 
-	usb_log_debug2("EPL(%p-%s): removing EP(%p).\n",
+	usb_log_debug2("EPL(%p-%s): removing EP(%p).",
 	    instance, instance->name, ep);
 
@@ -159,10 +160,10 @@
 	qh_t *prev_qh;
 	/* Remove from the hardware queue */
-	if (list_first(&instance->endpoint_list) == &ep->link) {
+	if (list_first(&instance->endpoint_list) == &ep->eplist_link) {
 		/* I'm the first one here */
 		prev_qh = instance->list_head;
 		qpos = "FIRST";
 	} else {
-		prev_qh = ehci_endpoint_list_instance(ep->link.prev)->qh;
+		prev_qh = ehci_endpoint_list_instance(ep->eplist_link.prev)->qh;
 		qpos = "NOT FIRST";
 	}
@@ -172,9 +173,9 @@
 	write_barrier();
 
-	usb_log_debug("EPL(%p-%s): EP(%p) removed (%s), horizontal %x.\n",
+	usb_log_debug("EPL(%p-%s): EP(%p) removed (%s), horizontal %x.",
 	    instance, instance->name,  ep, qpos, ep->qh->horizontal);
 
 	/* Remove from the endpoint list */
-	list_remove(&ep->link);
+	list_remove(&ep->eplist_link);
 	fibril_mutex_unlock(&instance->guard);
 }
Index: uspace/drv/bus/usb/ehci/endpoint_list.h
===================================================================
--- uspace/drv/bus/usb/ehci/endpoint_list.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ehci/endpoint_list.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -38,7 +38,6 @@
 #include <assert.h>
 #include <fibril_synch.h>
-#include <usb/host/utils/malloc32.h>
 
-#include "ehci_endpoint.h"
+#include "ehci_bus.h"
 #include "hw_struct/queue_head.h"
 
@@ -49,4 +48,5 @@
 	/** EHCI hw structure at the beginning of the queue */
 	qh_t *list_head;
+	dma_buffer_t dma_buffer;
 	/** Assigned name, provides nicer debug output */
 	const char *name;
@@ -64,5 +64,5 @@
 {
 	assert(instance);
-	free32(instance->list_head);
+	dma_buffer_free(&instance->dma_buffer);
 	instance->list_head = NULL;
 }
Index: uspace/drv/bus/usb/ehci/hc.c
===================================================================
--- uspace/drv/bus/usb/ehci/hc.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ehci/hc.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -45,5 +45,5 @@
 #include <usb/debug.h>
 #include <usb/usb.h>
-#include <usb/host/utils/malloc32.h>
+#include <usb/host/utility.h>
 
 #include "ehci_batch.h"
@@ -89,5 +89,4 @@
 };
 
-static void hc_start(hc_t *instance);
 static errno_t hc_init_memory(hc_t *instance);
 
@@ -100,8 +99,9 @@
  * @return Error code.
  */
-errno_t ehci_hc_gen_irq_code(irq_code_t *code, const hw_res_list_parsed_t *hw_res, int *irq)
+errno_t hc_gen_irq_code(irq_code_t *code, hc_device_t *hcd, const hw_res_list_parsed_t *hw_res, int *irq)
 {
 	assert(code);
 	assert(hw_res);
+	hc_t *instance = hcd_to_hc(hcd);
 
 	if (hw_res->irqs.count != 1 || hw_res->mem_ranges.count != 1)
@@ -130,20 +130,12 @@
 
 	memcpy(code->cmds, ehci_irq_commands, sizeof(ehci_irq_commands));
-	ehci_caps_regs_t *caps = NULL;
-
-	errno_t ret = pio_enable_range(&regs, (void**)&caps);
-	if (ret != EOK) {
-		free(code->ranges);
-		free(code->cmds);
-		return ret;
-	}
 
 	ehci_regs_t *registers =
-	    (ehci_regs_t *)(RNGABSPTR(regs) + EHCI_RD8(caps->caplength));
+		(ehci_regs_t *)(RNGABSPTR(regs) + EHCI_RD8(instance->caps->caplength));
 	code->cmds[0].addr = (void *) &registers->usbsts;
 	code->cmds[3].addr = (void *) &registers->usbsts;
 	EHCI_WR(code->cmds[1].value, EHCI_USED_INTERRUPTS);
 
-	usb_log_debug("Memory mapped regs at %p (size %zu), IRQ %d.\n",
+	usb_log_debug("Memory mapped regs at %p (size %zu), IRQ %d.",
 	    RNGABSPTR(regs), RNGSZ(regs), hw_res->irqs.irqs[0]);
 
@@ -159,7 +151,7 @@
  * @return Error code
  */
-errno_t hc_init(hc_t *instance, const hw_res_list_parsed_t *hw_res, bool interrupts)
-{
-	assert(instance);
+errno_t hc_add(hc_device_t *hcd, const hw_res_list_parsed_t *hw_res)
+{
+	hc_t *instance = hcd_to_hc(hcd);
 	assert(hw_res);
 	if (hw_res->mem_ranges.count != 1 ||
@@ -172,7 +164,8 @@
 	if (ret != EOK) {
 		usb_log_error("HC(%p): Failed to gain access to device "
-		    "registers: %s.\n", instance, str_error(ret));
+		    "registers: %s.", instance, str_error(ret));
 		return ret;
 	}
+
 	usb_log_info("HC(%p): Device registers at %"PRIx64" (%zuB) accessible.",
 	    instance, hw_res->mem_ranges.ranges[0].address.absolute,
@@ -184,5 +177,5 @@
 	    + EHCI_RD8(instance->caps->caplength));
 
-	list_initialize(&instance->pending_batches);
+	list_initialize(&instance->pending_endpoints);
 	fibril_mutex_initialize(&instance->guard);
 	fibril_condvar_initialize(&instance->async_doorbell);
@@ -197,8 +190,9 @@
 	usb_log_info("HC(%p): Initializing RH(%p).", instance, &instance->rh);
 	ehci_rh_init(
-	    &instance->rh, instance->caps, instance->registers, "ehci rh");
-	usb_log_debug("HC(%p): Starting HW.", instance);
-	hc_start(instance);
-
+	    &instance->rh, instance->caps, instance->registers, &instance->guard,
+	    "ehci rh");
+
+	ehci_bus_init(&instance->bus, instance);
+	hc_device_setup(hcd, (bus_t *) &instance->bus);
 	return EOK;
 }
@@ -208,13 +202,11 @@
  * @param[in] instance Host controller structure to use.
  */
-void hc_fini(hc_t *instance)
-{
-	assert(instance);
-	//TODO: stop the hw
-#if 0
-	endpoint_list_fini(&instance->async_list);
-	endpoint_list_fini(&instance->int_list);
-	return_page(instance->periodic_list_base);
-#endif
+int hc_gone(hc_device_t *hcd)
+{
+	hc_t *hc = hcd_to_hc(hcd);
+	endpoint_list_fini(&hc->async_list);
+	endpoint_list_fini(&hc->int_list);
+	dma_buffer_free(&hc->dma_buffer);
+	return EOK;
 };
 
@@ -224,6 +216,6 @@
 	assert(ep);
 	ehci_endpoint_t *ehci_ep = ehci_endpoint_get(ep);
-	usb_log_debug("HC(%p) enqueue EP(%d:%d:%s:%s)\n", instance,
-	    ep->address, ep->endpoint,
+	usb_log_debug("HC(%p) enqueue EP(%d:%d:%s:%s)", instance,
+	    ep->device->address, ep->endpoint,
 	    usb_str_transfer_type_short(ep->transfer_type),
 	    usb_str_direction(ep->direction));
@@ -248,6 +240,6 @@
 	assert(ep);
 	ehci_endpoint_t *ehci_ep = ehci_endpoint_get(ep);
-	usb_log_debug("HC(%p) dequeue EP(%d:%d:%s:%s)\n", instance,
-	    ep->address, ep->endpoint,
+	usb_log_debug("HC(%p) dequeue EP(%d:%d:%s:%s)", instance,
+	    ep->device->address, ep->endpoint,
 	    usb_str_transfer_type_short(ep->transfer_type),
 	    usb_str_direction(ep->direction));
@@ -273,16 +265,19 @@
 }
 
-errno_t ehci_hc_status(hcd_t *hcd, uint32_t *status)
-{
-	assert(hcd);
-	hc_t *instance = hcd_get_driver_data(hcd);
-	assert(instance);
+errno_t ehci_hc_status(bus_t *bus_base, uint32_t *status)
+{
+	assert(bus_base);
 	assert(status);
+
+	ehci_bus_t *bus = (ehci_bus_t *) bus_base;
+	hc_t *hc = bus->hc;
+	assert(hc);
+
 	*status = 0;
-	if (instance->registers) {
-		*status = EHCI_RD(instance->registers->usbsts);
-		EHCI_WR(instance->registers->usbsts, *status);
-	}
-	usb_log_debug2("HC(%p): Read status: %x", instance, *status);
+	if (hc->registers) {
+		*status = EHCI_RD(hc->registers->usbsts);
+		EHCI_WR(hc->registers->usbsts, *status);
+	}
+	usb_log_debug2("HC(%p): Read status: %x", hc, *status);
 	return EOK;
 }
@@ -294,27 +289,43 @@
  * @return Error code.
  */
-errno_t ehci_hc_schedule(hcd_t *hcd, usb_transfer_batch_t *batch)
-{
-	assert(hcd);
-	hc_t *instance = hcd_get_driver_data(hcd);
-	assert(instance);
+errno_t ehci_hc_schedule(usb_transfer_batch_t *batch)
+{
+	assert(batch);
+
+	ehci_bus_t *bus = (ehci_bus_t *) endpoint_get_bus(batch->ep);
+	hc_t *hc = bus->hc;
+	assert(hc);
 
 	/* Check for root hub communication */
-	if (batch->ep->address == ehci_rh_get_address(&instance->rh)) {
+	if (batch->target.address == ehci_rh_get_address(&hc->rh)) {
 		usb_log_debug("HC(%p): Scheduling BATCH(%p) for RH(%p)",
-		    instance, batch, &instance->rh);
-		return ehci_rh_schedule(&instance->rh, batch);
-	}
+		    hc, batch, &hc->rh);
+		return ehci_rh_schedule(&hc->rh, batch);
+	}
+
+	endpoint_t * const ep = batch->ep;
+	ehci_endpoint_t * const ehci_ep = ehci_endpoint_get(ep);
 	ehci_transfer_batch_t *ehci_batch = ehci_transfer_batch_get(batch);
-	if (!ehci_batch)
-		return ENOMEM;
-
-	fibril_mutex_lock(&instance->guard);
-	usb_log_debug2("HC(%p): Appending BATCH(%p)", instance, batch);
-	list_append(&ehci_batch->link, &instance->pending_batches);
-	usb_log_debug("HC(%p): Committing BATCH(%p)", instance, batch);
+
+	int err;
+
+	if ((err = ehci_transfer_batch_prepare(ehci_batch)))
+		return err;
+
+	fibril_mutex_lock(&hc->guard);
+
+	if ((err = endpoint_activate_locked(ep, batch))) {
+		fibril_mutex_unlock(&hc->guard);
+		return err;
+	}
+
+	usb_log_debug("HC(%p): Committing BATCH(%p)", hc, batch);
 	ehci_transfer_batch_commit(ehci_batch);
 
-	fibril_mutex_unlock(&instance->guard);
+	/* Enqueue endpoint to the checked list */
+	usb_log_debug2("HC(%p): Appending BATCH(%p)", hc, batch);
+	list_append(&ehci_ep->pending_link, &hc->pending_endpoints);
+
+	fibril_mutex_unlock(&hc->guard);
 	return EOK;
 }
@@ -325,42 +336,51 @@
  * @param[in] status Value of the status register at the time of interrupt.
  */
-void ehci_hc_interrupt(hcd_t *hcd, uint32_t status)
-{
-	assert(hcd);
-	hc_t *instance = hcd_get_driver_data(hcd);
-	status = EHCI_RD(status);
-	assert(instance);
-
-	usb_log_debug2("HC(%p): Interrupt: %"PRIx32, instance, status);
+void ehci_hc_interrupt(bus_t *bus_base, uint32_t status)
+{
+	assert(bus_base);
+
+	ehci_bus_t *bus = (ehci_bus_t *) bus_base;
+	hc_t *hc = bus->hc;
+	assert(hc);
+
+	usb_log_debug2("HC(%p): Interrupt: %"PRIx32, hc, status);
 	if (status & USB_STS_PORT_CHANGE_FLAG) {
-		ehci_rh_interrupt(&instance->rh);
+		ehci_rh_interrupt(&hc->rh);
 	}
 
 	if (status & USB_STS_IRQ_ASYNC_ADVANCE_FLAG) {
-		fibril_mutex_lock(&instance->guard);
-		usb_log_debug2("HC(%p): Signaling doorbell", instance);
-		fibril_condvar_broadcast(&instance->async_doorbell);
-		fibril_mutex_unlock(&instance->guard);
+		fibril_mutex_lock(&hc->guard);
+		usb_log_debug2("HC(%p): Signaling doorbell", hc);
+		fibril_condvar_broadcast(&hc->async_doorbell);
+		fibril_mutex_unlock(&hc->guard);
 	}
 
 	if (status & (USB_STS_IRQ_FLAG | USB_STS_ERR_IRQ_FLAG)) {
-		fibril_mutex_lock(&instance->guard);
-
-		usb_log_debug2("HC(%p): Scanning %lu pending batches", instance,
-			list_count(&instance->pending_batches));
-		list_foreach_safe(instance->pending_batches, current, next) {
-			ehci_transfer_batch_t *batch =
-			    ehci_transfer_batch_from_link(current);
-
-			if (ehci_transfer_batch_is_complete(batch)) {
+		fibril_mutex_lock(&hc->guard);
+
+		usb_log_debug2("HC(%p): Scanning %lu pending endpoints", hc,
+			list_count(&hc->pending_endpoints));
+		list_foreach_safe(hc->pending_endpoints, current, next) {
+			ehci_endpoint_t *ep
+				= list_get_instance(current, ehci_endpoint_t, pending_link);
+
+			ehci_transfer_batch_t *batch
+				= ehci_transfer_batch_get(ep->base.active_batch);
+			assert(batch);
+
+			if (ehci_transfer_batch_check_completed(batch)) {
+				endpoint_deactivate_locked(&ep->base);
 				list_remove(current);
-				ehci_transfer_batch_finish_dispose(batch);
+				hc_reset_toggles(&batch->base, &ehci_ep_toggle_reset);
+				usb_transfer_batch_finish(&batch->base);
 			}
 		}
-		fibril_mutex_unlock(&instance->guard);
+		fibril_mutex_unlock(&hc->guard);
+
+
 	}
 
 	if (status & USB_STS_HOST_ERROR_FLAG) {
-		usb_log_fatal("HCD(%p): HOST SYSTEM ERROR!", instance);
+		usb_log_fatal("HCD(%p): HOST SYSTEM ERROR!", hc);
 		//TODO do something here
 	}
@@ -371,7 +391,9 @@
  * @param[in] instance EHCI hc driver structure.
  */
-void hc_start(hc_t *instance)
-{
-	assert(instance);
+int hc_start(hc_device_t *hcd)
+{
+	hc_t *instance = hcd_to_hc(hcd);
+	usb_log_debug("HC(%p): Starting HW.", instance);
+
 	/* Turn off the HC if it's running, Reseting a running device is
 	 * undefined */
@@ -404,7 +426,7 @@
 
 	/* Enable periodic list */
-	assert(instance->periodic_list_base);
+	assert(instance->periodic_list);
 	uintptr_t phys_base =
-	    addr_to_phys((void*)instance->periodic_list_base);
+	    addr_to_phys((void*)instance->periodic_list);
 	assert((phys_base & USB_PERIODIC_LIST_BASE_MASK) == phys_base);
 	EHCI_WR(instance->registers->periodiclistbase, phys_base);
@@ -425,9 +447,9 @@
 	usb_log_debug("HC(%p): HW started.", instance);
 
-	usb_log_debug2("HC(%p): Registers: \n"
-	    "\tUSBCMD(%p): %x(0x00080000 = at least 1ms between interrupts)\n"
-	    "\tUSBSTS(%p): %x(0x00001000 = HC halted)\n"
-	    "\tUSBINT(%p): %x(0x0 = no interrupts).\n"
-	    "\tCONFIG(%p): %x(0x0 = ports controlled by companion hc).\n",
+	usb_log_debug2("HC(%p): Registers: "
+	    "\tUSBCMD(%p): %x(0x00080000 = at least 1ms between interrupts)"
+	    "\tUSBSTS(%p): %x(0x00001000 = HC halted)"
+	    "\tUSBINT(%p): %x(0x0 = no interrupts)."
+	    "\tCONFIG(%p): %x(0x0 = ports controlled by companion hc).",
 	    instance,
 	    &instance->registers->usbcmd, EHCI_RD(instance->registers->usbcmd),
@@ -438,4 +460,14 @@
 	EHCI_WR(instance->registers->usbsts, EHCI_RD(instance->registers->usbsts));
 	EHCI_WR(instance->registers->usbintr, EHCI_USED_INTERRUPTS);
+
+	return EOK;
+}
+
+/**
+ * Setup roothub as a virtual hub.
+ */
+int hc_setup_roothub(hc_device_t *hcd)
+{
+	return hc_setup_virtual_root_hub(hcd, USB_SPEED_HIGH);
 }
 
@@ -473,6 +505,5 @@
 
 	/* Take 1024 periodic list heads, we ignore low mem options */
-	instance->periodic_list_base = get_page();
-	if (!instance->periodic_list_base) {
+	if (dma_buffer_alloc(&instance->dma_buffer, PAGE_SIZE)) {
 		usb_log_error("HC(%p): Failed to get ISO schedule page.",
 		    instance);
@@ -481,11 +512,11 @@
 		return ENOMEM;
 	}
+	instance->periodic_list = instance->dma_buffer.virt;
 
 	usb_log_debug2("HC(%p): Initializing Periodic list.", instance);
-	for (unsigned i = 0;
-	    i < PAGE_SIZE/sizeof(instance->periodic_list_base[0]); ++i)
+	for (unsigned i = 0; i < PAGE_SIZE/sizeof(link_pointer_t); ++i)
 	{
 		/* Disable everything for now */
-		instance->periodic_list_base[i] =
+		instance->periodic_list[i] =
 		    LINK_POINTER_QH(addr_to_phys(instance->int_list.list_head));
 	}
Index: uspace/drv/bus/usb/ehci/hc.h
===================================================================
--- uspace/drv/bus/usb/ehci/hc.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ehci/hc.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -55,4 +55,7 @@
 /** Main EHCI driver structure */
 typedef struct hc {
+	/* Common device header */
+	hc_device_t base;
+
 	/** Memory mapped CAPS register area */
 	ehci_caps_regs_t *caps;
@@ -60,6 +63,8 @@
 	ehci_regs_t *registers;
 
-	/** Iso transfer list */
-	link_pointer_t *periodic_list_base;
+	/** Iso transfer list, backed by dma_buffer */
+	link_pointer_t *periodic_list;
+
+	dma_buffer_t dma_buffer;
 
 	/** CONTROL and BULK schedules */
@@ -70,5 +75,5 @@
 
 	/** List of active transfers */
-	list_t pending_batches;
+	list_t pending_endpoints;
 
 	/** Guards schedule and endpoint manipulation */
@@ -80,17 +85,30 @@
 	/** USB hub emulation structure */
 	ehci_rh_t rh;
+
+	/** USB bookkeeping */
+	ehci_bus_t bus;
 } hc_t;
 
-errno_t hc_init(hc_t *instance, const hw_res_list_parsed_t *hw_res, bool interrupts);
-void hc_fini(hc_t *instance);
+static inline hc_t *hcd_to_hc(hc_device_t *hcd)
+{
+	assert(hcd);
+	return (hc_t *) hcd;
+}
 
-void hc_enqueue_endpoint(hc_t *instance, const endpoint_t *ep);
-void hc_dequeue_endpoint(hc_t *instance, const endpoint_t *ep);
+void hc_enqueue_endpoint(hc_t *, const endpoint_t *);
+void hc_dequeue_endpoint(hc_t *, const endpoint_t *);
 
-errno_t ehci_hc_gen_irq_code(irq_code_t *code, const hw_res_list_parsed_t *hw_res, int *irq);
+/* Boottime operations */
+extern errno_t hc_add(hc_device_t *, const hw_res_list_parsed_t *);
+extern errno_t hc_start(hc_device_t *);
+extern errno_t hc_setup_roothub(hc_device_t *);
+extern errno_t hc_gen_irq_code(irq_code_t *, hc_device_t *, const hw_res_list_parsed_t *, int *);
+extern errno_t hc_gone(hc_device_t *);
 
-void ehci_hc_interrupt(hcd_t *hcd, uint32_t status);
-errno_t ehci_hc_status(hcd_t *hcd, uint32_t *status);
-errno_t ehci_hc_schedule(hcd_t *hcd, usb_transfer_batch_t *batch);
+/** Runtime operations */
+extern void ehci_hc_interrupt(bus_t *, uint32_t);
+extern errno_t ehci_hc_status(bus_t *, uint32_t *);
+extern errno_t ehci_hc_schedule(usb_transfer_batch_t *);
+
 #endif
 /**
Index: uspace/drv/bus/usb/ehci/hw_struct/queue_head.c
===================================================================
--- uspace/drv/bus/usb/ehci/hw_struct/queue_head.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ehci/hw_struct/queue_head.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -37,4 +37,5 @@
 #include <mem.h>
 #include <macros.h>
+#include <usb/host/bus.h>
 
 #include "mem_access.h"
@@ -63,13 +64,13 @@
 		return;
 	}
-	assert(ep->speed < ARRAY_SIZE(speed));
+	assert(ep->device->speed < ARRAY_SIZE(speed));
 	EHCI_MEM32_WR(instance->ep_char,
-	    QH_EP_CHAR_ADDR_SET(ep->address) |
+	    QH_EP_CHAR_ADDR_SET(ep->device->address) |
 	    QH_EP_CHAR_EP_SET(ep->endpoint) |
-	    speed[ep->speed] |
+	    speed[ep->device->speed] |
 	    QH_EP_CHAR_MAX_LENGTH_SET(ep->max_packet_size)
 	);
 	if (ep->transfer_type == USB_TRANSFER_CONTROL) {
-		if (ep->speed != USB_SPEED_HIGH)
+		if (ep->device->speed != USB_SPEED_HIGH)
 			EHCI_MEM32_SET(instance->ep_char, QH_EP_CHAR_C_FLAG);
 		/* Let BULK and INT use queue head managed toggle,
@@ -78,9 +79,10 @@
 	}
 	uint32_t ep_cap = QH_EP_CAP_C_MASK_SET(3 << 2) |
-		    QH_EP_CAP_MULTI_SET(ep->packets);
-	if (ep->speed != USB_SPEED_HIGH) {
+		    QH_EP_CAP_MULTI_SET(ep->packets_per_uframe);
+	if (usb_speed_is_11(ep->device->speed)) {
+		assert(ep->device->tt.dev != NULL);
 		ep_cap |=
-		    QH_EP_CAP_TT_PORT_SET(ep->tt.port) |
-		    QH_EP_CAP_TT_ADDR_SET(ep->tt.address);
+		    QH_EP_CAP_TT_PORT_SET(ep->device->tt.port) |
+		    QH_EP_CAP_TT_ADDR_SET(ep->device->tt.dev->address);
 	}
 	if (ep->transfer_type == USB_TRANSFER_INTERRUPT) {
Index: uspace/drv/bus/usb/ehci/hw_struct/queue_head.h
===================================================================
--- uspace/drv/bus/usb/ehci/hw_struct/queue_head.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ehci/hw_struct/queue_head.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -193,9 +193,9 @@
 }
 
-static inline void qh_set_next_td(qh_t *qh, td_t *td)
+static inline void qh_set_next_td(qh_t *qh, uintptr_t td)
 {
 	assert(qh);
 	assert(td);
-	EHCI_MEM32_WR(qh->next, LINK_POINTER_TD(addr_to_phys(td)));
+	EHCI_MEM32_WR(qh->next, LINK_POINTER_TD(td));
 }
 
Index: uspace/drv/bus/usb/ehci/hw_struct/transfer_descriptor.c
===================================================================
--- uspace/drv/bus/usb/ehci/hw_struct/transfer_descriptor.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ehci/hw_struct/transfer_descriptor.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -39,5 +39,4 @@
 
 #include <usb/usb.h>
-#include <usb/host/utils/malloc32.h>
 
 #include "mem_access.h"
@@ -70,8 +69,10 @@
 };
 
+#include <usb/debug.h>
+
 /**
  * Initialize EHCI TD.
  * @param instance TD structure to initialize.
- * @param next Next TD in ED list.
+ * @param next_phys Next TD in ED list.
  * @param direction Used to determine PID, BOTH means setup PID.
  * @param buffer Pointer to the first byte of transferred data.
@@ -80,7 +81,6 @@
  *        any other value means that ED toggle will be used.
  */
-void td_init(td_t *instance, const td_t *next,
-    usb_direction_t direction, const void *buffer, size_t size, int toggle,
-    bool ioc)
+void td_init(td_t *instance, uintptr_t next_phys, uintptr_t buffer,
+    usb_direction_t direction, size_t size, int toggle, bool ioc)
 {
 	assert(instance);
@@ -98,22 +98,19 @@
 	}
 
-	if (buffer != NULL) {
+	if (buffer != 0) {
 		assert(size != 0);
 		for (unsigned i = 0; (i < ARRAY_SIZE(instance->buffer_pointer))
 		    && size; ++i) {
-			const uintptr_t page =
-			    (addr_to_phys(buffer) & TD_BUFFER_POINTER_MASK);
-			const size_t offset =
-			    ((uintptr_t)buffer & TD_BUFFER_POINTER_OFFSET_MASK);
+			const uintptr_t offset = buffer & TD_BUFFER_POINTER_OFFSET_MASK;
 			assert(offset == 0 || i == 0);
-			size -= min((4096 - offset), size);
-			buffer += min((4096 - offset), size);
-			EHCI_MEM32_WR(instance->buffer_pointer[i],
-			    page | offset);
+			const size_t this_size = min(size, 4096 - offset);
+			EHCI_MEM32_WR(instance->buffer_pointer[i], buffer);
+			size -= this_size;
+			buffer += this_size;
 		}
 	}
 
-	EHCI_MEM32_WR(instance->next, next ?
-	    LINK_POINTER_TD(addr_to_phys(next)) : LINK_POINTER_TERM);
+	EHCI_MEM32_WR(instance->next, next_phys ?
+	    LINK_POINTER_TD(next_phys) : LINK_POINTER_TERM);
 
 	EHCI_MEM32_WR(instance->alternate, LINK_POINTER_TERM);
Index: uspace/drv/bus/usb/ehci/hw_struct/transfer_descriptor.h
===================================================================
--- uspace/drv/bus/usb/ehci/hw_struct/transfer_descriptor.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ehci/hw_struct/transfer_descriptor.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -37,4 +37,5 @@
 #include <stddef.h>
 #include <stdint.h>
+#include <macros.h>
 #include "link_pointer.h"
 #include "mem_access.h"
@@ -75,5 +76,11 @@
 	/* 64 bit struct only */
 	volatile uint32_t extended_bp[5];
-} td_t;
+
+	/* TDs must be 32-byte aligned */
+	PADD32 [3];
+
+} __attribute__((packed)) td_t;
+
+static_assert(sizeof(td_t) % 32 == 0);
 
 static inline bool td_active(const td_t *td)
@@ -92,5 +99,5 @@
 errno_t td_error(const td_t *td);
 
-void td_init(td_t *td, const td_t *next, usb_direction_t dir, const void * buf,
+void td_init(td_t *td, uintptr_t next_phys, uintptr_t buf, usb_direction_t dir,
     size_t buf_size, int toggle, bool ioc);
 
Index: uspace/drv/bus/usb/ehci/main.c
===================================================================
--- uspace/drv/bus/usb/ehci/main.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ehci/main.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -35,95 +35,23 @@
  */
 
-#include <ddf/driver.h>
-#include <ddf/interrupt.h>
-#include <device/hw_res.h>
-#include <errno.h>
-#include <str_error.h>
 #include <io/logctl.h>
-
-#include <usb_iface.h>
-#include <usb/debug.h>
-#include <usb/host/ddf_helpers.h>
+#include <usb/host/hcd.h>
 
 #include "res.h"
 #include "hc.h"
-#include "ehci_endpoint.h"
 
 #define NAME "ehci"
 
-static errno_t ehci_driver_init(hcd_t *, const hw_res_list_parsed_t *, bool);
-static void ehci_driver_fini(hcd_t *);
+static const hc_driver_t ehci_driver = {
+	.name = NAME,
+	.hc_device_size = sizeof(hc_t),
 
-static const ddf_hc_driver_t ehci_hc_driver = {
+	.hc_add = hc_add,
+	.irq_code_gen = hc_gen_irq_code,
 	.claim = disable_legacy,
-	.hc_speed = USB_SPEED_HIGH,
-	.irq_code_gen = ehci_hc_gen_irq_code,
-	.init = ehci_driver_init,
-	.fini = ehci_driver_fini,
-	.name = "EHCI-PCI",
-	.ops = {
-		.schedule       = ehci_hc_schedule,
-		.ep_add_hook    = ehci_endpoint_init,
-		.ep_remove_hook = ehci_endpoint_fini,
-		.irq_hook       = ehci_hc_interrupt,
-		.status_hook    = ehci_hc_status,
-	}
+	.start = hc_start,
+	.setup_root_hub = hc_setup_roothub,
+	.hc_gone = hc_gone,
 };
-
-
-static errno_t ehci_driver_init(hcd_t *hcd, const hw_res_list_parsed_t *res,
-    bool irq)
-{
-	assert(hcd);
-	assert(hcd_get_driver_data(hcd) == NULL);
-
-	hc_t *instance = malloc(sizeof(hc_t));
-	if (!instance)
-		return ENOMEM;
-
-	const errno_t ret = hc_init(instance, res, irq);
-	if (ret == EOK) {
-		hcd_set_implementation(hcd, instance, &ehci_hc_driver.ops);
-	} else {
-		free(instance);
-	}
-	return ret;
-}
-
-static void ehci_driver_fini(hcd_t *hcd)
-{
-	assert(hcd);
-	hc_t *hc = hcd_get_driver_data(hcd);
-	if (hc)
-		hc_fini(hc);
-
-	free(hc);
-	hcd_set_implementation(hcd, NULL, NULL);
-}
-
-/** Initializes a new ddf driver instance of EHCI hcd.
- *
- * @param[in] device DDF instance of the device to initialize.
- * @return Error code.
- */
-static errno_t ehci_dev_add(ddf_dev_t *device)
-{
-	usb_log_debug("ehci_dev_add() called\n");
-	assert(device);
-
-	return hcd_ddf_add_hc(device, &ehci_hc_driver);
-
-}
-
-
-static const driver_ops_t ehci_driver_ops = {
-	.dev_add = ehci_dev_add,
-};
-
-static const driver_t ehci_driver = {
-	.name = NAME,
-	.driver_ops = &ehci_driver_ops
-};
-
 
 /** Initializes global driver structures (NONE).
@@ -139,5 +67,5 @@
 	log_init(NAME);
 	logctl_set_log_level(NAME, LVL_NOTE);
-	return ddf_driver_main(&ehci_driver);
+	return hc_driver_main(&ehci_driver);
 }
 
Index: uspace/drv/bus/usb/ehci/res.c
===================================================================
--- uspace/drv/bus/usb/ehci/res.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ehci/res.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -45,4 +45,5 @@
 #include <pci_dev_iface.h>
 
+#include "hc.h"
 #include "res.h"
 #include "ehci_regs.h"
@@ -73,16 +74,16 @@
 	    eecp + USBLEGSUP_OFFSET, &usblegsup);
 	if (ret != EOK) {
-		usb_log_error("Failed to read USBLEGSUP: %s.\n", str_error(ret));
-		return ret;
-	}
-	usb_log_debug2("USBLEGSUP: %" PRIx32 ".\n", usblegsup);
+		usb_log_error("Failed to read USBLEGSUP: %s.", str_error(ret));
+		return ret;
+	}
+	usb_log_debug2("USBLEGSUP: %" PRIx32 ".", usblegsup);
 
 	/* Request control from firmware/BIOS by writing 1 to highest
 	 * byte. (OS Control semaphore)*/
-	usb_log_debug("Requesting OS control.\n");
+	usb_log_debug("Requesting OS control.");
 	ret = pci_config_space_write_8(parent_sess,
 	    eecp + USBLEGSUP_OFFSET + 3, 1);
 	if (ret != EOK) {
-		usb_log_error("Failed to request OS EHCI control: %s.\n",
+		usb_log_error("Failed to request OS EHCI control: %s.",
 		    str_error(ret));
 		return ret;
@@ -102,5 +103,5 @@
 
 	if ((usblegsup & USBLEGSUP_BIOS_CONTROL) == 0) {
-		usb_log_info("BIOS released control after %zu usec.\n", wait);
+		usb_log_info("BIOS released control after %zu usec.", wait);
 		return EOK;
 	}
@@ -108,9 +109,9 @@
 	/* BIOS failed to hand over control, this should not happen. */
 	usb_log_warning( "BIOS failed to release control after "
-	    "%zu usecs, force it.\n", wait);
+	    "%zu usecs, force it.", wait);
 	ret = pci_config_space_write_32(parent_sess,
 	    eecp + USBLEGSUP_OFFSET, USBLEGSUP_OS_CONTROL);
 	if (ret != EOK) {
-		usb_log_error("Failed to force OS control: %s.\n",
+		usb_log_error("Failed to force OS control: %s.",
 		    str_error(ret));
 		return ret;
@@ -129,9 +130,9 @@
 		    eecp + USBLEGCTLSTS_OFFSET, &usblegctlsts);
 		if (ret != EOK) {
-			usb_log_error("Failed to get USBLEGCTLSTS: %s.\n",
+			usb_log_error("Failed to get USBLEGCTLSTS: %s.",
 			    str_error(ret));
 			return ret;
 		}
-		usb_log_debug2("USBLEGCTLSTS: %" PRIx32 ".\n", usblegctlsts);
+		usb_log_debug2("USBLEGCTLSTS: %" PRIx32 ".", usblegctlsts);
 		/*
 		 * Zero SMI enables in legacy control register.
@@ -142,5 +143,5 @@
 		    eecp + USBLEGCTLSTS_OFFSET, 0xe0000000);
 		if (ret != EOK) {
-			usb_log_error("Failed to zero USBLEGCTLSTS: %s\n",
+			usb_log_error("Failed to zero USBLEGCTLSTS: %s",
 			    str_error(ret));
 			return ret;
@@ -152,9 +153,9 @@
 		    eecp + USBLEGCTLSTS_OFFSET, &usblegctlsts);
 		if (ret != EOK) {
-			usb_log_error("Failed to get USBLEGCTLSTS 2: %s.\n",
+			usb_log_error("Failed to get USBLEGCTLSTS 2: %s.",
 			    str_error(ret));
 			return ret;
 		}
-		usb_log_debug2("Zeroed USBLEGCTLSTS: %" PRIx32 ".\n",
+		usb_log_debug2("Zeroed USBLEGCTLSTS: %" PRIx32 ".",
 		    usblegctlsts);
 	}
@@ -164,53 +165,24 @@
 	    eecp + USBLEGSUP_OFFSET, &usblegsup);
 	if (ret != EOK) {
-		usb_log_error("Failed to read USBLEGSUP: %s.\n",
-		    str_error(ret));
-		return ret;
-	}
-	usb_log_debug2("USBLEGSUP: %" PRIx32 ".\n", usblegsup);
+		usb_log_error("Failed to read USBLEGSUP: %s.",
+		    str_error(ret));
+		return ret;
+	}
+	usb_log_debug2("USBLEGSUP: %" PRIx32 ".", usblegsup);
 	return ret;
 }
 
-errno_t disable_legacy(ddf_dev_t *device)
+errno_t disable_legacy(hc_device_t *hcd)
 {
-	assert(device);
-
-	async_sess_t *parent_sess = ddf_dev_parent_sess_get(device);
+	hc_t *hc = hcd_to_hc(hcd);
+
+	async_sess_t *parent_sess = ddf_dev_parent_sess_get(hcd->ddf_dev);
 	if (parent_sess == NULL)
 		return ENOMEM;
 
-	usb_log_debug("Disabling EHCI legacy support.\n");
-
-	hw_res_list_parsed_t res;
-	hw_res_list_parsed_init(&res);
-	errno_t ret = hw_res_get_list_parsed(parent_sess, &res, 0);
-	if (ret != EOK) {
-		usb_log_error("Failed to get resource list: %s\n",
-		    str_error(ret));
-		goto clean;
-	}
-
-	if (res.mem_ranges.count < 1) {
-		usb_log_error("Incorrect mem range count: %zu",
-		    res.mem_ranges.count);
-		ret = EINVAL;
-		goto clean;
-	}
-
-	/* Map EHCI registers */
-	void *regs = NULL;
-	ret = pio_enable_range(&res.mem_ranges.ranges[0], &regs);
-	if (ret != EOK) {
-		usb_log_error("Failed to map registers %p: %s.\n",
-		    RNGABSPTR(res.mem_ranges.ranges[0]), str_error(ret));
-		goto clean;
-	}
-
-	usb_log_debug("Registers mapped at: %p.\n", regs);
-
-	ehci_caps_regs_t *ehci_caps = regs;
-
-	const uint32_t hcc_params = EHCI_RD(ehci_caps->hccparams);
-	usb_log_debug2("Value of hcc params register: %x.\n", hcc_params);
+	usb_log_debug("Disabling EHCI legacy support.");
+
+	const uint32_t hcc_params = EHCI_RD(hc->caps->hccparams);
+	usb_log_debug2("Value of hcc params register: %x.", hcc_params);
 
 	/* Read value of EHCI Extended Capabilities Pointer
@@ -218,15 +190,14 @@
 	const uint32_t eecp =
 	    (hcc_params >> EHCI_CAPS_HCC_EECP_SHIFT) & EHCI_CAPS_HCC_EECP_MASK;
-	usb_log_debug2("Value of EECP: %x.\n", eecp);
-
-	ret = disable_extended_caps(parent_sess, eecp);
-	if (ret != EOK) {
-		usb_log_error("Failed to disable extended capabilities: %s.\n",
+	usb_log_debug2("Value of EECP: %x.", eecp);
+
+	int ret = disable_extended_caps(parent_sess, eecp);
+	if (ret != EOK) {
+		usb_log_error("Failed to disable extended capabilities: %s.",
 		    str_error(ret));
 		    goto clean;
 	}
 clean:
-	//TODO unmap registers
-	hw_res_list_parsed_clean(&res);
+	async_hangup(parent_sess);
 	return ret;
 }
Index: uspace/drv/bus/usb/ehci/res.h
===================================================================
--- uspace/drv/bus/usb/ehci/res.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ehci/res.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -36,8 +36,7 @@
 #define DRV_EHCI_PCI_H
 
-#include <ddf/driver.h>
-#include <device/hw_res_parsed.h>
+typedef struct hc_device hc_device_t;
 
-extern errno_t disable_legacy(ddf_dev_t *);
+extern errno_t disable_legacy(hc_device_t *);
 
 #endif
Index: uspace/drv/bus/usb/ohci/Makefile
===================================================================
--- uspace/drv/bus/usb/ohci/Makefile	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ohci/Makefile	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -38,5 +38,5 @@
 	main.c \
 	ohci_batch.c \
-	ohci_endpoint.c \
+	ohci_bus.c \
 	ohci_rh.c \
 	hw_struct/endpoint_descriptor.c \
Index: uspace/drv/bus/usb/ohci/endpoint_list.c
===================================================================
--- uspace/drv/bus/usb/ohci/endpoint_list.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ohci/endpoint_list.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -57,9 +57,9 @@
 	instance->list_head = malloc32(sizeof(ed_t));
 	if (!instance->list_head) {
-		usb_log_error("Failed to allocate list head.\n");
+		usb_log_error("Failed to allocate list head.");
 		return ENOMEM;
 	}
 	instance->list_head_pa = addr_to_phys(instance->list_head);
-	usb_log_debug2("Transfer list %s setup with ED: %p(0x%0" PRIx32 ")).\n",
+	usb_log_debug2("Transfer list %s setup with ED: %p(0x%0" PRIx32 ")).",
 	    name, instance->list_head, instance->list_head_pa);
 
@@ -96,5 +96,5 @@
 	assert(instance);
 	assert(ep);
-	usb_log_debug2("Queue %s: Adding endpoint(%p).\n", instance->name, ep);
+	usb_log_debug2("Queue %s: Adding endpoint(%p).", instance->name, ep);
 
 	fibril_mutex_lock(&instance->guard);
@@ -108,5 +108,5 @@
 		/* There are active EDs, get the last one */
 		ohci_endpoint_t *last = list_get_instance(
-		    list_last(&instance->endpoint_list), ohci_endpoint_t, link);
+		    list_last(&instance->endpoint_list), ohci_endpoint_t, eplist_link);
 		last_ed = last->ed;
 	}
@@ -122,12 +122,12 @@
 
 	/* Add to the sw list */
-	list_append(&ep->link, &instance->endpoint_list);
+	list_append(&ep->eplist_link, &instance->endpoint_list);
 
 	ohci_endpoint_t *first = list_get_instance(
-	    list_first(&instance->endpoint_list), ohci_endpoint_t, link);
-	usb_log_debug("HCD EP(%p) added to list %s, first is %p(%p).\n",
+	    list_first(&instance->endpoint_list), ohci_endpoint_t, eplist_link);
+	usb_log_debug("HCD EP(%p) added to list %s, first is %p(%p).",
 		ep, instance->name, first, first->ed);
 	if (last_ed == instance->list_head) {
-		usb_log_debug2("%s head ED(%p-0x%0" PRIx32 "): %x:%x:%x:%x.\n",
+		usb_log_debug2("%s head ED(%p-0x%0" PRIx32 "): %x:%x:%x:%x.",
 		    instance->name, last_ed, instance->list_head_pa,
 		    last_ed->status, last_ed->td_tail, last_ed->td_head,
@@ -151,10 +151,10 @@
 	fibril_mutex_lock(&instance->guard);
 
-	usb_log_debug2("Queue %s: removing endpoint(%p).\n", instance->name, ep);
+	usb_log_debug2("Queue %s: removing endpoint(%p).", instance->name, ep);
 
 	const char *qpos = NULL;
 	ed_t *prev_ed;
 	/* Remove from the hardware queue */
-	if (list_first(&instance->endpoint_list) == &ep->link) {
+	if (list_first(&instance->endpoint_list) == &ep->eplist_link) {
 		/* I'm the first one here */
 		prev_ed = instance->list_head;
@@ -162,5 +162,5 @@
 	} else {
 		ohci_endpoint_t *prev =
-		    list_get_instance(ep->link.prev, ohci_endpoint_t, link);
+		    list_get_instance(ep->eplist_link.prev, ohci_endpoint_t, eplist_link);
 		prev_ed = prev->ed;
 		qpos = "NOT FIRST";
@@ -171,9 +171,9 @@
 	write_barrier();
 
-	usb_log_debug("HCD EP(%p) removed (%s) from %s, next %x.\n",
+	usb_log_debug("HCD EP(%p) removed (%s) from %s, next %x.",
 	    ep, qpos, instance->name, ep->ed->next);
 
 	/* Remove from the endpoint list */
-	list_remove(&ep->link);
+	list_remove(&ep->eplist_link);
 	fibril_mutex_unlock(&instance->guard);
 }
Index: uspace/drv/bus/usb/ohci/endpoint_list.h
===================================================================
--- uspace/drv/bus/usb/ohci/endpoint_list.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ohci/endpoint_list.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -41,5 +41,5 @@
 #include <usb/host/utils/malloc32.h>
 
-#include "ohci_endpoint.h"
+#include "ohci_bus.h"
 #include "hw_struct/endpoint_descriptor.h"
 
Index: uspace/drv/bus/usb/ohci/hc.c
===================================================================
--- uspace/drv/bus/usb/ohci/hc.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ohci/hc.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -45,7 +45,8 @@
 
 #include <usb/debug.h>
+#include <usb/host/utility.h>
 #include <usb/usb.h>
 
-#include "ohci_endpoint.h"
+#include "ohci_bus.h"
 #include "ohci_batch.h"
 
@@ -89,6 +90,4 @@
 };
 
-static void hc_gain_control(hc_t *instance);
-static void hc_start(hc_t *instance);
 static errno_t hc_init_transfer_lists(hc_t *instance);
 static errno_t hc_init_memory(hc_t *instance);
@@ -103,5 +102,5 @@
  * @return Error code.
  */
-errno_t ohci_hc_gen_irq_code(irq_code_t *code, const hw_res_list_parsed_t *hw_res, int *irq)
+errno_t hc_gen_irq_code(irq_code_t *code, hc_device_t *hcd, const hw_res_list_parsed_t *hw_res, int *irq)
 {
 	assert(code);
@@ -138,5 +137,5 @@
 	OHCI_WR(code->cmds[1].value, OHCI_USED_INTERRUPTS);
 
-	usb_log_debug("Memory mapped regs at %p (size %zu), IRQ %d.\n",
+	usb_log_debug("Memory mapped regs at %p (size %zu), IRQ %d.",
 	    RNGABSPTR(regs), RNGSZ(regs), hw_res->irqs.irqs[0]);
 
@@ -152,7 +151,7 @@
  * @return Error code
  */
-errno_t hc_init(hc_t *instance, const hw_res_list_parsed_t *hw_res, bool interrupts)
-{
-	assert(instance);
+errno_t hc_add(hc_device_t *hcd, const hw_res_list_parsed_t *hw_res)
+{
+	hc_t *instance = hcd_to_hc(hcd);
 	assert(hw_res);
 	if (hw_res->mem_ranges.count != 1 ||
@@ -163,19 +162,18 @@
 	    (void **) &instance->registers);
 	if (ret != EOK) {
-		usb_log_error("Failed to gain access to registers: %s.\n",
+		usb_log_error("Failed to gain access to registers: %s.",
 		    str_error(ret));
 		return ret;
 	}
-	usb_log_debug("Device registers at %" PRIx64 " (%zuB) accessible.\n",
+	usb_log_debug("Device registers at %" PRIx64 " (%zuB) accessible.",
 	    hw_res->mem_ranges.ranges[0].address.absolute,
 	    hw_res->mem_ranges.ranges[0].size);
 
-	list_initialize(&instance->pending_batches);
+	list_initialize(&instance->pending_endpoints);
 	fibril_mutex_initialize(&instance->guard);
-	instance->hw_interrupts = interrupts;
 
 	ret = hc_init_memory(instance);
 	if (ret != EOK) {
-		usb_log_error("Failed to create OHCI memory structures: %s.\n",
+		usb_log_error("Failed to create OHCI memory structures: %s.",
 		    str_error(ret));
 		// TODO: We should disable pio access here
@@ -183,9 +181,4 @@
 	}
 
-	hc_gain_control(instance);
-
-	ohci_rh_init(&instance->rh, instance->registers, "ohci rh");
-	hc_start(instance);
-
 	return EOK;
 }
@@ -195,9 +188,10 @@
  * @param[in] instance Host controller structure to use.
  */
-void hc_fini(hc_t *instance)
+int hc_gone(hc_device_t *instance)
 {
 	assert(instance);
 	/* TODO: implement*/
-};
+	return ENOTSUP;
+}
 
 void hc_enqueue_endpoint(hc_t *instance, const endpoint_t *ep)
@@ -269,14 +263,16 @@
 }
 
-errno_t ohci_hc_status(hcd_t *hcd, uint32_t *status)
-{
-	assert(hcd);
+errno_t ohci_hc_status(bus_t *bus_base, uint32_t *status)
+{
+	assert(bus_base);
 	assert(status);
-	hc_t *instance = hcd_get_driver_data(hcd);
-	assert(instance);
-
-	if (instance->registers){
-		*status = OHCI_RD(instance->registers->interrupt_status);
-		OHCI_WR(instance->registers->interrupt_status, *status);
+
+	ohci_bus_t *bus = (ohci_bus_t *) bus_base;
+	hc_t *hc = bus->hc;
+	assert(hc);
+
+	if (hc->registers){
+		*status = OHCI_RD(hc->registers->interrupt_status);
+		OHCI_WR(hc->registers->interrupt_status, *status);
 	}
 	return EOK;
@@ -289,22 +285,35 @@
  * @return Error code.
  */
-errno_t ohci_hc_schedule(hcd_t *hcd, usb_transfer_batch_t *batch)
-{
-	assert(hcd);
-	hc_t *instance = hcd_get_driver_data(hcd);
-	assert(instance);
+errno_t ohci_hc_schedule(usb_transfer_batch_t *batch)
+{
+	assert(batch);
+
+	ohci_bus_t *bus = (ohci_bus_t *) endpoint_get_bus(batch->ep);
+	hc_t *hc = bus->hc;
+	assert(hc);
 
 	/* Check for root hub communication */
-	if (batch->ep->address == ohci_rh_get_address(&instance->rh)) {
-		usb_log_debug("OHCI root hub request.\n");
-		return ohci_rh_schedule(&instance->rh, batch);
-	}
+	if (batch->target.address == ohci_rh_get_address(&hc->rh)) {
+		usb_log_debug("OHCI root hub request.");
+		return ohci_rh_schedule(&hc->rh, batch);
+	}
+
+	endpoint_t *ep = batch->ep;
+	ohci_endpoint_t * const ohci_ep = ohci_endpoint_get(ep);
 	ohci_transfer_batch_t *ohci_batch = ohci_transfer_batch_get(batch);
-	if (!ohci_batch)
-		return ENOMEM;
-
-	fibril_mutex_lock(&instance->guard);
-	list_append(&ohci_batch->link, &instance->pending_batches);
+	int err;
+
+	fibril_mutex_lock(&hc->guard);
+	if ((err = endpoint_activate_locked(ep, batch))) {
+		fibril_mutex_unlock(&hc->guard);
+		return err;
+	}
+
+	if ((err = ohci_transfer_batch_prepare(ohci_batch)))
+		return err;
+
 	ohci_transfer_batch_commit(ohci_batch);
+	list_append(&ohci_ep->pending_link, &hc->pending_endpoints);
+	fibril_mutex_unlock(&hc->guard);
 
 	/* Control and bulk schedules need a kick to start working */
@@ -312,13 +321,13 @@
 	{
 	case USB_TRANSFER_CONTROL:
-		OHCI_SET(instance->registers->command_status, CS_CLF);
+		OHCI_SET(hc->registers->command_status, CS_CLF);
 		break;
 	case USB_TRANSFER_BULK:
-		OHCI_SET(instance->registers->command_status, CS_BLF);
+		OHCI_SET(hc->registers->command_status, CS_BLF);
 		break;
 	default:
 		break;
 	}
-	fibril_mutex_unlock(&instance->guard);
+
 	return EOK;
 }
@@ -329,43 +338,49 @@
  * @param[in] status Value of the status register at the time of interrupt.
  */
-void ohci_hc_interrupt(hcd_t *hcd, uint32_t status)
-{
-	assert(hcd);
-	hc_t *instance = hcd_get_driver_data(hcd);
+void ohci_hc_interrupt(bus_t *bus_base, uint32_t status)
+{
+	assert(bus_base);
+
+	ohci_bus_t *bus = (ohci_bus_t *) bus_base;
+	hc_t *hc = bus->hc;
+	assert(hc);
+
 	status = OHCI_RD(status);
-	assert(instance);
+	assert(hc);
 	if ((status & ~I_SF) == 0) /* ignore sof status */
 		return;
-	usb_log_debug2("OHCI(%p) interrupt: %x.\n", instance, status);
+	usb_log_debug2("OHCI(%p) interrupt: %x.", hc, status);
 	if (status & I_RHSC)
-		ohci_rh_interrupt(&instance->rh);
+		ohci_rh_interrupt(&hc->rh);
 
 	if (status & I_WDH) {
-		fibril_mutex_lock(&instance->guard);
-		usb_log_debug2("HCCA: %p-%#" PRIx32 " (%p).\n", instance->hcca,
-		    OHCI_RD(instance->registers->hcca),
-		    (void *) addr_to_phys(instance->hcca));
-		usb_log_debug2("Periodic current: %#" PRIx32 ".\n",
-		    OHCI_RD(instance->registers->periodic_current));
-
-		link_t *current = list_first(&instance->pending_batches);
-		while (current && current != &instance->pending_batches.head) {
-			link_t *next = current->next;
-			ohci_transfer_batch_t *batch =
-			    ohci_transfer_batch_from_link(current);
-
-			if (ohci_transfer_batch_is_complete(batch)) {
+		fibril_mutex_lock(&hc->guard);
+		usb_log_debug2("HCCA: %p-%#" PRIx32 " (%p).", hc->hcca,
+		    OHCI_RD(hc->registers->hcca),
+		    (void *) addr_to_phys(hc->hcca));
+		usb_log_debug2("Periodic current: %#" PRIx32 ".",
+		    OHCI_RD(hc->registers->periodic_current));
+
+		list_foreach_safe(hc->pending_endpoints, current, next) {
+			ohci_endpoint_t *ep
+				= list_get_instance(current, ohci_endpoint_t, pending_link);
+
+			ohci_transfer_batch_t *batch
+				= ohci_transfer_batch_get(ep->base.active_batch);
+			assert(batch);
+
+			if (ohci_transfer_batch_check_completed(batch)) {
+				endpoint_deactivate_locked(&ep->base);
 				list_remove(current);
-				ohci_transfer_batch_finish_dispose(batch);
+				hc_reset_toggles(&batch->base, &ohci_ep_toggle_reset);
+				usb_transfer_batch_finish(&batch->base);
 			}
-
-			current = next;
 		}
-		fibril_mutex_unlock(&instance->guard);
+		fibril_mutex_unlock(&hc->guard);
 	}
 
 	if (status & I_UE) {
-		usb_log_fatal("Error like no other!\n");
-		hc_start(instance);
+		usb_log_fatal("Error like no other!");
+		hc_start(&hc->base);
 	}
 
@@ -379,9 +394,9 @@
  * @param[in] instance OHCI hc driver structure.
  */
-void hc_gain_control(hc_t *instance)
-{
-	assert(instance);
-
-	usb_log_debug("Requesting OHCI control.\n");
+int hc_gain_control(hc_device_t *hcd)
+{
+	hc_t *instance = hcd_to_hc(hcd);
+
+	usb_log_debug("Requesting OHCI control.");
 	if (OHCI_RD(instance->registers->revision) & R_LEGACY_FLAG) {
 		/* Turn off legacy emulation, it should be enough to zero
@@ -392,5 +407,5 @@
 		volatile uint32_t *ohci_emulation_reg =
 		(uint32_t*)((char*)instance->registers + LEGACY_REGS_OFFSET);
-		usb_log_debug("OHCI legacy register %p: %x.\n",
+		usb_log_debug("OHCI legacy register %p: %x.",
 		    ohci_emulation_reg, OHCI_RD(*ohci_emulation_reg));
 		/* Zero everything but A20State */
@@ -398,5 +413,5 @@
 		OHCI_CLR(*ohci_emulation_reg, ~0x100);
 		usb_log_debug(
-		    "OHCI legacy register (should be 0 or 0x100) %p: %x.\n",
+		    "OHCI legacy register (should be 0 or 0x100) %p: %x.",
 		    ohci_emulation_reg, OHCI_RD(*ohci_emulation_reg));
 	}
@@ -404,5 +419,5 @@
 	/* Interrupt routing enabled => smm driver is active */
 	if (OHCI_RD(instance->registers->control) & C_IR) {
-		usb_log_debug("SMM driver: request ownership change.\n");
+		usb_log_debug("SMM driver: request ownership change.");
 		// TODO: should we ack interrupts before doing this?
 		OHCI_SET(instance->registers->command_status, CS_OCR);
@@ -411,8 +426,8 @@
 			async_usleep(1000);
 		}
-		usb_log_info("SMM driver: Ownership taken.\n");
+		usb_log_info("SMM driver: Ownership taken.");
 		C_HCFS_SET(instance->registers->control, C_HCFS_RESET);
 		async_usleep(50000);
-		return;
+		return EOK;
 	}
 
@@ -420,20 +435,21 @@
 	/* Interrupt routing disabled && status != USB_RESET => BIOS active */
 	if (hc_status != C_HCFS_RESET) {
-		usb_log_debug("BIOS driver found.\n");
+		usb_log_debug("BIOS driver found.");
 		if (hc_status == C_HCFS_OPERATIONAL) {
-			usb_log_info("BIOS driver: HC operational.\n");
-			return;
+			usb_log_info("BIOS driver: HC operational.");
+			return EOK;
 		}
 		/* HC is suspended assert resume for 20ms */
 		C_HCFS_SET(instance->registers->control, C_HCFS_RESUME);
 		async_usleep(20000);
-		usb_log_info("BIOS driver: HC resumed.\n");
-		return;
+		usb_log_info("BIOS driver: HC resumed.");
+		return EOK;
 	}
 
 	/* HC is in reset (hw startup) => no other driver
 	 * maintain reset for at least the time specified in USB spec (50 ms)*/
-	usb_log_debug("Host controller found in reset state.\n");
+	usb_log_debug("Host controller found in reset state.");
 	async_usleep(50000);
+	return EOK;
 }
 
@@ -442,16 +458,19 @@
  * @param[in] instance OHCI hc driver structure.
  */
-void hc_start(hc_t *instance)
-{
+int hc_start(hc_device_t *hcd)
+{
+	hc_t *instance = hcd_to_hc(hcd);
+	ohci_rh_init(&instance->rh, instance->registers, &instance->guard, "ohci rh");
+
 	/* OHCI guide page 42 */
 	assert(instance);
-	usb_log_debug2("Started hc initialization routine.\n");
+	usb_log_debug2("Started hc initialization routine.");
 
 	/* Save contents of fm_interval register */
 	const uint32_t fm_interval = OHCI_RD(instance->registers->fm_interval);
-	usb_log_debug2("Old value of HcFmInterval: %x.\n", fm_interval);
+	usb_log_debug2("Old value of HcFmInterval: %x.", fm_interval);
 
 	/* Reset hc */
-	usb_log_debug2("HC reset.\n");
+	usb_log_debug2("HC reset.");
 	size_t time = 0;
 	OHCI_WR(instance->registers->command_status, CS_HCR);
@@ -460,5 +479,5 @@
 		time += 10;
 	}
-	usb_log_debug2("HC reset complete in %zu us.\n", time);
+	usb_log_debug2("HC reset complete in %zu us.", time);
 
 	/* Restore fm_interval */
@@ -467,5 +486,5 @@
 
 	/* hc is now in suspend state */
-	usb_log_debug2("HC should be in suspend state(%x).\n",
+	usb_log_debug2("HC should be in suspend state(%x).",
 	    OHCI_RD(instance->registers->control));
 
@@ -476,5 +495,5 @@
 	OHCI_WR(instance->registers->bulk_head,
 	    instance->lists[USB_TRANSFER_BULK].list_head_pa);
-	usb_log_debug2("Bulk HEAD set to: %p (%#" PRIx32 ").\n",
+	usb_log_debug2("Bulk HEAD set to: %p (%#" PRIx32 ").",
 	    instance->lists[USB_TRANSFER_BULK].list_head,
 	    instance->lists[USB_TRANSFER_BULK].list_head_pa);
@@ -482,5 +501,5 @@
 	OHCI_WR(instance->registers->control_head,
 	    instance->lists[USB_TRANSFER_CONTROL].list_head_pa);
-	usb_log_debug2("Control HEAD set to: %p (%#" PRIx32 ").\n",
+	usb_log_debug2("Control HEAD set to: %p (%#" PRIx32 ").",
 	    instance->lists[USB_TRANSFER_CONTROL].list_head,
 	    instance->lists[USB_TRANSFER_CONTROL].list_head_pa);
@@ -488,12 +507,12 @@
 	/* Enable queues */
 	OHCI_SET(instance->registers->control, (C_PLE | C_IE | C_CLE | C_BLE));
-	usb_log_debug("Queues enabled(%x).\n",
+	usb_log_debug("Queues enabled(%x).",
 	    OHCI_RD(instance->registers->control));
 
 	/* Enable interrupts */
-	if (instance->hw_interrupts) {
+	if (instance->base.irq_cap >= 0) {
 		OHCI_WR(instance->registers->interrupt_enable,
 		    OHCI_USED_INTERRUPTS);
-		usb_log_debug("Enabled interrupts: %x.\n",
+		usb_log_debug("Enabled interrupts: %x.",
 		    OHCI_RD(instance->registers->interrupt_enable));
 		OHCI_WR(instance->registers->interrupt_enable, I_MI);
@@ -505,10 +524,20 @@
 	OHCI_WR(instance->registers->periodic_start,
 	    ((frame_length / 10) * 9) & PS_MASK << PS_SHIFT);
-	usb_log_debug2("All periodic start set to: %x(%u - 90%% of %d).\n",
+	usb_log_debug2("All periodic start set to: %x(%u - 90%% of %d).",
 	    OHCI_RD(instance->registers->periodic_start),
 	    OHCI_RD(instance->registers->periodic_start), frame_length);
 	C_HCFS_SET(instance->registers->control, C_HCFS_OPERATIONAL);
-	usb_log_debug("OHCI HC up and running (ctl_reg=0x%x).\n",
+	usb_log_debug("OHCI HC up and running (ctl_reg=0x%x).",
 	    OHCI_RD(instance->registers->control));
+
+	return EOK;
+}
+
+/**
+ * Setup roothub as a virtual hub.
+ */
+int hc_setup_roothub(hc_device_t *hcd)
+{
+	return hc_setup_virtual_root_hub(hcd, USB_SPEED_FULL);
 }
 
@@ -526,5 +555,5 @@
 	const errno_t ret = endpoint_list_init(&instance->lists[type], name); \
 	if (ret != EOK) { \
-		usb_log_error("Failed to setup %s endpoint list: %s.\n", \
+		usb_log_error("Failed to setup %s endpoint list: %s.", \
 		    name, str_error(ret)); \
 		endpoint_list_fini(&instance->lists[USB_TRANSFER_ISOCHRONOUS]);\
@@ -558,5 +587,5 @@
 	memset(&instance->rh, 0, sizeof(instance->rh));
 	/* Init queues */
-	const errno_t ret = hc_init_transfer_lists(instance);
+	errno_t ret = hc_init_transfer_lists(instance);
 	if (ret != EOK) {
 		return ret;
@@ -567,5 +596,5 @@
 	if (instance->hcca == NULL)
 		return ENOMEM;
-	usb_log_debug2("OHCI HCCA initialized at %p.\n", instance->hcca);
+	usb_log_debug2("OHCI HCCA initialized at %p.", instance->hcca);
 
 	for (unsigned i = 0; i < HCCA_INT_EP_COUNT; ++i) {
@@ -573,8 +602,16 @@
 		    instance->lists[USB_TRANSFER_INTERRUPT].list_head_pa);
 	}
-	usb_log_debug2("Interrupt HEADs set to: %p (%#" PRIx32 ").\n",
+	usb_log_debug2("Interrupt HEADs set to: %p (%#" PRIx32 ").",
 	    instance->lists[USB_TRANSFER_INTERRUPT].list_head,
 	    instance->lists[USB_TRANSFER_INTERRUPT].list_head_pa);
 
+	if ((ret = ohci_bus_init(&instance->bus, instance))) {
+		usb_log_error("HC(%p): Failed to setup bus : %s",
+		    instance, str_error(ret));
+		return ret;
+	}
+
+	hc_device_setup(&instance->base, (bus_t *) &instance->bus);
+
 	return EOK;
 }
Index: uspace/drv/bus/usb/ohci/hc.h
===================================================================
--- uspace/drv/bus/usb/ohci/hc.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ohci/hc.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -52,4 +52,5 @@
 #include "ohci_regs.h"
 #include "ohci_rh.h"
+#include "ohci_bus.h"
 #include "endpoint_list.h"
 #include "hw_struct/hcca.h"
@@ -57,6 +58,10 @@
 /** Main OHCI driver structure */
 typedef struct hc {
+	/** Common hcd header */
+	hc_device_t base;
+
 	/** Memory mapped I/O registers area */
 	ohci_regs_t *registers;
+	
 	/** Host controller communication area structure */
 	hcca_t *hcca;
@@ -64,31 +69,37 @@
 	/** Transfer schedules */
 	endpoint_list_t lists[4];
-	/** List of active transfers */
-	list_t pending_batches;
 
-	/** Fibril for periodic checks if interrupts can't be used */
-	fid_t interrupt_emulator;
+	/** List of active endpoints */
+	list_t pending_endpoints;
 
 	/** Guards schedule and endpoint manipulation */
 	fibril_mutex_t guard;
 
-	/** interrupts available */
-	bool hw_interrupts;
-
 	/** USB hub emulation structure */
 	ohci_rh_t rh;
+
+	/** USB bookkeeping */
+	ohci_bus_t bus;
 } hc_t;
 
-extern errno_t hc_init(hc_t *, const hw_res_list_parsed_t *, bool);
-extern void hc_fini(hc_t *);
+static inline hc_t * hcd_to_hc(hc_device_t *hcd)
+{
+	assert(hcd);
+	return (hc_t *) hcd;
+}
+
+extern errno_t hc_add(hc_device_t *, const hw_res_list_parsed_t *);
+extern errno_t hc_gen_irq_code(irq_code_t *, hc_device_t *, const hw_res_list_parsed_t *, int *);
+extern errno_t hc_gain_control(hc_device_t *);
+extern errno_t hc_start(hc_device_t *);
+extern errno_t hc_setup_roothub(hc_device_t *);
+extern errno_t hc_gone(hc_device_t *);
 
 extern void hc_enqueue_endpoint(hc_t *, const endpoint_t *);
 extern void hc_dequeue_endpoint(hc_t *, const endpoint_t *);
 
-errno_t ohci_hc_gen_irq_code(irq_code_t *code, const hw_res_list_parsed_t *hw_res, int *irq);
-
-extern void ohci_hc_interrupt(hcd_t *, uint32_t);
-extern errno_t ohci_hc_status(hcd_t *, uint32_t *);
-extern errno_t ohci_hc_schedule(hcd_t *, usb_transfer_batch_t *);
+extern errno_t ohci_hc_schedule(usb_transfer_batch_t *);
+extern errno_t ohci_hc_status(bus_t *, uint32_t *);
+extern void ohci_hc_interrupt(bus_t *, uint32_t);
 
 #endif
Index: uspace/drv/bus/usb/ohci/hw_struct/endpoint_descriptor.c
===================================================================
--- uspace/drv/bus/usb/ohci/hw_struct/endpoint_descriptor.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ohci/hw_struct/endpoint_descriptor.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -40,4 +40,6 @@
 #include <usb/usb.h>
 #include <usb/host/utils/malloc32.h>
+#include <usb/host/endpoint.h>
+#include <usb/host/bus.h>
 
 #include "mem_access.h"
@@ -79,5 +81,5 @@
 	/* Status: address, endpoint nr, direction mask and max packet size. */
 	OHCI_MEM32_WR(instance->status,
-	    ((ep->address & ED_STATUS_FA_MASK) << ED_STATUS_FA_SHIFT)
+	    ((ep->device->address & ED_STATUS_FA_MASK) << ED_STATUS_FA_SHIFT)
 	    | ((ep->endpoint & ED_STATUS_EN_MASK) << ED_STATUS_EN_SHIFT)
 	    | ((dir[ep->direction] & ED_STATUS_D_MASK) << ED_STATUS_D_SHIFT)
@@ -86,5 +88,5 @@
 
 	/* Low speed flag */
-	if (ep->speed == USB_SPEED_LOW)
+	if (ep->device->speed == USB_SPEED_LOW)
 		OHCI_MEM32_SET(instance->status, ED_STATUS_S_FLAG);
 
@@ -98,9 +100,4 @@
 	OHCI_MEM32_WR(instance->td_head, pa & ED_TDHEAD_PTR_MASK);
 	OHCI_MEM32_WR(instance->td_tail, pa & ED_TDTAIL_PTR_MASK);
-
-	/* Set toggle bit */
-	if (ep->toggle)
-		OHCI_MEM32_SET(instance->td_head, ED_TDHEAD_TOGGLE_CARRY);
-
 }
 /**
Index: uspace/drv/bus/usb/ohci/main.c
===================================================================
--- uspace/drv/bus/usb/ohci/main.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ohci/main.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -39,79 +39,24 @@
 #include <errno.h>
 #include <io/log.h>
+#include <io/logctl.h>
 #include <str_error.h>
 
 #include <usb/debug.h>
-#include <usb/host/ddf_helpers.h>
 
 #include "hc.h"
+#include "ohci_bus.h"
 
 #define NAME "ohci"
-static errno_t ohci_driver_init(hcd_t *, const hw_res_list_parsed_t *, bool);
-static void ohci_driver_fini(hcd_t *);
 
-static const ddf_hc_driver_t ohci_hc_driver = {
-        .hc_speed = USB_SPEED_FULL,
-        .irq_code_gen = ohci_hc_gen_irq_code,
-        .init = ohci_driver_init,
-        .fini = ohci_driver_fini,
-        .name = "OHCI",
-	.ops = {
-                .schedule       = ohci_hc_schedule,
-                .ep_add_hook    = ohci_endpoint_init,
-                .ep_remove_hook = ohci_endpoint_fini,
-                .irq_hook       = ohci_hc_interrupt,
-                .status_hook    = ohci_hc_status,
-	},
-};
+static const hc_driver_t ohci_driver = {
+	.name = NAME,
+	.hc_device_size = sizeof(hc_t),
 
-
-static errno_t ohci_driver_init(hcd_t *hcd, const hw_res_list_parsed_t *res, bool irq)
-{
-	assert(hcd);
-	assert(hcd_get_driver_data(hcd) == NULL);
-
-	hc_t *instance = malloc(sizeof(hc_t));
-	if (!instance)
-		return ENOMEM;
-
-	const errno_t ret = hc_init(instance, res, irq);
-	if (ret == EOK) {
-		hcd_set_implementation(hcd, instance, &ohci_hc_driver.ops);
-	} else {
-		free(instance);
-	}
-	return ret;
-}
-
-static void ohci_driver_fini(hcd_t *hcd)
-{
-	assert(hcd);
-	hc_t *hc = hcd_get_driver_data(hcd);
-	if (hc)
-		hc_fini(hc);
-
-	hcd_set_implementation(hcd, NULL, NULL);
-	free(hc);
-}
-
-/** Initializes a new ddf driver instance of OHCI hcd.
- *
- * @param[in] device DDF instance of the device to initialize.
- * @return Error code.
- */
-static errno_t ohci_dev_add(ddf_dev_t *device)
-{
-	usb_log_debug("ohci_dev_add() called\n");
-	assert(device);
-	return hcd_ddf_add_hc(device, &ohci_hc_driver);
-}
-
-static const driver_ops_t ohci_driver_ops = {
-	.dev_add = ohci_dev_add,
-};
-
-static const driver_t ohci_driver = {
-	.name = NAME,
-	.driver_ops = &ohci_driver_ops
+	.hc_add = hc_add,
+	.irq_code_gen = hc_gen_irq_code,
+	.claim = hc_gain_control,
+	.start = hc_start,
+	.setup_root_hub = hc_setup_roothub,
+	.hc_gone = hc_gone,
 };
 
@@ -127,5 +72,6 @@
 {
 	log_init(NAME);
-	return ddf_driver_main(&ohci_driver);
+	logctl_set_log_level(NAME, LVL_DEBUG2);
+	return hc_driver_main(&ohci_driver);
 }
 
Index: uspace/drv/bus/usb/ohci/ohci_batch.c
===================================================================
--- uspace/drv/bus/usb/ohci/ohci_batch.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ohci/ohci_batch.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -45,7 +45,7 @@
 
 #include "ohci_batch.h"
-#include "ohci_endpoint.h"
-
-static void (*const batch_setup[])(ohci_transfer_batch_t*, usb_direction_t);
+#include "ohci_bus.h"
+
+static void (*const batch_setup[])(ohci_transfer_batch_t*);
 
 /** Safely destructs ohci_transfer_batch_t structure
@@ -53,11 +53,10 @@
  * @param[in] ohci_batch Instance to destroy.
  */
-static void ohci_transfer_batch_dispose(ohci_transfer_batch_t *ohci_batch)
-{
-	if (!ohci_batch)
-		return;
+void ohci_transfer_batch_destroy(ohci_transfer_batch_t *ohci_batch)
+{
+	assert(ohci_batch);
 	if (ohci_batch->tds) {
 		const ohci_endpoint_t *ohci_ep =
-		    ohci_endpoint_get(ohci_batch->usb_batch->ep);
+		    ohci_endpoint_get(ohci_batch->base.ep);
 		assert(ohci_ep);
 		for (unsigned i = 0; i < ohci_batch->td_count; ++i) {
@@ -67,35 +66,17 @@
 		free(ohci_batch->tds);
 	}
-	usb_transfer_batch_destroy(ohci_batch->usb_batch);
 	free32(ohci_batch->device_buffer);
 	free(ohci_batch);
 }
 
-/** Finishes usb_transfer_batch and destroys the structure.
- *
- * @param[in] uhci_batch Instance to finish and destroy.
- */
-void ohci_transfer_batch_finish_dispose(ohci_transfer_batch_t *ohci_batch)
-{
-	assert(ohci_batch);
-	assert(ohci_batch->usb_batch);
-	usb_transfer_batch_finish(ohci_batch->usb_batch,
-	    ohci_batch->device_buffer + ohci_batch->usb_batch->setup_size);
-	ohci_transfer_batch_dispose(ohci_batch);
-}
-
 /** Allocate memory and initialize internal data structure.
  *
- * @param[in] usb_batch Pointer to generic USB batch structure.
+ * @param[in] ep Endpoint for which the batch will be created
  * @return Valid pointer if all structures were successfully created,
  * NULL otherwise.
- *
- * Determines the number of needed transfer descriptors (TDs).
- * Prepares a transport buffer (that is accessible by the hardware).
- * Initializes parameters needed for the transfer and callback.
- */
-ohci_transfer_batch_t * ohci_transfer_batch_get(usb_transfer_batch_t *usb_batch)
-{
-	assert(usb_batch);
+ */
+ohci_transfer_batch_t * ohci_transfer_batch_create(endpoint_t *ep)
+{
+	assert(ep);
 
 	ohci_transfer_batch_t *ohci_batch =
@@ -103,9 +84,29 @@
 	if (!ohci_batch) {
 		usb_log_error("Failed to allocate OHCI batch data.");
-		goto dispose;
-	}
-	link_initialize(&ohci_batch->link);
-	ohci_batch->td_count =
-	    (usb_batch->buffer_size + OHCI_TD_MAX_TRANSFER - 1)
+		return NULL;
+	}
+
+	usb_transfer_batch_init(&ohci_batch->base, ep);
+
+	return ohci_batch;
+}
+
+/** Prepares a batch to be sent.
+ *
+ * Determines the number of needed transfer descriptors (TDs).
+ * Prepares a transport buffer (that is accessible by the hardware).
+ * Initializes parameters needed for the transfer and callback.
+ */
+int ohci_transfer_batch_prepare(ohci_transfer_batch_t *ohci_batch)
+{
+	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;
+
+	ohci_batch->td_count = (usb_batch->buffer_size + OHCI_TD_MAX_TRANSFER - 1)
 	    / OHCI_TD_MAX_TRANSFER;
 	/* Control transfer need Setup and Status stage */
@@ -118,5 +119,5 @@
 	if (!ohci_batch->tds) {
 		usb_log_error("Failed to allocate OHCI transfer descriptors.");
-		goto dispose;
+		return ENOMEM;
 	}
 
@@ -129,8 +130,7 @@
 		if (!ohci_batch->tds[i]) {
 			usb_log_error("Failed to allocate TD %d.", i);
-			goto dispose;
+			return ENOMEM;
 		}
 	}
-
 
 	/* NOTE: OHCI is capable of handling buffer that crosses page boundaries
@@ -138,31 +138,25 @@
 	 * than two pages (the first page is computed using start pointer, the
 	 * other using the end pointer) */
-        if (usb_batch->setup_size + usb_batch->buffer_size > 0) {
+        if (setup_size + usb_batch->buffer_size > 0) {
 		/* Use one buffer for setup and data stage */
 		ohci_batch->device_buffer =
-		    malloc32(usb_batch->setup_size + usb_batch->buffer_size);
+		    malloc32(setup_size + usb_batch->buffer_size);
 		if (!ohci_batch->device_buffer) {
 			usb_log_error("Failed to allocate device buffer");
-			goto dispose;
+			return ENOMEM;
 		}
 		/* Copy setup data */
-                memcpy(ohci_batch->device_buffer, usb_batch->setup_buffer,
-		    usb_batch->setup_size);
+                memcpy(ohci_batch->device_buffer, usb_batch->setup.buffer, setup_size);
 		/* Copy generic data */
 		if (usb_batch->ep->direction != USB_DIRECTION_IN)
 			memcpy(
-			    ohci_batch->device_buffer + usb_batch->setup_size,
+			    ohci_batch->device_buffer + setup_size,
 			    usb_batch->buffer, usb_batch->buffer_size);
         }
-	ohci_batch->usb_batch = usb_batch;
-
-	const usb_direction_t dir = usb_transfer_batch_direction(usb_batch);
+
 	assert(batch_setup[usb_batch->ep->transfer_type]);
-	batch_setup[usb_batch->ep->transfer_type](ohci_batch, dir);
-
-	return ohci_batch;
-dispose:
-	ohci_transfer_batch_dispose(ohci_batch);
-	return NULL;
+	batch_setup[usb_batch->ep->transfer_type](ohci_batch);
+
+	return EOK;
 }
 
@@ -176,12 +170,11 @@
  * completes with the last TD.
  */
-bool ohci_transfer_batch_is_complete(const ohci_transfer_batch_t *ohci_batch)
-{
-	assert(ohci_batch);
-	assert(ohci_batch->usb_batch);
-
-	usb_log_debug("Batch %p checking %zu td(s) for completion.\n",
-	    ohci_batch->usb_batch, ohci_batch->td_count);
-	usb_log_debug2("ED: %08x:%08x:%08x:%08x.\n",
+bool ohci_transfer_batch_check_completed(ohci_transfer_batch_t *ohci_batch)
+{
+	assert(ohci_batch);
+
+	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);
@@ -194,6 +187,5 @@
 
 	/* Assume all data got through */
-	ohci_batch->usb_batch->transfered_size =
-	    ohci_batch->usb_batch->buffer_size;
+	ohci_batch->base.transferred_size = ohci_batch->base.buffer_size;
 
 	/* Assume we will leave the last(unused) TD behind */
@@ -203,10 +195,10 @@
 	for (size_t i = 0; i < ohci_batch->td_count; ++i) {
 		assert(ohci_batch->tds[i] != NULL);
-		usb_log_debug("TD %zu: %08x:%08x:%08x:%08x.\n", i,
+		usb_log_debug("TD %zu: %08x:%08x:%08x:%08x.", i,
 		    ohci_batch->tds[i]->status, ohci_batch->tds[i]->cbp,
 		    ohci_batch->tds[i]->next, ohci_batch->tds[i]->be);
 
-		ohci_batch->usb_batch->error = td_error(ohci_batch->tds[i]);
-		if (ohci_batch->usb_batch->error == EOK) {
+		ohci_batch->base.error = td_error(ohci_batch->tds[i]);
+		if (ohci_batch->base.error == EOK) {
 			/* If the TD got all its data through, it will report
 			 * 0 bytes remain, the sole exception is INPUT with
@@ -221,9 +213,9 @@
 			 * we leave the very last(unused) TD behind.
 			 */
-			ohci_batch->usb_batch->transfered_size
+			ohci_batch->base.transferred_size
 			    -= td_remain_size(ohci_batch->tds[i]);
 		} else {
-			usb_log_debug("Batch %p found error TD(%zu):%08x.\n",
-			    ohci_batch->usb_batch, i,
+			usb_log_debug("Batch %p found error TD(%zu):%08x.",
+			    &ohci_batch->base, i,
 			    ohci_batch->tds[i]->status);
 
@@ -252,9 +244,18 @@
 		}
 	}
-	assert(ohci_batch->usb_batch->transfered_size <=
-	    ohci_batch->usb_batch->buffer_size);
+	assert(ohci_batch->base.transferred_size <=
+	    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->base.transferred_size);
 
 	/* Store the remaining TD */
-	ohci_endpoint_t *ohci_ep = ohci_endpoint_get(ohci_batch->usb_batch->ep);
+	ohci_endpoint_t *ohci_ep = ohci_endpoint_get(ohci_batch->base.ep);
 	assert(ohci_ep);
 	ohci_ep->td = ohci_batch->tds[leave_td];
@@ -286,10 +287,12 @@
  * Status stage with toggle 1 and direction supplied by parameter.
  */
-static void batch_control(ohci_transfer_batch_t *ohci_batch, usb_direction_t dir)
-{
-	assert(ohci_batch);
-	assert(ohci_batch->usb_batch);
+static void batch_control(ohci_transfer_batch_t *ohci_batch)
+{
+	assert(ohci_batch);
+
+	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.\n", ohci_batch->ed,
+
+	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);
@@ -307,13 +310,13 @@
 	td_init(
 	    ohci_batch->tds[0], ohci_batch->tds[1], USB_DIRECTION_BOTH,
-	    buffer, ohci_batch->usb_batch->setup_size, toggle);
-	usb_log_debug("Created CONTROL SETUP TD: %08x:%08x:%08x:%08x.\n",
+	    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 += ohci_batch->usb_batch->setup_size;
+	buffer += USB_SETUP_PACKET_SIZE;
 
 	/* Data stage */
 	size_t td_current = 1;
-	size_t remain_size = ohci_batch->usb_batch->buffer_size;
+	size_t remain_size = ohci_batch->base.buffer_size;
 	while (remain_size > 0) {
 		const size_t transfer_size =
@@ -324,5 +327,5 @@
 		    ohci_batch->tds[td_current + 1],
 		    data_dir, buffer, transfer_size, toggle);
-		usb_log_debug("Created CONTROL DATA TD: %08x:%08x:%08x:%08x.\n",
+		usb_log_debug("Created CONTROL DATA TD: %08x:%08x:%08x:%08x.",
 		    ohci_batch->tds[td_current]->status,
 		    ohci_batch->tds[td_current]->cbp,
@@ -340,5 +343,5 @@
 	td_init(ohci_batch->tds[td_current], ohci_batch->tds[td_current + 1],
 	    status_dir, NULL, 0, 1);
-	usb_log_debug("Created CONTROL STATUS TD: %08x:%08x:%08x:%08x.\n",
+	usb_log_debug("Created CONTROL STATUS TD: %08x:%08x:%08x:%08x.",
 	    ohci_batch->tds[td_current]->status,
 	    ohci_batch->tds[td_current]->cbp,
@@ -346,9 +349,9 @@
 	    ohci_batch->tds[td_current]->be);
 	usb_log_debug2(
-	    "Batch %p %s %s " USB_TRANSFER_BATCH_FMT " initialized.\n", \
-	    ohci_batch->usb_batch,
-	    usb_str_transfer_type(ohci_batch->usb_batch->ep->transfer_type),
+	    "Batch %p %s %s " USB_TRANSFER_BATCH_FMT " initialized.", \
+	    &ohci_batch->base,
+	    usb_str_transfer_type(ohci_batch->base.ep->transfer_type),
 	    usb_str_direction(dir),
-	    USB_TRANSFER_BATCH_ARGS(*ohci_batch->usb_batch));
+	    USB_TRANSFER_BATCH_ARGS(ohci_batch->base));
 }
 
@@ -361,15 +364,16 @@
  * OHCI hw in ED.
  */
-static void batch_data(ohci_transfer_batch_t *ohci_batch, usb_direction_t dir)
-{
-	assert(ohci_batch);
-	assert(ohci_batch->usb_batch);
+static void batch_data(ohci_transfer_batch_t *ohci_batch)
+{
+	assert(ohci_batch);
+
+	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.\n", ohci_batch->ed,
+	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->usb_batch->buffer_size;
+	size_t remain_size = ohci_batch->base.buffer_size;
 	char *buffer = ohci_batch->device_buffer;
 	while (remain_size > 0) {
@@ -381,5 +385,5 @@
 		    dir, buffer, transfer_size, -1);
 
-		usb_log_debug("Created DATA TD: %08x:%08x:%08x:%08x.\n",
+		usb_log_debug("Created DATA TD: %08x:%08x:%08x:%08x.",
 		    ohci_batch->tds[td_current]->status,
 		    ohci_batch->tds[td_current]->cbp,
@@ -393,13 +397,13 @@
 	}
 	usb_log_debug2(
-	    "Batch %p %s %s " USB_TRANSFER_BATCH_FMT " initialized.\n", \
-	    ohci_batch->usb_batch,
-	    usb_str_transfer_type(ohci_batch->usb_batch->ep->transfer_type),
+	    "Batch %p %s %s " USB_TRANSFER_BATCH_FMT " initialized.", \
+	    &ohci_batch->base,
+	    usb_str_transfer_type(ohci_batch->base.ep->transfer_type),
 	    usb_str_direction(dir),
-	    USB_TRANSFER_BATCH_ARGS(*ohci_batch->usb_batch));
+	    USB_TRANSFER_BATCH_ARGS(ohci_batch->base));
 }
 
 /** Transfer setup table. */
-static void (*const batch_setup[])(ohci_transfer_batch_t*, usb_direction_t) =
+static void (*const batch_setup[])(ohci_transfer_batch_t*) =
 {
 	[USB_TRANSFER_CONTROL] = batch_control,
Index: uspace/drv/bus/usb/ohci/ohci_batch.h
===================================================================
--- uspace/drv/bus/usb/ohci/ohci_batch.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ohci/ohci_batch.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -45,6 +45,6 @@
 /** OHCI specific data required for USB transfer */
 typedef struct ohci_transfer_batch {
-	/** Link */
-	link_t link;
+	usb_transfer_batch_t base;
+
 	/** Endpoint descriptor of the target endpoint. */
 	ed_t *ed;
@@ -55,18 +55,19 @@
 	/** Data buffer, must be accessible by the OHCI hw. */
 	char *device_buffer;
-	/** Generic USB transfer structure */
-	usb_transfer_batch_t *usb_batch;
 } ohci_transfer_batch_t;
 
-ohci_transfer_batch_t * ohci_transfer_batch_get(usb_transfer_batch_t *batch);
-bool ohci_transfer_batch_is_complete(const ohci_transfer_batch_t *batch);
+ohci_transfer_batch_t * ohci_transfer_batch_create(endpoint_t *batch);
+int ohci_transfer_batch_prepare(ohci_transfer_batch_t *ohci_batch);
 void ohci_transfer_batch_commit(const ohci_transfer_batch_t *batch);
-void ohci_transfer_batch_finish_dispose(ohci_transfer_batch_t *batch);
+bool ohci_transfer_batch_check_completed(ohci_transfer_batch_t *batch);
+void ohci_transfer_batch_destroy(ohci_transfer_batch_t *ohci_batch);
 
-static inline ohci_transfer_batch_t *ohci_transfer_batch_from_link(link_t *l)
+static inline ohci_transfer_batch_t * ohci_transfer_batch_get(usb_transfer_batch_t *usb_batch)
 {
-	assert(l);
-	return list_get_instance(l, ohci_transfer_batch_t, link);
+	assert(usb_batch);
+
+	return (ohci_transfer_batch_t *) usb_batch;
 }
+
 #endif
 /**
Index: uspace/drv/bus/usb/ohci/ohci_bus.c
===================================================================
--- uspace/drv/bus/usb/ohci/ohci_bus.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/ohci/ohci_bus.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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 drvusbohci
+ * @{
+ */
+/** @file
+ * @brief OHCI driver
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <usb/host/utils/malloc32.h>
+#include <usb/host/bandwidth.h>
+
+#include "ohci_bus.h"
+#include "ohci_batch.h"
+#include "hc.h"
+
+/**
+ * Callback to reset toggle on ED.
+ *
+ * @param[in] hcd_ep hcd endpoint structure
+ * @param[in] toggle new value of toggle bit
+ */
+void ohci_ep_toggle_reset(endpoint_t *ep)
+{
+	ohci_endpoint_t *instance = ohci_endpoint_get(ep);
+	assert(instance);
+	assert(instance->ed);
+	ed_toggle_set(instance->ed, 0);
+}
+
+static int ohci_device_enumerate(device_t *dev)
+{
+	ohci_bus_t *bus = (ohci_bus_t *) dev->bus;
+	return usb2_bus_device_enumerate(&bus->helper, dev);
+}
+
+static void ohci_device_gone(device_t *dev)
+{
+	ohci_bus_t *bus = (ohci_bus_t *) dev->bus;
+	usb2_bus_device_gone(&bus->helper, dev);
+}
+
+/** Creates new hcd endpoint representation.
+ */
+static endpoint_t *ohci_endpoint_create(device_t *dev, const usb_endpoint_descriptors_t *desc)
+{
+	assert(dev);
+
+	ohci_endpoint_t *ohci_ep = malloc(sizeof(ohci_endpoint_t));
+	if (ohci_ep == NULL)
+		return NULL;
+
+	endpoint_init(&ohci_ep->base, dev, desc);
+
+	ohci_ep->ed = malloc32(sizeof(ed_t));
+	if (ohci_ep->ed == NULL) {
+		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;
+	}
+
+	link_initialize(&ohci_ep->eplist_link);
+	link_initialize(&ohci_ep->pending_link);
+	return &ohci_ep->base;
+}
+
+/** Disposes hcd endpoint structure
+ *
+ * @param[in] hcd driver using this instance.
+ * @param[in] ep endpoint structure.
+ */
+static void ohci_endpoint_destroy(endpoint_t *ep)
+{
+	assert(ep);
+	ohci_endpoint_t *instance = ohci_endpoint_get(ep);
+
+	free32(instance->ed);
+	free32(instance->td);
+	free(instance);
+}
+
+
+static int ohci_register_ep(endpoint_t *ep)
+{
+	bus_t *bus_base = endpoint_get_bus(ep);
+	ohci_bus_t *bus = (ohci_bus_t *) bus_base;
+	ohci_endpoint_t *ohci_ep = ohci_endpoint_get(ep);
+
+	const int err = usb2_bus_endpoint_register(&bus->helper, ep);
+	if (err)
+		return err;
+
+	ed_init(ohci_ep->ed, ep, ohci_ep->td);
+	hc_enqueue_endpoint(bus->hc, ep);
+	endpoint_set_online(ep, &bus->hc->guard);
+
+	return EOK;
+}
+
+static void ohci_unregister_ep(endpoint_t *ep)
+{
+	ohci_bus_t * const bus = (ohci_bus_t *) endpoint_get_bus(ep);
+	hc_t * const hc = bus->hc;
+	assert(ep);
+
+	usb2_bus_endpoint_unregister(&bus->helper, ep);
+	hc_dequeue_endpoint(bus->hc, ep);
+
+	/*
+	 * Now we can be sure the active transfer will not be completed,
+	 * as it's out of the schedule, and HC acknowledged it.
+	 */
+
+	ohci_endpoint_t *ohci_ep = ohci_endpoint_get(ep);
+
+	fibril_mutex_lock(&hc->guard);
+	endpoint_set_offline_locked(ep);
+	list_remove(&ohci_ep->pending_link);
+	usb_transfer_batch_t * const batch = ep->active_batch;
+	endpoint_deactivate_locked(ep);
+	fibril_mutex_unlock(&hc->guard);
+
+	if (batch) {
+		batch->error = EINTR;
+		batch->transferred_size = 0;
+		usb_transfer_batch_finish(batch);
+	}
+}
+
+static usb_transfer_batch_t *ohci_create_batch(endpoint_t *ep)
+{
+	ohci_transfer_batch_t *batch = ohci_transfer_batch_create(ep);
+	return &batch->base;
+}
+
+static void ohci_destroy_batch(usb_transfer_batch_t *batch)
+{
+	ohci_transfer_batch_destroy(ohci_transfer_batch_get(batch));
+}
+
+static const bus_ops_t ohci_bus_ops = {
+	.interrupt = ohci_hc_interrupt,
+	.status = ohci_hc_status,
+
+	.device_enumerate = ohci_device_enumerate,
+	.device_gone = ohci_device_gone,
+
+	.endpoint_destroy = ohci_endpoint_destroy,
+	.endpoint_create = ohci_endpoint_create,
+	.endpoint_register = ohci_register_ep,
+	.endpoint_unregister = ohci_unregister_ep,
+
+	.batch_create = ohci_create_batch,
+	.batch_destroy = ohci_destroy_batch,
+	.batch_schedule = ohci_hc_schedule,
+};
+
+
+int ohci_bus_init(ohci_bus_t *bus, hc_t *hc)
+{
+	assert(hc);
+	assert(bus);
+
+	bus_t *bus_base = (bus_t *) bus;
+	bus_init(bus_base, sizeof(device_t));
+	bus_base->ops = &ohci_bus_ops;
+
+	usb2_bus_helper_init(&bus->helper, &bandwidth_accounting_usb11);
+
+	bus->hc = hc;
+
+	return EOK;
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/ohci/ohci_bus.h
===================================================================
--- uspace/drv/bus/usb/ohci/ohci_bus.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/ohci/ohci_bus.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * Copyright (c) 2017 Ondrej Hlavaty <aearsis@eideo.cz>
+ * 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 drvusbohci
+ * @{
+ */
+/** @file
+ * @brief OHCI driver
+ */
+#ifndef DRV_OHCI_HCD_BUS_H
+#define DRV_OHCI_HCD_BUS_H
+
+#include <assert.h>
+#include <adt/list.h>
+#include <usb/host/usb2_bus.h>
+
+#include "hw_struct/endpoint_descriptor.h"
+#include "hw_struct/transfer_descriptor.h"
+
+/** Connector structure linking ED to to prepared TD. */
+typedef struct ohci_endpoint {
+	endpoint_t base;
+
+	/** OHCI endpoint descriptor */
+	ed_t *ed;
+	/** Currently enqueued transfer descriptor */
+	td_t *td;
+	/** Link in endpoint_list*/
+	link_t eplist_link;
+	/** Link in pending_endpoints */
+	link_t pending_link;
+} ohci_endpoint_t;
+
+typedef struct hc hc_t;
+
+typedef struct {
+	bus_t base;
+	usb2_bus_helper_t helper;
+	hc_t *hc;
+} ohci_bus_t;
+
+errno_t ohci_bus_init(ohci_bus_t *, hc_t *);
+void ohci_ep_toggle_reset(endpoint_t *);
+
+/** Get and convert assigned ohci_endpoint_t structure
+ * @param[in] ep USBD endpoint structure.
+ * @return Pointer to assigned hcd endpoint structure
+ */
+static inline ohci_endpoint_t * ohci_endpoint_get(const endpoint_t *ep)
+{
+	assert(ep);
+	return (ohci_endpoint_t *) ep;
+}
+
+#endif
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/ohci/ohci_endpoint.c
===================================================================
--- uspace/drv/bus/usb/ohci/ohci_endpoint.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ 	(revision )
@@ -1,123 +1,0 @@
-/*
- * Copyright (c) 2011 Jan Vesely
- * 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 drvusbohci
- * @{
- */
-/** @file
- * @brief OHCI driver
- */
-
-#include <assert.h>
-#include <stdlib.h>
-#include <usb/host/utils/malloc32.h>
-
-#include "ohci_endpoint.h"
-#include "hc.h"
-
-/** Callback to set toggle on ED.
- *
- * @param[in] hcd_ep hcd endpoint structure
- * @param[in] toggle new value of toggle bit
- */
-static void ohci_ep_toggle_set(void *ohci_ep, int toggle)
-{
-	ohci_endpoint_t *instance = ohci_ep;
-	assert(instance);
-	assert(instance->ed);
-	ed_toggle_set(instance->ed, toggle);
-}
-
-/** Callback to get value of toggle bit.
- *
- * @param[in] hcd_ep hcd endpoint structure
- * @return Current value of toggle bit.
- */
-static int ohci_ep_toggle_get(void *ohci_ep)
-{
-	ohci_endpoint_t *instance = ohci_ep;
-	assert(instance);
-	assert(instance->ed);
-	return ed_toggle_get(instance->ed);
-}
-
-/** Creates new hcd endpoint representation.
- *
- * @param[in] ep USBD endpoint structure
- * @return Error code.
- */
-errno_t ohci_endpoint_init(hcd_t *hcd, endpoint_t *ep)
-{
-	assert(ep);
-	ohci_endpoint_t *ohci_ep = malloc(sizeof(ohci_endpoint_t));
-	if (ohci_ep == NULL)
-		return ENOMEM;
-
-	ohci_ep->ed = malloc32(sizeof(ed_t));
-	if (ohci_ep->ed == NULL) {
-		free(ohci_ep);
-		return ENOMEM;
-	}
-
-	ohci_ep->td = malloc32(sizeof(td_t));
-	if (ohci_ep->td == NULL) {
-		free32(ohci_ep->ed);
-		free(ohci_ep);
-		return ENOMEM;
-	}
-
-	link_initialize(&ohci_ep->link);
-	ed_init(ohci_ep->ed, ep, ohci_ep->td);
-	endpoint_set_hc_data(
-	    ep, ohci_ep, ohci_ep_toggle_get, ohci_ep_toggle_set);
-	hc_enqueue_endpoint(hcd_get_driver_data(hcd), ep);
-	return EOK;
-}
-
-/** Disposes hcd endpoint structure
- *
- * @param[in] hcd driver using this instance.
- * @param[in] ep endpoint structure.
- */
-void ohci_endpoint_fini(hcd_t *hcd, endpoint_t *ep)
-{
-	assert(hcd);
-	assert(ep);
-	ohci_endpoint_t *instance = ohci_endpoint_get(ep);
-	hc_dequeue_endpoint(hcd_get_driver_data(hcd), ep);
-	endpoint_clear_hc_data(ep);
-	if (instance) {
-		free32(instance->ed);
-		free32(instance->td);
-		free(instance);
-	}
-}
-
-/**
- * @}
- */
Index: uspace/drv/bus/usb/ohci/ohci_endpoint.h
===================================================================
--- uspace/drv/bus/usb/ohci/ohci_endpoint.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ 	(revision )
@@ -1,71 +1,0 @@
-/*
- * Copyright (c) 2011 Jan Vesely
- * 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 drvusbohci
- * @{
- */
-/** @file
- * @brief OHCI driver
- */
-#ifndef DRV_OHCI_HCD_ENDPOINT_H
-#define DRV_OHCI_HCD_ENDPOINT_H
-
-#include <assert.h>
-#include <adt/list.h>
-#include <usb/host/endpoint.h>
-#include <usb/host/hcd.h>
-
-#include "hw_struct/endpoint_descriptor.h"
-#include "hw_struct/transfer_descriptor.h"
-
-/** Connector structure linking ED to to prepared TD. */
-typedef struct ohci_endpoint {
-	/** OHCI endpoint descriptor */
-	ed_t *ed;
-	/** Currently enqueued transfer descriptor */
-	td_t *td;
-	/** Linked list used by driver software */
-	link_t link;
-} ohci_endpoint_t;
-
-errno_t ohci_endpoint_init(hcd_t *hcd, endpoint_t *ep);
-void ohci_endpoint_fini(hcd_t *hcd, endpoint_t *ep);
-
-/** Get and convert assigned ohci_endpoint_t structure
- * @param[in] ep USBD endpoint structure.
- * @return Pointer to assigned hcd endpoint structure
- */
-static inline ohci_endpoint_t * ohci_endpoint_get(const endpoint_t *ep)
-{
-	assert(ep);
-	return ep->hc_data.data;
-}
-
-#endif
-/**
- * @}
- */
Index: uspace/drv/bus/usb/ohci/ohci_rh.c
===================================================================
--- uspace/drv/bus/usb/ohci/ohci_rh.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ohci/ohci_rh.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -109,20 +109,21 @@
  * initializes internal virtual hub.
  */
-errno_t ohci_rh_init(ohci_rh_t *instance, ohci_regs_t *regs, const char *name)
+errno_t ohci_rh_init(ohci_rh_t *instance, ohci_regs_t *regs,
+    fibril_mutex_t *guard, const char *name)
 {
 	assert(instance);
 	instance->registers = regs;
 	instance->port_count = OHCI_RD(regs->rh_desc_a) & RHDA_NDS_MASK;
-	usb_log_debug2("rh_desc_a: %x.\n", OHCI_RD(regs->rh_desc_a));
+	usb_log_debug2("rh_desc_a: %x.", OHCI_RD(regs->rh_desc_a));
 	if (instance->port_count > OHCI_MAX_PORTS) {
 		usb_log_warning("OHCI specification does not allow %d ports. "
-		    "Max %d ports will be used.\n", instance->port_count,
+		    "Max %d ports will be used.", instance->port_count,
 		    OHCI_MAX_PORTS);
 		instance->port_count = OHCI_MAX_PORTS;
 	}
-	usb_log_info("%s: Found %u ports.\n", name, instance->port_count);
+	usb_log_info("%s: Found %u ports.", name, instance->port_count);
 
 #if defined OHCI_POWER_SWITCH_no
-	usb_log_info("%s: Set power mode to no power switching.\n", name);
+	usb_log_info("%s: Set power mode to no power switching.", name);
 	/* Set port power mode to no power-switching. (always on) */
 	OHCI_SET(regs->rh_desc_a, RHDA_NPS_FLAG);
@@ -132,5 +133,5 @@
 
 #elif defined OHCI_POWER_SWITCH_ganged
-	usb_log_info("%s: Set power mode to ganged power switching.\n", name);
+	usb_log_info("%s: Set power mode to ganged power switching.", name);
 	/* Set port power mode to ganged power-switching. */
 	OHCI_CLR(regs->rh_desc_a, RHDA_NPS_FLAG);
@@ -144,5 +145,5 @@
 	OHCI_CLR(regs->rh_desc_a, RHDA_OCPM_FLAG);
 #else
-	usb_log_info("%s: Set power mode to per-port power switching.\n", name);
+	usb_log_info("%s: Set power mode to per-port power switching.", name);
 	/* Set port power mode to per port power-switching. */
 	OHCI_CLR(regs->rh_desc_a, RHDA_NPS_FLAG);
@@ -162,5 +163,6 @@
 
 	ohci_rh_hub_desc_init(instance);
-	instance->unfinished_interrupt_transfer = NULL;
+	instance->status_change_endpoint = NULL;
+	instance->guard = guard;
 	return virthub_base_init(&instance->base, name, &ops, instance,
 	    NULL, &instance->hub_descriptor.header, HUB_STATUS_CHANGE_PIPE);
@@ -178,20 +180,27 @@
 	assert(instance);
 	assert(batch);
-	const usb_target_t target = {{
-		.address = batch->ep->address,
-		.endpoint = batch->ep->endpoint,
-	}};
-	batch->error = virthub_base_request(&instance->base, target,
-	    usb_transfer_batch_direction(batch), (void*)batch->setup_buffer,
-	    batch->buffer, batch->buffer_size, &batch->transfered_size);
+	batch->error = virthub_base_request(&instance->base, batch->target,
+	    batch->dir, &batch->setup.packet,
+	    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(instance->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(instance->guard);
 	} else {
-		usb_transfer_batch_finish(batch, NULL);
-		usb_transfer_batch_destroy(batch);
+		usb_transfer_batch_finish(batch);
 	}
 	return EOK;
@@ -207,18 +216,23 @@
 errno_t 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;
+	fibril_mutex_lock(instance->guard);
+	endpoint_t *ep = instance->status_change_endpoint;
+	if (!ep) {
+		fibril_mutex_unlock(instance->guard);
+		return EOK;
+	}
+
+	usb_transfer_batch_t * const batch = ep->active_batch;
+	endpoint_deactivate_locked(ep);
+	instance->status_change_endpoint = NULL;
+	fibril_mutex_unlock(instance->guard);
+
+	endpoint_del_ref(ep);
+
 	if (batch) {
-		const usb_target_t target = {{
-			.address = batch->ep->address,
-			.endpoint = batch->ep->endpoint,
-		}};
-		batch->error = virthub_base_request(&instance->base, target,
-		    usb_transfer_batch_direction(batch),
-		    (void*)batch->setup_buffer,
-		    batch->buffer, batch->buffer_size, &batch->transfered_size);
-		usb_transfer_batch_finish(batch, NULL);
-		usb_transfer_batch_destroy(batch);
+		batch->error = virthub_base_request(&instance->base, batch->target,
+		    batch->dir, &batch->setup.packet,
+		    batch->buffer, batch->buffer_size, &batch->transferred_size);
+		usb_transfer_batch_finish(batch);
 	}
 	return EOK;
@@ -351,10 +365,10 @@
 		}
 
-	case USB_HUB_FEATURE_PORT_ENABLE:         /*1*/
+	case USB2_HUB_FEATURE_PORT_ENABLE:         /*1*/
 		OHCI_WR(hub->registers->rh_port_status[port],
 		    RHPS_CLEAR_PORT_ENABLE);
 		return EOK;
 
-	case USB_HUB_FEATURE_PORT_SUSPEND:        /*2*/
+	case USB2_HUB_FEATURE_PORT_SUSPEND:        /*2*/
 		OHCI_WR(hub->registers->rh_port_status[port],
 		    RHPS_CLEAR_PORT_SUSPEND);
@@ -362,10 +376,10 @@
 
 	case USB_HUB_FEATURE_C_PORT_CONNECTION:   /*16*/
-	case USB_HUB_FEATURE_C_PORT_ENABLE:       /*17*/
-	case USB_HUB_FEATURE_C_PORT_SUSPEND:      /*18*/
+	case USB2_HUB_FEATURE_C_PORT_ENABLE:       /*17*/
+	case USB2_HUB_FEATURE_C_PORT_SUSPEND:      /*18*/
 	case USB_HUB_FEATURE_C_PORT_OVER_CURRENT: /*19*/
 	case USB_HUB_FEATURE_C_PORT_RESET:        /*20*/
 		usb_log_debug2("Clearing port C_CONNECTION, C_ENABLE, "
-		    "C_SUSPEND, C_OC or C_RESET on port %u.\n", port);
+		    "C_SUSPEND, C_OC or C_RESET on port %u.", port);
 		/* Bit offsets correspond to the feature number */
 		OHCI_WR(hub->registers->rh_port_status[port],
@@ -412,9 +426,9 @@
 		/* Fall through, for per port power */
 		/* Fallthrough */
-	case USB_HUB_FEATURE_PORT_ENABLE:  /*1*/
-	case USB_HUB_FEATURE_PORT_SUSPEND: /*2*/
+	case USB2_HUB_FEATURE_PORT_ENABLE:  /*1*/
+	case USB2_HUB_FEATURE_PORT_SUSPEND: /*2*/
 	case USB_HUB_FEATURE_PORT_RESET:   /*4*/
 		usb_log_debug2("Setting port POWER, ENABLE, SUSPEND or RESET "
-		    "on port %u.\n", port);
+		    "on port %u.", port);
 		/* Bit offsets correspond to the feature number */
 		OHCI_WR(hub->registers->rh_port_status[port], 1 << feature);
@@ -462,5 +476,5 @@
 	}
 
-	usb_log_debug2("OHCI root hub interrupt mask: %hx.\n", mask);
+	usb_log_debug2("OHCI root hub interrupt mask: %hx.", mask);
 
 	if (mask == 0)
Index: uspace/drv/bus/usb/ohci/ohci_rh.h
===================================================================
--- uspace/drv/bus/usb/ohci/ohci_rh.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/ohci/ohci_rh.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -61,9 +61,10 @@
 		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;
+	fibril_mutex_t *guard;
 } ohci_rh_t;
 
-errno_t ohci_rh_init(ohci_rh_t *instance, ohci_regs_t *regs, const char *name);
+errno_t ohci_rh_init(ohci_rh_t *, ohci_regs_t *, fibril_mutex_t *, const char *);
 errno_t ohci_rh_schedule(ohci_rh_t *instance, usb_transfer_batch_t *batch);
 errno_t ohci_rh_interrupt(ohci_rh_t *instance);
Index: uspace/drv/bus/usb/uhci/hc.c
===================================================================
--- uspace/drv/bus/usb/uhci/hc.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/uhci/hc.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -50,6 +50,9 @@
 #include <usb/usb.h>
 #include <usb/host/utils/malloc32.h>
+#include <usb/host/bandwidth.h>
+#include <usb/host/utility.h>
 
 #include "uhci_batch.h"
+#include "transfer_list.h"
 #include "hc.h"
 
@@ -107,5 +110,5 @@
  * @return Error code.
  */
-errno_t uhci_hc_gen_irq_code(irq_code_t *code, const hw_res_list_parsed_t *hw_res, int *irq)
+errno_t hc_gen_irq_code(irq_code_t *code, hc_device_t *hcd, const hw_res_list_parsed_t *hw_res, int *irq)
 {
 	assert(code);
@@ -140,5 +143,5 @@
 	code->cmds[3].addr = (void*)&registers->usbsts;
 
-	usb_log_debug("I/O regs at %p (size %zu), IRQ %d.\n",
+	usb_log_debug("I/O regs at %p (size %zu), IRQ %d.",
 	    RNGABSPTR(regs), RNGSZ(regs), hw_res->irqs.irqs[0]);
 
@@ -157,36 +160,24 @@
  * - resume from suspend state (not implemented)
  */
-void uhci_hc_interrupt(hcd_t *hcd, uint32_t status)
-{
-	assert(hcd);
-	hc_t *instance = hcd_get_driver_data(hcd);
-	assert(instance);
+static void hc_interrupt(bus_t *bus, uint32_t status)
+{
+	hc_t *instance = bus_to_hc(bus);
+
 	/* Lower 2 bits are transaction error and transaction complete */
 	if (status & (UHCI_STATUS_INTERRUPT | UHCI_STATUS_ERROR_INTERRUPT)) {
-		LIST_INITIALIZE(done);
-		transfer_list_remove_finished(
-		    &instance->transfers_interrupt, &done);
-		transfer_list_remove_finished(
-		    &instance->transfers_control_slow, &done);
-		transfer_list_remove_finished(
-		    &instance->transfers_control_full, &done);
-		transfer_list_remove_finished(
-		    &instance->transfers_bulk_full, &done);
-
-		list_foreach_safe(done, current, next) {
-			list_remove(current);
-			uhci_transfer_batch_t *batch =
-			    uhci_transfer_batch_from_link(current);
-			uhci_transfer_batch_finish_dispose(batch);
-		}
-	}
+		transfer_list_check_finished(&instance->transfers_interrupt);
+		transfer_list_check_finished(&instance->transfers_control_slow);
+		transfer_list_check_finished(&instance->transfers_control_full);
+		transfer_list_check_finished(&instance->transfers_bulk_full);
+	}
+
 	/* Resume interrupts are not supported */
 	if (status & UHCI_STATUS_RESUME) {
-		usb_log_error("Resume interrupt!\n");
+		usb_log_error("Resume interrupt!");
 	}
 
 	/* Bits 4 and 5 indicate hc error */
 	if (status & (UHCI_STATUS_PROCESS_ERROR | UHCI_STATUS_SYSTEM_ERROR)) {
-		usb_log_error("UHCI hardware failure!.\n");
+		usb_log_error("UHCI hardware failure!.");
 		++instance->hw_failures;
 		transfer_list_abort_all(&instance->transfers_interrupt);
@@ -199,6 +190,6 @@
 			hc_init_hw(instance);
 		} else {
-			usb_log_fatal("Too many UHCI hardware failures!.\n");
-			hc_fini(instance);
+			usb_log_fatal("Too many UHCI hardware failures!.");
+			hc_gone(&instance->base);
 		}
 	}
@@ -216,7 +207,7 @@
  * interrupt fibrils.
  */
-errno_t hc_init(hc_t *instance, const hw_res_list_parsed_t *hw_res, bool interrupts)
-{
-	assert(instance);
+errno_t hc_add(hc_device_t *hcd, const hw_res_list_parsed_t *hw_res)
+{
+	hc_t *instance = hcd_to_hc(hcd);
 	assert(hw_res);
 	if (hw_res->io_ranges.count != 1 ||
@@ -224,5 +215,4 @@
 	    return EINVAL;
 
-	instance->hw_interrupts = interrupts;
 	instance->hw_failures = 0;
 
@@ -231,10 +221,10 @@
 	    (void **) &instance->registers);
 	if (ret != EOK) {
-		usb_log_error("Failed to gain access to registers: %s.\n",
+		usb_log_error("Failed to gain access to registers: %s.",
 	            str_error(ret));
 		return ret;
 	}
 
-	usb_log_debug("Device registers at %" PRIx64 " (%zuB) accessible.\n",
+	usb_log_debug("Device registers at %" PRIx64 " (%zuB) accessible.",
 	    hw_res->io_ranges.ranges[0].address.absolute,
 	    hw_res->io_ranges.ranges[0].size);
@@ -242,5 +232,5 @@
 	ret = hc_init_mem_structures(instance);
 	if (ret != EOK) {
-		usb_log_error("Failed to init UHCI memory structures: %s.\n",
+		usb_log_error("Failed to init UHCI memory structures: %s.",
 		    str_error(ret));
 		// TODO: we should disable pio here
@@ -248,10 +238,19 @@
 	}
 
+	return EOK;
+}
+
+int hc_start(hc_device_t *hcd)
+{
+	hc_t *instance = hcd_to_hc(hcd);
 	hc_init_hw(instance);
 	(void)hc_debug_checker;
 
-	uhci_rh_init(&instance->rh, instance->registers->ports, "uhci");
-
-	return EOK;
+	return uhci_rh_init(&instance->rh, instance->registers->ports, "uhci");
+}
+
+int hc_setup_roothub(hc_device_t *hcd)
+{
+	return hc_setup_virtual_root_hub(hcd, USB_SPEED_FULL);
 }
 
@@ -260,8 +259,9 @@
  * @param[in] instance Host controller structure to use.
  */
-void hc_fini(hc_t *instance)
+int hc_gone(hc_device_t *instance)
 {
 	assert(instance);
 	//TODO Implement
+	return ENOTSUP;
 }
 
@@ -293,5 +293,5 @@
 	pio_write_32(&registers->flbaseadd, pa);
 
-	if (instance->hw_interrupts) {
+	if (instance->base.irq_cap >= 0) {
 		/* Enable all interrupts, but resume interrupt */
 		pio_write_16(&instance->registers->usbintr,
@@ -301,5 +301,5 @@
 	const uint16_t cmd = pio_read_16(&registers->usbcmd);
 	if (cmd != 0)
-		usb_log_warning("Previous command value: %x.\n", cmd);
+		usb_log_warning("Previous command value: %x.", cmd);
 
 	/* Start the hc with large(64B) packet FSBR */
@@ -308,4 +308,135 @@
 }
 
+static usb_transfer_batch_t *create_transfer_batch(endpoint_t *ep)
+{
+	uhci_transfer_batch_t *batch = uhci_transfer_batch_create(ep);
+	return &batch->base;
+}
+
+static void destroy_transfer_batch(usb_transfer_batch_t *batch)
+{
+	uhci_transfer_batch_destroy(uhci_transfer_batch_get(batch));
+}
+
+static endpoint_t *endpoint_create(device_t *device, const usb_endpoint_descriptors_t *desc)
+{
+	endpoint_t *ep = calloc(1, sizeof(uhci_endpoint_t));
+	if (ep)
+		endpoint_init(ep, device, desc);
+	return ep;
+}
+
+static errno_t endpoint_register(endpoint_t *ep)
+{
+	hc_t * const hc = bus_to_hc(endpoint_get_bus(ep));
+
+	const errno_t err = usb2_bus_endpoint_register(&hc->bus_helper, ep);
+	if (err)
+		return err;
+
+	transfer_list_t *list = hc->transfers[ep->device->speed][ep->transfer_type];
+	if (!list)
+		/*
+		 * We don't support this combination (e.g. isochronous). Do not
+		 * fail early, because that would block any device with these
+		 * endpoints from connecting. Instead, make sure these transfers
+		 * are denied soon enough with ENOTSUP not to fail on asserts.
+		 */
+		return EOK;
+
+	endpoint_set_online(ep, &list->guard);
+	return EOK;
+}
+
+static void endpoint_unregister(endpoint_t *ep)
+{
+	hc_t * const hc = bus_to_hc(endpoint_get_bus(ep));
+	usb2_bus_endpoint_unregister(&hc->bus_helper, ep);
+
+	// Check for the roothub, as it does not schedule into lists
+	if (ep->device->address == uhci_rh_get_address(&hc->rh)) {
+		// FIXME: We shall check the roothub for active transfer. But
+		// as it is polling, there is no way to make it stop doing so.
+		// Return after rewriting uhci rh.
+		return;
+	}
+
+	transfer_list_t *list = hc->transfers[ep->device->speed][ep->transfer_type];
+	if (!list)
+		/*
+		 * We don't support this combination (e.g. isochronous),
+		 * so no transfer can be active.
+		 */
+		return;
+
+	fibril_mutex_lock(&list->guard);
+
+	endpoint_set_offline_locked(ep);
+	/* From now on, no other transfer will be scheduled. */
+
+	if (!ep->active_batch) {
+		fibril_mutex_unlock(&list->guard);
+		return;
+	}
+
+	/* First, offer the batch a short chance to be finished. */
+	endpoint_wait_timeout_locked(ep, 10000);
+
+	if (!ep->active_batch) {
+		fibril_mutex_unlock(&list->guard);
+		return;
+	}
+
+	uhci_transfer_batch_t * const batch =
+		uhci_transfer_batch_get(ep->active_batch);
+
+	/* Remove the batch from the schedule to stop it from being finished. */
+	endpoint_deactivate_locked(ep);
+	transfer_list_remove_batch(list, batch);
+
+	fibril_mutex_unlock(&list->guard);
+
+	/*
+	 * We removed the batch from software schedule only, it's still possible
+	 * that HC has it in its caches. Better wait a while before we release
+	 * the buffers.
+	 */
+	async_usleep(20000);
+	batch->base.error = EINTR;
+	batch->base.transferred_size = 0;
+	usb_transfer_batch_finish(&batch->base);
+}
+
+static int device_enumerate(device_t *dev)
+{
+	hc_t * const hc = bus_to_hc(dev->bus);
+	return usb2_bus_device_enumerate(&hc->bus_helper, dev);
+}
+
+static void device_gone(device_t *dev)
+{
+	hc_t * const hc = bus_to_hc(dev->bus);
+	usb2_bus_device_gone(&hc->bus_helper, dev);
+}
+
+static int hc_status(bus_t *, uint32_t *);
+static int hc_schedule(usb_transfer_batch_t *);
+
+static const bus_ops_t uhci_bus_ops = {
+	.interrupt = hc_interrupt,
+	.status = hc_status,
+
+	.device_enumerate = device_enumerate,
+	.device_gone = device_gone,
+
+	.endpoint_create = endpoint_create,
+	.endpoint_register = endpoint_register,
+	.endpoint_unregister = endpoint_unregister,
+
+	.batch_create = create_transfer_batch,
+	.batch_schedule = hc_schedule,
+	.batch_destroy = destroy_transfer_batch,
+};
+
 /** Initialize UHCI hc memory structures.
  *
@@ -321,4 +452,11 @@
 {
 	assert(instance);
+
+	usb2_bus_helper_init(&instance->bus_helper, &bandwidth_accounting_usb11);
+
+	bus_init(&instance->bus, sizeof(device_t));
+	instance->bus.ops = &uhci_bus_ops;
+
+	hc_device_setup(&instance->base, &instance->bus);
 
 	/* Init USB frame list page */
@@ -327,14 +465,15 @@
 		return ENOMEM;
 	}
-	usb_log_debug("Initialized frame list at %p.\n", instance->frame_list);
+	usb_log_debug("Initialized frame list at %p.", instance->frame_list);
 
 	/* Init transfer lists */
 	errno_t ret = hc_init_transfer_lists(instance);
 	if (ret != EOK) {
-		usb_log_error("Failed to initialize transfer lists.\n");
+		usb_log_error("Failed to initialize transfer lists.");
 		return_page(instance->frame_list);
 		return ENOMEM;
 	}
-	usb_log_debug("Initialized transfer lists.\n");
+	list_initialize(&instance->pending_endpoints);
+	usb_log_debug("Initialized transfer lists.");
 
 
@@ -366,5 +505,5 @@
 	errno_t ret = transfer_list_init(&instance->transfers_##type, name); \
 	if (ret != EOK) { \
-		usb_log_error("Failed to setup %s transfer list: %s.\n", \
+		usb_log_error("Failed to setup %s transfer list: %s.", \
 		    name, str_error(ret)); \
 		transfer_list_fini(&instance->transfers_bulk_full); \
@@ -411,10 +550,8 @@
 }
 
-errno_t uhci_hc_status(hcd_t *hcd, uint32_t *status)
-{
-	assert(hcd);
+static errno_t hc_status(bus_t *bus, uint32_t *status)
+{
+	hc_t *instance = bus_to_hc(bus);
 	assert(status);
-	hc_t *instance = hcd_get_driver_data(hcd);
-	assert(instance);
 
 	*status = 0;
@@ -427,34 +564,31 @@
 }
 
-/** Schedule batch for execution.
+/**
+ * Schedule batch for execution.
  *
  * @param[in] instance UHCI structure to use.
  * @param[in] batch Transfer batch to schedule.
  * @return Error code
- *
- * Checks for bandwidth availability and appends the batch to the proper queue.
- */
-errno_t uhci_hc_schedule(hcd_t *hcd, usb_transfer_batch_t *batch)
-{
-	assert(hcd);
-	hc_t *instance = hcd_get_driver_data(hcd);
-	assert(instance);
-	assert(batch);
-
-	if (batch->ep->address == uhci_rh_get_address(&instance->rh))
-		return uhci_rh_schedule(&instance->rh, batch);
-
+ */
+static errno_t hc_schedule(usb_transfer_batch_t *batch)
+{
 	uhci_transfer_batch_t *uhci_batch = uhci_transfer_batch_get(batch);
-	if (!uhci_batch) {
-		usb_log_error("Failed to create UHCI transfer structures.\n");
-		return ENOMEM;
-	}
-
-	transfer_list_t *list =
-	    instance->transfers[batch->ep->speed][batch->ep->transfer_type];
-	assert(list);
-	transfer_list_add_batch(list, uhci_batch);
-
-	return EOK;
+	endpoint_t *ep = batch->ep;
+	hc_t *hc = bus_to_hc(endpoint_get_bus(ep));
+
+	if (batch->target.address == uhci_rh_get_address(&hc->rh))
+		return uhci_rh_schedule(&hc->rh, batch);
+
+	transfer_list_t * const list =
+	    hc->transfers[ep->device->speed][ep->transfer_type];
+
+	if (!list)
+		return ENOTSUP;
+
+	errno_t err;
+	if ((err = uhci_transfer_batch_prepare(uhci_batch)))
+		return err;
+
+	return transfer_list_add_batch(list, uhci_batch);
 }
 
@@ -479,5 +613,5 @@
 
 		if (((cmd & UHCI_CMD_RUN_STOP) != 1) || (sts != 0)) {
-			usb_log_debug2("Command: %X Status: %X Intr: %x\n",
+			usb_log_debug2("Command: %X Status: %X Intr: %x",
 			    cmd, sts, intr);
 		}
@@ -486,5 +620,5 @@
 		    pio_read_32(&instance->registers->flbaseadd) & ~0xfff;
 		if (frame_list != addr_to_phys(instance->frame_list)) {
-			usb_log_debug("Framelist address: %p vs. %p.\n",
+			usb_log_debug("Framelist address: %p vs. %p.",
 			    (void *) frame_list,
 			    (void *) addr_to_phys(instance->frame_list));
@@ -497,5 +631,5 @@
 		uintptr_t real_pa = addr_to_phys(QH(interrupt));
 		if (expected_pa != real_pa) {
-			usb_log_debug("Interrupt QH: %p (frame %d) vs. %p.\n",
+			usb_log_debug("Interrupt QH: %p (frame %d) vs. %p.",
 			    (void *) expected_pa, frnum, (void *) real_pa);
 		}
@@ -504,5 +638,5 @@
 		real_pa = addr_to_phys(QH(control_slow));
 		if (expected_pa != real_pa) {
-			usb_log_debug("Control Slow QH: %p vs. %p.\n",
+			usb_log_debug("Control Slow QH: %p vs. %p.",
 			    (void *) expected_pa, (void *) real_pa);
 		}
@@ -511,5 +645,5 @@
 		real_pa = addr_to_phys(QH(control_full));
 		if (expected_pa != real_pa) {
-			usb_log_debug("Control Full QH: %p vs. %p.\n",
+			usb_log_debug("Control Full QH: %p vs. %p.",
 			    (void *) expected_pa, (void *) real_pa);
 		}
@@ -518,5 +652,5 @@
 		real_pa = addr_to_phys(QH(bulk_full));
 		if (expected_pa != real_pa ) {
-			usb_log_debug("Bulk QH: %p vs. %p.\n",
+			usb_log_debug("Bulk QH: %p vs. %p.",
 			    (void *) expected_pa, (void *) real_pa);
 		}
Index: uspace/drv/bus/usb/uhci/hc.h
===================================================================
--- uspace/drv/bus/usb/uhci/hc.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/uhci/hc.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -43,4 +43,5 @@
 #include <ddi.h>
 #include <usb/host/hcd.h>
+#include <usb/host/usb2_bus.h>
 #include <usb/host/usb_transfer_batch.h>
 
@@ -99,5 +100,11 @@
 /** Main UHCI driver structure */
 typedef struct hc {
+	/* Common hc_device header */
+	hc_device_t base;
+
 	uhci_rh_t rh;
+	bus_t bus;
+	usb2_bus_helper_t bus_helper;
+
 	/** Addresses of I/O registers */
 	uhci_regs_t *registers;
@@ -117,6 +124,12 @@
 	/** Pointer table to the above lists, helps during scheduling */
 	transfer_list_t *transfers[2][4];
-	/** Indicator of hw interrupts availability */
-	bool hw_interrupts;
+
+	/**
+	 * Guard for the pending list. Can be locked under EP guard, but not
+	 * vice versa.
+	 */
+	fibril_mutex_t guard;
+	/** List of endpoints with a transfer scheduled */
+	list_t pending_endpoints;
 
 	/** Number of hw failures detected. */
@@ -124,12 +137,29 @@
 } hc_t;
 
-extern errno_t hc_init(hc_t *, const hw_res_list_parsed_t *, bool);
-extern void hc_fini(hc_t *);
+typedef struct uhci_endpoint {
+	endpoint_t base;
 
-extern errno_t uhci_hc_gen_irq_code(irq_code_t *, const hw_res_list_parsed_t *, int *);
+	bool toggle;
+} uhci_endpoint_t;
 
-extern void uhci_hc_interrupt(hcd_t *, uint32_t);
-extern errno_t uhci_hc_status(hcd_t *, uint32_t *);
-extern errno_t uhci_hc_schedule(hcd_t *, usb_transfer_batch_t *);
+static inline hc_t *hcd_to_hc(hc_device_t *hcd)
+{
+	assert(hcd);
+	return (hc_t *) hcd;
+}
+
+static inline hc_t *bus_to_hc(bus_t *bus)
+{
+	assert(bus);
+	return member_to_inst(bus, hc_t, bus);
+}
+
+int hc_unschedule_batch(usb_transfer_batch_t *);
+
+extern errno_t hc_add(hc_device_t *, const hw_res_list_parsed_t *);
+extern errno_t hc_gen_irq_code(irq_code_t *, hc_device_t *, const hw_res_list_parsed_t *, int *);
+extern errno_t hc_start(hc_device_t *);
+extern errno_t hc_setup_roothub(hc_device_t *);
+extern errno_t hc_gone(hc_device_t *);
 
 #endif
Index: uspace/drv/bus/usb/uhci/hw_struct/transfer_descriptor.c
===================================================================
--- uspace/drv/bus/usb/uhci/hw_struct/transfer_descriptor.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/uhci/hw_struct/transfer_descriptor.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -104,10 +104,10 @@
 	instance->buffer_ptr = addr_to_phys(buffer);
 
-	usb_log_debug2("Created TD(%p): %X:%X:%X:%X(%p).\n",
+	usb_log_debug2("Created TD(%p): %X:%X:%X:%X(%p).",
 	    instance, instance->next, instance->status, instance->device,
 	    instance->buffer_ptr, buffer);
 	td_print_status(instance);
 	if (pid == USB_PID_SETUP) {
-		usb_log_debug2("SETUP BUFFER: %s\n",
+		usb_log_debug2("SETUP BUFFER: %s",
 		    usb_debug_str_buffer(buffer, 8, 8));
 	}
@@ -160,5 +160,5 @@
 	assert(instance);
 	const uint32_t s = instance->status;
-	usb_log_debug2("TD(%p) status(%#" PRIx32 "):%s %d,%s%s%s%s%s%s%s%s%s%s%s %zu.\n",
+	usb_log_debug2("TD(%p) status(%#" PRIx32 "):%s %d,%s%s%s%s%s%s%s%s%s%s%s %zu.",
 	    instance, instance->status,
 	    (s & TD_STATUS_SPD_FLAG) ? " SPD," : "",
Index: uspace/drv/bus/usb/uhci/main.c
===================================================================
--- uspace/drv/bus/usb/uhci/main.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/uhci/main.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -43,5 +43,5 @@
 #include <str_error.h>
 #include <usb/debug.h>
-#include <usb/host/ddf_helpers.h>
+#include <usb/host/utility.h>
 
 #include "hc.h"
@@ -49,50 +49,16 @@
 #define NAME "uhci"
 
-static errno_t uhci_driver_init(hcd_t *, const hw_res_list_parsed_t *, bool);
-static void uhci_driver_fini(hcd_t *);
-static errno_t disable_legacy(ddf_dev_t *);
+static errno_t disable_legacy(hc_device_t *);
 
-static const ddf_hc_driver_t uhci_hc_driver = {
-        .claim = disable_legacy,
-        .hc_speed = USB_SPEED_FULL,
-        .irq_code_gen = uhci_hc_gen_irq_code,
-        .init = uhci_driver_init,
-        .fini = uhci_driver_fini,
-        .name = "UHCI",
-	.ops = {
-		.schedule    = uhci_hc_schedule,
-		.irq_hook    = uhci_hc_interrupt,
-		.status_hook = uhci_hc_status,
-	},
+static const hc_driver_t uhci_driver = {
+	.name = NAME,
+	.hc_device_size = sizeof(hc_t),
+	.claim = disable_legacy,
+	.irq_code_gen = hc_gen_irq_code,
+	.hc_add = hc_add,
+	.start = hc_start,
+	.setup_root_hub = hc_setup_roothub,
+	.hc_gone = hc_gone,
 };
-
-static errno_t uhci_driver_init(hcd_t *hcd, const hw_res_list_parsed_t *res, bool irq)
-{
-	assert(hcd);
-	assert(hcd_get_driver_data(hcd) == NULL);
-
-	hc_t *instance = malloc(sizeof(hc_t));
-	if (!instance)
-		return ENOMEM;
-
-	const errno_t ret = hc_init(instance, res, irq);
-	if (ret == EOK) {
-		hcd_set_implementation(hcd, instance, &uhci_hc_driver.ops);
-	} else {
-		free(instance);
-	}
-	return ret;
-}
-
-static void uhci_driver_fini(hcd_t *hcd)
-{
-	assert(hcd);
-	hc_t *hc = hcd_get_driver_data(hcd);
-	if (hc)
-		hc_fini(hc);
-
-	hcd_set_implementation(hcd, NULL, NULL);
-	free(hc);
-}
 
 /** Call the PCI driver with a request to clear legacy support register
@@ -101,9 +67,9 @@
  * @return Error code.
  */
-static errno_t disable_legacy(ddf_dev_t *device)
+static errno_t disable_legacy(hc_device_t *hcd)
 {
-	assert(device);
+	assert(hcd);
 
-	async_sess_t *parent_sess = ddf_dev_parent_sess_get(device);
+	async_sess_t *parent_sess = ddf_dev_parent_sess_get(hcd->ddf_dev);
 	if (parent_sess == NULL)
 		return ENOMEM;
@@ -113,26 +79,4 @@
 	return pci_config_space_write_16(parent_sess, 0xc0, 0xaf00);
 }
-
-/** Initialize a new ddf driver instance for uhci hc and hub.
- *
- * @param[in] device DDF instance of the device to initialize.
- * @return Error code.
- */
-static errno_t uhci_dev_add(ddf_dev_t *device)
-{
-	usb_log_debug2("uhci_dev_add() called\n");
-	assert(device);
-	return hcd_ddf_add_hc(device, &uhci_hc_driver);
-}
-
-static const driver_ops_t uhci_driver_ops = {
-	.dev_add = uhci_dev_add,
-};
-
-static const driver_t uhci_driver = {
-	.name = NAME,
-	.driver_ops = &uhci_driver_ops
-};
-
 
 /** Initialize global driver structures (NONE).
@@ -149,5 +93,5 @@
 	log_init(NAME);
 	logctl_set_log_level(NAME, LVL_NOTE);
-	return ddf_driver_main(&uhci_driver);
+	return hc_driver_main(&uhci_driver);
 }
 /**
Index: uspace/drv/bus/usb/uhci/transfer_list.c
===================================================================
--- uspace/drv/bus/usb/uhci/transfer_list.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/uhci/transfer_list.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -41,10 +41,9 @@
 #include <usb/host/usb_transfer_batch.h>
 #include <usb/host/utils/malloc32.h>
+#include <usb/host/utility.h>
 
 #include "hw_struct/link_pointer.h"
 #include "transfer_list.h"
-
-static void transfer_list_remove_batch(
-    transfer_list_t *instance, uhci_transfer_batch_t *uhci_batch);
+#include "hc.h"
 
 /** Initialize transfer list structures.
@@ -62,9 +61,9 @@
 	instance->queue_head = malloc32(sizeof(qh_t));
 	if (!instance->queue_head) {
-		usb_log_error("Failed to allocate queue head.\n");
+		usb_log_error("Failed to allocate queue head.");
 		return ENOMEM;
 	}
 	const uint32_t queue_head_pa = addr_to_phys(instance->queue_head);
-	usb_log_debug2("Transfer list %s setup with QH: %p (%#" PRIx32" ).\n",
+	usb_log_debug2("Transfer list %s setup with QH: %p (%#" PRIx32" ).",
 	    name, instance->queue_head, queue_head_pa);
 
@@ -103,20 +102,31 @@
 }
 
-/** Add transfer batch to the list and queue.
- *
- * @param[in] instance List to use.
- * @param[in] batch Transfer batch to submit.
+/**
+ * Add transfer batch to the list and queue.
  *
  * The batch is added to the end of the list and queue.
- */
-void transfer_list_add_batch(
+ *
+ * @param[in] instance List to use.
+ * @param[in] batch Transfer batch to submit. After return, the batch must
+ *                  not be used further.
+ */
+int transfer_list_add_batch(
     transfer_list_t *instance, uhci_transfer_batch_t *uhci_batch)
 {
 	assert(instance);
 	assert(uhci_batch);
-	usb_log_debug2("Batch %p adding to queue %s.\n",
-	    uhci_batch->usb_batch, instance->name);
+
+	endpoint_t *ep = uhci_batch->base.ep;
 
 	fibril_mutex_lock(&instance->guard);
+
+	const int err = endpoint_activate_locked(ep, &uhci_batch->base);
+	if (err) {
+		fibril_mutex_unlock(&instance->guard);
+		return err;
+	}
+
+	usb_log_debug2("Batch %p adding to queue %s.",
+	    uhci_batch, instance->name);
 
 	/* Assume there is nothing scheduled */
@@ -145,7 +155,17 @@
 
 	usb_log_debug2("Batch %p " USB_TRANSFER_BATCH_FMT
-	    " scheduled in queue %s.\n", uhci_batch->usb_batch,
-	    USB_TRANSFER_BATCH_ARGS(*uhci_batch->usb_batch), instance->name);
+	    " scheduled in queue %s.", uhci_batch,
+	    USB_TRANSFER_BATCH_ARGS(uhci_batch->base), instance->name);
 	fibril_mutex_unlock(&instance->guard);
+	return EOK;
+}
+
+/**
+ * Reset toggle on endpoint callback.
+ */
+static void uhci_reset_toggle(endpoint_t *ep)
+{
+	uhci_endpoint_t *uhci_ep = (uhci_endpoint_t *) ep;
+	uhci_ep->toggle = 0;
 }
 
@@ -155,22 +175,19 @@
  * @param[in] done list to fill
  */
-void transfer_list_remove_finished(transfer_list_t *instance, list_t *done)
-{
-	assert(instance);
-	assert(done);
+void transfer_list_check_finished(transfer_list_t *instance)
+{
+	assert(instance);
 
 	fibril_mutex_lock(&instance->guard);
-	link_t *current = list_first(&instance->batch_list);
-	while (current && current != &instance->batch_list.head) {
-		link_t * const next = current->next;
-		uhci_transfer_batch_t *batch =
-		    uhci_transfer_batch_from_link(current);
-
-		if (uhci_transfer_batch_is_complete(batch)) {
-			/* Save for processing */
+	list_foreach_safe(instance->batch_list, current, next) {
+		uhci_transfer_batch_t *batch = uhci_transfer_batch_from_link(current);
+
+		if (uhci_transfer_batch_check_completed(batch)) {
+			assert(batch->base.ep->active_batch == &batch->base);
+			endpoint_deactivate_locked(batch->base.ep);
+			hc_reset_toggles(&batch->base, &uhci_reset_toggle);
 			transfer_list_remove_batch(instance, batch);
-			list_append(current, done);
+			usb_transfer_batch_finish(&batch->base);
 		}
-		current = next;
 	}
 	fibril_mutex_unlock(&instance->guard);
@@ -186,8 +203,6 @@
 	while (!list_empty(&instance->batch_list)) {
 		link_t * const current = list_first(&instance->batch_list);
-		uhci_transfer_batch_t *batch =
-		    uhci_transfer_batch_from_link(current);
+		uhci_transfer_batch_t *batch = uhci_transfer_batch_from_link(current);
 		transfer_list_remove_batch(instance, batch);
-		uhci_transfer_batch_abort(batch);
 	}
 	fibril_mutex_unlock(&instance->guard);
@@ -209,7 +224,8 @@
 	assert(uhci_batch->qh);
 	assert(fibril_mutex_is_locked(&instance->guard));
-
-	usb_log_debug2("Batch %p removing from queue %s.\n",
-	    uhci_batch->usb_batch, instance->name);
+	assert(!list_empty(&instance->batch_list));
+
+	usb_log_debug2("Batch %p removing from queue %s.",
+	    uhci_batch, instance->name);
 
 	/* Assume I'm the first */
@@ -233,6 +249,6 @@
 	list_remove(&uhci_batch->link);
 	usb_log_debug2("Batch %p " USB_TRANSFER_BATCH_FMT " removed (%s) "
-	    "from %s, next: %x.\n", uhci_batch->usb_batch,
-	    USB_TRANSFER_BATCH_ARGS(*uhci_batch->usb_batch),
+	    "from %s, next: %x.", uhci_batch,
+	    USB_TRANSFER_BATCH_ARGS(uhci_batch->base),
 	    qpos, instance->name, uhci_batch->qh->next);
 }
Index: uspace/drv/bus/usb/uhci/transfer_list.h
===================================================================
--- uspace/drv/bus/usb/uhci/transfer_list.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/uhci/transfer_list.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -56,11 +56,11 @@
 } transfer_list_t;
 
-void transfer_list_fini(transfer_list_t *instance);
-errno_t transfer_list_init(transfer_list_t *instance, const char *name);
-void transfer_list_set_next(transfer_list_t *instance, transfer_list_t *next);
-void transfer_list_add_batch(
-    transfer_list_t *instance, uhci_transfer_batch_t *batch);
-void transfer_list_remove_finished(transfer_list_t *instance, list_t *done);
-void transfer_list_abort_all(transfer_list_t *instance);
+void transfer_list_fini(transfer_list_t *);
+errno_t transfer_list_init(transfer_list_t *, const char *);
+void transfer_list_set_next(transfer_list_t *, transfer_list_t *);
+errno_t transfer_list_add_batch(transfer_list_t *, uhci_transfer_batch_t *);
+void transfer_list_remove_batch(transfer_list_t *, uhci_transfer_batch_t *);
+void transfer_list_check_finished(transfer_list_t *);
+void transfer_list_abort_all(transfer_list_t *);
 
 #endif
Index: uspace/drv/bus/usb/uhci/uhci_batch.c
===================================================================
--- uspace/drv/bus/usb/uhci/uhci_batch.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/uhci/uhci_batch.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -46,37 +46,22 @@
 
 #include "uhci_batch.h"
+#include "hc.h"
 #include "hw_struct/transfer_descriptor.h"
 
 #define DEFAULT_ERROR_COUNT 3
 
-/** Safely destructs uhci_transfer_batch_t structure.
+/** Transfer batch setup table. */
+static void (*const batch_setup[])(uhci_transfer_batch_t*);
+
+/** Destroys uhci_transfer_batch_t structure.
  *
  * @param[in] uhci_batch Instance to destroy.
  */
-static void uhci_transfer_batch_dispose(uhci_transfer_batch_t *uhci_batch)
-{
-	if (uhci_batch) {
-		usb_transfer_batch_destroy(uhci_batch->usb_batch);
-		free32(uhci_batch->device_buffer);
-		free(uhci_batch);
-	}
-}
-
-/** Finishes usb_transfer_batch and destroys the structure.
- *
- * @param[in] uhci_batch Instance to finish and destroy.
- */
-void uhci_transfer_batch_finish_dispose(uhci_transfer_batch_t *uhci_batch)
+void uhci_transfer_batch_destroy(uhci_transfer_batch_t *uhci_batch)
 {
 	assert(uhci_batch);
-	assert(uhci_batch->usb_batch);
-	assert(!link_in_use(&uhci_batch->link));
-	usb_transfer_batch_finish(uhci_batch->usb_batch,
-	    uhci_transfer_batch_data_buffer(uhci_batch));
-	uhci_transfer_batch_dispose(uhci_batch);
-}
-
-/** Transfer batch setup table. */
-static void (*const batch_setup[])(uhci_transfer_batch_t*, usb_direction_t);
+	free32(uhci_batch->device_buffer);
+	free(uhci_batch);
+}
 
 /** Allocate memory and initialize internal data structure.
@@ -85,4 +70,21 @@
  * @return Valid pointer if all structures were successfully created,
  * NULL otherwise.
+ */
+uhci_transfer_batch_t * uhci_transfer_batch_create(endpoint_t *ep)
+{
+	uhci_transfer_batch_t *uhci_batch =
+	    calloc(1, sizeof(uhci_transfer_batch_t));
+	if (!uhci_batch) {
+		usb_log_error("Failed to allocate UHCI batch.");
+		return NULL;
+	}
+
+	usb_transfer_batch_init(&uhci_batch->base, ep);
+
+	link_initialize(&uhci_batch->link);
+	return uhci_batch;
+}
+
+/* Prepares batch for commiting.
  *
  * Determines the number of needed transfer descriptors (TDs).
@@ -90,31 +92,28 @@
  * Initializes parameters needed for the transfer and callback.
  */
-uhci_transfer_batch_t * uhci_transfer_batch_get(usb_transfer_batch_t *usb_batch)
+int uhci_transfer_batch_prepare(uhci_transfer_batch_t *uhci_batch)
 {
 	static_assert((sizeof(td_t) % 16) == 0);
-#define CHECK_NULL_DISPOSE_RETURN(ptr, message...) \
-	if (ptr == NULL) { \
-		usb_log_error(message); \
-		uhci_transfer_batch_dispose(uhci_batch); \
-		return NULL; \
-	} else (void)0
-
-	uhci_transfer_batch_t *uhci_batch =
-	    calloc(1, sizeof(uhci_transfer_batch_t));
-	CHECK_NULL_DISPOSE_RETURN(uhci_batch,
-	    "Failed to allocate UHCI batch.\n");
-	link_initialize(&uhci_batch->link);
-	uhci_batch->td_count =
-	    (usb_batch->buffer_size + usb_batch->ep->max_packet_size - 1)
-	    / usb_batch->ep->max_packet_size;
+
+	usb_transfer_batch_t *usb_batch = &uhci_batch->base;
+
+	uhci_batch->td_count = (usb_batch->buffer_size + usb_batch->ep->max_packet_size - 1)
+		/ usb_batch->ep->max_packet_size;
+
 	if (usb_batch->ep->transfer_type == USB_TRANSFER_CONTROL) {
 		uhci_batch->td_count += 2;
 	}
 
+	const size_t setup_size = (usb_batch->ep->transfer_type == USB_TRANSFER_CONTROL)
+		? USB_SETUP_PACKET_SIZE
+		: 0;
+
 	const size_t total_size = (sizeof(td_t) * uhci_batch->td_count)
-	    + sizeof(qh_t) + usb_batch->setup_size + usb_batch->buffer_size;
+	    + sizeof(qh_t) + setup_size + usb_batch->buffer_size;
 	uhci_batch->device_buffer = malloc32(total_size);
-	CHECK_NULL_DISPOSE_RETURN(uhci_batch->device_buffer,
-	    "Failed to allocate UHCI buffer.\n");
+	if (!uhci_batch->device_buffer) {
+		usb_log_error("Failed to allocate UHCI buffer.");
+		return ENOMEM;
+	}
 	memset(uhci_batch->device_buffer, 0, total_size);
 
@@ -130,21 +129,18 @@
 	    + sizeof(qh_t);
 	/* Copy SETUP packet data to the device buffer */
-	memcpy(dest, usb_batch->setup_buffer, usb_batch->setup_size);
-	dest += usb_batch->setup_size;
+	memcpy(dest, usb_batch->setup.buffer, setup_size);
+	dest += setup_size;
 	/* Copy generic data unless they are provided by the device */
-	if (usb_batch->ep->direction != USB_DIRECTION_IN) {
+	if (usb_batch->dir != USB_DIRECTION_IN) {
 		memcpy(dest, usb_batch->buffer, usb_batch->buffer_size);
 	}
-	uhci_batch->usb_batch = usb_batch;
 	usb_log_debug2("Batch %p " USB_TRANSFER_BATCH_FMT
-	    " memory structures ready.\n", usb_batch,
+	    " memory structures ready.", usb_batch,
 	    USB_TRANSFER_BATCH_ARGS(*usb_batch));
 
-	const usb_direction_t dir = usb_transfer_batch_direction(usb_batch);
-
 	assert(batch_setup[usb_batch->ep->transfer_type]);
-	batch_setup[usb_batch->ep->transfer_type](uhci_batch, dir);
-
-	return uhci_batch;
+	batch_setup[usb_batch->ep->transfer_type](uhci_batch);
+
+	return EOK;
 }
 
@@ -158,15 +154,16 @@
  * is reached.
  */
-bool uhci_transfer_batch_is_complete(const uhci_transfer_batch_t *uhci_batch)
+bool uhci_transfer_batch_check_completed(uhci_transfer_batch_t *uhci_batch)
 {
 	assert(uhci_batch);
-	assert(uhci_batch->usb_batch);
+	usb_transfer_batch_t *batch = &uhci_batch->base;
 
 	usb_log_debug2("Batch %p " USB_TRANSFER_BATCH_FMT
-	    " checking %zu transfer(s) for completion.\n",
-	    uhci_batch->usb_batch,
-	    USB_TRANSFER_BATCH_ARGS(*uhci_batch->usb_batch),
+	    " checking %zu transfer(s) for completion.",
+	    uhci_batch, USB_TRANSFER_BATCH_ARGS(*batch),
 	    uhci_batch->td_count);
-	uhci_batch->usb_batch->transfered_size = 0;
+	batch->transferred_size = 0;
+
+	uhci_endpoint_t *uhci_ep = (uhci_endpoint_t *) batch->ep;
 
 	for (size_t i = 0;i < uhci_batch->td_count; ++i) {
@@ -175,21 +172,18 @@
 		}
 
-		uhci_batch->usb_batch->error = td_status(&uhci_batch->tds[i]);
-		if (uhci_batch->usb_batch->error != EOK) {
-			assert(uhci_batch->usb_batch->ep != NULL);
+		batch->error = td_status(&uhci_batch->tds[i]);
+		if (batch->error != EOK) {
+			assert(batch->ep != NULL);
 
 			usb_log_debug("Batch %p found error TD(%zu->%p):%"
-			    PRIx32 ".\n", uhci_batch->usb_batch, i,
+			    PRIx32 ".", uhci_batch, i,
 			    &uhci_batch->tds[i], uhci_batch->tds[i].status);
 			td_print_status(&uhci_batch->tds[i]);
 
-			endpoint_toggle_set(uhci_batch->usb_batch->ep,
-			    td_toggle(&uhci_batch->tds[i]));
-			if (i > 0)
-				goto substract_ret;
-			return true;
+			uhci_ep->toggle = td_toggle(&uhci_batch->tds[i]);
+			goto substract_ret;
 		}
 
-		uhci_batch->usb_batch->transfered_size
+		batch->transferred_size
 		    += td_act_size(&uhci_batch->tds[i]);
 		if (td_is_short(&uhci_batch->tds[i]))
@@ -197,6 +191,16 @@
 	}
 substract_ret:
-	uhci_batch->usb_batch->transfered_size
-	    -= uhci_batch->usb_batch->setup_size;
+	if (batch->transferred_size > 0 && batch->ep->transfer_type == USB_TRANSFER_CONTROL) {
+		assert(batch->transferred_size >= USB_SETUP_PACKET_SIZE);
+		batch->transferred_size -= USB_SETUP_PACKET_SIZE;
+	}
+
+	if (batch->dir == USB_DIRECTION_IN) {
+		assert(batch->transferred_size <= batch->buffer_size);
+		memcpy(batch->buffer,
+		    uhci_transfer_batch_data_buffer(uhci_batch),
+		    batch->transferred_size);
+	}
+
 	return true;
 }
@@ -216,9 +220,9 @@
  * The last transfer is marked with IOC flag.
  */
-static void batch_data(uhci_transfer_batch_t *uhci_batch, usb_direction_t dir)
+static void batch_data(uhci_transfer_batch_t *uhci_batch)
 {
 	assert(uhci_batch);
-	assert(uhci_batch->usb_batch);
-	assert(uhci_batch->usb_batch->ep);
+
+	usb_direction_t dir = uhci_batch->base.dir;
 	assert(dir == USB_DIRECTION_OUT || dir == USB_DIRECTION_IN);
 
@@ -226,15 +230,14 @@
 	const usb_packet_id pid = direction_pids[dir];
 	const bool low_speed =
-	    uhci_batch->usb_batch->ep->speed == USB_SPEED_LOW;
-	const size_t mps = uhci_batch->usb_batch->ep->max_packet_size;
-	const usb_target_t target = {{
-	    uhci_batch->usb_batch->ep->address,
-	    uhci_batch->usb_batch->ep->endpoint }};
-
-	int toggle = endpoint_toggle_get(uhci_batch->usb_batch->ep);
+	    uhci_batch->base.ep->device->speed == USB_SPEED_LOW;
+	const size_t mps = uhci_batch->base.ep->max_packet_size;
+
+	uhci_endpoint_t *uhci_ep = (uhci_endpoint_t *) uhci_batch->base.ep;
+
+	int toggle = uhci_ep->toggle;
 	assert(toggle == 0 || toggle == 1);
 
 	size_t td = 0;
-	size_t remain_size = uhci_batch->usb_batch->buffer_size;
+	size_t remain_size = uhci_batch->base.buffer_size;
 	char *buffer = uhci_transfer_batch_data_buffer(uhci_batch);
 
@@ -248,5 +251,5 @@
 		td_init(
 		    &uhci_batch->tds[td], DEFAULT_ERROR_COUNT, packet_size,
-		    toggle, false, low_speed, target, pid, buffer, next_td);
+		    toggle, false, low_speed, uhci_batch->base.target, pid, buffer, next_td);
 
 		++td;
@@ -256,11 +259,11 @@
 	}
 	td_set_ioc(&uhci_batch->tds[td - 1]);
-	endpoint_toggle_set(uhci_batch->usb_batch->ep, toggle);
+	uhci_ep->toggle = toggle;
 	usb_log_debug2(
-	    "Batch %p %s %s " USB_TRANSFER_BATCH_FMT " initialized.\n", \
-	    uhci_batch->usb_batch,
-	    usb_str_transfer_type(uhci_batch->usb_batch->ep->transfer_type),
-	    usb_str_direction(uhci_batch->usb_batch->ep->direction),
-	    USB_TRANSFER_BATCH_ARGS(*uhci_batch->usb_batch));
+	    "Batch %p %s %s " USB_TRANSFER_BATCH_FMT " initialized.", \
+	    uhci_batch,
+	    usb_str_transfer_type(uhci_batch->base.ep->transfer_type),
+	    usb_str_direction(uhci_batch->base.ep->direction),
+	    USB_TRANSFER_BATCH_ARGS(uhci_batch->base));
 }
 
@@ -276,9 +279,9 @@
  * The last transfer is marked with IOC.
  */
-static void batch_control(uhci_transfer_batch_t *uhci_batch, usb_direction_t dir)
+static void batch_control(uhci_transfer_batch_t *uhci_batch)
 {
 	assert(uhci_batch);
-	assert(uhci_batch->usb_batch);
-	assert(uhci_batch->usb_batch->ep);
+
+	usb_direction_t dir = uhci_batch->base.dir;
 	assert(dir == USB_DIRECTION_OUT || dir == USB_DIRECTION_IN);
 	assert(uhci_batch->td_count >= 2);
@@ -291,14 +294,12 @@
 	const usb_packet_id status_stage_pid = status_stage_pids[dir];
 	const bool low_speed =
-	    uhci_batch->usb_batch->ep->speed == USB_SPEED_LOW;
-	const size_t mps = uhci_batch->usb_batch->ep->max_packet_size;
-	const usb_target_t target = {{
-	    uhci_batch->usb_batch->ep->address,
-	    uhci_batch->usb_batch->ep->endpoint }};
+	    uhci_batch->base.ep->device->speed == USB_SPEED_LOW;
+	const size_t mps = uhci_batch->base.ep->max_packet_size;
+	const usb_target_t target = uhci_batch->base.target;
 
 	/* setup stage */
 	td_init(
 	    &uhci_batch->tds[0], DEFAULT_ERROR_COUNT,
-	    uhci_batch->usb_batch->setup_size, 0, false,
+	    USB_SETUP_PACKET_SIZE, 0, false,
 	    low_speed, target, USB_PID_SETUP,
 	    uhci_transfer_batch_setup_buffer(uhci_batch), &uhci_batch->tds[1]);
@@ -307,5 +308,5 @@
 	size_t td = 1;
 	unsigned toggle = 1;
-	size_t remain_size = uhci_batch->usb_batch->buffer_size;
+	size_t remain_size = uhci_batch->base.buffer_size;
 	char *buffer = uhci_transfer_batch_data_buffer(uhci_batch);
 
@@ -333,9 +334,9 @@
 	td_set_ioc(&uhci_batch->tds[td]);
 
-	usb_log_debug2("Control last TD status: %x.\n",
+	usb_log_debug2("Control last TD status: %x.",
 	    uhci_batch->tds[td].status);
 }
 
-static void (*const batch_setup[])(uhci_transfer_batch_t*, usb_direction_t) =
+static void (*const batch_setup[])(uhci_transfer_batch_t*) =
 {
 	[USB_TRANSFER_CONTROL] = batch_control,
Index: uspace/drv/bus/usb/uhci/uhci_batch.h
===================================================================
--- uspace/drv/bus/usb/uhci/uhci_batch.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/uhci/uhci_batch.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -43,4 +43,5 @@
 #include <stddef.h>
 #include <usb/host/usb_transfer_batch.h>
+#include <usb/host/endpoint.h>
 
 #include "hw_struct/queue_head.h"
@@ -49,4 +50,6 @@
 /** UHCI specific data required for USB transfer */
 typedef struct uhci_transfer_batch {
+	usb_transfer_batch_t base;
+
 	/** Queue head
 	 * This QH is used to maintain UHCI schedule structure and the element
@@ -60,13 +63,12 @@
 	/** Data buffer, must be accessible by the UHCI hw */
 	void *device_buffer;
-	/** Generic transfer data */
-	usb_transfer_batch_t *usb_batch;
 	/** List element */
 	link_t link;
 } uhci_transfer_batch_t;
 
-uhci_transfer_batch_t * uhci_transfer_batch_get(usb_transfer_batch_t *batch);
-void uhci_transfer_batch_finish_dispose(uhci_transfer_batch_t *uhci_batch);
-bool uhci_transfer_batch_is_complete(const uhci_transfer_batch_t *uhci_batch);
+uhci_transfer_batch_t * uhci_transfer_batch_create(endpoint_t *);
+int uhci_transfer_batch_prepare(uhci_transfer_batch_t *);
+bool uhci_transfer_batch_check_completed(uhci_transfer_batch_t *);
+void uhci_transfer_batch_destroy(uhci_transfer_batch_t *);
 
 /** Get offset to setup buffer accessible to the HC hw.
@@ -91,21 +93,6 @@
 {
 	assert(uhci_batch);
-	assert(uhci_batch->usb_batch);
 	return uhci_transfer_batch_setup_buffer(uhci_batch) +
-	    uhci_batch->usb_batch->setup_size;
-}
-
-/** Aborts the batch.
- * Sets error to EINTR and size off transferd data to 0, before finishing the
- * batch.
- * @param uhci_batch Batch to abort.
- */
-static inline void uhci_transfer_batch_abort(uhci_transfer_batch_t *uhci_batch)
-{
-	assert(uhci_batch);
-	assert(uhci_batch->usb_batch);
-	uhci_batch->usb_batch->error = EINTR;
-	uhci_batch->usb_batch->transfered_size = 0;
-	uhci_transfer_batch_finish_dispose(uhci_batch);
+	    (uhci_batch->base.ep->transfer_type == USB_TRANSFER_CONTROL ? USB_SETUP_PACKET_SIZE : 0);
 }
 
@@ -120,4 +107,10 @@
 }
 
+static inline uhci_transfer_batch_t *uhci_transfer_batch_get(usb_transfer_batch_t *b)
+{
+	assert(b);
+	return (uhci_transfer_batch_t *) b;
+}
+
 #endif
 
Index: uspace/drv/bus/usb/uhci/uhci_rh.c
===================================================================
--- uspace/drv/bus/usb/uhci/uhci_rh.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/uhci/uhci_rh.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -39,4 +39,5 @@
 #include <usb/classes/hub.h>
 #include <usb/request.h>
+#include <usb/host/endpoint.h>
 #include <usb/usb.h>
 
@@ -103,12 +104,8 @@
 	assert(batch);
 
-	const usb_target_t target = {{
-		.address = batch->ep->address,
-		.endpoint = batch->ep->endpoint
-	}};
 	do {
-		batch->error = virthub_base_request(&instance->base, target,
-		    usb_transfer_batch_direction(batch), (void*)batch->setup_buffer,
-		    batch->buffer, batch->buffer_size, &batch->transfered_size);
+		batch->error = virthub_base_request(&instance->base, batch->target,
+		    batch->dir, (void*) batch->setup.buffer,
+		    batch->buffer, batch->buffer_size, &batch->transferred_size);
 		if (batch->error == ENAK)
 			async_usleep(instance->base.endpoint_descriptor.poll_interval * 1000);
@@ -116,6 +113,5 @@
 		//ENAK is technically an error condition
 	} while (batch->error == ENAK);
-	usb_transfer_batch_finish(batch, NULL);
-	usb_transfer_batch_destroy(batch);
+	usb_transfer_batch_finish(batch);
 	return EOK;
 }
@@ -206,5 +202,5 @@
 	data[0] = ((value & STATUS_LINE_D_MINUS) ? 1 : 0)
 	    | ((value & STATUS_LINE_D_PLUS) ? 2 : 0);
-	RH_DEBUG(hub, port, "Bus state %" PRIx8 "(source %" PRIx16")\n",
+	RH_DEBUG(hub, port, "Bus state %" PRIx8 "(source %" PRIx16")",
 	    data[0], value);
 	*act_size = 1;
@@ -214,6 +210,6 @@
 #define BIT_VAL(val, bit) \
 	((val & bit) ? 1 : 0)
-#define UHCI2USB(val, bit, feat) \
-	(BIT_VAL(val, bit) << feat)
+#define UHCI2USB(val, bit, mask) \
+	(BIT_VAL(val, bit) ? (mask) : 0)
 
 /** Port status request handler.
@@ -240,17 +236,17 @@
 	const uint16_t val = pio_read_16(hub->ports[port]);
 	const uint32_t status = uint32_host2usb(
-	    UHCI2USB(val, STATUS_CONNECTED, USB_HUB_FEATURE_PORT_CONNECTION) |
-	    UHCI2USB(val, STATUS_ENABLED, USB_HUB_FEATURE_PORT_ENABLE) |
-	    UHCI2USB(val, STATUS_SUSPEND, USB_HUB_FEATURE_PORT_SUSPEND) |
-	    UHCI2USB(val, STATUS_IN_RESET, USB_HUB_FEATURE_PORT_RESET) |
-	    UHCI2USB(val, STATUS_ALWAYS_ONE, USB_HUB_FEATURE_PORT_POWER) |
-	    UHCI2USB(val, STATUS_LOW_SPEED, USB_HUB_FEATURE_PORT_LOW_SPEED) |
-	    UHCI2USB(val, STATUS_CONNECTED_CHANGED, USB_HUB_FEATURE_C_PORT_CONNECTION) |
-	    UHCI2USB(val, STATUS_ENABLED_CHANGED, USB_HUB_FEATURE_C_PORT_ENABLE) |
-//	    UHCI2USB(val, STATUS_SUSPEND, USB_HUB_FEATURE_C_PORT_SUSPEND) |
-	    ((hub->reset_changed[port] ? 1 : 0) << USB_HUB_FEATURE_C_PORT_RESET)
+	    UHCI2USB(val, STATUS_CONNECTED, USB_HUB_PORT_STATUS_CONNECTION) |
+	    UHCI2USB(val, STATUS_ENABLED, USB_HUB_PORT_STATUS_ENABLE) |
+	    UHCI2USB(val, STATUS_SUSPEND, USB2_HUB_PORT_STATUS_SUSPEND) |
+	    UHCI2USB(val, STATUS_IN_RESET, USB_HUB_PORT_STATUS_RESET) |
+	    UHCI2USB(val, STATUS_ALWAYS_ONE, USB2_HUB_PORT_STATUS_POWER) |
+	    UHCI2USB(val, STATUS_LOW_SPEED, USB2_HUB_PORT_STATUS_LOW_SPEED) |
+	    UHCI2USB(val, STATUS_CONNECTED_CHANGED, USB_HUB_PORT_STATUS_C_CONNECTION) |
+	    UHCI2USB(val, STATUS_ENABLED_CHANGED, USB2_HUB_PORT_STATUS_C_ENABLE) |
+//	    UHCI2USB(val, STATUS_SUSPEND, USB2_HUB_PORT_STATUS_C_SUSPEND) |
+	    (hub->reset_changed[port] ?  USB_HUB_PORT_STATUS_C_RESET : 0)
 	);
 	RH_DEBUG(hub, port, "Port status %" PRIx32 " (source %" PRIx16
-	    "%s)\n", uint32_usb2host(status), val,
+	    "%s)", uint32_usb2host(status), val,
 	    hub->reset_changed[port] ? "-reset" : "");
 	memcpy(data, &status, sizeof(status));
@@ -278,45 +274,45 @@
 	const uint16_t val = status & (~STATUS_WC_BITS);
 	switch (feature) {
-	case USB_HUB_FEATURE_PORT_ENABLE:
+	case USB2_HUB_FEATURE_PORT_ENABLE:
 		RH_DEBUG(hub, port, "Clear port enable (status %"
-		    PRIx16 ")\n", status);
+		    PRIx16 ")", status);
 		pio_write_16(hub->ports[port], val & ~STATUS_ENABLED);
 		break;
-	case USB_HUB_FEATURE_PORT_SUSPEND:
+	case USB2_HUB_FEATURE_PORT_SUSPEND:
 		RH_DEBUG(hub, port, "Clear port suspend (status %"
-		    PRIx16 ")\n", status);
+		    PRIx16 ")", status);
 		pio_write_16(hub->ports[port], val & ~STATUS_SUSPEND);
 		// TODO we should do resume magic
-		usb_log_warning("Resume is not implemented on port %u\n", port);
+		usb_log_warning("Resume is not implemented on port %u", port);
 		break;
 	case USB_HUB_FEATURE_PORT_POWER:
-		RH_DEBUG(hub, port, "Clear port power (status %" PRIx16 ")\n",
+		RH_DEBUG(hub, port, "Clear port power (status %" PRIx16 ")",
 		    status);
 		/* We are always powered */
-		usb_log_warning("Tried to power off port %u\n", port);
+		usb_log_warning("Tried to power off port %u", port);
 		break;
 	case USB_HUB_FEATURE_C_PORT_CONNECTION:
 		RH_DEBUG(hub, port, "Clear port conn change (status %"
-		    PRIx16 ")\n", status);
+		    PRIx16 ")", status);
 		pio_write_16(hub->ports[port], val | STATUS_CONNECTED_CHANGED);
 		break;
 	case USB_HUB_FEATURE_C_PORT_RESET:
 		RH_DEBUG(hub, port, "Clear port reset change (status %"
-		    PRIx16 ")\n", status);
+		    PRIx16 ")", status);
 		hub->reset_changed[port] = false;
 		break;
-	case USB_HUB_FEATURE_C_PORT_ENABLE:
+	case USB2_HUB_FEATURE_C_PORT_ENABLE:
 		RH_DEBUG(hub, port, "Clear port enable change (status %"
-		    PRIx16 ")\n", status);
+		    PRIx16 ")", status);
 		pio_write_16(hub->ports[port], status | STATUS_ENABLED_CHANGED);
 		break;
-	case USB_HUB_FEATURE_C_PORT_SUSPEND:
+	case USB2_HUB_FEATURE_C_PORT_SUSPEND:
 		RH_DEBUG(hub, port, "Clear port suspend change (status %"
-		    PRIx16 ")\n", status);
+		    PRIx16 ")", status);
 		//TODO
 		return ENOTSUP;
 	case USB_HUB_FEATURE_C_PORT_OVER_CURRENT:
 		RH_DEBUG(hub, port, "Clear port OC change (status %"
-		    PRIx16 ")\n", status);
+		    PRIx16 ")", status);
 		/* UHCI Does not report over current */
 		//TODO: newer chips do, but some have broken wiring
@@ -324,6 +320,6 @@
 	default:
 		RH_DEBUG(hub, port, "Clear unknown feature %d (status %"
-		    PRIx16 ")\n", feature, status);
-		usb_log_warning("Clearing feature %d is unsupported\n",
+		    PRIx16 ")", feature, status);
+		usb_log_warning("Clearing feature %d is unsupported",
 		    feature);
 		return ESTALL;
@@ -352,29 +348,29 @@
 	case USB_HUB_FEATURE_PORT_RESET:
 		RH_DEBUG(hub, port, "Set port reset before (status %" PRIx16
-		    ")\n", status);
+		    ")", status);
 		uhci_port_reset_enable(hub->ports[port]);
 		hub->reset_changed[port] = true;
 		RH_DEBUG(hub, port, "Set port reset after (status %" PRIx16
-		    ")\n", pio_read_16(hub->ports[port]));
-		break;
-	case USB_HUB_FEATURE_PORT_SUSPEND:
+		    ")", pio_read_16(hub->ports[port]));
+		break;
+	case USB2_HUB_FEATURE_PORT_SUSPEND:
 		RH_DEBUG(hub, port, "Set port suspend (status %" PRIx16
-		    ")\n", status);
+		    ")", status);
 		pio_write_16(hub->ports[port],
 		    (status & ~STATUS_WC_BITS) | STATUS_SUSPEND);
-		usb_log_warning("Suspend is not implemented on port %u\n", port);
+		usb_log_warning("Suspend is not implemented on port %u", port);
 		break;
 	case USB_HUB_FEATURE_PORT_POWER:
 		RH_DEBUG(hub, port, "Set port power (status %" PRIx16
-		    ")\n", status);
+		    ")", status);
 		/* We are always powered */
-		usb_log_warning("Tried to power port %u\n", port);
+		usb_log_warning("Tried to power port %u", port);
 		break;
 	case USB_HUB_FEATURE_C_PORT_CONNECTION:
-	case USB_HUB_FEATURE_C_PORT_ENABLE:
-	case USB_HUB_FEATURE_C_PORT_SUSPEND:
+	case USB2_HUB_FEATURE_C_PORT_ENABLE:
+	case USB2_HUB_FEATURE_C_PORT_SUSPEND:
 	case USB_HUB_FEATURE_C_PORT_OVER_CURRENT:
 		RH_DEBUG(hub, port, "Set port change flag (status %" PRIx16
-		    ")\n", status);
+		    ")", status);
 		/* These are voluntary and don't have to be set
 		 * there is no way we could do it on UHCI anyway */
@@ -382,6 +378,6 @@
 	default:
 		RH_DEBUG(hub, port, "Set unknown feature %d (status %" PRIx16
-		    ")\n", feature, status);
-		usb_log_warning("Setting feature %d is unsupported\n",
+		    ")", feature, status);
+		usb_log_warning("Setting feature %d is unsupported",
 		    feature);
 		return ESTALL;
@@ -422,5 +418,5 @@
 		RH_DEBUG(hub, -1, "Event mask %" PRIx8
 		    " (status_a %" PRIx16 "%s),"
-		    " (status_b %" PRIx16 "%s)\n", status,
+		    " (status_b %" PRIx16 "%s)", status,
 		    status_a, hub->reset_changed[0] ? "-reset" : "",
 		    status_b, hub->reset_changed[1] ? "-reset" : "" );
Index: uspace/drv/bus/usb/usbdiag/Makefile
===================================================================
--- uspace/drv/bus/usb/usbdiag/Makefile	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/usbdiag/Makefile	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,40 @@
+#
+# Copyright (c) 2017 Petr Manek
+# 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.
+#
+
+USPACE_PREFIX = ../../../..
+
+LIBS = usbdev usb drv
+
+BINARY = usbdiag
+
+SOURCES = \
+	device.c \
+	tests.c \
+	main.c \
+
+include $(USPACE_PREFIX)/Makefile.common
Index: uspace/drv/bus/usb/usbdiag/device.c
===================================================================
--- uspace/drv/bus/usb/usbdiag/device.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/usbdiag/device.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2017 Petr Manek
+ * 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 drvusbdiag
+ * @{
+ */
+/**
+ * @file
+ * Code for managing debug device structures.
+ */
+#include <errno.h>
+#include <str_error.h>
+#include <macros.h>
+#include <usb/debug.h>
+#include <usbdiag_iface.h>
+
+#include "device.h"
+#include "tests.h"
+
+#define NAME "usbdiag"
+
+static usbdiag_iface_t diag_interface = {
+	.burst_intr_in = usbdiag_burst_test_intr_in,
+	.burst_intr_out = usbdiag_burst_test_intr_out,
+	.burst_bulk_in = usbdiag_burst_test_bulk_in,
+	.burst_bulk_out = usbdiag_burst_test_bulk_out,
+	.burst_isoch_in = usbdiag_burst_test_isoch_in,
+	.burst_isoch_out = usbdiag_burst_test_isoch_out,
+	.data_intr_in = usbdiag_data_test_intr_in,
+	.data_intr_out = usbdiag_data_test_intr_out,
+	.data_bulk_in = usbdiag_data_test_bulk_in,
+	.data_bulk_out = usbdiag_data_test_bulk_out,
+	.data_isoch_in = usbdiag_data_test_isoch_in,
+	.data_isoch_out = usbdiag_data_test_isoch_out
+};
+
+static ddf_dev_ops_t diag_ops = {
+	.interfaces[USBDIAG_DEV_IFACE] = &diag_interface
+};
+
+static int device_init(usbdiag_dev_t *dev, const usb_endpoint_description_t **endpoints)
+{
+	int rc;
+	ddf_fun_t *fun = usb_device_ddf_fun_create(dev->usb_dev, fun_exposed, "tmon");
+	if (!fun) {
+		rc = ENOMEM;
+		goto err;
+	}
+
+	ddf_fun_set_ops(fun, &diag_ops);
+	dev->fun = fun;
+
+#define _MAP_EP(target, ep_no) do {\
+	usb_endpoint_mapping_t *epm = usb_device_get_mapped_ep_desc(dev->usb_dev, endpoints[USBDIAG_EP_##ep_no]);\
+	if (!epm || !epm->present) {\
+		usb_log_error("Failed to map endpoint: " #ep_no ".");\
+		rc = ENOENT;\
+		goto err_fun;\
+	}\
+	target = &epm->pipe;\
+	} while (0);
+
+	_MAP_EP(dev->intr_in, INTR_IN);
+	_MAP_EP(dev->intr_out, INTR_OUT);
+	_MAP_EP(dev->bulk_in, BULK_IN);
+	_MAP_EP(dev->bulk_out, BULK_OUT);
+	_MAP_EP(dev->isoch_in, ISOCH_IN);
+	_MAP_EP(dev->isoch_out, ISOCH_OUT);
+
+#undef _MAP_EP
+
+	return EOK;
+
+err_fun:
+	ddf_fun_destroy(fun);
+err:
+	return rc;
+}
+
+static void device_fini(usbdiag_dev_t *dev)
+{
+	ddf_fun_destroy(dev->fun);
+}
+
+int usbdiag_dev_create(usb_device_t *dev, usbdiag_dev_t **out_diag_dev, const usb_endpoint_description_t **endpoints)
+{
+	assert(dev);
+	assert(out_diag_dev);
+
+	usbdiag_dev_t *diag_dev = usb_device_data_alloc(dev, sizeof(usbdiag_dev_t));
+	if (!diag_dev)
+		return ENOMEM;
+
+	diag_dev->usb_dev = dev;
+
+	int err;
+	if ((err = device_init(diag_dev, endpoints)))
+		goto err_init;
+
+	*out_diag_dev = diag_dev;
+	return EOK;
+
+err_init:
+	/* There is no usb_device_data_free. */
+	return err;
+}
+
+void usbdiag_dev_destroy(usbdiag_dev_t *dev)
+{
+	assert(dev);
+
+	device_fini(dev);
+	/* There is no usb_device_data_free. */
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/usbdiag/device.h
===================================================================
--- uspace/drv/bus/usb/usbdiag/device.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/usbdiag/device.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2017 Petr Manek
+ * 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 drvusbdiag
+ * @{
+ */
+/** @file
+ * USB diagnostic device structures.
+ */
+
+#ifndef USBDIAG_DEVICE_H_
+#define USBDIAG_DEVICE_H_
+
+#include <usb/dev/device.h>
+#include <usb/dev/driver.h>
+
+#define USBDIAG_EP_INTR_IN    1
+#define USBDIAG_EP_INTR_OUT   2
+#define USBDIAG_EP_BULK_IN    3
+#define USBDIAG_EP_BULK_OUT   4
+#define USBDIAG_EP_ISOCH_IN   5
+#define USBDIAG_EP_ISOCH_OUT  6
+
+/**
+ * USB diagnostic device.
+ */
+typedef struct usbdiag_dev {
+	usb_device_t *usb_dev;
+	ddf_fun_t *fun;
+	usb_pipe_t *intr_in;
+	usb_pipe_t *intr_out;
+	usb_pipe_t *bulk_in;
+	usb_pipe_t *bulk_out;
+	usb_pipe_t *isoch_in;
+	usb_pipe_t *isoch_out;
+} usbdiag_dev_t;
+
+int usbdiag_dev_create(usb_device_t *, usbdiag_dev_t **, const usb_endpoint_description_t **);
+void usbdiag_dev_destroy(usbdiag_dev_t *);
+
+static inline usbdiag_dev_t * usb_device_to_usbdiag_dev(usb_device_t *usb_dev)
+{
+	assert(usb_dev);
+	return usb_device_data_get(usb_dev);
+}
+
+static inline usbdiag_dev_t * ddf_dev_to_usbdiag_dev(ddf_dev_t *ddf_dev)
+{
+	assert(ddf_dev);
+	return usb_device_to_usbdiag_dev(usb_device_get(ddf_dev));
+}
+
+static inline usbdiag_dev_t * ddf_fun_to_usbdiag_dev(ddf_fun_t *ddf_fun)
+{
+	assert(ddf_fun);
+	return ddf_dev_to_usbdiag_dev(ddf_fun_get_dev(ddf_fun));
+}
+
+#endif /* USBDIAG_USBDIAG_H_ */
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/usbdiag/main.c
===================================================================
--- uspace/drv/bus/usb/usbdiag/main.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/usbdiag/main.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2017 Petr Manek
+ * 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 drvusbdiag
+ * @{
+ */
+/**
+ * @file
+ * Main routines of USB diagnostic device driver.
+ */
+#include <errno.h>
+#include <usb/debug.h>
+#include <usb/classes/classes.h>
+#include <usb/dev/driver.h>
+#include <usbdiag_iface.h>
+#include <str_error.h>
+
+#include "device.h"
+
+#define NAME "usbdiag"
+
+static const usb_endpoint_description_t *diag_endpoints[];
+
+static int device_add(usb_device_t *dev)
+{
+	int rc;
+	usb_log_info("Adding device '%s'", usb_device_get_name(dev));
+
+	usbdiag_dev_t *diag_dev;
+	if ((rc = usbdiag_dev_create(dev, &diag_dev, diag_endpoints))) {
+		usb_log_error("Failed create device: %s.", str_error(rc));
+		goto err;
+	}
+
+	if ((rc = ddf_fun_bind(diag_dev->fun))) {
+		usb_log_error("Failed to bind DDF function: %s.", str_error(rc));
+		goto err_create;
+	}
+
+	if ((rc = ddf_fun_add_to_category(diag_dev->fun, USBDIAG_CATEGORY))) {
+		usb_log_error("Failed add DDF to category '"
+		    USBDIAG_CATEGORY "': %s.\n", str_error(rc));
+		goto err_bind;
+	}
+
+	return EOK;
+
+err_bind:
+	ddf_fun_unbind(diag_dev->fun);
+err_create:
+	usbdiag_dev_destroy(diag_dev);
+err:
+	return rc;
+}
+
+static int device_cleanup(usbdiag_dev_t *diag_dev)
+{
+	/* TODO: Join some fibrils? */
+
+	/* Free memory. */
+	usbdiag_dev_destroy(diag_dev);
+	return EOK;
+}
+
+static int device_remove(usb_device_t *dev)
+{
+	int rc;
+	usb_log_info("Removing device '%s'", usb_device_get_name(dev));
+
+	usbdiag_dev_t *diag_dev = usb_device_to_usbdiag_dev(dev);
+
+	/* TODO: Make sure nothing is going on with the device. */
+
+	if ((rc = ddf_fun_unbind(diag_dev->fun))) {
+		usb_log_error("Failed to unbind DDF function: %s", str_error(rc));
+		goto err;
+	}
+
+	usb_log_info("Device '%s' removed.", usb_device_get_name(dev));
+	return device_cleanup(diag_dev);
+
+err:
+	return rc;
+}
+
+static int device_gone(usb_device_t *dev)
+{
+	int rc;
+	usb_log_info("Device '%s' gone.", usb_device_get_name(dev));
+
+	usbdiag_dev_t *diag_dev = usb_device_to_usbdiag_dev(dev);
+
+	/* TODO: Make sure nothing is going on with the device. */
+
+	if ((rc = ddf_fun_unbind(diag_dev->fun))) {
+		usb_log_error("Failed to unbind DDF function: %s", str_error(rc));
+		goto err;
+	}
+
+	return device_cleanup(diag_dev);
+
+err:
+	return rc;
+}
+
+static int function_online(ddf_fun_t *fun)
+{
+	return ddf_fun_online(fun);
+}
+
+static int function_offline(ddf_fun_t *fun)
+{
+	return ddf_fun_offline(fun);
+}
+
+static const usb_endpoint_description_t intr_in_ep = {
+	.transfer_type = USB_TRANSFER_INTERRUPT,
+	.direction = USB_DIRECTION_IN,
+	.interface_class = USB_CLASS_DIAGNOSTIC,
+	.interface_subclass = 0x00,
+	.interface_protocol = 0x01,
+	.flags = 0
+};
+static const usb_endpoint_description_t intr_out_ep = {
+	.transfer_type = USB_TRANSFER_INTERRUPT,
+	.direction = USB_DIRECTION_OUT,
+	.interface_class = USB_CLASS_DIAGNOSTIC,
+	.interface_subclass = 0x00,
+	.interface_protocol = 0x01,
+	.flags = 0
+};
+static const usb_endpoint_description_t bulk_in_ep = {
+	.transfer_type = USB_TRANSFER_BULK,
+	.direction = USB_DIRECTION_IN,
+	.interface_class = USB_CLASS_DIAGNOSTIC,
+	.interface_subclass = 0x00,
+	.interface_protocol = 0x01,
+	.flags = 0
+};
+static const usb_endpoint_description_t bulk_out_ep = {
+	.transfer_type = USB_TRANSFER_BULK,
+	.direction = USB_DIRECTION_OUT,
+	.interface_class = USB_CLASS_DIAGNOSTIC,
+	.interface_subclass = 0x00,
+	.interface_protocol = 0x01,
+	.flags = 0
+};
+static const usb_endpoint_description_t isoch_in_ep = {
+	.transfer_type = USB_TRANSFER_ISOCHRONOUS,
+	.direction = USB_DIRECTION_IN,
+	.interface_class = USB_CLASS_DIAGNOSTIC,
+	.interface_subclass = 0x00,
+	.interface_protocol = 0x01,
+	.flags = 0
+};
+static const usb_endpoint_description_t isoch_out_ep = {
+	.transfer_type = USB_TRANSFER_ISOCHRONOUS,
+	.direction = USB_DIRECTION_OUT,
+	.interface_class = USB_CLASS_DIAGNOSTIC,
+	.interface_subclass = 0x00,
+	.interface_protocol = 0x01,
+	.flags = 0
+};
+
+static const usb_endpoint_description_t *diag_endpoints[] = {
+	[USBDIAG_EP_INTR_IN] = &intr_in_ep,
+	[USBDIAG_EP_INTR_OUT] = &intr_out_ep,
+	[USBDIAG_EP_BULK_IN] = &bulk_in_ep,
+	[USBDIAG_EP_BULK_OUT] = &bulk_out_ep,
+	[USBDIAG_EP_ISOCH_IN] = &isoch_in_ep,
+	[USBDIAG_EP_ISOCH_OUT] = &isoch_out_ep,
+	NULL
+};
+
+/** USB diagnostic driver ops. */
+static const usb_driver_ops_t diag_driver_ops = {
+	.device_add = device_add,
+	.device_remove = device_remove,
+	.device_gone = device_gone,
+	.function_online = function_online,
+	.function_offline = function_offline
+};
+
+/** USB diagnostic driver. */
+static const usb_driver_t diag_driver = {
+	.name = NAME,
+	.ops = &diag_driver_ops,
+	.endpoints = &diag_endpoints[1] /* EPs are indexed from 1. */
+};
+
+int main(int argc, char *argv[])
+{
+	printf(NAME ": USB diagnostic device driver.\n");
+
+	log_init(NAME);
+
+	return usb_driver_main(&diag_driver);
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/usbdiag/tests.c
===================================================================
--- uspace/drv/bus/usb/usbdiag/tests.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/usbdiag/tests.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,375 @@
+/*
+ * Copyright (c) 2017 Petr Manek
+ * 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 drvusbdiag
+ * @{
+ */
+/**
+ * @file
+ * Code for executing diagnostic tests.
+ */
+#include <errno.h>
+#include <str_error.h>
+#include <usb/debug.h>
+#include <usbdiag_iface.h>
+#include <time.h>
+#include "device.h"
+#include "tests.h"
+
+#define NAME "usbdiag"
+
+static int burst_in_test(usb_pipe_t *pipe, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	if (!pipe)
+		return EBADMEM;
+
+	char *buffer = (char *) malloc(size);
+	if (!buffer)
+		return ENOMEM;
+
+	// TODO: Are we sure that no other test is running on this endpoint?
+
+	usb_log_info("Performing %s IN burst test.", usb_str_transfer_type(pipe->desc.transfer_type));
+
+	int rc = EOK;
+	struct timeval start_time;
+	gettimeofday(&start_time, NULL);
+
+	for (int i = 0; i < cycles; ++i) {
+		// Read device's response.
+		size_t remaining = size;
+		size_t transferred;
+
+		while (remaining > 0) {
+			if ((rc = usb_pipe_read(pipe, buffer + size - remaining, remaining, &transferred))) {
+				usb_log_error("Read of %s IN endpoint failed with error: %s", usb_str_transfer_type(pipe->desc.transfer_type), str_error(rc));
+				break;
+			}
+
+			if (transferred > remaining) {
+				usb_log_error("Read of %s IN endpoint returned more data than expected.", usb_str_transfer_type(pipe->desc.transfer_type));
+				rc = EINVAL;
+				break;
+			}
+
+			remaining -= transferred;
+		}
+
+		if (rc)
+			break;
+	}
+
+	struct timeval final_time;
+	gettimeofday(&final_time, NULL);
+	usbdiag_dur_t in_duration = ((final_time.tv_usec - start_time.tv_usec) / 1000) +
+	    ((final_time.tv_sec - start_time.tv_sec) * 1000);
+
+	usb_log_info("Burst test on %s IN endpoint completed in %lu ms.", usb_str_transfer_type(pipe->desc.transfer_type), in_duration);
+
+	free(buffer);
+	if (duration)
+		*duration = in_duration;
+
+	return rc;
+}
+
+static int burst_out_test(usb_pipe_t *pipe, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	if (!pipe)
+		return EBADMEM;
+
+	char *buffer = (char *) malloc(size);
+	if (!buffer)
+		return ENOMEM;
+
+	memset(buffer, 42, size);
+
+	// TODO: Are we sure that no other test is running on this endpoint?
+
+	usb_log_info("Performing %s OUT burst test.", usb_str_transfer_type(pipe->desc.transfer_type));
+
+	int rc = EOK;
+	struct timeval start_time;
+	gettimeofday(&start_time, NULL);
+
+	for (int i = 0; i < cycles; ++i) {
+		// Write buffer to device.
+		if ((rc = usb_pipe_write(pipe, buffer, size))) {
+			usb_log_error("Write to %s OUT endpoint failed with error: %s", usb_str_transfer_type(pipe->desc.transfer_type), str_error(rc));
+			break;
+		}
+	}
+
+	struct timeval final_time;
+	gettimeofday(&final_time, NULL);
+	usbdiag_dur_t in_duration = ((final_time.tv_usec - start_time.tv_usec) / 1000) +
+	    ((final_time.tv_sec - start_time.tv_sec) * 1000);
+
+	usb_log_info("Burst test on %s OUT endpoint completed in %ld ms.", usb_str_transfer_type(pipe->desc.transfer_type), in_duration);
+
+	free(buffer);
+	if (duration)
+		*duration = in_duration;
+
+	return rc;
+}
+
+static const uint32_t test_data_src = 0xDEADBEEF;
+
+static int data_in_test(usb_pipe_t *pipe, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	if (!pipe)
+		return EBADMEM;
+
+	const uint32_t test_data = uint32_host2usb(test_data_src);
+
+	if (size % sizeof(test_data))
+		return EINVAL;
+
+	char *buffer = (char *) malloc(size);
+	if (!buffer)
+		return ENOMEM;
+
+	// TODO: Are we sure that no other test is running on this endpoint?
+
+	usb_log_info("Performing %s IN data test.", usb_str_transfer_type(pipe->desc.transfer_type));
+
+	int rc = EOK;
+	struct timeval start_time;
+	gettimeofday(&start_time, NULL);
+
+	for (int i = 0; i < cycles; ++i) {
+		// Read device's response.
+		size_t remaining = size;
+		size_t transferred;
+
+		while (remaining > 0) {
+			if ((rc = usb_pipe_read(pipe, buffer + size - remaining, remaining, &transferred))) {
+				usb_log_error("Read of %s IN endpoint failed with error: %s", usb_str_transfer_type(pipe->desc.transfer_type), str_error(rc));
+				break;
+			}
+
+			if (transferred > remaining) {
+				usb_log_error("Read of %s IN endpoint returned more data than expected.", usb_str_transfer_type(pipe->desc.transfer_type));
+				rc = EINVAL;
+				break;
+			}
+
+			remaining -= transferred;
+		}
+
+		if (rc)
+			break;
+
+		for (size_t i = 0; i < size; i += sizeof(test_data)) {
+			if (*(uint32_t *)(buffer + i) != test_data) {
+				usb_log_error("Read of %s IN endpoint returned "
+				    "invald data at address %zu.",
+				    usb_str_transfer_type(pipe->desc.transfer_type), i);
+				rc = EINVAL;
+				break;
+			}
+		}
+
+		if (rc)
+			break;
+	}
+
+	struct timeval final_time;
+	gettimeofday(&final_time, NULL);
+	usbdiag_dur_t in_duration = ((final_time.tv_usec - start_time.tv_usec) / 1000) +
+	    ((final_time.tv_sec - start_time.tv_sec) * 1000);
+
+	usb_log_info("Data test on %s IN endpoint completed in %lu ms.", usb_str_transfer_type(pipe->desc.transfer_type), in_duration);
+
+	free(buffer);
+	if (duration)
+		*duration = in_duration;
+
+	return rc;
+}
+
+static int data_out_test(usb_pipe_t *pipe, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	if (!pipe)
+		return EBADMEM;
+
+	const uint32_t test_data = uint32_host2usb(test_data_src);
+
+	if (size % sizeof(test_data))
+		return EINVAL;
+
+	char *buffer = (char *) malloc(size);
+	if (!buffer)
+		return ENOMEM;
+
+	for (size_t i = 0; i < size; i += sizeof(test_data)) {
+		memcpy(buffer + i, &test_data, sizeof(test_data));
+	}
+
+	// TODO: Are we sure that no other test is running on this endpoint?
+
+	usb_log_info("Performing %s OUT data test.", usb_str_transfer_type(pipe->desc.transfer_type));
+
+	int rc = EOK;
+	struct timeval start_time;
+	gettimeofday(&start_time, NULL);
+
+	for (int i = 0; i < cycles; ++i) {
+		// Write buffer to device.
+		if ((rc = usb_pipe_write(pipe, buffer, size))) {
+			usb_log_error("Write to %s OUT endpoint failed with error: %s", usb_str_transfer_type(pipe->desc.transfer_type), str_error(rc));
+			break;
+		}
+	}
+
+	struct timeval final_time;
+	gettimeofday(&final_time, NULL);
+	usbdiag_dur_t in_duration = ((final_time.tv_usec - start_time.tv_usec) / 1000) +
+	    ((final_time.tv_sec - start_time.tv_sec) * 1000);
+
+	usb_log_info("Data test on %s OUT endpoint completed in %ld ms.", usb_str_transfer_type(pipe->desc.transfer_type), in_duration);
+
+	free(buffer);
+	if (duration)
+		*duration = in_duration;
+
+	return rc;
+}
+
+int usbdiag_burst_test_intr_in(ddf_fun_t *fun, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	usbdiag_dev_t *dev = ddf_fun_to_usbdiag_dev(fun);
+	if (!dev)
+		return EBADMEM;
+
+	return burst_in_test(dev->intr_in, cycles, size, duration);
+}
+
+int usbdiag_burst_test_intr_out(ddf_fun_t *fun, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	usbdiag_dev_t *dev = ddf_fun_to_usbdiag_dev(fun);
+	if (!dev)
+		return EBADMEM;
+
+	return burst_out_test(dev->intr_out, cycles, size, duration);
+}
+
+int usbdiag_burst_test_bulk_in(ddf_fun_t *fun, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	usbdiag_dev_t *dev = ddf_fun_to_usbdiag_dev(fun);
+	if (!dev)
+		return EBADMEM;
+
+	return burst_in_test(dev->bulk_in, cycles, size, duration);
+}
+
+int usbdiag_burst_test_bulk_out(ddf_fun_t *fun, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	usbdiag_dev_t *dev = ddf_fun_to_usbdiag_dev(fun);
+	if (!dev)
+		return EBADMEM;
+
+	return burst_out_test(dev->bulk_out, cycles, size, duration);
+}
+
+int usbdiag_burst_test_isoch_in(ddf_fun_t *fun, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	usbdiag_dev_t *dev = ddf_fun_to_usbdiag_dev(fun);
+	if (!dev)
+		return EBADMEM;
+
+	return burst_in_test(dev->isoch_in, cycles, size, duration);
+}
+
+int usbdiag_burst_test_isoch_out(ddf_fun_t *fun, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	usbdiag_dev_t *dev = ddf_fun_to_usbdiag_dev(fun);
+	if (!dev)
+		return EBADMEM;
+
+	return burst_out_test(dev->isoch_out, cycles, size, duration);
+}
+
+int usbdiag_data_test_intr_in(ddf_fun_t *fun, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	usbdiag_dev_t *dev = ddf_fun_to_usbdiag_dev(fun);
+	if (!dev)
+		return EBADMEM;
+
+	return data_in_test(dev->intr_in, cycles, size, duration);
+}
+
+int usbdiag_data_test_intr_out(ddf_fun_t *fun, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	usbdiag_dev_t *dev = ddf_fun_to_usbdiag_dev(fun);
+	if (!dev)
+		return EBADMEM;
+
+	return data_out_test(dev->intr_out, cycles, size, duration);
+}
+
+int usbdiag_data_test_bulk_in(ddf_fun_t *fun, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	usbdiag_dev_t *dev = ddf_fun_to_usbdiag_dev(fun);
+	if (!dev)
+		return EBADMEM;
+
+	return data_in_test(dev->bulk_in, cycles, size, duration);
+}
+
+int usbdiag_data_test_bulk_out(ddf_fun_t *fun, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	usbdiag_dev_t *dev = ddf_fun_to_usbdiag_dev(fun);
+	if (!dev)
+		return EBADMEM;
+
+	return data_out_test(dev->bulk_out, cycles, size, duration);
+}
+
+int usbdiag_data_test_isoch_in(ddf_fun_t *fun, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	usbdiag_dev_t *dev = ddf_fun_to_usbdiag_dev(fun);
+	if (!dev)
+		return EBADMEM;
+
+	return data_in_test(dev->isoch_in, cycles, size, duration);
+}
+
+int usbdiag_data_test_isoch_out(ddf_fun_t *fun, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	usbdiag_dev_t *dev = ddf_fun_to_usbdiag_dev(fun);
+	if (!dev)
+		return EBADMEM;
+
+	return data_out_test(dev->isoch_out, cycles, size, duration);
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/usbdiag/tests.h
===================================================================
--- uspace/drv/bus/usb/usbdiag/tests.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/usbdiag/tests.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2017 Petr Manek
+ * 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 drvusbdiag
+ * @{
+ */
+/** @file
+ * USB diagnostic tests.
+ */
+
+#ifndef USBDIAG_TESTS_H_
+#define USBDIAG_TESTS_H_
+
+#include <ddf/driver.h>
+
+int usbdiag_burst_test_intr_in(ddf_fun_t *, int, size_t, usbdiag_dur_t *);
+int usbdiag_burst_test_intr_out(ddf_fun_t *, int, size_t, usbdiag_dur_t *);
+int usbdiag_burst_test_bulk_in(ddf_fun_t *, int, size_t, usbdiag_dur_t *);
+int usbdiag_burst_test_bulk_out(ddf_fun_t *, int, size_t, usbdiag_dur_t *);
+int usbdiag_burst_test_isoch_in(ddf_fun_t *, int, size_t, usbdiag_dur_t *);
+int usbdiag_burst_test_isoch_out(ddf_fun_t *, int, size_t, usbdiag_dur_t *);
+
+int usbdiag_data_test_intr_in(ddf_fun_t *, int, size_t, usbdiag_dur_t *);
+int usbdiag_data_test_intr_out(ddf_fun_t *, int, size_t, usbdiag_dur_t *);
+int usbdiag_data_test_bulk_in(ddf_fun_t *, int, size_t, usbdiag_dur_t *);
+int usbdiag_data_test_bulk_out(ddf_fun_t *, int, size_t, usbdiag_dur_t *);
+int usbdiag_data_test_isoch_in(ddf_fun_t *, int, size_t, usbdiag_dur_t *);
+int usbdiag_data_test_isoch_out(ddf_fun_t *, int, size_t, usbdiag_dur_t *);
+
+#endif /* USBDIAG_TESTS_H_ */
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/usbdiag/usbdiag.ma
===================================================================
--- uspace/drv/bus/usb/usbdiag/usbdiag.ma	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/usbdiag/usbdiag.ma	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,2 @@
+100 usb&diagnostic
+100 usb&class=diagnostic
Index: uspace/drv/bus/usb/usbflbk/main.c
===================================================================
--- uspace/drv/bus/usb/usbflbk/main.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/usbflbk/main.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -48,5 +48,5 @@
 static errno_t usbfallback_device_add(usb_device_t *dev)
 {
-	usb_log_info("Pretending to control %s `%s'.\n",
+	usb_log_info("Pretending to control %s `%s'.",
 	    usb_device_get_iface_number(dev) < 0 ? "device" : "interface",
 	    usb_device_get_name(dev));
@@ -62,4 +62,12 @@
 {
 	assert(dev);
+	usb_log_info("Device '%s' gone.", usb_device_get_name(dev));
+	return EOK;
+}
+
+static int usbfallback_device_remove(usb_device_t *dev)
+{
+	assert(dev);
+	usb_log_info("Device '%s' removed.", usb_device_get_name(dev));
 	return EOK;
 }
@@ -68,5 +76,5 @@
 static const usb_driver_ops_t usbfallback_driver_ops = {
 	.device_add = usbfallback_device_add,
-	.device_rem = usbfallback_device_gone,
+	.device_remove = usbfallback_device_remove,
 	.device_gone = usbfallback_device_gone,
 };
Index: uspace/drv/bus/usb/usbhub/main.c
===================================================================
--- uspace/drv/bus/usb/usbhub/main.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/usbhub/main.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -48,13 +48,8 @@
 static const usb_driver_ops_t usb_hub_driver_ops = {
 	.device_add = usb_hub_device_add,
-//	.device_rem = usb_hub_device_remove,
+	.device_remove = usb_hub_device_remove,
 	.device_gone = usb_hub_device_gone,
 };
 
-/** Hub endpoints, excluding control endpoint. */
-static const usb_endpoint_description_t *usb_hub_endpoints[] = {
-	&hub_status_change_endpoint_description,
-	NULL,
-};
 /** Static usb hub driver information. */
 static const usb_driver_t usb_hub_driver = {
Index: uspace/drv/bus/usb/usbhub/port.c
===================================================================
--- uspace/drv/bus/usb/usbhub/port.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/usbhub/port.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -40,4 +40,5 @@
 #include <inttypes.h>
 #include <fibril_synch.h>
+#include <usbhc_iface.h>
 
 #include <usb/debug.h>
@@ -47,84 +48,213 @@
 #include "status.h"
 
-/** Information for fibril for device discovery. */
-struct add_device_phase1 {
-	usb_hub_dev_t *hub;
-	usb_hub_port_t *port;
-	usb_speed_t speed;
-};
-
-static errno_t usb_hub_port_device_gone(usb_hub_port_t *port, usb_hub_dev_t *hub);
-static void usb_hub_port_reset_completed(usb_hub_port_t *port,
-    usb_hub_dev_t *hub, usb_port_status_t status);
-static errno_t get_port_status(usb_hub_port_t *port, usb_port_status_t *status);
-static errno_t add_device_phase1_worker_fibril(void *arg);
-static errno_t create_add_device_fibril(usb_hub_port_t *port, usb_hub_dev_t *hub,
-    usb_speed_t speed);
-
-errno_t usb_hub_port_fini(usb_hub_port_t *port, usb_hub_dev_t *hub)
+#define port_log(lvl, port, fmt, ...) do { usb_log_##lvl("(%p-%u): " fmt, (port->hub), (port->port_number), ##__VA_ARGS__); } while (0)
+
+/** Initialize hub port information.
+ *
+ * @param port Port to be initialized.
+ */
+void usb_hub_port_init(usb_hub_port_t *port, usb_hub_dev_t *hub, unsigned int port_number)
 {
 	assert(port);
-	if (port->device_attached)
-		return usb_hub_port_device_gone(port, hub);
+	memset(port, 0, sizeof(*port));
+	port->hub = hub;
+	port->port_number = port_number;
+	usb_port_init(&port->base);
+}
+
+static inline usb_hub_port_t *get_hub_port(usb_port_t *port)
+{
+	assert(port);
+	return (usb_hub_port_t *) port;
+}
+
+/**
+ * Inform the HC that the device on port is gone.
+ */
+static void remove_device(usb_port_t *port_base)
+{
+	usb_hub_port_t *port = get_hub_port(port_base);
+
+	async_exch_t *exch = usb_device_bus_exchange_begin(port->hub->usb_device);
+	if (!exch) {
+		port_log(error, port, "Cannot remove the device, failed creating exchange.");
+		return;
+	}
+	
+	const int err = usbhc_device_remove(exch, port->port_number);
+	if (err)
+		port_log(error, port, "Failed to remove device: %s", str_error(err));
+
+	usb_device_bus_exchange_end(exch);
+}
+
+
+static usb_speed_t get_port_speed(usb_hub_port_t *port, uint32_t status)
+{
+	assert(port);
+	assert(port->hub);
+
+	return usb_port_speed(port->hub->speed, status);
+}
+
+/**
+ * Routine for adding a new device in USB2.
+ */
+static errno_t enumerate_device_usb2(usb_hub_port_t *port, async_exch_t *exch)
+{
+	errno_t err;
+
+	port_log(debug, port, "Requesting default address.");
+	err = usb_hub_reserve_default_address(port->hub, exch, &port->base);
+	if (err != EOK) {
+		port_log(error, port, "Failed to reserve default address: %s", str_error(err));
+		return err;
+	}
+
+	/* Reservation of default address could have blocked */
+	if (port->base.state != PORT_CONNECTING)
+		goto out_address;
+
+	port_log(debug, port, "Resetting port.");
+	if ((err = usb_hub_set_port_feature(port->hub, port->port_number, USB_HUB_FEATURE_PORT_RESET))) {
+		port_log(warning, port, "Port reset request failed: %s", str_error(err));
+		goto out_address;
+	}
+
+	if ((err = usb_port_wait_for_enabled(&port->base))) {
+		port_log(error, port, "Failed to reset port: %s", str_error(err));
+		goto out_address;
+	}
+
+	port_log(debug, port, "Enumerating device.");
+	if ((err = usbhc_device_enumerate(exch, port->port_number, port->speed))) {
+		port_log(error, port, "Failed to enumerate device: %s", str_error(err));
+		/* Disable the port */
+		usb_hub_clear_port_feature(port->hub, port->port_number, USB2_HUB_FEATURE_PORT_ENABLE);
+		goto out_address;
+	}
+
+	port_log(debug, port, "Device enumerated");
+
+out_address:
+	usb_hub_release_default_address(port->hub, exch);
+	return err;
+}
+
+/**
+ * Routine for adding a new device in USB 3.
+ */
+static errno_t enumerate_device_usb3(usb_hub_port_t *port, async_exch_t *exch)
+{
+	errno_t err;
+
+	port_log(debug, port, "Issuing a warm reset.");
+	if ((err = usb_hub_set_port_feature(port->hub, port->port_number, USB3_HUB_FEATURE_BH_PORT_RESET))) {
+		port_log(warning, port, "Port reset request failed: %s", str_error(err));
+		return err;
+	}
+
+	if ((err = usb_port_wait_for_enabled(&port->base))) {
+		port_log(error, port, "Failed to reset port: %s", str_error(err));
+		return err;
+	}
+
+	port_log(debug, port, "Enumerating device.");
+	if ((err = usbhc_device_enumerate(exch, port->port_number, port->speed))) {
+		port_log(error, port, "Failed to enumerate device: %s", str_error(err));
+		return err;
+	}
+
+	port_log(debug, port, "Device enumerated");
 	return EOK;
 }
 
-/**
- * Clear feature on hub port.
- *
- * @param port Port structure.
- * @param feature Feature selector.
- * @return Operation result
- */
-errno_t usb_hub_port_clear_feature(
-    usb_hub_port_t *port, usb_hub_class_feature_t feature)
-{
-	assert(port);
-	const usb_device_request_setup_packet_t clear_request = {
-		.request_type = USB_HUB_REQ_TYPE_CLEAR_PORT_FEATURE,
-		.request = USB_DEVREQ_CLEAR_FEATURE,
-		.value = feature,
-		.index = port->port_number,
-		.length = 0,
-	};
-	return usb_pipe_control_write(port->control_pipe, &clear_request,
-	    sizeof(clear_request), NULL, 0);
-}
-
-/**
- * Set feature on hub port.
- *
- * @param port Port structure.
- * @param feature Feature selector.
- * @return Operation result
- */
-errno_t usb_hub_port_set_feature(
-    usb_hub_port_t *port, usb_hub_class_feature_t feature)
-{
-	assert(port);
-	const usb_device_request_setup_packet_t clear_request = {
-		.request_type = USB_HUB_REQ_TYPE_SET_PORT_FEATURE,
-		.request = USB_DEVREQ_SET_FEATURE,
-		.index = port->port_number,
-		.value = feature,
-		.length = 0,
-	};
-	return usb_pipe_control_write(port->control_pipe, &clear_request,
-	    sizeof(clear_request), NULL, 0);
-}
-
-/**
- * Mark reset process as failed due to external reasons
- *
- * @param port Port structure
- */
-void usb_hub_port_reset_fail(usb_hub_port_t *port)
-{
-	assert(port);
-	fibril_mutex_lock(&port->mutex);
-	if (port->reset_status == IN_RESET)
-		port->reset_status = RESET_FAIL;
-	fibril_condvar_broadcast(&port->reset_cv);
-	fibril_mutex_unlock(&port->mutex);
+static errno_t enumerate_device(usb_port_t *port_base)
+{
+	usb_hub_port_t *port = get_hub_port(port_base);
+
+	port_log(debug, port, "Setting up new device.");
+	async_exch_t *exch = usb_device_bus_exchange_begin(port->hub->usb_device);
+	if (!exch) {
+		port_log(error, port, "Failed to create exchange.");
+		return ENOMEM;
+	}
+
+	const errno_t err = port->hub->speed == USB_SPEED_SUPER
+		? enumerate_device_usb3(port, exch)
+		: enumerate_device_usb2(port, exch);
+
+	usb_device_bus_exchange_end(exch);
+	return err;
+}
+
+static void port_changed_connection(usb_hub_port_t *port, usb_port_status_t status)
+{
+	const bool connected = !!(status & USB_HUB_PORT_STATUS_CONNECTION);
+	port_log(debug, port, "Connection change: device %s.", connected ? "attached" : "removed");
+
+	if (connected) {
+		usb_port_connected(&port->base, &enumerate_device);
+	} else {
+		usb_port_disabled(&port->base, &remove_device);
+	}
+}
+
+static void port_changed_enabled(usb_hub_port_t *port, usb_port_status_t status)
+{
+	const bool enabled = !!(status & USB_HUB_PORT_STATUS_ENABLE);
+	if (enabled) {
+		port_log(warning, port, "Port unexpectedly changed to enabled.");
+	} else {
+		usb_port_disabled(&port->base, &remove_device);
+	}
+}
+
+static void port_changed_overcurrent(usb_hub_port_t *port, usb_port_status_t status)
+{
+	const bool overcurrent = !!(status & USB_HUB_PORT_STATUS_OC);
+
+	/* According to the USB specs:
+	 * 11.13.5 Over-current Reporting and Recovery
+	 * Hub device is responsible for putting port in power off
+	 * mode. USB system software is responsible for powering port
+	 * back on when the over-current condition is gone */
+
+	usb_port_disabled(&port->base, &remove_device);
+
+	if (!overcurrent) {
+		const int err = usb_hub_set_port_feature(port->hub, port->port_number, USB_HUB_FEATURE_PORT_POWER);
+		if (err)
+			port_log(error, port, "Failed to set port power after OC: %s.", str_error(err));
+	}
+}
+
+static void port_changed_reset(usb_hub_port_t *port, usb_port_status_t status)
+{
+	const bool enabled = !!(status & USB_HUB_PORT_STATUS_ENABLE);
+
+	if (enabled) {
+		port->speed = get_port_speed(port, status);
+		usb_port_enabled(&port->base);
+	} else
+		usb_port_disabled(&port->base, &remove_device);
+}
+
+typedef void (*change_handler_t)(usb_hub_port_t *, usb_port_status_t);
+
+static void check_port_change(usb_hub_port_t *port, usb_port_status_t *status,
+    change_handler_t handler, usb_port_status_t mask, usb_hub_class_feature_t feature)
+{
+	if ((*status & mask) == 0)
+		return;
+
+	/* Clear the change so it won't come again */
+	usb_hub_clear_port_feature(port->hub, port->port_number, feature);
+
+	if (handler)
+		handler(port, *status);
+
+	/* Mark the change as resolved */
+	*status &= ~mask;
 }
 
@@ -136,369 +266,42 @@
  * @param hub hub representation
  */
-void usb_hub_port_process_interrupt(usb_hub_port_t *port, usb_hub_dev_t *hub)
+void usb_hub_port_process_interrupt(usb_hub_port_t *port)
 {
 	assert(port);
-	assert(hub);
-	usb_log_debug2("(%p-%u): Interrupt.\n", hub, port->port_number);
+	port_log(debug2, port, "Interrupt.");
 
 	usb_port_status_t status = 0;
-	const errno_t opResult = get_port_status(port, &status);
-	if (opResult != EOK) {
-		usb_log_error("(%p-%u): Failed to get port status: %s.\n", hub,
-		    port->port_number, str_error(opResult));
+	const int err = usb_hub_get_port_status(port->hub, port->port_number, &status);
+	if (err != EOK) {
+		port_log(error, port, "Failed to get port status: %s.", str_error(err));
 		return;
 	}
 
-	/* Connection change */
-	if (status & USB_HUB_PORT_C_STATUS_CONNECTION) {
-		const bool connected =
-		    (status & USB_HUB_PORT_STATUS_CONNECTION) != 0;
-		usb_log_debug("(%p-%u): Connection change: device %s.\n", hub,
-		    port->port_number, connected ? "attached" : "removed");
-
-		/* ACK the change */
-		const errno_t opResult = usb_hub_port_clear_feature(port,
-		    USB_HUB_FEATURE_C_PORT_CONNECTION);
-		if (opResult != EOK) {
-			usb_log_warning("(%p-%u): Failed to clear "
-			    "port-change-connection flag: %s.\n", hub,
-			    port->port_number, str_error(opResult));
-		}
-
-		if (connected) {
-			const errno_t opResult = create_add_device_fibril(port, hub,
-			    usb_port_speed(status));
-			if (opResult != EOK) {
-				usb_log_error("(%p-%u): Cannot handle change on"
-				   " port: %s.\n", hub, port->port_number,
-				   str_error(opResult));
-			}
-		} else {
-			/* Handle the case we were in reset */
-			// FIXME: usb_hub_port_reset_fail(port);
-			/* If enabled change was reported leave the removal
-			 * to that handler, it shall ACK the change too. */
-			if (!(status & USB_HUB_PORT_C_STATUS_ENABLED)) {
-				usb_hub_port_device_gone(port, hub);
-			}
-		}
-	}
-
-	/* Enable change, ports are automatically disabled on errors. */
-	if (status & USB_HUB_PORT_C_STATUS_ENABLED) {
-		// TODO: maybe HS reset failed?
-		usb_log_info("(%p-%u): Port disabled because of errors.\n", hub,
-		   port->port_number);
-		usb_hub_port_device_gone(port, hub);
-		const errno_t rc = usb_hub_port_clear_feature(port,
-		        USB_HUB_FEATURE_C_PORT_ENABLE);
-		if (rc != EOK) {
-			usb_log_error("(%p-%u): Failed to clear port enable "
-			    "change feature: %s.", hub, port->port_number,
-			    str_error(rc));
-		}
-
-	}
-
-	/* Suspend change */
-	if (status & USB_HUB_PORT_C_STATUS_SUSPEND) {
-		usb_log_error("(%p-%u): Port went to suspend state, this should"
-		    " NOT happen as we do not support suspend state!", hub,
-		    port->port_number);
-		const errno_t rc = usb_hub_port_clear_feature(port,
-		        USB_HUB_FEATURE_C_PORT_SUSPEND);
-		if (rc != EOK) {
-			usb_log_error("(%p-%u): Failed to clear port suspend "
-			    "change feature: %s.", hub, port->port_number,
-			    str_error(rc));
-		}
-	}
-
-	/* Over current */
-	if (status & USB_HUB_PORT_C_STATUS_OC) {
-		usb_log_debug("(%p-%u): Port OC reported!.", hub,
-		    port->port_number);
-		/* According to the USB specs:
-		 * 11.13.5 Over-current Reporting and Recovery
-		 * Hub device is responsible for putting port in power off
-		 * mode. USB system software is responsible for powering port
-		 * back on when the over-current condition is gone */
-		const errno_t rc = usb_hub_port_clear_feature(port,
-		    USB_HUB_FEATURE_C_PORT_OVER_CURRENT);
-		if (rc != EOK) {
-			usb_log_error("(%p-%u): Failed to clear port OC change "
-			    "feature: %s.\n", hub, port->port_number,
-			    str_error(rc));
-		}
-		if (!(status & ~USB_HUB_PORT_STATUS_OC)) {
-			const errno_t rc = usb_hub_port_set_feature(
-			    port, USB_HUB_FEATURE_PORT_POWER);
-			if (rc != EOK) {
-				usb_log_error("(%p-%u): Failed to set port "
-				    "power after OC: %s.", hub,
-				    port->port_number, str_error(rc));
-			}
-		}
-	}
-
-	/* Port reset, set on port reset complete. */
-	if (status & USB_HUB_PORT_C_STATUS_RESET) {
-		usb_hub_port_reset_completed(port, hub, status);
-	}
-
-	usb_log_debug2("(%p-%u): Port status %#08" PRIx32, hub,
-	    port->port_number, status);
-}
-
-/**
- * routine called when a device on port has been removed
- *
- * If the device on port had default address, it releases default address.
- * Otherwise does not do anything, because DDF does not allow to remove device
- * from it`s device tree.
- * @param port port structure
- * @param hub hub representation
- */
-errno_t usb_hub_port_device_gone(usb_hub_port_t *port, usb_hub_dev_t *hub)
-{
-	assert(port);
-	assert(hub);
-	async_exch_t *exch = usb_device_bus_exchange_begin(hub->usb_device);
-	if (!exch)
-		return ENOMEM;
-	const errno_t rc = usb_device_remove(exch, port->port_number);
-	usb_device_bus_exchange_end(exch);
-	if (rc == EOK)
-		port->device_attached = false;
-	return rc;
-
-}
-
-/**
- * Process port reset change
- *
- * After this change port should be enabled, unless some problem occurred.
- * This functions triggers second phase of enabling new device.
- * @param port Port structure
- * @param status Port status mask
- */
-void usb_hub_port_reset_completed(usb_hub_port_t *port, usb_hub_dev_t *hub,
-    usb_port_status_t status)
-{
-	assert(port);
-	fibril_mutex_lock(&port->mutex);
-	const bool enabled = (status & USB_HUB_PORT_STATUS_ENABLED) != 0;
-	/* Finalize device adding. */
-
-	if (enabled) {
-		port->reset_status = RESET_OK;
-		usb_log_debug("(%p-%u): Port reset complete.\n", hub,
-		    port->port_number);
+	check_port_change(port, &status, &port_changed_connection,
+	    USB_HUB_PORT_STATUS_C_CONNECTION, USB_HUB_FEATURE_C_PORT_CONNECTION);
+
+	check_port_change(port, &status, &port_changed_overcurrent,
+	    USB_HUB_PORT_STATUS_C_OC, USB_HUB_FEATURE_C_PORT_OVER_CURRENT);
+
+	check_port_change(port, &status, &port_changed_reset,
+	    USB_HUB_PORT_STATUS_C_RESET, USB_HUB_FEATURE_C_PORT_RESET);
+
+	if (port->hub->speed <= USB_SPEED_HIGH) {
+		check_port_change(port, &status, &port_changed_enabled,
+		    USB2_HUB_PORT_STATUS_C_ENABLE, USB2_HUB_FEATURE_C_PORT_ENABLE);
 	} else {
-		port->reset_status = RESET_FAIL;
-		usb_log_warning("(%p-%u): Port reset complete but port not "
-		    "enabled.", hub, port->port_number);
-	}
-	fibril_condvar_broadcast(&port->reset_cv);
-	fibril_mutex_unlock(&port->mutex);
-
-	/* Clear the port reset change. */
-	errno_t rc = usb_hub_port_clear_feature(port, USB_HUB_FEATURE_C_PORT_RESET);
-	if (rc != EOK) {
-		usb_log_error("(%p-%u): Failed to clear port reset change: %s.",
-		    hub, port->port_number, str_error(rc));
-	}
-}
-
-/** Retrieve port status.
- *
- * @param[in] port Port structure
- * @param[out] status Where to store the port status.
- * @return Error code.
- */
-static errno_t get_port_status(usb_hub_port_t *port, usb_port_status_t *status)
-{
-	assert(port);
-	/* USB hub specific GET_PORT_STATUS request. See USB Spec 11.16.2.6
-	 * Generic GET_STATUS request cannot be used because of the difference
-	 * in status data size (2B vs. 4B)*/
-	const usb_device_request_setup_packet_t request = {
-		.request_type = USB_HUB_REQ_TYPE_GET_PORT_STATUS,
-		.request = USB_HUB_REQUEST_GET_STATUS,
-		.value = 0,
-		.index = uint16_host2usb(port->port_number),
-		.length = sizeof(usb_port_status_t),
-	};
-	size_t recv_size;
-	usb_port_status_t status_tmp;
-
-	const errno_t rc = usb_pipe_control_read(port->control_pipe,
-	    &request, sizeof(usb_device_request_setup_packet_t),
-	    &status_tmp, sizeof(status_tmp), &recv_size);
-	if (rc != EOK) {
-		return rc;
-	}
-
-	if (recv_size != sizeof (status_tmp)) {
-		return ELIMIT;
-	}
-
-	if (status != NULL) {
-		*status = status_tmp;
-	}
-
-	return EOK;
-}
-
-static errno_t port_enable(usb_hub_port_t *port, usb_hub_dev_t *hub, bool enable)
-{
-	if (enable) {
-		errno_t rc =
-		    usb_hub_port_set_feature(port, USB_HUB_FEATURE_PORT_RESET);
-		if (rc != EOK) {
-			usb_log_error("(%p-%u): Port reset request failed: %s.",
-			    hub, port->port_number, str_error(rc));
-			return rc;
-		}
-		/* Wait until reset completes. */
-		fibril_mutex_lock(&port->mutex);
-		port->reset_status = IN_RESET;
-		while (port->reset_status == IN_RESET)
-			fibril_condvar_wait(&port->reset_cv, &port->mutex);
-		rc = port->reset_status == RESET_OK ? EOK : ESTALL;
-		fibril_mutex_unlock(&port->mutex);
-		return rc;
-	} else {
-		return usb_hub_port_clear_feature(port,
-				USB_HUB_FEATURE_PORT_ENABLE);
-	}
-}
-
-/** Fibril for adding a new device.
- *
- * Separate fibril is needed because the port reset completion is announced
- * via interrupt pipe and thus we cannot block here.
- *
- * @param arg Pointer to struct add_device_phase1.
- * @return 0 Always.
- */
-errno_t add_device_phase1_worker_fibril(void *arg)
-{
-	struct add_device_phase1 *params = arg;
-	assert(params);
-
-	errno_t ret = EOK;
-	usb_hub_dev_t *hub = params->hub;
-	usb_hub_port_t *port = params->port;
-	const usb_speed_t speed = params->speed;
-	free(arg);
-
-	usb_log_debug("(%p-%u): New device sequence.", hub, port->port_number);
-
-	async_exch_t *exch = usb_device_bus_exchange_begin(hub->usb_device);
-	if (!exch) {
-		usb_log_error("(%p-%u): Failed to begin bus exchange.", hub,
-		    port->port_number);
-		ret = ENOMEM;
-		goto out;
-	}
-
-	/* Reserve default address */
-	while ((ret = usb_reserve_default_address(exch, speed)) == ENOENT) {
-		async_usleep(1000000);
-	}
-	if (ret != EOK) {
-		usb_log_error("(%p-%u): Failed to reserve default address: %s",
-		    hub, port->port_number, str_error(ret));
-		goto out;
-	}
-
-	usb_log_debug("(%p-%u): Got default address reseting port.", hub,
-	    port->port_number);
-	/* Reset port */
-	ret = port_enable(port, hub, true);
-	if (ret != EOK) {
-		usb_log_error("(%p-%u): Failed to reset port.", hub,
-		    port->port_number);
-		if (usb_release_default_address(exch) != EOK)
-			usb_log_warning("(%p-%u): Failed to release default "
-			    "address.", hub, port->port_number);
-		ret = EIO;
-		goto out;
-	}
-	usb_log_debug("(%p-%u): Port reset, enumerating device", hub,
-	    port->port_number);
-
-	ret = usb_device_enumerate(exch, port->port_number);
-	if (ret != EOK) {
-		usb_log_error("(%p-%u): Failed to enumerate device: %s", hub,
-		    port->port_number, str_error(ret));
-		const errno_t ret = port_enable(port, hub, false);
-		if (ret != EOK) {
-			usb_log_warning("(%p-%u)Failed to disable port (%s), "
-			    "NOT releasing default address.", hub,
-			    port->port_number, str_error(ret));
-		} else {
-			const errno_t ret = usb_release_default_address(exch);
-			if (ret != EOK)
-				usb_log_warning("(%p-%u): Failed to release "
-				    "default address: %s", hub,
-				    port->port_number, str_error(ret));
-		}
-	} else {
-		usb_log_debug("(%p-%u): Device enumerated", hub,
-		    port->port_number);
-		port->device_attached = true;
-		if (usb_release_default_address(exch) != EOK)
-			usb_log_warning("(%p-%u): Failed to release default "
-			    "address", hub, port->port_number);
-	}
-out:
-	usb_device_bus_exchange_end(exch);
-
-	fibril_mutex_lock(&hub->pending_ops_mutex);
-	assert(hub->pending_ops_count > 0);
-	--hub->pending_ops_count;
-	fibril_condvar_signal(&hub->pending_ops_cv);
-	fibril_mutex_unlock(&hub->pending_ops_mutex);
-
-	return ret;
-}
-
-/** Start device adding when connection change is detected.
- *
- * This fires a new fibril to complete the device addition.
- *
- * @param hub Hub where the change occured.
- * @param port Port index (starting at 1).
- * @param speed Speed of the device.
- * @return Error code.
- */
-static errno_t create_add_device_fibril(usb_hub_port_t *port, usb_hub_dev_t *hub,
-    usb_speed_t speed)
-{
-	assert(hub);
-	assert(port);
-	struct add_device_phase1 *data
-	    = malloc(sizeof(struct add_device_phase1));
-	if (data == NULL) {
-		return ENOMEM;
-	}
-	data->hub = hub;
-	data->port = port;
-	data->speed = speed;
-
-	fid_t fibril = fibril_create(add_device_phase1_worker_fibril, data);
-	if (fibril == 0) {
-		free(data);
-		return ENOMEM;
-	}
-	fibril_mutex_lock(&hub->pending_ops_mutex);
-	++hub->pending_ops_count;
-	fibril_mutex_unlock(&hub->pending_ops_mutex);
-	fibril_add_ready(fibril);
-
-	return EOK;
-}
+		check_port_change(port, &status, &port_changed_reset,
+		    USB3_HUB_PORT_STATUS_C_BH_RESET, USB3_HUB_FEATURE_C_BH_PORT_RESET);
+
+		check_port_change(port, &status, NULL,
+		    USB3_HUB_PORT_STATUS_C_LINK_STATE, USB3_HUB_FEATURE_C_PORT_LINK_STATE);
+	}
+
+	/* Check for changes we ignored */
+	if (status & 0xffff0000) {
+		port_log(debug, port, "Port status change igored. Status: %#08" PRIx32, status);
+	}
+}
+
 
 /**
Index: uspace/drv/bus/usb/usbhub/port.h
===================================================================
--- uspace/drv/bus/usb/usbhub/port.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/usbhub/port.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -2,4 +2,5 @@
  * Copyright (c) 2011 Vojtech Horky
  * Copyright (c) 2011 Jan Vesely
+ * Copyright (c) 2017 Ondra Hlavaty
  * All rights reserved.
  *
@@ -32,5 +33,5 @@
  */
 /** @file
- * Hub ports related functions.
+ * Hub port handling.
  */
 
@@ -40,5 +41,5 @@
 #include <usb/dev/driver.h>
 #include <usb/classes/hub.h>
-#include <usb_iface.h>
+#include <usb/port.h>
 
 typedef struct usb_hub_dev usb_hub_dev_t;
@@ -46,48 +47,16 @@
 /** Information about single port on a hub. */
 typedef struct {
+	usb_port_t base;
+	/* Parenting hub */
+	usb_hub_dev_t *hub;
 	/** Port number as reported in descriptors. */
 	unsigned int port_number;
-	/** Device communication pipe. */
-	usb_pipe_t *control_pipe;
-	/** Mutex needed not only by CV for checking port reset. */
-	fibril_mutex_t mutex;
-	/** CV for waiting to port reset completion. */
-	fibril_condvar_t reset_cv;
-	/** Port reset status.
-	 * Guarded by @c reset_mutex.
-	 */
-	enum {
-		NO_RESET,
-		IN_RESET,
-		RESET_OK,
-		RESET_FAIL,
-	} reset_status;
-	/** Device reported to USB bus driver */
-	bool device_attached;
+	/** Speed at the time of enabling the port */
+	usb_speed_t speed;
 } usb_hub_port_t;
 
-/** Initialize hub port information.
- *
- * @param port Port to be initialized.
- */
-static inline void usb_hub_port_init(usb_hub_port_t *port,
-    unsigned int port_number, usb_pipe_t *control_pipe)
-{
-	assert(port);
-	port->port_number = port_number;
-	port->control_pipe = control_pipe;
-	port->reset_status = NO_RESET;
-	port->device_attached = false;
-	fibril_mutex_initialize(&port->mutex);
-	fibril_condvar_initialize(&port->reset_cv);
-}
+void usb_hub_port_init(usb_hub_port_t *, usb_hub_dev_t *, unsigned int);
 
-errno_t usb_hub_port_fini(usb_hub_port_t *port, usb_hub_dev_t *hub);
-errno_t usb_hub_port_clear_feature(
-    usb_hub_port_t *port, usb_hub_class_feature_t feature);
-errno_t usb_hub_port_set_feature(
-    usb_hub_port_t *port, usb_hub_class_feature_t feature);
-void usb_hub_port_reset_fail(usb_hub_port_t *port);
-void usb_hub_port_process_interrupt(usb_hub_port_t *port, usb_hub_dev_t *hub);
+void usb_hub_port_process_interrupt(usb_hub_port_t *port);
 
 #endif
Index: uspace/drv/bus/usb/usbhub/status.h
===================================================================
--- uspace/drv/bus/usb/usbhub/status.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/usbhub/status.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -38,46 +38,4 @@
 
 /**
- * structure holding port status and changes flags.
- * should not be accessed directly, use supplied getter/setter methods.
- *
- * For more information refer to tables 11-15 and 11-16 in
- * "Universal Serial Bus Specification Revision 1.1" pages 274 and 277
- * (290 and 293 in pdf)
- *
- */
-typedef uint32_t usb_port_status_t;
-#define USB_HUB_PORT_STATUS_CONNECTION \
-    (uint32_usb2host(1 << (USB_HUB_FEATURE_PORT_CONNECTION)))
-#define USB_HUB_PORT_STATUS_ENABLED \
-    (uint32_usb2host(1 << (USB_HUB_FEATURE_PORT_ENABLE)))
-#define USB_HUB_PORT_STATUS_SUSPEND \
-    (uint32_usb2host(1 << (USB_HUB_FEATURE_PORT_SUSPEND)))
-#define USB_HUB_PORT_STATUS_OC \
-    (uint32_usb2host(1 << (USB_HUB_FEATURE_PORT_OVER_CURRENT)))
-#define USB_HUB_PORT_STATUS_RESET \
-    (uint32_usb2host(1 << (USB_HUB_FEATURE_PORT_RESET)))
-#define USB_HUB_PORT_STATUS_POWER \
-    (uint32_usb2host(1 << (USB_HUB_FEATURE_PORT_POWER)))
-#define USB_HUB_PORT_STATUS_LOW_SPEED \
-    (uint32_usb2host(1 << (USB_HUB_FEATURE_PORT_LOW_SPEED)))
-#define USB_HUB_PORT_STATUS_HIGH_SPEED \
-    (uint32_usb2host(1 << 10))
-#define USB_HUB_PORT_STATUS_TEST_MODE \
-    (uint32_usb2host(1 << 11))
-#define USB_HUB_PORT_INDICATOR_CONTROL \
-    (uint32_usb2host(1 << 12))
-
-#define USB_HUB_PORT_C_STATUS_CONNECTION \
-    (uint32_usb2host(1 << (USB_HUB_FEATURE_C_PORT_CONNECTION)))
-#define USB_HUB_PORT_C_STATUS_ENABLED \
-    (uint32_usb2host(1 << (USB_HUB_FEATURE_C_PORT_ENABLE)))
-#define USB_HUB_PORT_C_STATUS_SUSPEND \
-    (uint32_usb2host(1 << (USB_HUB_FEATURE_C_PORT_SUSPEND)))
-#define USB_HUB_PORT_C_STATUS_OC \
-    (uint32_usb2host(1 << (USB_HUB_FEATURE_C_PORT_OVER_CURRENT)))
-#define USB_HUB_PORT_C_STATUS_RESET \
-    (uint32_usb2host(1 << (USB_HUB_FEATURE_C_PORT_RESET)))
-
-/**
  * structure holding hub status and changes flags.
  *
@@ -97,17 +55,12 @@
     (uint32_usb2host(1 << (16 + USB_HUB_FEATURE_C_HUB_LOCAL_POWER)))
 
-
-/**
- * speed getter for port status
- *
- * @param status
- * @return speed of usb device (for more see usb specification)
- */
-static inline usb_speed_t usb_port_speed(usb_port_status_t status)
+static inline usb_speed_t usb_port_speed(usb_speed_t hub_speed, uint32_t status)
 {
-	if ((status & USB_HUB_PORT_STATUS_LOW_SPEED) != 0)
+	if (hub_speed == USB_SPEED_SUPER)
+		return USB_SPEED_SUPER;
+	if (hub_speed == USB_SPEED_HIGH && (status & USB2_HUB_PORT_STATUS_HIGH_SPEED))
+		return USB_SPEED_HIGH;
+	if ((status & USB2_HUB_PORT_STATUS_LOW_SPEED) != 0)
 		return USB_SPEED_LOW;
-	if ((status & USB_HUB_PORT_STATUS_HIGH_SPEED) != 0)
-		return USB_SPEED_HIGH;
 	return USB_SPEED_FULL;
 }
Index: uspace/drv/bus/usb/usbhub/usbhub.c
===================================================================
--- uspace/drv/bus/usb/usbhub/usbhub.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/usbhub/usbhub.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -51,5 +51,5 @@
 #include <usb/classes/hub.h>
 #include <usb/dev/poll.h>
-#include <usb_iface.h>
+#include <usbhc_iface.h>
 
 #include "usbhub.h"
@@ -58,16 +58,34 @@
 #define HUB_FNC_NAME "hub"
 
-/** Hub status-change endpoint description.
- *
- * For more information see section 11.15.1 of USB 1.1 specification.
- */
-const usb_endpoint_description_t hub_status_change_endpoint_description =
-{
-	.transfer_type = USB_TRANSFER_INTERRUPT,
-	.direction = USB_DIRECTION_IN,
-	.interface_class = USB_CLASS_HUB,
-	.interface_subclass = 0,
-	.interface_protocol = 0,
-	.flags = 0
+#define HUB_STATUS_CHANGE_EP(protocol) { \
+	.transfer_type = USB_TRANSFER_INTERRUPT, \
+	.direction = USB_DIRECTION_IN, \
+	.interface_class = USB_CLASS_HUB, \
+	.interface_subclass = 0, \
+	.interface_protocol = (protocol), \
+	.flags = 0 \
+}
+
+/**
+ * Hub status-change endpoint description.
+ *
+ * According to USB 2.0 specification, there are two possible arrangements of
+ * endpoints, depending on whether the hub has a MTT or not.
+ *
+ * Under any circumstances, there shall be exactly one endpoint descriptor.
+ * Though to be sure, let's map the protocol precisely. The possible
+ * combinations are:
+ *	                      | bDeviceProtocol | bInterfaceProtocol
+ *	Only single TT        |       0         |         0
+ *	MTT in Single-TT mode |       2         |         1
+ *	MTT in MTT mode       |       2         |         2     (iface alt. 1)
+ */
+static const usb_endpoint_description_t
+	status_change_single_tt_only = HUB_STATUS_CHANGE_EP(0),
+	status_change_mtt_available = HUB_STATUS_CHANGE_EP(1);
+
+const usb_endpoint_description_t *usb_hub_endpoints [] = {
+	&status_change_single_tt_only,
+	&status_change_mtt_available,
 };
 
@@ -81,11 +99,21 @@
 };
 
-static errno_t usb_set_first_configuration(usb_device_t *usb_device);
-static errno_t usb_hub_process_hub_specific_info(usb_hub_dev_t *hub_dev);
-static void usb_hub_over_current(const usb_hub_dev_t *hub_dev,
-    usb_hub_status_t status);
-static void usb_hub_global_interrupt(const usb_hub_dev_t *hub_dev);
-static void usb_hub_polling_terminated_callback(usb_device_t *device,
-    bool was_error, void *data);
+static errno_t usb_set_first_configuration(usb_device_t *);
+static errno_t usb_hub_process_hub_specific_info(usb_hub_dev_t *);
+static void usb_hub_over_current(const usb_hub_dev_t *, usb_hub_status_t);
+static errno_t usb_hub_polling_init(usb_hub_dev_t *, usb_endpoint_mapping_t *);
+static void usb_hub_global_interrupt(const usb_hub_dev_t *);
+
+static bool usb_hub_polling_error_callback(usb_device_t *dev,
+	errno_t err_code, void *arg)
+{
+	assert(dev);
+	assert(arg);
+
+	usb_log_error("Device %s polling error: %s",
+		usb_device_get_name(dev), str_error(err_code));
+
+	return true;
+}
 
 /**
@@ -99,114 +127,86 @@
 errno_t usb_hub_device_add(usb_device_t *usb_dev)
 {
+	int err;
 	assert(usb_dev);
+
 	/* Create driver soft-state structure */
 	usb_hub_dev_t *hub_dev =
 	    usb_device_data_alloc(usb_dev, sizeof(usb_hub_dev_t));
 	if (hub_dev == NULL) {
-		usb_log_error("Failed to create hub driver structure.\n");
+		usb_log_error("Failed to create hub driver structure.");
 		return ENOMEM;
 	}
 	hub_dev->usb_device = usb_dev;
-	hub_dev->pending_ops_count = 0;
-	hub_dev->running = false;
-	fibril_mutex_initialize(&hub_dev->pending_ops_mutex);
-	fibril_condvar_initialize(&hub_dev->pending_ops_cv);
+	hub_dev->speed = usb_device_get_speed(usb_dev);
 
 	/* Set hub's first configuration. (There should be only one) */
-	errno_t opResult = usb_set_first_configuration(usb_dev);
-	if (opResult != EOK) {
-		usb_log_error("Could not set hub configuration: %s\n",
-		    str_error(opResult));
-		return opResult;
+	if ((err = usb_set_first_configuration(usb_dev))) {
+		usb_log_error("Could not set hub configuration: %s", str_error(err));
+		return err;
 	}
 
 	/* Get port count and create attached_devices. */
-	opResult = usb_hub_process_hub_specific_info(hub_dev);
-	if (opResult != EOK) {
-		usb_log_error("Could process hub specific info, %s\n",
-		    str_error(opResult));
-		return opResult;
+	if ((err = usb_hub_process_hub_specific_info(hub_dev))) {
+		usb_log_error("Could process hub specific info, %s", str_error(err));
+		return err;
+	}
+
+	const usb_endpoint_description_t *status_change = hub_dev->mtt_available
+	    ? &status_change_mtt_available
+	    : &status_change_single_tt_only;
+
+	usb_endpoint_mapping_t *status_change_mapping
+		= usb_device_get_mapped_ep_desc(hub_dev->usb_device, status_change);
+	if (!status_change_mapping) {
+		usb_log_error("Failed to map the Status Change Endpoint of a hub.");
+		return EIO;
 	}
 
 	/* Create hub control function. */
-	usb_log_debug("Creating DDF function '" HUB_FNC_NAME "'.\n");
+	usb_log_debug("Creating DDF function '" HUB_FNC_NAME "'.");
 	hub_dev->hub_fun = usb_device_ddf_fun_create(hub_dev->usb_device,
 	    fun_exposed, HUB_FNC_NAME);
 	if (hub_dev->hub_fun == NULL) {
-		usb_log_error("Failed to create hub function.\n");
+		usb_log_error("Failed to create hub function.");
 		return ENOMEM;
 	}
 
 	/* Bind hub control function. */
-	opResult = ddf_fun_bind(hub_dev->hub_fun);
-	if (opResult != EOK) {
-		usb_log_error("Failed to bind hub function: %s.\n",
-		   str_error(opResult));
-		ddf_fun_destroy(hub_dev->hub_fun);
-		return opResult;
+	if ((err = ddf_fun_bind(hub_dev->hub_fun))) {
+		usb_log_error("Failed to bind hub function: %s.", str_error(err));
+		goto err_ddf_fun;
 	}
 
 	/* Start hub operation. */
-	opResult = usb_device_auto_poll_desc(hub_dev->usb_device,
-	    &hub_status_change_endpoint_description,
-	    hub_port_changes_callback, ((hub_dev->port_count + 1 + 7) / 8),
-	    -1, usb_hub_polling_terminated_callback, hub_dev);
-	if (opResult != EOK) {
-		/* Function is already bound */
-		ddf_fun_unbind(hub_dev->hub_fun);
-		ddf_fun_destroy(hub_dev->hub_fun);
-		usb_log_error("Failed to create polling fibril: %s.\n",
-		    str_error(opResult));
-		return opResult;
-	}
-	hub_dev->running = true;
-	usb_log_info("Controlling hub '%s' (%p: %zu ports).\n",
+	if ((err = usb_hub_polling_init(hub_dev, status_change_mapping))) {
+		usb_log_error("Failed to start polling: %s.", str_error(err));
+		goto err_bound;
+	}
+
+	usb_log_info("Controlling %s-speed hub '%s' (%p: %zu ports).",
+	    usb_str_speed(hub_dev->speed),
 	    usb_device_get_name(hub_dev->usb_device), hub_dev,
 	    hub_dev->port_count);
 
 	return EOK;
-}
-
-/**
- * Turn off power to all ports.
- *
- * @param usb_dev generic usb device information
- * @return error code
- */
-errno_t usb_hub_device_remove(usb_device_t *usb_dev)
-{
-	return ENOTSUP;
-}
-
-/**
- * Remove all attached devices
- * @param usb_dev generic usb device information
- * @return error code
- */
-errno_t usb_hub_device_gone(usb_device_t *usb_dev)
-{
-	assert(usb_dev);
-	usb_hub_dev_t *hub = usb_device_data_get(usb_dev);
-	assert(hub);
-	unsigned tries = 10;
-	while (hub->running) {
-		async_usleep(100000);
-		if (!tries--) {
-			usb_log_error("(%p): Can't remove hub, still running.",
-			    hub);
-			return EBUSY;
-		}
-	}
-
-	assert(!hub->running);
+
+err_bound:
+	ddf_fun_unbind(hub_dev->hub_fun);
+err_ddf_fun:
+	ddf_fun_destroy(hub_dev->hub_fun);
+	return err;
+}
+
+static errno_t usb_hub_cleanup(usb_hub_dev_t *hub)
+{
+	free(hub->polling.buffer);
+	usb_polling_fini(&hub->polling);
 
 	for (size_t port = 0; port < hub->port_count; ++port) {
-		const errno_t ret = usb_hub_port_fini(&hub->ports[port], hub);
-		if (ret != EOK)
-			return ret;
+		usb_port_fini(&hub->ports[port].base);
 	}
 	free(hub->ports);
 
-	const errno_t ret = ddf_fun_unbind(hub->hub_fun);
+	const int ret = ddf_fun_unbind(hub->hub_fun);
 	if (ret != EOK) {
 		usb_log_error("(%p) Failed to unbind '%s' function: %s.",
@@ -217,8 +217,86 @@
 
 	usb_log_info("(%p) USB hub driver stopped and cleaned.", hub);
+
+	/* Device data (usb_hub_dev_t) will be freed by usbdev. */
 	return EOK;
 }
 
-/** Callback for polling hub for changes.
+/**
+ * Turn off power to all ports.
+ *
+ * @param usb_dev generic usb device information
+ * @return error code
+ */
+errno_t usb_hub_device_remove(usb_device_t *usb_dev)
+{
+	assert(usb_dev);
+	usb_hub_dev_t *hub = usb_device_data_get(usb_dev);
+	assert(hub);
+
+	usb_log_info("(%p) USB hub removed, joining polling fibril.", hub);
+
+	/* Join polling fibril (ignoring error code). */
+	usb_polling_join(&hub->polling);
+	usb_log_info("(%p) USB hub polling stopped, freeing memory.", hub);
+
+	/* Destroy hub. */
+	return usb_hub_cleanup(hub);
+}
+
+/**
+ * Remove all attached devices
+ * @param usb_dev generic usb device information
+ * @return error code
+ */
+errno_t usb_hub_device_gone(usb_device_t *usb_dev)
+{
+	assert(usb_dev);
+	usb_hub_dev_t *hub = usb_device_data_get(usb_dev);
+	assert(hub);
+
+	usb_log_info("(%p) USB hub gone, joining polling fibril.", hub);
+
+	/* Join polling fibril (ignoring error code). */
+	usb_polling_join(&hub->polling);
+	usb_log_info("(%p) USB hub polling stopped, freeing memory.", hub);
+
+	/* Destroy hub. */
+	return usb_hub_cleanup(hub);
+}
+
+/**
+ * Initialize and start the polling of the Status Change Endpoint.
+ *
+ * @param mapping The mapping of Status Change Endpoint
+ */
+static errno_t usb_hub_polling_init(usb_hub_dev_t *hub_dev,
+	usb_endpoint_mapping_t *mapping)
+{
+	errno_t err;
+	usb_polling_t *polling = &hub_dev->polling;
+
+	if ((err = usb_polling_init(polling)))
+		return err;
+
+	polling->device = hub_dev->usb_device;
+	polling->ep_mapping = mapping;
+	polling->request_size = ((hub_dev->port_count + 1 + 7) / 8);
+	polling->buffer = malloc(polling->request_size);
+	polling->on_data = hub_port_changes_callback;
+	polling->on_error = usb_hub_polling_error_callback;
+	polling->arg = hub_dev;
+
+	if ((err = usb_polling_start(polling))) {
+		/* Polling is already initialized. */
+		free(polling->buffer);
+		usb_polling_fini(polling);
+		return err;
+	}
+
+	return EOK;
+}
+
+/**
+ * Callback for polling hub for changes.
  *
  * @param dev Device where the change occured.
@@ -245,13 +323,38 @@
 	}
 
-	/* N + 1 bit indicates change on port N */
+	/* Nth bit indicates change on port N */
 	for (size_t port = 0; port < hub->port_count; ++port) {
 		const size_t bit = port + 1;
 		const bool change = (change_bitmap[bit / 8] >> (bit % 8)) & 1;
 		if (change) {
-			usb_hub_port_process_interrupt(&hub->ports[port], hub);
+			usb_hub_port_process_interrupt(&hub->ports[port]);
 		}
 	}
 	return true;
+}
+
+static void usb_hub_power_ports(usb_hub_dev_t *hub_dev)
+{
+	if (!hub_dev->power_switched) {
+		usb_log_info("(%p): Power switching not supported, "
+		    "ports always powered.", hub_dev);
+		return;
+	}
+
+	usb_log_info("(%p): Hub port power switching enabled (%s).", hub_dev,
+	    hub_dev->per_port_power ? "per port" : "ganged");
+
+	for (unsigned int port = 0; port < hub_dev->port_count; ++port) {
+		usb_log_debug("(%p): Powering port %u.", hub_dev, port + 1);
+		const int ret = usb_hub_set_port_feature(hub_dev, port + 1,
+		    USB_HUB_FEATURE_PORT_POWER);
+
+		if (ret != EOK) {
+			usb_log_error("(%p-%u): Cannot power on port: %s.",
+			    hub_dev, hub_dev->ports[port].port_number,
+			    str_error(ret));
+			/* Continue to try at least other ports */
+		}
+	}
 }
 
@@ -270,24 +373,35 @@
 	assert(hub_dev);
 
+	usb_log_debug("(%p): Retrieving descriptor.", hub_dev);
+	usb_pipe_t *control_pipe = usb_device_get_default_pipe(hub_dev->usb_device);
+
+	usb_descriptor_type_t desc_type = hub_dev->speed >= USB_SPEED_SUPER
+		? USB_DESCTYPE_SSPEED_HUB : USB_DESCTYPE_HUB;
+
 	/* Get hub descriptor. */
-	usb_log_debug("(%p): Retrieving descriptor.", hub_dev);
-	usb_pipe_t *control_pipe =
-	    usb_device_get_default_pipe(hub_dev->usb_device);
-
 	usb_hub_descriptor_header_t descriptor;
 	size_t received_size;
 	errno_t opResult = usb_request_get_descriptor(control_pipe,
 	    USB_REQUEST_TYPE_CLASS, USB_REQUEST_RECIPIENT_DEVICE,
-	    USB_DESCTYPE_HUB, 0, 0, &descriptor,
+	    desc_type, 0, 0, &descriptor,
 	    sizeof(usb_hub_descriptor_header_t), &received_size);
 	if (opResult != EOK) {
-		usb_log_error("(%p): Failed to receive hub descriptor: %s.\n",
+		usb_log_error("(%p): Failed to receive hub descriptor: %s.",
 		    hub_dev, str_error(opResult));
 		return opResult;
 	}
 
-	usb_log_debug("(%p): Setting port count to %d.\n", hub_dev,
+	usb_log_debug("(%p): Setting port count to %d.", hub_dev,
 	    descriptor.port_count);
 	hub_dev->port_count = descriptor.port_count;
+	hub_dev->control_pipe = control_pipe;
+
+	usb_log_debug("(%p): Setting hub depth to %u.", hub_dev,
+	    usb_device_get_depth(hub_dev->usb_device));
+	if ((opResult = usb_hub_set_depth(hub_dev))) {
+		usb_log_error("(%p): Failed to set hub depth: %s.",
+		    hub_dev, str_error(opResult));
+		return opResult;
+	}
 
 	hub_dev->ports = calloc(hub_dev->port_count, sizeof(usb_hub_port_t));
@@ -297,6 +411,5 @@
 
 	for (size_t port = 0; port < hub_dev->port_count; ++port) {
-		usb_hub_port_init(
-		    &hub_dev->ports[port], port + 1, control_pipe);
+		usb_hub_port_init(&hub_dev->ports[port], hub_dev, port + 1);
 	}
 
@@ -306,30 +419,10 @@
 	    descriptor.characteristics & HUB_CHAR_POWER_PER_PORT_FLAG;
 
-	if (!hub_dev->power_switched) {
-		usb_log_info("(%p): Power switching not supported, "
-		    "ports always powered.", hub_dev);
-		return EOK;
-	}
-
-	usb_log_info("(%p): Hub port power switching enabled (%s).\n", hub_dev,
-	    hub_dev->per_port_power ? "per port" : "ganged");
-
-	for (unsigned int port = 0; port < hub_dev->port_count; ++port) {
-		usb_log_debug("(%p): Powering port %u.", hub_dev, port);
-		const errno_t ret = usb_hub_port_set_feature(
-		    &hub_dev->ports[port], USB_HUB_FEATURE_PORT_POWER);
-
-		if (ret != EOK) {
-			usb_log_error("(%p-%u): Cannot power on port: %s.\n",
-			    hub_dev, hub_dev->ports[port].port_number,
-			    str_error(ret));
-		} else {
-			if (!hub_dev->per_port_power) {
-				usb_log_debug("(%p) Ganged power switching, "
-				    "one port is enough.", hub_dev);
-				break;
-			}
-		}
-	}
+	const uint8_t protocol = usb_device_descriptors(hub_dev->usb_device)
+		->device.device_protocol;
+	hub_dev->mtt_available = (protocol == 2);
+
+	usb_hub_power_ports(hub_dev);
+
 	return EOK;
 }
@@ -349,8 +442,8 @@
 	const size_t configuration_count =
 	    usb_device_descriptors(usb_device)->device.configuration_count;
-	usb_log_debug("Hub has %zu configurations.\n", configuration_count);
+	usb_log_debug("Hub has %zu configurations.", configuration_count);
 
 	if (configuration_count < 1) {
-		usb_log_error("There are no configurations available\n");
+		usb_log_error("There are no configurations available");
 		return EINVAL;
 	}
@@ -369,14 +462,15 @@
 	/* Set configuration. Use the configuration that was in
 	 * usb_device->descriptors.configuration i.e. The first one. */
-	const errno_t opResult = usb_request_set_configuration(
+	errno_t opResult = usb_request_set_configuration(
 	    usb_device_get_default_pipe(usb_device),
 	    config_descriptor->configuration_number);
 	if (opResult != EOK) {
-		usb_log_error("Failed to set hub configuration: %s.\n",
+		usb_log_error("Failed to set hub configuration: %s.",
 		    str_error(opResult));
 	} else {
-		usb_log_debug("\tUsed configuration %d\n",
+		usb_log_debug("\tUsed configuration %d",
 		    config_descriptor->configuration_number);
 	}
+
 	return opResult;
 }
@@ -406,6 +500,6 @@
 	/* Over-current condition is gone, it is safe to turn the ports on. */
 	for (size_t port = 0; port < hub_dev->port_count; ++port) {
-		const errno_t ret = usb_hub_port_set_feature(
-		    &hub_dev->ports[port], USB_HUB_FEATURE_PORT_POWER);
+		const errno_t ret = usb_hub_set_port_feature(hub_dev, port,
+		    USB_HUB_FEATURE_PORT_POWER);
 		if (ret != EOK) {
 			usb_log_warning("(%p-%u): HUB OVER-CURRENT GONE: Cannot"
@@ -417,5 +511,110 @@
 		}
 	}
-
+}
+
+/**
+ * Set feature on the real hub port.
+ *
+ * @param port Port structure.
+ * @param feature Feature selector.
+ */
+int usb_hub_set_depth(const usb_hub_dev_t *hub)
+{
+	assert(hub);
+
+	/* Slower hubs do not care about depth */
+	if (hub->speed < USB_SPEED_SUPER)
+		return EOK;
+
+	const usb_device_request_setup_packet_t set_request = {
+		.request_type = USB_HUB_REQ_TYPE_SET_HUB_DEPTH,
+		.request = USB_HUB_REQUEST_SET_HUB_DEPTH,
+		.value = uint16_host2usb(usb_device_get_depth(hub->usb_device) - 1),
+		.index = 0,
+		.length = 0,
+	};
+	return usb_pipe_control_write(hub->control_pipe, &set_request,
+	    sizeof(set_request), NULL, 0);
+}
+
+/**
+ * Set feature on the real hub port.
+ *
+ * @param port Port structure.
+ * @param feature Feature selector.
+ */
+int usb_hub_set_port_feature(const usb_hub_dev_t *hub, size_t port_number,
+    usb_hub_class_feature_t feature)
+{
+	assert(hub);
+	const usb_device_request_setup_packet_t clear_request = {
+		.request_type = USB_HUB_REQ_TYPE_SET_PORT_FEATURE,
+		.request = USB_DEVREQ_SET_FEATURE,
+		.index = uint16_host2usb(port_number),
+		.value = feature,
+		.length = 0,
+	};
+	return usb_pipe_control_write(hub->control_pipe, &clear_request,
+	    sizeof(clear_request), NULL, 0);
+}
+
+/**
+ * Clear feature on the real hub port.
+ *
+ * @param port Port structure.
+ * @param feature Feature selector.
+ */
+int usb_hub_clear_port_feature(const usb_hub_dev_t *hub, size_t port_number,
+    usb_hub_class_feature_t feature)
+{
+	assert(hub);
+	const usb_device_request_setup_packet_t clear_request = {
+		.request_type = USB_HUB_REQ_TYPE_CLEAR_PORT_FEATURE,
+		.request = USB_DEVREQ_CLEAR_FEATURE,
+		.value = feature,
+		.index = uint16_host2usb(port_number),
+		.length = 0,
+	};
+	return usb_pipe_control_write(hub->control_pipe,
+	    &clear_request, sizeof(clear_request), NULL, 0);
+}
+
+/**
+ * Retrieve port status.
+ *
+ * @param[in] port Port structure
+ * @param[out] status Where to store the port status.
+ * @return Error code.
+ */
+int usb_hub_get_port_status(const usb_hub_dev_t *hub, size_t port_number,
+    usb_port_status_t *status)
+{
+	assert(hub);
+	assert(status);
+
+	/* USB hub specific GET_PORT_STATUS request. See USB Spec 11.16.2.6
+	 * Generic GET_STATUS request cannot be used because of the difference
+	 * in status data size (2B vs. 4B)*/
+	const usb_device_request_setup_packet_t request = {
+		.request_type = USB_HUB_REQ_TYPE_GET_PORT_STATUS,
+		.request = USB_HUB_REQUEST_GET_STATUS,
+		.value = 0,
+		.index = uint16_host2usb(port_number),
+		.length = sizeof(usb_port_status_t),
+	};
+	size_t recv_size;
+
+	uint32_t buffer;
+	const int rc = usb_pipe_control_read(hub->control_pipe,
+	    &request, sizeof(usb_device_request_setup_packet_t),
+	    &buffer, sizeof(buffer), &recv_size);
+	if (rc != EOK)
+		return rc;
+
+	if (recv_size != sizeof(*status))
+		return ELIMIT;
+
+	*status = uint32_usb2host(buffer);
+	return EOK;
 }
 
@@ -492,40 +691,78 @@
 
 /**
- * callback called from hub polling fibril when the fibril terminates
- *
- * Does not perform cleanup, just marks the hub as not running.
- * @param device usb device afected
- * @param was_error indicates that the fibril is stoped due to an error
- * @param data pointer to usb_hub_dev_t structure
- */
-static void usb_hub_polling_terminated_callback(usb_device_t *device,
-    bool was_error, void *data)
-{
-	usb_hub_dev_t *hub = data;
+ * Instead of just sleeping, we may as well sleep on a condition variable.
+ * This has the advantage that we may instantly wait other hub from the polling
+ * sleep, mitigating the delay of polling while still being synchronized with
+ * other devices in need of the default address (there shall not be any).
+ */
+static FIBRIL_CONDVAR_INITIALIZE(global_hub_default_address_cv);
+static FIBRIL_MUTEX_INITIALIZE(global_hub_default_address_guard);
+
+/**
+ * Reserve a default address for a port across all other devices connected to
+ * the bus. We aggregate requests for ports to minimize delays between
+ * connecting multiple devices from one hub - which happens e.g. when the hub
+ * is connected with already attached devices.
+ */
+int usb_hub_reserve_default_address(usb_hub_dev_t *hub, async_exch_t *exch,
+    usb_port_t *port)
+{
 	assert(hub);
-
-	fibril_mutex_lock(&hub->pending_ops_mutex);
-
-	/* The device is dead. However there might be some pending operations
-	 * that we need to wait for.
-	 * One of them is device adding in progress.
-	 * The respective fibril is probably waiting for status change
-	 * in port reset (port enable) callback.
-	 * Such change would never come (otherwise we would not be here).
-	 * Thus, we would flush all pending port resets.
+	assert(exch);
+	assert(port);
+	assert(fibril_mutex_is_locked(&port->guard));
+
+	int err = usbhc_reserve_default_address(exch);
+	/*
+	 * EINVAL signalls that its our hub (hopefully different port) that has
+	 * this address reserved
 	 */
-	if (hub->pending_ops_count > 0) {
-		for (size_t port = 0; port < hub->port_count; ++port) {
-			usb_hub_port_reset_fail(&hub->ports[port]);
-		}
-	}
-	/* And now wait for them. */
-	while (hub->pending_ops_count > 0) {
-		fibril_condvar_wait(&hub->pending_ops_cv,
-		    &hub->pending_ops_mutex);
-	}
-	fibril_mutex_unlock(&hub->pending_ops_mutex);
-	hub->running = false;
-}
+	while (err == EAGAIN || err == EINVAL) {
+		/* Drop the port guard, we're going to wait */
+		fibril_mutex_unlock(&port->guard);
+
+		/* This sleeping might be disturbed by other hub */
+		fibril_mutex_lock(&global_hub_default_address_guard);
+		fibril_condvar_wait_timeout(&global_hub_default_address_cv,
+		    &global_hub_default_address_guard, 2000000);
+		fibril_mutex_unlock(&global_hub_default_address_guard);
+
+		fibril_mutex_lock(&port->guard);
+		err = usbhc_reserve_default_address(exch);
+	}
+
+	if (err)
+		return err;
+
+	/*
+	 * As we dropped the port guard, we need to check whether the device is
+	 * still connected. If the release fails, we still hold the default
+	 * address -- but then there is probably a bigger problem with the HC
+	 * anyway.
+	 */
+	if (port->state != PORT_CONNECTING) {
+		err = usb_hub_release_default_address(hub, exch);
+		return err ? err : EINTR;
+	}
+
+	return EOK;
+}
+
+/**
+ * Release the default address from a port.
+ */
+int usb_hub_release_default_address(usb_hub_dev_t *hub, async_exch_t *exch)
+{
+	const int ret = usbhc_release_default_address(exch);
+
+	/*
+	 * This is an optimistic optimization - it may wake
+	 * one hub from polling sleep instantly.
+	 */
+	fibril_condvar_signal(&global_hub_default_address_cv);
+
+	return ret;
+}
+
 /**
  * @}
Index: uspace/drv/bus/usb/usbhub/usbhub.h
===================================================================
--- uspace/drv/bus/usb/usbhub/usbhub.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/usbhub/usbhub.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -39,4 +39,5 @@
 
 #include <ddf/driver.h>
+#include <fibril_synch.h>
 
 #include <usb/classes/hub.h>
@@ -44,10 +45,10 @@
 #include <usb/dev/pipes.h>
 #include <usb/dev/driver.h>
-
-#include <fibril_synch.h>
+#include <usb/dev/poll.h>
 
 #define NAME "usbhub"
 
 #include "port.h"
+#include "status.h"
 
 /** Information about attached hub. */
@@ -57,36 +58,37 @@
 	/** Port structures, one for each port */
 	usb_hub_port_t *ports;
+	/** Speed of the hub */
+	usb_speed_t speed;
 	/** Generic usb device data*/
 	usb_device_t *usb_device;
-
-	/** Number of pending operations on the mutex to prevent shooting
-	 * ourselves in the foot.
-	 * When the hub is disconnected but we are in the middle of some
-	 * operation, we cannot destroy this structure right away because
-	 * the pending operation might use it.
-	 */
-	size_t pending_ops_count;
-	/** Guard for pending_ops_count. */
-	fibril_mutex_t pending_ops_mutex;
-	/** Condition variable for pending_ops_count. */
-	fibril_condvar_t pending_ops_cv;
+	/** Data polling handle. */
+	usb_polling_t polling;
 	/** Pointer to usbhub function. */
 	ddf_fun_t *hub_fun;
-	/** Status indicator */
-	bool running;
+	/** Device communication pipe. */
+	usb_pipe_t *control_pipe;
 	/** Hub supports port power switching. */
 	bool power_switched;
 	/** Each port is switched individually. */
 	bool per_port_power;
+	/** Whether MTT is available */
+	bool mtt_available;
 };
 
-extern const usb_endpoint_description_t hub_status_change_endpoint_description;
+extern const usb_endpoint_description_t *usb_hub_endpoints [];
 
-extern errno_t usb_hub_device_add(usb_device_t *);
-extern errno_t usb_hub_device_remove(usb_device_t *);
-extern errno_t usb_hub_device_gone(usb_device_t *);
+errno_t usb_hub_device_add(usb_device_t *);
+errno_t usb_hub_device_remove(usb_device_t *);
+errno_t usb_hub_device_gone(usb_device_t *);
 
-extern bool hub_port_changes_callback(usb_device_t *, uint8_t *, size_t,
-    void *);
+errno_t usb_hub_set_depth(const usb_hub_dev_t *);
+errno_t usb_hub_get_port_status(const usb_hub_dev_t *, size_t, usb_port_status_t *);
+errno_t usb_hub_set_port_feature(const usb_hub_dev_t *, size_t, usb_hub_class_feature_t);
+errno_t usb_hub_clear_port_feature(const usb_hub_dev_t *, size_t, usb_hub_class_feature_t);
+
+bool hub_port_changes_callback(usb_device_t *, uint8_t *, size_t, void *);
+
+errno_t usb_hub_reserve_default_address(usb_hub_dev_t *, async_exch_t *, usb_port_t *);
+errno_t usb_hub_release_default_address(usb_hub_dev_t *, async_exch_t *);
 
 #endif
Index: uspace/drv/bus/usb/usbmid/dump.c
===================================================================
--- uspace/drv/bus/usb/usbmid/dump.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/usbmid/dump.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -56,5 +56,5 @@
 		usb_standard_interface_descriptor_t *descriptor
 		    = (usb_standard_interface_descriptor_t *) data;
-		usb_log_info("Found interface: %s (0x%02x/0x%02x/0x%02x).\n",
+		usb_log_info("Found interface: %s (0x%02x/0x%02x/0x%02x).",
 		    usb_str_class(descriptor->interface_class),
 		    (int) descriptor->interface_class,
Index: uspace/drv/bus/usb/usbmid/explore.c
===================================================================
--- uspace/drv/bus/usb/usbmid/explore.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/usbmid/explore.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -106,5 +106,5 @@
 
 
-		usb_log_info("Creating child for interface %d (%s).\n",
+		usb_log_info("Creating child for interface %d (%s).",
 		    interface->interface_number,
 		    usb_str_class(interface->interface_class));
@@ -144,5 +144,5 @@
 		    dev_class, usb_str_class(dev_class),
 		    USB_CLASS_USE_INTERFACE);
-		usb_log_error("Not a multi-interface device, refusing.\n");
+		usb_log_error("Not a multi-interface device, refusing.");
 		return ENOTSUP;
 	}
@@ -160,5 +160,5 @@
 	    config_descriptor->configuration_number);
 	if (rc != EOK) {
-		usb_log_error("Failed to set device configuration: %s.\n",
+		usb_log_error("Failed to set device configuration: %s.",
 		    str_error(rc));
 		return rc;
@@ -168,5 +168,5 @@
 	usb_mid_t *usb_mid = usb_device_data_alloc(dev, sizeof(usb_mid_t));
 	if (!usb_mid) {
-		usb_log_error("Failed to create USB MID structure.\n");
+		usb_log_error("Failed to create USB MID structure.");
 		return ENOMEM;
 	}
@@ -175,5 +175,5 @@
 	usb_mid->ctl_fun = usb_device_ddf_fun_create(dev, fun_exposed, "ctl");
 	if (usb_mid->ctl_fun == NULL) {
-		usb_log_error("Failed to create control function.\n");
+		usb_log_error("Failed to create control function.");
 		return ENOMEM;
 	}
@@ -182,5 +182,5 @@
 	rc = ddf_fun_bind(usb_mid->ctl_fun);
 	if (rc != EOK) {
-		usb_log_error("Failed to bind control function: %s.\n",
+		usb_log_error("Failed to bind control function: %s.",
 		    str_error(rc));
 		ddf_fun_destroy(usb_mid->ctl_fun);
Index: uspace/drv/bus/usb/usbmid/main.c
===================================================================
--- uspace/drv/bus/usb/usbmid/main.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/usbmid/main.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -51,7 +51,28 @@
 static errno_t usbmid_device_add(usb_device_t *dev)
 {
-	usb_log_info("Taking care of new MID `%s'.\n", usb_device_get_name(dev));
+	usb_log_info("Taking care of new MID `%s'.", usb_device_get_name(dev));
 
 	return usbmid_explore_device(dev);
+}
+
+static int destroy_interfaces(usb_mid_t *usb_mid)
+{
+	int ret = EOK;
+
+	while (!list_empty(&usb_mid->interface_list)) {
+		link_t *item = list_first(&usb_mid->interface_list);
+		list_remove(item);
+
+		usbmid_interface_t *iface = usbmid_interface_from_link(item);
+
+		const int pret = usbmid_interface_destroy(iface);
+		if (pret != EOK) {
+			usb_log_error("Failed to remove child `%s': %s",
+			    ddf_fun_get_name(iface->fun), str_error(pret));
+			ret = pret;
+		}
+	}
+
+	return ret;
 }
 
@@ -70,5 +91,5 @@
 	errno_t ret = ddf_fun_unbind(usb_mid->ctl_fun);
 	if (ret != EOK) {
-		usb_log_error("Failed to unbind USB MID ctl function: %s.\n",
+		usb_log_error("Failed to unbind USB MID ctl function: %s.",
 		    str_error(ret));
 		return ret;
@@ -77,30 +98,17 @@
 
 	/* Remove all children */
-	while (!list_empty(&usb_mid->interface_list)) {
-		link_t *item = list_first(&usb_mid->interface_list);
-		list_remove(item);
-
-		usbmid_interface_t *iface = usbmid_interface_from_link(item);
-
-		usb_log_info("Removing child `%s'.\n",
+	list_foreach(usb_mid->interface_list, link, usbmid_interface_t, iface) {
+		usb_log_info("Removing child `%s'.",
 		    ddf_fun_get_name(iface->fun));
 
-		/* Tell the child to go off-line. */
+		/* Tell the child to go offline. */
 		errno_t pret = ddf_fun_offline(iface->fun);
 		if (pret != EOK) {
-			usb_log_warning("Failed to turn off child `%s': %s\n",
+			usb_log_warning("Failed to turn off child `%s': %s",
 			    ddf_fun_get_name(iface->fun), str_error(pret));
-			ret = pret;
-		}
-
-		/* Now remove the child. */
-		pret = usbmid_interface_destroy(iface);
-		if (pret != EOK) {
-			usb_log_error("Failed to destroy child `%s': %s\n",
-			    ddf_fun_get_name(iface->fun), str_error(pret));
-			ret = pret;
 		}
 	}
-	return ret;
+
+	return destroy_interfaces(usb_mid);
 }
 
@@ -116,10 +124,10 @@
 	assert(usb_mid);
 
-	usb_log_info("USB MID gone: `%s'.\n", usb_device_get_name(dev));
+	usb_log_info("USB MID gone: `%s'.", usb_device_get_name(dev));
 
 	/* Remove ctl function */
 	errno_t ret = ddf_fun_unbind(usb_mid->ctl_fun);
 	if (ret != EOK) {
-		usb_log_error("Failed to unbind USB MID ctl function: %s.\n",
+		usb_log_error("Failed to unbind USB MID ctl function: %s.",
 		    str_error(ret));
 		return ret;
@@ -127,22 +135,26 @@
 	ddf_fun_destroy(usb_mid->ctl_fun);
 
-	/* Now remove all other functions */
-	while (!list_empty(&usb_mid->interface_list)) {
-		link_t *item = list_first(&usb_mid->interface_list);
-		list_remove(item);
+	/* Destroy children and tell their drivers they are gone. */
+	return destroy_interfaces(usb_mid);
+}
 
-		usbmid_interface_t *iface = usbmid_interface_from_link(item);
+static int usbmid_function_online(ddf_fun_t *fun)
+{
+	usb_device_t *usb_dev = ddf_dev_data_get(ddf_fun_get_dev(fun));
+	usb_mid_t *usb_mid = usb_device_data_get(usb_dev);
+	if (fun == usb_mid->ctl_fun)
+		return ENOTSUP;
 
-		usb_log_info("Child `%s' is gone.\n",
-		    ddf_fun_get_name(iface->fun));
+	return ddf_fun_online(fun);
+}
 
-		const errno_t pret = usbmid_interface_destroy(iface);
-		if (pret != EOK) {
-			usb_log_error("Failed to remove child `%s': %s\n",
-			    ddf_fun_get_name(iface->fun), str_error(pret));
-			ret = pret;
-		}
-	}
-	return ret;
+static errno_t usbmid_function_offline(ddf_fun_t *fun)
+{
+	usb_device_t *usb_dev = ddf_dev_data_get(ddf_fun_get_dev(fun));
+	usb_mid_t *usb_mid = usb_device_data_get(usb_dev);
+	if (fun == usb_mid->ctl_fun)
+		return ENOTSUP;
+
+	return ddf_fun_offline(fun);
 }
 
@@ -150,6 +162,8 @@
 static const usb_driver_ops_t mid_driver_ops = {
 	.device_add = usbmid_device_add,
-	.device_rem = usbmid_device_remove,
+	.device_remove = usbmid_device_remove,
 	.device_gone = usbmid_device_gone,
+	.function_online = usbmid_function_online,
+	.function_offline = usbmid_function_offline
 };
 
Index: uspace/drv/bus/usb/usbmid/usbmid.c
===================================================================
--- uspace/drv/bus/usb/usbmid/usbmid.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/usbmid/usbmid.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -45,27 +45,31 @@
 #include "usbmid.h"
 
-/** Get USB device handle by calling the parent usb_device_t.
+/**
+ * Get USB device description by calling HC and altering the interface field.
  *
  * @param[in] fun Device function the operation is running on.
- * @param[out] handle Device handle.
+ * @param[out] desc Device descriptor.
  * @return Error code.
  */
-static errno_t usb_iface_device_handle(ddf_fun_t *fun, devman_handle_t *handle)
-{
-	assert(fun);
-	assert(handle);
-	usb_device_t *usb_dev = usb_device_get(ddf_fun_get_dev(fun));
-	*handle = usb_device_get_devman_handle(usb_dev);
-	return EOK;
-}
-
-/** Callback for DDF USB get interface. */
-static errno_t usb_iface_iface_no(ddf_fun_t *fun, int *iface_no)
+static errno_t usb_iface_description(ddf_fun_t *fun, usb_device_desc_t *desc)
 {
 	usbmid_interface_t *iface = ddf_fun_data_get(fun);
 	assert(iface);
+	usb_device_t *usb_dev = ddf_dev_data_get(ddf_fun_get_dev(fun));
+	assert(usb_dev);
 
-	if (iface_no)
-		*iface_no = iface->interface_no;
+	async_exch_t *exch = usb_device_bus_exchange_begin(usb_dev);
+	if (!exch)
+		return EPARTY;
+
+	usb_device_desc_t tmp_desc;
+	const errno_t ret = usb_get_my_description(exch, &tmp_desc);
+
+	if (ret == EOK && desc) {
+		*desc = tmp_desc;
+		desc->iface = iface->interface_no;
+	}
+
+	usb_device_bus_exchange_end(exch);
 
 	return EOK;
@@ -74,6 +78,5 @@
 /** DDF interface of the child - USB functions. */
 static usb_iface_t child_usb_iface = {
-	.get_my_device_handle = usb_iface_device_handle,
-	.get_my_interface = usb_iface_iface_no,
+	.get_my_description = usb_iface_description,
 };
 
Index: uspace/drv/bus/usb/vhc/conndev.c
===================================================================
--- uspace/drv/bus/usb/vhc/conndev.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/vhc/conndev.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -113,5 +113,5 @@
 		receive_device_name(callback);
 		
-		usb_log_info("New virtual device `%s' (id: %" PRIxn ").\n",
+		usb_log_info("New virtual device `%s' (id: %" PRIxn ").",
 		    plugged_device_name, plugged_device_handle);
 	} else
@@ -130,5 +130,5 @@
 
 	if (plugged_device_handle != 0) {
-		usb_log_info("Virtual device `%s' disconnected (id: %" PRIxn ").\n",
+		usb_log_info("Virtual device `%s' disconnected (id: %" PRIxn ").",
 		    plugged_device_name, plugged_device_handle);
 		vhc_virtdev_unplug(vhc, plugged_device_handle);
Index: uspace/drv/bus/usb/vhc/hub/hub.c
===================================================================
--- uspace/drv/bus/usb/vhc/hub/hub.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/vhc/hub/hub.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -231,5 +231,5 @@
 	}
 
-	usb_log_debug("Setting port %zu to state %d.\n", port_index, state);
+	usb_log_debug("Setting port %zu to state %d.", port_index, state);
 
 	switch (state) {
@@ -423,5 +423,5 @@
 	uint16_t old_value = port->status_change;
 	port->status_change |= change;
-	usb_log_debug("Changing status change on %zu: %04x => %04x\n",
+	usb_log_debug("Changing status change on %zu: %04x => %04x",
 	    port->index,
 	    (unsigned int) old_value, (unsigned int) port->status_change);
@@ -510,5 +510,5 @@
 	fid_t fibril = fibril_create(set_port_state_delayed_fibril, change);
 	if (fibril == 0) {
-		printf("Failed to create fibril\n");
+		usb_log_error("Failed to create fibril.");
 		free(change);
 		return;
Index: uspace/drv/bus/usb/vhc/hub/virthubops.c
===================================================================
--- uspace/drv/bus/usb/vhc/hub/virthubops.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/vhc/hub/virthubops.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -137,5 +137,5 @@
 
 	switch (feature) {
-		case USB_HUB_FEATURE_PORT_ENABLE:
+		case USB2_HUB_FEATURE_PORT_ENABLE:
 			if ((port_state != HUB_PORT_STATE_NOT_CONFIGURED)
 			    && (port_state != HUB_PORT_STATE_POWERED_OFF)) {
@@ -145,5 +145,5 @@
 			break;
 
-		case USB_HUB_FEATURE_PORT_SUSPEND:
+		case USB2_HUB_FEATURE_PORT_SUSPEND:
 			if (port_state != HUB_PORT_STATE_SUSPENDED) {
 				rc = EOK;
@@ -166,10 +166,10 @@
 			break;
 
-		case USB_HUB_FEATURE_C_PORT_ENABLE:
+		case USB2_HUB_FEATURE_C_PORT_ENABLE:
 			hub_clear_port_status_change(hub, port, HUB_STATUS_C_PORT_ENABLE);
 			rc = EOK;
 			break;
 
-		case USB_HUB_FEATURE_C_PORT_SUSPEND:
+		case USB2_HUB_FEATURE_C_PORT_SUSPEND:
 			hub_clear_port_status_change(hub, port, HUB_STATUS_C_PORT_SUSPEND);
 			rc = EOK;
@@ -317,5 +317,5 @@
 			break;
 
-		case USB_HUB_FEATURE_PORT_SUSPEND:
+		case USB2_HUB_FEATURE_PORT_SUSPEND:
 			if (port_state == HUB_PORT_STATE_ENABLED) {
 				hub_set_port_state(hub, port, HUB_PORT_STATE_SUSPENDED);
Index: uspace/drv/bus/usb/vhc/main.c
===================================================================
--- uspace/drv/bus/usb/vhc/main.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/vhc/main.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -40,4 +40,5 @@
 
 #include <usb/host/ddf_helpers.h>
+#include <usb/host/utility.h>
 
 #include <usb/debug.h>
@@ -69,39 +70,33 @@
 		return ret;
 	}
-	vhc_init(vhc);
 	return EOK;
 }
 
-hcd_ops_t vhc_hc_ops = {
-	.schedule = vhc_schedule,
-};
-
 static errno_t vhc_dev_add(ddf_dev_t *dev)
 {
+	/* Initialize generic structures */
+	errno_t ret = hcd_ddf_setup_hc(dev, sizeof(vhc_data_t));
+	if (ret != EOK) {
+		usb_log_error("Failed to init HCD structures: %s.",
+		   str_error(ret));
+		return ret;
+	}
+	vhc_data_t *vhc = ddf_dev_data_get(dev);
+	vhc_init(vhc);
+
+	hc_device_setup(&vhc->base, (bus_t *) &vhc->bus);
+
 	/* Initialize virtual structure */
 	ddf_fun_t *ctl_fun = NULL;
-	errno_t ret = vhc_control_node(dev, &ctl_fun);
+	ret = vhc_control_node(dev, &ctl_fun);
 	if (ret != EOK) {
-		usb_log_error("Failed to setup control node.\n");
-		return ret;
-	}
-	vhc_data_t *data = ddf_fun_data_get(ctl_fun);
-
-	/* Initialize generic structures */
-	ret = hcd_ddf_setup_hc(dev, USB_SPEED_FULL,
-	    BANDWIDTH_AVAILABLE_USB11, bandwidth_count_usb11);
-	if (ret != EOK) {
-		usb_log_error("Failed to init HCD structures: %s.\n",
-		   str_error(ret));
-		ddf_fun_destroy(ctl_fun);
+		usb_log_error("Failed to setup control node.");
 		return ret;
 	}
 
-	hcd_set_implementation(dev_to_hcd(dev), data, &vhc_hc_ops);
-
 	/* Add virtual hub device */
-	ret = vhc_virtdev_plug_hub(data, &data->hub, NULL, 0);
+	ret = vhc_virtdev_plug_hub(vhc, &vhc->hub, NULL, 0);
 	if (ret != EOK) {
-		usb_log_error("Failed to plug root hub: %s.\n", str_error(ret));
+		usb_log_error("Failed to plug root hub: %s.", str_error(ret));
 		ddf_fun_destroy(ctl_fun);
 		return ret;
@@ -112,7 +107,7 @@
 	 * needs to be ready at this time.
 	 */
-	ret = hcd_ddf_setup_root_hub(dev);
+	ret = hc_setup_virtual_root_hub(&vhc->base, USB_SPEED_HIGH);
 	if (ret != EOK) {
-		usb_log_error("Failed to init VHC root hub: %s\n",
+		usb_log_error("Failed to init VHC root hub: %s",
 			str_error(ret));
 		// TODO do something here...
Index: uspace/drv/bus/usb/vhc/transfer.c
===================================================================
--- uspace/drv/bus/usb/vhc/transfer.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/vhc/transfer.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -31,4 +31,7 @@
 #include <usb/debug.h>
 #include <usbvirt/device.h>
+#include <usb/host/bandwidth.h>
+#include <usb/host/endpoint.h>
+#include <usb/host/usb_transfer_batch.h>
 #include <usbvirt/ipc.h>
 #include "vhcd.h"
@@ -37,15 +40,15 @@
 static bool is_set_address_transfer(vhc_transfer_t *transfer)
 {
-	if (transfer->batch->ep->endpoint != 0) {
-		return false;
-	}
-	if (transfer->batch->ep->transfer_type != USB_TRANSFER_CONTROL) {
-		return false;
-	}
-	if (usb_transfer_batch_direction(transfer->batch) != USB_DIRECTION_OUT) {
-		return false;
-	}
-	const usb_device_request_setup_packet_t *setup =
-	    (void*)transfer->batch->setup_buffer;
+	if (transfer->batch.target.endpoint != 0) {
+		return false;
+	}
+	if (transfer->batch.ep->transfer_type != USB_TRANSFER_CONTROL) {
+		return false;
+	}
+	if (transfer->batch.dir != USB_DIRECTION_OUT) {
+		return false;
+	}
+	const usb_device_request_setup_packet_t *setup
+		= &transfer->batch.setup.packet;
 	if (setup->request_type != 0) {
 		return false;
@@ -62,11 +65,11 @@
 {
 	errno_t rc;
-	
-	const usb_direction_t dir = usb_transfer_batch_direction(batch);
+
+	const usb_direction_t dir = batch->dir;
 
 	if (batch->ep->transfer_type == USB_TRANSFER_CONTROL) {
 		if (dir == USB_DIRECTION_IN) {
 			rc = usbvirt_control_read(dev,
-			    batch->setup_buffer, batch->setup_size,
+			    batch->setup.buffer, USB_SETUP_PACKET_SIZE,
 			    batch->buffer, batch->buffer_size,
 			    actual_data_size);
@@ -74,5 +77,5 @@
 			assert(dir == USB_DIRECTION_OUT);
 			rc = usbvirt_control_write(dev,
-			    batch->setup_buffer, batch->setup_size,
+			    batch->setup.buffer, USB_SETUP_PACKET_SIZE,
 			    batch->buffer, batch->buffer_size);
 		}
@@ -99,10 +102,10 @@
 	errno_t rc;
 
-	const usb_direction_t dir = usb_transfer_batch_direction(batch);
+	const usb_direction_t dir = batch->dir;
 
 	if (batch->ep->transfer_type == USB_TRANSFER_CONTROL) {
 		if (dir == USB_DIRECTION_IN) {
 			rc = usbvirt_ipc_send_control_read(sess,
-			    batch->setup_buffer, batch->setup_size,
+			    batch->setup.buffer, USB_SETUP_PACKET_SIZE,
 			    batch->buffer, batch->buffer_size,
 			    actual_data_size);
@@ -110,5 +113,5 @@
 			assert(dir == USB_DIRECTION_OUT);
 			rc = usbvirt_ipc_send_control_write(sess,
-			    batch->setup_buffer, batch->setup_size,
+			    batch->setup.buffer, USB_SETUP_PACKET_SIZE,
 			    batch->buffer, batch->buffer_size);
 		}
@@ -147,10 +150,45 @@
 	assert(outcome != ENAK);
 	assert(transfer);
-	assert(transfer->batch);
-	usb_transfer_batch_finish_error(transfer->batch, NULL,
-	    data_transfer_size, outcome);
-	usb_transfer_batch_destroy(transfer->batch);
-	free(transfer);
-}
+	transfer->batch.error = outcome;
+	transfer->batch.transferred_size = data_transfer_size;
+	usb_transfer_batch_finish(&transfer->batch);
+}
+
+static usb_transfer_batch_t *batch_create(endpoint_t *ep)
+{
+	vhc_transfer_t *transfer = calloc(1, sizeof(vhc_transfer_t));
+	usb_transfer_batch_init(&transfer->batch, ep);
+	link_initialize(&transfer->link);
+	return &transfer->batch;
+}
+
+static int device_enumerate(device_t *device)
+{
+	vhc_data_t *vhc = bus_to_vhc(device->bus);
+	return usb2_bus_device_enumerate(&vhc->bus_helper, device);
+}
+
+static int endpoint_register(endpoint_t *endpoint)
+{
+	vhc_data_t *vhc = bus_to_vhc(endpoint->device->bus);
+	return usb2_bus_endpoint_register(&vhc->bus_helper, endpoint);
+}
+
+static void endpoint_unregister(endpoint_t *endpoint)
+{
+	vhc_data_t *vhc = bus_to_vhc(endpoint->device->bus);
+	usb2_bus_endpoint_unregister(&vhc->bus_helper, endpoint);
+
+	// TODO: abort transfer?
+}
+
+static const bus_ops_t vhc_bus_ops = {
+	.batch_create = batch_create,
+	.batch_schedule = vhc_schedule,
+
+	.device_enumerate = device_enumerate,
+	.endpoint_register = endpoint_register,
+	.endpoint_unregister = endpoint_unregister,
+};
 
 errno_t vhc_init(vhc_data_t *instance)
@@ -159,20 +197,16 @@
 	list_initialize(&instance->devices);
 	fibril_mutex_initialize(&instance->guard);
-	instance->magic = 0xDEADBEEF;
+	bus_init(&instance->bus, sizeof(device_t));
+	usb2_bus_helper_init(&instance->bus_helper, &bandwidth_accounting_usb11);
+	instance->bus.ops = &vhc_bus_ops;
 	return virthub_init(&instance->hub, "root hub");
 }
 
-errno_t vhc_schedule(hcd_t *hcd, usb_transfer_batch_t *batch)
-{
-	assert(hcd);
+errno_t vhc_schedule(usb_transfer_batch_t *batch)
+{
 	assert(batch);
-	vhc_data_t *vhc = hcd_get_driver_data(hcd);
+	vhc_transfer_t *transfer = (vhc_transfer_t *) batch;
+	vhc_data_t *vhc = bus_to_vhc(endpoint_get_bus(batch->ep));
 	assert(vhc);
-
-	vhc_transfer_t *transfer = malloc(sizeof(vhc_transfer_t));
-	if (!transfer)
-		return ENOMEM;
-	link_initialize(&transfer->link);
-	transfer->batch = batch;
 
 	fibril_mutex_lock(&vhc->guard);
@@ -182,5 +216,5 @@
 	list_foreach(vhc->devices, link, vhc_virtdev_t, dev) {
 		fibril_mutex_lock(&dev->guard);
-		if (dev->address == transfer->batch->ep->address) {
+		if (dev->address == transfer->batch.target.address) {
 			if (!targets) {
 				list_append(&transfer->link, &dev->transfer_queue);
@@ -194,5 +228,5 @@
 	
 	if (targets > 1)
-		usb_log_warning("Transfer would be accepted by more devices!\n");
+		usb_log_warning("Transfer would be accepted by more devices!");
 
 	return targets ? EOK : ENOENT;
@@ -217,15 +251,15 @@
 		size_t data_transfer_size = 0;
 		if (dev->dev_sess) {
-			rc = process_transfer_remote(transfer->batch,
+			rc = process_transfer_remote(&transfer->batch,
 			    dev->dev_sess, &data_transfer_size);
 		} else if (dev->dev_local != NULL) {
-			rc = process_transfer_local(transfer->batch,
+			rc = process_transfer_local(&transfer->batch,
 			    dev->dev_local, &data_transfer_size);
 		} else {
-			usb_log_warning("Device has no remote phone nor local node.\n");
+			usb_log_warning("Device has no remote phone nor local node.");
 			rc = ESTALL;
 		}
 
-		usb_log_debug2("Transfer %p processed: %s.\n",
+		usb_log_debug2("Transfer %p processed: %s.",
 		    transfer, str_error(rc));
 
@@ -234,7 +268,7 @@
 			if (is_set_address_transfer(transfer)) {
 				usb_device_request_setup_packet_t *setup =
-				    (void*) transfer->batch->setup_buffer;
+				    (void*) transfer->batch.setup.buffer;
 				dev->address = setup->value;
-				usb_log_debug2("Address changed to %d\n",
+				usb_log_debug2("Address changed to %d",
 				    dev->address);
 			}
Index: uspace/drv/bus/usb/vhc/vhcd.h
===================================================================
--- uspace/drv/bus/usb/vhc/vhcd.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/bus/usb/vhc/vhcd.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -38,8 +38,10 @@
 
 #include <usbvirt/device.h>
-#include <usbhc_iface.h>
 #include <async.h>
+#include <macros.h>
 
 #include <usb/host/hcd.h>
+#include <usb/host/usb2_bus.h>
+#include <usb/host/usb_transfer_batch.h>
 
 #define NAME "vhc"
@@ -56,5 +58,10 @@
 
 typedef struct {
-	uint32_t magic;
+	hc_device_t base;
+
+	bus_t bus;
+	usb2_bus_helper_t bus_helper;
+
+	ddf_fun_t *virtual_fun;
 	list_t devices;
 	fibril_mutex_t guard;
@@ -63,7 +70,19 @@
 
 typedef struct {
+	usb_transfer_batch_t batch;
 	link_t link;
-	usb_transfer_batch_t *batch;
 } vhc_transfer_t;
+
+static inline vhc_data_t *hcd_to_vhc(hc_device_t *hcd)
+{
+	assert(hcd);
+	return (vhc_data_t *) hcd;
+}
+
+static inline vhc_data_t *bus_to_vhc(bus_t *bus)
+{
+	assert(bus);
+	return member_to_inst(bus, vhc_data_t, bus);
+}
 
 void on_client_close(ddf_fun_t *fun);
@@ -76,6 +95,6 @@
 void vhc_virtdev_unplug(vhc_data_t *, uintptr_t);
 
-errno_t vhc_init(vhc_data_t *instance);
-errno_t vhc_schedule(hcd_t *hcd, usb_transfer_batch_t *batch);
+errno_t vhc_init(vhc_data_t *);
+errno_t vhc_schedule(usb_transfer_batch_t *);
 errno_t vhc_transfer_queue_processor(void *arg);
 
Index: uspace/drv/bus/usb/xhci/Makefile
===================================================================
--- uspace/drv/bus/usb/xhci/Makefile	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/Makefile	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,53 @@
+#
+# Copyright (c) 2017 Ondrej Hlavaty <aearsis@eideo.cz>
+# 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.
+#
+
+USPACE_PREFIX = ../../../..
+
+LIBS = usbhost usbvirt usb drv
+
+BINARY = xhci
+
+SOURCES = \
+	bus.c \
+	commands.c \
+	debug.c \
+	device.c \
+	endpoint.c \
+	hc.c \
+	isoch.c \
+	main.c \
+	rh.c \
+	scratchpad.c \
+	streams.c \
+	transfers.c \
+	trb_ring.c
+
+TEST_SOURCES = \
+	test/reg-ops.c
+
+include $(USPACE_PREFIX)/Makefile.common
Index: uspace/drv/bus/usb/xhci/bus.c
===================================================================
--- uspace/drv/bus/usb/xhci/bus.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/bus.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2017 Ondrej Hlavaty <aearsis@eideo.cz>
+ * 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 libusbhost
+ * @{
+ */
+/** @file
+ * xHCI bus interface.
+ */
+
+#include <usb/descriptor.h>
+
+#include "hc.h"
+#include "device.h"
+#include "endpoint.h"
+#include "transfers.h"
+
+#include "bus.h"
+
+
+static const bus_ops_t xhci_bus_ops = {
+	.interrupt = hc_interrupt,
+	.status = hc_status,
+
+	.device_enumerate = xhci_device_enumerate,
+	.device_gone = xhci_device_gone,
+	.device_online = xhci_device_online,
+	.device_offline = xhci_device_offline,
+
+	.endpoint_create = xhci_endpoint_create,
+	.endpoint_destroy = xhci_endpoint_destroy,
+	.endpoint_register = xhci_endpoint_register,
+	.endpoint_unregister = xhci_endpoint_unregister,
+
+	.batch_schedule = xhci_transfer_schedule,
+	.batch_create = xhci_transfer_create,
+	.batch_destroy = xhci_transfer_destroy,
+};
+
+/** Initialize XHCI bus.
+ * @param[in] bus Allocated XHCI bus to initialize.
+ * @param[in] hc Associated host controller, which manages the bus.
+ *
+ * @return Error code.
+ */
+int xhci_bus_init(xhci_bus_t *bus, xhci_hc_t *hc)
+{
+	assert(bus);
+
+	bus_init(&bus->base, sizeof(xhci_device_t));
+
+	bus->devices_by_slot = calloc(hc->max_slots, sizeof(xhci_device_t *));
+	if (!bus->devices_by_slot)
+		return ENOMEM;
+
+	bus->hc = hc;
+	bus->base.ops = &xhci_bus_ops;
+	return EOK;
+}
+
+/** Finalize XHCI bus.
+ * @param[in] bus XHCI bus to finalize.
+ */
+void xhci_bus_fini(xhci_bus_t *bus)
+{
+	// FIXME: Ensure there are no more devices?
+	free(bus->devices_by_slot);
+	// FIXME: Something else we forgot?
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/bus.h
===================================================================
--- uspace/drv/bus/usb/xhci/bus.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/bus.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2017 Ondrej Hlavaty <aearsis@eideo.cz>
+ * 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 libusbhost
+ * @{
+ */
+/** @file
+ *
+ * An implementation of bus keeper for xHCI. The (physical) HC itself takes
+ * care about addressing devices, so this implementation is actually simpler
+ * than those of [OUE]HCI.
+ */
+#ifndef XHCI_BUS_H
+#define XHCI_BUS_H
+
+#include <usb/host/bus.h>
+
+typedef struct xhci_hc xhci_hc_t;
+typedef struct xhci_device xhci_device_t;
+
+/** Endpoint management structure */
+typedef struct xhci_bus {
+	bus_t base;		/**< Inheritance. Keep this first. */
+
+	xhci_hc_t *hc;				/**< Pointer to managing HC (to issue commands) */
+
+	xhci_device_t **devices_by_slot;	/**< Devices by Slot ID */
+} xhci_bus_t;
+
+extern int xhci_bus_init(xhci_bus_t *, xhci_hc_t *);
+extern void xhci_bus_fini(xhci_bus_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/commands.c
===================================================================
--- uspace/drv/bus/usb/xhci/commands.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/commands.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,806 @@
+/*
+ * Copyright (c) 2017 Jaroslav Jindrak
+ * 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 Command sending functions.
+ */
+
+#include <errno.h>
+#include <str_error.h>
+#include <usb/debug.h>
+#include "commands.h"
+#include "debug.h"
+#include "hc.h"
+#include "hw_struct/context.h"
+#include "hw_struct/trb.h"
+
+#define TRB_SET_TSP(trb, tsp)   (trb).control |= host2xhci(32, (((tsp) & 0x1) << 9))
+#define TRB_SET_TYPE(trb, type) (trb).control |= host2xhci(32, (type) << 10)
+#define TRB_SET_DC(trb, dc)     (trb).control |= host2xhci(32, (dc) << 9)
+#define TRB_SET_EP(trb, ep)     (trb).control |= host2xhci(32, ((ep) & 0x5) << 16)
+#define TRB_SET_STREAM(trb, st) (trb).control |= host2xhci(32, ((st) & 0xFFFF) << 16)
+#define TRB_SET_SUSP(trb, susp) (trb).control |= host2xhci(32, ((susp) & 0x1) << 23)
+#define TRB_SET_SLOT(trb, slot) (trb).control |= host2xhci(32, (slot) << 24)
+#define TRB_SET_DEV_SPEED(trb, speed)	(trb).control |= host2xhci(32, (speed & 0xF) << 16)
+#define TRB_SET_DEQUEUE_PTR(trb, dptr) (trb).parameter |= host2xhci(64, (dptr))
+#define TRB_SET_ICTX(trb, phys) (trb).parameter |= host2xhci(64, (phys) & (~0xF))
+
+#define TRB_GET_CODE(trb) XHCI_DWORD_EXTRACT((trb).status, 31, 24)
+#define TRB_GET_SLOT(trb) XHCI_DWORD_EXTRACT((trb).control, 31, 24)
+#define TRB_GET_PHYS(trb) (XHCI_QWORD_EXTRACT((trb).parameter, 63, 4) << 4)
+
+/* Control functions */
+
+static xhci_cmd_ring_t *get_cmd_ring(xhci_hc_t *hc)
+{
+	assert(hc);
+	return &hc->cr;
+}
+
+/**
+ * Initialize the command subsystem. Allocates the comand ring.
+ *
+ * Does not configure the CR pointer to the hardware, because the xHC will be
+ * reset before starting.
+ */
+int xhci_init_commands(xhci_hc_t *hc)
+{
+	xhci_cmd_ring_t *cr = get_cmd_ring(hc);
+	int err;
+
+	if ((err = xhci_trb_ring_init(&cr->trb_ring, 0)))
+		return err;
+
+	fibril_mutex_initialize(&cr->guard);
+	fibril_condvar_initialize(&cr->state_cv);
+	fibril_condvar_initialize(&cr->stopped_cv);
+
+	list_initialize(&cr->cmd_list);
+
+	cr->state = XHCI_CR_STATE_OPEN;
+
+	return EOK;
+}
+
+/**
+ * Finish the command subsystem. Stops the hardware from running commands, then
+ * deallocates the ring.
+ */
+void xhci_fini_commands(xhci_hc_t *hc)
+{
+	assert(hc);
+	xhci_stop_command_ring(hc);
+
+	xhci_cmd_ring_t *cr = get_cmd_ring(hc);
+
+	fibril_mutex_lock(&cr->guard);
+	xhci_trb_ring_fini(&cr->trb_ring);
+	fibril_mutex_unlock(&cr->guard);
+}
+
+/**
+ * Initialize a command structure for the given command.
+ */
+void xhci_cmd_init(xhci_cmd_t *cmd, xhci_cmd_type_t type)
+{
+	memset(cmd, 0, sizeof(*cmd));
+
+	link_initialize(&cmd->_header.link);
+
+	fibril_mutex_initialize(&cmd->_header.completed_mtx);
+	fibril_condvar_initialize(&cmd->_header.completed_cv);
+
+	cmd->_header.cmd = type;
+}
+
+/**
+ * Finish the command structure. Some command invocation includes allocating
+ * a context structure. To have the convenience in calling commands, this
+ * method deallocates all resources.
+ */
+void xhci_cmd_fini(xhci_cmd_t *cmd)
+{
+	list_remove(&cmd->_header.link);
+
+	dma_buffer_free(&cmd->input_ctx);
+	dma_buffer_free(&cmd->bandwidth_ctx);
+
+	if (cmd->_header.async) {
+		free(cmd);
+	}
+}
+
+/**
+ * Find a command issued by TRB at @c phys inside the command list.
+ *
+ * Call with guard locked only.
+ */
+static inline xhci_cmd_t *find_command(xhci_hc_t *hc, uint64_t phys)
+{
+	xhci_cmd_ring_t *cr = get_cmd_ring(hc);
+	assert(fibril_mutex_is_locked(&cr->guard));
+
+	link_t *cmd_link = list_first(&cr->cmd_list);
+
+	while (cmd_link != NULL) {
+		xhci_cmd_t *cmd = list_get_instance(cmd_link, xhci_cmd_t, _header.link);
+
+		if (cmd->_header.trb_phys == phys)
+			break;
+
+		cmd_link = list_next(cmd_link, &cr->cmd_list);
+	}
+
+	return cmd_link ? list_get_instance(cmd_link, xhci_cmd_t, _header.link)
+		: NULL;
+}
+
+static void cr_set_state(xhci_cmd_ring_t *cr, xhci_cr_state_t state)
+{
+	assert(fibril_mutex_is_locked(&cr->guard));
+
+	cr->state = state;
+	if (state == XHCI_CR_STATE_OPEN
+	    || state == XHCI_CR_STATE_CLOSED)
+		fibril_condvar_broadcast(&cr->state_cv);
+}
+
+static int wait_for_ring_open(xhci_cmd_ring_t *cr)
+{
+	assert(fibril_mutex_is_locked(&cr->guard));
+
+	while (true) {
+		switch (cr->state) {
+		case XHCI_CR_STATE_CHANGING:
+		case XHCI_CR_STATE_FULL:
+			fibril_condvar_wait(&cr->state_cv, &cr->guard);
+			break;
+		case XHCI_CR_STATE_OPEN:
+			return EOK;
+		case XHCI_CR_STATE_CLOSED:
+			return ENAK;
+		}
+	}
+}
+
+/**
+ * Enqueue a command on the TRB ring. Ring the doorbell to initiate processing.
+ * Register the command as waiting for completion inside the command list.
+ */
+static inline int enqueue_command(xhci_hc_t *hc, xhci_cmd_t *cmd)
+{
+	xhci_cmd_ring_t *cr = get_cmd_ring(hc);
+	assert(cmd);
+
+	fibril_mutex_lock(&cr->guard);
+
+	if (wait_for_ring_open(cr)) {
+		fibril_mutex_unlock(&cr->guard);
+		return ENAK;
+	}
+
+	usb_log_debug("Sending command %s", xhci_trb_str_type(TRB_TYPE(cmd->_header.trb)));
+
+	list_append(&cmd->_header.link, &cr->cmd_list);
+
+	int err = EOK;
+	while (err == EOK) {
+		err = xhci_trb_ring_enqueue(&cr->trb_ring,
+		    &cmd->_header.trb, &cmd->_header.trb_phys);
+		if (err != EAGAIN)
+			break;
+
+		cr_set_state(cr, XHCI_CR_STATE_FULL);
+		err = wait_for_ring_open(cr);
+	}
+
+	if (err == EOK)
+		hc_ring_doorbell(hc, 0, 0);
+
+	fibril_mutex_unlock(&cr->guard);
+
+	return err;
+}
+
+/**
+ * Stop the command ring. Stop processing commands, block issuing new ones.
+ * Wait until hardware acknowledges it is stopped.
+ */
+void xhci_stop_command_ring(xhci_hc_t *hc)
+{
+	xhci_cmd_ring_t *cr = get_cmd_ring(hc);
+
+	fibril_mutex_lock(&cr->guard);
+
+	// Prevent others from starting CR again.
+	cr_set_state(cr, XHCI_CR_STATE_CLOSED);
+
+	/* Some systems, inc. QEMU, need whole 64-bit qword to be written */
+	XHCI_REG_SET(hc->op_regs, XHCI_OP_CS, 1);
+	XHCI_REG_SET(hc->op_regs, XHCI_OP_CRCR_HI, 0);
+
+	while (XHCI_REG_RD(hc->op_regs, XHCI_OP_CRR))
+		fibril_condvar_wait(&cr->stopped_cv, &cr->guard);
+
+	fibril_mutex_unlock(&cr->guard);
+}
+
+/**
+ * Abort currently processed command. Note that it is only aborted when the
+ * command is "blocking" - see section 4.6.1.2 of xHCI spec.
+ */
+static void abort_command_ring(xhci_hc_t *hc)
+{
+	/* Some systems, inc. QEMU, need whole 64-bit qword to be written */
+	XHCI_REG_SET(hc->op_regs, XHCI_OP_CA, 1);
+	XHCI_REG_SET(hc->op_regs, XHCI_OP_CRCR_HI, 0);
+}
+
+static const char *trb_codes [] = {
+#define TRBC(t) [XHCI_TRBC_##t] = #t
+	TRBC(INVALID),
+	TRBC(SUCCESS),
+	TRBC(DATA_BUFFER_ERROR),
+	TRBC(BABBLE_DETECTED_ERROR),
+	TRBC(USB_TRANSACTION_ERROR),
+	TRBC(TRB_ERROR),
+	TRBC(STALL_ERROR),
+	TRBC(RESOURCE_ERROR),
+	TRBC(BANDWIDTH_ERROR),
+	TRBC(NO_SLOTS_ERROR),
+	TRBC(INVALID_STREAM_ERROR),
+	TRBC(SLOT_NOT_ENABLED_ERROR),
+	TRBC(EP_NOT_ENABLED_ERROR),
+	TRBC(SHORT_PACKET),
+	TRBC(RING_UNDERRUN),
+	TRBC(RING_OVERRUN),
+	TRBC(VF_EVENT_RING_FULL),
+	TRBC(PARAMETER_ERROR),
+	TRBC(BANDWIDTH_OVERRUN_ERROR),
+	TRBC(CONTEXT_STATE_ERROR),
+	TRBC(NO_PING_RESPONSE_ERROR),
+	TRBC(EVENT_RING_FULL_ERROR),
+	TRBC(INCOMPATIBLE_DEVICE_ERROR),
+	TRBC(MISSED_SERVICE_ERROR),
+	TRBC(COMMAND_RING_STOPPED),
+	TRBC(COMMAND_ABORTED),
+	TRBC(STOPPED),
+	TRBC(STOPPED_LENGTH_INVALID),
+	TRBC(STOPPED_SHORT_PACKET),
+	TRBC(MAX_EXIT_LATENCY_TOO_LARGE_ERROR),
+	[30] = "<reserved>",
+	TRBC(ISOCH_BUFFER_OVERRUN),
+	TRBC(EVENT_LOST_ERROR),
+	TRBC(UNDEFINED_ERROR),
+	TRBC(INVALID_STREAM_ID_ERROR),
+	TRBC(SECONDARY_BANDWIDTH_ERROR),
+	TRBC(SPLIT_TRANSACTION_ERROR),
+	[XHCI_TRBC_MAX] = NULL
+#undef TRBC
+};
+
+/**
+ * Report an error according to command completion code.
+ */
+static void report_error(int code)
+{
+	if (code < XHCI_TRBC_MAX && trb_codes[code] != NULL)
+		usb_log_error("Command resulted in error: %s.", trb_codes[code]);
+	else
+		usb_log_error("Command resulted in reserved or vendor specific error.");
+}
+
+/**
+ * Handle a command completion. Feed the fibril waiting for result.
+ *
+ * @param trb The COMMAND_COMPLETION TRB found in event ring.
+ */
+int xhci_handle_command_completion(xhci_hc_t *hc, xhci_trb_t *trb)
+{
+	xhci_cmd_ring_t *cr = get_cmd_ring(hc);
+	assert(trb);
+
+	fibril_mutex_lock(&cr->guard);
+
+	int code = TRB_GET_CODE(*trb);
+
+	if (code == XHCI_TRBC_COMMAND_RING_STOPPED) {
+		/* This can either mean that the ring is being stopped, or
+		 * a command was aborted. In either way, wake threads waiting
+		 * on stopped_cv.
+		 *
+		 * Note that we need to hold mutex, because we must be sure the
+		 * requesting thread is waiting inside the CV.
+		 */
+		usb_log_debug("Command ring stopped.");
+		fibril_condvar_broadcast(&cr->stopped_cv);
+		fibril_mutex_unlock(&cr->guard);
+		return EOK;
+	}
+
+	const uint64_t phys = TRB_GET_PHYS(*trb);
+	xhci_trb_ring_update_dequeue(&cr->trb_ring, phys);
+
+	if (cr->state == XHCI_CR_STATE_FULL)
+		cr_set_state(cr, XHCI_CR_STATE_OPEN);
+
+	xhci_cmd_t *command = find_command(hc, phys);
+	if (command == NULL) {
+		usb_log_error("No command struct for completion event found.");
+
+		if (code != XHCI_TRBC_SUCCESS)
+			report_error(code);
+
+		return EOK;
+	}
+
+	list_remove(&command->_header.link);
+
+	/* Semantics of NO_OP_CMD is that success is marked as a TRB error. */
+	if (command->_header.cmd == XHCI_CMD_NO_OP && code == XHCI_TRBC_TRB_ERROR)
+		code = XHCI_TRBC_SUCCESS;
+
+	command->status = code;
+	command->slot_id = TRB_GET_SLOT(*trb);
+
+	usb_log_debug("Completed command %s",
+	    xhci_trb_str_type(TRB_TYPE(command->_header.trb)));
+
+	if (code != XHCI_TRBC_SUCCESS) {
+		report_error(code);
+		xhci_dump_trb(&command->_header.trb);
+	}
+
+	fibril_mutex_unlock(&cr->guard);
+
+	fibril_mutex_lock(&command->_header.completed_mtx);
+	command->_header.completed = true;
+	fibril_condvar_broadcast(&command->_header.completed_cv);
+	fibril_mutex_unlock(&command->_header.completed_mtx);
+
+	if (command->_header.async) {
+		/* Free the command and other DS upon completion. */
+		xhci_cmd_fini(command);
+	}
+
+	return EOK;
+}
+
+/* Command-issuing functions */
+
+static int no_op_cmd(xhci_hc_t *hc, xhci_cmd_t *cmd)
+{
+	assert(hc);
+
+	xhci_trb_clean(&cmd->_header.trb);
+
+	TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_NO_OP_CMD);
+
+	return enqueue_command(hc, cmd);
+}
+
+static int enable_slot_cmd(xhci_hc_t *hc, xhci_cmd_t *cmd)
+{
+	assert(hc);
+
+	xhci_trb_clean(&cmd->_header.trb);
+
+	TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_ENABLE_SLOT_CMD);
+	cmd->_header.trb.control |=
+	    host2xhci(32, XHCI_REG_RD(hc->xecp, XHCI_EC_SP_SLOT_TYPE) << 16);
+
+	return enqueue_command(hc, cmd);
+}
+
+static int disable_slot_cmd(xhci_hc_t *hc, xhci_cmd_t *cmd)
+{
+	assert(hc);
+	assert(cmd);
+
+	xhci_trb_clean(&cmd->_header.trb);
+
+	TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_DISABLE_SLOT_CMD);
+	TRB_SET_SLOT(cmd->_header.trb, cmd->slot_id);
+
+	return enqueue_command(hc, cmd);
+}
+
+static int address_device_cmd(xhci_hc_t *hc, xhci_cmd_t *cmd)
+{
+	assert(hc);
+	assert(cmd);
+	assert(dma_buffer_is_set(&cmd->input_ctx));
+
+	/**
+	 * TODO: Requirements for this command:
+	 *           dcbaa[slot_id] is properly sized and initialized
+	 *           ictx has valids slot context and endpoint 0, all
+	 *           other should be ignored at this point (see section 4.6.5).
+	 */
+
+	xhci_trb_clean(&cmd->_header.trb);
+
+	TRB_SET_ICTX(cmd->_header.trb, cmd->input_ctx.phys);
+
+	/**
+	 * Note: According to section 6.4.3.4, we can set the 9th bit
+	 *       of the control field of the trb (BSR) to 1 and then the xHC
+	 *       will not issue the SET_ADDRESS request to the USB device.
+	 *       This can be used to provide compatibility with legacy USB devices
+	 *       that require their device descriptor to be read before such request.
+	 */
+	TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_ADDRESS_DEVICE_CMD);
+	TRB_SET_SLOT(cmd->_header.trb, cmd->slot_id);
+
+	return enqueue_command(hc, cmd);
+}
+
+static int configure_endpoint_cmd(xhci_hc_t *hc, xhci_cmd_t *cmd)
+{
+	assert(hc);
+	assert(cmd);
+
+	xhci_trb_clean(&cmd->_header.trb);
+
+	if (!cmd->deconfigure) {
+		/* If the DC flag is on, input context is not evaluated. */
+		assert(dma_buffer_is_set(&cmd->input_ctx));
+
+		TRB_SET_ICTX(cmd->_header.trb, cmd->input_ctx.phys);
+	}
+
+	TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_CONFIGURE_ENDPOINT_CMD);
+	TRB_SET_SLOT(cmd->_header.trb, cmd->slot_id);
+	TRB_SET_DC(cmd->_header.trb, cmd->deconfigure);
+
+	return enqueue_command(hc, cmd);
+}
+
+static int evaluate_context_cmd(xhci_hc_t *hc, xhci_cmd_t *cmd)
+{
+	assert(hc);
+	assert(cmd);
+	assert(dma_buffer_is_set(&cmd->input_ctx));
+
+	/**
+	 * Note: All Drop Context flags of the input context shall be 0,
+	 *       all Add Context flags shall be initialize to indicate IDs
+	 *       of the contexts affected by the command.
+	 *       Refer to sections 6.2.2.3 and 6.3.3.3 for further info.
+	 */
+	xhci_trb_clean(&cmd->_header.trb);
+
+	TRB_SET_ICTX(cmd->_header.trb, cmd->input_ctx.phys);
+
+	TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_EVALUATE_CONTEXT_CMD);
+	TRB_SET_SLOT(cmd->_header.trb, cmd->slot_id);
+
+	return enqueue_command(hc, cmd);
+}
+
+static int reset_endpoint_cmd(xhci_hc_t *hc, xhci_cmd_t *cmd)
+{
+	assert(hc);
+	assert(cmd);
+
+	xhci_trb_clean(&cmd->_header.trb);
+
+	TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_RESET_ENDPOINT_CMD);
+	TRB_SET_TSP(cmd->_header.trb, cmd->tsp);
+	TRB_SET_EP(cmd->_header.trb, cmd->endpoint_id);
+	TRB_SET_SLOT(cmd->_header.trb, cmd->slot_id);
+
+	return enqueue_command(hc, cmd);
+}
+
+static int stop_endpoint_cmd(xhci_hc_t *hc, xhci_cmd_t *cmd)
+{
+	assert(hc);
+	assert(cmd);
+
+	xhci_trb_clean(&cmd->_header.trb);
+
+	TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_STOP_ENDPOINT_CMD);
+	TRB_SET_EP(cmd->_header.trb, cmd->endpoint_id);
+	TRB_SET_SUSP(cmd->_header.trb, cmd->susp);
+	TRB_SET_SLOT(cmd->_header.trb, cmd->slot_id);
+
+	return enqueue_command(hc, cmd);
+}
+
+static int set_tr_dequeue_pointer_cmd(xhci_hc_t *hc, xhci_cmd_t *cmd)
+{
+	assert(hc);
+	assert(cmd);
+
+	xhci_trb_clean(&cmd->_header.trb);
+
+	TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_SET_TR_DEQUEUE_POINTER_CMD);
+	TRB_SET_EP(cmd->_header.trb, cmd->endpoint_id);
+	TRB_SET_STREAM(cmd->_header.trb, cmd->stream_id);
+	TRB_SET_SLOT(cmd->_header.trb, cmd->slot_id);
+	TRB_SET_DEQUEUE_PTR(cmd->_header.trb, cmd->dequeue_ptr);
+
+	return enqueue_command(hc, cmd);
+}
+
+static int reset_device_cmd(xhci_hc_t *hc, xhci_cmd_t *cmd)
+{
+	assert(hc);
+	assert(cmd);
+
+	xhci_trb_clean(&cmd->_header.trb);
+
+	TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_RESET_DEVICE_CMD);
+	TRB_SET_SLOT(cmd->_header.trb, cmd->slot_id);
+
+	return enqueue_command(hc, cmd);
+}
+
+static int get_port_bandwidth_cmd(xhci_hc_t *hc, xhci_cmd_t *cmd)
+{
+	assert(hc);
+	assert(cmd);
+
+	xhci_trb_clean(&cmd->_header.trb);
+
+	TRB_SET_ICTX(cmd->_header.trb, cmd->bandwidth_ctx.phys);
+
+	TRB_SET_TYPE(cmd->_header.trb, XHCI_TRB_TYPE_GET_PORT_BANDWIDTH_CMD);
+	TRB_SET_SLOT(cmd->_header.trb, cmd->slot_id);
+	TRB_SET_DEV_SPEED(cmd->_header.trb, cmd->device_speed);
+
+	return enqueue_command(hc, cmd);
+}
+
+/* The table of command-issuing functions. */
+
+typedef int (*cmd_handler) (xhci_hc_t *hc, xhci_cmd_t *cmd);
+
+static cmd_handler cmd_handlers [] = {
+	[XHCI_CMD_ENABLE_SLOT] = enable_slot_cmd,
+	[XHCI_CMD_DISABLE_SLOT] = disable_slot_cmd,
+	[XHCI_CMD_ADDRESS_DEVICE] = address_device_cmd,
+	[XHCI_CMD_CONFIGURE_ENDPOINT] = configure_endpoint_cmd,
+	[XHCI_CMD_EVALUATE_CONTEXT] = evaluate_context_cmd,
+	[XHCI_CMD_RESET_ENDPOINT] = reset_endpoint_cmd,
+	[XHCI_CMD_STOP_ENDPOINT] = stop_endpoint_cmd,
+	[XHCI_CMD_SET_TR_DEQUEUE_POINTER] = set_tr_dequeue_pointer_cmd,
+	[XHCI_CMD_RESET_DEVICE] = reset_device_cmd,
+	[XHCI_CMD_FORCE_EVENT] = NULL,
+	[XHCI_CMD_NEGOTIATE_BANDWIDTH] = NULL,
+	[XHCI_CMD_SET_LATENCY_TOLERANCE_VALUE] = NULL,
+	[XHCI_CMD_GET_PORT_BANDWIDTH] = get_port_bandwidth_cmd,
+	[XHCI_CMD_FORCE_HEADER] = NULL,
+	[XHCI_CMD_NO_OP] = no_op_cmd
+};
+
+/**
+ * Try to abort currently processed command. This is tricky, because
+ * calling fibril is not necessarily the one which issued the blocked command.
+ * Also, the trickiness intensifies by the fact that stopping a CR is denoted by
+ * event, which is again handled in different fibril. but, once we go to sleep
+ * on waiting for that event, another fibril may wake up and try to abort the
+ * blocked command.
+ *
+ * So, we mark the command ring as being restarted, wait for it to stop, and
+ * then start it again. If there was a blocked command, it will be satisfied by
+ * COMMAND_ABORTED event.
+ */
+static int try_abort_current_command(xhci_hc_t *hc)
+{
+	xhci_cmd_ring_t *cr = get_cmd_ring(hc);
+
+	fibril_mutex_lock(&cr->guard);
+
+	if (cr->state == XHCI_CR_STATE_CLOSED) {
+		fibril_mutex_unlock(&cr->guard);
+		return ENAK;
+	}
+
+	if (cr->state == XHCI_CR_STATE_CHANGING) {
+		fibril_mutex_unlock(&cr->guard);
+		return EOK;
+	}
+
+	usb_log_error("Timeout while waiting for command: aborting current command.");
+
+	cr_set_state(cr, XHCI_CR_STATE_CHANGING);
+
+	abort_command_ring(hc);
+
+	fibril_condvar_wait_timeout(&cr->stopped_cv, &cr->guard, XHCI_CR_ABORT_TIMEOUT);
+
+	if (XHCI_REG_RD(hc->op_regs, XHCI_OP_CRR)) {
+		/* 4.6.1.2, implementation note
+		 * Assume there are larger problems with HC and
+		 * reset it.
+		 */
+		usb_log_error("Command didn't abort.");
+
+		cr_set_state(cr, XHCI_CR_STATE_CLOSED);
+
+		// TODO: Reset HC completely.
+		// Don't forget to somehow complete all commands with error.
+
+		fibril_mutex_unlock(&cr->guard);
+		return ENAK;
+	}
+
+	cr_set_state(cr, XHCI_CR_STATE_OPEN);
+
+	fibril_mutex_unlock(&cr->guard);
+
+	usb_log_error("Command ring stopped. Starting again.");
+	hc_ring_doorbell(hc, 0, 0);
+
+	return EOK;
+}
+
+/**
+ * Wait, until the command is completed. The completion is triggered by
+ * COMMAND_COMPLETION event. As we do not want to rely on HW completing the
+ * command in timely manner, we timeout. Note that we can't just return an
+ * error after the timeout pass - it may be other command blocking the ring,
+ * and ours can be completed afterwards. Therefore, it is not guaranteed that
+ * this function will return in XHCI_COMMAND_TIMEOUT. It will continue waiting
+ * until COMMAND_COMPLETION event arrives.
+ */
+static int wait_for_cmd_completion(xhci_hc_t *hc, xhci_cmd_t *cmd)
+{
+	int rv = EOK;
+
+	if (fibril_get_id() == hc->event_handler) {
+		usb_log_error("Deadlock detected in waiting for command.");
+		abort();
+	}
+
+	fibril_mutex_lock(&cmd->_header.completed_mtx);
+	while (!cmd->_header.completed) {
+
+		rv = fibril_condvar_wait_timeout(&cmd->_header.completed_cv,
+		    &cmd->_header.completed_mtx, XHCI_COMMAND_TIMEOUT);
+
+		/* The waiting timed out. Current command (not necessarily
+		 * ours) is probably blocked.
+		 */
+		if (!cmd->_header.completed && rv == ETIMEOUT) {
+			fibril_mutex_unlock(&cmd->_header.completed_mtx);
+
+			rv = try_abort_current_command(hc);
+			if (rv)
+				return rv;
+
+			fibril_mutex_lock(&cmd->_header.completed_mtx);
+		}
+	}
+	fibril_mutex_unlock(&cmd->_header.completed_mtx);
+
+	return rv;
+}
+
+/**
+ * Issue command and block the current fibril until it is completed or timeout
+ * expires. Nothing is deallocated. Caller should always execute `xhci_cmd_fini`.
+ */
+int xhci_cmd_sync(xhci_hc_t *hc, xhci_cmd_t *cmd)
+{
+	assert(hc);
+	assert(cmd);
+
+	int err;
+
+	if (!cmd_handlers[cmd->_header.cmd]) {
+		/* Handler not implemented. */
+		return ENOTSUP;
+	}
+
+	if ((err = cmd_handlers[cmd->_header.cmd](hc, cmd))) {
+		/* Command could not be issued. */
+		return err;
+	}
+
+	if ((err = wait_for_cmd_completion(hc, cmd))) {
+		/* Command failed. */
+		return err;
+	}
+
+	switch (cmd->status) {
+	case XHCI_TRBC_SUCCESS:
+		return EOK;
+	case XHCI_TRBC_USB_TRANSACTION_ERROR:
+		return ESTALL;
+	case XHCI_TRBC_RESOURCE_ERROR:
+	case XHCI_TRBC_BANDWIDTH_ERROR:
+	case XHCI_TRBC_NO_SLOTS_ERROR:
+		return ELIMIT;
+	case XHCI_TRBC_SLOT_NOT_ENABLED_ERROR:
+		return ENOENT;
+	default:
+		return EINVAL;
+	}
+}
+
+/**
+ * Does the same thing as `xhci_cmd_sync` and executes `xhci_cmd_fini`. This
+ * is a useful shorthand for issuing commands without out parameters.
+ */
+int xhci_cmd_sync_fini(xhci_hc_t *hc, xhci_cmd_t *cmd)
+{
+	const int err = xhci_cmd_sync(hc, cmd);
+	xhci_cmd_fini(cmd);
+
+	return err;
+}
+
+/**
+ * Does the same thing as `xhci_cmd_sync_fini` without blocking the current
+ * fibril. The command is copied to stack memory and `fini` is called upon its completion.
+ */
+int xhci_cmd_async_fini(xhci_hc_t *hc, xhci_cmd_t *stack_cmd)
+{
+	assert(hc);
+	assert(stack_cmd);
+
+	/* Save the command for later. */
+	xhci_cmd_t *heap_cmd = (xhci_cmd_t *) malloc(sizeof(xhci_cmd_t));
+	if (!heap_cmd) {
+		return ENOMEM;
+	}
+
+	/* TODO: Is this good for the mutex and the condvar? */
+	memcpy(heap_cmd, stack_cmd, sizeof(xhci_cmd_t));
+	heap_cmd->_header.async = true;
+
+	/* Issue the command. */
+	int err;
+
+	if (!cmd_handlers[heap_cmd->_header.cmd]) {
+		/* Handler not implemented. */
+		err = ENOTSUP;
+		goto err_heap_cmd;
+	}
+
+	if ((err = cmd_handlers[heap_cmd->_header.cmd](hc, heap_cmd))) {
+		/* Command could not be issued. */
+		goto err_heap_cmd;
+	}
+
+	return EOK;
+
+err_heap_cmd:
+	free(heap_cmd);
+	return err;
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/commands.h
===================================================================
--- uspace/drv/bus/usb/xhci/commands.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/commands.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2017 Jaroslav Jindrak
+ * 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
+ * Utility functions used to place TRBs onto the command ring.
+ */
+
+#ifndef XHCI_COMMANDS_H
+#define XHCI_COMMANDS_H
+
+#include <adt/list.h>
+#include <stdbool.h>
+#include <fibril_synch.h>
+#include <usb/host/dma_buffer.h>
+#include "hw_struct/trb.h"
+#include "trb_ring.h"
+
+#define XHCI_COMMAND_TIMEOUT       10000000
+#define XHCI_CR_ABORT_TIMEOUT       5000000
+
+typedef struct xhci_hc xhci_hc_t;
+typedef struct xhci_input_ctx xhci_input_ctx_t;
+typedef struct xhci_port_bandwidth_ctx xhci_port_bandwidth_ctx_t;
+
+typedef enum xhci_cmd_type {
+	XHCI_CMD_ENABLE_SLOT,
+	XHCI_CMD_DISABLE_SLOT,
+	XHCI_CMD_ADDRESS_DEVICE,
+	XHCI_CMD_CONFIGURE_ENDPOINT,
+	XHCI_CMD_EVALUATE_CONTEXT,
+	XHCI_CMD_RESET_ENDPOINT,
+	XHCI_CMD_STOP_ENDPOINT,
+	XHCI_CMD_SET_TR_DEQUEUE_POINTER,
+	XHCI_CMD_RESET_DEVICE,
+	XHCI_CMD_FORCE_EVENT,
+	XHCI_CMD_NEGOTIATE_BANDWIDTH,
+	XHCI_CMD_SET_LATENCY_TOLERANCE_VALUE,
+	XHCI_CMD_GET_PORT_BANDWIDTH,
+	XHCI_CMD_FORCE_HEADER,
+	XHCI_CMD_NO_OP,
+} xhci_cmd_type_t;
+
+typedef enum {
+	XHCI_CR_STATE_CLOSED,		/**< Commands are rejected with ENAK. */
+	XHCI_CR_STATE_OPEN,		/**< Commands are enqueued normally. */
+	XHCI_CR_STATE_CHANGING,		/**< Commands wait until state changes. */
+	XHCI_CR_STATE_FULL,		/**< Commands wait until something completes. */
+} xhci_cr_state_t;
+
+typedef struct xhci_command_ring {
+	xhci_trb_ring_t trb_ring;
+
+	fibril_mutex_t guard;		/**< Guard access to this structure. */
+	list_t cmd_list;
+
+	xhci_cr_state_t state;		/**< Whether commands are allowed to be
+					     added. */
+	fibril_condvar_t state_cv;	/**< For waiting on CR state change. */
+
+	fibril_condvar_t stopped_cv;	/**< For waiting on CR stopped event. */
+} xhci_cmd_ring_t;
+
+typedef struct xhci_command {
+	/** Internal fields used for bookkeeping. Need not worry about these. */
+	struct {
+		link_t link;
+
+		xhci_cmd_type_t cmd;
+
+		xhci_trb_t trb;
+		uintptr_t trb_phys;
+
+		bool async;
+		bool completed;
+
+		/* Will broadcast after command completes. */
+		fibril_mutex_t completed_mtx;
+		fibril_condvar_t completed_cv;
+	} _header;
+
+	/** Below are arguments of all commands mixed together.
+	 *  Be sure to know which command accepts what arguments. */
+
+	uint32_t slot_id;
+	uint32_t endpoint_id;
+	uint16_t stream_id;
+
+	dma_buffer_t input_ctx, bandwidth_ctx;
+	uintptr_t dequeue_ptr;
+
+	bool tsp;
+	uint8_t susp;
+	uint8_t device_speed;
+	uint32_t status;
+	bool deconfigure;
+} xhci_cmd_t;
+
+/* Command handling control */
+extern int xhci_init_commands(xhci_hc_t *);
+extern void xhci_fini_commands(xhci_hc_t *);
+
+extern void xhci_stop_command_ring(xhci_hc_t *);
+extern void xhci_abort_command_ring(xhci_hc_t *);
+extern void xhci_start_command_ring(xhci_hc_t *);
+
+extern int xhci_handle_command_completion(xhci_hc_t *, xhci_trb_t *);
+
+/* Command lifecycle */
+extern void xhci_cmd_init(xhci_cmd_t *, xhci_cmd_type_t);
+extern void xhci_cmd_fini(xhci_cmd_t *);
+
+/* Issuing commands */
+extern int xhci_cmd_sync(xhci_hc_t *, xhci_cmd_t *);
+extern int xhci_cmd_sync_fini(xhci_hc_t *, xhci_cmd_t *);
+extern int xhci_cmd_async_fini(xhci_hc_t *, xhci_cmd_t *);
+
+static inline int xhci_cmd_sync_inline_wrapper(xhci_hc_t *hc, xhci_cmd_t cmd)
+{
+	/* Poor man's xhci_cmd_init (everything else is zeroed) */
+	link_initialize(&cmd._header.link);
+	fibril_mutex_initialize(&cmd._header.completed_mtx);
+	fibril_condvar_initialize(&cmd._header.completed_cv);
+
+	/* Issue the command */
+	const int err = xhci_cmd_sync(hc, &cmd);
+	xhci_cmd_fini(&cmd);
+
+	return err;
+}
+
+/** The inline macro expects:
+ *    - hc      - HC to schedule command on (xhci_hc_t *).
+ *    - command - Member of `xhci_cmd_type_t` without the "XHCI_CMD_" prefix.
+ *    - VA_ARGS - (optional) Command arguments in struct initialization notation.
+ *
+ *  The return code and semantics matches those of `xhci_cmd_sync_fini`.
+ *
+ *  Example:
+ *    int err = xhci_cmd_sync_inline(hc, DISABLE_SLOT, .slot_id = 42);
+ */
+
+#define xhci_cmd_sync_inline(hc, command, ...) \
+	xhci_cmd_sync_inline_wrapper(hc, \
+	(xhci_cmd_t) { ._header.cmd = XHCI_CMD_##command, ##__VA_ARGS__ })
+
+#endif
+
+
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/debug.c
===================================================================
--- uspace/drv/bus/usb/xhci/debug.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/debug.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,416 @@
+/*
+ * Copyright (c) 2017 Ondrej Hlavaty
+ * 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
+ * Various functions to examine current state of the xHC.
+ */
+
+#include <inttypes.h>
+#include <byteorder.h>
+#include <usb/debug.h>
+
+#include "hw_struct/trb.h"
+#include "debug.h"
+#include "hc.h"
+
+#define PX "\t%-21s = "
+
+#define DUMP_REG_FIELD(ptr, title, size, ...) \
+	usb_log_debug(PX "%" PRIu##size, title, XHCI_REG_RD_FIELD(ptr, size, ##__VA_ARGS__))
+
+#define DUMP_REG_RANGE(ptr, title, size, ...) \
+	usb_log_debug(PX "%" PRIu##size, title, XHCI_REG_RD_RANGE(ptr, size, ##__VA_ARGS__))
+
+#define DUMP_REG_FLAG(ptr, title, size, ...) \
+	usb_log_debug(PX "%s", title, XHCI_REG_RD_FLAG(ptr, size, ##__VA_ARGS__) ? "true" : "false")
+
+#define DUMP_REG_INNER(set, title, field, size, type, ...) \
+	DUMP_REG_##type(&(set)->field, title, size, ##__VA_ARGS__)
+
+#define DUMP_REG(set, c) DUMP_REG_INNER(set, #c, c)
+
+/**
+ * Dumps all capability registers.
+ */
+void xhci_dump_cap_regs(const xhci_cap_regs_t *cap)
+{
+	usb_log_debug("Capabilities:");
+
+	DUMP_REG(cap, XHCI_CAP_LENGTH);
+	DUMP_REG(cap, XHCI_CAP_VERSION);
+	DUMP_REG(cap, XHCI_CAP_MAX_SLOTS);
+	DUMP_REG(cap, XHCI_CAP_MAX_INTRS);
+	DUMP_REG(cap, XHCI_CAP_MAX_PORTS);
+	DUMP_REG(cap, XHCI_CAP_IST);
+	DUMP_REG(cap, XHCI_CAP_ERST_MAX);
+	usb_log_debug(PX "%u", "Max Scratchpad bufs", xhci_get_max_spbuf(cap));
+	DUMP_REG(cap, XHCI_CAP_SPR);
+	DUMP_REG(cap, XHCI_CAP_U1EL);
+	DUMP_REG(cap, XHCI_CAP_U2EL);
+	DUMP_REG(cap, XHCI_CAP_AC64);
+	DUMP_REG(cap, XHCI_CAP_BNC);
+	DUMP_REG(cap, XHCI_CAP_CSZ);
+	DUMP_REG(cap, XHCI_CAP_PPC);
+	DUMP_REG(cap, XHCI_CAP_PIND);
+	DUMP_REG(cap, XHCI_CAP_C);
+	DUMP_REG(cap, XHCI_CAP_LTC);
+	DUMP_REG(cap, XHCI_CAP_NSS);
+	DUMP_REG(cap, XHCI_CAP_PAE);
+	DUMP_REG(cap, XHCI_CAP_SPC);
+	DUMP_REG(cap, XHCI_CAP_SEC);
+	DUMP_REG(cap, XHCI_CAP_CFC);
+	DUMP_REG(cap, XHCI_CAP_MAX_PSA_SIZE);
+	DUMP_REG(cap, XHCI_CAP_XECP);
+	DUMP_REG(cap, XHCI_CAP_DBOFF);
+	DUMP_REG(cap, XHCI_CAP_RTSOFF);
+	DUMP_REG(cap, XHCI_CAP_U3C);
+	DUMP_REG(cap, XHCI_CAP_CMC);
+	DUMP_REG(cap, XHCI_CAP_FSC);
+	DUMP_REG(cap, XHCI_CAP_CTC);
+	DUMP_REG(cap, XHCI_CAP_LEC);
+	DUMP_REG(cap, XHCI_CAP_CIC);
+}
+
+/**
+ * Dumps registers of one port.
+ */
+void xhci_dump_port(const xhci_port_regs_t *port)
+{
+	DUMP_REG(port, XHCI_PORT_CCS);
+	DUMP_REG(port, XHCI_PORT_PED);
+	DUMP_REG(port, XHCI_PORT_OCA);
+	DUMP_REG(port, XHCI_PORT_PR);
+	DUMP_REG(port, XHCI_PORT_PLS);
+	DUMP_REG(port, XHCI_PORT_PP);
+	DUMP_REG(port, XHCI_PORT_PS);
+	DUMP_REG(port, XHCI_PORT_PIC);
+	DUMP_REG(port, XHCI_PORT_LWS);
+	DUMP_REG(port, XHCI_PORT_CSC);
+	DUMP_REG(port, XHCI_PORT_PEC);
+	DUMP_REG(port, XHCI_PORT_WRC);
+	DUMP_REG(port, XHCI_PORT_OCC);
+	DUMP_REG(port, XHCI_PORT_PRC);
+	DUMP_REG(port, XHCI_PORT_PLC);
+	DUMP_REG(port, XHCI_PORT_CEC);
+	DUMP_REG(port, XHCI_PORT_CAS);
+	DUMP_REG(port, XHCI_PORT_WCE);
+	DUMP_REG(port, XHCI_PORT_WDE);
+	DUMP_REG(port, XHCI_PORT_WOE);
+	DUMP_REG(port, XHCI_PORT_DR);
+	DUMP_REG(port, XHCI_PORT_WPR);
+	DUMP_REG(port, XHCI_PORT_USB3_U1TO);
+	DUMP_REG(port, XHCI_PORT_USB3_U2TO);
+	DUMP_REG(port, XHCI_PORT_USB3_FLPMA);
+	DUMP_REG(port, XHCI_PORT_USB3_LEC);
+	DUMP_REG(port, XHCI_PORT_USB3_RLC);
+	DUMP_REG(port, XHCI_PORT_USB3_TLC);
+	DUMP_REG(port, XHCI_PORT_USB2_L1S);
+	DUMP_REG(port, XHCI_PORT_USB2_RWE);
+	DUMP_REG(port, XHCI_PORT_USB2_BESL);
+	DUMP_REG(port, XHCI_PORT_USB2_L1DS);
+	DUMP_REG(port, XHCI_PORT_USB2_HLE);
+	DUMP_REG(port, XHCI_PORT_USB2_TM);
+	DUMP_REG(port, XHCI_PORT_USB2_HIRDM);
+	DUMP_REG(port, XHCI_PORT_USB2_L1TO);
+	DUMP_REG(port, XHCI_PORT_USB2_BESLD);
+}
+
+/**
+ * Dumps all registers that define state of the HC.
+ */
+void xhci_dump_state(const xhci_hc_t *hc)
+{
+	usb_log_debug("Operational registers:");
+
+	DUMP_REG(hc->op_regs, XHCI_OP_RS);
+	DUMP_REG(hc->op_regs, XHCI_OP_HCRST);
+	DUMP_REG(hc->op_regs, XHCI_OP_INTE);
+	DUMP_REG(hc->op_regs, XHCI_OP_HSEE);
+	DUMP_REG(hc->op_regs, XHCI_OP_LHCRST);
+	DUMP_REG(hc->op_regs, XHCI_OP_CSS);
+	DUMP_REG(hc->op_regs, XHCI_OP_CRS);
+	DUMP_REG(hc->op_regs, XHCI_OP_EWE);
+	DUMP_REG(hc->op_regs, XHCI_OP_EU3S);
+	DUMP_REG(hc->op_regs, XHCI_OP_CME);
+	DUMP_REG(hc->op_regs, XHCI_OP_HCH);
+	DUMP_REG(hc->op_regs, XHCI_OP_HSE);
+	DUMP_REG(hc->op_regs, XHCI_OP_EINT);
+	DUMP_REG(hc->op_regs, XHCI_OP_PCD);
+	DUMP_REG(hc->op_regs, XHCI_OP_SSS);
+	DUMP_REG(hc->op_regs, XHCI_OP_RSS);
+	DUMP_REG(hc->op_regs, XHCI_OP_SRE);
+	DUMP_REG(hc->op_regs, XHCI_OP_CNR);
+	DUMP_REG(hc->op_regs, XHCI_OP_HCE);
+	DUMP_REG(hc->op_regs, XHCI_OP_PAGESIZE);
+	DUMP_REG(hc->op_regs, XHCI_OP_NOTIFICATION);
+	DUMP_REG(hc->op_regs, XHCI_OP_RCS);
+	DUMP_REG(hc->op_regs, XHCI_OP_CS);
+	DUMP_REG(hc->op_regs, XHCI_OP_CA);
+	DUMP_REG(hc->op_regs, XHCI_OP_CRR);
+	DUMP_REG(hc->op_regs, XHCI_OP_CRCR_LO);
+	DUMP_REG(hc->op_regs, XHCI_OP_CRCR_HI);
+	DUMP_REG(hc->op_regs, XHCI_OP_DCBAAP_LO);
+	DUMP_REG(hc->op_regs, XHCI_OP_DCBAAP_HI);
+	DUMP_REG(hc->rt_regs, XHCI_RT_MFINDEX);
+
+	usb_log_debug("Interrupter 0 state:");
+	DUMP_REG(&hc->rt_regs->ir[0], XHCI_INTR_IP);
+	DUMP_REG(&hc->rt_regs->ir[0], XHCI_INTR_IE);
+	DUMP_REG(&hc->rt_regs->ir[0], XHCI_INTR_IMI);
+	DUMP_REG(&hc->rt_regs->ir[0], XHCI_INTR_IMC);
+	DUMP_REG(&hc->rt_regs->ir[0], XHCI_INTR_ERSTSZ);
+	DUMP_REG(&hc->rt_regs->ir[0], XHCI_INTR_ERSTBA_LO);
+	DUMP_REG(&hc->rt_regs->ir[0], XHCI_INTR_ERSTBA_HI);
+	DUMP_REG(&hc->rt_regs->ir[0], XHCI_INTR_ERDP_LO);
+	DUMP_REG(&hc->rt_regs->ir[0], XHCI_INTR_ERDP_HI);
+}
+
+/**
+ * Dump registers of all ports.
+ */
+void xhci_dump_ports(const xhci_hc_t *hc)
+{
+	const size_t num_ports = XHCI_REG_RD(hc->cap_regs, XHCI_CAP_MAX_PORTS);
+	for (size_t i = 0; i < num_ports; i++) {
+		usb_log_debug("Port %zu state:", i);
+
+		xhci_dump_port(&hc->op_regs->portrs[i]);
+	}
+}
+
+static const char *trb_types [] = {
+	[0] = "<empty>",
+#define TRB(t) [XHCI_TRB_TYPE_##t] = #t
+	TRB(NORMAL),
+	TRB(SETUP_STAGE),
+	TRB(DATA_STAGE),
+	TRB(STATUS_STAGE),
+	TRB(ISOCH),
+	TRB(LINK),
+	TRB(EVENT_DATA),
+	TRB(NO_OP),
+	TRB(ENABLE_SLOT_CMD),
+	TRB(DISABLE_SLOT_CMD),
+	TRB(ADDRESS_DEVICE_CMD),
+	TRB(CONFIGURE_ENDPOINT_CMD),
+	TRB(EVALUATE_CONTEXT_CMD),
+	TRB(RESET_ENDPOINT_CMD),
+	TRB(STOP_ENDPOINT_CMD),
+	TRB(SET_TR_DEQUEUE_POINTER_CMD),
+	TRB(RESET_DEVICE_CMD),
+	TRB(FORCE_EVENT_CMD),
+	TRB(NEGOTIATE_BANDWIDTH_CMD),
+	TRB(SET_LATENCY_TOLERANCE_VALUE_CMD),
+	TRB(GET_PORT_BANDWIDTH_CMD),
+	TRB(FORCE_HEADER_CMD),
+	TRB(NO_OP_CMD),
+	TRB(TRANSFER_EVENT),
+	TRB(COMMAND_COMPLETION_EVENT),
+	TRB(PORT_STATUS_CHANGE_EVENT),
+	TRB(BANDWIDTH_REQUEST_EVENT),
+	TRB(DOORBELL_EVENT),
+	TRB(HOST_CONTROLLER_EVENT),
+	TRB(DEVICE_NOTIFICATION_EVENT),
+	TRB(MFINDEX_WRAP_EVENT),
+#undef TRB
+	[XHCI_TRB_TYPE_MAX] = NULL,
+};
+
+/**
+ * Stringify XHCI_TRB_TYPE_*.
+ */
+const char *xhci_trb_str_type(unsigned type)
+{
+	static char type_buf [20];
+
+	if (type < XHCI_TRB_TYPE_MAX && trb_types[type] != NULL)
+		return trb_types[type];
+
+	snprintf(type_buf, sizeof(type_buf), "<unknown (%u)>", type);
+	return type_buf;
+}
+
+/**
+ * Dump a TRB.
+ */
+void xhci_dump_trb(const xhci_trb_t *trb)
+{
+	usb_log_debug("TRB(%p): type %s, cycle %u, status 0x%#08" PRIx32 ", "
+	    "parameter 0x%#016" PRIx64, trb, xhci_trb_str_type(TRB_TYPE(*trb)),
+	    TRB_CYCLE(*trb), trb->status, trb->parameter);
+}
+
+static const char *ec_ids [] = {
+	[0] = "<empty>",
+#define EC(t) [XHCI_EC_##t] = #t
+	EC(USB_LEGACY),
+	EC(SUPPORTED_PROTOCOL),
+	EC(EXTENDED_POWER_MANAGEMENT),
+	EC(IOV),
+	EC(MSI),
+	EC(LOCALMEM),
+	EC(DEBUG),
+	EC(MSIX),
+#undef EC
+	[XHCI_EC_MAX] = NULL
+};
+
+/**
+ * Dump Extended Capability ID.
+ */
+const char *xhci_ec_str_id(unsigned id)
+{
+	static char buf [20];
+
+	if (id < XHCI_EC_MAX && ec_ids[id] != NULL)
+		return ec_ids[id];
+
+	snprintf(buf, sizeof(buf), "<unknown (%u)>", id);
+	return buf;
+}
+
+/**
+ * Dump Protocol Speed ID.
+ */
+static void xhci_dump_psi(const xhci_psi_t *psi)
+{
+	static const char speed_exp [] = " KMG";
+	static const char *psi_types [] = { "", " rsvd", " RX", " TX" };
+	
+	usb_log_debug("Speed %u%s: %5u %cb/s, %s",
+	    XHCI_REG_RD(psi, XHCI_PSI_PSIV),
+	    psi_types[XHCI_REG_RD(psi, XHCI_PSI_PLT)],
+	    XHCI_REG_RD(psi, XHCI_PSI_PSIM),
+	    speed_exp[XHCI_REG_RD(psi, XHCI_PSI_PSIE)],
+	    XHCI_REG_RD(psi, XHCI_PSI_PFD) ? "full-duplex" : "");
+}
+
+/**
+ * Dump given Extended Capability.
+ */
+void xhci_dump_extcap(const xhci_extcap_t *ec)
+{
+	xhci_sp_name_t name;
+	unsigned ports_from, ports_to;
+
+	unsigned id = XHCI_REG_RD(ec, XHCI_EC_CAP_ID);
+	usb_log_debug("Extended capability %s", xhci_ec_str_id(id));
+
+	switch (id) {
+		case XHCI_EC_SUPPORTED_PROTOCOL:
+			name.packed = host2uint32_t_le(XHCI_REG_RD(ec, XHCI_EC_SP_NAME));
+			ports_from = XHCI_REG_RD(ec, XHCI_EC_SP_CP_OFF);
+			ports_to = ports_from + XHCI_REG_RD(ec, XHCI_EC_SP_CP_COUNT) - 1;
+			unsigned psic = XHCI_REG_RD(ec, XHCI_EC_SP_PSIC);
+
+			usb_log_debug("\tProtocol %.4s%u.%u, ports %u-%u, "
+			    "%u protocol speeds", name.str,
+			    XHCI_REG_RD(ec, XHCI_EC_SP_MAJOR),
+			    XHCI_REG_RD(ec, XHCI_EC_SP_MINOR),
+			    ports_from, ports_to, psic);
+
+			for (unsigned i = 0; i < psic; i++)
+				xhci_dump_psi(xhci_extcap_psi(ec, i));
+			break;
+	}
+}
+
+void xhci_dump_slot_ctx(const struct xhci_slot_ctx *ctx)
+{
+#define SLOT_DUMP(name)	usb_log_debug("\t" #name ":\t0x%x", XHCI_SLOT_##name(*ctx))
+	SLOT_DUMP(ROUTE_STRING);
+	SLOT_DUMP(SPEED);
+	SLOT_DUMP(MTT);
+	SLOT_DUMP(HUB);
+	SLOT_DUMP(CTX_ENTRIES);
+	SLOT_DUMP(MAX_EXIT_LATENCY);
+	SLOT_DUMP(ROOT_HUB_PORT);
+	SLOT_DUMP(NUM_PORTS);
+	SLOT_DUMP(TT_HUB_SLOT_ID);
+	SLOT_DUMP(TT_PORT_NUM);
+	SLOT_DUMP(TT_THINK_TIME);
+	SLOT_DUMP(INTERRUPTER);
+	SLOT_DUMP(DEVICE_ADDRESS);
+	SLOT_DUMP(STATE);
+#undef SLOT_DUMP
+}
+
+void xhci_dump_endpoint_ctx(const struct xhci_endpoint_ctx *ctx)
+{
+#define EP_DUMP_DW(name)	usb_log_debug("\t" #name ":\t0x%x", XHCI_EP_##name(*ctx))
+#define EP_DUMP_QW(name)	usb_log_debug("\t" #name ":\t0x%llx", XHCI_EP_##name(*ctx))
+	EP_DUMP_DW(STATE);
+	EP_DUMP_DW(MULT);
+	EP_DUMP_DW(MAX_P_STREAMS);
+	EP_DUMP_DW(LSA);
+	EP_DUMP_DW(INTERVAL);
+	EP_DUMP_DW(ERROR_COUNT);
+	EP_DUMP_DW(TYPE);
+	EP_DUMP_DW(HID);
+	EP_DUMP_DW(MAX_BURST_SIZE);
+	EP_DUMP_DW(MAX_PACKET_SIZE);
+	EP_DUMP_QW(DCS);
+	EP_DUMP_QW(TR_DPTR);
+	EP_DUMP_DW(MAX_ESIT_PAYLOAD_LO);
+	EP_DUMP_DW(MAX_ESIT_PAYLOAD_HI);
+#undef EP_DUMP_DW
+#undef EP_DUMP_QW
+}
+
+void xhci_dump_input_ctx(const xhci_hc_t * hc, const struct xhci_input_ctx *ictx)
+{
+	xhci_device_ctx_t *device_ctx = XHCI_GET_DEVICE_CTX(ictx, hc);
+	xhci_slot_ctx_t *slot_ctx = XHCI_GET_SLOT_CTX(device_ctx, hc);
+	xhci_input_ctrl_ctx_t *ctrl_ctx = XHCI_GET_CTRL_CTX(ictx, hc);
+
+	usb_log_debug("Input control context:");
+	usb_log_debug("\tDrop:\t0x%08x", xhci2host(32, ctrl_ctx->data[0]));
+	usb_log_debug("\tAdd:\t0x%08x", xhci2host(32, ctrl_ctx->data[1]));
+
+	usb_log_debug("\tConfig:\t0x%02x", XHCI_INPUT_CTRL_CTX_CONFIG_VALUE(*ctrl_ctx));
+	usb_log_debug("\tIface:\t0x%02x", XHCI_INPUT_CTRL_CTX_IFACE_NUMBER(*ctrl_ctx));
+	usb_log_debug("\tAlternate:\t0x%02x", XHCI_INPUT_CTRL_CTX_ALTER_SETTING(*ctrl_ctx));
+
+	usb_log_debug("Slot context:");
+	xhci_dump_slot_ctx(slot_ctx);
+
+	for (uint8_t dci = 1; dci <= XHCI_EP_COUNT; dci++)
+		if (XHCI_INPUT_CTRL_CTX_DROP(*ctrl_ctx, dci)
+		    || XHCI_INPUT_CTRL_CTX_ADD(*ctrl_ctx, dci)) {
+			usb_log_debug("Endpoint context DCI %u:", dci);
+			xhci_ep_ctx_t *ep_ctx = XHCI_GET_EP_CTX(device_ctx, hc, dci);
+			xhci_dump_endpoint_ctx(ep_ctx);
+		}
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/debug.h
===================================================================
--- uspace/drv/bus/usb/xhci/debug.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/debug.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2017 Ondrej Hlavaty
+ * 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.
+ * 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
+ *
+ * Utility functions for debugging and logging purposes.
+ */
+
+#ifndef XHCI_DEBUG_H
+#define XHCI_DEBUG_H
+
+/**
+ * As the debug header is likely to be included in every file, avoid including
+ * all headers of xhci to support "include what you use".
+ */
+struct xhci_hc;
+struct xhci_cap_regs;
+struct xhci_port_regs;
+struct xhci_trb;
+struct xhci_extcap;
+struct xhci_slot_ctx;
+struct xhci_endpoint_ctx;
+struct xhci_input_ctx;
+
+extern void xhci_dump_cap_regs(const struct xhci_cap_regs *);
+extern void xhci_dump_port(const struct xhci_port_regs *);
+extern void xhci_dump_state(const struct xhci_hc *);
+extern void xhci_dump_ports(const struct xhci_hc *);
+
+extern const char *xhci_trb_str_type(unsigned);
+extern void xhci_dump_trb(const struct xhci_trb *trb);
+
+extern const char *xhci_ec_str_id(unsigned);
+extern void xhci_dump_extcap(const struct xhci_extcap *);
+
+extern void xhci_dump_slot_ctx(const struct xhci_slot_ctx *);
+extern void xhci_dump_endpoint_ctx(const struct xhci_endpoint_ctx *);
+extern void xhci_dump_input_ctx(const struct xhci_hc *, const struct xhci_input_ctx *);
+
+#endif
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/device.c
===================================================================
--- uspace/drv/bus/usb/xhci/device.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/device.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,365 @@
+/*
+ * Copyright (c) 2017 Ondrej Hlavaty <aearsis@eideo.cz>
+ * 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 libusbhost
+ * @{
+ */
+/** @file
+ * HC Endpoint management.
+ */
+
+#include <usb/host/utility.h>
+#include <usb/host/ddf_helpers.h>
+#include <usb/host/endpoint.h>
+#include <usb/host/hcd.h>
+#include <usb/host/utility.h>
+#include <usb/classes/classes.h>
+#include <usb/classes/hub.h>
+#include <usb/descriptor.h>
+#include <usb/debug.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <str_error.h>
+#include <macros.h>
+#include <stdbool.h>
+
+#include "hc.h"
+#include "bus.h"
+#include "endpoint.h"
+#include "hw_struct/context.h"
+
+#include "device.h"
+
+/**
+ * Initial descriptor used for control endpoint 0,
+ * before more configuration is retrieved.
+ */
+static const usb_endpoint_descriptors_t ep0_initial_desc = {
+	.endpoint.max_packet_size = CTRL_PIPE_MIN_PACKET_SIZE,
+};
+
+/**
+ * Assign address and control endpoint to a new XHCI device. Once this function
+ * successfully returns, the device is online.
+ *
+ * @param[in] bus XHCI bus, in which the address is assigned.
+ * @param[in] dev New device to address and configure./e
+ * @return Error code.
+ */
+static int address_device(xhci_device_t *dev)
+{
+	int err;
+
+	/* Enable new slot. */
+	if ((err = hc_enable_slot(dev)) != EOK)
+		return err;
+	usb_log_debug("Obtained slot ID: %u.", dev->slot_id);
+
+	/* Temporary reference */
+	endpoint_t *ep0_base;
+	if ((err = bus_endpoint_add(&dev->base, &ep0_initial_desc, &ep0_base)))
+		goto err_slot;
+
+	usb_log_debug("Looking up new device initial MPS: %s",
+	    usb_str_speed(dev->base.speed));
+	ep0_base->max_packet_size = hc_get_ep0_initial_mps(dev->base.speed);
+
+	/* Address device */
+	if ((err = hc_address_device(dev)))
+		goto err_added;
+
+	/* Temporary reference */
+	endpoint_del_ref(ep0_base);
+
+	return EOK;
+
+err_added:
+	bus_endpoint_remove(ep0_base);
+	/* Temporary reference */
+	endpoint_del_ref(ep0_base);
+err_slot:
+	hc_disable_slot(dev);
+	return err;
+}
+
+/**
+ * Retrieve and set maximum packet size for endpoint zero of a XHCI device.
+ *
+ * @param[in] hc Host controller, which manages the device.
+ * @param[in] dev Device with operational endpoint zero.
+ * @return Error code.
+ */
+static int setup_ep0_packet_size(xhci_hc_t *hc, xhci_device_t *dev)
+{
+	int err;
+
+	uint16_t max_packet_size;
+	if ((err = hc_get_ep0_max_packet_size(&max_packet_size, &dev->base)))
+		return err;
+
+	xhci_endpoint_t *ep0 = xhci_endpoint_get(dev->base.endpoints[0]);
+	assert(ep0);
+
+	if (ep0->base.max_packet_size == max_packet_size)
+		return EOK;
+
+	ep0->base.max_packet_size = max_packet_size;
+	ep0->base.max_transfer_size = max_packet_size * ep0->base.packets_per_uframe;
+
+	if ((err = hc_update_endpoint(ep0)))
+		return err;
+
+	return EOK;
+}
+
+/**
+ * Check whether the device is a hub and if so, fill its characterstics.
+ *
+ * If this fails, it does not necessarily mean the device is unusable.
+ * Just the TT will not work correctly.
+ */
+static int setup_hub(xhci_device_t *dev, usb_standard_device_descriptor_t *desc)
+{
+	if (desc->device_class != USB_CLASS_HUB)
+		return EOK;
+
+	usb_hub_descriptor_header_t hub_desc = { 0 };
+	const int err = hc_get_hub_desc(&dev->base, &hub_desc);
+	if (err)
+		return err;
+
+	dev->is_hub = 1;
+	dev->num_ports = hub_desc.port_count;
+
+	if (dev->base.speed == USB_SPEED_HIGH) {
+		dev->tt_think_time = 8 +
+			8  * !!(hub_desc.characteristics & HUB_CHAR_TT_THINK_8) +
+			16 * !!(hub_desc.characteristics & HUB_CHAR_TT_THINK_16);
+	}
+
+	usb_log_debug("Device(%u): recognised USB hub with %u ports",
+	    dev->base.address, dev->num_ports);
+	return EOK;
+}
+
+/**
+ * Respond to a new device on the XHCI bus. Address it, negotiate packet size
+ * and retrieve USB descriptors.
+ *
+ * @param[in] bus XHCI bus, where the new device emerged.
+ * @param[in] dev XHCI device, which has appeared on the bus.
+ *
+ * @return Error code.
+ */
+int xhci_device_enumerate(device_t *dev)
+{
+	int err;
+	xhci_bus_t *bus = bus_to_xhci_bus(dev->bus);
+	xhci_device_t *xhci_dev = xhci_device_get(dev);
+
+	/* Calculate route string */
+	xhci_device_t *xhci_hub = xhci_device_get(dev->hub);
+	xhci_dev->route_str = xhci_hub->route_str;
+
+	/* Roothub port is not part of the route string */
+	if (dev->tier >= 2) {
+		const unsigned offset = 4 * (dev->tier - 2);
+		xhci_dev->route_str |= (dev->port & 0xf) << offset;
+		xhci_dev->rh_port = xhci_hub->rh_port;
+	}
+
+	int retries = 3;
+	do {
+		/* Assign an address to the device */
+		err = address_device(xhci_dev);
+	} while (err == ESTALL && --retries > 0);
+
+	if (err) {
+		usb_log_error("Failed to setup address of the new device: %s",
+		    str_error(err));
+		return err;
+	}
+
+	/* Setup EP0 might already need to issue a transfer. */
+	fibril_mutex_lock(&bus->base.guard);
+	assert(bus->devices_by_slot[xhci_dev->slot_id] == NULL);
+	bus->devices_by_slot[xhci_dev->slot_id] = xhci_dev;
+	fibril_mutex_unlock(&bus->base.guard);
+
+	if ((err = setup_ep0_packet_size(bus->hc, xhci_dev))) {
+		usb_log_error("Failed to setup control endpoint "
+		    "of the new device: %s", str_error(err));
+		goto err_address;
+	}
+
+	usb_standard_device_descriptor_t desc = { 0 };
+
+	if ((err = hc_get_device_desc(dev, &desc))) {
+		usb_log_error("Device(%d): failed to get device "
+		   "descriptor: %s", dev->address, str_error(err));
+		goto err_address;
+	}
+
+	if ((err = setup_hub(xhci_dev, &desc)))
+		usb_log_warning("Device(%d): failed to setup hub "
+		    "characteristics: %s.  Continuing anyway.",
+		    dev->address, str_error(err));
+
+	if ((err = hcd_ddf_setup_match_ids(dev, &desc))) {
+		usb_log_error("Device(%d): failed to setup match IDs: %s",
+		    dev->address, str_error(err));
+		goto err_address;
+	}
+
+	return EOK;
+
+err_address:
+	bus_endpoint_remove(xhci_dev->base.endpoints[0]);
+	bus->devices_by_slot[xhci_dev->slot_id] = NULL;
+	hc_disable_slot(xhci_dev);
+	return err;
+}
+
+/**
+ * Remove device from XHCI bus. Transition it to the offline state, abort all
+ * ongoing transfers and unregister all of its endpoints.
+ *
+ * Bus callback.
+ *
+ * @param[in] bus XHCI bus, from which the device is removed.
+ * @param[in] dev XHCI device, which is removed from the bus.
+ * @return Error code.
+ */
+void xhci_device_gone(device_t *dev)
+{
+	int err;
+	xhci_bus_t *bus = bus_to_xhci_bus(dev->bus);
+	xhci_device_t *xhci_dev = xhci_device_get(dev);
+
+	/* Disable the slot, dropping all endpoints. */
+	const uint32_t slot_id = xhci_dev->slot_id;
+	if ((err = hc_disable_slot(xhci_dev))) {
+		usb_log_warning("Failed to disable slot of device " XHCI_DEV_FMT
+		    ": %s", XHCI_DEV_ARGS(*xhci_dev), str_error(err));
+	}
+
+	bus->devices_by_slot[slot_id] = NULL;
+}
+
+/**
+ * Reverts things device_offline did, getting the device back up.
+ *
+ * Bus callback.
+ */
+int xhci_device_online(device_t *dev_base)
+{
+	int err;
+
+	xhci_bus_t *bus = bus_to_xhci_bus(dev_base->bus);
+	assert(bus);
+
+	xhci_device_t *dev = xhci_device_get(dev_base);
+	assert(dev);
+
+	/* Transition the device from the Addressed to the Configured state. */
+	if ((err = hc_configure_device(dev))) {
+		usb_log_warning("Failed to configure device " XHCI_DEV_FMT ".",
+		    XHCI_DEV_ARGS(*dev));
+		return err;
+	}
+
+	return EOK;
+}
+
+/**
+ * Make given device offline. Offline the DDF function, tear down all
+ * endpoints, issue Deconfigure Device command to xHC.
+ *
+ * Bus callback.
+ */
+void xhci_device_offline(device_t *dev_base)
+{
+	int err;
+
+	xhci_bus_t *bus = bus_to_xhci_bus(dev_base->bus);
+	assert(bus);
+
+	xhci_device_t *dev = xhci_device_get(dev_base);
+	assert(dev);
+
+	/* Issue one HC command to simultaneously drop all endpoints except zero. */
+	if ((err = hc_deconfigure_device(dev))) {
+		usb_log_warning("Failed to deconfigure device "
+		    XHCI_DEV_FMT ".", XHCI_DEV_ARGS(*dev));
+	}
+}
+
+/**
+ * Fill a slot context that is part of an Input Context with appropriate
+ * values.
+ *
+ * @param ctx Slot context, zeroed out.
+ */
+void xhci_setup_slot_context(xhci_device_t *dev, xhci_slot_ctx_t *ctx)
+{
+	/* Initialize slot_ctx according to section 4.3.3 point 3. */
+	XHCI_SLOT_ROOT_HUB_PORT_SET(*ctx, dev->rh_port);
+	XHCI_SLOT_ROUTE_STRING_SET(*ctx, dev->route_str);
+	XHCI_SLOT_SPEED_SET(*ctx, hc_speed_to_psiv(dev->base.speed));
+
+	/*
+	 * Note: This function is used even before this flag can be set, to
+	 *       issue the address device command. It is OK, because these
+	 *       flags are not required to be valid for that command.
+	 */
+	if (dev->is_hub) {
+		XHCI_SLOT_HUB_SET(*ctx, 1);
+		XHCI_SLOT_NUM_PORTS_SET(*ctx, dev->num_ports);
+		XHCI_SLOT_TT_THINK_TIME_SET(*ctx, dev->tt_think_time);
+		XHCI_SLOT_MTT_SET(*ctx, 0); // MTT not supported yet
+	}
+
+	/* Setup Transaction Translation. TODO: Test this with HS hub. */
+	if (dev->base.tt.dev != NULL) {
+		xhci_device_t *hub = xhci_device_get(dev->base.tt.dev);
+		XHCI_SLOT_TT_HUB_SLOT_ID_SET(*ctx, hub->slot_id);
+		XHCI_SLOT_TT_HUB_PORT_SET(*ctx, dev->base.tt.port);
+	}
+
+	/*
+	 * As we always allocate space for whole input context, we can set this
+	 * to maximum. The only exception being Address Device command, which
+	 * explicitly requires this to be se to 1.
+	 */
+	XHCI_SLOT_CTX_ENTRIES_SET(*ctx, 31);
+}
+
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/device.h
===================================================================
--- uspace/drv/bus/usb/xhci/device.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/device.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2017 Ondrej Hlavaty <aearsis@eideo.cz>
+ * 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 libusbhost
+ * @{
+ */
+/** @file
+ *
+ */
+#ifndef XHCI_DEVICE_H
+#define XHCI_DEVICE_H
+
+#include <usb/host/bus.h>
+#include <usb/host/dma_buffer.h>
+
+typedef struct xhci_slot_ctx xhci_slot_ctx_t;
+
+typedef struct xhci_device {
+	device_t base;		/**< Inheritance. Keep this first. */
+
+	/** Slot ID assigned to the device by xHC. */
+	uint32_t slot_id;
+
+	/** Corresponding port on RH */
+	uint8_t rh_port;
+
+	/** Route string */
+	uint32_t route_str;
+
+	/** Place to store the allocated context */
+	dma_buffer_t dev_ctx;
+
+	/** Hub specific information. Valid only if the device is_hub. */
+	bool is_hub;
+	uint8_t num_ports;
+	uint8_t tt_think_time;
+} xhci_device_t;
+
+#define XHCI_DEV_FMT  "(%s, slot %d)"
+#define XHCI_DEV_ARGS(dev)		 ddf_fun_get_name((dev).base.fun), (dev).slot_id
+
+/* Bus callbacks */
+int xhci_device_enumerate(device_t *);
+void xhci_device_offline(device_t *);
+int xhci_device_online(device_t *);
+void xhci_device_gone(device_t *);
+
+void xhci_setup_slot_context(xhci_device_t *, xhci_slot_ctx_t *);
+
+static inline xhci_device_t * xhci_device_get(device_t *dev)
+{
+	assert(dev);
+	return (xhci_device_t *) dev;
+}
+
+#endif
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/endpoint.c
===================================================================
--- uspace/drv/bus/usb/xhci/endpoint.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/endpoint.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,471 @@
+/*
+ * Copyright (c) 2017 Petr Manek
+ * 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 <usb/host/endpoint.h>
+#include <usb/descriptor.h>
+
+#include <errno.h>
+#include <macros.h>
+#include <str_error.h>
+
+#include "hc.h"
+#include "bus.h"
+#include "commands.h"
+#include "device.h"
+#include "endpoint.h"
+#include "streams.h"
+
+static int alloc_transfer_ds(xhci_endpoint_t *);
+
+/**
+ * Initialize new XHCI endpoint.
+ * @param[in] xhci_ep Allocated XHCI endpoint to initialize.
+ * @param[in] dev Device, to which the endpoint belongs.
+ * @param[in] desc USB endpoint descriptor carrying configuration data.
+ *
+ * @return Error code.
+ */
+static int xhci_endpoint_init(xhci_endpoint_t *xhci_ep, device_t *dev,
+    const usb_endpoint_descriptors_t *desc)
+{
+	int rc;
+	assert(xhci_ep);
+
+	endpoint_t *ep = &xhci_ep->base;
+
+	endpoint_init(ep, dev, desc);
+
+	fibril_mutex_initialize(&xhci_ep->guard);
+
+	xhci_ep->max_burst = desc->companion.max_burst + 1;
+
+	if (ep->transfer_type == USB_TRANSFER_BULK)
+		xhci_ep->max_streams = 1 << (USB_SSC_MAX_STREAMS(desc->companion));
+	else
+		xhci_ep->max_streams = 1;
+
+	if (ep->transfer_type == USB_TRANSFER_ISOCHRONOUS)
+		xhci_ep->mult = USB_SSC_MULT(desc->companion) + 1;
+	else
+		xhci_ep->mult = 1;
+
+	/*
+	 * In USB 3, the semantics of wMaxPacketSize changed. Now the number of
+	 * packets per service interval is determined from max_burst and mult.
+	 */
+	if (dev->speed >= USB_SPEED_SUPER) {
+		ep->packets_per_uframe = xhci_ep->max_burst * xhci_ep->mult;
+		ep->max_transfer_size = ep->max_packet_size * ep->packets_per_uframe;
+	}
+
+	xhci_ep->interval = desc->endpoint.poll_interval;
+
+	/*
+	 * Only Low/Full speed interrupt endpoints have interval as a linear field,
+	 * others have 2-based log of it.
+	 */
+	if (dev->speed >= USB_SPEED_HIGH
+	    || ep->transfer_type != USB_TRANSFER_INTERRUPT) {
+		xhci_ep->interval = 1 << (xhci_ep->interval - 1);
+	}
+
+	/* Full speed devices have interval in frames */
+	if (dev->speed <= USB_SPEED_FULL) {
+		xhci_ep->interval *= 8;
+	}
+
+	if (xhci_ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS)
+		isoch_init(xhci_ep, desc);
+
+	if ((rc = alloc_transfer_ds(xhci_ep)))
+		goto err;
+
+	return EOK;
+
+err:
+	return rc;
+}
+
+/**
+ * Create a new xHCI endpoint structure.
+ *
+ * Bus callback.
+ */
+endpoint_t *xhci_endpoint_create(device_t *dev,
+    const usb_endpoint_descriptors_t *desc)
+{
+	const usb_transfer_type_t type = USB_ED_GET_TRANSFER_TYPE(desc->endpoint);
+
+	xhci_endpoint_t *ep = calloc(1, sizeof(xhci_endpoint_t)
+		+ (type == USB_TRANSFER_ISOCHRONOUS) * sizeof(*ep->isoch));
+	if (!ep)
+		return NULL;
+
+	if (xhci_endpoint_init(ep, dev, desc)) {
+		free(ep);
+		return NULL;
+	}
+
+	return &ep->base;
+}
+
+/**
+ * Finalize XHCI endpoint.
+ * @param[in] xhci_ep XHCI endpoint to finalize.
+ */
+static void xhci_endpoint_fini(xhci_endpoint_t *xhci_ep)
+{
+	assert(xhci_ep);
+
+	xhci_endpoint_free_transfer_ds(xhci_ep);
+
+	// TODO: Something missed?
+}
+
+/**
+ * Destroy given xHCI endpoint structure.
+ *
+ * Bus callback.
+ */
+void xhci_endpoint_destroy(endpoint_t *ep)
+{
+	xhci_endpoint_t *xhci_ep = xhci_endpoint_get(ep);
+
+	xhci_endpoint_fini(xhci_ep);
+	free(xhci_ep);
+}
+
+
+/**
+ * Register an andpoint to the xHC.
+ *
+ * Bus callback.
+ */
+int xhci_endpoint_register(endpoint_t *ep_base)
+{
+	int err;
+	xhci_endpoint_t *ep = xhci_endpoint_get(ep_base);
+
+	if (ep_base->endpoint != 0 && (err = hc_add_endpoint(ep)))
+		return err;
+
+	endpoint_set_online(ep_base, &ep->guard);
+	return EOK;
+}
+
+/**
+ * Abort a transfer on an endpoint.
+ */
+static void endpoint_abort(endpoint_t *ep)
+{
+	xhci_device_t *dev = xhci_device_get(ep->device);
+	xhci_endpoint_t *xhci_ep = xhci_endpoint_get(ep);
+
+	/* This function can only abort endpoints without streams. */
+	assert(xhci_ep->primary_stream_data_array == NULL);
+
+	fibril_mutex_lock(&xhci_ep->guard);
+
+	endpoint_set_offline_locked(ep);
+
+	if (!ep->active_batch) {
+		fibril_mutex_unlock(&xhci_ep->guard);
+		return;
+	}
+
+	/* First, offer the batch a short chance to be finished. */
+	endpoint_wait_timeout_locked(ep, 10000);
+
+	if (!ep->active_batch) {
+		fibril_mutex_unlock(&xhci_ep->guard);
+		return;
+	}
+
+	usb_transfer_batch_t * const batch = ep->active_batch;
+
+	const int err = hc_stop_endpoint(xhci_ep);
+	if (err) {
+		usb_log_error("Failed to stop endpoint %u of device "
+		    XHCI_DEV_FMT ": %s", ep->endpoint, XHCI_DEV_ARGS(*dev),
+		    str_error(err));
+	}
+
+	fibril_mutex_unlock(&xhci_ep->guard);
+
+	batch->error = EINTR;
+	batch->transferred_size = 0;
+	usb_transfer_batch_finish(batch);
+	return;
+}
+
+/**
+ * Unregister an endpoint. If the device is still available, inform the xHC
+ * about it.
+ *
+ * Bus callback.
+ */
+void xhci_endpoint_unregister(endpoint_t *ep_base)
+{
+	int err;
+	xhci_endpoint_t *ep = xhci_endpoint_get(ep_base);
+	xhci_device_t *dev = xhci_device_get(ep_base->device);
+
+	endpoint_abort(ep_base);
+
+	/* If device slot is still available, drop the endpoint. */
+	if (ep_base->endpoint != 0 && dev->slot_id) {
+
+		if ((err = hc_drop_endpoint(ep))) {
+			usb_log_error("Failed to drop endpoint " XHCI_EP_FMT ": %s",
+			    XHCI_EP_ARGS(*ep), str_error(err));
+		}
+	} else {
+		usb_log_debug("Not going to drop endpoint " XHCI_EP_FMT " because"
+		    " the slot has already been disabled.", XHCI_EP_ARGS(*ep));
+	}
+}
+
+/**
+ * Determine the type of a XHCI endpoint.
+ * @param[in] ep XHCI endpoint to query.
+ *
+ * @return EP_TYPE_[CONTROL|ISOCH|BULK|INTERRUPT]_[IN|OUT]
+ */
+int xhci_endpoint_type(xhci_endpoint_t *ep)
+{
+	const bool in = ep->base.direction == USB_DIRECTION_IN;
+
+	switch (ep->base.transfer_type) {
+	case USB_TRANSFER_CONTROL:
+		return EP_TYPE_CONTROL;
+
+	case USB_TRANSFER_ISOCHRONOUS:
+		return in ? EP_TYPE_ISOCH_IN
+			  : EP_TYPE_ISOCH_OUT;
+
+	case USB_TRANSFER_BULK:
+		return in ? EP_TYPE_BULK_IN
+			  : EP_TYPE_BULK_OUT;
+
+	case USB_TRANSFER_INTERRUPT:
+		return in ? EP_TYPE_INTERRUPT_IN
+			  : EP_TYPE_INTERRUPT_OUT;
+	}
+
+	return EP_TYPE_INVALID;
+}
+
+/**
+ * Allocate transfer data structures for XHCI endpoint not using streams.
+ * @param[in] xhci_ep XHCI endpoint to allocate data structures for.
+ *
+ * @return Error code.
+ */
+static int alloc_transfer_ds(xhci_endpoint_t *xhci_ep)
+{
+	/* Can't use XHCI_EP_FMT because the endpoint may not have device. */
+	usb_log_debug("Allocating main transfer ring for endpoint " XHCI_EP_FMT,
+	    XHCI_EP_ARGS(*xhci_ep));
+
+	xhci_ep->primary_stream_data_array = NULL;
+	xhci_ep->primary_stream_data_size = 0;
+
+	int err;
+	if ((err = xhci_trb_ring_init(&xhci_ep->ring, 0))) {
+		return err;
+	}
+
+	if (xhci_ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS) {
+		if ((err = isoch_alloc_transfers(xhci_ep))) {
+			xhci_trb_ring_fini(&xhci_ep->ring);
+			return err;
+		}
+	}
+
+	return EOK;
+}
+
+/**
+ * Free transfer data structures for XHCI endpoint.
+ * @param[in] xhci_ep XHCI endpoint to free data structures for.
+ */
+void xhci_endpoint_free_transfer_ds(xhci_endpoint_t *xhci_ep)
+{
+	if (xhci_ep->primary_stream_data_size) {
+		xhci_stream_free_ds(xhci_ep);
+	} else {
+		usb_log_debug("Freeing main transfer ring of endpoint " XHCI_EP_FMT,
+		    XHCI_EP_ARGS(*xhci_ep));
+		xhci_trb_ring_fini(&xhci_ep->ring);
+	}
+
+	if (xhci_ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS)
+		isoch_fini(xhci_ep);
+}
+
+xhci_trb_ring_t *xhci_endpoint_get_ring(xhci_endpoint_t *ep, uint32_t stream_id)
+{
+	if (ep->primary_stream_data_size == 0)
+		return stream_id == 0 ? &ep->ring : NULL;
+
+	xhci_stream_data_t *stream_data = xhci_get_stream_ctx_data(ep, stream_id);
+	if (stream_data == NULL) {
+		usb_log_warning("No transfer ring was found for stream %u.", stream_id);
+		return NULL;
+	}
+
+	return &stream_data->ring;
+}
+
+/**
+ * Configure endpoint context of a control endpoint.
+ * @param[in] ep XHCI control endpoint.
+ * @param[in] ctx Endpoint context to configure.
+ */
+static void setup_control_ep_ctx(xhci_endpoint_t *ep, xhci_ep_ctx_t *ctx)
+{
+	XHCI_EP_TYPE_SET(*ctx, xhci_endpoint_type(ep));
+	XHCI_EP_MAX_PACKET_SIZE_SET(*ctx, ep->base.max_packet_size);
+	XHCI_EP_MAX_BURST_SIZE_SET(*ctx, ep->max_burst - 1);
+	XHCI_EP_MULT_SET(*ctx, ep->mult - 1);
+	XHCI_EP_ERROR_COUNT_SET(*ctx, 3);
+	XHCI_EP_TR_DPTR_SET(*ctx, ep->ring.dequeue);
+	XHCI_EP_DCS_SET(*ctx, 1);
+}
+
+/**
+ * Configure endpoint context of a bulk endpoint.
+ * @param[in] ep XHCI bulk endpoint.
+ * @param[in] ctx Endpoint context to configure.
+ */
+static void setup_bulk_ep_ctx(xhci_endpoint_t *ep, xhci_ep_ctx_t *ctx)
+{
+	XHCI_EP_TYPE_SET(*ctx, xhci_endpoint_type(ep));
+	XHCI_EP_MAX_PACKET_SIZE_SET(*ctx, ep->base.max_packet_size);
+	XHCI_EP_MAX_BURST_SIZE_SET(*ctx, ep->max_burst - 1);
+	XHCI_EP_ERROR_COUNT_SET(*ctx, 3);
+
+	XHCI_EP_MAX_P_STREAMS_SET(*ctx, 0);
+	XHCI_EP_TR_DPTR_SET(*ctx, ep->ring.dequeue);
+	XHCI_EP_DCS_SET(*ctx, 1);
+}
+
+/**
+ * Configure endpoint context of a isochronous endpoint.
+ * @param[in] ep XHCI isochronous endpoint.
+ * @param[in] ctx Endpoint context to configure.
+ */
+static void setup_isoch_ep_ctx(xhci_endpoint_t *ep, xhci_ep_ctx_t *ctx)
+{
+	XHCI_EP_TYPE_SET(*ctx, xhci_endpoint_type(ep));
+	XHCI_EP_MAX_PACKET_SIZE_SET(*ctx, ep->base.max_packet_size & 0x07FF);
+	XHCI_EP_MAX_BURST_SIZE_SET(*ctx, ep->max_burst - 1);
+	XHCI_EP_MULT_SET(*ctx, ep->mult - 1);
+	XHCI_EP_ERROR_COUNT_SET(*ctx, 0);
+	XHCI_EP_TR_DPTR_SET(*ctx, ep->ring.dequeue);
+	XHCI_EP_DCS_SET(*ctx, 1);
+	XHCI_EP_INTERVAL_SET(*ctx, fnzb32(ep->interval) % 32);
+
+	XHCI_EP_MAX_ESIT_PAYLOAD_LO_SET(*ctx, ep->isoch->max_size & 0xFFFF);
+	XHCI_EP_MAX_ESIT_PAYLOAD_HI_SET(*ctx, (ep->isoch->max_size >> 16) & 0xFF);
+}
+
+/**
+ * Configure endpoint context of a interrupt endpoint.
+ * @param[in] ep XHCI interrupt endpoint.
+ * @param[in] ctx Endpoint context to configure.
+ */
+static void setup_interrupt_ep_ctx(xhci_endpoint_t *ep, xhci_ep_ctx_t *ctx)
+{
+	XHCI_EP_TYPE_SET(*ctx, xhci_endpoint_type(ep));
+	XHCI_EP_MAX_PACKET_SIZE_SET(*ctx, ep->base.max_packet_size & 0x07FF);
+	XHCI_EP_MAX_BURST_SIZE_SET(*ctx, ep->max_burst - 1);
+	XHCI_EP_MULT_SET(*ctx, 0);
+	XHCI_EP_ERROR_COUNT_SET(*ctx, 3);
+	XHCI_EP_TR_DPTR_SET(*ctx, ep->ring.dequeue);
+	XHCI_EP_DCS_SET(*ctx, 1);
+	XHCI_EP_INTERVAL_SET(*ctx, fnzb32(ep->interval) % 32);
+	// TODO: max ESIT payload
+}
+
+/** Type of endpoint context configuration function. */
+typedef void (*setup_ep_ctx_helper)(xhci_endpoint_t *, xhci_ep_ctx_t *);
+
+/**
+ * Static array, which maps USB endpoint types to their respective endpoint
+ * context configuration functions.
+ */
+static const setup_ep_ctx_helper setup_ep_ctx_helpers[] = {
+	[USB_TRANSFER_CONTROL] = setup_control_ep_ctx,
+	[USB_TRANSFER_ISOCHRONOUS] = setup_isoch_ep_ctx,
+	[USB_TRANSFER_BULK] = setup_bulk_ep_ctx,
+	[USB_TRANSFER_INTERRUPT] = setup_interrupt_ep_ctx,
+};
+
+/** Configure endpoint context of XHCI endpoint.
+ * @param[in] ep Associated XHCI endpoint.
+ * @param[in] ep_ctx Endpoint context to configure.
+ */
+void xhci_setup_endpoint_context(xhci_endpoint_t *ep, xhci_ep_ctx_t *ep_ctx)
+{
+	assert(ep);
+	assert(ep_ctx);
+
+	usb_transfer_type_t tt = ep->base.transfer_type;
+
+	memset(ep_ctx, 0, sizeof(*ep_ctx));
+	setup_ep_ctx_helpers[tt](ep, ep_ctx);
+}
+
+/**
+ * Clear endpoint halt condition by resetting the endpoint and skipping the
+ * offending transfer.
+ */
+int xhci_endpoint_clear_halt(xhci_endpoint_t *ep, uint32_t stream_id)
+{
+	int err;
+
+	if ((err = hc_reset_endpoint(ep)))
+		return err;
+
+	if ((err = hc_reset_ring(ep, stream_id)))
+		return err;
+
+	return EOK;
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/endpoint.h
===================================================================
--- uspace/drv/bus/usb/xhci/endpoint.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/endpoint.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2017 Petr Manek
+ * 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.
+ */
+
+#ifndef XHCI_ENDPOINT_H
+#define XHCI_ENDPOINT_H
+
+#include <assert.h>
+
+#include <usb/debug.h>
+#include <usb/host/dma_buffer.h>
+#include <usb/host/endpoint.h>
+#include <usb/host/hcd.h>
+#include <ddf/driver.h>
+
+#include "device.h"
+#include "isoch.h"
+#include "transfers.h"
+#include "trb_ring.h"
+
+typedef struct xhci_device xhci_device_t;
+typedef struct xhci_endpoint xhci_endpoint_t;
+typedef struct xhci_stream_data xhci_stream_data_t;
+typedef struct xhci_bus xhci_bus_t;
+
+enum {
+	EP_TYPE_INVALID = 0,
+	EP_TYPE_ISOCH_OUT = 1,
+	EP_TYPE_BULK_OUT = 2,
+	EP_TYPE_INTERRUPT_OUT = 3,
+	EP_TYPE_CONTROL = 4,
+	EP_TYPE_ISOCH_IN = 5,
+	EP_TYPE_BULK_IN = 6,
+	EP_TYPE_INTERRUPT_IN = 7
+};
+
+/** Connector structure linking endpoint context to the endpoint. */
+typedef struct xhci_endpoint {
+	endpoint_t base;	/**< Inheritance. Keep this first. */
+
+	/** Guarding scheduling of this endpoint. */
+	fibril_mutex_t guard;
+
+	/** Main transfer ring (unused if streams are enabled) */
+	xhci_trb_ring_t ring;
+
+	/** Primary stream context data array (or NULL if endpoint doesn't use streams). */
+	xhci_stream_data_t *primary_stream_data_array;
+
+	/** Primary stream context array - allocated for xHC hardware. */
+	xhci_stream_ctx_t *primary_stream_ctx_array;
+	dma_buffer_t primary_stream_ctx_dma;
+
+	/** Size of the allocated primary stream data array (and context array). */
+	uint16_t primary_stream_data_size;
+
+	/* Maximum number of primary streams (0 - 2^16). */
+	uint32_t max_streams;
+
+	/** Maximum number of consecutive USB transactions (0-15) that should be executed per scheduling opportunity */
+	uint8_t max_burst;
+
+	/** Maximum number of bursts within an interval that this endpoint supports */
+	uint8_t mult;
+
+	/** Scheduling interval for periodic endpoints, as a number of 125us units. (0 - 2^16) */
+	uint32_t interval;
+
+	/** This field is a valid pointer for (and only for) isochronous transfers. */
+	xhci_isoch_t isoch [0];
+} xhci_endpoint_t;
+
+#define XHCI_EP_FMT  "(%d:%d %s)"
+/* FIXME: "Device -1" messes up log messages, figure out a better way. */
+#define XHCI_EP_ARGS(ep)		\
+	((ep).base.device ? (ep).base.device->address : -1),	\
+	((ep).base.endpoint),		\
+	(usb_str_transfer_type((ep).base.transfer_type))
+
+extern int xhci_endpoint_type(xhci_endpoint_t *ep);
+
+extern endpoint_t *xhci_endpoint_create(device_t *, const usb_endpoint_descriptors_t *);
+extern int xhci_endpoint_register(endpoint_t *);
+extern void xhci_endpoint_unregister(endpoint_t *);
+extern void xhci_endpoint_destroy(endpoint_t *);
+
+extern void xhci_endpoint_free_transfer_ds(xhci_endpoint_t *);
+extern xhci_trb_ring_t *xhci_endpoint_get_ring(xhci_endpoint_t *, uint32_t);
+
+extern void xhci_setup_endpoint_context(xhci_endpoint_t *, xhci_ep_ctx_t *);
+extern int xhci_endpoint_clear_halt(xhci_endpoint_t *, unsigned);
+
+static inline xhci_endpoint_t * xhci_endpoint_get(endpoint_t *ep)
+{
+	assert(ep);
+	return (xhci_endpoint_t *) ep;
+}
+
+static inline xhci_device_t * xhci_ep_to_dev(xhci_endpoint_t *ep)
+{
+	assert(ep);
+	return xhci_device_get(ep->base.device);
+}
+
+#endif
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/hc.c
===================================================================
--- uspace/drv/bus/usb/xhci/hc.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/hc.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,1063 @@
+/*
+ * Copyright (c) 2017 Ondrej Hlavaty
+ * 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 data bookkeeping.
+ */
+
+#include <errno.h>
+#include <str_error.h>
+#include <usb/debug.h>
+#include <usb/host/endpoint.h>
+#include "debug.h"
+#include "hc.h"
+#include "rh.h"
+#include "hw_struct/trb.h"
+#include "hw_struct/context.h"
+#include "endpoint.h"
+#include "transfers.h"
+#include "trb_ring.h"
+
+/**
+ * Default USB Speed ID mapping: Table 157
+ */
+#define PSI_TO_BPS(psie, psim) (((uint64_t) psim) << (10 * psie))
+#define PORT_SPEED(usb, mjr, psie, psim) { \
+	.name = "USB ", \
+	.major = mjr, \
+	.minor = 0, \
+	.usb_speed = USB_SPEED_##usb, \
+	.rx_bps = PSI_TO_BPS(psie, psim), \
+	.tx_bps = PSI_TO_BPS(psie, psim) \
+}
+
+static const xhci_port_speed_t default_psiv_to_port_speed [] = {
+	[1] = PORT_SPEED(FULL, 2, 2, 12),
+	[2] = PORT_SPEED(LOW, 2, 1, 1500),
+	[3] = PORT_SPEED(HIGH, 2, 2, 480),
+	[4] = PORT_SPEED(SUPER, 3, 3, 5),
+};
+
+static const unsigned usb_speed_to_psiv [] = {
+	[USB_SPEED_FULL] = 1,
+	[USB_SPEED_LOW] = 2,
+	[USB_SPEED_HIGH] = 3,
+	[USB_SPEED_SUPER] = 4,
+};
+
+/**
+ * Walk the list of extended capabilities.
+ *
+ * The most interesting thing hidden in extended capabilities is the mapping of
+ * ports to protocol versions and speeds.
+ */
+static int hc_parse_ec(xhci_hc_t *hc)
+{
+	unsigned psic, major, minor;
+	xhci_sp_name_t name;
+
+	xhci_port_speed_t *speeds = hc->speeds;
+
+	for (xhci_extcap_t *ec = hc->xecp; ec; ec = xhci_extcap_next(ec)) {
+		xhci_dump_extcap(ec);
+		switch (XHCI_REG_RD(ec, XHCI_EC_CAP_ID)) {
+		case XHCI_EC_USB_LEGACY:
+			assert(hc->legsup == NULL);
+			hc->legsup = (xhci_legsup_t *) ec;
+			break;
+		case XHCI_EC_SUPPORTED_PROTOCOL:
+			psic = XHCI_REG_RD(ec, XHCI_EC_SP_PSIC);
+			major = XHCI_REG_RD(ec, XHCI_EC_SP_MAJOR);
+			minor = XHCI_REG_RD(ec, XHCI_EC_SP_MINOR);
+			name.packed = host2uint32_t_le(XHCI_REG_RD(ec, XHCI_EC_SP_NAME));
+
+			if (name.packed != xhci_name_usb.packed) {
+				/**
+				 * The detection of such protocol would work,
+				 * but the rest of the implementation is made
+				 * for the USB protocol only.
+				 */
+				usb_log_error("Unknown protocol %.4s.", name.str);
+				return ENOTSUP;
+			}
+
+			unsigned offset = XHCI_REG_RD(ec, XHCI_EC_SP_CP_OFF);
+			unsigned count = XHCI_REG_RD(ec, XHCI_EC_SP_CP_COUNT);
+			xhci_rh_set_ports_protocol(&hc->rh, offset, count, major);
+
+			// "Implied" speed
+			if (psic == 0) {
+				assert(minor == 0);
+
+				if (major == 2) {
+					speeds[1] = default_psiv_to_port_speed[1];
+					speeds[2] = default_psiv_to_port_speed[2];
+					speeds[3] = default_psiv_to_port_speed[3];
+				} else if (major == 3) {
+					speeds[4] = default_psiv_to_port_speed[4];
+				} else {
+					return EINVAL;
+				}
+
+				usb_log_debug("Implied speed of USB %u.0 set up.", major);
+			} else {
+				for (unsigned i = 0; i < psic; i++) {
+					xhci_psi_t *psi = xhci_extcap_psi(ec, i);
+					unsigned sim = XHCI_REG_RD(psi, XHCI_PSI_PSIM);
+					unsigned psiv = XHCI_REG_RD(psi, XHCI_PSI_PSIV);
+					unsigned psie = XHCI_REG_RD(psi, XHCI_PSI_PSIE);
+					unsigned psim = XHCI_REG_RD(psi, XHCI_PSI_PSIM);
+					uint64_t bps = PSI_TO_BPS(psie, psim);
+
+					/*
+					 * Speed is not implied, but using one of default PSIV. This
+					 * is not clearly stated in xHCI spec. There is a clear
+					 * intention to allow xHCI to specify its own speed
+					 * parameters, but throughout the document, they used fixed
+					 * values for e.g. High-speed (3), without stating the
+					 * controller shall have implied default speeds - and for
+					 * instance Intel controllers do not. So let's check if the
+					 * values match and if so, accept the implied USB speed too.
+					 *
+					 * The main reason we need this is the usb_speed to have
+					 * mapping also for devices connected to hubs.
+					 */
+					if (psiv < ARRAY_SIZE(default_psiv_to_port_speed)
+					   && default_psiv_to_port_speed[psiv].major == major
+					   && default_psiv_to_port_speed[psiv].minor == minor
+					   && default_psiv_to_port_speed[psiv].rx_bps == bps
+					   && default_psiv_to_port_speed[psiv].tx_bps == bps) {
+						speeds[psiv] = default_psiv_to_port_speed[psiv];
+						usb_log_debug("Assumed default %s speed of USB %u.",
+							usb_str_speed(speeds[psiv].usb_speed), major);
+						continue;
+					}
+
+					// Custom speed
+					speeds[psiv].major = major;
+					speeds[psiv].minor = minor;
+					str_ncpy(speeds[psiv].name, 4, name.str, 4);
+					speeds[psiv].usb_speed = USB_SPEED_MAX;
+
+					if (sim == XHCI_PSI_PLT_SYMM || sim == XHCI_PSI_PLT_RX)
+						speeds[psiv].rx_bps = bps;
+					if (sim == XHCI_PSI_PLT_SYMM || sim == XHCI_PSI_PLT_TX) {
+						speeds[psiv].tx_bps = bps;
+						usb_log_debug("Speed %u set up for bps %" PRIu64
+							" / %" PRIu64 ".", psiv, speeds[psiv].rx_bps,
+							speeds[psiv].tx_bps);
+					}
+				}
+			}
+		}
+	}
+	return EOK;
+}
+
+/**
+ * Initialize MMIO spaces of xHC.
+ */
+int hc_init_mmio(xhci_hc_t *hc, const hw_res_list_parsed_t *hw_res)
+{
+	int err;
+
+	if (hw_res->mem_ranges.count != 1) {
+		usb_log_error("Unexpected MMIO area, bailing out.");
+		return EINVAL;
+	}
+
+	hc->mmio_range = hw_res->mem_ranges.ranges[0];
+
+	usb_log_debug("MMIO area at %p (size %zu), IRQ %d.",
+	    RNGABSPTR(hc->mmio_range), RNGSZ(hc->mmio_range), hw_res->irqs.irqs[0]);
+
+	if (RNGSZ(hc->mmio_range) < sizeof(xhci_cap_regs_t))
+		return EOVERFLOW;
+
+	void *base;
+	if ((err = pio_enable_range(&hc->mmio_range, &base)))
+		return err;
+
+	hc->reg_base = base;
+	hc->cap_regs = (xhci_cap_regs_t *)  base;
+	hc->op_regs  = (xhci_op_regs_t *)  (base + XHCI_REG_RD(hc->cap_regs, XHCI_CAP_LENGTH));
+	hc->rt_regs  = (xhci_rt_regs_t *)  (base + XHCI_REG_RD(hc->cap_regs, XHCI_CAP_RTSOFF));
+	hc->db_arry  = (xhci_doorbell_t *) (base + XHCI_REG_RD(hc->cap_regs, XHCI_CAP_DBOFF));
+
+	uintptr_t xec_offset = XHCI_REG_RD(hc->cap_regs, XHCI_CAP_XECP) * sizeof(xhci_dword_t);
+	if (xec_offset > 0)
+		hc->xecp = (xhci_extcap_t *) (base + xec_offset);
+
+	usb_log_debug("Initialized MMIO reg areas:");
+	usb_log_debug("\tCapability regs: %p", hc->cap_regs);
+	usb_log_debug("\tOperational regs: %p", hc->op_regs);
+	usb_log_debug("\tRuntime regs: %p", hc->rt_regs);
+	usb_log_debug("\tDoorbell array base: %p", hc->db_arry);
+
+	xhci_dump_cap_regs(hc->cap_regs);
+
+	hc->ac64 = XHCI_REG_RD(hc->cap_regs, XHCI_CAP_AC64);
+	hc->csz = XHCI_REG_RD(hc->cap_regs, XHCI_CAP_CSZ);
+	hc->max_slots = XHCI_REG_RD(hc->cap_regs, XHCI_CAP_MAX_SLOTS);
+
+	struct timeval tv;
+	getuptime(&tv);
+	hc->wrap_time = tv.tv_sec * 1000000 + tv.tv_usec;
+	hc->wrap_count = 0;
+
+	unsigned ist = XHCI_REG_RD(hc->cap_regs, XHCI_CAP_IST);
+	hc->ist = (ist & 0x10 >> 1) * (ist & 0xf);
+
+	if ((err = xhci_rh_init(&hc->rh, hc)))
+		goto err_pio;
+
+	if ((err = hc_parse_ec(hc)))
+		goto err_rh;
+
+	return EOK;
+
+err_rh:
+	xhci_rh_fini(&hc->rh);
+err_pio:
+	pio_disable(hc->reg_base, RNGSZ(hc->mmio_range));
+	return err;
+}
+
+static int event_worker(void *arg);
+
+/**
+ * Initialize structures kept in allocated memory.
+ */
+int hc_init_memory(xhci_hc_t *hc, ddf_dev_t *device)
+{
+	int err;
+
+	if (dma_buffer_alloc(&hc->dcbaa_dma, (1 + hc->max_slots) * sizeof(uint64_t)))
+		return ENOMEM;
+	hc->dcbaa = hc->dcbaa_dma.virt;
+
+	if ((err = xhci_event_ring_init(&hc->event_ring, 1)))
+		goto err_dcbaa;
+
+	if ((err = xhci_scratchpad_alloc(hc)))
+		goto err_event_ring;
+
+	if ((err = xhci_init_commands(hc)))
+		goto err_scratch;
+
+	if ((err = xhci_bus_init(&hc->bus, hc)))
+		goto err_cmd;
+
+	fid_t fid = fibril_create(&event_worker, hc);
+	if (!fid)
+		goto err_bus;
+
+	// TODO: completion_reset
+	hc->event_fibril_completion.active = true;
+	fibril_mutex_initialize(&hc->event_fibril_completion.guard);
+	fibril_condvar_initialize(&hc->event_fibril_completion.cv);
+
+	xhci_sw_ring_init(&hc->sw_ring, PAGE_SIZE / sizeof(xhci_trb_t));
+
+	fibril_add_ready(fid);
+
+	return EOK;
+
+err_bus:
+	xhci_bus_fini(&hc->bus);
+err_cmd:
+	xhci_fini_commands(hc);
+err_scratch:
+	xhci_scratchpad_free(hc);
+err_event_ring:
+	xhci_event_ring_fini(&hc->event_ring);
+err_dcbaa:
+	hc->dcbaa = NULL;
+	dma_buffer_free(&hc->dcbaa_dma);
+	return err;
+}
+
+/*
+ * Pseudocode:
+ *	ip = read(intr[0].iman)
+ *	if (ip) {
+ *		status = read(usbsts)
+ *		assert status
+ *		assert ip
+ *		accept (passing status)
+ *	}
+ *	decline
+ */
+static const irq_cmd_t irq_commands[] = {
+	{
+		.cmd = CMD_PIO_READ_32,
+		.dstarg = 3,
+		.addr = NULL	/* intr[0].iman */
+	},
+	{
+		.cmd = CMD_AND,
+		.srcarg = 3,
+		.dstarg = 4,
+		.value = 0	/* host2xhci(32, 1) */
+	},
+	{
+		.cmd = CMD_PREDICATE,
+		.srcarg = 4,
+		.value = 5
+	},
+	{
+		.cmd = CMD_PIO_READ_32,
+		.dstarg = 1,
+		.addr = NULL	/* usbsts */
+	},
+	{
+		.cmd = CMD_AND,
+		.srcarg = 1,
+		.dstarg = 2,
+		.value = 0	/* host2xhci(32, XHCI_STATUS_ACK_MASK) */
+	},
+	{
+		.cmd = CMD_PIO_WRITE_A_32,
+		.srcarg = 2,
+		.addr = NULL	/* usbsts */
+	},
+	{
+		.cmd = CMD_PIO_WRITE_A_32,
+		.srcarg = 3,
+		.addr = NULL	/* intr[0].iman */
+	},
+	{
+		.cmd = CMD_ACCEPT
+	},
+	{
+		.cmd = CMD_DECLINE
+	}
+};
+
+
+/**
+ * Generates code to accept interrupts. The xHCI is designed primarily for
+ * MSI/MSI-X, but we use PCI Interrupt Pin. In this mode, all the Interrupters
+ * (except 0) are disabled.
+ */
+int hc_irq_code_gen(irq_code_t *code, xhci_hc_t *hc, const hw_res_list_parsed_t *hw_res, int *irq)
+{
+	assert(code);
+	assert(hw_res);
+
+	if (hw_res->irqs.count != 1) {
+		usb_log_info("Unexpected HW resources to enable interrupts.");
+		return EINVAL;
+	}
+
+	code->ranges = malloc(sizeof(irq_pio_range_t));
+	if (code->ranges == NULL)
+		return ENOMEM;
+
+	code->cmds = malloc(sizeof(irq_commands));
+	if (code->cmds == NULL) {
+		free(code->ranges);
+		return ENOMEM;
+	}
+
+	code->rangecount = 1;
+	code->ranges[0] = (irq_pio_range_t) {
+	    .base = RNGABS(hc->mmio_range),
+	    .size = RNGSZ(hc->mmio_range),
+	};
+
+	code->cmdcount = ARRAY_SIZE(irq_commands);
+	memcpy(code->cmds, irq_commands, sizeof(irq_commands));
+
+	void *intr0_iman = RNGABSPTR(hc->mmio_range)
+	    + XHCI_REG_RD(hc->cap_regs, XHCI_CAP_RTSOFF)
+	    + offsetof(xhci_rt_regs_t, ir[0]);
+	void *usbsts = RNGABSPTR(hc->mmio_range)
+	    + XHCI_REG_RD(hc->cap_regs, XHCI_CAP_LENGTH)
+	    + offsetof(xhci_op_regs_t, usbsts);
+
+	code->cmds[0].addr = intr0_iman;
+	code->cmds[1].value = host2xhci(32, 1);
+	code->cmds[3].addr = usbsts;
+	code->cmds[4].value = host2xhci(32, XHCI_STATUS_ACK_MASK);
+	code->cmds[5].addr = usbsts;
+	code->cmds[6].addr = intr0_iman;
+
+        *irq = hw_res->irqs.irqs[0];
+        return EOK;
+}
+
+/**
+ * Claim xHC from BIOS. Implements handoff as per Section 4.22.1 of xHCI spec.
+ */
+int hc_claim(xhci_hc_t *hc, ddf_dev_t *dev)
+{
+	/* No legacy support capability, the controller is solely for us */
+	if (!hc->legsup)
+		return EOK;
+
+	if (xhci_reg_wait(&hc->op_regs->usbsts, XHCI_REG_MASK(XHCI_OP_CNR), 0))
+		return ETIMEOUT;
+
+	usb_log_debug("LEGSUP: bios: %x, os: %x", hc->legsup->sem_bios, hc->legsup->sem_os);
+	XHCI_REG_SET(hc->legsup, XHCI_LEGSUP_SEM_OS, 1);
+	for (int i = 0; i <= (XHCI_LEGSUP_BIOS_TIMEOUT_US / XHCI_LEGSUP_POLLING_DELAY_1MS); i++) {
+		usb_log_debug("LEGSUP: elapsed: %i ms, bios: %x, os: %x", i,
+			XHCI_REG_RD(hc->legsup, XHCI_LEGSUP_SEM_BIOS),
+			XHCI_REG_RD(hc->legsup, XHCI_LEGSUP_SEM_OS));
+		if (XHCI_REG_RD(hc->legsup, XHCI_LEGSUP_SEM_BIOS) == 0) {
+			return XHCI_REG_RD(hc->legsup, XHCI_LEGSUP_SEM_OS) == 1 ? EOK : EIO;
+		}
+		async_usleep(XHCI_LEGSUP_POLLING_DELAY_1MS);
+	}
+	usb_log_error("BIOS did not release XHCI legacy hold!");
+
+	return ENOTSUP;
+}
+
+/**
+ * Ask the xHC to reset its state. Implements sequence
+ */
+static int hc_reset(xhci_hc_t *hc)
+{
+	if (xhci_reg_wait(&hc->op_regs->usbsts, XHCI_REG_MASK(XHCI_OP_CNR), 0))
+		return ETIMEOUT;
+
+	/* Stop the HC: set R/S to 0 */
+	XHCI_REG_CLR(hc->op_regs, XHCI_OP_RS, 1);
+
+	/* Wait until the HC is halted - it shall take at most 16 ms */
+	if (xhci_reg_wait(&hc->op_regs->usbsts, XHCI_REG_MASK(XHCI_OP_HCH),
+	    XHCI_REG_MASK(XHCI_OP_HCH)))
+		return ETIMEOUT;
+
+	/* Reset */
+	XHCI_REG_SET(hc->op_regs, XHCI_OP_HCRST, 1);
+
+	/* Wait until the reset is complete */
+	if (xhci_reg_wait(&hc->op_regs->usbcmd, XHCI_REG_MASK(XHCI_OP_HCRST), 0))
+		return ETIMEOUT;
+
+	return EOK;
+}
+
+/**
+ * Initialize the HC: section 4.2
+ */
+int hc_start(xhci_hc_t *hc, bool irq)
+{
+	int err;
+
+	if ((err = hc_reset(hc)))
+		return err;
+
+	if (xhci_reg_wait(&hc->op_regs->usbsts, XHCI_REG_MASK(XHCI_OP_CNR), 0))
+		return ETIMEOUT;
+
+	uint64_t dcbaaptr = hc->dcbaa_dma.phys;
+	XHCI_REG_WR(hc->op_regs, XHCI_OP_DCBAAP_LO, LOWER32(dcbaaptr));
+	XHCI_REG_WR(hc->op_regs, XHCI_OP_DCBAAP_HI, UPPER32(dcbaaptr));
+	XHCI_REG_WR(hc->op_regs, XHCI_OP_MAX_SLOTS_EN, hc->max_slots);
+
+	uintptr_t crcr;
+	xhci_trb_ring_reset_dequeue_state(&hc->cr.trb_ring, &crcr);
+	XHCI_REG_WR(hc->op_regs, XHCI_OP_CRCR_LO, LOWER32(crcr));
+	XHCI_REG_WR(hc->op_regs, XHCI_OP_CRCR_HI, UPPER32(crcr));
+
+	XHCI_REG_SET(hc->op_regs, XHCI_OP_EWE, 1);
+
+	xhci_interrupter_regs_t *intr0 = &hc->rt_regs->ir[0];
+	XHCI_REG_WR(intr0, XHCI_INTR_ERSTSZ, hc->event_ring.segment_count);
+	uint64_t erdp = hc->event_ring.dequeue_ptr;
+	XHCI_REG_WR(intr0, XHCI_INTR_ERDP_LO, LOWER32(erdp));
+	XHCI_REG_WR(intr0, XHCI_INTR_ERDP_HI, UPPER32(erdp));
+	uint64_t erstptr = hc->event_ring.erst.phys;
+	XHCI_REG_WR(intr0, XHCI_INTR_ERSTBA_LO, LOWER32(erstptr));
+	XHCI_REG_WR(intr0, XHCI_INTR_ERSTBA_HI, UPPER32(erstptr));
+
+	if (irq) {
+		XHCI_REG_SET(intr0, XHCI_INTR_IE, 1);
+		XHCI_REG_SET(hc->op_regs, XHCI_OP_INTE, 1);
+	}
+
+	XHCI_REG_SET(hc->op_regs, XHCI_OP_HSEE, 1);
+
+	XHCI_REG_SET(hc->op_regs, XHCI_OP_RS, 1);
+
+	xhci_rh_startup(&hc->rh);
+
+	return EOK;
+}
+
+/**
+ * Used only when polling. Shall supplement the irq_commands.
+ */
+int hc_status(bus_t *bus, uint32_t *status)
+{
+	xhci_hc_t *hc = bus_to_hc(bus);
+	int ip = XHCI_REG_RD(hc->rt_regs->ir, XHCI_INTR_IP);
+	if (ip) {
+		*status = XHCI_REG_RD(hc->op_regs, XHCI_OP_STATUS);
+		XHCI_REG_WR(hc->op_regs, XHCI_OP_STATUS, *status & XHCI_STATUS_ACK_MASK);
+		XHCI_REG_WR(hc->rt_regs->ir, XHCI_INTR_IP, 1);
+
+		/* interrupt handler expects status from irq_commands, which is
+		 * in xhci order. */
+		*status = host2xhci(32, *status);
+	}
+
+	usb_log_debug("Polled status: %x", *status);
+	return EOK;
+}
+
+static int xhci_handle_mfindex_wrap_event(xhci_hc_t *hc, xhci_trb_t *trb)
+{
+	struct timeval tv;
+	getuptime(&tv);
+	usb_log_debug("Microframe index wrapped (@%lu.%li, %"PRIu64" total).",
+	    tv.tv_sec, tv.tv_usec, hc->wrap_count);
+	hc->wrap_time = ((uint64_t) tv.tv_sec) * 1000000 + ((uint64_t) tv.tv_usec);
+	++hc->wrap_count;
+	return EOK;
+}
+
+typedef int (*event_handler) (xhci_hc_t *, xhci_trb_t *trb);
+
+/**
+ * These events are handled by separate event handling fibril.
+ */
+static event_handler event_handlers [] = {
+	[XHCI_TRB_TYPE_TRANSFER_EVENT] = &xhci_handle_transfer_event,
+};
+
+/**
+ * These events are handled directly in the interrupt handler, thus they must
+ * not block waiting for another interrupt.
+ */
+static event_handler event_handlers_fast [] = {
+	[XHCI_TRB_TYPE_COMMAND_COMPLETION_EVENT] = &xhci_handle_command_completion,
+	[XHCI_TRB_TYPE_MFINDEX_WRAP_EVENT] = &xhci_handle_mfindex_wrap_event,
+};
+
+static int hc_handle_event(xhci_hc_t *hc, xhci_trb_t *trb)
+{
+	const unsigned type = TRB_TYPE(*trb);
+
+	if (type <= ARRAY_SIZE(event_handlers_fast) && event_handlers_fast[type])
+		return event_handlers_fast[type](hc, trb);
+
+	if (type <= ARRAY_SIZE(event_handlers) && event_handlers[type])
+		return xhci_sw_ring_enqueue(&hc->sw_ring, trb);
+
+	if (type == XHCI_TRB_TYPE_PORT_STATUS_CHANGE_EVENT)
+		return xhci_sw_ring_enqueue(&hc->rh.event_ring, trb);
+
+	return ENOTSUP;
+}
+
+static int event_worker(void *arg)
+{
+	int err;
+	xhci_trb_t trb;
+	xhci_hc_t * const hc = arg;
+	assert(hc);
+
+	while (xhci_sw_ring_dequeue(&hc->sw_ring, &trb) != EINTR) {
+		const unsigned type = TRB_TYPE(trb);
+
+		if ((err = event_handlers[type](hc, &trb)))
+			usb_log_error("Failed to handle event: %s", str_error(err));
+	}
+
+	// TODO: completion_complete
+	fibril_mutex_lock(&hc->event_fibril_completion.guard);
+	hc->event_fibril_completion.active = false;
+	fibril_condvar_broadcast(&hc->event_fibril_completion.cv);
+	fibril_mutex_unlock(&hc->event_fibril_completion.guard);
+
+	return EOK;
+}
+
+/**
+ * Dequeue from event ring and handle dequeued events.
+ *
+ * As there can be events, that blocks on waiting for subsequent events,
+ * we solve this problem by first copying the event TRBs from the event ring,
+ * then asserting EHB and only after, handling the events.
+ *
+ * Whenever the event handling blocks, it switches fibril, and incoming
+ * IPC notification will create new event handling fibril for us.
+ */
+static void hc_run_event_ring(xhci_hc_t *hc, xhci_event_ring_t *event_ring,
+    xhci_interrupter_regs_t *intr)
+{
+	int err;
+
+	xhci_trb_t trb;
+	hc->event_handler = fibril_get_id();
+
+	while ((err = xhci_event_ring_dequeue(event_ring, &trb)) != ENOENT) {
+		if ((err = hc_handle_event(hc, &trb)) != EOK) {
+			usb_log_error("Failed to handle event in interrupt: %s", str_error(err));
+		}
+
+		uint64_t erdp = hc->event_ring.dequeue_ptr;
+		XHCI_REG_WR(intr, XHCI_INTR_ERDP_LO, LOWER32(erdp));
+		XHCI_REG_WR(intr, XHCI_INTR_ERDP_HI, UPPER32(erdp));
+	}
+
+	hc->event_handler = 0;
+
+	/* Update the ERDP to make room in the ring. */
+	uint64_t erdp = hc->event_ring.dequeue_ptr;
+	erdp |= XHCI_REG_MASK(XHCI_INTR_ERDP_EHB);
+	XHCI_REG_WR(intr, XHCI_INTR_ERDP_LO, LOWER32(erdp));
+	XHCI_REG_WR(intr, XHCI_INTR_ERDP_HI, UPPER32(erdp));
+
+	usb_log_debug2("Event ring run finished.");
+}
+
+/**
+ * Handle an interrupt request from xHC. Resolve all situations that trigger an
+ * interrupt separately.
+ *
+ * Note that all RW1C bits in USBSTS register are cleared at the time of
+ * handling the interrupt in irq_code. This method is the top-half.
+ *
+ * @param status contents of USBSTS register at the time of the interrupt.
+ */
+void hc_interrupt(bus_t *bus, uint32_t status)
+{
+	xhci_hc_t *hc = bus_to_hc(bus);
+	status = xhci2host(32, status);
+
+	if (status & XHCI_REG_MASK(XHCI_OP_HSE)) {
+		usb_log_error("Host controller error occured. Bad things gonna happen...");
+		status &= ~XHCI_REG_MASK(XHCI_OP_HSE);
+	}
+
+	if (status & XHCI_REG_MASK(XHCI_OP_EINT)) {
+		usb_log_debug2("Event interrupt, running the event ring.");
+		hc_run_event_ring(hc, &hc->event_ring, &hc->rt_regs->ir[0]);
+		status &= ~XHCI_REG_MASK(XHCI_OP_EINT);
+	}
+
+	if (status & XHCI_REG_MASK(XHCI_OP_SRE)) {
+		usb_log_error("Save/Restore error occured. WTF, "
+		    "S/R mechanism not implemented!");
+		status &= ~XHCI_REG_MASK(XHCI_OP_SRE);
+	}
+
+	/* According to Note on p. 302, we may safely ignore the PCD bit. */
+	status &= ~XHCI_REG_MASK(XHCI_OP_PCD);
+
+	if (status) {
+		usb_log_error("Non-zero status after interrupt handling (%08x) "
+			" - missing something?", status);
+	}
+}
+
+/**
+ * Tear down all in-memory structures.
+ */
+void hc_fini(xhci_hc_t *hc)
+{
+	xhci_sw_ring_stop(&hc->sw_ring);
+
+	// TODO: completion_wait
+	fibril_mutex_lock(&hc->event_fibril_completion.guard);
+	while (hc->event_fibril_completion.active)
+		fibril_condvar_wait(&hc->event_fibril_completion.cv,
+		    &hc->event_fibril_completion.guard);
+	fibril_mutex_unlock(&hc->event_fibril_completion.guard);
+	xhci_sw_ring_fini(&hc->sw_ring);
+
+	xhci_bus_fini(&hc->bus);
+	xhci_event_ring_fini(&hc->event_ring);
+	xhci_scratchpad_free(hc);
+	dma_buffer_free(&hc->dcbaa_dma);
+	xhci_fini_commands(hc);
+	xhci_rh_fini(&hc->rh);
+	pio_disable(hc->reg_base, RNGSZ(hc->mmio_range));
+	usb_log_info("Finalized.");
+}
+
+unsigned hc_speed_to_psiv(usb_speed_t speed)
+{
+	assert(speed < ARRAY_SIZE(usb_speed_to_psiv));
+	return usb_speed_to_psiv[speed];
+}
+
+/**
+ * Ring a xHC Doorbell. Implements section 4.7.
+ */
+void hc_ring_doorbell(xhci_hc_t *hc, unsigned doorbell, unsigned target)
+{
+	assert(hc);
+	uint32_t v = host2xhci(32, target & BIT_RRANGE(uint32_t, 7));
+	pio_write_32(&hc->db_arry[doorbell], v);
+	usb_log_debug2("Ringing doorbell %d (target: %d)", doorbell, target);
+}
+
+/**
+ * Return an index to device context.
+ */
+static uint8_t endpoint_dci(xhci_endpoint_t *ep)
+{
+	return (2 * ep->base.endpoint) +
+		(ep->base.transfer_type == USB_TRANSFER_CONTROL
+		 || ep->base.direction == USB_DIRECTION_IN);
+}
+
+void hc_ring_ep_doorbell(xhci_endpoint_t *ep, uint32_t stream_id)
+{
+	xhci_device_t * const dev = xhci_ep_to_dev(ep);
+	xhci_hc_t * const hc = bus_to_hc(dev->base.bus);
+	const uint8_t dci = endpoint_dci(ep);
+	const uint32_t target = (stream_id << 16) | (dci & 0x1ff);
+	hc_ring_doorbell(hc, dev->slot_id, target);
+}
+
+/**
+ * Issue an Enable Slot command. Allocate memory for the slot and fill the
+ * DCBAA with the newly created slot.
+ */
+int hc_enable_slot(xhci_device_t *dev)
+{
+	int err;
+	xhci_hc_t * const hc = bus_to_hc(dev->base.bus);
+
+	/* Prepare memory for the context */
+	if ((err = dma_buffer_alloc(&dev->dev_ctx, XHCI_DEVICE_CTX_SIZE(hc))))
+		return err;
+	memset(dev->dev_ctx.virt, 0, XHCI_DEVICE_CTX_SIZE(hc));
+
+	/* Get the slot number */
+	xhci_cmd_t cmd;
+	xhci_cmd_init(&cmd, XHCI_CMD_ENABLE_SLOT);
+
+	err = xhci_cmd_sync(hc, &cmd);
+
+	/* Link them together */
+	if (err == EOK) {
+		dev->slot_id = cmd.slot_id;
+		hc->dcbaa[dev->slot_id] = host2xhci(64, dev->dev_ctx.phys);
+	}
+
+	xhci_cmd_fini(&cmd);
+
+	if (err)
+		dma_buffer_free(&dev->dev_ctx);
+
+	return err;
+}
+
+/**
+ * Issue a Disable Slot command for a slot occupied by device.
+ * Frees the device context.
+ */
+int hc_disable_slot(xhci_device_t *dev)
+{
+	int err;
+	xhci_hc_t * const hc = bus_to_hc(dev->base.bus);
+
+	if ((err = xhci_cmd_sync_inline(hc, DISABLE_SLOT, .slot_id = dev->slot_id))) {
+		return err;
+	}
+
+	/* Free the device context. */
+	hc->dcbaa[dev->slot_id] = 0;
+	dma_buffer_free(&dev->dev_ctx);
+
+	/* Mark the slot as invalid. */
+	dev->slot_id = 0;
+
+	return EOK;
+}
+
+/**
+ * Prepare an empty Endpoint Input Context inside a dma buffer.
+ */
+static int create_configure_ep_input_ctx(xhci_device_t *dev, dma_buffer_t *dma_buf)
+{
+	const xhci_hc_t * hc = bus_to_hc(dev->base.bus);
+	const int err = dma_buffer_alloc(dma_buf, XHCI_INPUT_CTX_SIZE(hc));
+	if (err)
+		return err;
+
+	xhci_input_ctx_t *ictx = dma_buf->virt;
+	memset(ictx, 0, XHCI_INPUT_CTX_SIZE(hc));
+
+	// Quoting sec. 4.6.5 and 4.6.6: A1, D0, D1 are down (already zeroed), A0 is up.
+	XHCI_INPUT_CTRL_CTX_ADD_SET(*XHCI_GET_CTRL_CTX(ictx, hc), 0);
+	xhci_slot_ctx_t *slot_ctx = XHCI_GET_SLOT_CTX(XHCI_GET_DEVICE_CTX(ictx, hc), hc);
+	xhci_setup_slot_context(dev, slot_ctx);
+
+	return EOK;
+}
+
+/**
+ * Initialize a device, assigning it an address. Implements section 4.3.4.
+ *
+ * @param dev Device to assing an address (unconfigured yet)
+ */
+int hc_address_device(xhci_device_t *dev)
+{
+	int err = ENOMEM;
+	xhci_hc_t * const hc = bus_to_hc(dev->base.bus);
+	xhci_endpoint_t *ep0 = xhci_endpoint_get(dev->base.endpoints[0]);
+
+	/* Although we have the precise PSIV value on devices of tier 1,
+	 * we have to rely on reverse mapping on others. */
+	if (!usb_speed_to_psiv[dev->base.speed]) {
+		usb_log_error("Device reported an USB speed (%s) that cannot be mapped "
+		    "to HC port speed.", usb_str_speed(dev->base.speed));
+		return EINVAL;
+	}
+
+	/* Issue configure endpoint command (sec 4.3.5). */
+	dma_buffer_t ictx_dma_buf;
+	if ((err = create_configure_ep_input_ctx(dev, &ictx_dma_buf)))
+		return err;
+	xhci_input_ctx_t *ictx = ictx_dma_buf.virt;
+
+	/* Copy endpoint 0 context and set A1 flag. */
+	XHCI_INPUT_CTRL_CTX_ADD_SET(*XHCI_GET_CTRL_CTX(ictx, hc), 1);
+	xhci_ep_ctx_t *ep_ctx = XHCI_GET_EP_CTX(XHCI_GET_DEVICE_CTX(ictx, hc), hc, 1);
+	xhci_setup_endpoint_context(ep0, ep_ctx);
+
+	/* Address device needs Ctx entries set to 1 only */
+	xhci_slot_ctx_t *slot_ctx = XHCI_GET_SLOT_CTX(XHCI_GET_DEVICE_CTX(ictx, hc), hc);
+	XHCI_SLOT_CTX_ENTRIES_SET(*slot_ctx, 1);
+
+	/* Issue Address Device command. */
+	if ((err = xhci_cmd_sync_inline(hc, ADDRESS_DEVICE,
+		.slot_id = dev->slot_id,
+		.input_ctx = ictx_dma_buf
+	    )))
+		return err;
+
+	xhci_device_ctx_t *device_ctx = dev->dev_ctx.virt;
+	dev->base.address = XHCI_SLOT_DEVICE_ADDRESS(*XHCI_GET_SLOT_CTX(device_ctx, hc));
+	usb_log_debug("Obtained USB address: %d.", dev->base.address);
+
+	return EOK;
+}
+
+/**
+ * Issue a Configure Device command for a device in slot.
+ *
+ * @param slot_id Slot ID assigned to the device.
+ */
+int hc_configure_device(xhci_device_t *dev)
+{
+	xhci_hc_t * const hc = bus_to_hc(dev->base.bus);
+
+	/* Issue configure endpoint command (sec 4.3.5). */
+	dma_buffer_t ictx_dma_buf;
+	const int err = create_configure_ep_input_ctx(dev, &ictx_dma_buf);
+	if (err)
+		return err;
+
+	return xhci_cmd_sync_inline(hc, CONFIGURE_ENDPOINT,
+		.slot_id = dev->slot_id,
+		.input_ctx = ictx_dma_buf
+	);
+}
+
+/**
+ * Issue a Deconfigure Device command for a device in slot.
+ *
+ * @param dev The owner of the device
+ */
+int hc_deconfigure_device(xhci_device_t *dev)
+{
+	xhci_hc_t * const hc = bus_to_hc(dev->base.bus);
+
+	/* Issue configure endpoint command (sec 4.3.5) with the DC flag. */
+	return xhci_cmd_sync_inline(hc, CONFIGURE_ENDPOINT,
+		.slot_id = dev->slot_id,
+		.deconfigure = true
+	);
+}
+
+/**
+ * Instruct xHC to add an endpoint with supplied endpoint context.
+ *
+ * @param dev The owner of the device
+ * @param ep_idx Endpoint DCI in question
+ * @param ep_ctx Endpoint context of the endpoint
+ */
+int hc_add_endpoint(xhci_endpoint_t *ep)
+{
+	xhci_device_t * const dev = xhci_ep_to_dev(ep);
+	const unsigned dci = endpoint_dci(ep);
+
+	/* Issue configure endpoint command (sec 4.3.5). */
+	dma_buffer_t ictx_dma_buf;
+	const int err = create_configure_ep_input_ctx(dev, &ictx_dma_buf);
+	if (err)
+		return err;
+
+	xhci_input_ctx_t *ictx = ictx_dma_buf.virt;
+
+	xhci_hc_t * const hc = bus_to_hc(dev->base.bus);
+	XHCI_INPUT_CTRL_CTX_ADD_SET(*XHCI_GET_CTRL_CTX(ictx, hc), dci);
+
+	xhci_ep_ctx_t *ep_ctx = XHCI_GET_EP_CTX(XHCI_GET_DEVICE_CTX(ictx, hc), hc, dci);
+	xhci_setup_endpoint_context(ep, ep_ctx);
+
+	return xhci_cmd_sync_inline(hc, CONFIGURE_ENDPOINT,
+		.slot_id = dev->slot_id,
+		.input_ctx = ictx_dma_buf
+	);
+}
+
+/**
+ * Instruct xHC to drop an endpoint.
+ *
+ * @param dev The owner of the endpoint
+ * @param ep_idx Endpoint DCI in question
+ */
+int hc_drop_endpoint(xhci_endpoint_t *ep)
+{
+	xhci_device_t * const dev = xhci_ep_to_dev(ep);
+	const unsigned dci = endpoint_dci(ep);
+
+	/* Issue configure endpoint command (sec 4.3.5). */
+	dma_buffer_t ictx_dma_buf;
+	const int err = create_configure_ep_input_ctx(dev, &ictx_dma_buf);
+	if (err)
+		return err;
+
+	xhci_hc_t * const hc = bus_to_hc(dev->base.bus);
+	xhci_input_ctx_t *ictx = ictx_dma_buf.virt;
+	XHCI_INPUT_CTRL_CTX_DROP_SET(*XHCI_GET_CTRL_CTX(ictx, hc), dci);
+
+	return xhci_cmd_sync_inline(hc, CONFIGURE_ENDPOINT,
+		.slot_id = dev->slot_id,
+		.input_ctx = ictx_dma_buf
+	);
+}
+
+/**
+ * Instruct xHC to update information about an endpoint, using supplied
+ * endpoint context.
+ *
+ * @param dev The owner of the endpoint
+ * @param ep_idx Endpoint DCI in question
+ * @param ep_ctx Endpoint context of the endpoint
+ */
+int hc_update_endpoint(xhci_endpoint_t *ep)
+{
+	xhci_device_t * const dev = xhci_ep_to_dev(ep);
+	const unsigned dci = endpoint_dci(ep);
+
+	dma_buffer_t ictx_dma_buf;
+	xhci_hc_t * const hc = bus_to_hc(dev->base.bus);
+
+	const int err = dma_buffer_alloc(&ictx_dma_buf, XHCI_INPUT_CTX_SIZE(hc));
+	if (err)
+		return err;
+
+	xhci_input_ctx_t *ictx = ictx_dma_buf.virt;
+	memset(ictx, 0, XHCI_INPUT_CTX_SIZE(hc));
+
+	XHCI_INPUT_CTRL_CTX_ADD_SET(*XHCI_GET_CTRL_CTX(ictx, hc), dci);
+	xhci_ep_ctx_t *ep_ctx = XHCI_GET_EP_CTX(XHCI_GET_DEVICE_CTX(ictx, hc), hc, dci);
+	xhci_setup_endpoint_context(ep, ep_ctx);
+
+	return xhci_cmd_sync_inline(hc, EVALUATE_CONTEXT,
+		.slot_id = dev->slot_id,
+		.input_ctx = ictx_dma_buf
+	);
+}
+
+/**
+ * Instruct xHC to stop running a transfer ring on an endpoint.
+ *
+ * @param dev The owner of the endpoint
+ * @param ep_idx Endpoint DCI in question
+ */
+int hc_stop_endpoint(xhci_endpoint_t *ep)
+{
+	xhci_device_t * const dev = xhci_ep_to_dev(ep);
+	const unsigned dci = endpoint_dci(ep);
+	xhci_hc_t * const hc = bus_to_hc(dev->base.bus);
+	return xhci_cmd_sync_inline(hc, STOP_ENDPOINT,
+		.slot_id = dev->slot_id,
+		.endpoint_id = dci
+	);
+}
+
+/**
+ * Instruct xHC to reset halted endpoint.
+ *
+ * @param dev The owner of the endpoint
+ * @param ep_idx Endpoint DCI in question
+ */
+int hc_reset_endpoint(xhci_endpoint_t *ep)
+{
+	xhci_device_t * const dev = xhci_ep_to_dev(ep);
+	const unsigned dci = endpoint_dci(ep);
+	xhci_hc_t * const hc = bus_to_hc(dev->base.bus);
+	return xhci_cmd_sync_inline(hc, RESET_ENDPOINT,
+		.slot_id = dev->slot_id,
+		.endpoint_id = dci
+	);
+}
+
+/**
+ * Reset a ring position in both software and hardware.
+ *
+ * @param dev The owner of the endpoint
+ */
+int hc_reset_ring(xhci_endpoint_t *ep, uint32_t stream_id)
+{
+	xhci_device_t * const dev = xhci_ep_to_dev(ep);
+	const unsigned dci = endpoint_dci(ep);
+	uintptr_t addr;
+
+	xhci_trb_ring_t *ring = xhci_endpoint_get_ring(ep, stream_id);
+	xhci_trb_ring_reset_dequeue_state(ring, &addr);
+
+	xhci_hc_t * const hc = bus_to_hc(endpoint_get_bus(&ep->base));
+	return xhci_cmd_sync_inline(hc, SET_TR_DEQUEUE_POINTER,
+		.slot_id = dev->slot_id,
+		.endpoint_id = dci,
+		.stream_id = stream_id,
+		.dequeue_ptr = addr,
+	);
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/hc.h
===================================================================
--- uspace/drv/bus/usb/xhci/hc.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/hc.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2017 Ondrej Hlavaty
+ * 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 data bookkeeping.
+ */
+
+#ifndef XHCI_HC_H
+#define XHCI_HC_H
+
+#include <fibril_synch.h>
+#include <usb/host/usb_transfer_batch.h>
+#include "hw_struct/regs.h"
+#include "hw_struct/context.h"
+#include "scratchpad.h"
+#include "trb_ring.h"
+
+#include "rh.h"
+#include "commands.h"
+#include "bus.h"
+
+typedef struct xhci_command xhci_cmd_t;
+
+typedef struct xhci_hc {
+	/** Common HC device header */
+	hc_device_t base;
+
+	/* MMIO range */
+	addr_range_t mmio_range;
+
+	/* Mapped register sets */
+	void *reg_base;
+	xhci_cap_regs_t *cap_regs;
+	xhci_op_regs_t *op_regs;
+	xhci_rt_regs_t *rt_regs;
+	xhci_doorbell_t *db_arry;
+	xhci_extcap_t *xecp;		/**< First extended capability */
+	xhci_legsup_t *legsup;		/**< Legacy support capability */
+
+	/* Structures in allocated memory */
+	xhci_event_ring_t event_ring;
+	uint64_t *dcbaa;
+	dma_buffer_t dcbaa_dma;
+	dma_buffer_t scratchpad_array;
+
+	/* Command ring management */
+	xhci_cmd_ring_t cr;
+
+	/* Buffer for events */
+	xhci_sw_ring_t sw_ring;
+
+	struct {
+		fibril_mutex_t guard;
+		fibril_condvar_t cv;
+		bool active;
+	} event_fibril_completion;
+
+	/* Root hub emulation */
+	xhci_rh_t rh;
+
+	/* Bus bookkeeping */
+	xhci_bus_t bus;
+
+	/* Fibril that is currently hanling events */
+	fid_t event_handler;
+
+	/* Cached capabilities */
+	unsigned max_slots;
+	bool ac64;
+	bool csz;
+	uint64_t wrap_time;		/** The last time when mfindex wrap happened */
+	uint64_t wrap_count;	/** Amount of mfindex wraps HC has done */
+	unsigned ist;			/**< IST in microframes */
+
+	/** Port speed mapping */
+	xhci_port_speed_t speeds [16];
+} xhci_hc_t;
+
+static inline xhci_hc_t *bus_to_hc(bus_t *bus)
+{
+	assert(bus);
+	return member_to_inst(bus, xhci_hc_t, bus);
+}
+
+typedef struct xhci_endpoint xhci_endpoint_t;
+typedef struct xhci_device xhci_device_t;
+
+extern int hc_init_mmio(xhci_hc_t *, const hw_res_list_parsed_t *);
+extern int hc_init_memory(xhci_hc_t *, ddf_dev_t *);
+extern int hc_claim(xhci_hc_t *, ddf_dev_t *);
+extern int hc_irq_code_gen(irq_code_t *, xhci_hc_t *, const hw_res_list_parsed_t *, int *);
+extern int hc_start(xhci_hc_t *, bool);
+extern void hc_fini(xhci_hc_t *);
+
+extern void hc_ring_doorbell(xhci_hc_t *, unsigned, unsigned);
+extern void hc_ring_ep_doorbell(xhci_endpoint_t *, uint32_t);
+extern unsigned hc_speed_to_psiv(usb_speed_t);
+
+extern int hc_enable_slot(xhci_device_t *);
+extern int hc_disable_slot(xhci_device_t *);
+extern int hc_address_device(xhci_device_t *);
+extern int hc_configure_device(xhci_device_t *);
+extern int hc_deconfigure_device(xhci_device_t *);
+extern int hc_add_endpoint(xhci_endpoint_t *);
+extern int hc_drop_endpoint(xhci_endpoint_t *);
+extern int hc_update_endpoint(xhci_endpoint_t *);
+extern int hc_stop_endpoint(xhci_endpoint_t *);
+extern int hc_reset_endpoint(xhci_endpoint_t *);
+extern int hc_reset_ring(xhci_endpoint_t *, uint32_t);
+
+extern int hc_status(bus_t *, uint32_t *);
+extern void hc_interrupt(bus_t *, uint32_t);
+
+#endif
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/hw_struct/README
===================================================================
--- uspace/drv/bus/usb/xhci/hw_struct/README	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/hw_struct/README	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,3 @@
+This folder contains hardware communication structures and constants, as defined
+in chapter 6 of the xHCI specification. It shall not be modified, as it reflects
+the order hardware expects it.
Index: uspace/drv/bus/usb/xhci/hw_struct/common.h
===================================================================
--- uspace/drv/bus/usb/xhci/hw_struct/common.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/hw_struct/common.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2017 Ondrej Hlavaty
+ * 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 Common macros for HW structures.
+ *
+ * There are a lot of data structures that are defined on bit-basis.
+ * Therefore, we provide macros to define getters and setters.
+ */
+
+#ifndef XHCI_COMMON_H
+#define XHCI_COMMON_H
+
+#include <assert.h>
+#include <bitops.h>
+#include <byteorder.h>
+#include <ddi.h>
+#include <errno.h>
+
+#define host2xhci(size, val) host2uint##size##_t_le((val))
+#define xhci2host(size, val) uint##size##_t_le2host((val))
+
+/**
+ * 4 bytes, little-endian.
+ */
+typedef ioport32_t xhci_dword_t __attribute__((aligned(4)));
+
+/**
+ * 8 bytes, little-endian.
+ */
+typedef volatile uint64_t xhci_qword_t __attribute__((aligned(8)));
+
+#define XHCI_DWORD_EXTRACT(field, hi, lo) \
+	(BIT_RANGE_EXTRACT(uint32_t, hi, lo, xhci2host(32, field)))
+#define XHCI_QWORD_EXTRACT(field, hi, lo) \
+	(BIT_RANGE_EXTRACT(uint64_t, hi, lo, xhci2host(64, field)))
+
+/**
+ * Common base for setters on xhci_dword_t storage.
+ *
+ * Not thread-safe, proper synchronization over this dword must be assured.
+ */
+static inline void xhci_dword_set_bits(xhci_dword_t *storage, uint32_t value,
+	unsigned hi, unsigned lo)
+{
+	const uint32_t mask = host2xhci(32, BIT_RANGE(uint32_t, hi, lo));
+	const uint32_t set = host2xhci(32, value << lo);
+	*storage = (*storage & ~mask) | set;
+}
+
+/**
+ * Setter for whole qword.
+ */
+static inline void xhci_qword_set(xhci_qword_t *storage, uint64_t value)
+{
+	*storage = host2xhci(64, value);
+}
+
+static inline void xhci_qword_set_bits(xhci_qword_t *storage, uint64_t value,
+	unsigned hi, unsigned lo)
+{
+	const uint64_t mask = host2xhci(64, BIT_RANGE(uint64_t, hi, lo));
+	const uint64_t set = host2xhci(64, value << lo);
+	*storage = (*storage & ~mask) | set;
+}
+
+static inline int xhci_reg_wait(xhci_dword_t *reg, uint32_t mask,
+	uint32_t expected)
+{
+	mask = host2xhci(32, mask);
+	expected = host2xhci(32, expected);
+
+	unsigned retries = 100;
+	uint32_t value = *reg & mask;
+
+	for (; retries > 0 && value != expected; --retries) {
+		async_usleep(10000);
+		value = *reg & mask;
+	}
+
+	return value == expected ? EOK : ETIMEOUT;
+}
+
+#endif
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/hw_struct/context.h
===================================================================
--- uspace/drv/bus/usb/xhci/hw_struct/context.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/hw_struct/context.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,277 @@
+/*
+ * Copyright (c) 2017 Ondrej Hlavaty <aearsis@eideo.cz>
+ * 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
+ * Context data structures of the xHC.
+ *
+ * Most of them are to be initialized to zero and passed ownership to the HC,
+ * so they are mostly read-only.
+ *
+ * Feel free to write a setter when in need.
+ */
+
+#ifndef XHCI_CONTEXT_H
+#define XHCI_CONTEXT_H
+
+#include <stdint.h>
+#include "common.h"
+
+/**
+ * Endpoint context: section 6.2.3
+ */
+typedef struct xhci_endpoint_ctx {
+	xhci_dword_t data[2];
+	xhci_qword_t data2;
+	xhci_dword_t data3;
+	xhci_dword_t reserved[3];
+
+#define XHCI_EP_COUNT 31
+
+#define XHCI_EP_TYPE_ISOCH_OUT		1
+#define XHCI_EP_TYPE_BULK_OUT		2
+#define XHCI_EP_TYPE_INTERRUPT_OUT	3
+#define XHCI_EP_TYPE_CONTROL		4
+#define XHCI_EP_TYPE_ISOCH_IN		5
+#define XHCI_EP_TYPE_BULK_IN		6
+#define XHCI_EP_TYPE_INTERRUPT_IN	7
+
+#define XHCI_EP_TYPE_SET(ctx, val) \
+	xhci_dword_set_bits(&(ctx).data[1], val, 5, 3)
+#define XHCI_EP_MAX_PACKET_SIZE_SET(ctx, val) \
+	xhci_dword_set_bits(&(ctx).data[1], val, 31, 16)
+#define XHCI_EP_MAX_BURST_SIZE_SET(ctx, val) \
+	xhci_dword_set_bits(&(ctx).data[1], val, 15, 8)
+#define XHCI_EP_TR_DPTR_SET(ctx, val) \
+	xhci_qword_set_bits(&(ctx).data2, (val >> 4), 63, 4)
+#define XHCI_EP_DCS_SET(ctx, val) \
+	xhci_qword_set_bits(&(ctx).data2, val, 0, 0)
+#define XHCI_EP_MAX_ESIT_PAYLOAD_LO_SET(ctx, val) \
+	xhci_dword_set_bits(&(ctx).data3, val, 31, 16)
+#define XHCI_EP_MAX_ESIT_PAYLOAD_HI_SET(ctx, val) \
+	xhci_dword_set_bits(&(ctx).data[0], val, 31, 24)
+#define XHCI_EP_INTERVAL_SET(ctx, val) \
+	xhci_dword_set_bits(&(ctx).data[0], val, 23, 16)
+#define XHCI_EP_MAX_P_STREAMS_SET(ctx, val) \
+	xhci_dword_set_bits(&(ctx).data[0], val, 14, 10)
+#define XHCI_EP_LSA_SET(ctx, val) \
+	xhci_dword_set_bits(&(ctx).data[0], val, 15, 15)
+#define XHCI_EP_MULT_SET(ctx, val) \
+	xhci_dword_set_bits(&(ctx).data[0], val, 9, 8)
+#define XHCI_EP_ERROR_COUNT_SET(ctx, val) \
+	xhci_dword_set_bits(&(ctx).data[1], val, 2, 1)
+
+#define XHCI_EP_STATE(ctx)              XHCI_DWORD_EXTRACT((ctx).data[0],  2,  0)
+#define XHCI_EP_MULT(ctx)               XHCI_DWORD_EXTRACT((ctx).data[0],  9,  8)
+#define XHCI_EP_MAX_P_STREAMS(ctx)      XHCI_DWORD_EXTRACT((ctx).data[0], 14, 10)
+#define XHCI_EP_LSA(ctx)                XHCI_DWORD_EXTRACT((ctx).data[0], 15, 15)
+#define XHCI_EP_INTERVAL(ctx)           XHCI_DWORD_EXTRACT((ctx).data[0], 23, 16)
+
+#define XHCI_EP_ERROR_COUNT(ctx)        XHCI_DWORD_EXTRACT((ctx).data[1],  2,  1)
+#define XHCI_EP_TYPE(ctx)               XHCI_DWORD_EXTRACT((ctx).data[1],  5,  3)
+#define XHCI_EP_HID(ctx)                XHCI_DWORD_EXTRACT((ctx).data[1],  7,  7)
+#define XHCI_EP_MAX_BURST_SIZE(ctx)     XHCI_DWORD_EXTRACT((ctx).data[1], 15,  8)
+#define XHCI_EP_MAX_PACKET_SIZE(ctx)    XHCI_DWORD_EXTRACT((ctx).data[1], 31, 16)
+
+#define XHCI_EP_DCS(ctx)                XHCI_QWORD_EXTRACT((ctx).data2,  0,  0)
+#define XHCI_EP_TR_DPTR(ctx)            XHCI_QWORD_EXTRACT((ctx).data2, 63,  4)
+
+#define XHCI_EP_MAX_ESIT_PAYLOAD_LO(ctx) XHCI_DWORD_EXTRACT((ctx).data3, 31, 16)
+#define XHCI_EP_MAX_ESIT_PAYLOAD_HI(ctx) XHCI_DWORD_EXTRACT((ctx).data[0], 31, 24)
+
+} __attribute__((packed)) xhci_ep_ctx_t;
+
+enum {
+	EP_STATE_DISABLED = 0,
+	EP_STATE_RUNNING = 1,
+	EP_STATE_HALTED = 2,
+	EP_STATE_STOPPED = 3,
+	EP_STATE_ERROR = 4,
+};
+
+/**
+ * Slot context: section 6.2.2
+ */
+typedef struct xhci_slot_ctx {
+	xhci_dword_t data [4];
+	xhci_dword_t reserved [4];
+
+#define XHCI_SLOT_ROUTE_STRING_SET(ctx, val) \
+	xhci_dword_set_bits(&(ctx).data[0], (val & 0xFFFFF), 19, 0)
+#define XHCI_SLOT_SPEED_SET(ctx, val) \
+	xhci_dword_set_bits(&(ctx).data[0], (val & 0xF), 23, 20)
+#define XHCI_SLOT_MTT_SET(ctx, val) \
+	xhci_dword_set_bits(&(ctx).data[0], !!val, 25, 25)
+#define XHCI_SLOT_HUB_SET(ctx, val) \
+	xhci_dword_set_bits(&(ctx).data[0], !!val, 26, 26)
+#define XHCI_SLOT_CTX_ENTRIES_SET(ctx, val) \
+	xhci_dword_set_bits(&(ctx).data[0], val, 31, 27)
+
+#define XHCI_SLOT_ROOT_HUB_PORT_SET(ctx, val) \
+	xhci_dword_set_bits(&(ctx).data[1], val, 23, 16)
+#define XHCI_SLOT_NUM_PORTS_SET(ctx, val) \
+	xhci_dword_set_bits(&(ctx).data[1], val, 31, 24)
+
+#define XHCI_SLOT_TT_HUB_SLOT_ID_SET(ctx, val) \
+	xhci_dword_set_bits(&(ctx).data[2], (val & 0xFF), 7, 0)
+#define XHCI_SLOT_TT_HUB_PORT_SET(ctx, val) \
+	xhci_dword_set_bits(&(ctx).data[2], (val & 0xFF), 15, 8)
+#define XHCI_SLOT_TT_THINK_TIME_SET(ctx, val) \
+	xhci_dword_set_bits(&(ctx).data[2], (val & 0xFF), 17, 16)
+
+#define XHCI_SLOT_ROUTE_STRING(ctx)     XHCI_DWORD_EXTRACT((ctx).data[0], 19,  0)
+#define XHCI_SLOT_SPEED(ctx)            XHCI_DWORD_EXTRACT((ctx).data[0], 23, 20)
+#define XHCI_SLOT_MTT(ctx)              XHCI_DWORD_EXTRACT((ctx).data[0], 25, 25)
+#define XHCI_SLOT_HUB(ctx)              XHCI_DWORD_EXTRACT((ctx).data[0], 26, 26)
+#define XHCI_SLOT_CTX_ENTRIES(ctx)      XHCI_DWORD_EXTRACT((ctx).data[0], 31, 27)
+
+#define XHCI_SLOT_MAX_EXIT_LATENCY(ctx) XHCI_DWORD_EXTRACT((ctx).data[1], 15,  0)
+#define XHCI_SLOT_ROOT_HUB_PORT(ctx)    XHCI_DWORD_EXTRACT((ctx).data[1], 23, 16)
+#define XHCI_SLOT_NUM_PORTS(ctx)        XHCI_DWORD_EXTRACT((ctx).data[1], 31, 24)
+
+#define XHCI_SLOT_TT_HUB_SLOT_ID(ctx)   XHCI_DWORD_EXTRACT((ctx).data[2],  7,  0)
+#define XHCI_SLOT_TT_PORT_NUM(ctx)      XHCI_DWORD_EXTRACT((ctx).data[2], 15,  8)
+#define XHCI_SLOT_TT_THINK_TIME(ctx)    XHCI_DWORD_EXTRACT((ctx).data[2], 17, 16)
+#define XHCI_SLOT_INTERRUPTER(ctx)      XHCI_DWORD_EXTRACT((ctx).data[2], 31, 22)
+
+#define XHCI_SLOT_DEVICE_ADDRESS(ctx)   XHCI_DWORD_EXTRACT((ctx).data[3],  7,  0)
+#define XHCI_SLOT_STATE(ctx)            XHCI_DWORD_EXTRACT((ctx).data[3], 31, 27)
+
+} __attribute__((packed)) xhci_slot_ctx_t;
+
+enum {
+	SLOT_STATE_DISABLED = 0,
+	SLOT_STATE_DEFAULT = 1,
+	SLOT_STATE_ADDRESS = 2,
+	SLOT_STATE_CONFIGURED = 3,
+};
+
+/**
+ * Handling HCs with 32 or 64-bytes context size (CSZ)
+ */
+#define XHCI_CTX_SIZE_SMALL 32
+#define XHCI_ONE_CTX_SIZE(hc) (XHCI_CTX_SIZE_SMALL << hc->csz)
+#define XHCI_GET_CTX_FIELD(type, ctx, hc, ci) (xhci_##type##_ctx_to_charptr(ctx) + (ci) * XHCI_ONE_CTX_SIZE(hc))
+
+/**
+ * Device context: section 6.2.1
+ */
+#define XHCI_DEVICE_CTX_SIZE(hc) ((1 + XHCI_EP_COUNT) * XHCI_ONE_CTX_SIZE(hc))
+#define XHCI_GET_EP_CTX(dev_ctx, hc, dci) ((xhci_ep_ctx_t *)   XHCI_GET_CTX_FIELD(device, (dev_ctx), (hc), (dci)))
+#define XHCI_GET_SLOT_CTX(dev_ctx, hc)    ((xhci_slot_ctx_t *) XHCI_GET_CTX_FIELD(device, (dev_ctx), (hc), 0))
+
+/**
+ * As control, slot and endpoint contexts differ in size on different HCs,
+ * we need to use macros to access them at the correct offsets. The following
+ * empty structs (xhci_device_ctx_t and xhci_input_ctx_t) are used only as
+ * void pointers for type-checking.
+ */
+typedef struct xhci_device_ctx {
+} xhci_device_ctx_t;
+
+/**
+ * Force type checking.
+ */
+static inline char *xhci_device_ctx_to_charptr(const xhci_device_ctx_t *ctx)
+{
+	return (char *) ctx;
+}
+
+/**
+ * Stream context: section 6.2.4 {
+ */
+typedef struct xhci_stream_ctx {
+	uint64_t data [2];
+#define XHCI_STREAM_DCS(ctx)       XHCI_QWORD_EXTRACT((ctx).data[0],  0, 0)
+#define XHCI_STREAM_SCT(ctx)       XHCI_QWORD_EXTRACT((ctx).data[0],  3, 1)
+#define XHCI_STREAM_DEQ_PTR(ctx)   (XHCI_QWORD_EXTRACT((ctx).data[0], 63, 4) << 4)
+#define XHCI_STREAM_EDTLA(ctx)     XHCI_QWORD_EXTRACT((ctx).data[1], 24, 0)
+
+#define XHCI_STREAM_SCT_SET(ctx, val) \
+	xhci_qword_set_bits(&(ctx).data[0], val, 3, 1)
+#define XHCI_STREAM_DEQ_PTR_SET(ctx, val) \
+	xhci_qword_set_bits(&(ctx).data[0], (val >> 4), 63, 4)
+} __attribute__((packed)) xhci_stream_ctx_t;
+
+/**
+ * Input control context: section 6.2.5.1
+ * Note: According to section 6.2.5.1 figure 78,
+ *       the context size register value in hccparams1
+ *       dictates whether input control context shall have
+ *       32 or 64 bytes, but in any case only dwords 0, 1 and 7
+ *       are used, the rest are reserved.
+ */
+typedef struct xhci_input_ctrl_ctx {
+	uint32_t data [8];
+#define XHCI_INPUT_CTRL_CTX_DROP(ctx, idx) \
+	XHCI_DWORD_EXTRACT((ctx).data[0], (idx), (idx))
+
+#define XHCI_INPUT_CTRL_CTX_DROP_SET(ctx, idx) (ctx).data[0] |= (1 << (idx))
+#define XHCI_INPUT_CTRL_CTX_DROP_CLEAR(ctx, idx) (ctx).data[0] &= ~(1 << (idx))
+
+#define XHCI_INPUT_CTRL_CTX_ADD(ctx, idx) \
+	XHCI_DWORD_EXTRACT((ctx).data[1], (idx), (idx))
+
+#define XHCI_INPUT_CTRL_CTX_ADD_SET(ctx, idx) (ctx).data[1] |= (1 << (idx))
+#define XHCI_INPUT_CTRL_CTX_ADD_CLEAR(ctx, idx) (ctx).data[1] &= ~(1 << (idx))
+
+#define XHCI_INPUT_CTRL_CTX_CONFIG_VALUE(ctx)   XHCI_DWORD_EXTRACT((ctx).data[7],  7,  0)
+#define XHCI_INPUT_CTRL_CTX_IFACE_NUMBER(ctx)   XHCI_DWORD_EXTRACT((ctx).data[7], 15,  8)
+#define XHCI_INPUT_CTRL_CTX_ALTER_SETTING(ctx)  XHCI_DWORD_EXTRACT((ctx).data[7], 23, 16)
+} __attribute__((packed)) xhci_input_ctrl_ctx_t;
+
+/**
+ * Input context: section 6.2.5
+ */
+#define XHCI_INPUT_CTX_SIZE(hc) (XHCI_ONE_CTX_SIZE(hc) + XHCI_DEVICE_CTX_SIZE(hc))
+#define XHCI_GET_CTRL_CTX(ictx, hc)  ((xhci_input_ctrl_ctx_t *) XHCI_GET_CTX_FIELD(input, (ictx), (hc), 0))
+#define XHCI_GET_DEVICE_CTX(dev_ctx, hc) ((xhci_device_ctx_t *) XHCI_GET_CTX_FIELD(input, (ictx), (hc), 1))
+
+typedef struct xhci_input_ctx {
+} xhci_input_ctx_t;
+
+/**
+ * Force type checking.
+ */
+static inline char *xhci_input_ctx_to_charptr(const xhci_input_ctx_t *ctx)
+{
+	return (char *) ctx;
+}
+
+/**
+ * Port bandwidth context: section 6.2.6
+ * The number of ports depends on the amount of ports available to the hub.
+ */
+typedef struct xhci_port_bandwidth_ctx {
+	uint8_t reserved;
+	uint8_t ports [];
+} __attribute__((packed)) xhci_port_bandwidth_ctx_t;
+
+#endif
Index: uspace/drv/bus/usb/xhci/hw_struct/regs.h
===================================================================
--- uspace/drv/bus/usb/xhci/hw_struct/regs.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/hw_struct/regs.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,610 @@
+/*
+ * Copyright (c) 2017 Ondrej Hlavaty
+ * 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
+ * Memory-mapped register structures of the xHC.
+ *
+ * The main pr
+ */
+
+#ifndef XHCI_REGS_H
+#define XHCI_REGS_H
+
+#include <macros.h>
+#include <ddi.h>
+#include "common.h"
+
+#define XHCI_PIO_CHANGE_UDELAY 5
+
+/*
+ * These four are the main macros to be used.
+ * Semantics is usual - READ reads value, WRITE changes value, SET sets
+ * selected bits, CLEAR clears selected bits to 0.
+ *
+ * The key thing here is the order of macro expansion, expanding the reg_spec
+ * argument as more arguments (comma delimited) for the inner macro.
+ */
+#define XHCI_REG_RD(reg_set, reg_spec)         XHCI_REG_RD_INNER(reg_set, reg_spec)
+#define XHCI_REG_WR(reg_set, reg_spec, value)  XHCI_REG_WR_INNER(reg_set, value, reg_spec)
+#define XHCI_REG_SET(reg_set, reg_spec, value) XHCI_REG_SET_INNER(reg_set, value, reg_spec)
+#define XHCI_REG_CLR(reg_set, reg_spec, value) XHCI_REG_CLR_INNER(reg_set, value, reg_spec)
+#define XHCI_REG_MASK(reg_spec)                XHCI_REG_MASK_INNER(reg_spec)
+#define XHCI_REG_SHIFT(reg_spec)               XHCI_REG_SHIFT_INNER(reg_spec)
+
+/*
+ * These take a pointer to the field, and selects the type-specific macro.
+ */
+#define XHCI_REG_RD_INNER(reg_set, field, size, type, ...) \
+	XHCI_REG_RD_##type(&((reg_set)->field), size, ##__VA_ARGS__)
+
+#define XHCI_REG_WR_INNER(reg_set, value, field, size, type, ...) \
+	XHCI_REG_WR_##type(&(reg_set)->field, value, size, ##__VA_ARGS__)
+
+#define XHCI_REG_SET_INNER(reg_set, value, field, size, type, ...) \
+	XHCI_REG_SET_##type(&(reg_set)->field, value, size, ##__VA_ARGS__)
+
+#define XHCI_REG_CLR_INNER(reg_set, value, field, size, type, ...) \
+	XHCI_REG_CLR_##type(&(reg_set)->field, value, size, ##__VA_ARGS__)
+
+#define XHCI_REG_MASK_INNER(field, size, type, ...) \
+	XHCI_REG_MASK_##type(size, ##__VA_ARGS__)
+
+#define XHCI_REG_SHIFT_INNER(field, size, type, ...) \
+	XHCI_REG_SHIFT_##type(size, ##__VA_ARGS__)
+
+/*
+ * Field handling is the easiest. Just do it with whole field.
+ */
+#define XHCI_REG_RD_FIELD(ptr, size) \
+	xhci2host(size, pio_read_##size((ptr)))
+#define XHCI_REG_WR_FIELD(ptr, value, size) \
+	pio_write_##size((ptr), host2xhci(size, value))
+#define XHCI_REG_SET_FIELD(ptr, value, size) \
+	pio_set_##size((ptr), host2xhci(size, value), XHCI_PIO_CHANGE_UDELAY);
+#define XHCI_REG_CLR_FIELD(ptr, value, size) \
+	pio_clear_##size((ptr), host2xhci(size, value), XHCI_PIO_CHANGE_UDELAY);
+#define XHCI_REG_MASK_FIELD(size)            (~((uint##size##_t) 0))
+#define XHCI_REG_SHIFT_FIELD(size)           (0)
+
+/*
+ * Flags are just trivial case of ranges.
+ */
+#define XHCI_REG_RD_FLAG(ptr, size, offset) \
+	XHCI_REG_RD_RANGE((ptr), size, (offset), (offset))
+#define XHCI_REG_WR_FLAG(ptr, value, size, offset) \
+	XHCI_REG_WR_RANGE((ptr), (value), size, (offset), (offset))
+#define XHCI_REG_SET_FLAG(ptr, value, size, offset) \
+	XHCI_REG_SET_RANGE((ptr), (value), size, (offset), (offset))
+#define XHCI_REG_CLR_FLAG(ptr, value, size, offset) \
+	XHCI_REG_CLR_RANGE((ptr), (value), size, (offset), (offset))
+#define XHCI_REG_MASK_FLAG(size, offset)            BIT_V(uint##size##_t, offset)
+#define XHCI_REG_SHIFT_FLAG(size, offset)           (offset)
+
+/*
+ * Ranges are the most difficult. We need to play around with bitmasks.
+ */
+#define XHCI_REG_RD_RANGE(ptr, size, hi, lo) \
+	BIT_RANGE_EXTRACT(uint##size##_t, (hi), (lo), XHCI_REG_RD_FIELD((ptr), size))
+
+#define XHCI_REG_WR_RANGE(ptr, value, size, hi, lo) \
+	pio_change_##size((ptr), host2xhci(size, BIT_RANGE_INSERT(uint##size##_t, \
+			(hi), (lo), (value))), \
+		host2xhci(size, BIT_RANGE(uint##size##_t, (hi), (lo))), \
+		XHCI_PIO_CHANGE_UDELAY);
+
+#define XHCI_REG_SET_RANGE(ptr, value, size, hi, lo) \
+	pio_set_##size((ptr), host2xhci(size, BIT_RANGE_INSERT(uint##size##_t, \
+			(hi), (lo), (value))), \
+		XHCI_PIO_CHANGE_UDELAY);
+
+#define XHCI_REG_CLR_RANGE(ptr, value, size, hi, lo) \
+	pio_clear_##size((ptr), host2xhci(size, BIT_RANGE_INSERT(uint##size##_t, \
+			(hi), (lo), (value))), \
+		XHCI_PIO_CHANGE_UDELAY);
+
+#define XHCI_REG_MASK_RANGE(size, hi, lo)  BIT_RANGE(uint##size##_t, hi, lo)
+#define XHCI_REG_SHIFT_RANGE(size, hi, lo) (lo)
+
+/** HC capability registers: section 5.3 */
+typedef const struct xhci_cap_regs {
+
+	/* Size of this structure, offset for the operation registers */
+	const ioport8_t caplength;
+
+	const PADD8;
+
+	/* BCD of specification version */
+	const ioport16_t hciversion;
+
+	/*
+	 *  7:0  - MaxSlots
+	 * 18:8  - MaxIntrs
+	 * 31:24 - MaxPorts
+	 */
+	const ioport32_t hcsparams1;
+
+	/*
+	 *  3:0  - IST
+	 *  7:4  - ERST Max
+	 * 25:21 - Max Scratchpad Bufs Hi
+	 *    26 - SPR
+	 * 31:27 - Max Scratchpad Bufs Lo
+	 */
+	const ioport32_t hcsparams2;
+
+	/*
+	 *  7:0  - U1 Device Exit Latency
+	 * 31:16 - U2 Device Exit Latency
+	 */
+	const ioport32_t hcsparams3;
+
+	/*
+	 *          11  10   9   8   7   6 5    4   3   2   1    0
+	 * 11:0  - CFC SEC SPC PAE NSS LTC C PIND PPC CSZ BNC AC64
+	 * 15:12 - MaxPSASize
+	 * 31:16 - xECP
+	 */
+	const ioport32_t hccparams1;
+
+	/*
+	 * 31:2 - Doorbell Array Offset
+	 */
+	const ioport32_t dboff;
+
+	/*
+	 * 31:5 - Runtime Register Space Offset
+	 */
+	const ioport32_t rtsoff;
+
+	/*
+	 *                 5   4   3   2   1   0
+	 * 5:0  - Flags: CIC LEC CTC FSC CMC U3C
+	 */
+	const ioport32_t hccparams2;
+
+	// the rest to operational registers is reserved
+} xhci_cap_regs_t;
+
+/*
+ * The register specifiers are to be used as the reg_spec argument.
+ *
+ * The values are field, bitsize, type, (type specific args)
+ * When the type is RANGE: hi, lo
+ */
+#define XHCI_CAP_LENGTH        caplength,  8, FIELD
+#define XHCI_CAP_VERSION      hciversion, 16, FIELD
+#define XHCI_CAP_MAX_SLOTS    hcsparams1, 32, RANGE,  7,  0
+#define XHCI_CAP_MAX_INTRS    hcsparams1, 32, RANGE, 18,  8
+#define XHCI_CAP_MAX_PORTS    hcsparams1, 32, RANGE, 31, 24
+#define XHCI_CAP_IST          hcsparams2, 32, RANGE,  3,  0
+#define XHCI_CAP_ERST_MAX     hcsparams2, 32, RANGE,  7,  4
+#define XHCI_CAP_MAX_SPBUF_HI hcsparams2, 32, RANGE, 25, 21
+#define XHCI_CAP_SPR          hcsparams2, 32,  FLAG, 26
+#define XHCI_CAP_MAX_SPBUF_LO hcsparams2, 32, RANGE, 31, 27
+#define XHCI_CAP_U1EL         hcsparams3, 32, RANGE,  7,  0
+#define XHCI_CAP_U2EL         hcsparams3, 32, RANGE, 31, 16
+#define XHCI_CAP_AC64         hccparams1, 32,  FLAG,  0
+#define XHCI_CAP_BNC          hccparams1, 32,  FLAG,  1
+#define XHCI_CAP_CSZ          hccparams1, 32,  FLAG,  2
+#define XHCI_CAP_PPC          hccparams1, 32,  FLAG,  3
+#define XHCI_CAP_PIND         hccparams1, 32,  FLAG,  4
+#define XHCI_CAP_C            hccparams1, 32,  FLAG,  5
+#define XHCI_CAP_LTC          hccparams1, 32,  FLAG,  6
+#define XHCI_CAP_NSS          hccparams1, 32,  FLAG,  7
+#define XHCI_CAP_PAE          hccparams1, 32,  FLAG,  8
+#define XHCI_CAP_SPC          hccparams1, 32,  FLAG,  9
+#define XHCI_CAP_SEC          hccparams1, 32,  FLAG, 10
+#define XHCI_CAP_CFC          hccparams1, 32,  FLAG, 11
+#define XHCI_CAP_MAX_PSA_SIZE hccparams1, 32, RANGE, 15, 12
+#define XHCI_CAP_XECP         hccparams1, 32, RANGE, 31, 16
+#define XHCI_CAP_DBOFF             dboff, 32, FIELD
+#define XHCI_CAP_RTSOFF           rtsoff, 32, FIELD
+#define XHCI_CAP_U3C          hccparams2, 32,  FLAG,  0
+#define XHCI_CAP_CMC          hccparams2, 32,  FLAG,  1
+#define XHCI_CAP_FSC          hccparams2, 32,  FLAG,  2
+#define XHCI_CAP_CTC          hccparams2, 32,  FLAG,  3
+#define XHCI_CAP_LEC          hccparams2, 32,  FLAG,  4
+#define XHCI_CAP_CIC          hccparams2, 32,  FLAG,  5
+
+static inline unsigned xhci_get_max_spbuf(xhci_cap_regs_t *cap_regs) {
+	return XHCI_REG_RD(cap_regs, XHCI_CAP_MAX_SPBUF_HI) << 5
+		| XHCI_REG_RD(cap_regs, XHCI_CAP_MAX_SPBUF_LO);
+}
+
+/**
+ * XHCI Port Register Set: section 5.4, table 32
+ */
+typedef struct xhci_port_regs {
+	/*
+	 *          4   3 2   1   0
+	 *  4:0  - PR OCA Z PED CCS
+	 *  8:5  - PLS
+	 *    9  - PP
+	 * 13:10 - Port Speed
+	 * 15:14 - PIC
+	 *          27  26  25  24  23  22  21  20  19  18  17  16
+	 * 27:16 - WOE WDE WCE CAS CEC PLC PRC OCC WRC PEC CSC LWS
+	 *    30 - DR
+	 *    31 - WPR
+	 */
+	ioport32_t portsc;
+
+	/*
+	 * Contents of this fields depends on the protocol supported by the port.
+	 * USB3:
+	 *      7:0  - U1 Timeout
+	 *     15:8  - U2 Timeout
+	 *        16 - Force Link PM Accept
+	 * USB2:
+	 *      2:0  - L1S
+	 *        3  - RWE
+	 *      7:4  - BESL
+	 *     15:8  - L1 Device Slot
+	 *        16 - HLE
+	 *     31:28 - Test Mode
+	 */
+	ioport32_t portpmsc;
+
+	/*
+	 * This field is valid only for USB3 ports.
+	 * 15:0  - Link Error Count
+	 * 19:16 - RLC
+	 * 23:20 - TLC
+	 */
+	ioport32_t portli;
+
+	/*
+	 * This field is valid only for USB2 ports.
+	 *  1:0  - HIRDM
+	 *  9:2  - L1 Timeout
+	 * 13:10 - BESLD
+	 */
+	ioport32_t porthlpmc;
+} xhci_port_regs_t;
+
+#define XHCI_PORT_CCS           portsc, 32,  FLAG,  0
+#define XHCI_PORT_PED           portsc, 32,  FLAG,  1
+#define XHCI_PORT_OCA           portsc, 32,  FLAG,  3
+#define XHCI_PORT_PR            portsc, 32,  FLAG,  4
+#define XHCI_PORT_PLS           portsc, 32, RANGE,  8,  5
+#define XHCI_PORT_PP            portsc, 32,  FLAG,  9
+#define XHCI_PORT_PS            portsc, 32, RANGE, 13, 10
+#define XHCI_PORT_PIC           portsc, 32, RANGE, 15, 14
+#define XHCI_PORT_LWS           portsc, 32,  FLAG, 16
+#define XHCI_PORT_CSC           portsc, 32,  FLAG, 17
+#define XHCI_PORT_PEC           portsc, 32,  FLAG, 18
+#define XHCI_PORT_WRC           portsc, 32,  FLAG, 19
+#define XHCI_PORT_OCC           portsc, 32,  FLAG, 20
+#define XHCI_PORT_PRC           portsc, 32,  FLAG, 21
+#define XHCI_PORT_PLC           portsc, 32,  FLAG, 22
+#define XHCI_PORT_CEC           portsc, 32,  FLAG, 23
+#define XHCI_PORT_CAS           portsc, 32,  FLAG, 24
+#define XHCI_PORT_WCE           portsc, 32,  FLAG, 25
+#define XHCI_PORT_WDE           portsc, 32,  FLAG, 26
+#define XHCI_PORT_WOE           portsc, 32,  FLAG, 27
+#define XHCI_PORT_DR            portsc, 32,  FLAG, 30
+#define XHCI_PORT_WPR           portsc, 32,  FLAG, 31
+
+#define XHCI_PORT_USB3_U1TO   portpmsc, 32, RANGE,  7,  0
+#define XHCI_PORT_USB3_U2TO   portpmsc, 32, RANGE, 15,  8
+#define XHCI_PORT_USB3_FLPMA  portpmsc, 32,  FLAG, 16
+#define XHCI_PORT_USB3_LEC      portli, 32, RANGE, 15,  0
+#define XHCI_PORT_USB3_RLC      portli, 32, RANGE, 19, 16
+#define XHCI_PORT_USB3_TLC      portli, 32, RANGE, 23, 20
+
+#define XHCI_PORT_USB2_L1S    portpmsc, 32, RANGE,  2,  0
+#define XHCI_PORT_USB2_RWE    portpmsc, 32,  FLAG,  3
+#define XHCI_PORT_USB2_BESL   portpmsc, 32, RANGE,  7,  4
+#define XHCI_PORT_USB2_L1DS   portpmsc, 32, RANGE, 15,  8
+#define XHCI_PORT_USB2_HLE    portpmsc, 32,  FLAG, 16
+#define XHCI_PORT_USB2_TM     portpmsc, 32, RANGE, 31, 28
+#define XHCI_PORT_USB2_HIRDM porthlpmc, 32, RANGE,  1,  0
+#define XHCI_PORT_USB2_L1TO  porthlpmc, 32, RANGE,  9,  2
+#define XHCI_PORT_USB2_BESLD porthlpmc, 32, RANGE, 13, 10
+
+/**
+ * XHCI Operational Registers: section 5.4
+ */
+typedef struct xhci_op_regs {
+
+	/*
+	 *            3    2     1   0
+	 *  3:0  - HSEE INTE HCRST R/S
+	 *
+	 *           11  10   9   8      7
+	 * 11:7  - EU3S EWE CRS CSS LHCRST
+	 *    13 - CME
+	 */
+	ioport32_t usbcmd;
+
+	/*
+	 *          4    3   2 1   0
+	 *  4:0 - PCD EINT HSE _ HCH
+	 *
+	 *        12  11  10   9   8
+	 * 12:8 - HCE CNR SRE RSS SSS
+	 */
+	ioport32_t usbsts;
+
+	/*
+	 * Bitmask of page sizes supported: 128M .. 4K
+	 */
+	ioport32_t pagesize;
+
+	PADD32[2];
+
+	/*
+	 * 15:0 - Notification enable
+	 */
+	ioport32_t dnctrl;
+
+	/*          3  2  1   0
+	 *  3:0 - CRR CA CS RCS
+	 * 64:6 - Command Ring Pointer
+	 */
+	ioport32_t crcr_lo;
+	ioport32_t crcr_hi;
+
+	PADD32[4];
+
+	ioport32_t dcbaap_lo;
+	ioport32_t dcbaap_hi;
+
+	/*
+	 * 7:0 - MaxSlotsEn
+	 *   8 - U3E
+	 *   9 - CIE
+	 */
+	ioport32_t config;
+
+	/* Offset of portrs from op_regs addr is 0x400. */
+	PADD32[241];
+
+	/*
+	 * Individual ports register sets
+	 */
+	xhci_port_regs_t portrs[256];
+} xhci_op_regs_t;
+
+#define XHCI_OP_RS              usbcmd, 32,  FLAG,  0
+#define XHCI_OP_HCRST           usbcmd, 32,  FLAG,  1
+#define XHCI_OP_INTE            usbcmd, 32,  FLAG,  2
+#define XHCI_OP_HSEE            usbcmd, 32,  FLAG,  3
+#define XHCI_OP_LHCRST          usbcmd, 32,  FLAG,  7
+#define XHCI_OP_CSS             usbcmd, 32,  FLAG,  8
+#define XHCI_OP_CRS             usbcmd, 32,  FLAG,  9
+#define XHCI_OP_EWE             usbcmd, 32,  FLAG, 10
+#define XHCI_OP_EU3S            usbcmd, 32,  FLAG, 11
+#define XHCI_OP_CME             usbcmd, 32,  FLAG, 13
+#define XHCI_OP_HCH             usbsts, 32,  FLAG,  0
+#define XHCI_OP_HSE             usbsts, 32,  FLAG,  2
+#define XHCI_OP_EINT            usbsts, 32,  FLAG,  3
+#define XHCI_OP_PCD             usbsts, 32,  FLAG,  4
+#define XHCI_OP_SSS             usbsts, 32,  FLAG,  8
+#define XHCI_OP_RSS             usbsts, 32,  FLAG,  9
+#define XHCI_OP_SRE             usbsts, 32,  FLAG, 10
+#define XHCI_OP_CNR             usbsts, 32,  FLAG, 11
+#define XHCI_OP_HCE             usbsts, 32,  FLAG, 12
+#define XHCI_OP_PAGESIZE      pagesize, 32, FIELD
+#define XHCI_OP_NOTIFICATION    dnctrl, 32, RANGE, 15, 0
+#define XHCI_OP_RCS            crcr_lo, 32,  FLAG, 0
+#define XHCI_OP_CS             crcr_lo, 32,  FLAG, 1
+#define XHCI_OP_CA             crcr_lo, 32,  FLAG, 2
+#define XHCI_OP_CRR            crcr_lo, 32,  FLAG, 3
+/*
+ * This shall be RANGE, 6, 0, but the value containing CR pointer and RCS flag
+ * must be written at once.
+ */
+#define XHCI_OP_CRCR_LO        crcr_lo, 32, FIELD
+#define XHCI_OP_CRCR_HI        crcr_hi, 32, FIELD
+#define XHCI_OP_DCBAAP_LO    dcbaap_lo, 32, FIELD
+#define XHCI_OP_DCBAAP_HI    dcbaap_hi, 32, FIELD
+#define XHCI_OP_MAX_SLOTS_EN    config, 32, RANGE, 7, 0
+#define XHCI_OP_U3E             config, 32,  FLAG, 8
+#define XHCI_OP_CIE             config, 32,  FLAG, 9
+
+/* Aggregating field to read & write whole status at once */
+#define XHCI_OP_STATUS          usbsts, 32, RANGE, 12, 0
+
+/* RW1C fields in usbsts */
+#define XHCI_STATUS_ACK_MASK     0x41C
+
+/**
+ * Interrupter Register Set: section 5.5.2
+ */
+typedef struct xhci_interrupter_regs {
+	/*
+	 * 0 - Interrupt Pending
+	 * 1 - Interrupt Enable
+	 */
+	ioport32_t iman;
+
+	/*
+	 * 15:0  - Interrupt Moderation Interval
+	 * 31:16 - Interrupt Moderation Counter
+	 */
+	ioport32_t imod;
+
+	ioport32_t erstsz;
+
+	PADD32;
+
+	ioport32_t erstba_lo;
+	ioport32_t erstba_hi;
+
+	/*
+	 *  2:0 - Dequeue ERST Segment Index
+	 *    3 - Event Handler Busy
+	 * 63:4 - Event Ring Dequeue Pointer
+	 */
+	ioport32_t erdp_lo;
+	ioport32_t erdp_hi;
+} xhci_interrupter_regs_t;
+
+#define XHCI_INTR_IP              iman, 32,  FLAG,  0
+#define XHCI_INTR_IE              iman, 32,  FLAG,  1
+#define XHCI_INTR_IMI             imod, 32, RANGE, 15, 0
+#define XHCI_INTR_IMC             imod, 32, RANGE, 31, 16
+#define XHCI_INTR_ERSTSZ        erstsz, 32, FIELD
+#define XHCI_INTR_ERSTBA_LO  erstba_lo, 32, FIELD
+#define XHCI_INTR_ERSTBA_HI  erstba_hi, 32, FIELD
+#define XHCI_INTR_ERDP_ESI     erdp_lo, 32, RANGE,  2, 0
+#define XHCI_INTR_ERDP_EHB     erdp_lo, 32,  FLAG,  3
+#define XHCI_INTR_ERDP_LO      erdp_lo, 32, FIELD
+#define XHCI_INTR_ERDP_HI      erdp_hi, 32, FIELD
+
+/**
+ * XHCI Runtime registers: section 5.5
+ */
+typedef struct xhci_rt_regs {
+	ioport32_t mfindex;
+
+	PADD32 [7];
+
+	xhci_interrupter_regs_t ir [];
+} xhci_rt_regs_t;
+
+#define XHCI_RT_MFINDEX        mfindex, 32, RANGE, 13, 0
+#define XHCI_MFINDEX_MAX	(1 << 14)
+
+/**
+ * XHCI Doorbell Registers: section 5.6
+ *
+ * These registers are to be written as a whole field.
+ */
+typedef ioport32_t xhci_doorbell_t;
+
+enum xhci_plt {
+	XHCI_PSI_PLT_SYMM,
+	XHCI_PSI_PLT_RSVD,
+	XHCI_PSI_PLT_RX,
+	XHCI_PSI_PLT_TX
+};
+
+/**
+ * Protocol speed ID: section 7.2.1
+ */
+typedef struct xhci_psi {
+	xhci_dword_t psi;
+} xhci_psi_t;
+
+#define XHCI_PSI_PSIV    psi, 32, RANGE,  3,  0
+#define XHCI_PSI_PSIE    psi, 32, RANGE,  5,  4
+#define XHCI_PSI_PLT     psi, 32, RANGE,  7,  6
+#define XHCI_PSI_PFD     psi, 32,  FLAG,  8
+#define XHCI_PSI_PSIM    psi, 32, RANGE, 31, 16
+
+enum xhci_extcap_type {
+	XHCI_EC_RESERVED = 0,
+	XHCI_EC_USB_LEGACY,
+	XHCI_EC_SUPPORTED_PROTOCOL,
+	XHCI_EC_EXTENDED_POWER_MANAGEMENT,
+	XHCI_EC_IOV,
+	XHCI_EC_MSI,
+	XHCI_EC_LOCALMEM,
+	XHCI_EC_DEBUG = 10,
+	XHCI_EC_MSIX = 17,
+	XHCI_EC_MAX = 255
+};
+
+/**
+ * xHCI Extended Capability: section 7
+ */
+typedef struct xhci_extcap {
+	xhci_dword_t header;
+	xhci_dword_t cap_specific[];
+} xhci_extcap_t;
+
+#define XHCI_EC_CAP_ID                  header, 32, RANGE,  7,  0
+#define XHCI_EC_SIZE                    header, 32, RANGE, 15,  8
+
+/* Supported protocol */
+#define XHCI_EC_SP_MINOR                header, 32, RANGE, 23, 16
+#define XHCI_EC_SP_MAJOR                header, 32, RANGE, 31, 24
+#define XHCI_EC_SP_NAME        cap_specific[0], 32, FIELD
+#define XHCI_EC_SP_CP_OFF      cap_specific[1], 32, RANGE,  7,  0
+#define XHCI_EC_SP_CP_COUNT    cap_specific[1], 32, RANGE, 15,  8
+#define XHCI_EC_SP_PSIC        cap_specific[1], 32, RANGE, 31, 28
+#define XHCI_EC_SP_SLOT_TYPE   cap_specific[2], 32, RANGE,  4,  0
+
+typedef union {
+	char str [4];
+	uint32_t packed;
+} xhci_sp_name_t;
+
+static const xhci_sp_name_t xhci_name_usb = {
+	.str = "USB "
+};
+
+static inline xhci_extcap_t *xhci_extcap_next(const xhci_extcap_t *cur)
+{
+	unsigned dword_offset = XHCI_REG_RD(cur, XHCI_EC_SIZE);
+	if (!dword_offset)
+		return NULL;
+	return (xhci_extcap_t *) (((xhci_dword_t *) cur) + dword_offset);
+}
+
+static inline xhci_psi_t *xhci_extcap_psi(const xhci_extcap_t *ec, unsigned psid)
+{
+	assert(XHCI_REG_RD(ec, XHCI_EC_CAP_ID) == XHCI_EC_SUPPORTED_PROTOCOL);
+	assert(XHCI_REG_RD(ec, XHCI_EC_SP_PSIC) > psid);
+
+	unsigned dword_offset = 4 + psid;
+	return (xhci_psi_t *) (((xhci_dword_t *) ec) + dword_offset);
+}
+
+/**
+ * USB Legacy Support: section 7.1
+ *
+ * Legacy support have an exception from dword-access, because it needs to be
+ * byte-accessed.
+ */
+typedef struct xhci_extcap_legsup {
+	ioport8_t cap_id;
+	ioport8_t size;			/**< Next Capability Pointer */
+	ioport8_t sem_bios;
+	ioport8_t sem_os;
+
+	/** USB Legacy Support Control/Status - RW for BIOS, RO for OS */
+	xhci_dword_t usblegctlsts;
+} xhci_legsup_t;
+
+#define XHCI_LEGSUP_SEM_BIOS	sem_bios, 8, FLAG, 0
+#define XHCI_LEGSUP_SEM_OS	sem_os, 8, FLAG, 0
+
+/* 4.22.1 BIOS may take up to 1 second to release the device */
+#define XHCI_LEGSUP_BIOS_TIMEOUT_US   1000000
+#define XHCI_LEGSUP_POLLING_DELAY_1MS    1000
+
+#endif
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/hw_struct/trb.h
===================================================================
--- uspace/drv/bus/usb/xhci/hw_struct/trb.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/hw_struct/trb.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2017 Ondrej Hlavaty
+ * 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
+ * TRB-related structures of the xHC.
+ *
+ * This file contains all the types of TRB and the TRB ring handling.
+ */
+
+#ifndef XHCI_TRB_H
+#define XHCI_TRB_H
+
+#include "common.h"
+#include <libarch/barrier.h>
+
+/**
+ * TRB types: section 6.4.6, table 139
+ */
+enum xhci_trb_type {
+	XHCI_TRB_TYPE_RESERVED = 0,
+
+// Transfer ring:
+	XHCI_TRB_TYPE_NORMAL,
+	XHCI_TRB_TYPE_SETUP_STAGE,
+	XHCI_TRB_TYPE_DATA_STAGE,
+	XHCI_TRB_TYPE_STATUS_STAGE,
+	XHCI_TRB_TYPE_ISOCH,
+	XHCI_TRB_TYPE_LINK,
+	XHCI_TRB_TYPE_EVENT_DATA,
+	XHCI_TRB_TYPE_NO_OP,
+
+// Command ring:
+	XHCI_TRB_TYPE_ENABLE_SLOT_CMD,
+	XHCI_TRB_TYPE_DISABLE_SLOT_CMD,
+	XHCI_TRB_TYPE_ADDRESS_DEVICE_CMD,
+	XHCI_TRB_TYPE_CONFIGURE_ENDPOINT_CMD,
+	XHCI_TRB_TYPE_EVALUATE_CONTEXT_CMD,
+	XHCI_TRB_TYPE_RESET_ENDPOINT_CMD,
+	XHCI_TRB_TYPE_STOP_ENDPOINT_CMD,
+	XHCI_TRB_TYPE_SET_TR_DEQUEUE_POINTER_CMD,
+	XHCI_TRB_TYPE_RESET_DEVICE_CMD,
+	XHCI_TRB_TYPE_FORCE_EVENT_CMD,
+	XHCI_TRB_TYPE_NEGOTIATE_BANDWIDTH_CMD,
+	XHCI_TRB_TYPE_SET_LATENCY_TOLERANCE_VALUE_CMD,
+	XHCI_TRB_TYPE_GET_PORT_BANDWIDTH_CMD,
+	XHCI_TRB_TYPE_FORCE_HEADER_CMD,
+	XHCI_TRB_TYPE_NO_OP_CMD,
+// Reserved: 24-31
+
+// Event ring:
+	XHCI_TRB_TYPE_TRANSFER_EVENT = 32,
+	XHCI_TRB_TYPE_COMMAND_COMPLETION_EVENT,
+	XHCI_TRB_TYPE_PORT_STATUS_CHANGE_EVENT,
+	XHCI_TRB_TYPE_BANDWIDTH_REQUEST_EVENT,
+	XHCI_TRB_TYPE_DOORBELL_EVENT,
+	XHCI_TRB_TYPE_HOST_CONTROLLER_EVENT,
+	XHCI_TRB_TYPE_DEVICE_NOTIFICATION_EVENT,
+	XHCI_TRB_TYPE_MFINDEX_WRAP_EVENT,
+
+	XHCI_TRB_TYPE_MAX
+};
+
+/**
+ * TRB template: section 4.11.1
+ */
+typedef struct xhci_trb {
+	xhci_qword_t parameter;
+	xhci_dword_t status;
+	xhci_dword_t control;
+} __attribute__((packed)) __attribute__((aligned(16))) xhci_trb_t;
+
+#define TRB_TYPE(trb)           XHCI_DWORD_EXTRACT((trb).control, 15, 10)
+#define TRB_CYCLE(trb)          XHCI_DWORD_EXTRACT((trb).control, 0, 0)
+#define TRB_LINK_TC(trb)        XHCI_DWORD_EXTRACT((trb).control, 1, 1)
+#define TRB_IOC(trb)            XHCI_DWORD_EXTRACT((trb).control, 5, 5)
+#define TRB_EVENT_DATA(trb)		XHCI_DWORD_EXTRACT((trb).control, 2, 2)
+
+#define TRB_TRANSFER_LENGTH(trb)	XHCI_DWORD_EXTRACT((trb).status, 23, 0)
+#define TRB_COMPLETION_CODE(trb)	XHCI_DWORD_EXTRACT((trb).status, 31, 24)
+
+#define TRB_LINK_SET_TC(trb, val) \
+	xhci_dword_set_bits(&(trb).control, val, 1, 1)
+#define TRB_SET_CYCLE(trb, val) \
+	xhci_dword_set_bits(&(trb).control, val, 0, 0)
+
+#define TRB_CTRL_SET_SETUP_WLENGTH(trb, val) \
+	xhci_qword_set_bits(&(trb).parameter, val, 63, 48)
+#define TRB_CTRL_SET_SETUP_WINDEX(trb, val) \
+	xhci_qword_set_bits(&(trb).parameter, val, 47, 32)
+#define TRB_CTRL_SET_SETUP_WVALUE(trb, val) \
+	xhci_qword_set_bits(&(trb).parameter, val, 31, 16)
+#define TRB_CTRL_SET_SETUP_BREQ(trb, val) \
+	xhci_qword_set_bits(&(trb).parameter, val, 15, 8)
+#define TRB_CTRL_SET_SETUP_BMREQTYPE(trb, val) \
+	xhci_qword_set_bits(&(trb).parameter, val, 7, 0)
+
+#define TRB_CTRL_SET_TD_SIZE(trb, val) \
+	xhci_dword_set_bits(&(trb).status, val, 21, 17)
+#define TRB_CTRL_SET_XFER_LEN(trb, val) \
+	xhci_dword_set_bits(&(trb).status, val, 16, 0)
+
+#define TRB_CTRL_SET_ENT(trb, val) \
+	xhci_dword_set_bits(&(trb).control, val, 1, 1)
+#define TRB_CTRL_SET_ISP(trb, val) \
+	xhci_dword_set_bits(&(trb).control, val, 2, 2)
+#define TRB_CTRL_SET_NS(trb, val) \
+	xhci_dword_set_bits(&(trb).control, val, 3, 3)
+#define TRB_CTRL_SET_CHAIN(trb, val) \
+	xhci_dword_set_bits(&(trb).control, val, 4, 4)
+#define TRB_CTRL_SET_IOC(trb, val) \
+	xhci_dword_set_bits(&(trb).control, val, 5, 5)
+#define TRB_CTRL_SET_IDT(trb, val) \
+	xhci_dword_set_bits(&(trb).control, val, 6, 6)
+
+#define TRB_CTRL_SET_TRB_TYPE(trb, val) \
+	xhci_dword_set_bits(&(trb).control, val, 15, 10)
+#define TRB_CTRL_SET_DIR(trb, val) \
+	xhci_dword_set_bits(&(trb).control, val, 16, 16)
+#define TRB_CTRL_SET_TRT(trb, val) \
+	xhci_dword_set_bits(&(trb).control, val, 17, 16)
+
+#define TRB_ISOCH_SET_TBC(trb, val) \
+	xhci_dword_set_bits(&(trb).control, val, 8, 7)
+#define TRB_ISOCH_SET_TLBPC(trb, val) \
+	xhci_dword_set_bits(&(trb).control, val, 19, 16)
+#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)
+
+/**
+ * The Chain bit is valid only in specific TRB types.
+ */
+static inline bool xhci_trb_is_chained(xhci_trb_t *trb) {
+	const int type = TRB_TYPE(*trb);
+	const bool chain_bit = XHCI_DWORD_EXTRACT(trb->control, 4, 4);
+
+	return chain_bit &&
+	    (type == XHCI_TRB_TYPE_NORMAL
+	    || type == XHCI_TRB_TYPE_DATA_STAGE
+	    || type == XHCI_TRB_TYPE_STATUS_STAGE
+	    || type == XHCI_TRB_TYPE_ISOCH);
+}
+
+static inline void xhci_trb_link_fill(xhci_trb_t *trb, uintptr_t next_phys)
+{
+	// TRBs require 16-byte alignment
+	assert((next_phys & 0xf) == 0);
+
+	xhci_dword_set_bits(&trb->control, XHCI_TRB_TYPE_LINK, 15, 10);
+	xhci_qword_set(&trb->parameter, next_phys);
+}
+
+static inline void xhci_trb_copy_to_pio(xhci_trb_t *dst, xhci_trb_t *src)
+{
+	/*
+	 * As we do not know, whether our architecture is capable of copying 16
+	 * bytes atomically, let's copy the fields one by one.
+	 */
+	dst->parameter = src->parameter;
+	dst->status = src->status;
+
+	write_barrier();
+
+	dst->control = src->control;
+}
+
+static inline void xhci_trb_clean(xhci_trb_t *trb)
+{
+	memset(trb, 0, sizeof(*trb));
+}
+
+/**
+ * Event Ring Segment Table: section 6.5
+ */
+typedef struct xhci_erst_entry {
+	xhci_qword_t rs_base_ptr;       /* 64B aligned */
+	xhci_dword_t size;              /* only low 16 bits, the rest is RsvdZ */
+	xhci_dword_t _reserved;
+} xhci_erst_entry_t;
+
+static inline void xhci_fill_erst_entry(xhci_erst_entry_t *entry,
+    uintptr_t phys, int segments)
+{
+	xhci_qword_set(&entry->rs_base_ptr, phys);
+	xhci_dword_set_bits(&entry->size, segments, 16, 0);
+}
+
+typedef enum xhci_trb_completion_code {
+	XHCI_TRBC_INVALID = 0,
+	XHCI_TRBC_SUCCESS,
+	XHCI_TRBC_DATA_BUFFER_ERROR,
+	XHCI_TRBC_BABBLE_DETECTED_ERROR,
+	XHCI_TRBC_USB_TRANSACTION_ERROR,
+	XHCI_TRBC_TRB_ERROR,
+	XHCI_TRBC_STALL_ERROR,
+	XHCI_TRBC_RESOURCE_ERROR,
+	XHCI_TRBC_BANDWIDTH_ERROR,
+	XHCI_TRBC_NO_SLOTS_ERROR,
+	XHCI_TRBC_INVALID_STREAM_ERROR,
+	XHCI_TRBC_SLOT_NOT_ENABLED_ERROR,
+	XHCI_TRBC_EP_NOT_ENABLED_ERROR,
+	XHCI_TRBC_SHORT_PACKET,
+	XHCI_TRBC_RING_UNDERRUN,
+	XHCI_TRBC_RING_OVERRUN,
+	XHCI_TRBC_VF_EVENT_RING_FULL,
+	XHCI_TRBC_PARAMETER_ERROR,
+	XHCI_TRBC_BANDWIDTH_OVERRUN_ERROR,
+	XHCI_TRBC_CONTEXT_STATE_ERROR,
+	XHCI_TRBC_NO_PING_RESPONSE_ERROR,
+	XHCI_TRBC_EVENT_RING_FULL_ERROR,
+	XHCI_TRBC_INCOMPATIBLE_DEVICE_ERROR,
+	XHCI_TRBC_MISSED_SERVICE_ERROR,
+	XHCI_TRBC_COMMAND_RING_STOPPED,
+	XHCI_TRBC_COMMAND_ABORTED,
+	XHCI_TRBC_STOPPED,
+	XHCI_TRBC_STOPPED_LENGTH_INVALID,
+	XHCI_TRBC_STOPPED_SHORT_PACKET,
+	XHCI_TRBC_MAX_EXIT_LATENCY_TOO_LARGE_ERROR,
+	/* 30 reserved */
+	XHCI_TRBC_ISOCH_BUFFER_OVERRUN = 31,
+	XHCI_TRBC_EVENT_LOST_ERROR,
+	XHCI_TRBC_UNDEFINED_ERROR,
+	XHCI_TRBC_INVALID_STREAM_ID_ERROR,
+	XHCI_TRBC_SECONDARY_BANDWIDTH_ERROR,
+	XHCI_TRBC_SPLIT_TRANSACTION_ERROR,
+	XHCI_TRBC_MAX
+	/**
+	 *  37 - 191 reserved
+	 * 192 - 223 vendor defined error
+	 * 224 - 255 vendor defined info
+	 */
+} xhci_trb_completion_code_t;
+
+#endif
Index: uspace/drv/bus/usb/xhci/isoch.c
===================================================================
--- uspace/drv/bus/usb/xhci/isoch.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/isoch.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,669 @@
+/*
+ * 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 <macros.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);
+
+	const xhci_hc_t *hc = bus_to_xhci_bus(ep->base.device->bus)->hc;
+
+	/*
+	 * We shall cover at least twice the IST period, otherwise we will get
+	 * an over/underrun every time.
+	 */
+	isoch->buffer_count = (2 * hc->ist) / ep->interval;
+
+	/* 2 buffers are the very minimum. */
+	isoch->buffer_count = max(2, isoch->buffer_count);
+
+	usb_log_debug("[isoch] isoch setup with %zu buffers", isoch->buffer_count);
+}
+
+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 < isoch->buffer_count; ++i) {
+		isoch->transfers[i].state = ISOCH_EMPTY;
+	}
+
+	fibril_timer_clear_locked(isoch->feeding_timer);
+	isoch->last_mf = -1U;
+	usb_log_info("[isoch] Endpoint" XHCI_EP_FMT ": Data flow reset.",
+	    XHCI_EP_ARGS(*ep));
+}
+
+static void isoch_reset_no_timer(xhci_endpoint_t *ep)
+{
+	xhci_isoch_t * const isoch = ep->isoch;
+	assert(fibril_mutex_is_locked(&isoch->guard));
+	/*
+	 * As we cannot clear timer when we are triggered by it,
+	 * we have to avoid doing it in common method.
+	 */
+	fibril_timer_clear_locked(isoch->reset_timer);
+	isoch_reset(ep);
+}
+
+static void isoch_reset_timer(void *ep) {
+	xhci_isoch_t * const isoch = xhci_endpoint_get(ep)->isoch;
+	fibril_mutex_lock(&isoch->guard);
+	isoch_reset(ep);
+	fibril_mutex_unlock(&isoch->guard);
+}
+
+/*
+ * Fast transfers could trigger the reset timer before the data is processed,
+ * leading into false reset.
+ */
+#define RESET_TIMER_DELAY 100000
+static void timer_schedule_reset(xhci_endpoint_t *ep) {
+	xhci_isoch_t * const isoch = ep->isoch;
+	const suseconds_t delay = isoch->buffer_count * ep->interval * 125
+	    + RESET_TIMER_DELAY;
+
+	fibril_timer_clear_locked(isoch->reset_timer);
+	fibril_timer_set_locked(isoch->reset_timer, delay,
+		isoch_reset_timer, 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);
+		fibril_timer_clear(isoch->reset_timer);
+		fibril_timer_destroy(isoch->reset_timer);
+	}
+
+	if (isoch->transfers) {
+		for (size_t i = 0; i < isoch->buffer_count; ++i)
+			dma_buffer_free(&isoch->transfers[i].data);
+		free(isoch->transfers);
+	}
+}
+
+/**
+ * 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);
+	isoch->reset_timer = fibril_timer_create(&isoch->guard);
+	if (!isoch->feeding_timer)
+		return ENOMEM;
+
+	isoch->transfers = calloc(isoch->buffer_count, sizeof(xhci_isoch_transfer_t));
+	if(!isoch->transfers)
+		goto err;
+
+	for (size_t i = 0; i < isoch->buffer_count; ++i) {
+		xhci_isoch_transfer_t *transfer = &isoch->transfers[i];
+		if (dma_buffer_alloc(&transfer->data, ep->base.max_transfer_size)) {
+			goto err;
+		}
+	}
+
+	fibril_mutex_lock(&isoch->guard);
+	isoch_reset_no_timer(ep);
+	fibril_mutex_unlock(&isoch->guard);
+
+	return EOK;
+err:
+	isoch_fini(ep);
+	return ENOMEM;
+}
+
+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;
+}
+
+/** The number of bits the MFINDEX is stored in at HW */
+#define EPOCH_BITS 14
+/** The delay in usec for the epoch wrap */
+#define EPOCH_DELAY 500000
+/** The amount of microframes the epoch is checked for a delay */
+#define EPOCH_LOW_MFINDEX 8 * 100
+
+static inline uint64_t get_system_time()
+{
+	struct timeval tv;
+	getuptime(&tv);
+	return ((uint64_t) tv.tv_sec) * 1000000 + ((uint64_t) tv.tv_usec);
+}
+
+static inline uint64_t get_current_microframe(const xhci_hc_t *hc)
+{
+	const uint32_t reg_mfindex = XHCI_REG_RD(hc->rt_regs, XHCI_RT_MFINDEX);
+	/*
+	 * If the mfindex is low and the time passed since last mfindex wrap is too
+	 * high, we have entered the new epoch already (and haven't received event
+	 * yet).
+	 */
+	uint64_t epoch = hc->wrap_count;
+	if (reg_mfindex < EPOCH_LOW_MFINDEX
+	    && get_system_time() - hc->wrap_time > EPOCH_DELAY) {
+		++epoch;
+	}
+	return (epoch << EPOCH_BITS) + reg_mfindex;
+}
+
+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_mf == -1U) {
+		const xhci_bus_t *bus = bus_to_xhci_bus(ep->base.device->bus);
+		const xhci_hc_t *hc = bus->hc;
+
+		/*
+		 * Delay the first frame by some time to fill the buffer, but at most 10
+		 * miliseconds.
+		 */
+		const uint64_t delay = min(isoch->buffer_count * ep->interval, 10 * 8);
+		it->mfindex = get_current_microframe(hc) + 1 + delay + hc->ist;
+
+		// Align to ESIT start boundary
+		it->mfindex += ep->interval - 1;
+		it->mfindex &= ~(ep->interval - 1);
+	} else {
+		it->mfindex = isoch->last_mf + ep->interval;
+	}
+}
+
+/** 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;
+	uint64_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,
+    uint64_t mfindex)
+{
+	const uint64_t current_mf = get_current_microframe(hc);
+	const uint64_t start = current_mf + hc->ist + 1;
+	const uint64_t end = current_mf + END_FRAME_DELAY;
+
+	if (mfindex < start) {
+		res->position = WINDOW_TOO_LATE;
+		res->offset = start - mfindex;
+	} else if (mfindex <= end) {
+		res->position = WINDOW_INSIDE;
+	} else {
+		res->position = WINDOW_TOO_SOON;
+		res->offset = mfindex - end;
+	}
+}
+
+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->transfers[isoch->hw_enqueue].state == ISOCH_FILLED) {
+		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_debug("[isoch] delaying feeding buffer %zu for %ldus",
+				it - isoch->transfers, delay);
+			fibril_timer_set_locked(isoch->feeding_timer, delay,
+			    isoch_feed_out_timer, ep);
+			goto out;
+		}
+
+		case WINDOW_INSIDE:
+			usb_log_debug("[isoch] feeding buffer %zu at 0x%llx",
+			    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) % isoch->buffer_count;
+			break;
+
+		case WINDOW_TOO_LATE:
+			/*
+			 * Missed the opportunity to schedule. Just mark this transfer as
+			 * skipped.
+			 */
+			usb_log_debug("[isoch] missed feeding buffer %zu at 0x%llx by "
+				"%llu 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) % isoch->buffer_count;
+			break;
+		}
+	}
+
+out:
+	if (fed) {
+		hc_ring_ep_doorbell(ep, 0);
+		/*
+		 * The ring may be dead. If no event happens until the delay, reset the
+		 * endpoint.
+		 */
+		timer_schedule_reset(ep);
+	}
+
+}
+
+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 = ep->base.max_transfer_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_debug("[isoch] delaying feeding buffer %zu for %ldus",
+			    it - isoch->transfers, delay);
+			fibril_timer_set_locked(isoch->feeding_timer, delay,
+			    isoch_feed_in_timer, ep);
+			goto out;
+		}
+
+		case WINDOW_TOO_LATE:
+			usb_log_debug("[isoch] missed feeding buffer %zu at 0x%llx by"
+				"%llu 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) % isoch->buffer_count;
+			isoch->last_mf = it->mfindex;
+
+			usb_log_debug("[isoch] feeding buffer %zu at 0x%llx",
+			    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;
+		}
+	}
+out:
+
+	if (fed) {
+		hc_ring_ep_doorbell(ep, 0);
+		/*
+		 * The ring may be dead. If no event happens until the delay, reset the
+		 * endpoint.
+		 */
+		timer_schedule_reset(ep);
+	}
+}
+
+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 > ep->base.max_transfer_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) % isoch->buffer_count;
+
+	/* Withdraw results from previous transfers. */
+	transfer->batch.transferred_size = 0;
+	xhci_isoch_transfer_t *res = &isoch->transfers[isoch->dequeue];
+	while (res->state == ISOCH_COMPLETE) {
+		isoch->dequeue = (isoch->dequeue + 1) % isoch->buffer_count;
+
+		res->state = ISOCH_EMPTY;
+		transfer->batch.transferred_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_mf = it->mfindex;
+	usb_log_debug("[isoch] buffer %zu will be on schedule at 0x%llx",
+	    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 < ep->base.max_transfer_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_debug("[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) % isoch->buffer_count;
+
+	/* Withdraw results from previous transfer. */
+	if (!it->error) {
+		memcpy(transfer->batch.buffer, it->data.virt, it->size);
+		transfer->batch.transferred_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;
+}
+
+void 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.
+			 * In either case, reset the ring.
+			 */
+			usb_log_warning("Ring over/underrun.");
+			isoch_reset_no_timer(ep);
+			fibril_condvar_broadcast(&ep->isoch->avail);
+			fibril_mutex_unlock(&ep->isoch->guard);
+			goto out;
+		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;
+	}
+
+	/*
+	 * The order of delivering events is not necessarily the one we would
+	 * expect. It is safer to walk the list of our transfers and check
+	 * which one it is.
+	 * To minimize the amount of transfers checked, we start at dequeue pointer
+	 * and exit the loop as soon as the transfer is found.
+	 */
+	bool found_mine = false;
+	for (size_t i = 0, di = isoch->dequeue; i < isoch->buffer_count; ++i, ++di) {
+		/* Wrap it back to 0, don't use modulo every loop traversal */
+		if (di == isoch->buffer_count) {
+			di = 0;
+		}
+
+		xhci_isoch_transfer_t * const it = &isoch->transfers[di];
+
+		if (it->state == ISOCH_FED && it->interrupt_trb_phys == trb->parameter) {
+			usb_log_debug("[isoch] buffer %zu completed", it - isoch->transfers);
+			it->state = ISOCH_COMPLETE;
+			it->size -= TRB_TRANSFER_LENGTH(*trb);
+			it->error = err;
+			found_mine = true;
+			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, we set a timer to
+	 * reset it after the buffers should have been consumed. If there
+	 * is no issue, the timer will get restarted often enough.
+	 */
+	timer_schedule_reset(ep);
+
+out:
+	fibril_condvar_broadcast(&ep->isoch->avail);
+	fibril_mutex_unlock(&ep->isoch->guard);
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/isoch.h
===================================================================
--- uspace/drv/bus/usb/xhci/isoch.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/isoch.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,131 @@
+/*
+ * 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 */
+	uint64_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;
+
+	/** Resets endpoint if there is no traffic. */
+	fibril_timer_t *reset_timer;
+
+	/** The maximum size of an isochronous transfer and therefore the size of buffers */
+	size_t max_size;
+
+	/** The microframe at which the last TRB was scheduled. */
+	uint64_t last_mf;
+
+	/** The number of transfer buffers allocated */
+	size_t buffer_count;
+
+	/** Isochronous scheduled transfers with respective buffers */
+	xhci_isoch_transfer_t *transfers;
+
+	/**
+	 * 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;
+
+extern void isoch_init(xhci_endpoint_t *, const usb_endpoint_descriptors_t *);
+extern void isoch_fini(xhci_endpoint_t *);
+extern int isoch_alloc_transfers(xhci_endpoint_t *);
+
+extern int isoch_schedule_out(xhci_transfer_t *);
+extern int isoch_schedule_in(xhci_transfer_t *);
+extern void isoch_handle_transfer_event(xhci_hc_t *, xhci_endpoint_t *, xhci_trb_t *);
+
+#endif
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/main.c
===================================================================
--- uspace/drv/bus/usb/xhci/main.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/main.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2017 Ondrej Hlavaty <aearsis@eideo.cz>
+ * 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
+ * Main routines of XHCI driver.
+ */
+
+#include <ddf/driver.h>
+#include <errno.h>
+#include <io/log.h>
+#include <io/logctl.h>
+#include <usb/debug.h>
+#include <usb/host/ddf_helpers.h>
+
+#include "hc.h"
+#include "rh.h"
+#include "endpoint.h"
+
+#define NAME "xhci"
+
+static inline xhci_hc_t *hcd_to_hc(hc_device_t *hcd)
+{
+	assert(hcd);
+	return (xhci_hc_t *) hcd;
+}
+
+static int hcd_hc_add(hc_device_t *hcd, const hw_res_list_parsed_t *hw_res)
+{
+	int err;
+	xhci_hc_t *hc = hcd_to_hc(hcd);
+	hc_device_setup(hcd, (bus_t *) &hc->bus);
+
+	if ((err = hc_init_mmio(hc, hw_res)))
+		return err;
+
+	if ((err = hc_init_memory(hc, hcd->ddf_dev)))
+		return err;
+
+	return EOK;
+}
+
+static int hcd_irq_code_gen(irq_code_t *code, hc_device_t *hcd, const hw_res_list_parsed_t *hw_res, int *irq)
+{
+	xhci_hc_t *hc = hcd_to_hc(hcd);
+	return hc_irq_code_gen(code, hc, hw_res, irq);
+}
+
+static int hcd_claim(hc_device_t *hcd)
+{
+	xhci_hc_t *hc = hcd_to_hc(hcd);
+	return hc_claim(hc, hcd->ddf_dev);
+}
+
+static int hcd_start(hc_device_t *hcd)
+{
+	xhci_hc_t *hc = hcd_to_hc(hcd);
+	return hc_start(hc, hcd->irq_cap >= 0);
+}
+
+static int hcd_hc_gone(hc_device_t *hcd)
+{
+	xhci_hc_t *hc = hcd_to_hc(hcd);
+	hc_fini(hc);
+	return EOK;
+}
+
+static const hc_driver_t xhci_driver = {
+	.name = NAME,
+	.hc_device_size = sizeof(xhci_hc_t),
+
+	.hc_add = hcd_hc_add,
+	.irq_code_gen = hcd_irq_code_gen,
+	.claim = hcd_claim,
+	.start = hcd_start,
+	.hc_gone = hcd_hc_gone,
+};
+
+/** Initializes global driver structures (NONE).
+ *
+ * @param[in] argc Nmber of arguments in argv vector (ignored).
+ * @param[in] argv Cmdline argument vector (ignored).
+ * @return Error code.
+ *
+ * Driver debug level is set here.
+ */
+int main(int argc, char *argv[])
+{
+	log_init(NAME);
+	logctl_set_log_level(NAME, LVL_DEBUG);
+	return hc_driver_main(&xhci_driver);
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/rh.c
===================================================================
--- uspace/drv/bus/usb/xhci/rh.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/rh.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,345 @@
+/*
+ * Copyright (c) 2017 Michal Staruch
+ * 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 roothub structures abstraction.
+ */
+
+#include <errno.h>
+#include <str_error.h>
+#include <usb/request.h>
+#include <usb/debug.h>
+#include <usb/host/bus.h>
+#include <usb/host/ddf_helpers.h>
+#include <usb/host/dma_buffer.h>
+#include <usb/host/hcd.h>
+#include <usb/port.h>
+
+#include "debug.h"
+#include "commands.h"
+#include "endpoint.h"
+#include "hc.h"
+#include "hw_struct/trb.h"
+#include "rh.h"
+#include "transfers.h"
+
+/* This mask only lists registers, which imply port change. */
+static const uint32_t port_events_mask =
+	XHCI_REG_MASK(XHCI_PORT_CSC) |
+	XHCI_REG_MASK(XHCI_PORT_PEC) |
+	XHCI_REG_MASK(XHCI_PORT_WRC) |
+	XHCI_REG_MASK(XHCI_PORT_OCC) |
+	XHCI_REG_MASK(XHCI_PORT_PRC) |
+	XHCI_REG_MASK(XHCI_PORT_PLC) |
+	XHCI_REG_MASK(XHCI_PORT_CEC);
+
+static const uint32_t port_reset_mask =
+	XHCI_REG_MASK(XHCI_PORT_WRC) |
+	XHCI_REG_MASK(XHCI_PORT_PRC);
+
+typedef struct rh_port {
+	usb_port_t base;
+	xhci_rh_t *rh;
+	uint8_t major;
+	xhci_port_regs_t *regs;
+	xhci_device_t *device;
+} rh_port_t;
+
+static int rh_worker(void *);
+
+/**
+ * Initialize the roothub subsystem.
+ */
+int xhci_rh_init(xhci_rh_t *rh, xhci_hc_t *hc)
+{
+	assert(rh);
+	assert(hc);
+
+	rh->hc = hc;
+	rh->max_ports = XHCI_REG_RD(hc->cap_regs, XHCI_CAP_MAX_PORTS);
+	rh->ports = calloc(rh->max_ports, sizeof(rh_port_t));
+	if (!rh->ports)
+		return ENOMEM;
+
+	const int err = bus_device_init(&rh->device.base, &rh->hc->bus.base);
+	if (err) {
+		free(rh->ports);
+		return err;
+	}
+
+	fid_t fid = fibril_create(&rh_worker, rh);
+	if (!fid) {
+		free(rh->ports);
+		return err;
+	}
+
+	for (unsigned i = 0; i < rh->max_ports; i++) {
+		usb_port_init(&rh->ports[i].base);
+		rh->ports[i].rh = rh;
+		rh->ports[i].regs = &rh->hc->op_regs->portrs[i];
+	}
+
+	/* Initialize route string */
+	rh->device.route_str = 0;
+
+	xhci_sw_ring_init(&rh->event_ring, rh->max_ports);
+
+	hc->event_fibril_completion.active = true;
+	fibril_mutex_initialize(&hc->event_fibril_completion.guard);
+	fibril_condvar_initialize(&hc->event_fibril_completion.cv);
+
+	fibril_add_ready(fid);
+
+	return EOK;
+}
+
+/**
+ * Finalize the RH subsystem.
+ */
+int xhci_rh_fini(xhci_rh_t *rh)
+{
+	assert(rh);
+	for (unsigned i = 0; i < rh->max_ports; i++)
+		usb_port_fini(&rh->ports[i].base);
+
+	xhci_sw_ring_stop(&rh->event_ring);
+
+	// TODO: completion_wait
+	fibril_mutex_lock(&rh->event_fibril_completion.guard);
+	while (rh->event_fibril_completion.active)
+		fibril_condvar_wait(&rh->event_fibril_completion.cv,
+		    &rh->event_fibril_completion.guard);
+	fibril_mutex_unlock(&rh->event_fibril_completion.guard);
+	xhci_sw_ring_fini(&rh->event_ring);
+	return EOK;
+}
+
+static rh_port_t *get_rh_port(usb_port_t *port)
+{
+	assert(port);
+	return (rh_port_t *) port;
+}
+
+/**
+ * Create and setup a device directly connected to RH. As the xHCI is not using
+ * a virtual usbhub device for RH, this routine is called for devices directly.
+ */
+static int rh_enumerate_device(usb_port_t *usb_port)
+{
+	int err;
+	rh_port_t *port = get_rh_port(usb_port);
+
+	if (port->major <= 2) {
+		/* USB ports for lower speeds needs their port reset first. */
+		XHCI_REG_SET(port->regs, XHCI_PORT_PR, 1);
+		if ((err = usb_port_wait_for_enabled(&port->base)))
+			return err;
+	} else {
+		/* Do the Warm reset to ensure the state is clear. */
+		XHCI_REG_SET(port->regs, XHCI_PORT_WPR, 1);
+		if ((err = usb_port_wait_for_enabled(&port->base)))
+			return err;
+	}
+
+	/*
+	 * We cannot know in advance, whether the speed in the status register
+	 * is valid - it depends on the protocol. So we read it later, but then
+	 * we have to check if the port is still enabled.
+	 */
+	uint32_t status = XHCI_REG_RD_FIELD(&port->regs->portsc, 32);
+
+	bool enabled = !!(status & XHCI_REG_MASK(XHCI_PORT_PED));
+	if (!enabled)
+		return ENOENT;
+
+	unsigned psiv = (status & XHCI_REG_MASK(XHCI_PORT_PS))
+	    >> XHCI_REG_SHIFT(XHCI_PORT_PS);
+	const usb_speed_t speed = port->rh->hc->speeds[psiv].usb_speed;
+
+	device_t *dev = hcd_ddf_fun_create(&port->rh->hc->base, speed);
+	if (!dev) {
+		usb_log_error("Failed to create USB device function.");
+		return ENOMEM;
+	}
+
+	dev->hub = &port->rh->device.base;
+	dev->tier = 1;
+	dev->port = port - port->rh->ports + 1;
+
+	port->device = xhci_device_get(dev);
+	port->device->rh_port = dev->port;
+
+	usb_log_debug("Enumerating new %s-speed device on port %u.",
+	    usb_str_speed(dev->speed), dev->port);
+
+	if ((err = bus_device_enumerate(dev))) {
+		usb_log_error("Failed to enumerate USB device: %s", str_error(err));
+		return err;
+	}
+
+	if (!ddf_fun_get_name(dev->fun)) {
+		bus_device_set_default_name(dev);
+	}
+
+	if ((err = ddf_fun_bind(dev->fun))) {
+		usb_log_error("Failed to register device " XHCI_DEV_FMT " DDF function: %s.",
+		    XHCI_DEV_ARGS(*port->device), str_error(err));
+		goto err_usb_dev;
+	}
+
+	return EOK;
+
+err_usb_dev:
+	hcd_ddf_fun_destroy(dev);
+	port->device = NULL;
+	return err;
+}
+
+/**
+ * Deal with a detached device.
+ */
+static void rh_remove_device(usb_port_t *usb_port)
+{
+	rh_port_t *port = get_rh_port(usb_port);
+
+	assert(port->device);
+	usb_log_info("Device " XHCI_DEV_FMT " at port %zu has been disconnected.",
+	    XHCI_DEV_ARGS(*port->device), port - port->rh->ports + 1);
+
+	/* Remove device from XHCI bus. */
+	bus_device_gone(&port->device->base);
+
+	/* Mark the device as detached. */
+	port->device = NULL;
+}
+
+/**
+ * Handle all changes on specified port.
+ */
+static void handle_port_change(xhci_rh_t *rh, uint8_t port_id)
+{
+	rh_port_t * const port = &rh->ports[port_id - 1];
+
+	uint32_t status = XHCI_REG_RD_FIELD(&port->regs->portsc, 32);
+
+	while (status & port_events_mask) {
+		/*
+		 * The PED bit in xHCI has RW1C semantics, which means that
+		 * writing 1 to it will disable the port. Which means all
+		 * standard mechanisms of register handling fails here.
+		 */
+		XHCI_REG_WR_FIELD(&port->regs->portsc,
+		    status & ~XHCI_REG_MASK(XHCI_PORT_PED), 32);
+
+		const bool connected = !!(status & XHCI_REG_MASK(XHCI_PORT_CCS));
+		const bool enabled = !!(status & XHCI_REG_MASK(XHCI_PORT_PED));
+
+		if (status & XHCI_REG_MASK(XHCI_PORT_CSC)) {
+			usb_log_info("Connected state changed on port %u.", port_id);
+			status &= ~XHCI_REG_MASK(XHCI_PORT_CSC);
+
+			if (connected) {
+				usb_port_connected(&port->base, &rh_enumerate_device);
+			} else {
+				usb_port_disabled(&port->base, &rh_remove_device);
+			}
+		}
+
+		if (status & port_reset_mask) {
+			status &= ~port_reset_mask;
+
+			if (enabled) {
+				usb_port_enabled(&port->base);
+			} else {
+				usb_port_disabled(&port->base, &rh_remove_device);
+			}
+		}
+
+		status &= port_events_mask;
+		if (status != 0)
+			usb_log_debug("RH port %u change not handled: 0x%x", port_id, status);
+		
+		/* Make sure that PSCEG is 0 before exiting the loop. */
+		status = XHCI_REG_RD_FIELD(&port->regs->portsc, 32);
+	}
+}
+
+void xhci_rh_set_ports_protocol(xhci_rh_t *rh,
+    unsigned offset, unsigned count, unsigned major)
+{
+	for (unsigned i = offset; i < offset + count; i++)
+		rh->ports[i - 1].major = major;
+}
+
+void xhci_rh_startup(xhci_rh_t *rh)
+{
+	/* The reset changed status of all ports, and SW originated reason does
+	 * not cause an interrupt.
+	 */
+	for (uint8_t i = 0; i < rh->max_ports; ++i) {
+		handle_port_change(rh, i + 1);
+
+		rh_port_t * const port = &rh->ports[i];
+
+		/*
+		 * When xHCI starts, for some reasons USB 3 ports do not have
+		 * the CSC bit, even though they are connected. Try to find
+		 * such ports.
+		 */
+		if (XHCI_REG_RD(port->regs, XHCI_PORT_CCS)
+		    && port->base.state == PORT_DISABLED)
+			usb_port_connected(&port->base, &rh_enumerate_device);
+	}
+}
+
+static int rh_worker(void *arg)
+{
+	xhci_rh_t * const rh = arg;
+
+	xhci_trb_t trb;
+	while (xhci_sw_ring_dequeue(&rh->event_ring, &trb) == EOK) {
+		uint8_t port_id = XHCI_QWORD_EXTRACT(trb.parameter, 31, 24);
+		usb_log_debug("Port status change event detected for port %u.", port_id);
+		handle_port_change(rh, port_id);
+	}
+
+	// TODO: completion_complete
+	fibril_mutex_lock(&rh->event_fibril_completion.guard);
+	rh->event_fibril_completion.active = false;
+	fibril_condvar_broadcast(&rh->event_fibril_completion.cv);
+	fibril_mutex_unlock(&rh->event_fibril_completion.guard);
+
+	return EOK;
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/rh.h
===================================================================
--- uspace/drv/bus/usb/xhci/rh.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/rh.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2017 Michal Staruch
+ * 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 roothub structures abstraction.
+ */
+
+#ifndef XHCI_RH_H
+#define XHCI_RH_H
+
+#include <usb/host/usb_transfer_batch.h>
+#include <usb/host/bus.h>
+
+#include "hw_struct/regs.h"
+#include "endpoint.h"
+
+typedef struct xhci_hc xhci_hc_t;
+typedef struct ddf_dev ddf_dev_t;
+
+/**
+ * xHCI lets the controller define speeds of ports it controls.
+ */
+typedef struct xhci_port_speed {
+	char name [4];
+	uint8_t major, minor;
+	uint64_t rx_bps, tx_bps;
+	usb_speed_t usb_speed;
+} xhci_port_speed_t;
+
+typedef struct hcd_roothub hcd_roothub_t;
+typedef struct xhci_bus xhci_bus_t;
+typedef struct rh_port rh_port_t;
+
+/* XHCI root hub instance */
+typedef struct {
+	/** Host controller */
+	xhci_hc_t *hc;
+
+	/* Root for the device tree */
+	xhci_device_t device;
+
+	/* Number of hub ports. */
+	size_t max_ports;
+
+	/* Array of port structures. (size is `max_ports`) */
+	rh_port_t *ports;
+
+	/* Event ring for roothub */
+	xhci_sw_ring_t event_ring;
+
+	struct {
+		fibril_mutex_t guard;
+		fibril_condvar_t cv;
+		bool active;
+	} event_fibril_completion;
+} xhci_rh_t;
+
+extern int xhci_rh_init(xhci_rh_t *, xhci_hc_t *);
+extern int xhci_rh_fini(xhci_rh_t *);
+
+extern void xhci_rh_set_ports_protocol(xhci_rh_t *, unsigned, unsigned, unsigned);
+extern void xhci_rh_startup(xhci_rh_t *);
+
+#endif
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/scratchpad.c
===================================================================
--- uspace/drv/bus/usb/xhci/scratchpad.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/scratchpad.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2017 Jaroslav Jindrak
+ * 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
+ * Scratchpad buffer array bookkeeping.
+ */
+
+#include <errno.h>
+#include <usb/debug.h>
+#include "hc.h"
+#include <align.h>
+#include "hw_struct/regs.h"
+#include "scratchpad.h"
+
+/**
+ * Get the number of scratchpad buffers needed.
+ */
+static inline unsigned xhci_scratchpad_count(xhci_hc_t *hc)
+{
+	unsigned lo, hi;
+
+	lo = XHCI_REG_RD(hc->cap_regs, XHCI_CAP_MAX_SPBUF_LO);
+	hi = XHCI_REG_RD(hc->cap_regs, XHCI_CAP_MAX_SPBUF_HI);
+
+	return (hi << 5) | lo;
+}
+
+/**
+ * Allocate all scratchpad buffers, and configure the xHC.
+ */
+int xhci_scratchpad_alloc(xhci_hc_t *hc)
+{
+	const unsigned num_bufs = xhci_scratchpad_count(hc);
+
+	if (!num_bufs)
+		return EOK;
+
+	const unsigned array_size = ALIGN_UP(num_bufs * sizeof(uint64_t), PAGE_SIZE);
+	const size_t size = array_size + num_bufs * PAGE_SIZE;
+
+	if (dma_buffer_alloc(&hc->scratchpad_array, size))
+		return ENOMEM;
+
+	memset(hc->scratchpad_array.virt, 0, size);
+
+	uint64_t phys_begin = hc->scratchpad_array.phys + array_size;
+	uint64_t *array = hc->scratchpad_array.virt;
+
+	for (unsigned i = 0; i < num_bufs; ++i)
+		array[i] = host2xhci(64, phys_begin + i * PAGE_SIZE);
+
+	hc->dcbaa[0] = host2xhci(64, hc->scratchpad_array.phys);
+
+	usb_log_debug("Allocated %d scratchpad buffers.", num_bufs);
+
+	return EOK;
+}
+
+/**
+ * Deallocate the scratchpads and deconfigure xHC.
+ */
+void xhci_scratchpad_free(xhci_hc_t *hc)
+{
+	const unsigned num_bufs = xhci_scratchpad_count(hc);
+
+	if (!num_bufs)
+		return;
+
+	hc->dcbaa[0] = 0;
+	dma_buffer_free(&hc->scratchpad_array);
+	return;
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/scratchpad.h
===================================================================
--- uspace/drv/bus/usb/xhci/scratchpad.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/scratchpad.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2017 Jaroslav Jindrak
+ * 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
+ * Scratchpad buffers are PAGE_SIZE sized page boundary aligned buffers
+ * that are free to use by the xHC.
+ *
+ * This file provides means of allocation and deallocation of these
+ * buffers.
+ */
+
+#ifndef XHCI_SCRATCHPAD_H
+#define XHCI_SCRATCHPAD_H
+
+#include <usb/host/dma_buffer.h>
+
+typedef struct xhci_hc xhci_hc_t;
+
+extern int xhci_scratchpad_alloc(xhci_hc_t *);
+extern void xhci_scratchpad_free(xhci_hc_t *);
+
+#endif
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/streams.c
===================================================================
--- uspace/drv/bus/usb/xhci/streams.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/streams.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,493 @@
+/*
+ * Copyright (c) 2017 Michal Staruch
+ * 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 Structures and functions for Superspeed bulk streams.
+ */
+
+#include "endpoint.h"
+#include "hc.h"
+#include "hw_struct/regs.h"
+#include "streams.h"
+
+/**
+ * Finds stream data with given stream ID if it exists.
+ * Note that streams with ID 0, 65534 and 65535 are reserved.
+ * Splits the ID into primary and secondary context ID and searches the structures.
+ * @param[in] ep Affected endpoint.
+ * @param[in] stream_id Id of the stream.
+ */
+xhci_stream_data_t *xhci_get_stream_ctx_data(xhci_endpoint_t *ep, uint32_t stream_id)
+{
+	if (stream_id == 0 || stream_id >= 65534) {
+		return NULL;
+	}
+
+	/* See 4.12.2.1 for the calculation of the IDs and dividing the stream_id */
+	uint32_t primary_stream_id =
+	    (uint32_t) (stream_id & (ep->primary_stream_data_size - 1));
+	uint32_t secondary_stream_id =
+	    (uint32_t) ((stream_id / ep->primary_stream_data_size) & 0xFF);
+
+	if (primary_stream_id >= ep->primary_stream_data_size) {
+		return NULL;
+	}
+
+	xhci_stream_data_t *primary_data =
+	    &ep->primary_stream_data_array[primary_stream_id];
+	if (secondary_stream_id != 0 && !primary_data->secondary_size) {
+		return NULL;
+	}
+
+	if (!primary_data->secondary_size) {
+		return primary_data;
+	}
+
+	xhci_stream_data_t *secondary_data = primary_data->secondary_data;
+	if (secondary_stream_id >= primary_data->secondary_size) {
+		return NULL;
+	}
+
+	return &secondary_data[secondary_stream_id];
+}
+
+/**
+ * Initializes primary stream data structures in endpoint.
+ * @param[in] xhci_ep Used XHCI bulk endpoint.
+ * @param[in] count Amount of primary streams.
+ */
+static int initialize_primary_structures(xhci_endpoint_t *xhci_ep, unsigned count)
+{
+	usb_log_debug("Allocating primary stream context array of size %u "
+		"for endpoint " XHCI_EP_FMT, count, XHCI_EP_ARGS(*xhci_ep));
+
+	if ((dma_buffer_alloc(&xhci_ep->primary_stream_ctx_dma,
+		count * sizeof(xhci_stream_ctx_t)))) {
+		return ENOMEM;
+	}
+
+	xhci_ep->primary_stream_ctx_array = xhci_ep->primary_stream_ctx_dma.virt;
+	xhci_ep->primary_stream_data_array = calloc(count, sizeof(xhci_stream_data_t));
+	if (!xhci_ep->primary_stream_data_array) {
+		dma_buffer_free(&xhci_ep->primary_stream_ctx_dma);
+		return ENOMEM;
+	}
+
+	xhci_ep->primary_stream_data_size = count;
+
+	return EOK;
+}
+
+static void clear_primary_structures(xhci_endpoint_t *xhci_ep)
+{
+	usb_log_debug("Deallocating primary stream structures for "
+		"endpoint " XHCI_EP_FMT, XHCI_EP_ARGS(*xhci_ep));
+
+	dma_buffer_free(&xhci_ep->primary_stream_ctx_dma);
+	free(xhci_ep->primary_stream_data_array);
+
+	xhci_ep->primary_stream_data_array = NULL;
+	xhci_ep->primary_stream_data_size = 0;
+}
+
+static void clear_secondary_streams(xhci_endpoint_t *xhci_ep, unsigned index)
+{
+	xhci_stream_data_t *data = &xhci_ep->primary_stream_data_array[index];
+	if (!data->secondary_size) {
+		xhci_trb_ring_fini(&data->ring);
+		return;
+	}
+
+	for (size_t i = 0; i < data->secondary_size; ++i) {
+		xhci_trb_ring_fini(&data->secondary_data[i].ring);
+	}
+
+	dma_buffer_free(&data->secondary_stream_ctx_dma);
+	free(data->secondary_data);
+}
+
+void xhci_stream_free_ds(xhci_endpoint_t *xhci_ep)
+{
+	usb_log_debug("Freeing stream rings and context arrays of endpoint "
+		XHCI_EP_FMT, XHCI_EP_ARGS(*xhci_ep));
+
+	for (size_t index = 0; index < xhci_ep->primary_stream_data_size; ++index) {
+		clear_secondary_streams(xhci_ep, index);
+	}
+	clear_primary_structures(xhci_ep);
+}
+
+/**
+ * Initialize a single primary stream structure with given index.
+ * @param[in] hc Host controller of the endpoint.
+ * @param[in] xhci_ep XHCI bulk endpoint to use.
+ * @param[in] index index of the initialized stream structure.
+ */
+static int initialize_primary_stream(xhci_hc_t *hc, xhci_endpoint_t *xhci_ep,
+    unsigned index)
+{
+	xhci_stream_ctx_t *ctx = &xhci_ep->primary_stream_ctx_array[index];
+	xhci_stream_data_t *data = &xhci_ep->primary_stream_data_array[index];
+	memset(data, 0, sizeof(xhci_stream_data_t));
+
+	int err = EOK;
+
+	/* Init and register TRB ring for the primary stream */
+	if ((err = xhci_trb_ring_init(&data->ring, 0))) {
+		return err;
+	}
+	XHCI_STREAM_DEQ_PTR_SET(*ctx, data->ring.dequeue);
+
+	/* Set to linear stream array */
+	XHCI_STREAM_SCT_SET(*ctx, 1);
+
+	return EOK;
+}
+
+/**
+ * Initialize primary streams of XHCI bulk endpoint.
+ * @param[in] hc Host controller of the endpoint.
+ * @param[in] xhci_ep XHCI bulk endpoint to use.
+ */
+static int initialize_primary_streams(xhci_hc_t *hc, xhci_endpoint_t *xhci_ep)
+{
+	int err = EOK;
+	size_t index;
+	for (index = 0; index < xhci_ep->primary_stream_data_size; ++index) {
+		err = initialize_primary_stream(hc, xhci_ep, index);
+		if (err) {
+			goto err_clean;
+		}
+	}
+
+	return EOK;
+
+err_clean:
+	for (size_t i = 0; i < index; ++i) {
+		xhci_trb_ring_fini(&xhci_ep->primary_stream_data_array[i].ring);
+	}
+	return err;
+}
+
+/**
+ * Initialize secondary streams of XHCI bulk endpoint.
+ * @param[in] hc Host controller of the endpoint.
+ * @param[in] xhci_epi XHCI bulk endpoint to use.
+ * @param[in] idx Index to primary stream array
+ * @param[in] count Number of secondary streams to initialize.
+ */
+static int initialize_secondary_streams(xhci_hc_t *hc, xhci_endpoint_t *xhci_ep,
+    unsigned idx, unsigned count)
+{
+	if (count == 0) {
+		/*
+		 * The primary stream context can still point to a single ring, not
+		 * a secondary.
+		 */
+		return initialize_primary_stream(hc, xhci_ep, idx);
+	}
+
+	if ((count & (count - 1)) != 0 || count < 8 || count > 256) {
+		usb_log_error("The secondary stream array size must be a power of 2 "
+			"between 8 and 256.");
+		return EINVAL;
+	}
+
+	xhci_stream_ctx_t *ctx = &xhci_ep->primary_stream_ctx_array[idx];
+	xhci_stream_data_t *data = &xhci_ep->primary_stream_data_array[idx];
+	memset(data, 0, sizeof(xhci_stream_data_t));
+
+	data->secondary_size = count;
+	data->secondary_data = calloc(count, sizeof(xhci_stream_data_t));
+	if (!data->secondary_size) {
+		return ENOMEM;
+	}
+
+	if ((dma_buffer_alloc(&data->secondary_stream_ctx_dma,
+		count * sizeof(xhci_stream_ctx_t)))) {
+		free(data->secondary_data);
+		return ENOMEM;
+	}
+	data->secondary_stream_ctx_array = data->secondary_stream_ctx_dma.virt;
+
+	XHCI_STREAM_DEQ_PTR_SET(*ctx, data->secondary_stream_ctx_dma.phys);
+	XHCI_STREAM_SCT_SET(*ctx, fnzb32(count) + 1);
+
+	/* Initialize all the rings. */
+	int err = EOK;
+	size_t index;
+	for (index = 0; index < count; ++index) {
+		xhci_stream_ctx_t *secondary_ctx = &data->secondary_stream_ctx_array[index];
+		xhci_stream_data_t *secondary_data = &data->secondary_data[index];
+		/* Init and register TRB ring for every secondary stream */
+		if ((err = xhci_trb_ring_init(&secondary_data->ring, 0))) {
+			goto err_init;
+		}
+
+		XHCI_STREAM_DEQ_PTR_SET(*secondary_ctx, secondary_data->ring.dequeue);
+		/* Set to secondary stream array */
+		XHCI_STREAM_SCT_SET(*secondary_ctx, 0);
+	}
+
+	return EOK;
+
+err_init:
+	for (size_t i = 0; i < index; ++i) {
+		xhci_trb_ring_fini(&data->secondary_data[i].ring);
+	}
+	return err;
+}
+
+/**
+ * Configure XHCI bulk endpoint's stream context.
+ * @param[in] xhci_ep Associated XHCI bulk endpoint.
+ * @param[in] ctx Endpoint context to configure.
+ * @param[in] pstreams The value of MaxPStreams.
+ * @param[in] lsa Specifies if the stream IDs point to primary stream array.
+ */
+static void setup_stream_context(xhci_endpoint_t *xhci_ep, xhci_ep_ctx_t *ctx,
+    unsigned pstreams, unsigned lsa)
+{
+	XHCI_EP_TYPE_SET(*ctx, xhci_endpoint_type(xhci_ep));
+	XHCI_EP_MAX_PACKET_SIZE_SET(*ctx, xhci_ep->base.max_packet_size);
+	XHCI_EP_MAX_BURST_SIZE_SET(*ctx, xhci_ep->max_burst - 1);
+	XHCI_EP_ERROR_COUNT_SET(*ctx, 3);
+
+	XHCI_EP_MAX_P_STREAMS_SET(*ctx, pstreams);
+	XHCI_EP_TR_DPTR_SET(*ctx, xhci_ep->primary_stream_ctx_dma.phys);
+	XHCI_EP_LSA_SET(*ctx, lsa);
+}
+
+/**
+ * Verifies if all the common preconditions are satisfied.
+ * @param[in] hc Host controller of the endpoint.
+ * @param[in] dev Used device.
+ * @param[in] xhci_ep Associated XHCI bulk endpoint.
+ * @param[in] count Amount of primary streams requested.
+ */
+static int verify_stream_conditions(xhci_hc_t *hc, xhci_device_t *dev,
+	xhci_endpoint_t *xhci_ep, unsigned count)
+{
+	if (xhci_ep->base.transfer_type != USB_TRANSFER_BULK
+		|| dev->base.speed != USB_SPEED_SUPER) {
+		usb_log_error("Streams are only supported by superspeed bulk endpoints.");
+		return EINVAL;
+	}
+
+	if (xhci_ep->max_streams <= 1) {
+		usb_log_error("Streams are not supported by endpoint "
+		    XHCI_EP_FMT, XHCI_EP_ARGS(*xhci_ep));
+		return EINVAL;
+	}
+
+	if (count < 2) {
+		usb_log_error("The minumum amount of primary streams is 2.");
+		return EINVAL;
+	}
+
+	/*
+	 * The maximum amount of primary streams is 2 ^ (MaxPSA + 1)
+	 * See table 26 of XHCI specification.
+	 */
+	uint8_t max_psa_size = 1 << (XHCI_REG_RD(hc->cap_regs, XHCI_CAP_MAX_PSA_SIZE) + 1);
+	if (count > max_psa_size) {
+		usb_log_error("Host controller only supports "
+			"%u primary streams.", max_psa_size);
+		return EINVAL;
+	}
+
+	if (count > xhci_ep->max_streams) {
+		usb_log_error("Endpoint " XHCI_EP_FMT " supports only %" PRIu32 " streams.",
+			XHCI_EP_ARGS(*xhci_ep), xhci_ep->max_streams);
+		return EINVAL;
+	}
+
+	if ((count & (count - 1)) != 0) {
+		usb_log_error("The amount of primary streams must be a power of 2.");
+		return EINVAL;
+	}
+
+	return EOK;
+}
+
+/**
+ * Cancels streams and reconfigures endpoint back to single ring no stream endpoint.
+ * @param[in] hc Host controller of the endpoint.
+ * @param[in] dev Used device.
+ * @param[in] xhci_ep Associated XHCI bulk endpoint.
+ */
+int xhci_endpoint_remove_streams(xhci_hc_t *hc, xhci_device_t *dev,
+    xhci_endpoint_t *xhci_ep)
+{
+	if (!xhci_ep->primary_stream_data_size) {
+		usb_log_warning("There are no streams enabled on the endpoint, doing nothing.");
+		return EOK;
+	}
+
+	hc_stop_endpoint(xhci_ep);
+	xhci_endpoint_free_transfer_ds(xhci_ep);
+
+	/* Streams are now removed, proceed with reconfiguring endpoint. */
+	int err;
+	if ((err = xhci_trb_ring_init(&xhci_ep->ring, 0))) {
+		usb_log_error("Failed to initialize a transfer ring.");
+		return err;
+	}
+
+	return hc_update_endpoint(xhci_ep);
+}
+
+/**
+ * Initialize, setup and register primary streams.
+ * @param[in] hc Host controller of the endpoint.
+ * @param[in] dev Used device.
+ * @param[in] xhci_ep Associated XHCI bulk endpoint.
+ * @param[in] count Amount of primary streams requested.
+ */
+int xhci_endpoint_request_primary_streams(xhci_hc_t *hc, xhci_device_t *dev,
+	xhci_endpoint_t *xhci_ep, unsigned count)
+{
+	int err = verify_stream_conditions(hc, dev, xhci_ep, count);
+	if (err) {
+		return err;
+	}
+
+	/*
+	 * We have passed the checks.
+	 * Stop the endpoint, destroy the ring, and transition to streams.
+	 */
+	hc_stop_endpoint(xhci_ep);
+	xhci_endpoint_free_transfer_ds(xhci_ep);
+
+	err = initialize_primary_structures(xhci_ep, count);
+	if (err) {
+		return err;
+	}
+
+	memset(xhci_ep->primary_stream_ctx_array, 0, count * sizeof(xhci_stream_ctx_t));
+	err = initialize_primary_streams(hc, xhci_ep);
+	if (err) {
+		clear_primary_structures(xhci_ep);
+		return err;
+	}
+
+	xhci_ep_ctx_t ep_ctx;
+	/*
+	 * Allowed values are 1-15, where 2 ^ pstreams is the actual amount of
+	 * streams.
+	 */
+	const size_t pstreams = fnzb32(count) - 1;
+	setup_stream_context(xhci_ep, &ep_ctx, pstreams, 1);
+
+	return hc_update_endpoint(xhci_ep);
+}
+
+/**
+ * Initialize, setup and register secondary streams.
+ * @param[in] hc Host controller of the endpoint.
+ * @param[in] dev Used device.
+ * @param[in] xhci_ep Associated XHCI bulk endpoint.
+ * @param[in] sizes Amount of secondary streams in each of the primary streams.
+ *                  This array should have exactly count elements. If the size
+ *                  is 0, then a primary ring is created with that index.
+ * @param[in] count Amount of primary streams requested.
+ */
+int xhci_endpoint_request_secondary_streams(xhci_hc_t *hc, xhci_device_t *dev,
+	xhci_endpoint_t *xhci_ep, unsigned *sizes, unsigned count)
+{
+	/* Check if HC supports secondary indexing */
+	if (XHCI_REG_RD(hc->cap_regs, XHCI_CAP_NSS)) {
+		usb_log_error("The host controller doesn't support secondary streams.");
+		return ENOTSUP;
+	}
+
+	int err = verify_stream_conditions(hc, dev, xhci_ep, count);
+	if (err) {
+		return err;
+	}
+
+	if (count > 256) {
+		usb_log_error("The amount of primary streams cannot be higher than 256.");
+		return EINVAL;
+	}
+
+	/*
+	 * Find the largest requested secondary stream size,
+	 * that one is the maximum ID that device can receive.
+	 * We need to make sure the device can handle that ID.
+	 */
+	unsigned max = 0;
+	for (size_t index = 0; index < count; ++index) {
+		if (sizes[count] > max) {
+			max = sizes[count];
+		}
+	}
+
+	if (max * count > xhci_ep->max_streams) {
+		usb_log_error("Endpoint " XHCI_EP_FMT " supports only %" PRIu32 " streams.",
+			XHCI_EP_ARGS(*xhci_ep), xhci_ep->max_streams);
+		return EINVAL;
+	}
+
+	/*
+	 * We have passed all checks.
+	 * Stop the endpoint, destroy the ring, and transition to streams.
+	 */
+	hc_stop_endpoint(xhci_ep);
+	xhci_endpoint_free_transfer_ds(xhci_ep);
+
+	err = initialize_primary_structures(xhci_ep, count);
+	if (err) {
+		return err;
+	}
+
+	memset(xhci_ep->primary_stream_ctx_array, 0, count * sizeof(xhci_stream_ctx_t));
+	size_t index;
+	for (index = 0; index < count; ++index) {
+		err = initialize_secondary_streams(hc, xhci_ep, index, *(sizes + index));
+		if (err) {
+			goto err_init;
+		}
+	}
+
+	xhci_ep_ctx_t ep_ctx;
+	const size_t pstreams = fnzb32(count) - 1;
+	setup_stream_context(xhci_ep, &ep_ctx, pstreams, 0);
+
+	return hc_update_endpoint(xhci_ep);
+
+err_init:
+	for (size_t i = 0; i < index; ++i) {
+		clear_secondary_streams(xhci_ep, i);
+	}
+	clear_primary_structures(xhci_ep);
+	return err;
+}
Index: uspace/drv/bus/usb/xhci/streams.h
===================================================================
--- uspace/drv/bus/usb/xhci/streams.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/streams.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2017 Michal Staruch
+ * 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 Structures and functions for Superspeed bulk streams.
+ */
+
+#ifndef XHCI_STREAMS_H
+#define XHCI_STREAMS_H
+
+#include "endpoint.h"
+#include "hw_struct/context.h"
+#include "trb_ring.h"
+
+typedef struct xhci_endpoint xhci_endpoint_t;
+typedef struct xhci_stream_data xhci_stream_data_t;
+
+typedef struct xhci_stream_data {
+	/** The TRB ring for the context, if valid */
+	xhci_trb_ring_t ring;
+
+	/** Pointer to the array of secondary stream context data for primary data. */
+	xhci_stream_data_t *secondary_data;
+
+	/** The size of secondary stream context data array */
+	uint32_t secondary_size;
+
+	/** Secondary stream context array - allocated for xHC hardware.
+	 * Required for later dealocation of secondary structure.
+	 */
+	xhci_stream_ctx_t *secondary_stream_ctx_array;
+	dma_buffer_t secondary_stream_ctx_dma;
+} xhci_stream_data_t;
+
+extern xhci_stream_data_t *xhci_get_stream_ctx_data(xhci_endpoint_t *ep, uint32_t stream_id);
+extern void xhci_stream_free_ds(xhci_endpoint_t *xhci_ep);
+
+extern int xhci_endpoint_remove_streams(xhci_hc_t *hc, xhci_device_t *dev, xhci_endpoint_t *xhci_ep);
+extern int xhci_endpoint_request_primary_streams(xhci_hc_t *hc, xhci_device_t *dev,
+	xhci_endpoint_t *xhci_ep, unsigned count);
+extern int xhci_endpoint_request_secondary_streams(xhci_hc_t *hc, xhci_device_t *dev,
+	xhci_endpoint_t *xhci_ep, unsigned *sizes, unsigned count);
+
+#endif
Index: uspace/drv/bus/usb/xhci/test/reg-ops.c
===================================================================
--- uspace/drv/bus/usb/xhci/test/reg-ops.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/test/reg-ops.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,181 @@
+#include <pcut/pcut.h>
+#include "../hw_struct/regs.h"
+
+PCUT_INIT
+
+static struct {
+	uint32_t field32;
+	uint16_t field16;
+	uint8_t field8;
+} regs[1];
+
+#define REG_8_FLAG    field8,  8,  FLAG,  3
+#define REG_8_RANGE   field8,  8, RANGE,  6, 2
+#define REG_8_FIELD   field8,  8, FIELD
+#define REG_16_FLAG  field16, 16,  FLAG,  8
+#define REG_16_RANGE field16, 16, RANGE, 11, 4
+#define REG_16_FIELD field16, 16, FIELD
+#define REG_32_FLAG  field32, 32,  FLAG, 16
+#define REG_32_RANGE field32, 32, RANGE, 23, 8
+#define REG_32_FIELD field32, 32, FIELD
+
+#define RESET memset(regs, 0, sizeof(regs[0]))
+#define EQ(exp, act) PCUT_ASSERT_INT_EQUALS((exp), (act))
+
+PCUT_TEST(ops_8_field) {
+	RESET;
+	EQ(0, XHCI_REG_RD(regs, REG_8_FIELD));
+
+	XHCI_REG_WR(regs, REG_8_FIELD, 0x55);
+	EQ(0x55, XHCI_REG_RD(regs, REG_8_FIELD));
+	EQ(0x55, regs->field8);
+
+	RESET;
+	XHCI_REG_SET(regs, REG_8_FIELD, 0x55);
+	EQ(0x55, XHCI_REG_RD(regs, REG_8_FIELD));
+	EQ(0x55, regs->field8);
+
+	XHCI_REG_CLR(regs, REG_8_FIELD, 0x5);
+	EQ(0x50, XHCI_REG_RD(regs, REG_8_FIELD));
+	EQ(0x50, regs->field8);
+}
+
+PCUT_TEST(ops_8_range) {
+	RESET;
+	EQ(0, XHCI_REG_RD(regs, REG_8_RANGE));
+
+	XHCI_REG_WR(regs, REG_8_RANGE, 0x55);
+	EQ(0x15, XHCI_REG_RD(regs, REG_8_RANGE));
+	EQ(0x54, regs->field8);
+
+	XHCI_REG_SET(regs, REG_8_RANGE, 0x2);
+	EQ(0x17, XHCI_REG_RD(regs, REG_8_RANGE));
+	EQ(0x5c, regs->field8);
+
+	XHCI_REG_CLR(regs, REG_8_RANGE, 0x2);
+	EQ(0x15, XHCI_REG_RD(regs, REG_8_RANGE));
+	EQ(0x54, regs->field8);
+}
+
+PCUT_TEST(ops_8_flag) {
+	RESET;
+	EQ(0, XHCI_REG_RD(regs, REG_8_FLAG));
+
+	XHCI_REG_WR(regs, REG_8_FLAG, 1);
+	EQ(1, XHCI_REG_RD(regs, REG_8_FLAG));
+	EQ(8, regs->field8);
+
+	RESET;
+	XHCI_REG_SET(regs, REG_8_FLAG, 1);
+	EQ(1, XHCI_REG_RD(regs, REG_8_FLAG));
+	EQ(8, regs->field8);
+
+	XHCI_REG_CLR(regs, REG_8_FLAG, 1);
+	EQ(0, XHCI_REG_RD(regs, REG_8_FLAG));
+	EQ(0, regs->field8);
+}
+
+PCUT_TEST(ops_16_field) {
+	RESET;
+	EQ(0, XHCI_REG_RD(regs, REG_16_FIELD));
+
+	XHCI_REG_WR(regs, REG_16_FIELD, 0x5555);
+	EQ(0x5555, XHCI_REG_RD(regs, REG_16_FIELD));
+	EQ(0x5555, xhci2host(16, regs->field16));
+
+	XHCI_REG_SET(regs, REG_16_FIELD, 0x00aa);
+	EQ(0x55ff, XHCI_REG_RD(regs, REG_16_FIELD));
+	EQ(0x55ff, xhci2host(16, regs->field16));
+
+	XHCI_REG_CLR(regs, REG_16_FIELD, 0x055a);
+	EQ(0x50a5, XHCI_REG_RD(regs, REG_16_FIELD));
+	EQ(0x50a5, xhci2host(16, regs->field16));
+}
+
+PCUT_TEST(ops_16_range) {
+	RESET;
+	EQ(0, XHCI_REG_RD(regs, REG_16_RANGE));
+
+	XHCI_REG_WR(regs, REG_16_RANGE, 0x5a5a);
+	EQ(0x5a, XHCI_REG_RD(regs, REG_16_RANGE));
+	EQ(0x05a0, xhci2host(16, regs->field16));
+
+	XHCI_REG_SET(regs, REG_16_RANGE, 0xa5);
+	EQ(0xff, XHCI_REG_RD(regs, REG_16_RANGE));
+	EQ(0x0ff0, xhci2host(16, regs->field16));
+
+	XHCI_REG_CLR(regs, REG_16_RANGE, 0x5a);
+	EQ(0xa5, XHCI_REG_RD(regs, REG_16_RANGE));
+	EQ(0x0a50, xhci2host(16, regs->field16));
+}
+
+PCUT_TEST(ops_16_flag) {
+	RESET;
+	EQ(0, XHCI_REG_RD(regs, REG_16_FLAG));
+
+	XHCI_REG_WR(regs, REG_16_FLAG, 1);
+	EQ(1, XHCI_REG_RD(regs, REG_16_FLAG));
+	EQ(0x100, xhci2host(16, regs->field16));
+
+	RESET;
+	XHCI_REG_SET(regs, REG_16_FLAG, 1);
+	EQ(1, XHCI_REG_RD(regs, REG_16_FLAG));
+	EQ(0x100, xhci2host(16, regs->field16));
+
+	XHCI_REG_CLR(regs, REG_16_FLAG, 1);
+	EQ(0, XHCI_REG_RD(regs, REG_16_FLAG));
+	EQ(0, xhci2host(16, regs->field16));
+}
+
+PCUT_TEST(ops_32_field) {
+	RESET;
+	EQ(0, XHCI_REG_RD(regs, REG_32_FIELD));
+
+	XHCI_REG_WR(regs, REG_32_FIELD, 0xffaa5500);
+	EQ(0xffaa5500, XHCI_REG_RD(regs, REG_32_FIELD));
+	EQ(0xffaa5500, xhci2host(32, regs->field32));
+
+	XHCI_REG_SET(regs, REG_32_FIELD, 0x0055aa00);
+	EQ(0xffffff00, XHCI_REG_RD(regs, REG_32_FIELD));
+	EQ(0xffffff00, xhci2host(32, regs->field32));
+
+	XHCI_REG_CLR(regs, REG_32_FIELD, 0x00aa55ff);
+	EQ(0xff55aa00, XHCI_REG_RD(regs, REG_32_FIELD));
+	EQ(0xff55aa00, xhci2host(32, regs->field32));
+}
+
+PCUT_TEST(ops_32_range) {
+	RESET;
+	EQ(0, XHCI_REG_RD(regs, REG_32_RANGE));
+
+	XHCI_REG_WR(regs, REG_32_RANGE, 0xff5a0);
+	EQ(0xf5a0, XHCI_REG_RD(regs, REG_32_RANGE));
+	EQ(0x00f5a000, xhci2host(32, regs->field32));
+
+	XHCI_REG_SET(regs, REG_32_RANGE, 0xffa50);
+	EQ(0xfff0, XHCI_REG_RD(regs, REG_32_RANGE));
+	EQ(0x00fff000, xhci2host(32, regs->field32));
+
+	XHCI_REG_CLR(regs, REG_32_RANGE, 0xf05af);
+	EQ(0xfa50, XHCI_REG_RD(regs, REG_32_RANGE));
+	EQ(0x00fa5000, xhci2host(32, regs->field32));
+}
+
+PCUT_TEST(ops_32_flag) {
+	RESET;
+	EQ(0, XHCI_REG_RD(regs, REG_32_FLAG));
+
+	XHCI_REG_WR(regs, REG_32_FLAG, 1);
+	EQ(1, XHCI_REG_RD(regs, REG_32_FLAG));
+	EQ(0x10000, xhci2host(32, regs->field32));
+
+	RESET;
+	XHCI_REG_SET(regs, REG_32_FLAG, 1);
+	EQ(1, XHCI_REG_RD(regs, REG_32_FLAG));
+	EQ(0x10000, xhci2host(32, regs->field32));
+
+	XHCI_REG_CLR(regs, REG_32_FLAG, 1);
+	EQ(0, XHCI_REG_RD(regs, REG_32_FLAG));
+	EQ(0, xhci2host(32, regs->field32));
+}
+PCUT_MAIN();
Index: uspace/drv/bus/usb/xhci/transfers.c
===================================================================
--- uspace/drv/bus/usb/xhci/transfers.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/transfers.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,509 @@
+/*
+ * Copyright (c) 2017 Michal Staruch
+ * 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 transfer ring management
+ */
+
+#include <usb/debug.h>
+#include <usb/request.h>
+#include "endpoint.h"
+#include "hc.h"
+#include "hw_struct/trb.h"
+#include "streams.h"
+#include "transfers.h"
+#include "trb_ring.h"
+
+typedef enum {
+    STAGE_OUT,
+    STAGE_IN,
+} stage_dir_flag_t;
+
+#define REQUEST_TYPE_DTD (0x80)
+#define REQUEST_TYPE_IS_DEVICE_TO_HOST(rq) ((rq) & REQUEST_TYPE_DTD)
+
+
+/** Get direction flag of data stage.
+ *  See Table 7 of xHCI specification.
+ */
+static inline stage_dir_flag_t get_status_direction_flag(xhci_trb_t* trb,
+	uint8_t bmRequestType, uint16_t wLength)
+{
+	/* See Table 7 of xHCI specification */
+	return REQUEST_TYPE_IS_DEVICE_TO_HOST(bmRequestType) && (wLength > 0)
+		? STAGE_OUT
+		: STAGE_IN;
+}
+
+typedef enum {
+    DATA_STAGE_NO = 0,
+    DATA_STAGE_OUT = 2,
+    DATA_STAGE_IN = 3,
+} data_stage_type_t;
+
+/** Get transfer type flag.
+ *  See Table 8 of xHCI specification.
+ */
+static inline data_stage_type_t get_transfer_type(xhci_trb_t* trb, uint8_t
+	bmRequestType, uint16_t wLength)
+{
+	if (wLength == 0)
+		return DATA_STAGE_NO;
+
+	/* See Table 7 of xHCI specification */
+	return REQUEST_TYPE_IS_DEVICE_TO_HOST(bmRequestType)
+		? DATA_STAGE_IN
+		: DATA_STAGE_NO;
+}
+
+static inline bool configure_endpoint_needed(usb_device_request_setup_packet_t *setup)
+{
+	usb_request_type_t request_type = SETUP_REQUEST_TYPE_GET_TYPE(setup->request_type);
+
+	return request_type == USB_REQUEST_TYPE_STANDARD &&
+		(setup->request == USB_DEVREQ_SET_CONFIGURATION
+		|| setup->request == USB_DEVREQ_SET_INTERFACE);
+}
+
+/**
+ * Create a xHCI-specific transfer batch.
+ *
+ * Bus callback.
+ */
+usb_transfer_batch_t * xhci_transfer_create(endpoint_t* ep)
+{
+	xhci_transfer_t *transfer = calloc(1, sizeof(xhci_transfer_t));
+	if (!transfer)
+		return NULL;
+
+	usb_transfer_batch_init(&transfer->batch, ep);
+	return &transfer->batch;
+}
+
+/**
+ * Destroy a xHCI transfer.
+ */
+void xhci_transfer_destroy(usb_transfer_batch_t* batch)
+{
+	xhci_transfer_t *transfer = xhci_transfer_from_batch(batch);
+
+	dma_buffer_free(&transfer->hc_buffer);
+	free(transfer);
+}
+
+static xhci_trb_ring_t *get_ring(xhci_transfer_t *transfer)
+{
+	xhci_endpoint_t *xhci_ep = xhci_endpoint_get(transfer->batch.ep);
+	return xhci_endpoint_get_ring(xhci_ep, transfer->batch.target.stream);
+}
+
+static int schedule_control(xhci_hc_t* hc, xhci_transfer_t* transfer)
+{
+	usb_transfer_batch_t *batch = &transfer->batch;
+	xhci_endpoint_t *xhci_ep = xhci_endpoint_get(transfer->batch.ep);
+
+	usb_device_request_setup_packet_t* setup = &batch->setup.packet;
+
+	xhci_trb_t trbs[3];
+	int trbs_used = 0;
+
+	xhci_trb_t *trb_setup = trbs + trbs_used++;
+	xhci_trb_clean(trb_setup);
+
+	TRB_CTRL_SET_SETUP_WVALUE(*trb_setup, setup->value);
+	TRB_CTRL_SET_SETUP_WLENGTH(*trb_setup, setup->length);
+	TRB_CTRL_SET_SETUP_WINDEX(*trb_setup, setup->index);
+	TRB_CTRL_SET_SETUP_BREQ(*trb_setup, setup->request);
+	TRB_CTRL_SET_SETUP_BMREQTYPE(*trb_setup, setup->request_type);
+
+	/* Size of the setup packet is always 8 */
+	TRB_CTRL_SET_XFER_LEN(*trb_setup, 8);
+
+	/* Immediate data */
+	TRB_CTRL_SET_IDT(*trb_setup, 1);
+	TRB_CTRL_SET_TRB_TYPE(*trb_setup, XHCI_TRB_TYPE_SETUP_STAGE);
+	TRB_CTRL_SET_TRT(*trb_setup,
+	    get_transfer_type(trb_setup, setup->request_type, setup->length));
+
+	/* Data stage */
+	xhci_trb_t *trb_data = NULL;
+	if (setup->length > 0) {
+		trb_data = trbs + trbs_used++;
+		xhci_trb_clean(trb_data);
+
+		trb_data->parameter = host2xhci(64, transfer->hc_buffer.phys);
+
+		// data size (sent for OUT, or buffer size)
+		TRB_CTRL_SET_XFER_LEN(*trb_data, batch->buffer_size);
+		// FIXME: TD size 4.11.2.4
+		TRB_CTRL_SET_TD_SIZE(*trb_data, 1);
+
+		// Some more fields here, no idea what they mean
+		TRB_CTRL_SET_TRB_TYPE(*trb_data, XHCI_TRB_TYPE_DATA_STAGE);
+
+		int stage_dir = REQUEST_TYPE_IS_DEVICE_TO_HOST(setup->request_type)
+					? STAGE_IN : STAGE_OUT;
+		TRB_CTRL_SET_DIR(*trb_data, stage_dir);
+	}
+
+	/* Status stage */
+	xhci_trb_t *trb_status = trbs + trbs_used++;
+	xhci_trb_clean(trb_status);
+
+	TRB_CTRL_SET_IOC(*trb_status, 1);
+	TRB_CTRL_SET_TRB_TYPE(*trb_status, XHCI_TRB_TYPE_STATUS_STAGE);
+	TRB_CTRL_SET_DIR(*trb_status, get_status_direction_flag(trb_setup,
+	    setup->request_type, setup->length));
+
+	// Issue a Configure Endpoint command, if needed.
+	if (configure_endpoint_needed(setup)) {
+		const int err = hc_configure_device(xhci_ep_to_dev(xhci_ep));
+		if (err)
+			return err;
+	}
+
+	return xhci_trb_ring_enqueue_multiple(get_ring(transfer), trbs,
+	    trbs_used, &transfer->interrupt_trb_phys);
+}
+
+static int schedule_bulk(xhci_hc_t* hc, xhci_transfer_t *transfer)
+{
+	xhci_trb_t trb;
+	xhci_trb_clean(&trb);
+	trb.parameter = host2xhci(64, transfer->hc_buffer.phys);
+
+	// data size (sent for OUT, or buffer size)
+	TRB_CTRL_SET_XFER_LEN(trb, transfer->batch.buffer_size);
+
+	/* The stream-enabled endpoints need to chain ED trb */
+	xhci_endpoint_t *ep = xhci_endpoint_get(transfer->batch.ep);
+	if (!ep->primary_stream_data_size) {
+		// FIXME: TD size 4.11.2.4
+		TRB_CTRL_SET_TD_SIZE(trb, 1);
+
+		// we want an interrupt after this td is done
+		TRB_CTRL_SET_IOC(trb, 1);
+		TRB_CTRL_SET_TRB_TYPE(trb, XHCI_TRB_TYPE_NORMAL);
+
+		xhci_trb_ring_t* ring = get_ring(transfer);
+		return xhci_trb_ring_enqueue(ring, &trb, &transfer->interrupt_trb_phys);
+	}
+	else {
+		TRB_CTRL_SET_TD_SIZE(trb, 2);
+		TRB_CTRL_SET_TRB_TYPE(trb, XHCI_TRB_TYPE_NORMAL);
+		TRB_CTRL_SET_CHAIN(trb, 1);
+		TRB_CTRL_SET_ENT(trb, 1);
+
+		xhci_trb_ring_t* ring = get_ring(transfer);
+		if (!ring) {
+			return EINVAL;
+		}
+
+		int err = xhci_trb_ring_enqueue(ring, &trb, &transfer->interrupt_trb_phys);
+
+		if (err) {
+			return err;
+		}
+
+		xhci_trb_clean(&trb);
+		trb.parameter = host2xhci(64, (uintptr_t) transfer);
+		TRB_CTRL_SET_TRB_TYPE(trb, XHCI_TRB_TYPE_EVENT_DATA);
+		TRB_CTRL_SET_IOC(trb, 1);
+
+		return xhci_trb_ring_enqueue(ring, &trb, &transfer->interrupt_trb_phys);
+	}
+}
+
+static int schedule_interrupt(xhci_hc_t* hc, xhci_transfer_t* transfer)
+{
+	xhci_trb_t trb;
+	xhci_trb_clean(&trb);
+	trb.parameter = host2xhci(64, transfer->hc_buffer.phys);
+
+	// data size (sent for OUT, or buffer size)
+	TRB_CTRL_SET_XFER_LEN(trb, transfer->batch.buffer_size);
+	// FIXME: TD size 4.11.2.4
+	TRB_CTRL_SET_TD_SIZE(trb, 1);
+
+	// we want an interrupt after this td is done
+	TRB_CTRL_SET_IOC(trb, 1);
+
+	TRB_CTRL_SET_TRB_TYPE(trb, XHCI_TRB_TYPE_NORMAL);
+
+	xhci_trb_ring_t* ring = get_ring(transfer);
+
+	return xhci_trb_ring_enqueue(ring, &trb, &transfer->interrupt_trb_phys);
+}
+
+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);
+}
+
+int xhci_handle_transfer_event(xhci_hc_t* hc, xhci_trb_t* trb)
+{
+	uintptr_t addr = trb->parameter;
+	const unsigned slot_id = XHCI_DWORD_EXTRACT(trb->control, 31, 24);
+	const unsigned ep_dci = XHCI_DWORD_EXTRACT(trb->control, 20, 16);
+
+	xhci_device_t *dev = hc->bus.devices_by_slot[slot_id];
+	if (!dev) {
+		usb_log_error("Transfer event on disabled slot %u", slot_id);
+		return ENOENT;
+	}
+
+	const usb_endpoint_t ep_num = ep_dci / 2;
+	const usb_endpoint_t dir = ep_dci % 2 ? USB_DIRECTION_IN : USB_DIRECTION_OUT;
+	/* Creating temporary reference */
+	endpoint_t *ep_base = bus_find_endpoint(&dev->base, ep_num, dir);
+	if (!ep_base) {
+		usb_log_error("Transfer event on dropped endpoint %u %s of device "
+		    XHCI_DEV_FMT, ep_num, usb_str_direction(dir), XHCI_DEV_ARGS(*dev));
+		return ENOENT;
+	}
+	xhci_endpoint_t *ep = xhci_endpoint_get(ep_base);
+
+	usb_transfer_batch_t *batch;
+	xhci_transfer_t *transfer;
+
+	if (TRB_EVENT_DATA(*trb)) {
+		/* We schedule those only when streams are involved */
+		assert(ep->primary_stream_ctx_array != NULL);
+
+		/* We are received transfer pointer instead - work with that */
+		transfer = (xhci_transfer_t *) addr;
+		xhci_trb_ring_update_dequeue(get_ring(transfer),
+		    transfer->interrupt_trb_phys);
+		batch = &transfer->batch;
+	}
+	else {
+		xhci_trb_ring_update_dequeue(&ep->ring, addr);
+
+		if (ep->base.transfer_type == USB_TRANSFER_ISOCHRONOUS) {
+			isoch_handle_transfer_event(hc, ep, trb);
+			/* Dropping temporary reference */
+			endpoint_del_ref(&ep->base);
+			return EOK;
+		}
+
+		fibril_mutex_lock(&ep->guard);
+		batch = ep->base.active_batch;
+		endpoint_deactivate_locked(&ep->base);
+		fibril_mutex_unlock(&ep->guard);
+
+		if (!batch) {
+			/* Dropping temporary reference */
+			endpoint_del_ref(&ep->base);
+			return ENOENT;
+		}
+
+		transfer = xhci_transfer_from_batch(batch);
+	}
+
+	const xhci_trb_completion_code_t completion_code = TRB_COMPLETION_CODE(*trb);
+	switch (completion_code) {
+		case XHCI_TRBC_SHORT_PACKET:
+		case XHCI_TRBC_SUCCESS:
+			batch->error = EOK;
+			batch->transferred_size = batch->buffer_size - TRB_TRANSFER_LENGTH(*trb);
+			break;
+
+		case XHCI_TRBC_DATA_BUFFER_ERROR:
+			usb_log_warning("Transfer ended with data buffer error.");
+			batch->error = EAGAIN;
+			batch->transferred_size = 0;
+			break;
+
+		case XHCI_TRBC_BABBLE_DETECTED_ERROR:
+			usb_log_warning("Babble detected during the transfer.");
+			batch->error = EAGAIN;
+			batch->transferred_size = 0;
+			break;
+
+		case XHCI_TRBC_USB_TRANSACTION_ERROR:
+			usb_log_warning("USB Transaction error.");
+			batch->error = EAGAIN;
+			batch->transferred_size = 0;
+			break;
+
+		case XHCI_TRBC_TRB_ERROR:
+			usb_log_error("Invalid transfer parameters.");
+			batch->error = EINVAL;
+			batch->transferred_size = 0;
+			break;
+
+		case XHCI_TRBC_STALL_ERROR:
+			usb_log_warning("Stall condition detected.");
+			batch->error = ESTALL;
+			batch->transferred_size = 0;
+			break;
+
+		case XHCI_TRBC_SPLIT_TRANSACTION_ERROR:
+			usb_log_error("Split transcation error detected.");
+			batch->error = EAGAIN;
+			batch->transferred_size = 0;
+			break;
+
+		default:
+			usb_log_warning("Transfer not successfull: %u", completion_code);
+			batch->error = EIO;
+	}
+
+	if (batch->dir == USB_DIRECTION_IN) {
+		assert(batch->buffer);
+		assert(batch->transferred_size <= batch->buffer_size);
+		memcpy(batch->buffer, transfer->hc_buffer.virt, batch->transferred_size);
+	}
+
+	usb_transfer_batch_finish(batch);
+	/* Dropping temporary reference */
+	endpoint_del_ref(&ep->base);
+	return EOK;
+}
+
+typedef int (*transfer_handler)(xhci_hc_t *, xhci_transfer_t *);
+
+static const transfer_handler transfer_handlers[] = {
+	[USB_TRANSFER_CONTROL] = schedule_control,
+	[USB_TRANSFER_ISOCHRONOUS] = NULL,
+	[USB_TRANSFER_BULK] = schedule_bulk,
+	[USB_TRANSFER_INTERRUPT] = schedule_interrupt,
+};
+
+/**
+ * Schedule a batch for xHC.
+ *
+ * Bus callback.
+ */
+int xhci_transfer_schedule(usb_transfer_batch_t *batch)
+{
+	endpoint_t *ep = batch->ep;
+
+	xhci_hc_t *hc = bus_to_hc(endpoint_get_bus(batch->ep));
+	xhci_transfer_t *transfer = xhci_transfer_from_batch(batch);
+	xhci_endpoint_t *xhci_ep = xhci_endpoint_get(ep);
+	xhci_device_t *xhci_dev = xhci_ep_to_dev(xhci_ep);
+
+	if (!batch->target.address) {
+		usb_log_error("Attempted to schedule transfer to address 0.");
+		return EINVAL;
+	}
+
+	// FIXME: find a better way to check if the ring is not initialized
+	if (!xhci_ep->ring.segment_count) {
+		usb_log_error("Ring not initialized for endpoint " XHCI_EP_FMT,
+		    XHCI_EP_ARGS(*xhci_ep));
+		return EINVAL;
+	}
+
+	// Isochronous transfer needs to be handled differently
+	if (batch->ep->transfer_type == USB_TRANSFER_ISOCHRONOUS) {
+		return schedule_isochronous(transfer);
+	}
+
+	const usb_transfer_type_t type = batch->ep->transfer_type;
+	assert(transfer_handlers[type]);
+
+	if (batch->buffer_size > 0) {
+		if (dma_buffer_alloc(&transfer->hc_buffer, batch->buffer_size))
+			return ENOMEM;
+	}
+
+	if (batch->dir != USB_DIRECTION_IN) {
+		// Sending stuff from host to device, we need to copy the actual data.
+		memcpy(transfer->hc_buffer.virt, batch->buffer, batch->buffer_size);
+	}
+
+	/*
+	 * If this is a ClearFeature(ENDPOINT_HALT) request, we have to issue
+	 * the Reset Endpoint command.
+	 */
+	if (batch->ep->transfer_type == USB_TRANSFER_CONTROL
+	    && batch->dir == USB_DIRECTION_OUT) {
+		const usb_device_request_setup_packet_t *request = &batch->setup.packet;
+		if (request->request == USB_DEVREQ_CLEAR_FEATURE
+		    && request->request_type == USB_REQUEST_RECIPIENT_ENDPOINT
+		    && request->value == USB_FEATURE_ENDPOINT_HALT) {
+			const uint16_t index = uint16_usb2host(request->index);
+			const usb_endpoint_t ep_num = index & 0xf;
+			const usb_direction_t dir = (index >> 7)
+			    ? USB_DIRECTION_IN
+			    : USB_DIRECTION_OUT;
+			endpoint_t *halted_ep = bus_find_endpoint(&xhci_dev->base, ep_num, dir);
+			if (halted_ep) {
+				/*
+				 * TODO: Find out how to come up with stream_id. It might be
+				 * possible that we have to clear all of them.
+				 */
+				const int err = xhci_endpoint_clear_halt(xhci_endpoint_get(halted_ep), 0);
+				endpoint_del_ref(halted_ep);
+				if (err) {
+					/*
+					 * The endpoint halt condition failed to be cleared in HC.
+					 * As it does not make sense to send the reset to the device
+					 * itself, return as unschedulable answer.
+					 *
+					 * Furthermore, if this is a request to clear EP 0 stall, it
+					 * would be gone forever, as the endpoint is halted.
+					 */
+					return err;
+				}
+			} else {
+				usb_log_warning("Device(%u): Resetting unregistered endpoint"
+					" %u %s.", xhci_dev->base.address, ep_num,
+					usb_str_direction(dir));
+			}
+		}
+	}
+
+
+	int err;
+	fibril_mutex_lock(&xhci_ep->guard);
+
+	if ((err = endpoint_activate_locked(ep, batch))) {
+		fibril_mutex_unlock(&xhci_ep->guard);
+		return err;
+	}
+
+	if ((err = transfer_handlers[batch->ep->transfer_type](hc, transfer))) {
+		endpoint_deactivate_locked(ep);
+		fibril_mutex_unlock(&xhci_ep->guard);
+		return err;
+	}
+
+	hc_ring_ep_doorbell(xhci_ep, batch->target.stream);
+	fibril_mutex_unlock(&xhci_ep->guard);
+	return EOK;
+}
Index: uspace/drv/bus/usb/xhci/transfers.h
===================================================================
--- uspace/drv/bus/usb/xhci/transfers.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/transfers.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2017 Michal Staruch
+ * 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 transfer ring management
+ */
+
+#ifndef XHCI_TRANSFERS_H
+#define XHCI_TRANSFERS_H
+
+#include <usb/host/usb_transfer_batch.h>
+
+#include "hw_struct/context.h"
+#include "trb_ring.h"
+
+typedef struct xhci_hc xhci_hc_t;
+
+typedef struct {
+	usb_transfer_batch_t batch;
+	link_t link;
+
+	uint8_t direction;
+
+	dma_buffer_t hc_buffer;
+
+	uintptr_t interrupt_trb_phys;
+} xhci_transfer_t;
+
+extern usb_transfer_batch_t* xhci_transfer_create(endpoint_t *);
+extern int xhci_transfer_schedule(usb_transfer_batch_t *);
+
+extern int xhci_handle_transfer_event(xhci_hc_t *, xhci_trb_t *);
+extern void xhci_transfer_destroy(usb_transfer_batch_t *);
+
+static inline xhci_transfer_t *xhci_transfer_from_batch(usb_transfer_batch_t *batch)
+{
+	assert(batch);
+	return (xhci_transfer_t *) batch;
+}
+
+/**
+ * @}
+ */
+#endif
Index: uspace/drv/bus/usb/xhci/trb_ring.c
===================================================================
--- uspace/drv/bus/usb/xhci/trb_ring.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/trb_ring.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,495 @@
+/*
+ * Copyright (c) 2017 Ondrej Hlavaty
+ * 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.
+ */
+
+#include <errno.h>
+#include <assert.h>
+#include <ddi.h>
+#include <as.h>
+#include <align.h>
+#include <libarch/barrier.h>
+#include <usb/debug.h>
+#include "hw_struct/trb.h"
+#include "trb_ring.h"
+
+/**
+ * A structure representing a segment of a TRB ring.
+ */
+
+#define SEGMENT_FOOTER_SIZE (sizeof(link_t) + sizeof(uintptr_t))
+
+#define SEGMENT_TRB_COUNT ((PAGE_SIZE - SEGMENT_FOOTER_SIZE) / sizeof(xhci_trb_t))
+#define SEGMENT_TRB_USEFUL_COUNT (SEGMENT_TRB_COUNT - 1)
+
+struct trb_segment {
+	xhci_trb_t trb_storage [SEGMENT_TRB_COUNT];
+
+	link_t segments_link;
+	uintptr_t phys;
+} __attribute__((aligned(PAGE_SIZE)));
+
+static_assert(sizeof(trb_segment_t) == PAGE_SIZE);
+
+
+/**
+ * Get the first TRB of a segment.
+ */
+static inline xhci_trb_t *segment_begin(trb_segment_t *segment)
+{
+	return segment->trb_storage;
+}
+
+/**
+ * Get the one-past-end TRB of a segment.
+ */
+static inline xhci_trb_t *segment_end(trb_segment_t *segment)
+{
+	return segment_begin(segment) + SEGMENT_TRB_COUNT;
+}
+
+/**
+ * Return a first segment of a list of segments.
+ */
+static inline trb_segment_t *get_first_segment(list_t *segments)
+{
+	return list_get_instance(list_first(segments), trb_segment_t, segments_link);
+
+}
+
+/**
+ * Allocate and initialize new segment.
+ *
+ * TODO: When the HC supports 64-bit addressing, there's no need to restrict
+ * to DMAMEM_4GiB.
+ */
+static int trb_segment_alloc(trb_segment_t **segment)
+{
+	dma_buffer_t dbuf;
+
+	const int err = dma_buffer_alloc(&dbuf, PAGE_SIZE);
+	if (err)
+		return err;
+
+	*segment = dbuf.virt;
+	memset(*segment, 0, PAGE_SIZE);
+	(*segment)->phys = dbuf.phys;
+	usb_log_debug("Allocated new ring segment.");
+	return EOK;
+}
+
+static void trb_segment_free(trb_segment_t *segment)
+{
+	dma_buffer_t dbuf = { .virt = segment, .phys = segment->phys };
+	dma_buffer_free(&dbuf);
+}
+
+/**
+ * Initializes the ring with one segment.
+ *
+ * @param[in] initial_size A number of free slots on the ring, 0 leaves the
+ * choice on a reasonable default (one page-sized segment).
+ */
+int xhci_trb_ring_init(xhci_trb_ring_t *ring, size_t initial_size)
+{
+	int err;
+	if (initial_size == 0)
+		initial_size = SEGMENT_TRB_USEFUL_COUNT;
+
+	list_initialize(&ring->segments);
+	size_t segment_count = (initial_size + SEGMENT_TRB_USEFUL_COUNT - 1)
+		/ SEGMENT_TRB_USEFUL_COUNT;
+
+	for (size_t i = 0; i < segment_count; ++i) {
+		struct trb_segment *segment;
+		if ((err = trb_segment_alloc(&segment)) != EOK)
+			return err;
+
+		list_append(&segment->segments_link, &ring->segments);
+		ring->segment_count = i + 1;
+	}
+
+	trb_segment_t * const segment = get_first_segment(&ring->segments);
+	xhci_trb_t *last = segment_end(segment) - 1;
+	xhci_trb_link_fill(last, segment->phys);
+	TRB_LINK_SET_TC(*last, true);
+
+	ring->enqueue_segment = segment;
+	ring->enqueue_trb = segment_begin(segment);
+	ring->dequeue = segment->phys;
+	ring->pcs = 1;
+
+	fibril_mutex_initialize(&ring->guard);
+
+	return EOK;
+}
+
+/**
+ * Free all segments inside the ring.
+ */
+void xhci_trb_ring_fini(xhci_trb_ring_t *ring)
+{
+	assert(ring);
+
+	list_foreach_safe(ring->segments, cur, next) {
+		trb_segment_t *segment =
+		    list_get_instance(cur, trb_segment_t, segments_link);
+		trb_segment_free(segment);
+	}
+}
+
+/**
+ * When the enqueue pointer targets a Link TRB, resolve it.
+ *
+ * Relies on segments being in the segment list in linked order.
+ *
+ * According to section 4.9.2.2, figure 16, the link TRBs cannot be chained, so
+ * it shall not be called in cycle, nor have an inner cycle.
+ */
+static void trb_ring_resolve_link(xhci_trb_ring_t *ring)
+{
+	link_t *next_segment =
+	    list_next(&ring->enqueue_segment->segments_link, &ring->segments);
+	if (!next_segment)
+		next_segment = list_first(&ring->segments);
+	assert(next_segment);
+
+	ring->enqueue_segment =
+	    list_get_instance(next_segment, trb_segment_t, segments_link);
+	ring->enqueue_trb = segment_begin(ring->enqueue_segment);
+}
+
+/**
+ * Get the physical address of the enqueue pointer.
+ */
+static uintptr_t trb_ring_enqueue_phys(xhci_trb_ring_t *ring)
+{
+	uintptr_t trb_id = ring->enqueue_trb - segment_begin(ring->enqueue_segment);
+	return ring->enqueue_segment->phys + trb_id * sizeof(xhci_trb_t);
+}
+
+/**
+ * Decides whether the TRB will trigger an interrupt after being processed.
+ */
+static bool trb_generates_interrupt(xhci_trb_t *trb)
+{
+	return TRB_TYPE(*trb) >= XHCI_TRB_TYPE_ENABLE_SLOT_CMD
+		|| TRB_IOC(*trb);
+}
+
+/**
+ * Enqueue TD composed of TRBs.
+ *
+ * This will copy specified number of TRBs chained together into the ring. The
+ * cycle flag in TRBs may be changed.
+ *
+ * The copied TRBs must be contiguous in memory, and must not contain Link TRBs.
+ *
+ * We cannot avoid the copying, because the TRB in ring should be updated
+ * atomically.
+ *
+ * @param first_trb the first TRB
+ * @param trbs number of TRBS to enqueue
+ * @param phys returns address of the last TRB enqueued
+ * @return EOK on success,
+ *         EAGAIN when the ring is too full to fit all TRBs (temporary)
+ */
+int xhci_trb_ring_enqueue_multiple(xhci_trb_ring_t *ring, xhci_trb_t *first_trb,
+	size_t trbs, uintptr_t *phys)
+{
+	int err;
+	assert(trbs > 0);
+	fibril_mutex_lock(&ring->guard);
+
+	xhci_trb_t * const saved_enqueue_trb = ring->enqueue_trb;
+	trb_segment_t * const saved_enqueue_segment = ring->enqueue_segment;
+	if (phys)
+		*phys = (uintptr_t)NULL;
+
+	/*
+	 * First, dry run and advance the enqueue pointer to see if the ring would
+	 * be full anytime during the transaction.
+	 */
+	xhci_trb_t *trb = first_trb;
+	for (size_t i = 0; i < trbs; ++i, ++trb) {
+		if (phys && trb_generates_interrupt(trb)) {
+			if (*phys) {
+				err = ENOTSUP;
+				goto err;
+			}
+			*phys = trb_ring_enqueue_phys(ring);
+		}
+
+		ring->enqueue_trb++;
+
+		if (TRB_TYPE(*ring->enqueue_trb) == XHCI_TRB_TYPE_LINK)
+			trb_ring_resolve_link(ring);
+
+		if (trb_ring_enqueue_phys(ring) == ring->dequeue) {
+			err = EAGAIN;
+			goto err;
+		}
+	}
+
+	ring->enqueue_segment = saved_enqueue_segment;
+	ring->enqueue_trb = saved_enqueue_trb;
+
+	/*
+	 * Now, copy the TRBs without further checking.
+	 */
+	trb = first_trb;
+	for (size_t i = 0; i < trbs; ++i, ++trb) {
+		TRB_SET_CYCLE(*trb, ring->pcs);
+		xhci_trb_copy_to_pio(ring->enqueue_trb, trb);
+
+		usb_log_debug2("TRB ring(%p): Enqueued TRB %p", ring, trb);
+		ring->enqueue_trb++;
+
+		if (TRB_TYPE(*ring->enqueue_trb) == XHCI_TRB_TYPE_LINK) {
+			TRB_SET_CYCLE(*ring->enqueue_trb, ring->pcs);
+
+			if (TRB_LINK_TC(*ring->enqueue_trb)) {
+				ring->pcs = !ring->pcs;
+				usb_log_debug("TRB ring(%p): PCS toggled", ring);
+			}
+
+			trb_ring_resolve_link(ring);
+		}
+	}
+
+	fibril_mutex_unlock(&ring->guard);
+	return EOK;
+
+err:
+	ring->enqueue_segment = saved_enqueue_segment;
+	ring->enqueue_trb = saved_enqueue_trb;
+	fibril_mutex_unlock(&ring->guard);
+	return err;
+}
+
+/**
+ * Enqueue TD composed of a single TRB. See: `xhci_trb_ring_enqueue_multiple`
+ */
+int xhci_trb_ring_enqueue(xhci_trb_ring_t *ring, xhci_trb_t *td, uintptr_t *phys)
+{
+	return xhci_trb_ring_enqueue_multiple(ring, td, 1, phys);
+}
+
+void xhci_trb_ring_reset_dequeue_state(xhci_trb_ring_t *ring, uintptr_t *addr)
+{
+	assert(ring);
+
+	ring->dequeue = trb_ring_enqueue_phys(ring);
+
+	if (addr)
+		*addr = ring->dequeue | ring->pcs;
+}
+
+/**
+ * Initializes an event ring.
+ *
+ * @param[in] initial_size A number of free slots on the ring, 0 leaves the
+ * choice on a reasonable default (one page-sized segment).
+ */
+int xhci_event_ring_init(xhci_event_ring_t *ring, size_t initial_size)
+{
+	int err;
+	if (initial_size == 0)
+		initial_size = SEGMENT_TRB_COUNT;
+
+	list_initialize(&ring->segments);
+
+	size_t segment_count = (initial_size + SEGMENT_TRB_COUNT - 1) / SEGMENT_TRB_COUNT;
+	size_t erst_size = segment_count * sizeof(xhci_erst_entry_t);
+
+	if (dma_buffer_alloc(&ring->erst, erst_size)) {
+		xhci_event_ring_fini(ring);
+		return ENOMEM;
+	}
+
+	xhci_erst_entry_t *erst = ring->erst.virt;
+	memset(erst, 0, erst_size);
+
+	for (size_t i = 0; i < segment_count; i++) {
+		trb_segment_t *segment;
+		if ((err = trb_segment_alloc(&segment)) != EOK) {
+			xhci_event_ring_fini(ring);
+			return err;
+		}
+
+		list_append(&segment->segments_link, &ring->segments);
+		ring->segment_count = i + 1;
+		xhci_fill_erst_entry(&erst[i], segment->phys, SEGMENT_TRB_COUNT);
+	}
+
+	trb_segment_t * const segment = get_first_segment(&ring->segments);
+	ring->dequeue_segment = segment;
+	ring->dequeue_trb = segment_begin(segment);
+	ring->dequeue_ptr = segment->phys;
+	ring->ccs = 1;
+
+	fibril_mutex_initialize(&ring->guard);
+
+	usb_log_debug("Initialized event ring.");
+	return EOK;
+}
+
+void xhci_event_ring_fini(xhci_event_ring_t *ring)
+{
+	list_foreach_safe(ring->segments, cur, next) {
+		trb_segment_t *segment = list_get_instance(cur, trb_segment_t, segments_link);
+		trb_segment_free(segment);
+	}
+
+	dma_buffer_free(&ring->erst);
+}
+
+/**
+ * Get the physical address of the dequeue pointer.
+ */
+static uintptr_t event_ring_dequeue_phys(xhci_event_ring_t *ring)
+{
+	uintptr_t trb_id = ring->dequeue_trb - segment_begin(ring->dequeue_segment);
+	return ring->dequeue_segment->phys + trb_id * sizeof(xhci_trb_t);
+}
+
+/**
+ * Fill the event with next valid event from the ring.
+ *
+ * @param event pointer to event to be overwritten
+ * @return EOK on success,
+ *         ENOENT when the ring is empty
+ */
+int xhci_event_ring_dequeue(xhci_event_ring_t *ring, xhci_trb_t *event)
+{
+	fibril_mutex_lock(&ring->guard);
+
+	/**
+	 * The ERDP reported to the HC is a half-phase off the one we need to
+	 * maintain. Therefore, we keep it extra.
+	 */
+	ring->dequeue_ptr = event_ring_dequeue_phys(ring);
+
+	if (TRB_CYCLE(*ring->dequeue_trb) != ring->ccs) {
+		fibril_mutex_unlock(&ring->guard);
+		return ENOENT; /* The ring is empty. */
+	}
+
+	/* Do not reorder the Cycle bit reading with memcpy */
+	read_barrier();
+
+	memcpy(event, ring->dequeue_trb, sizeof(xhci_trb_t));
+
+	ring->dequeue_trb++;
+	const unsigned index = ring->dequeue_trb - segment_begin(ring->dequeue_segment);
+
+	/* Wrapping around segment boundary */
+	if (index >= SEGMENT_TRB_COUNT) {
+		link_t *next_segment =
+		    list_next(&ring->dequeue_segment->segments_link, &ring->segments);
+
+		/* Wrapping around table boundary */
+		if (!next_segment) {
+			next_segment = list_first(&ring->segments);
+			ring->ccs = !ring->ccs;
+		}
+
+		ring->dequeue_segment =
+		    list_get_instance(next_segment, trb_segment_t, segments_link);
+		ring->dequeue_trb = segment_begin(ring->dequeue_segment);
+	}
+
+	fibril_mutex_unlock(&ring->guard);
+	return EOK;
+}
+
+void xhci_sw_ring_init(xhci_sw_ring_t *ring, size_t size)
+{
+	ring->begin = calloc(size, sizeof(xhci_trb_t));
+	ring->end = ring->begin + size;
+
+	ring->enqueue = ring->dequeue = ring->begin;
+
+	fibril_mutex_initialize(&ring->guard);
+	fibril_condvar_initialize(&ring->enqueued_cv);
+	fibril_condvar_initialize(&ring->dequeued_cv);
+
+	ring->running = true;
+}
+
+int xhci_sw_ring_enqueue(xhci_sw_ring_t *ring, xhci_trb_t *trb)
+{
+	assert(ring);
+	assert(trb);
+
+	fibril_mutex_lock(&ring->guard);
+	while (ring->running && TRB_CYCLE(*ring->enqueue))
+		fibril_condvar_wait(&ring->dequeued_cv, &ring->guard);
+
+	*ring->enqueue = *trb;
+	TRB_SET_CYCLE(*ring->enqueue, 1);
+	if (++ring->enqueue == ring->end)
+		ring->enqueue = ring->begin;
+	fibril_condvar_signal(&ring->enqueued_cv);
+	fibril_mutex_unlock(&ring->guard);
+
+	return ring->running ? EOK : EINTR;
+}
+
+int xhci_sw_ring_dequeue(xhci_sw_ring_t *ring, xhci_trb_t *trb)
+{
+	assert(ring);
+	assert(trb);
+
+	fibril_mutex_lock(&ring->guard);
+	while (ring->running && !TRB_CYCLE(*ring->dequeue))
+		fibril_condvar_wait(&ring->enqueued_cv, &ring->guard);
+
+	*trb = *ring->dequeue;
+	TRB_SET_CYCLE(*ring->dequeue, 0);
+	if (++ring->dequeue == ring->end)
+		ring->dequeue = ring->begin;
+	fibril_condvar_signal(&ring->dequeued_cv);
+	fibril_mutex_unlock(&ring->guard);
+
+	return ring->running ? EOK : EINTR;
+}
+
+void xhci_sw_ring_stop(xhci_sw_ring_t *ring)
+{
+	ring->running = false;
+	fibril_condvar_broadcast(&ring->enqueued_cv);
+	fibril_condvar_broadcast(&ring->dequeued_cv);
+}
+
+void xhci_sw_ring_fini(xhci_sw_ring_t *ring)
+{
+	free(ring->begin);
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/trb_ring.h
===================================================================
--- uspace/drv/bus/usb/xhci/trb_ring.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/trb_ring.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2017 Ondrej Hlavaty
+ * 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
+ * TRB Ring is a data structure for communication between HC and software.
+ *
+ * Despite this description, it is not used as an hardware structure - all but
+ * the Event ring is used as buffer of the TRBs itself, linked by Link TRB to
+ * form a (possibly multi-segment) circular buffer.
+ *
+ * This data structure abstracts this behavior.
+ */
+
+#ifndef XHCI_TRB_RING_H
+#define XHCI_TRB_RING_H
+
+#include <adt/list.h>
+#include <fibril_synch.h>
+#include <libarch/config.h>
+#include <usb/host/dma_buffer.h>
+
+typedef struct trb_segment trb_segment_t;
+typedef struct xhci_hc xhci_hc_t;
+typedef struct xhci_trb xhci_trb_t;
+typedef struct xhci_erst_entry xhci_erst_entry_t;
+
+/**
+ * A TRB ring of which the software is a producer - command / transfer.
+ */
+typedef struct xhci_trb_ring {
+	list_t segments;                /* List of assigned segments */
+	int segment_count;              /* Number of segments assigned */
+
+	/*
+	 * As the link TRBs connect physical addresses, we need to keep track of
+	 * active segment in virtual memory. The enqueue ptr should always belong
+	 * to the enqueue segment.
+	 */
+	trb_segment_t *enqueue_segment;
+	xhci_trb_t *enqueue_trb;
+
+	uintptr_t dequeue;              /* Last reported position of the dequeue pointer */
+	bool pcs;                       /* Producer Cycle State: section 4.9.2 */
+
+	fibril_mutex_t guard;
+} xhci_trb_ring_t;
+
+extern int xhci_trb_ring_init(xhci_trb_ring_t *, size_t);
+extern void xhci_trb_ring_fini(xhci_trb_ring_t *);
+extern int xhci_trb_ring_enqueue(xhci_trb_ring_t *, xhci_trb_t *, uintptr_t *);
+extern int xhci_trb_ring_enqueue_multiple(xhci_trb_ring_t *, xhci_trb_t *, size_t, uintptr_t *);
+
+extern void xhci_trb_ring_reset_dequeue_state(xhci_trb_ring_t *ring, uintptr_t *addr);
+
+/**
+ * When an event is received by the upper layer, it needs to update the dequeue
+ * pointer inside the ring. Otherwise, the ring will soon show up as full.
+ */
+static inline void xhci_trb_ring_update_dequeue(xhci_trb_ring_t *ring, uintptr_t phys) {
+	ring->dequeue = phys;
+}
+
+/**
+ * A TRB ring of which the software is a consumer (event rings).
+ */
+typedef struct xhci_event_ring {
+	list_t segments;                /* List of assigned segments */
+	int segment_count;              /* Number of segments assigned */
+
+	trb_segment_t *dequeue_segment; /* Current segment of the dequeue ptr */
+	xhci_trb_t *dequeue_trb;        /* Next TRB to be processed */
+	uintptr_t dequeue_ptr;          /* Physical address of the ERDP to be reported to the HC */
+
+	dma_buffer_t erst;              /* ERST given to the HC */
+
+	bool ccs;                       /* Consumer Cycle State: section 4.9.2 */
+
+	fibril_mutex_t guard;
+} xhci_event_ring_t;
+
+extern int xhci_event_ring_init(xhci_event_ring_t *, size_t);
+extern void xhci_event_ring_fini(xhci_event_ring_t *);
+extern int xhci_event_ring_dequeue(xhci_event_ring_t *, xhci_trb_t *);
+
+/**
+ * A TRB ring of which the software is both consumer and provider.
+ */
+typedef struct xhci_sw_ring {
+	xhci_trb_t *begin, *end;
+	xhci_trb_t *enqueue, *dequeue;
+
+	fibril_mutex_t guard;
+	fibril_condvar_t enqueued_cv, dequeued_cv;
+
+	bool running;
+} xhci_sw_ring_t;
+
+extern void xhci_sw_ring_init(xhci_sw_ring_t *, size_t);
+
+/* Both may block if the ring is full/empty. */
+extern int xhci_sw_ring_enqueue(xhci_sw_ring_t *, xhci_trb_t *);
+extern int xhci_sw_ring_dequeue(xhci_sw_ring_t *, xhci_trb_t *);
+
+extern void xhci_sw_ring_stop(xhci_sw_ring_t *);
+extern void xhci_sw_ring_fini(xhci_sw_ring_t *);
+
+#endif
+
+/**
+ * @}
+ */
Index: uspace/drv/bus/usb/xhci/xhci.ma
===================================================================
--- uspace/drv/bus/usb/xhci/xhci.ma	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/drv/bus/usb/xhci/xhci.ma	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,2 @@
+20 usb/host=xhci
+10 pci/class=0c&subclass=03&progif=30
Index: uspace/drv/hid/usbhid/blink1/blink1.c
===================================================================
--- uspace/drv/hid/usbhid/blink1/blink1.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/hid/usbhid/blink1/blink1.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -64,5 +64,5 @@
 	usb_blink1_t *blink1_dev = (usb_blink1_t *) ddf_fun_data_get(fun);
 	if (blink1_dev == NULL) {
-		usb_log_debug("Missing parameters.\n");
+		usb_log_debug("Missing parameters.");
 		return EINVAL;
 	}
@@ -105,5 +105,5 @@
 	    fun_exposed, HID_BLINK1_FUN_NAME);
 	if (fun == NULL) {
-		usb_log_error("Could not create DDF function node `%s'.\n",
+		usb_log_error("Could not create DDF function node `%s'.",
 		    HID_BLINK1_FUN_NAME);
 		return ENOMEM;
@@ -123,5 +123,5 @@
 	errno_t rc = ddf_fun_bind(fun);
 	if (rc != EOK) {
-		usb_log_error("Could not bind DDF function `%s': %s.\n",
+		usb_log_error("Could not bind DDF function `%s': %s.",
 		    ddf_fun_get_name(fun), str_error(rc));
 		ddf_fun_destroy(fun);
@@ -131,5 +131,5 @@
 	rc = ddf_fun_add_to_category(fun, HID_BLINK1_CATEGORY);
 	if (rc != EOK) {
-		usb_log_error("Could not add DDF function to category %s: %s.\n",
+		usb_log_error("Could not add DDF function to category %s: %s.",
 		    HID_BLINK1_CATEGORY, str_error(rc));
 		
Index: uspace/drv/hid/usbhid/generic/hiddev.c
===================================================================
--- uspace/drv/hid/usbhid/generic/hiddev.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/hid/usbhid/generic/hiddev.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -96,5 +96,5 @@
 	const usb_hid_dev_t *hid_dev = fun_hid_dev(fun);
 
-	usb_log_debug2("hid_dev: %p, Max input report size (%zu).\n",
+	usb_log_debug2("hid_dev: %p, Max input report size (%zu).",
 	    hid_dev, hid_dev->max_input_report_size);
 
@@ -105,5 +105,5 @@
     size_t size, size_t *act_size, int *event_nr, unsigned int flags)
 {
-	usb_log_debug2("Generic HID: Get event.\n");
+	usb_log_debug2("Generic HID: Get event.");
 
 	if (buffer == NULL || act_size == NULL || event_nr == NULL) {
@@ -115,5 +115,5 @@
 
 	if (hid_dev->input_report_size > size) {
-		usb_log_debug("input_report_size > size (%zu, %zu)\n",
+		usb_log_debug("input_report_size > size (%zu, %zu)",
 		    hid_dev->input_report_size, size);
 		return EINVAL;	// TODO: other error code
@@ -126,5 +126,5 @@
 	*event_nr = usb_hid_report_number(hid_dev);
 
-	usb_log_debug2("OK\n");
+	usb_log_debug2("OK");
 
 	return EOK;
@@ -133,9 +133,9 @@
 static size_t usb_generic_get_report_descriptor_length(ddf_fun_t *fun)
 {
-	usb_log_debug("Generic HID: Get report descriptor length.\n");
-
-	const usb_hid_dev_t *hid_dev = fun_hid_dev(fun);
-
-	usb_log_debug2("hid_dev->report_desc_size = %zu\n",
+	usb_log_debug("Generic HID: Get report descriptor length.");
+
+	const usb_hid_dev_t *hid_dev = fun_hid_dev(fun);
+
+	usb_log_debug2("hid_dev->report_desc_size = %zu",
 	    hid_dev->report_desc_size);
 
@@ -146,5 +146,5 @@
     size_t size, size_t *actual_size)
 {
-	usb_log_debug2("Generic HID: Get report descriptor.\n");
+	usb_log_debug2("Generic HID: Get report descriptor.");
 
 	const usb_hid_dev_t *hid_dev = fun_hid_dev(fun);
@@ -162,5 +162,5 @@
 static errno_t usb_generic_hid_client_connected(ddf_fun_t *fun)
 {
-	usb_log_debug("Generic HID: Client connected.\n");
+	usb_log_debug("Generic HID: Client connected.");
 	return EOK;
 }
@@ -173,8 +173,8 @@
 
 	if (ddf_fun_unbind(fun) != EOK) {
-		usb_log_error("Failed to unbind generic hid fun.\n");
+		usb_log_error("Failed to unbind generic hid fun.");
 		return;
 	}
-	usb_log_debug2("%s unbound.\n", ddf_fun_get_name(fun));
+	usb_log_debug2("%s unbound.", ddf_fun_get_name(fun));
 	ddf_fun_destroy(fun);
 }
@@ -189,9 +189,9 @@
 
 	/* Create the exposed function. */
-	usb_log_debug("Creating DDF function %s...\n", HID_GENERIC_FUN_NAME);
+	usb_log_debug("Creating DDF function %s...", HID_GENERIC_FUN_NAME);
 	ddf_fun_t *fun = usb_device_ddf_fun_create(hid_dev->usb_dev,
 	    fun_exposed, HID_GENERIC_FUN_NAME);
 	if (fun == NULL) {
-		usb_log_error("Could not create DDF function node.\n");
+		usb_log_error("Could not create DDF function node.");
 		return ENOMEM;
 	}
@@ -204,5 +204,5 @@
 	errno_t rc = ddf_fun_bind(fun);
 	if (rc != EOK) {
-		usb_log_error("Could not bind DDF function: %s.\n",
+		usb_log_error("Could not bind DDF function: %s.",
 		    str_error(rc));
 		ddf_fun_destroy(fun);
@@ -210,5 +210,5 @@
 	}
 
-	usb_log_debug("HID function created. Handle: %" PRIun "\n",
+	usb_log_debug("HID function created. Handle: %" PRIun "",
 	    ddf_fun_get_handle(fun));
 	*data = fun;
@@ -219,4 +219,5 @@
 bool usb_generic_hid_polling_callback(usb_hid_dev_t *hid_dev, void *data)
 {
+	/* Continue polling until the device is about to be removed. */
 	return true;
 }
Index: uspace/drv/hid/usbhid/kbd/kbddev.c
===================================================================
--- uspace/drv/hid/usbhid/kbd/kbddev.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/hid/usbhid/kbd/kbddev.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -185,8 +185,8 @@
 		if (kbd_dev->client_sess == NULL) {
 			kbd_dev->client_sess = sess;
-			usb_log_debug("%s: OK\n", __FUNCTION__);
+			usb_log_debug("%s: OK", __FUNCTION__);
 			async_answer_0(icallid, EOK);
 		} else {
-			usb_log_error("%s: console session already set\n",
+			usb_log_error("%s: console session already set",
 			   __FUNCTION__);
 			async_answer_0(icallid, ELIMIT);
@@ -195,5 +195,5 @@
 	}
 	default:
-			usb_log_error("%s: Unknown method: %d.\n",
+			usb_log_error("%s: Unknown method: %d.",
 			    __FUNCTION__, (int) method);
 			async_answer_0(icallid, EINVAL);
@@ -226,5 +226,5 @@
 	/* Reset the LED data. */
 	memset(kbd_dev->led_data, 0, kbd_dev->led_output_size * sizeof(int32_t));
-	usb_log_debug("Creating output report:\n");
+	usb_log_debug("Creating output report:");
 
 	usb_hid_report_field_t *field = usb_hid_report_get_sibling(
@@ -266,5 +266,5 @@
 	}
 
-	usb_log_debug("Output report buffer: %s\n",
+	usb_log_debug("Output report buffer: %s",
 	    usb_debug_str_buffer(kbd_dev->output_buffer, kbd_dev->output_size,
 	        0));
@@ -276,5 +276,5 @@
 	    kbd_dev->output_buffer, kbd_dev->output_size);
 	if (rc != EOK) {
-		usb_log_warning("Failed to set kbd indicators.\n");
+		usb_log_warning("Failed to set kbd indicators.");
 	}
 }
@@ -289,5 +289,5 @@
 void usb_kbd_push_ev(usb_kbd_t *kbd_dev, int type, unsigned key)
 {
-	usb_log_debug2("Sending kbdev event %d/%d to the console\n", type, key);
+	usb_log_debug2("Sending kbdev event %d/%d to the console", type, key);
 	if (kbd_dev->client_sess == NULL) {
 		usb_log_warning(
@@ -301,5 +301,5 @@
 		async_exchange_end(exch);
 	} else {
-		usb_log_warning("Failed to send key to console.\n");
+		usb_log_warning("Failed to send key to console.");
 	}
 }
@@ -328,9 +328,9 @@
  * An event is created only when key is pressed or released. Besides handling
  * the events (usb_kbd_push_ev()), the auto-repeat fibril is notified about
- * key presses and releases (see usb_kbd_repeat_start() and 
+ * key presses and releases (see usb_kbd_repeat_start() and
  * usb_kbd_repeat_stop()).
  *
  * @param kbd_dev Keyboard device structure.
- * @param key_codes Parsed keyboard report - codes of currently pressed keys 
+ * @param key_codes Parsed keyboard report - codes of currently pressed keys
  *                  according to HID Usage Tables.
  * @param count Number of key codes in report (size of the report).
@@ -353,5 +353,5 @@
 	    kbd_dev->key_count);
 	if (i != (size_t) -1) {
-		usb_log_error("Detected phantom state.\n");
+		usb_log_error("Detected phantom state.");
 		return;
 	}
@@ -403,5 +403,5 @@
 	ddf_dump_buffer(key_buffer, 512,
 	    kbd_dev->keys_old, 4, kbd_dev->key_count, 0);
-	usb_log_debug2("Stored keys %s.\n", key_buffer);
+	usb_log_debug2("Stored keys %s.", key_buffer);
 }
 
@@ -431,5 +431,5 @@
 	usb_hid_report_path_t *path = usb_hid_report_path();
 	if (path == NULL) {
-		usb_log_error("Failed to create hid/kbd report path.\n");
+		usb_log_error("Failed to create hid/kbd report path.");
 		return;
 	}
@@ -438,5 +438,5 @@
 	   usb_hid_report_path_append_item(path, USB_HIDUT_PAGE_KEYBOARD, 0);
 	if (ret != EOK) {
-		usb_log_error("Failed to append to hid/kbd report path.\n");
+		usb_log_error("Failed to append to hid/kbd report path.");
 		return;
 	}
@@ -452,5 +452,5 @@
 
 	while (field != NULL) {
-		usb_log_debug2("FIELD (%p) - VALUE(%d) USAGE(%u)\n",
+		usb_log_debug2("FIELD (%p) - VALUE(%d) USAGE(%u)",
 		    field, field->value, field->usage);
 
@@ -464,5 +464,5 @@
 			kbd_dev->keys[i] = 0;
 		}
-		usb_log_debug2("Saved %u. key usage %d\n", i, kbd_dev->keys[i]);
+		usb_log_debug2("Saved %u. key usage %d", i, kbd_dev->keys[i]);
 
 		++i;
@@ -502,5 +502,5 @@
 	usb_hid_report_path_t *path = usb_hid_report_path();
 	if (path == NULL) {
-		usb_log_error("Failed to create kbd report path.\n");
+		usb_log_error("Failed to create kbd report path.");
 		usb_kbd_destroy(kbd_dev);
 		return ENOMEM;
@@ -510,5 +510,5 @@
 	    usb_hid_report_path_append_item(path, USB_HIDUT_PAGE_KEYBOARD, 0);
 	if (ret != EOK) {
-		usb_log_error("Failed to append item to kbd report path.\n");
+		usb_log_error("Failed to append item to kbd report path.");
 		usb_hid_report_path_free(path);
 		usb_kbd_destroy(kbd_dev);
@@ -523,9 +523,9 @@
 	usb_hid_report_path_free(path);
 
-	usb_log_debug("Size of the input report: %zu\n", kbd_dev->key_count);
+	usb_log_debug("Size of the input report: %zu", kbd_dev->key_count);
 
 	kbd_dev->keys = calloc(kbd_dev->key_count, sizeof(int32_t));
 	if (kbd_dev->keys == NULL) {
-		usb_log_error("Failed to allocate key buffer.\n");
+		usb_log_error("Failed to allocate key buffer.");
 		usb_kbd_destroy(kbd_dev);
 		return ENOMEM;
@@ -534,5 +534,5 @@
 	kbd_dev->keys_old = calloc(kbd_dev->key_count, sizeof(int32_t));
 	if (kbd_dev->keys_old == NULL) {
-		usb_log_error("Failed to allocate old_key buffer.\n");
+		usb_log_error("Failed to allocate old_key buffer.");
 		usb_kbd_destroy(kbd_dev);
 		return ENOMEM;
@@ -544,14 +544,14 @@
 	    &kbd_dev->output_size, 0);
 	if (kbd_dev->output_buffer == NULL) {
-		usb_log_error("Error creating output report buffer.\n");
+		usb_log_error("Error creating output report buffer.");
 		usb_kbd_destroy(kbd_dev);
 		return ENOMEM;
 	}
 
-	usb_log_debug("Output buffer size: %zu\n", kbd_dev->output_size);
+	usb_log_debug("Output buffer size: %zu", kbd_dev->output_size);
 
 	kbd_dev->led_path = usb_hid_report_path();
 	if (kbd_dev->led_path == NULL) {
-		usb_log_error("Failed to create kbd led report path.\n");
+		usb_log_error("Failed to create kbd led report path.");
 		usb_kbd_destroy(kbd_dev);
 		return ENOMEM;
@@ -561,5 +561,5 @@
 	    kbd_dev->led_path, USB_HIDUT_PAGE_LED, 0);
 	if (ret != EOK) {
-		usb_log_error("Failed to append to kbd/led report path.\n");
+		usb_log_error("Failed to append to kbd/led report path.");
 		usb_kbd_destroy(kbd_dev);
 		return ret;
@@ -569,10 +569,10 @@
 	    &hid_dev->report, 0, USB_HID_REPORT_TYPE_OUTPUT);
 
-	usb_log_debug("Output report size (in items): %zu\n",
+	usb_log_debug("Output report size (in items): %zu",
 	    kbd_dev->led_output_size);
 
 	kbd_dev->led_data = calloc(kbd_dev->led_output_size, sizeof(int32_t));
 	if (kbd_dev->led_data == NULL) {
-		usb_log_error("Error creating buffer for LED output report.\n");
+		usb_log_error("Error creating buffer for LED output report.");
 		usb_kbd_destroy(kbd_dev);
 		return ENOMEM;
@@ -588,5 +588,5 @@
 
 	kbd_dev->initialized = USB_KBD_STATUS_INITIALIZED;
-	usb_log_debug("HID/KBD device structure initialized.\n");
+	usb_log_debug("HID/KBD device structure initialized.");
 
 	return EOK;
@@ -618,5 +618,5 @@
 errno_t usb_kbd_init(usb_hid_dev_t *hid_dev, void **data)
 {
-	usb_log_debug("Initializing HID/KBD structure...\n");
+	usb_log_debug("Initializing HID/KBD structure...");
 
 	if (hid_dev == NULL) {
@@ -627,9 +627,9 @@
 
 	/* Create the exposed function. */
-	usb_log_debug("Creating DDF function %s...\n", HID_KBD_FUN_NAME);
+	usb_log_debug("Creating DDF function %s...", HID_KBD_FUN_NAME);
 	ddf_fun_t *fun = usb_device_ddf_fun_create(hid_dev->usb_dev,
 	    fun_exposed, HID_KBD_FUN_NAME);
 	if (fun == NULL) {
-		usb_log_error("Could not create DDF function node.\n");
+		usb_log_error("Could not create DDF function node.");
 		return ENOMEM;
 	}
@@ -637,5 +637,5 @@
 	usb_kbd_t *kbd_dev = ddf_fun_data_alloc(fun, sizeof(usb_kbd_t));
 	if (kbd_dev == NULL) {
-		usb_log_error("Failed to allocate KBD device structure.\n");
+		usb_log_error("Failed to allocate KBD device structure.");
 		ddf_fun_destroy(fun);
 		return ENOMEM;
@@ -644,5 +644,5 @@
 	errno_t ret = kbd_dev_init(kbd_dev, hid_dev);
 	if (ret != EOK) {
-		usb_log_error("Failed to initialize KBD device  structure.\n");
+		usb_log_error("Failed to initialize KBD device  structure.");
 		ddf_fun_destroy(fun);
 		return ret;
@@ -655,5 +655,5 @@
 	ret = ddf_fun_bind(fun);
 	if (ret != EOK) {
-		usb_log_error("Could not bind DDF function: %s.\n",
+		usb_log_error("Could not bind DDF function: %s.",
 		    str_error(ret));
 		usb_kbd_destroy(kbd_dev);
@@ -662,8 +662,8 @@
 	}
 
-	usb_log_debug("%s function created. Handle: %" PRIun "\n",
+	usb_log_debug("%s function created. Handle: %" PRIun "",
 	    HID_KBD_FUN_NAME, ddf_fun_get_handle(fun));
 
-	usb_log_debug("Adding DDF function to category %s...\n",
+	usb_log_debug("Adding DDF function to category %s...",
 	    HID_KBD_CATEGORY_NAME);
 	ret = ddf_fun_add_to_category(fun, HID_KBD_CATEGORY_NAME);
@@ -709,4 +709,5 @@
 	usb_kbd_process_data(hid_dev, kbd_dev);
 
+	/* Continue polling until the device is about to be removed. */
 	return true;
 }
@@ -752,8 +753,8 @@
 	if (kbd_dev->fun) {
 		if (ddf_fun_unbind(kbd_dev->fun) != EOK) {
-			usb_log_warning("Failed to unbind %s.\n",
+			usb_log_warning("Failed to unbind %s.",
 			    ddf_fun_get_name(kbd_dev->fun));
 		} else {
-			usb_log_debug2("%s unbound.\n",
+			usb_log_debug2("%s unbound.",
 			    ddf_fun_get_name(kbd_dev->fun));
 			ddf_fun_destroy(kbd_dev->fun);
@@ -783,5 +784,5 @@
 
 	if (rc != EOK) {
-		usb_log_error("Failed to parse boot report descriptor: %s\n",
+		usb_log_error("Failed to parse boot report descriptor: %s",
 		    str_error(rc));
 		return rc;
Index: uspace/drv/hid/usbhid/kbd/kbdrepeat.c
===================================================================
--- uspace/drv/hid/usbhid/kbd/kbdrepeat.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/hid/usbhid/kbd/kbdrepeat.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -69,10 +69,10 @@
 	unsigned int delay = 0;
 
-	usb_log_debug("Starting autorepeat loop.\n");
+	usb_log_debug("Starting autorepeat loop.");
 
 	while (true) {
 		/* Check if the kbd structure is usable. */
 		if (!usb_kbd_is_initialized(kbd)) {
-			usb_log_warning("kbd not ready, exiting autorepeat.\n");
+			usb_log_warning("kbd not ready, exiting autorepeat.");
 			return;
 		}
@@ -82,5 +82,5 @@
 		if (kbd->repeat.key_new > 0) {
 			if (kbd->repeat.key_new == kbd->repeat.key_repeated) {
-				usb_log_debug2("Repeating key: %u.\n",
+				usb_log_debug2("Repeating key: %u.",
 				    kbd->repeat.key_repeated);
 				usb_kbd_push_ev(kbd, KEY_PRESS,
@@ -88,5 +88,5 @@
 				delay = kbd->repeat.delay_between;
 			} else {
-				usb_log_debug2("New key to repeat: %u.\n",
+				usb_log_debug2("New key to repeat: %u.",
 				    kbd->repeat.key_new);
 				kbd->repeat.key_repeated = kbd->repeat.key_new;
@@ -95,5 +95,5 @@
 		} else {
 			if (kbd->repeat.key_repeated > 0) {
-				usb_log_debug2("Stopping to repeat key: %u.\n",
+				usb_log_debug2("Stopping to repeat key: %u.",
 				    kbd->repeat.key_repeated);
 				kbd->repeat.key_repeated = 0;
@@ -119,8 +119,8 @@
 errno_t usb_kbd_repeat_fibril(void *arg)
 {
-	usb_log_debug("Autorepeat fibril spawned.\n");
+	usb_log_debug("Autorepeat fibril spawned.");
 
 	if (arg == NULL) {
-		usb_log_error("No device!\n");
+		usb_log_error("No device!");
 		return EINVAL;
 	}
Index: uspace/drv/hid/usbhid/main.c
===================================================================
--- uspace/drv/hid/usbhid/main.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/hid/usbhid/main.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -58,8 +58,8 @@
 static errno_t usb_hid_device_add(usb_device_t *dev)
 {
-	usb_log_debug("%s\n", __FUNCTION__);
+	usb_log_debug("%s", __FUNCTION__);
 
 	if (dev == NULL) {
-		usb_log_error("Wrong parameter given for add_device().\n");
+		usb_log_error("Wrong parameter given for add_device().");
 		return EINVAL;
 	}
@@ -73,5 +73,5 @@
 	    usb_device_data_alloc(dev, sizeof(usb_hid_dev_t));
 	if (hid_dev == NULL) {
-		usb_log_error("Failed to create USB/HID device structure.\n");
+		usb_log_error("Failed to create USB/HID device structure.");
 		return ENOMEM;
 	}
@@ -79,30 +79,18 @@
 	errno_t rc = usb_hid_init(hid_dev, dev);
 	if (rc != EOK) {
-		usb_log_error("Failed to initialize USB/HID device.\n");
+		usb_log_error("Failed to initialize USB/HID device.");
 		usb_hid_deinit(hid_dev);
 		return rc;
 	}
 
-	usb_log_debug("USB/HID device structure initialized.\n");
+	usb_log_debug("USB/HID device structure initialized.");
 
 	/* Start automated polling function.
 	 * This will create a separate fibril that will query the device
 	 * for the data continuously. */
-	rc = usb_device_auto_poll_desc(dev,
-	   /* Index of the polling pipe. */
-	   hid_dev->poll_pipe_mapping->description,
-	   /* Callback when data arrives. */
-	   usb_hid_polling_callback,
-	   /* How much data to request. */
-	   hid_dev->poll_pipe_mapping->pipe.max_packet_size,
-	   /* Delay */
-	   -1,
-	   /* Callback when the polling ends. */
-	   usb_hid_polling_ended_callback,
-	   /* Custom argument. */
-	   hid_dev);
+	rc = usb_polling_start(&hid_dev->polling);
 
 	if (rc != EOK) {
-		usb_log_error("Failed to start polling fibril for `%s'.\n",
+		usb_log_error("Failed to start polling fibril for `%s'.",
 		    usb_device_get_name(dev));
 		usb_hid_deinit(hid_dev);
@@ -111,5 +99,21 @@
 	hid_dev->running = true;
 
-	usb_log_info("HID device `%s' ready.\n", usb_device_get_name(dev));
+	usb_log_info("HID device `%s' ready.", usb_device_get_name(dev));
+
+	return EOK;
+}
+
+static int join_and_clean(usb_device_t *dev)
+{
+	assert(dev);
+	usb_hid_dev_t *hid_dev = usb_device_data_get(dev);
+	assert(hid_dev);
+
+	/* Join polling fibril (ignoring error code). */
+	usb_polling_join(&hid_dev->polling);
+
+	/* Clean up. */
+	usb_hid_deinit(hid_dev);
+	usb_log_info("%s destruction complete.", usb_device_get_name(dev));
 
 	return EOK;
@@ -122,9 +126,12 @@
  * @return Error code.
  */
-static errno_t usb_hid_device_rem(usb_device_t *dev)
+static errno_t usb_hid_device_remove(usb_device_t *dev)
 {
-	// TODO: Stop device polling
-	// TODO: Call deinit (stops autorepeat too)
-	return ENOTSUP;
+	assert(dev);
+	usb_hid_dev_t *hid_dev = usb_device_data_get(dev);
+	assert(hid_dev);
+
+	usb_log_info("Device %s removed.", usb_device_get_name(dev));
+	return join_and_clean(dev);
 }
 
@@ -140,17 +147,7 @@
 	usb_hid_dev_t *hid_dev = usb_device_data_get(dev);
 	assert(hid_dev);
-	unsigned tries = 100;
-	/* Wait for fail. */
-	while (hid_dev->running && tries--) {
-		async_usleep(100000);
-	}
-	if (hid_dev->running) {
-		usb_log_error("Can't remove hid, still running.\n");
-		return EBUSY;
-	}
 
-	usb_hid_deinit(hid_dev);
-	usb_log_debug2("%s destruction complete.\n", usb_device_get_name(dev));
-	return EOK;
+	usb_log_info("Device %s gone.", usb_device_get_name(dev));
+	return join_and_clean(dev);
 }
 
@@ -158,5 +155,5 @@
 static const usb_driver_ops_t usb_hid_driver_ops = {
 	.device_add = usb_hid_device_add,
-	.device_rem = usb_hid_device_rem,
+	.device_remove = usb_hid_device_remove,
 	.device_gone = usb_hid_device_gone,
 };
Index: uspace/drv/hid/usbhid/mouse/mousedev.c
===================================================================
--- uspace/drv/hid/usbhid/mouse/mousedev.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/hid/usbhid/mouse/mousedev.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -118,11 +118,11 @@
 
 	if (mouse_dev == NULL) {
-		usb_log_debug("%s: Missing parameters.\n", __FUNCTION__);
+		usb_log_debug("%s: Missing parameters.", __FUNCTION__);
 		async_answer_0(icallid, EINVAL);
 		return;
 	}
 
-	usb_log_debug("%s: fun->name: %s\n", __FUNCTION__, ddf_fun_get_name(fun));
-	usb_log_debug("%s: mouse_sess: %p\n",
+	usb_log_debug("%s: fun->name: %s", __FUNCTION__, ddf_fun_get_name(fun));
+	usb_log_debug("%s: mouse_sess: %p",
 	    __FUNCTION__, mouse_dev->mouse_sess);
 
@@ -132,9 +132,9 @@
 		if (mouse_dev->mouse_sess == NULL) {
 			mouse_dev->mouse_sess = sess;
-			usb_log_debug("Console session to %s set ok (%p).\n",
+			usb_log_debug("Console session to %s set ok (%p).",
 			    ddf_fun_get_name(fun), sess);
 			async_answer_0(icallid, EOK);
 		} else {
-			usb_log_error("Console session to %s already set.\n",
+			usb_log_error("Console session to %s already set.",
 			    ddf_fun_get_name(fun));
 			async_answer_0(icallid, ELIMIT);
@@ -142,14 +142,12 @@
 		}
 	} else {
-		usb_log_debug("%s: Invalid function.\n", __FUNCTION__);
+		usb_log_debug("%s: Invalid function.", __FUNCTION__);
 		async_answer_0(icallid, EINVAL);
 	}
 }
 
-static int get_mouse_axis_move_value(uint8_t rid, usb_hid_report_t *report,
+static const usb_hid_report_field_t *get_mouse_axis_move_field(uint8_t rid, usb_hid_report_t *report,
     int32_t usage)
 {
-	int result = 0;
-
 	usb_hid_report_path_t *path = usb_hid_report_path();
 	usb_hid_report_path_append_item(path, USB_HIDUT_PAGE_GENERIC_DESKTOP,
@@ -158,18 +156,14 @@
 	usb_hid_report_path_set_report_id(path, rid);
 
-	usb_hid_report_field_t *field = usb_hid_report_get_sibling(
+	const usb_hid_report_field_t *field = usb_hid_report_get_sibling(
 	    report, NULL, path, USB_HID_PATH_COMPARE_END,
 	    USB_HID_REPORT_TYPE_INPUT);
 
-	if (field != NULL) {
-		result = field->value;
-	}
-
 	usb_hid_report_path_free(path);
 
-	return result;
-}
-
-static bool usb_mouse_process_report(usb_hid_dev_t *hid_dev,
+	return field;
+}
+
+static void usb_mouse_process_report(usb_hid_dev_t *hid_dev,
     usb_mouse_t *mouse_dev)
 {
@@ -177,21 +171,51 @@
 
 	if (mouse_dev->mouse_sess == NULL) {
-		usb_log_warning(NAME " No console session.\n");
-		return true;
-	}
-
-	const int shift_x = get_mouse_axis_move_value(hid_dev->report_id,
-	    &hid_dev->report, USB_HIDUT_USAGE_GENERIC_DESKTOP_X);
-	const int shift_y = get_mouse_axis_move_value(hid_dev->report_id,
-	    &hid_dev->report, USB_HIDUT_USAGE_GENERIC_DESKTOP_Y);
-	const int wheel = get_mouse_axis_move_value(hid_dev->report_id,
-	    &hid_dev->report, USB_HIDUT_USAGE_GENERIC_DESKTOP_WHEEL);
-
-	if (shift_x || shift_y || wheel) {
+		usb_log_warning(NAME " No console session.");
+		return;
+	}
+
+	const usb_hid_report_field_t *move_x = get_mouse_axis_move_field(
+	    hid_dev->report_id, &hid_dev->report,
+	    USB_HIDUT_USAGE_GENERIC_DESKTOP_X);
+	const usb_hid_report_field_t *move_y = get_mouse_axis_move_field(
+	    hid_dev->report_id, &hid_dev->report,
+	    USB_HIDUT_USAGE_GENERIC_DESKTOP_Y);
+	const usb_hid_report_field_t *wheel= get_mouse_axis_move_field(
+	    hid_dev->report_id, &hid_dev->report,
+	    USB_HIDUT_USAGE_GENERIC_DESKTOP_WHEEL);
+
+	bool absolute_x = move_x && !USB_HID_ITEM_FLAG_RELATIVE(move_x->item_flags);
+	bool absolute_y = move_y && !USB_HID_ITEM_FLAG_RELATIVE(move_y->item_flags);
+
+	/* Tablet shall always report both X and Y */
+	if (absolute_x != absolute_y) {
+		usb_log_error(NAME " cannot handle mix of absolute and relative mouse move.");
+		return;
+	}
+
+	int shift_x = move_x ? move_x->value : 0;
+	int shift_y = move_y ? move_y->value : 0;
+	int shift_z =  wheel ?  wheel->value : 0;
+
+	if (absolute_x && absolute_y) {
+		async_exch_t *exch =
+		    async_exchange_begin(mouse_dev->mouse_sess);
+		if (exch != NULL) {
+			async_msg_4(exch, MOUSEEV_ABS_MOVE_EVENT,
+			    shift_x, shift_y, move_x->logical_maximum, move_y->logical_maximum);
+			async_exchange_end(exch);
+		}
+
+		// Even if we move the mouse absolutely, we need to resolve wheel
+		shift_x = shift_y = 0;
+	}
+
+
+	if (shift_x || shift_y || shift_z) {
 		async_exch_t *exch =
 		    async_exchange_begin(mouse_dev->mouse_sess);
 		if (exch != NULL) {
 			async_msg_3(exch, MOUSEEV_MOVE_EVENT,
-			    shift_x, shift_y, wheel);
+			    shift_x, shift_y, shift_z);
 			async_exchange_end(exch);
 		}
@@ -201,6 +225,6 @@
 	usb_hid_report_path_t *path = usb_hid_report_path();
 	if (path == NULL) {
-		usb_log_warning("Failed to create USB HID report path.\n");
-		return true;
+		usb_log_warning("Failed to create USB HID report path.");
+		return;
 	}
 	errno_t ret =
@@ -208,6 +232,6 @@
 	if (ret != EOK) {
 		usb_hid_report_path_free(path);
-		usb_log_warning("Failed to add buttons to report path.\n");
-		return true;
+		usb_log_warning("Failed to add buttons to report path.");
+		return;
 	}
 	usb_hid_report_path_set_report_id(path, hid_dev->report_id);
@@ -218,5 +242,5 @@
 
 	while (field != NULL) {
-		usb_log_debug2(NAME " VALUE(%X) USAGE(%X)\n", field->value,
+		usb_log_debug2(NAME " VALUE(%X) USAGE(%X)", field->value,
 		    field->usage);
 		assert(field->usage > field->usage_minimum);
@@ -242,6 +266,4 @@
 
 	usb_hid_report_path_free(path);
-
-	return true;
 }
 
@@ -309,5 +331,5 @@
 
 	if (mouse_dev->buttons == NULL) {
-		usb_log_error(NAME ": out of memory, giving up on device!\n");
+		usb_log_error(NAME ": out of memory, giving up on device!");
 		free(mouse_dev);
 		return ENOMEM;
@@ -322,5 +344,5 @@
 errno_t usb_mouse_init(usb_hid_dev_t *hid_dev, void **data)
 {
-	usb_log_debug("Initializing HID/Mouse structure...\n");
+	usb_log_debug("Initializing HID/Mouse structure...");
 
 	if (hid_dev == NULL) {
@@ -331,9 +353,9 @@
 
 	/* Create the exposed function. */
-	usb_log_debug("Creating DDF function %s...\n", HID_MOUSE_FUN_NAME);
+	usb_log_debug("Creating DDF function %s...", HID_MOUSE_FUN_NAME);
 	ddf_fun_t *fun = usb_device_ddf_fun_create(hid_dev->usb_dev,
 	    fun_exposed, HID_MOUSE_FUN_NAME);
 	if (fun == NULL) {
-		usb_log_error("Could not create DDF function node `%s'.\n",
+		usb_log_error("Could not create DDF function node `%s'.",
 		    HID_MOUSE_FUN_NAME);
 		return ENOMEM;
@@ -342,5 +364,5 @@
 	usb_mouse_t *mouse_dev = ddf_fun_data_alloc(fun, sizeof(usb_mouse_t));
 	if (mouse_dev == NULL) {
-		usb_log_error("Failed to alloc HID mouse device structure.\n");
+		usb_log_error("Failed to alloc HID mouse device structure.");
 		ddf_fun_destroy(fun);
 		return ENOMEM;
@@ -349,5 +371,5 @@
 	errno_t ret = mouse_dev_init(mouse_dev, hid_dev);
 	if (ret != EOK) {
-		usb_log_error("Failed to init HID mouse device structure.\n");
+		usb_log_error("Failed to init HID mouse device structure.");
 		return ret;
 	}
@@ -357,5 +379,5 @@
 	ret = ddf_fun_bind(fun);
 	if (ret != EOK) {
-		usb_log_error("Could not bind DDF function `%s': %s.\n",
+		usb_log_error("Could not bind DDF function `%s': %s.",
 		    ddf_fun_get_name(fun), str_error(ret));
 		ddf_fun_destroy(fun);
@@ -363,5 +385,5 @@
 	}
 
-	usb_log_debug("Adding DDF function `%s' to category %s...\n",
+	usb_log_debug("Adding DDF function `%s' to category %s...",
 	    ddf_fun_get_name(fun), HID_MOUSE_CATEGORY);
 	ret = ddf_fun_add_to_category(fun, HID_MOUSE_CATEGORY);
@@ -390,6 +412,8 @@
 
 	usb_mouse_t *mouse_dev = data;
-
-	return usb_mouse_process_report(hid_dev, mouse_dev);
+	usb_mouse_process_report(hid_dev, mouse_dev);
+
+	/* Continue polling until the device is about to be removed. */
+	return true;
 }
 
@@ -420,5 +444,5 @@
 
 	if (rc != EOK) {
-		usb_log_error("Failed to parse boot report descriptor: %s\n",
+		usb_log_error("Failed to parse boot report descriptor: %s",
 		    str_error(rc));
 		return rc;
Index: uspace/drv/hid/usbhid/multimedia/multimedia.c
===================================================================
--- uspace/drv/hid/usbhid/multimedia/multimedia.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/hid/usbhid/multimedia/multimedia.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -86,5 +86,5 @@
     ipc_callid_t icallid, ipc_call_t *icall)
 {
-	usb_log_debug(NAME " default_connection_handler()\n");
+	usb_log_debug(NAME " default_connection_handler()");
 
 	usb_multimedia_t *multim_dev = ddf_fun_data_get(fun);
@@ -95,5 +95,5 @@
 		if (multim_dev->console_sess == NULL) {
 			multim_dev->console_sess = sess;
-			usb_log_debug(NAME " Saved session to console: %p\n",
+			usb_log_debug(NAME " Saved session to console: %p",
 			    sess);
 			async_answer_0(icallid, EOK);
@@ -137,5 +137,5 @@
 	};
 
-	usb_log_debug2(NAME " Sending key %d to the console\n", ev.key);
+	usb_log_debug2(NAME " Sending key %d to the console", ev.key);
 	if (multim_dev->console_sess == NULL) {
 		usb_log_warning(
@@ -149,5 +149,5 @@
 		async_exchange_end(exch);
 	} else {
-		usb_log_warning("Failed to send multimedia key.\n");
+		usb_log_warning("Failed to send multimedia key.");
 	}
 }
@@ -159,5 +159,5 @@
 	}
 
-	usb_log_debug(NAME " Initializing HID/multimedia structure...\n");
+	usb_log_debug(NAME " Initializing HID/multimedia structure...");
 
 	/* Create the exposed function. */
@@ -165,5 +165,5 @@
 	    hid_dev->usb_dev, fun_exposed, NAME);
 	if (fun == NULL) {
-		usb_log_error("Could not create DDF function node.\n");
+		usb_log_error("Could not create DDF function node.");
 		return ENOMEM;
 	}
@@ -184,5 +184,5 @@
 	errno_t rc = ddf_fun_bind(fun);
 	if (rc != EOK) {
-		usb_log_error("Could not bind DDF function: %s.\n",
+		usb_log_error("Could not bind DDF function: %s.",
 		    str_error(rc));
 		ddf_fun_destroy(fun);
@@ -190,5 +190,5 @@
 	}
 
-	usb_log_debug(NAME " function created (handle: %" PRIun ").\n",
+	usb_log_debug(NAME " function created (handle: %" PRIun ").",
 	    ddf_fun_get_handle(fun));
 
@@ -199,5 +199,5 @@
 		    str_error(rc));
 		if (ddf_fun_unbind(fun) != EOK) {
-			usb_log_error("Failed to unbind %s, won't destroy.\n",
+			usb_log_error("Failed to unbind %s, won't destroy.",
 			    ddf_fun_get_name(fun));
 		} else {
@@ -210,5 +210,5 @@
 	*data = fun;
 
-	usb_log_debug(NAME " HID/multimedia structure initialized.\n");
+	usb_log_debug(NAME " HID/multimedia structure initialized.");
 	return EOK;
 }
@@ -224,8 +224,8 @@
 		async_hangup(multim_dev->console_sess);
 	if (ddf_fun_unbind(fun) != EOK) {
-		usb_log_error("Failed to unbind %s, won't destroy.\n",
+		usb_log_error("Failed to unbind %s, won't destroy.",
 		    ddf_fun_get_name(fun));
 	} else {
-		usb_log_debug2("%s unbound.\n", ddf_fun_get_name(fun));
+		usb_log_debug2("%s unbound.", ddf_fun_get_name(fun));
 		/* This frees multim_dev too as it was stored in
 		 * fun->data */
@@ -266,5 +266,5 @@
 	while (field != NULL) {
 		if (field->value != 0) {
-			usb_log_debug(NAME " KEY VALUE(%X) USAGE(%X)\n",
+			usb_log_debug(NAME " KEY VALUE(%X) USAGE(%X)",
 			    field->value, field->usage);
 			const unsigned key =
@@ -272,5 +272,5 @@
 			const char *key_str =
 			    usbhid_multimedia_usage_to_str(field->usage);
-			usb_log_info("Pressed key: %s\n", key_str);
+			usb_log_info("Pressed key: %s", key_str);
 			usb_multimedia_push_ev(multim_dev, KEY_PRESS, key);
 		}
Index: uspace/drv/hid/usbhid/usbhid.c
===================================================================
--- uspace/drv/hid/usbhid/usbhid.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/hid/usbhid/usbhid.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -134,5 +134,5 @@
 	usb_hid_report_path_t *usage_path = usb_hid_report_path();
 	if (usage_path == NULL) {
-		usb_log_debug("Failed to create usage path.\n");
+		usb_log_debug("Failed to create usage path.");
 		return false;
 	}
@@ -143,5 +143,5 @@
 		    mapping->usage_path[i].usage_page,
 		    mapping->usage_path[i].usage) != EOK) {
-			usb_log_debug("Failed to append to usage path.\n");
+			usb_log_debug("Failed to append to usage path.");
 			usb_hid_report_path_free(usage_path);
 			return false;
@@ -149,5 +149,5 @@
 	}
 
-	usb_log_debug("Compare flags: %d\n", mapping->compare);
+	usb_log_debug("Compare flags: %d", mapping->compare);
 
 	bool matches = false;
@@ -155,5 +155,5 @@
 
 	do {
-		usb_log_debug("Trying report id %u\n", report_id);
+		usb_log_debug("Trying report id %u", report_id);
 		if (report_id != 0) {
 			usb_hid_report_path_set_report_id(usage_path,
@@ -166,5 +166,5 @@
 		        USB_HID_REPORT_TYPE_INPUT);
 
-		usb_log_debug("Field: %p\n", field);
+		usb_log_debug("Field: %p", field);
 
 		if (field != NULL) {
@@ -243,5 +243,5 @@
 			    mapping->product_id);
 			if (usb_hid_ids_match(hid_dev, mapping)) {
-				usb_log_debug("IDs matched.\n");
+				usb_log_debug("IDs matched.");
 				matched = true;
 			}
@@ -250,5 +250,5 @@
 		/* Check usage match. */
 		if (mapping->usage_path != NULL) {
-			usb_log_debug("Comparing device against usage path.\n");
+			usb_log_debug("Comparing device against usage path.");
 			if (usb_hid_path_matches(hid_dev, mapping)) {
 				/* Does not matter if IDs were matched. */
@@ -258,5 +258,5 @@
 
 		if (matched) {
-			usb_log_debug("Subdriver matched.\n");
+			usb_log_debug("Subdriver matched.");
 			subdrivers[count++] = &mapping->subdriver;
 		}
@@ -285,5 +285,5 @@
 		    usb_device_get_mapped_ep_desc(dev, endpoints[i].desc);
 		if (epm && epm->present) {
-			usb_log_debug("Found: %s.\n", endpoints[i].description);
+			usb_log_debug("Found: %s.", endpoints[i].description);
 			hid_dev->poll_pipe_mapping = epm;
 			return EOK;
@@ -301,16 +301,16 @@
 
 	do {
-		usb_log_debug("Getting size of the report.\n");
+		usb_log_debug("Getting size of the report.");
 		const size_t size =
 		    usb_hid_report_byte_size(&hid_dev->report, report_id,
 		        USB_HID_REPORT_TYPE_INPUT);
-		usb_log_debug("Report ID: %u, size: %zu\n", report_id, size);
+		usb_log_debug("Report ID: %u, size: %zu", report_id, size);
 		max_size = (size > max_size) ? size : max_size;
-		usb_log_debug("Getting next report ID\n");
+		usb_log_debug("Getting next report ID");
 		report_id = usb_hid_get_next_report_id(&hid_dev->report,
 		    report_id, USB_HID_REPORT_TYPE_INPUT);
 	} while (report_id != 0);
 
-	usb_log_debug("Max size of input report: %zu\n", max_size);
+	usb_log_debug("Max size of input report: %zu", max_size);
 
 	assert(hid_dev->input_report == NULL);
@@ -323,4 +323,76 @@
 
 	return EOK;
+}
+
+static bool usb_hid_polling_callback(usb_device_t *dev, uint8_t *buffer,
+    size_t buffer_size, void *arg)
+{
+	if (dev == NULL || arg == NULL || buffer == NULL) {
+		usb_log_error("Missing arguments to polling callback.");
+		return false;
+	}
+	usb_hid_dev_t *hid_dev = arg;
+
+	assert(hid_dev->input_report != NULL);
+
+	usb_log_debug("New data [%zu/%zu]: %s", buffer_size,
+	    hid_dev->max_input_report_size,
+	    usb_debug_str_buffer(buffer, buffer_size, 0));
+
+	if (hid_dev->max_input_report_size >= buffer_size) {
+		/*! @todo This should probably be atomic. */
+		memcpy(hid_dev->input_report, buffer, buffer_size);
+		hid_dev->input_report_size = buffer_size;
+		usb_hid_new_report(hid_dev);
+	}
+
+	/* Parse the input report */
+	const int rc = usb_hid_parse_report(
+	    &hid_dev->report, buffer, buffer_size, &hid_dev->report_id);
+	if (rc != EOK) {
+		usb_log_warning("Failure in usb_hid_parse_report():"
+		    "%s\n", str_error(rc));
+	}
+
+	bool cont = false;
+	/* Continue if at least one of the subdrivers want to continue */
+	for (unsigned i = 0; i < hid_dev->subdriver_count; ++i) {
+		if (hid_dev->subdrivers[i].poll != NULL) {
+			cont = cont || hid_dev->subdrivers[i].poll(
+			    hid_dev, hid_dev->subdrivers[i].data);
+		}
+	}
+
+	return cont;
+}
+
+static bool usb_hid_polling_error_callback(usb_device_t *dev, int err_code, void *arg)
+{
+	assert(dev);
+	assert(arg);
+	usb_hid_dev_t *hid_dev = arg;
+
+	usb_log_error("Device %s polling error: %s", usb_device_get_name(dev),
+	    str_error(err_code));
+
+	/* Continue polling until the device is about to be removed. */
+	return hid_dev->running;
+}
+
+static void usb_hid_polling_ended_callback(usb_device_t *dev, bool reason, void *arg)
+{
+	assert(dev);
+	assert(arg);
+
+	usb_hid_dev_t *hid_dev = arg;
+
+	for (unsigned i = 0; i < hid_dev->subdriver_count; ++i) {
+		if (hid_dev->subdrivers[i].poll_end != NULL) {
+			hid_dev->subdrivers[i].poll_end(
+			    hid_dev, hid_dev->subdrivers[i].data, reason);
+		}
+	}
+
+	hid_dev->running = false;
 }
 
@@ -332,8 +404,8 @@
  * During initialization, the keyboard is switched into boot protocol, the idle
  * rate is set to 0 (infinity), resulting in the keyboard only reporting event
- * when a key is pressed or released. Finally, the LED lights are turned on 
+ * when a key is pressed or released. Finally, the LED lights are turned on
  * according to the default setup of lock keys.
  *
- * @note By default, the keyboards is initialized with Num Lock turned on and 
+ * @note By default, the keyboards is initialized with Num Lock turned on and
  *       other locks turned off.
  *
@@ -347,5 +419,5 @@
 	assert(dev);
 
-	usb_log_debug("Initializing HID structure...\n");
+	usb_log_debug("Initializing HID structure...");
 
 	usb_hid_report_init(&hid_dev->report);
@@ -369,10 +441,10 @@
 		usb_hid_find_subdrivers(hid_dev);
 	} else {
-		usb_log_error("Failed to parse report descriptor: fallback.\n");
+		usb_log_error("Failed to parse report descriptor: fallback.");
 		hid_dev->subdrivers = NULL;
 		hid_dev->subdriver_count = 0;
 	}
 
-	usb_log_debug("Subdriver count(before trying boot protocol): %d\n",
+	usb_log_debug("Subdriver count(before trying boot protocol): %d",
 	    hid_dev->subdriver_count);
 
@@ -385,5 +457,5 @@
 		switch (hid_dev->poll_pipe_mapping->interface->interface_protocol) {
 		case USB_HID_PROTOCOL_KEYBOARD:
-			usb_log_info("Falling back to kbd boot protocol.\n");
+			usb_log_info("Falling back to kbd boot protocol.");
 			rc = usb_kbd_set_boot_protocol(hid_dev);
 			if (rc == EOK) {
@@ -392,5 +464,5 @@
 			break;
 		case USB_HID_PROTOCOL_MOUSE:
-			usb_log_info("Falling back to mouse boot protocol.\n");
+			usb_log_info("Falling back to mouse boot protocol.");
 			rc = usb_mouse_set_boot_protocol(hid_dev);
 			if (rc == EOK) {
@@ -399,10 +471,10 @@
 			break;
 		default:
-			usb_log_info("Falling back to generic HID driver.\n");
+			usb_log_info("Falling back to generic HID driver.");
 			usb_hid_set_generic_hid_subdriver(hid_dev);
 		}
 	}
 
-	usb_log_debug("Subdriver count(after trying boot protocol): %d\n",
+	usb_log_debug("Subdriver count(after trying boot protocol): %d",
 	    hid_dev->subdriver_count);
 
@@ -419,5 +491,5 @@
 	for (unsigned i = 0; i < hid_dev->subdriver_count; ++i) {
 		if (hid_dev->subdrivers[i].init != NULL) {
-			usb_log_debug("Initializing subdriver %d.\n",i);
+			usb_log_debug("Initializing subdriver %d.",i);
 			const errno_t pret = hid_dev->subdrivers[i].init(hid_dev,
 			    &hid_dev->subdrivers[i].data);
@@ -442,69 +514,25 @@
 		rc = usb_hid_init_report(hid_dev);
 		if (rc != EOK) {
-			usb_log_error("Failed to initialize input report buffer"
-			    ".\n");
-		}
+			usb_log_error("Failed to initialize input report buffer: %s", str_error(rc));
+			// FIXME: What happens now?
+		}
+
+		usb_polling_t *polling = &hid_dev->polling;
+		if ((rc = usb_polling_init(polling))) {
+			usb_log_error("Failed to initialize polling: %s", str_error(rc));
+			// FIXME: What happens now?
+		}
+
+		polling->device = hid_dev->usb_dev;
+		polling->ep_mapping = hid_dev->poll_pipe_mapping;
+		polling->request_size = hid_dev->poll_pipe_mapping->pipe.desc.max_transfer_size;
+		polling->buffer = malloc(polling->request_size);
+		polling->on_data = usb_hid_polling_callback;
+		polling->on_polling_end = usb_hid_polling_ended_callback;
+		polling->on_error = usb_hid_polling_error_callback;
+		polling->arg = hid_dev;
 	}
 
 	return rc;
-}
-
-bool usb_hid_polling_callback(usb_device_t *dev, uint8_t *buffer,
-    size_t buffer_size, void *arg)
-{
-	if (dev == NULL || arg == NULL || buffer == NULL) {
-		usb_log_error("Missing arguments to polling callback.\n");
-		return false;
-	}
-	usb_hid_dev_t *hid_dev = arg;
-
-	assert(hid_dev->input_report != NULL);
-
-	usb_log_debug("New data [%zu/%zu]: %s\n", buffer_size,
-	    hid_dev->max_input_report_size,
-	    usb_debug_str_buffer(buffer, buffer_size, 0));
-
-	if (hid_dev->max_input_report_size >= buffer_size) {
-		/*! @todo This should probably be atomic. */
-		memcpy(hid_dev->input_report, buffer, buffer_size);
-		hid_dev->input_report_size = buffer_size;
-		usb_hid_new_report(hid_dev);
-	}
-
-	/* Parse the input report */
-	const errno_t rc = usb_hid_parse_report(
-	    &hid_dev->report, buffer, buffer_size, &hid_dev->report_id);
-	if (rc != EOK) {
-		usb_log_warning("Failure in usb_hid_parse_report():"
-		    "%s\n", str_error(rc));
-	}
-
-	bool cont = false;
-	/* Continue if at least one of the subdrivers want to continue */
-	for (unsigned i = 0; i < hid_dev->subdriver_count; ++i) {
-		if (hid_dev->subdrivers[i].poll != NULL) {
-			cont = cont || hid_dev->subdrivers[i].poll(
-			    hid_dev, hid_dev->subdrivers[i].data);
-		}
-	}
-
-	return cont;
-}
-
-void usb_hid_polling_ended_callback(usb_device_t *dev, bool reason, void *arg)
-{
-	assert(dev);
-	assert(arg);
-
-	usb_hid_dev_t *hid_dev = arg;
-
-	for (unsigned i = 0; i < hid_dev->subdriver_count; ++i) {
-		if (hid_dev->subdrivers[i].poll_end != NULL) {
-			hid_dev->subdrivers[i].poll_end(
-			    hid_dev, hid_dev->subdrivers[i].data, reason);
-		}
-	}
-
-	hid_dev->running = false;
 }
 
@@ -524,6 +552,8 @@
 	assert(hid_dev->subdrivers != NULL || hid_dev->subdriver_count == 0);
 
-
-	usb_log_debug("Subdrivers: %p, subdriver count: %d\n", 
+	free(hid_dev->polling.buffer);
+	usb_polling_fini(&hid_dev->polling);
+
+	usb_log_debug("Subdrivers: %p, subdriver count: %d",
 	    hid_dev->subdrivers, hid_dev->subdriver_count);
 
@@ -541,5 +571,4 @@
 	/* Destroy the parser */
 	usb_hid_report_deinit(&hid_dev->report);
-
 }
 
Index: uspace/drv/hid/usbhid/usbhid.h
===================================================================
--- uspace/drv/hid/usbhid/usbhid.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/hid/usbhid/usbhid.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -43,4 +43,5 @@
 #include <usb/dev/pipes.h>
 #include <usb/dev/driver.h>
+#include <usb/dev/poll.h>
 #include <usb/hid/hid.h>
 #include <stdbool.h>
@@ -106,4 +107,7 @@
 	usb_endpoint_mapping_t *poll_pipe_mapping;
 
+	/** Device polling structure. */
+	usb_polling_t polling;
+
 	/** Subdrivers. */
 	usb_hid_subdriver_t *subdrivers;
@@ -138,9 +142,4 @@
 void usb_hid_deinit(usb_hid_dev_t *hid_dev);
 
-bool usb_hid_polling_callback(usb_device_t *dev,
-    uint8_t *buffer, size_t buffer_size, void *arg);
-
-void usb_hid_polling_ended_callback(usb_device_t *dev, bool reason, void *arg);
-
 void usb_hid_new_report(usb_hid_dev_t *hid_dev);
 
Index: uspace/drv/nic/ar9271/ar9271.c
===================================================================
--- uspace/drv/nic/ar9271/ar9271.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/nic/ar9271/ar9271.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -666,5 +666,5 @@
 }
 
-static errno_t ar9271_init(ar9271_t *ar9271, usb_device_t *usb_device)
+static errno_t ar9271_init(ar9271_t *ar9271, usb_device_t *usb_device, const usb_endpoint_description_t **endpoints)
 {
 	ar9271->starting_up = true;
@@ -680,5 +680,5 @@
 	}
 	
-	errno_t rc = ath_usb_init(ar9271->ath_device, usb_device);
+	errno_t rc = ath_usb_init(ar9271->ath_device, usb_device, endpoints);
 	if (rc != EOK) {
 		free(ar9271->ath_device);
@@ -851,5 +851,5 @@
 	ar9271->ddf_dev = dev;
 	
-	rc = ar9271_init(ar9271, usb_device_get(dev));
+	rc = ar9271_init(ar9271, usb_device_get(dev), endpoints);
 	if (rc != EOK) {
 		free(ar9271);
Index: uspace/drv/nic/ar9271/ath_usb.c
===================================================================
--- uspace/drv/nic/ar9271/ath_usb.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/nic/ar9271/ath_usb.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -59,5 +59,5 @@
  *
  */
-errno_t ath_usb_init(ath_t *ath, usb_device_t *usb_device)
+errno_t ath_usb_init(ath_t *ath, usb_device_t *usb_device, const usb_endpoint_description_t **endpoints)
 {
 	ath_usb_t *ath_usb = malloc(sizeof(ath_usb_t));
@@ -70,9 +70,22 @@
 	ath_usb->usb_device = usb_device;
 	
-	/* TODO: Assign by iterating over pipes. */
-	ath_usb->output_data_pipe_number = 0;
-	ath_usb->input_data_pipe_number = 1;
-	ath_usb->input_ctrl_pipe_number = 2;
-	ath_usb->output_ctrl_pipe_number = 3;
+	int rc;
+
+#define _MAP_EP(target, ep_no) do {\
+	usb_endpoint_mapping_t *epm = usb_device_get_mapped_ep_desc(usb_device, endpoints[ep_no]);\
+	if (!epm || !epm->present) {\
+		usb_log_error("Failed to map endpoint: " #ep_no ".");\
+		rc = ENOENT;\
+		goto err_ath_usb;\
+	}\
+	target = &epm->pipe;\
+	} while (0);
+
+	_MAP_EP(ath_usb->output_data_pipe, 0);
+	_MAP_EP(ath_usb->input_data_pipe, 1);
+	_MAP_EP(ath_usb->input_ctrl_pipe, 2);
+	_MAP_EP(ath_usb->output_ctrl_pipe, 3);
+
+#undef _MAP_EP
 	
 	ath->ctrl_response_length = 64;
@@ -83,4 +96,7 @@
 	
 	return EOK;
+err_ath_usb:
+	free(ath_usb);
+	return rc;
 }
 
@@ -98,8 +114,5 @@
 {
 	ath_usb_t *ath_usb = (ath_usb_t *) ath->specific_data;
-	usb_pipe_t *pipe = &usb_device_get_mapped_ep(
-	    ath_usb->usb_device, ath_usb->output_ctrl_pipe_number)->pipe;
-	
-	return usb_pipe_write(pipe, buffer, buffer_size);
+	return usb_pipe_write(ath_usb->output_ctrl_pipe, buffer, buffer_size);
 }
 
@@ -118,8 +131,5 @@
 {
 	ath_usb_t *ath_usb = (ath_usb_t *) ath->specific_data;
-	usb_pipe_t *pipe = &usb_device_get_mapped_ep(
-	    ath_usb->usb_device, ath_usb->input_ctrl_pipe_number)->pipe;
-	
-	return usb_pipe_read(pipe, buffer, buffer_size, transferred_size);
+	return usb_pipe_read(ath_usb->input_ctrl_pipe, buffer, buffer_size, transferred_size);
 }
 
@@ -148,9 +158,6 @@
 	
 	ath_usb_t *ath_usb = (ath_usb_t *) ath->specific_data;
-	usb_pipe_t *pipe = &usb_device_get_mapped_ep(
-	    ath_usb->usb_device, ath_usb->output_data_pipe_number)->pipe;
-	
-	errno_t ret_val = usb_pipe_write(pipe, complete_buffer,
-	    complete_buffer_size);
+	const errno_t ret_val = usb_pipe_write(ath_usb->output_data_pipe,
+	    complete_buffer, complete_buffer_size);
 	
 	free(complete_buffer);
@@ -173,7 +180,4 @@
 {
 	ath_usb_t *ath_usb = (ath_usb_t *) ath->specific_data;
-	usb_pipe_t *pipe = &usb_device_get_mapped_ep(
-	    ath_usb->usb_device, ath_usb->input_data_pipe_number)->pipe;
-	
-	return usb_pipe_read(pipe, buffer, buffer_size, transferred_size);
+	return usb_pipe_read(ath_usb->input_data_pipe, buffer, buffer_size, transferred_size);
 }
Index: uspace/drv/nic/ar9271/ath_usb.h
===================================================================
--- uspace/drv/nic/ar9271/ath_usb.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/drv/nic/ar9271/ath_usb.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -45,8 +45,8 @@
 typedef struct {
 	/** USB pipes indexes */
-	int input_ctrl_pipe_number;
-	int output_ctrl_pipe_number;
-	int input_data_pipe_number;
-	int output_data_pipe_number;
+	usb_pipe_t *input_ctrl_pipe;
+	usb_pipe_t *output_ctrl_pipe;
+	usb_pipe_t *input_data_pipe;
+	usb_pipe_t *output_data_pipe;
 	
 	/** Pointer to connected USB device. */
@@ -59,5 +59,5 @@
 } ath_usb_data_header_t;
 
-extern errno_t ath_usb_init(ath_t *, usb_device_t *);
+extern errno_t ath_usb_init(ath_t *, usb_device_t *, const usb_endpoint_description_t **endpoints);
 
 #endif  /* ATHEROS_ATH_USB_H */
Index: uspace/lib/c/include/bitops.h
===================================================================
--- uspace/lib/c/include/bitops.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/c/include/bitops.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -54,4 +54,8 @@
 #define BIT_RANGE_EXTRACT(type, hi, lo, value) \
     (((value) >> (lo)) & BIT_RRANGE(type, (hi) - (lo) + 1))
+
+/** Insert @a value between bits @a hi .. @a lo. */
+#define BIT_RANGE_INSERT(type, hi, lo, value) \
+    (((value) & BIT_RRANGE(type, (hi) - (lo) + 1)) << (lo))
 
 /** Return position of first non-zero bit from left (i.e. [log_2(arg)]).
Index: uspace/lib/c/include/byteorder.h
===================================================================
--- uspace/lib/c/include/byteorder.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/c/include/byteorder.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -85,4 +85,9 @@
 #define ntohl(n)  uint32_t_be2host((n))
 
+#define uint8_t_be2host(n)  (n)
+#define uint8_t_le2host(n)  (n)
+#define host2uint8_t_be(n)  (n)
+#define host2uint8_t_le(n)  (n)
+
 static inline uint64_t uint64_t_byteorder_swap(uint64_t n)
 {
Index: uspace/lib/c/include/ipc/dev_iface.h
===================================================================
--- uspace/lib/c/include/ipc/dev_iface.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/c/include/ipc/dev_iface.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -44,11 +44,11 @@
 	/** Audio device pcm buffer interface */
 	AUDIO_PCM_BUFFER_IFACE,
-	
+
 	/** Network interface controller interface */
 	NIC_DEV_IFACE,
-		
+
 	/** IEEE 802.11 interface controller interface */
 	IEEE80211_DEV_IFACE,
-	
+
 	/** Interface provided by any PCI device. */
 	PCI_DEV_IFACE,
@@ -56,5 +56,7 @@
 	/** Interface provided by any USB device. */
 	USB_DEV_IFACE,
-	/** Interface provided by USB host controller. */
+	/** Interface provided by USB diagnostic devices. */
+	USBDIAG_DEV_IFACE,
+	/** Interface provided by USB host controller to USB device. */
 	USBHC_DEV_IFACE,
 	/** Interface provided by USB HID devices. */
Index: uspace/lib/drv/Makefile
===================================================================
--- uspace/lib/drv/Makefile	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/drv/Makefile	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -47,4 +47,5 @@
 	generic/remote_usb.c \
 	generic/remote_pci.c \
+	generic/remote_usbdiag.c \
 	generic/remote_usbhc.c \
 	generic/remote_usbhid.c \
Index: uspace/lib/drv/generic/dev_iface.c
===================================================================
--- uspace/lib/drv/generic/dev_iface.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/drv/generic/dev_iface.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -48,4 +48,5 @@
 #include "remote_ieee80211.h"
 #include "remote_usb.h"
+#include "remote_usbdiag.h"
 #include "remote_usbhc.h"
 #include "remote_usbhid.h"
@@ -65,4 +66,5 @@
 		[PCI_DEV_IFACE] = &remote_pci_iface,
 		[USB_DEV_IFACE] = &remote_usb_iface,
+		[USBDIAG_DEV_IFACE] = &remote_usbdiag_iface,
 		[USBHC_DEV_IFACE] = &remote_usbhc_iface,
 		[USBHID_DEV_IFACE] = &remote_usbhid_iface,
@@ -85,5 +87,5 @@
 	if (iface_method_idx >= rem_iface->method_count)
 		return NULL;
-	
+
 	return rem_iface->methods[iface_method_idx];
 }
Index: uspace/lib/drv/generic/private/remote_usbdiag.h
===================================================================
--- uspace/lib/drv/generic/private/remote_usbdiag.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/lib/drv/generic/private/remote_usbdiag.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2017 Petr Manek
+ * 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 libdrv
+* @{
+*/
+/** @file
+*/
+
+#ifndef LIBDRV_REMOTE_USBDIAG_H_
+#define LIBDRV_REMOTE_USBDIAG_H_
+
+extern remote_iface_t remote_usbdiag_iface;
+
+#endif
+
+/**
+* @}
+*/
Index: uspace/lib/drv/generic/private/remote_usbhc.h
===================================================================
--- uspace/lib/drv/generic/private/remote_usbhc.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/drv/generic/private/remote_usbhc.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -1,4 +1,5 @@
 /*
  * Copyright (c) 2010 Vojtech Horky
+ * Copyright (c) 2017 Ondrej Hlavaty
  * All rights reserved.
  *
Index: uspace/lib/drv/generic/remote_usb.c
===================================================================
--- uspace/lib/drv/generic/remote_usb.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/drv/generic/remote_usb.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -61,14 +61,5 @@
 
 typedef enum {
-	IPC_M_USB_GET_MY_INTERFACE,
-	IPC_M_USB_GET_MY_DEVICE_HANDLE,
-	IPC_M_USB_RESERVE_DEFAULT_ADDRESS,
-	IPC_M_USB_RELEASE_DEFAULT_ADDRESS,
-	IPC_M_USB_DEVICE_ENUMERATE,
-	IPC_M_USB_DEVICE_REMOVE,
-	IPC_M_USB_REGISTER_ENDPOINT,
-	IPC_M_USB_UNREGISTER_ENDPOINT,
-	IPC_M_USB_READ,
-	IPC_M_USB_WRITE,
+	IPC_M_USB_GET_MY_DESCRIPTION,
 } usb_iface_funcs_t;
 
@@ -79,235 +70,28 @@
  * @return Error code.
  */
-errno_t usb_get_my_interface(async_exch_t *exch, int *usb_iface)
-{
-	if (!exch)
-		return EBADMEM;
-	sysarg_t iface_no;
-	const errno_t ret = async_req_1_1(exch, DEV_IFACE_ID(USB_DEV_IFACE),
-	    IPC_M_USB_GET_MY_INTERFACE, &iface_no);
-	if (ret == EOK && usb_iface)
-		*usb_iface = (int)iface_no;
-	return ret;
-}
-
-/** Tell devman handle of the usb device function.
- *
- * @param[in]  exch   IPC communication exchange
- * @param[out] handle devman handle of the HC used by the target device.
- *
- * @return Error code.
- *
- */
-errno_t usb_get_my_device_handle(async_exch_t *exch, devman_handle_t *handle)
-{
-	devman_handle_t h = 0;
-	const errno_t ret = async_req_1_1(exch, DEV_IFACE_ID(USB_DEV_IFACE),
-	    IPC_M_USB_GET_MY_DEVICE_HANDLE, &h);
-	if (ret == EOK && handle)
-		*handle = (devman_handle_t)h;
-	return ret;
-}
-
-/** Reserve default USB address.
- * @param[in] exch IPC communication exchange
- * @param[in] speed Communication speed of the newly attached device
- * @return Error code.
- */
-errno_t usb_reserve_default_address(async_exch_t *exch, usb_speed_t speed)
-{
-	if (!exch)
-		return EBADMEM;
-	return async_req_2_0(exch, DEV_IFACE_ID(USB_DEV_IFACE),
-	    IPC_M_USB_RESERVE_DEFAULT_ADDRESS, speed);
-}
-
-/** Release default USB address.
- *
- * @param[in] exch IPC communication exchange
- *
- * @return Error code.
- *
- */
-errno_t usb_release_default_address(async_exch_t *exch)
-{
-	if (!exch)
-		return EBADMEM;
-	return async_req_1_0(exch, DEV_IFACE_ID(USB_DEV_IFACE),
-	    IPC_M_USB_RELEASE_DEFAULT_ADDRESS);
-}
-
-/** Trigger USB device enumeration
- *
- * @param[in]  exch   IPC communication exchange
- * @param[out] handle Identifier of the newly added device (if successful)
- *
- * @return Error code.
- *
- */
-errno_t usb_device_enumerate(async_exch_t *exch, unsigned port)
-{
-	if (!exch)
-		return EBADMEM;
-	const errno_t ret = async_req_2_0(exch, DEV_IFACE_ID(USB_DEV_IFACE),
-	    IPC_M_USB_DEVICE_ENUMERATE, port);
-	return ret;
-}
-
-/** Trigger USB device enumeration
- *
- * @param[in] exch   IPC communication exchange
- * @param[in] handle Identifier of the device
- *
- * @return Error code.
- *
- */
-errno_t usb_device_remove(async_exch_t *exch, unsigned port)
-{
-	if (!exch)
-		return EBADMEM;
-	return async_req_2_0(exch, DEV_IFACE_ID(USB_DEV_IFACE),
-	    IPC_M_USB_DEVICE_REMOVE, port);
-}
-
-static_assert(sizeof(sysarg_t) >= 4);
-
-typedef union {
-	uint8_t arr[sizeof(sysarg_t)];
-	sysarg_t arg;
-} pack8_t;
-
-errno_t usb_register_endpoint(async_exch_t *exch, usb_endpoint_t endpoint,
-    usb_transfer_type_t type, usb_direction_t direction,
-    size_t mps, unsigned packets, unsigned interval)
-{
-	if (!exch)
-		return EBADMEM;
-	pack8_t pack;
-	pack.arr[0] = type;
-	pack.arr[1] = direction;
-	pack.arr[2] = interval;
-	pack.arr[3] = packets;
-
-	return async_req_4_0(exch, DEV_IFACE_ID(USB_DEV_IFACE),
-	    IPC_M_USB_REGISTER_ENDPOINT, endpoint, pack.arg, mps);
-
-}
-
-errno_t usb_unregister_endpoint(async_exch_t *exch, usb_endpoint_t endpoint,
-    usb_direction_t direction)
-{
-	if (!exch)
-		return EBADMEM;
-	return async_req_3_0(exch, DEV_IFACE_ID(USB_DEV_IFACE),
-	    IPC_M_USB_UNREGISTER_ENDPOINT, endpoint, direction);
-}
-
-errno_t usb_read(async_exch_t *exch, usb_endpoint_t endpoint, uint64_t setup,
-    void *data, size_t size, size_t *rec_size)
+errno_t usb_get_my_description(async_exch_t *exch, usb_device_desc_t *desc)
 {
 	if (!exch)
 		return EBADMEM;
 
-	if (size == 0 && setup == 0)
-		return EOK;
+	usb_device_desc_t tmp_desc;
 
-	/* Make call identifying target USB device and type of transfer. */
-	aid_t opening_request = async_send_4(exch,
-	    DEV_IFACE_ID(USB_DEV_IFACE), IPC_M_USB_READ, endpoint,
-	    (setup & UINT32_MAX), (setup >> 32), NULL);
-
-	if (opening_request == 0) {
-		return ENOMEM;
-	}
-
-	/* Retrieve the data. */
-	ipc_call_t data_request_call;
-	aid_t data_request =
-	    async_data_read(exch, data, size, &data_request_call);
-
-	if (data_request == 0) {
-		// FIXME: How to let the other side know that we want to abort?
-		async_forget(opening_request);
-		return ENOMEM;
-	}
-
-	/* Wait for the answer. */
-	errno_t data_request_rc;
-	errno_t opening_request_rc;
-	async_wait_for(data_request, &data_request_rc);
-	async_wait_for(opening_request, &opening_request_rc);
-
-	if (data_request_rc != EOK) {
-		/* Prefer the return code of the opening request. */
-		if (opening_request_rc != EOK) {
-			return (errno_t) opening_request_rc;
-		} else {
-			return (errno_t) data_request_rc;
-		}
-	}
-	if (opening_request_rc != EOK) {
-		return (errno_t) opening_request_rc;
-	}
-
-	*rec_size = IPC_GET_ARG2(data_request_call);
-	return EOK;
+	const errno_t ret = async_req_1_5(exch, DEV_IFACE_ID(USB_DEV_IFACE),
+	    IPC_M_USB_GET_MY_DESCRIPTION,
+	    (sysarg_t *) &tmp_desc.address,
+	    (sysarg_t *) &tmp_desc.depth,
+	    (sysarg_t *) &tmp_desc.speed,
+	    &tmp_desc.handle,
+	    (sysarg_t *) &tmp_desc.iface);
+	if (ret == EOK && desc)
+		*desc = tmp_desc;
+	return ret;
 }
 
-errno_t usb_write(async_exch_t *exch, usb_endpoint_t endpoint, uint64_t setup,
-    const void *data, size_t size)
-{
-	if (!exch)
-		return EBADMEM;
-
-	if (size == 0 && setup == 0)
-		return EOK;
-
-	aid_t opening_request = async_send_5(exch,
-	    DEV_IFACE_ID(USB_DEV_IFACE), IPC_M_USB_WRITE, endpoint, size,
-	    (setup & UINT32_MAX), (setup >> 32), NULL);
-
-	if (opening_request == 0) {
-		return ENOMEM;
-	}
-
-	/* Send the data if any. */
-	if (size > 0) {
-		const errno_t ret = async_data_write_start(exch, data, size);
-		if (ret != EOK) {
-			async_forget(opening_request);
-			return ret;
-		}
-	}
-
-	/* Wait for the answer. */
-	errno_t opening_request_rc;
-	async_wait_for(opening_request, &opening_request_rc);
-
-	return (errno_t) opening_request_rc;
-}
-
-static void remote_usb_get_my_interface(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
-static void remote_usb_get_my_device_handle(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
-static void remote_usb_reserve_default_address(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
-static void remote_usb_release_default_address(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
-static void remote_usb_device_enumerate(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
-static void remote_usb_device_remove(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
-static void remote_usb_register_endpoint(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
-static void remote_usb_unregister_endpoint(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
-static void remote_usb_read(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call);
-static void remote_usb_write(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call);
+static void remote_usb_get_my_description(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
 
 /** Remote USB interface operations. */
 static const remote_iface_func_ptr_t remote_usb_iface_ops [] = {
-	[IPC_M_USB_GET_MY_INTERFACE] = remote_usb_get_my_interface,
-	[IPC_M_USB_GET_MY_DEVICE_HANDLE] = remote_usb_get_my_device_handle,
-	[IPC_M_USB_RESERVE_DEFAULT_ADDRESS] = remote_usb_reserve_default_address,
-	[IPC_M_USB_RELEASE_DEFAULT_ADDRESS] = remote_usb_release_default_address,
-	[IPC_M_USB_DEVICE_ENUMERATE] = remote_usb_device_enumerate,
-	[IPC_M_USB_DEVICE_REMOVE] = remote_usb_device_remove,
-	[IPC_M_USB_REGISTER_ENDPOINT] = remote_usb_register_endpoint,
-	[IPC_M_USB_UNREGISTER_ENDPOINT] = remote_usb_unregister_endpoint,
-	[IPC_M_USB_READ] = remote_usb_read,
-	[IPC_M_USB_WRITE] = remote_usb_write,
+	[IPC_M_USB_GET_MY_DESCRIPTION] = remote_usb_get_my_description,
 };
 
@@ -319,304 +103,28 @@
 };
 
-void remote_usb_get_my_interface(ddf_fun_t *fun, void *iface,
+void remote_usb_get_my_description(ddf_fun_t *fun, void *iface,
     ipc_callid_t callid, ipc_call_t *call)
 {
 	const usb_iface_t *usb_iface = (usb_iface_t *) iface;
 
-	if (usb_iface->get_my_interface == NULL) {
+	if (usb_iface->get_my_description == NULL) {
 		async_answer_0(callid, ENOTSUP);
 		return;
 	}
 
-	int iface_no;
-	const errno_t ret = usb_iface->get_my_interface(fun, &iface_no);
+	usb_device_desc_t desc;
+	const errno_t ret = usb_iface->get_my_description(fun, &desc);
 	if (ret != EOK) {
 		async_answer_0(callid, ret);
 	} else {
-		async_answer_1(callid, EOK, iface_no);
+		async_answer_5(callid, EOK,
+		    (sysarg_t) desc.address,
+		    (sysarg_t) desc.depth,
+		    (sysarg_t) desc.speed,
+		    desc.handle,
+		    desc.iface);
 	}
 }
 
-void remote_usb_get_my_device_handle(ddf_fun_t *fun, void *iface,
-    ipc_callid_t callid, ipc_call_t *call)
-{
-	const usb_iface_t *usb_iface = (usb_iface_t *) iface;
-
-	if (usb_iface->get_my_device_handle == NULL) {
-		async_answer_0(callid, ENOTSUP);
-		return;
-	}
-
-	devman_handle_t handle;
-	const errno_t ret = usb_iface->get_my_device_handle(fun, &handle);
-	if (ret != EOK) {
-		async_answer_0(callid, ret);
-	}
-
-	async_answer_1(callid, EOK, (sysarg_t) handle);
-}
-
-void remote_usb_reserve_default_address(ddf_fun_t *fun, void *iface,
-    ipc_callid_t callid, ipc_call_t *call)
-{
-	const usb_iface_t *usb_iface = (usb_iface_t *) iface;
-
-	if (usb_iface->reserve_default_address == NULL) {
-		async_answer_0(callid, ENOTSUP);
-		return;
-	}
-
-	usb_speed_t speed = DEV_IPC_GET_ARG1(*call);
-	const errno_t ret = usb_iface->reserve_default_address(fun, speed);
-	async_answer_0(callid, ret);
-}
-
-void remote_usb_release_default_address(ddf_fun_t *fun, void *iface,
-    ipc_callid_t callid, ipc_call_t *call)
-{
-	const usb_iface_t *usb_iface = (usb_iface_t *) iface;
-
-	if (usb_iface->release_default_address == NULL) {
-		async_answer_0(callid, ENOTSUP);
-		return;
-	}
-
-	const errno_t ret = usb_iface->release_default_address(fun);
-	async_answer_0(callid, ret);
-}
-
-static void remote_usb_device_enumerate(ddf_fun_t *fun, void *iface,
-    ipc_callid_t callid, ipc_call_t *call)
-{
-	const usb_iface_t *usb_iface = (usb_iface_t *) iface;
-
-	if (usb_iface->device_enumerate == NULL) {
-		async_answer_0(callid, ENOTSUP);
-		return;
-	}
-
-	const unsigned port = DEV_IPC_GET_ARG1(*call);
-	const errno_t ret = usb_iface->device_enumerate(fun, port);
-	async_answer_0(callid, ret);
-}
-
-static void remote_usb_device_remove(ddf_fun_t *fun, void *iface,
-    ipc_callid_t callid, ipc_call_t *call)
-{
-	const usb_iface_t *usb_iface = (usb_iface_t *) iface;
-
-	if (usb_iface->device_remove == NULL) {
-		async_answer_0(callid, ENOTSUP);
-		return;
-	}
-
-	const unsigned port = DEV_IPC_GET_ARG1(*call);
-	const errno_t ret = usb_iface->device_remove(fun, port);
-	async_answer_0(callid, ret);
-}
-
-static void remote_usb_register_endpoint(ddf_fun_t *fun, void *iface,
-    ipc_callid_t callid, ipc_call_t *call)
-{
-	usb_iface_t *usb_iface = (usb_iface_t *) iface;
-
-	if (!usb_iface->register_endpoint) {
-		async_answer_0(callid, ENOTSUP);
-		return;
-	}
-
-	const usb_endpoint_t endpoint = DEV_IPC_GET_ARG1(*call);
-	const pack8_t pack = { .arg = DEV_IPC_GET_ARG2(*call)};
-	const size_t max_packet_size = DEV_IPC_GET_ARG3(*call);
-
-	const usb_transfer_type_t transfer_type = pack.arr[0];
-	const usb_direction_t direction = pack.arr[1];
-	unsigned packets = pack.arr[2];
-	unsigned interval = pack.arr[3];
-
-	const errno_t ret = usb_iface->register_endpoint(fun, endpoint,
-	    transfer_type, direction, max_packet_size, packets, interval);
-
-	async_answer_0(callid, ret);
-}
-
-static void remote_usb_unregister_endpoint(ddf_fun_t *fun, void *iface,
-    ipc_callid_t callid, ipc_call_t *call)
-{
-	usb_iface_t *usb_iface = (usb_iface_t *) iface;
-
-	if (!usb_iface->unregister_endpoint) {
-		async_answer_0(callid, ENOTSUP);
-		return;
-	}
-
-	usb_endpoint_t endpoint = (usb_endpoint_t) DEV_IPC_GET_ARG1(*call);
-	usb_direction_t direction = (usb_direction_t) DEV_IPC_GET_ARG2(*call);
-
-	errno_t rc = usb_iface->unregister_endpoint(fun, endpoint, direction);
-
-	async_answer_0(callid, rc);
-}
-
-typedef struct {
-	ipc_callid_t caller;
-	ipc_callid_t data_caller;
-	void *buffer;
-} async_transaction_t;
-
-static void async_transaction_destroy(async_transaction_t *trans)
-{
-	if (trans == NULL) {
-		return;
-	}
-	if (trans->buffer != NULL) {
-		free(trans->buffer);
-	}
-
-	free(trans);
-}
-
-static async_transaction_t *async_transaction_create(ipc_callid_t caller)
-{
-	async_transaction_t *trans = malloc(sizeof(async_transaction_t));
-	if (trans == NULL) {
-		return NULL;
-	}
-
-	trans->caller = caller;
-	trans->data_caller = 0;
-	trans->buffer = NULL;
-
-	return trans;
-}
-
-static void callback_out(errno_t outcome, void *arg)
-{
-	async_transaction_t *trans = arg;
-
-	async_answer_0(trans->caller, outcome);
-
-	async_transaction_destroy(trans);
-}
-
-static void callback_in(errno_t outcome, size_t actual_size, void *arg)
-{
-	async_transaction_t *trans = (async_transaction_t *)arg;
-
-	if (outcome != EOK) {
-		async_answer_0(trans->caller, outcome);
-		if (trans->data_caller) {
-			async_answer_0(trans->data_caller, EINTR);
-		}
-		async_transaction_destroy(trans);
-		return;
-	}
-
-	if (trans->data_caller) {
-		async_data_read_finalize(trans->data_caller,
-		    trans->buffer, actual_size);
-	}
-
-	async_answer_0(trans->caller, EOK);
-
-	async_transaction_destroy(trans);
-}
-
-void remote_usb_read(
-    ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
-{
-	assert(fun);
-	assert(iface);
-	assert(call);
-
-	const usb_iface_t *usb_iface = iface;
-
-	if (!usb_iface->read) {
-		async_answer_0(callid, ENOTSUP);
-		return;
-	}
-
-	const usb_endpoint_t ep = DEV_IPC_GET_ARG1(*call);
-	const uint64_t setup =
-	    ((uint64_t)DEV_IPC_GET_ARG2(*call)) |
-	    (((uint64_t)DEV_IPC_GET_ARG3(*call)) << 32);
-
-	async_transaction_t *trans = async_transaction_create(callid);
-	if (trans == NULL) {
-		async_answer_0(callid, ENOMEM);
-		return;
-	}
-
-	size_t size = 0;
-	if (!async_data_read_receive(&trans->data_caller, &size)) {
-		async_answer_0(callid, EPARTY);
-		async_transaction_destroy(trans);
-		return;
-	}
-
-	trans->buffer = malloc(size);
-	if (trans->buffer == NULL) {
-		async_answer_0(trans->data_caller, ENOMEM);
-		async_answer_0(callid, ENOMEM);
-		async_transaction_destroy(trans);
-		return;
-	}
-
-	const errno_t rc = usb_iface->read(
-	    fun, ep, setup, trans->buffer, size, callback_in, trans);
-
-	if (rc != EOK) {
-		async_answer_0(trans->data_caller, rc);
-		async_answer_0(callid, rc);
-		async_transaction_destroy(trans);
-	}
-}
-
-void remote_usb_write(
-    ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
-{
-	assert(fun);
-	assert(iface);
-	assert(call);
-
-	const usb_iface_t *usb_iface = iface;
-
-	if (!usb_iface->write) {
-		async_answer_0(callid, ENOTSUP);
-		return;
-	}
-
-	const usb_endpoint_t ep = DEV_IPC_GET_ARG1(*call);
-	const size_t data_buffer_len = DEV_IPC_GET_ARG2(*call);
-	const uint64_t setup =
-	    ((uint64_t)DEV_IPC_GET_ARG3(*call)) |
-	    (((uint64_t)DEV_IPC_GET_ARG4(*call)) << 32);
-
-	async_transaction_t *trans = async_transaction_create(callid);
-	if (trans == NULL) {
-		async_answer_0(callid, ENOMEM);
-		return;
-	}
-
-	size_t size = 0;
-	if (data_buffer_len > 0) {
-		const errno_t rc = async_data_write_accept(&trans->buffer, false,
-		    1, data_buffer_len, 0, &size);
-
-		if (rc != EOK) {
-			async_answer_0(callid, rc);
-			async_transaction_destroy(trans);
-			return;
-		}
-	}
-
-	const errno_t rc = usb_iface->write(
-	    fun, ep, setup, trans->buffer, size, callback_out, trans);
-
-	if (rc != EOK) {
-		async_answer_0(callid, rc);
-		async_transaction_destroy(trans);
-	}
-}
 /**
  * @}
Index: uspace/lib/drv/generic/remote_usbdiag.c
===================================================================
--- uspace/lib/drv/generic/remote_usbdiag.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/lib/drv/generic/remote_usbdiag.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,528 @@
+/*
+ * Copyright (c) 2017 Petr Manek
+ * 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 libdrv
+ * @{
+ */
+/** @file
+ * USB diagnostic device remote interface.
+ */
+
+#include <async.h>
+#include <assert.h>
+#include <macros.h>
+#include <errno.h>
+#include <devman.h>
+
+#include "usbdiag_iface.h"
+
+typedef enum {
+	IPC_M_USBDIAG_BURST_INTR_IN,
+	IPC_M_USBDIAG_BURST_INTR_OUT,
+	IPC_M_USBDIAG_BURST_BULK_IN,
+	IPC_M_USBDIAG_BURST_BULK_OUT,
+	IPC_M_USBDIAG_BURST_ISOCH_IN,
+	IPC_M_USBDIAG_BURST_ISOCH_OUT,
+	IPC_M_USBDIAG_DATA_INTR_IN,
+	IPC_M_USBDIAG_DATA_INTR_OUT,
+	IPC_M_USBDIAG_DATA_BULK_IN,
+	IPC_M_USBDIAG_DATA_BULK_OUT,
+	IPC_M_USBDIAG_DATA_ISOCH_IN,
+	IPC_M_USBDIAG_DATA_ISOCH_OUT
+} usb_iface_funcs_t;
+
+async_sess_t *usbdiag_connect(devman_handle_t handle)
+{
+	return devman_device_connect(handle, IPC_FLAG_BLOCKING);
+}
+
+void usbdiag_disconnect(async_sess_t *sess)
+{
+	if (sess)
+		async_hangup(sess);
+}
+
+int usbdiag_burst_intr_in(async_exch_t *exch, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	if (!exch)
+		return EBADMEM;
+
+	sysarg_t duration_;
+	const int rc = async_req_3_1(exch, DEV_IFACE_ID(USBDIAG_DEV_IFACE), IPC_M_USBDIAG_BURST_INTR_IN, cycles, size, &duration_);
+
+	if (rc == EOK && duration)
+		*duration = duration_;
+
+	return rc;
+}
+
+int usbdiag_burst_intr_out(async_exch_t *exch, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	if (!exch)
+		return EBADMEM;
+
+	sysarg_t duration_;
+	const int rc = async_req_3_1(exch, DEV_IFACE_ID(USBDIAG_DEV_IFACE), IPC_M_USBDIAG_BURST_INTR_OUT, cycles, size, &duration_);
+
+	if (rc == EOK && duration)
+		*duration = duration_;
+
+	return rc;
+}
+
+int usbdiag_burst_bulk_in(async_exch_t *exch, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	if (!exch)
+		return EBADMEM;
+
+	sysarg_t duration_;
+	const int rc = async_req_3_1(exch, DEV_IFACE_ID(USBDIAG_DEV_IFACE), IPC_M_USBDIAG_BURST_BULK_IN, cycles, size, &duration_);
+
+	if (rc == EOK && duration)
+		*duration = duration_;
+
+	return rc;
+}
+
+int usbdiag_burst_bulk_out(async_exch_t *exch, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	if (!exch)
+		return EBADMEM;
+
+	sysarg_t duration_;
+	const int rc = async_req_3_1(exch, DEV_IFACE_ID(USBDIAG_DEV_IFACE), IPC_M_USBDIAG_BURST_BULK_OUT, cycles, size, &duration_);
+
+	if (rc == EOK && duration)
+		*duration = duration_;
+
+	return rc;
+}
+
+int usbdiag_burst_isoch_in(async_exch_t *exch, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	if (!exch)
+		return EBADMEM;
+
+	sysarg_t duration_;
+	const int rc = async_req_3_1(exch, DEV_IFACE_ID(USBDIAG_DEV_IFACE), IPC_M_USBDIAG_BURST_ISOCH_IN, cycles, size, &duration_);
+
+	if (rc == EOK && duration)
+		*duration = duration_;
+
+	return rc;
+}
+
+int usbdiag_burst_isoch_out(async_exch_t *exch, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	if (!exch)
+		return EBADMEM;
+
+	sysarg_t duration_;
+	const int rc = async_req_3_1(exch, DEV_IFACE_ID(USBDIAG_DEV_IFACE), IPC_M_USBDIAG_BURST_ISOCH_OUT, cycles, size, &duration_);
+
+	if (rc == EOK && duration)
+		*duration = duration_;
+
+	return rc;
+}
+
+int usbdiag_data_intr_in(async_exch_t *exch, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	if (!exch)
+		return EBADMEM;
+
+	sysarg_t duration_;
+	const int rc = async_req_3_1(exch, DEV_IFACE_ID(USBDIAG_DEV_IFACE), IPC_M_USBDIAG_DATA_INTR_IN, cycles, size, &duration_);
+
+	if (rc == EOK && duration)
+		*duration = duration_;
+
+	return rc;
+}
+
+int usbdiag_data_intr_out(async_exch_t *exch, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	if (!exch)
+		return EBADMEM;
+
+	sysarg_t duration_;
+	const int rc = async_req_3_1(exch, DEV_IFACE_ID(USBDIAG_DEV_IFACE), IPC_M_USBDIAG_DATA_INTR_OUT, cycles, size, &duration_);
+
+	if (rc == EOK && duration)
+		*duration = duration_;
+
+	return rc;
+}
+
+int usbdiag_data_bulk_in(async_exch_t *exch, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	if (!exch)
+		return EBADMEM;
+
+	sysarg_t duration_;
+	const int rc = async_req_3_1(exch, DEV_IFACE_ID(USBDIAG_DEV_IFACE), IPC_M_USBDIAG_DATA_BULK_IN, cycles, size, &duration_);
+
+	if (rc == EOK && duration)
+		*duration = duration_;
+
+	return rc;
+}
+
+int usbdiag_data_bulk_out(async_exch_t *exch, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	if (!exch)
+		return EBADMEM;
+
+	sysarg_t duration_;
+	const int rc = async_req_3_1(exch, DEV_IFACE_ID(USBDIAG_DEV_IFACE), IPC_M_USBDIAG_DATA_BULK_OUT, cycles, size, &duration_);
+
+	if (rc == EOK && duration)
+		*duration = duration_;
+
+	return rc;
+}
+
+int usbdiag_data_isoch_in(async_exch_t *exch, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	if (!exch)
+		return EBADMEM;
+
+	sysarg_t duration_;
+	const int rc = async_req_3_1(exch, DEV_IFACE_ID(USBDIAG_DEV_IFACE), IPC_M_USBDIAG_DATA_ISOCH_IN, cycles, size, &duration_);
+
+	if (rc == EOK && duration)
+		*duration = duration_;
+
+	return rc;
+}
+
+int usbdiag_data_isoch_out(async_exch_t *exch, int cycles, size_t size, usbdiag_dur_t *duration)
+{
+	if (!exch)
+		return EBADMEM;
+
+	sysarg_t duration_;
+	const int rc = async_req_3_1(exch, DEV_IFACE_ID(USBDIAG_DEV_IFACE), IPC_M_USBDIAG_DATA_ISOCH_OUT, cycles, size, &duration_);
+
+	if (rc == EOK && duration)
+		*duration = duration_;
+
+	return rc;
+}
+
+static void remote_usbdiag_burst_intr_in(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_usbdiag_burst_intr_out(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_usbdiag_burst_bulk_in(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_usbdiag_burst_bulk_out(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_usbdiag_burst_isoch_in(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_usbdiag_burst_isoch_out(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+
+static void remote_usbdiag_data_intr_in(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_usbdiag_data_intr_out(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_usbdiag_data_bulk_in(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_usbdiag_data_bulk_out(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_usbdiag_data_isoch_in(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_usbdiag_data_isoch_out(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+
+/** Remote USB diagnostic interface operations. */
+static const remote_iface_func_ptr_t remote_usbdiag_iface_ops [] = {
+	[IPC_M_USBDIAG_BURST_INTR_IN] = remote_usbdiag_burst_intr_in,
+	[IPC_M_USBDIAG_BURST_INTR_OUT] = remote_usbdiag_burst_intr_out,
+	[IPC_M_USBDIAG_BURST_BULK_IN] = remote_usbdiag_burst_bulk_in,
+	[IPC_M_USBDIAG_BURST_BULK_OUT] = remote_usbdiag_burst_bulk_out,
+	[IPC_M_USBDIAG_BURST_ISOCH_IN] = remote_usbdiag_burst_isoch_in,
+	[IPC_M_USBDIAG_BURST_ISOCH_OUT] = remote_usbdiag_burst_isoch_out,
+	[IPC_M_USBDIAG_DATA_INTR_IN] = remote_usbdiag_data_intr_in,
+	[IPC_M_USBDIAG_DATA_INTR_OUT] = remote_usbdiag_data_intr_out,
+	[IPC_M_USBDIAG_DATA_BULK_IN] = remote_usbdiag_data_bulk_in,
+	[IPC_M_USBDIAG_DATA_BULK_OUT] = remote_usbdiag_data_bulk_out,
+	[IPC_M_USBDIAG_DATA_ISOCH_IN] = remote_usbdiag_data_isoch_in,
+	[IPC_M_USBDIAG_DATA_ISOCH_OUT] = remote_usbdiag_data_isoch_out
+};
+
+/** Remote USB diagnostic interface structure. */
+const remote_iface_t remote_usbdiag_iface = {
+	.method_count = ARRAY_SIZE(remote_usbdiag_iface_ops),
+	.methods = remote_usbdiag_iface_ops,
+};
+
+void remote_usbdiag_burst_intr_in(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
+{
+	const usbdiag_iface_t *diag_iface = (usbdiag_iface_t *) iface;
+
+	if (diag_iface->burst_bulk_in == NULL) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	int cycles = DEV_IPC_GET_ARG1(*call);
+	size_t size = DEV_IPC_GET_ARG2(*call);
+	usbdiag_dur_t duration;
+	const int ret = diag_iface->burst_intr_in(fun, cycles, size, &duration);
+
+	if (ret != EOK) {
+		async_answer_0(callid, ret);
+	} else {
+		async_answer_1(callid, EOK, duration);
+	}
+}
+
+void remote_usbdiag_burst_intr_out(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
+{
+	const usbdiag_iface_t *diag_iface = (usbdiag_iface_t *) iface;
+
+	if (diag_iface->burst_bulk_out == NULL) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	int cycles = DEV_IPC_GET_ARG1(*call);
+	size_t size = DEV_IPC_GET_ARG2(*call);
+	usbdiag_dur_t duration;
+	const int ret = diag_iface->burst_intr_out(fun, cycles, size, &duration);
+
+	if (ret != EOK) {
+		async_answer_0(callid, ret);
+	} else {
+		async_answer_1(callid, EOK, duration);
+	}
+}
+
+void remote_usbdiag_burst_bulk_in(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
+{
+	const usbdiag_iface_t *diag_iface = (usbdiag_iface_t *) iface;
+
+	if (diag_iface->burst_bulk_in == NULL) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	int cycles = DEV_IPC_GET_ARG1(*call);
+	size_t size = DEV_IPC_GET_ARG2(*call);
+	usbdiag_dur_t duration;
+	const int ret = diag_iface->burst_bulk_in(fun, cycles, size, &duration);
+
+	if (ret != EOK) {
+		async_answer_0(callid, ret);
+	} else {
+		async_answer_1(callid, EOK, duration);
+	}
+}
+
+void remote_usbdiag_burst_bulk_out(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
+{
+	const usbdiag_iface_t *diag_iface = (usbdiag_iface_t *) iface;
+
+	if (diag_iface->burst_bulk_out == NULL) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	int cycles = DEV_IPC_GET_ARG1(*call);
+	size_t size = DEV_IPC_GET_ARG2(*call);
+	usbdiag_dur_t duration;
+	const int ret = diag_iface->burst_bulk_out(fun, cycles, size, &duration);
+
+	if (ret != EOK) {
+		async_answer_0(callid, ret);
+	} else {
+		async_answer_1(callid, EOK, duration);
+	}
+}
+
+void remote_usbdiag_burst_isoch_in(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
+{
+	const usbdiag_iface_t *diag_iface = (usbdiag_iface_t *) iface;
+
+	if (diag_iface->burst_isoch_in == NULL) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	int cycles = DEV_IPC_GET_ARG1(*call);
+	size_t size = DEV_IPC_GET_ARG2(*call);
+	usbdiag_dur_t duration;
+	const int ret = diag_iface->burst_isoch_in(fun, cycles, size, &duration);
+
+	if (ret != EOK) {
+		async_answer_0(callid, ret);
+	} else {
+		async_answer_1(callid, EOK, duration);
+	}
+}
+
+void remote_usbdiag_burst_isoch_out(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
+{
+	const usbdiag_iface_t *diag_iface = (usbdiag_iface_t *) iface;
+
+	if (diag_iface->burst_isoch_out == NULL) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	int cycles = DEV_IPC_GET_ARG1(*call);
+	size_t size = DEV_IPC_GET_ARG2(*call);
+	usbdiag_dur_t duration;
+	const int ret = diag_iface->burst_isoch_out(fun, cycles, size, &duration);
+
+	if (ret != EOK) {
+		async_answer_0(callid, ret);
+	} else {
+		async_answer_1(callid, EOK, duration);
+	}
+}
+
+void remote_usbdiag_data_intr_in(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
+{
+	const usbdiag_iface_t *diag_iface = (usbdiag_iface_t *) iface;
+
+	if (diag_iface->data_bulk_in == NULL) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	int cycles = DEV_IPC_GET_ARG1(*call);
+	size_t size = DEV_IPC_GET_ARG2(*call);
+	usbdiag_dur_t duration;
+	const int ret = diag_iface->data_intr_in(fun, cycles, size, &duration);
+
+	if (ret != EOK) {
+		async_answer_0(callid, ret);
+	} else {
+		async_answer_1(callid, EOK, duration);
+	}
+}
+
+void remote_usbdiag_data_intr_out(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
+{
+	const usbdiag_iface_t *diag_iface = (usbdiag_iface_t *) iface;
+
+	if (diag_iface->data_bulk_out == NULL) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	int cycles = DEV_IPC_GET_ARG1(*call);
+	size_t size = DEV_IPC_GET_ARG2(*call);
+	usbdiag_dur_t duration;
+	const int ret = diag_iface->data_intr_out(fun, cycles, size, &duration);
+
+	if (ret != EOK) {
+		async_answer_0(callid, ret);
+	} else {
+		async_answer_1(callid, EOK, duration);
+	}
+}
+
+void remote_usbdiag_data_bulk_in(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
+{
+	const usbdiag_iface_t *diag_iface = (usbdiag_iface_t *) iface;
+
+	if (diag_iface->data_bulk_in == NULL) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	int cycles = DEV_IPC_GET_ARG1(*call);
+	size_t size = DEV_IPC_GET_ARG2(*call);
+	usbdiag_dur_t duration;
+	const int ret = diag_iface->data_bulk_in(fun, cycles, size, &duration);
+
+	if (ret != EOK) {
+		async_answer_0(callid, ret);
+	} else {
+		async_answer_1(callid, EOK, duration);
+	}
+}
+
+void remote_usbdiag_data_bulk_out(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
+{
+	const usbdiag_iface_t *diag_iface = (usbdiag_iface_t *) iface;
+
+	if (diag_iface->data_bulk_out == NULL) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	int cycles = DEV_IPC_GET_ARG1(*call);
+	size_t size = DEV_IPC_GET_ARG2(*call);
+	usbdiag_dur_t duration;
+	const int ret = diag_iface->data_bulk_out(fun, cycles, size, &duration);
+
+	if (ret != EOK) {
+		async_answer_0(callid, ret);
+	} else {
+		async_answer_1(callid, EOK, duration);
+	}
+}
+
+void remote_usbdiag_data_isoch_in(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
+{
+	const usbdiag_iface_t *diag_iface = (usbdiag_iface_t *) iface;
+
+	if (diag_iface->data_isoch_in == NULL) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	int cycles = DEV_IPC_GET_ARG1(*call);
+	size_t size = DEV_IPC_GET_ARG2(*call);
+	usbdiag_dur_t duration;
+	const int ret = diag_iface->data_isoch_in(fun, cycles, size, &duration);
+
+	if (ret != EOK) {
+		async_answer_0(callid, ret);
+	} else {
+		async_answer_1(callid, EOK, duration);
+	}
+}
+
+void remote_usbdiag_data_isoch_out(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
+{
+	const usbdiag_iface_t *diag_iface = (usbdiag_iface_t *) iface;
+
+	if (diag_iface->data_isoch_out == NULL) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	int cycles = DEV_IPC_GET_ARG1(*call);
+	size_t size = DEV_IPC_GET_ARG2(*call);
+	usbdiag_dur_t duration;
+	const int ret = diag_iface->data_isoch_out(fun, cycles, size, &duration);
+
+	if (ret != EOK) {
+		async_answer_0(callid, ret);
+	} else {
+		async_answer_1(callid, EOK, duration);
+	}
+}
+
+/**
+ * @}
+ */
Index: uspace/lib/drv/generic/remote_usbhc.c
===================================================================
--- uspace/lib/drv/generic/remote_usbhc.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/drv/generic/remote_usbhc.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -1,5 +1,6 @@
 /*
- * Copyright (c) 2010-2011 Vojtech Horky
+ * Copyright (c) 2010 Vojtech Horky
  * Copyright (c) 2011 Jan Vesely
+ * Copyright (c) 2017 Ondrej Hlavaty
  * All rights reserved.
  *
@@ -35,68 +36,148 @@
 
 #include <async.h>
+#include <macros.h>
 #include <errno.h>
-#include <assert.h>
-#include <macros.h>
+#include <devman.h>
 
 #include "usbhc_iface.h"
 #include "ddf/driver.h"
 
-#define USB_MAX_PAYLOAD_SIZE 1020
-
-/** IPC methods for communication with HC through DDF interface.
- *
- * Notes for async methods:
- *
- * Methods for sending data to device (OUT transactions)
- * - e.g. IPC_M_USBHC_INTERRUPT_OUT -
- * always use the same semantics:
- * - first, IPC call with given method is made
- *   - argument #1 is target address
- *   - argument #2 is target endpoint
- *   - argument #3 is max packet size of the endpoint
- * - this call is immediately followed by IPC data write (from caller)
- * - the initial call (and the whole transaction) is answer after the
- *   transaction is scheduled by the HC and acknowledged by the device
- *   or immediately after error is detected
- * - the answer carries only the error code
- *
- * Methods for retrieving data from device (IN transactions)
- * - e.g. IPC_M_USBHC_INTERRUPT_IN -
- * also use the same semantics:
- * - first, IPC call with given method is made
- *   - argument #1 is target address
- *   - argument #2 is target endpoint
- * - this call is immediately followed by IPC data read (async version)
- * - the call is not answered until the device returns some data (or until
- *   error occurs)
- *
- * Some special methods (NO-DATA transactions) do not send any data. These
- * might behave as both OUT or IN transactions because communication parts
- * where actual buffers are exchanged are omitted.
- **
- * For all these methods, wrap functions exists. Important rule: functions
- * for IN transactions have (as parameters) buffers where retrieved data
- * will be stored. These buffers must be already allocated and shall not be
- * touch until the transaction is completed
- * (e.g. not before calling usb_wait_for() with appropriate handle).
- * OUT transactions buffers can be freed immediately after call is dispatched
- * (i.e. after return from wrapping function).
- *
- */
+
 typedef enum {
-	/** Get data from device.
-	 * See explanation at usb_iface_funcs_t (IN transaction).
-	 */
-	IPC_M_USBHC_READ,
-
-	/** Send data to device.
-	 * See explanation at usb_iface_funcs_t (OUT transaction).
-	 */
-	IPC_M_USBHC_WRITE,
+	IPC_M_USB_DEFAULT_ADDRESS_RESERVATION,
+	IPC_M_USB_DEVICE_ENUMERATE,
+	IPC_M_USB_DEVICE_REMOVE,
+	IPC_M_USB_REGISTER_ENDPOINT,
+	IPC_M_USB_UNREGISTER_ENDPOINT,
+	IPC_M_USB_READ,
+	IPC_M_USB_WRITE,
 } usbhc_iface_funcs_t;
 
-errno_t usbhc_read(async_exch_t *exch, usb_address_t address,
-    usb_endpoint_t endpoint, uint64_t setup, void *data, size_t size,
-    size_t *rec_size)
+/** Reserve default USB address.
+ * @param[in] exch IPC communication exchange
+ * @return Error code.
+ */
+int usbhc_reserve_default_address(async_exch_t *exch)
+{
+	if (!exch)
+		return EBADMEM;
+	return async_req_2_0(exch, DEV_IFACE_ID(USBHC_DEV_IFACE), IPC_M_USB_DEFAULT_ADDRESS_RESERVATION, true);
+}
+
+/** Release default USB address.
+ *
+ * @param[in] exch IPC communication exchange
+ *
+ * @return Error code.
+ */
+int usbhc_release_default_address(async_exch_t *exch)
+{
+	if (!exch)
+		return EBADMEM;
+	return async_req_2_0(exch, DEV_IFACE_ID(USBHC_DEV_IFACE), IPC_M_USB_DEFAULT_ADDRESS_RESERVATION, false);
+}
+
+/**
+ * Trigger USB device enumeration
+ *
+ * @param[in] exch IPC communication exchange
+ * @param[in] port Port number at which the device is attached
+ * @param[in] speed Communication speed of the newly attached device
+ *
+ * @return Error code.
+ */
+int usbhc_device_enumerate(async_exch_t *exch, unsigned port, usb_speed_t speed)
+{
+	if (!exch)
+		return EBADMEM;
+	const int ret = async_req_3_0(exch, DEV_IFACE_ID(USBHC_DEV_IFACE),
+	    IPC_M_USB_DEVICE_ENUMERATE, port, speed);
+	return ret;
+}
+
+/** Trigger USB device enumeration
+ *
+ * @param[in] exch   IPC communication exchange
+ * @param[in] handle Identifier of the device
+ *
+ * @return Error code.
+ *
+ */
+errno_t usbhc_device_remove(async_exch_t *exch, unsigned port)
+{
+	if (!exch)
+		return EBADMEM;
+	return async_req_2_0(exch, DEV_IFACE_ID(USBHC_DEV_IFACE),
+	    IPC_M_USB_DEVICE_REMOVE, port);
+}
+
+errno_t usbhc_register_endpoint(async_exch_t *exch, usb_pipe_desc_t *pipe_desc,
+    const usb_endpoint_descriptors_t *desc)
+{
+	if (!exch)
+		return EBADMEM;
+
+	if (!desc)
+		return EINVAL;
+
+	aid_t opening_request = async_send_1(exch,
+	    DEV_IFACE_ID(USBHC_DEV_IFACE), IPC_M_USB_REGISTER_ENDPOINT, NULL);
+
+	if (opening_request == 0) {
+		return ENOMEM;
+	}
+
+	int ret = async_data_write_start(exch, desc, sizeof(*desc));
+	if (ret != EOK) {
+		async_forget(opening_request);
+		return ret;
+	}
+
+	/* Wait for the answer. */
+	int opening_request_rc;
+	async_wait_for(opening_request, &opening_request_rc);
+
+	if (opening_request_rc)
+		return (int) opening_request_rc;
+
+	usb_pipe_desc_t dest;
+	ret = async_data_read_start(exch, &dest, sizeof(dest));
+	if (ret != EOK) {
+		return ret;
+	}
+
+	if (pipe_desc)
+		*pipe_desc = dest;
+
+	return EOK;
+}
+
+errno_t usbhc_unregister_endpoint(async_exch_t *exch, const usb_pipe_desc_t *pipe_desc)
+{
+	if (!exch)
+		return EBADMEM;
+
+	aid_t opening_request = async_send_1(exch,
+		DEV_IFACE_ID(USBHC_DEV_IFACE), IPC_M_USB_UNREGISTER_ENDPOINT, NULL);
+
+	if (opening_request == 0) {
+		return ENOMEM;
+	}
+
+	const int ret = async_data_write_start(exch, pipe_desc, sizeof(*pipe_desc));
+	if (ret != EOK) {
+		async_forget(opening_request);
+		return ret;
+	}
+
+	/* Wait for the answer. */
+	int opening_request_rc;
+	async_wait_for(opening_request, &opening_request_rc);
+
+	return (int) opening_request_rc;
+}
+
+errno_t usbhc_read(async_exch_t *exch, usb_endpoint_t endpoint, uint64_t setup,
+    void *data, size_t size, size_t *rec_size)
 {
 	if (!exch)
@@ -106,11 +187,7 @@
 		return EOK;
 
-	const usb_target_t target =
-	    {{ .address = address, .endpoint = endpoint }};
-
 	/* Make call identifying target USB device and type of transfer. */
 	aid_t opening_request = async_send_4(exch,
-	    DEV_IFACE_ID(USBHC_DEV_IFACE),
-	    IPC_M_USBHC_READ, target.packed,
+	    DEV_IFACE_ID(USBHC_DEV_IFACE), IPC_M_USB_READ, endpoint,
 	    (setup & UINT32_MAX), (setup >> 32), NULL);
 
@@ -152,6 +229,6 @@
 }
 
-errno_t usbhc_write(async_exch_t *exch, usb_address_t address,
-    usb_endpoint_t endpoint, uint64_t setup, const void *data, size_t size)
+errno_t usbhc_write(async_exch_t *exch, usb_endpoint_t endpoint, uint64_t setup,
+    const void *data, size_t size)
 {
 	if (!exch)
@@ -161,9 +238,6 @@
 		return EOK;
 
-	const usb_target_t target =
-	    {{ .address = address, .endpoint = endpoint }};
-
-	aid_t opening_request = async_send_5(exch, DEV_IFACE_ID(USBHC_DEV_IFACE),
-	    IPC_M_USBHC_WRITE, target.packed, size,
+	aid_t opening_request = async_send_5(exch,
+	    DEV_IFACE_ID(USBHC_DEV_IFACE), IPC_M_USB_WRITE, endpoint, size,
 	    (setup & UINT32_MAX), (setup >> 32), NULL);
 
@@ -188,18 +262,28 @@
 }
 
-static void remote_usbhc_read(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
-static void remote_usbhc_write(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
-
-/** Remote USB host controller interface operations. */
-static const remote_iface_func_ptr_t remote_usbhc_iface_ops[] = {
-	[IPC_M_USBHC_READ] = remote_usbhc_read,
-	[IPC_M_USBHC_WRITE] = remote_usbhc_write,
+static void remote_usbhc_default_address_reservation(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_usbhc_device_enumerate(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_usbhc_device_remove(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_usbhc_register_endpoint(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_usbhc_unregister_endpoint(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_usbhc_read(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call);
+static void remote_usbhc_write(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call);
+
+/** Remote USB interface operations. */
+static const remote_iface_func_ptr_t remote_usbhc_iface_ops [] = {
+	[IPC_M_USB_DEFAULT_ADDRESS_RESERVATION] = remote_usbhc_default_address_reservation,
+	[IPC_M_USB_DEVICE_ENUMERATE] = remote_usbhc_device_enumerate,
+	[IPC_M_USB_DEVICE_REMOVE] = remote_usbhc_device_remove,
+	[IPC_M_USB_REGISTER_ENDPOINT] = remote_usbhc_register_endpoint,
+	[IPC_M_USB_UNREGISTER_ENDPOINT] = remote_usbhc_unregister_endpoint,
+	[IPC_M_USB_READ] = remote_usbhc_read,
+	[IPC_M_USB_WRITE] = remote_usbhc_write,
 };
 
-/** Remote USB host controller interface structure.
+/** Remote USB interface structure.
  */
 const remote_iface_t remote_usbhc_iface = {
 	.method_count = ARRAY_SIZE(remote_usbhc_iface_ops),
-	.methods = remote_usbhc_iface_ops
+	.methods = remote_usbhc_iface_ops,
 };
 
@@ -210,12 +294,126 @@
 } async_transaction_t;
 
+void remote_usbhc_default_address_reservation(ddf_fun_t *fun, void *iface,
+    ipc_callid_t callid, ipc_call_t *call)
+{
+	const usbhc_iface_t *usbhc_iface = (usbhc_iface_t *) iface;
+
+	if (usbhc_iface->default_address_reservation == NULL) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	const bool reserve = IPC_GET_ARG2(*call);
+	const int ret = usbhc_iface->default_address_reservation(fun, reserve);
+	async_answer_0(callid, ret);
+}
+
+
+static void remote_usbhc_device_enumerate(ddf_fun_t *fun, void *iface,
+    ipc_callid_t callid, ipc_call_t *call)
+{
+	const usbhc_iface_t *usbhc_iface = (usbhc_iface_t *) iface;
+
+	if (usbhc_iface->device_enumerate == NULL) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	const unsigned port = DEV_IPC_GET_ARG1(*call);
+	usb_speed_t speed = DEV_IPC_GET_ARG2(*call);
+	const int ret = usbhc_iface->device_enumerate(fun, port, speed);
+	async_answer_0(callid, ret);
+}
+
+static void remote_usbhc_device_remove(ddf_fun_t *fun, void *iface,
+    ipc_callid_t callid, ipc_call_t *call)
+{
+	const usbhc_iface_t *usbhc_iface = (usbhc_iface_t *) iface;
+
+	if (usbhc_iface->device_remove == NULL) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	const unsigned port = DEV_IPC_GET_ARG1(*call);
+	const int ret = usbhc_iface->device_remove(fun, port);
+	async_answer_0(callid, ret);
+}
+
+static void remote_usbhc_register_endpoint(ddf_fun_t *fun, void *iface,
+    ipc_callid_t callid, ipc_call_t *call)
+{
+	assert(fun);
+	assert(iface);
+	assert(call);
+
+	const usbhc_iface_t *usbhc_iface = iface;
+
+	if (!usbhc_iface->register_endpoint) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	usb_endpoint_descriptors_t ep_desc;
+	ipc_callid_t data_callid;
+	size_t len;
+
+	if (!async_data_write_receive(&data_callid, &len)
+	    || len != sizeof(ep_desc)) {
+		async_answer_0(callid, EINVAL);
+		return;
+	}
+	async_data_write_finalize(data_callid, &ep_desc, sizeof(ep_desc));
+
+	usb_pipe_desc_t pipe_desc;
+
+	const int rc = usbhc_iface->register_endpoint(fun, &pipe_desc, &ep_desc);
+	async_answer_0(callid, rc);
+
+	if (!async_data_read_receive(&data_callid, &len)
+	    || len != sizeof(pipe_desc)) {
+		return;
+	}
+	async_data_read_finalize(data_callid, &pipe_desc, sizeof(pipe_desc));
+}
+
+static void remote_usbhc_unregister_endpoint(ddf_fun_t *fun, void *iface,
+    ipc_callid_t callid, ipc_call_t *call)
+{
+	assert(fun);
+	assert(iface);
+	assert(call);
+
+	const usbhc_iface_t *usbhc_iface = iface;
+
+	if (!usbhc_iface->unregister_endpoint) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	usb_pipe_desc_t pipe_desc;
+	ipc_callid_t data_callid;
+	size_t len;
+
+	if (!async_data_write_receive(&data_callid, &len)
+	    || len != sizeof(pipe_desc)) {
+		async_answer_0(callid, EINVAL);
+		return;
+	}
+	async_data_write_finalize(data_callid, &pipe_desc, sizeof(pipe_desc));
+
+	const int rc = usbhc_iface->unregister_endpoint(fun, &pipe_desc);
+	async_answer_0(callid, rc);
+}
+
 static void async_transaction_destroy(async_transaction_t *trans)
 {
-	if (trans == NULL)
-		return;
-	
-	if (trans->buffer != NULL)
+	if (trans == NULL) {
+		return;
+	}
+	if (trans->buffer != NULL) {
 		free(trans->buffer);
-	
+	}
+
 	free(trans);
 }
@@ -235,34 +433,31 @@
 }
 
-static void callback_out(errno_t outcome, void *arg)
+static errno_t callback_out(void *arg, int error, size_t transferred_size)
 {
 	async_transaction_t *trans = arg;
 
-	async_answer_0(trans->caller, outcome);
+	const errno_t err = async_answer_0(trans->caller, error);
 
 	async_transaction_destroy(trans);
-}
-
-static void callback_in(errno_t outcome, size_t actual_size, void *arg)
-{
-	async_transaction_t *trans = (async_transaction_t *)arg;
-
-	if (outcome != EOK) {
-		async_answer_0(trans->caller, outcome);
-		if (trans->data_caller) {
+
+	return err;
+}
+
+static errno_t callback_in(void *arg, int error, size_t transferred_size)
+{
+	async_transaction_t *trans = arg;
+
+	if (trans->data_caller) {
+		if (error == EOK) {
+			error = async_data_read_finalize(trans->data_caller,
+			    trans->buffer, transferred_size);
+		} else {
 			async_answer_0(trans->data_caller, EINTR);
 		}
-		async_transaction_destroy(trans);
-		return;
-	}
-
-	if (trans->data_caller) {
-		async_data_read_finalize(trans->data_caller,
-		    trans->buffer, actual_size);
-	}
-
-	async_answer_0(trans->caller, EOK);
-
+	}
+
+	const errno_t err = async_answer_0(trans->caller, error);
 	async_transaction_destroy(trans);
+	return err;
 }
 
@@ -274,12 +469,12 @@
 	assert(call);
 
-	const usbhc_iface_t *hc_iface = iface;
-
-	if (!hc_iface->read) {
-		async_answer_0(callid, ENOTSUP);
-		return;
-	}
-
-	const usb_target_t target = { .packed = DEV_IPC_GET_ARG1(*call) };
+	const usbhc_iface_t *usbhc_iface = iface;
+
+	if (!usbhc_iface->read) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	const usb_endpoint_t ep = DEV_IPC_GET_ARG1(*call);
 	const uint64_t setup =
 	    ((uint64_t)DEV_IPC_GET_ARG2(*call)) |
@@ -295,4 +490,5 @@
 	if (!async_data_read_receive(&trans->data_caller, &size)) {
 		async_answer_0(callid, EPARTY);
+		async_transaction_destroy(trans);
 		return;
 	}
@@ -306,5 +502,10 @@
 	}
 
-	const errno_t rc = hc_iface->read(
+	const usb_target_t target = {{
+		/* .address is initialized by read itself */
+		.endpoint = ep,
+	}};
+
+	const errno_t rc = usbhc_iface->read(
 	    fun, target, setup, trans->buffer, size, callback_in, trans);
 
@@ -323,12 +524,12 @@
 	assert(call);
 
-	const usbhc_iface_t *hc_iface = iface;
-
-	if (!hc_iface->write) {
-		async_answer_0(callid, ENOTSUP);
-		return;
-	}
-
-	const usb_target_t target = { .packed = DEV_IPC_GET_ARG1(*call) };
+	const usbhc_iface_t *usbhc_iface = iface;
+
+	if (!usbhc_iface->write) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	const usb_endpoint_t ep = DEV_IPC_GET_ARG1(*call);
 	const size_t data_buffer_len = DEV_IPC_GET_ARG2(*call);
 	const uint64_t setup =
@@ -345,6 +546,5 @@
 	if (data_buffer_len > 0) {
 		const errno_t rc = async_data_write_accept(&trans->buffer, false,
-		    1, USB_MAX_PAYLOAD_SIZE,
-		    0, &size);
+		    1, data_buffer_len, 0, &size);
 
 		if (rc != EOK) {
@@ -355,5 +555,12 @@
 	}
 
-	const errno_t rc = hc_iface->write(
+	const usb_target_t target = {{
+		/* .address is initialized by write itself */
+		.endpoint = ep,
+		/* streams are not given by the API call yet */
+		.stream = 0,
+	}};
+
+	const errno_t rc = usbhc_iface->write(
 	    fun, target, setup, trans->buffer, size, callback_out, trans);
 
Index: uspace/lib/drv/include/usb_iface.h
===================================================================
--- uspace/lib/drv/include/usb_iface.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/drv/include/usb_iface.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -32,5 +32,5 @@
  */
 /** @file
- * @brief USB interface definition.
+ * @brief USB device interface definition.
  */
 
@@ -40,54 +40,15 @@
 #include "ddf/driver.h"
 #include <async.h>
+#include <usbhc_iface.h>
 
 typedef async_sess_t usb_dev_session_t;
 
-/** USB speeds. */
-typedef enum {
-	/** USB 1.1 low speed (1.5Mbits/s). */
-	USB_SPEED_LOW,
-	/** USB 1.1 full speed (12Mbits/s). */
-	USB_SPEED_FULL,
-	/** USB 2.0 high speed (480Mbits/s). */
-	USB_SPEED_HIGH,
-	/** Psuedo-speed serving as a boundary. */
-	USB_SPEED_MAX
-} usb_speed_t;
-
-/** USB endpoint number type.
- * Negative values could be used to indicate error.
- */
-typedef int16_t usb_endpoint_t;
-
-/** USB address type.
- * Negative values could be used to indicate error.
- */
-typedef int16_t usb_address_t;
-
-/** USB transfer type. */
-typedef enum {
-	USB_TRANSFER_CONTROL = 0,
-	USB_TRANSFER_ISOCHRONOUS = 1,
-	USB_TRANSFER_BULK = 2,
-	USB_TRANSFER_INTERRUPT = 3
-} usb_transfer_type_t;
-
-/** USB data transfer direction. */
-typedef enum {
-	USB_DIRECTION_IN,
-	USB_DIRECTION_OUT,
-	USB_DIRECTION_BOTH
-} usb_direction_t;
-
-/** USB complete address type.
- * Pair address + endpoint is identification of transaction recipient.
- */
-typedef union {
-	struct {
-		usb_address_t address;
-		usb_endpoint_t endpoint;
-	} __attribute__((packed));
-	uint32_t packed;
-} usb_target_t;
+typedef struct {
+	usb_address_t address;		/** Current USB address */
+	uint8_t depth;			/** Depth in the hub hiearchy */
+	usb_speed_t speed;		/** Speed of the device */
+	devman_handle_t handle;		/** Handle to DDF function of the HC driver */
+	int iface;			/** Interface set by multi interface driver, -1 if none */
+} usb_device_desc_t;
 
 extern usb_dev_session_t *usb_dev_connect(devman_handle_t);
@@ -95,48 +56,9 @@
 extern void usb_dev_disconnect(usb_dev_session_t *);
 
-extern errno_t usb_get_my_interface(async_exch_t *, int *);
-extern errno_t usb_get_my_device_handle(async_exch_t *, devman_handle_t *);
-
-extern errno_t usb_reserve_default_address(async_exch_t *, usb_speed_t);
-extern errno_t usb_release_default_address(async_exch_t *);
-
-extern errno_t usb_device_enumerate(async_exch_t *, unsigned port);
-extern errno_t usb_device_remove(async_exch_t *, unsigned port);
-
-extern errno_t usb_register_endpoint(async_exch_t *, usb_endpoint_t,
-    usb_transfer_type_t, usb_direction_t, size_t, unsigned, unsigned);
-extern errno_t usb_unregister_endpoint(async_exch_t *, usb_endpoint_t,
-    usb_direction_t);
-extern errno_t usb_read(async_exch_t *, usb_endpoint_t, uint64_t, void *, size_t,
-    size_t *);
-extern errno_t usb_write(async_exch_t *, usb_endpoint_t, uint64_t, const void *,
-    size_t);
-
-/** Callback for outgoing transfer. */
-typedef void (*usb_iface_transfer_out_callback_t)(errno_t, void *);
-
-/** Callback for incoming transfer. */
-typedef void (*usb_iface_transfer_in_callback_t)(errno_t, size_t, void *);
+extern errno_t usb_get_my_description(async_exch_t *, usb_device_desc_t *);
 
 /** USB device communication interface. */
 typedef struct {
-	errno_t (*get_my_interface)(ddf_fun_t *, int *);
-	errno_t (*get_my_device_handle)(ddf_fun_t *, devman_handle_t *);
-
-	errno_t (*reserve_default_address)(ddf_fun_t *, usb_speed_t);
-	errno_t (*release_default_address)(ddf_fun_t *);
-
-	errno_t (*device_enumerate)(ddf_fun_t *, unsigned);
-	errno_t (*device_remove)(ddf_fun_t *, unsigned);
-
-	errno_t (*register_endpoint)(ddf_fun_t *, usb_endpoint_t,
-	    usb_transfer_type_t, usb_direction_t, size_t, unsigned, unsigned);
-	errno_t (*unregister_endpoint)(ddf_fun_t *, usb_endpoint_t,
-	    usb_direction_t);
-
-	errno_t (*read)(ddf_fun_t *, usb_endpoint_t, uint64_t, uint8_t *, size_t,
-	    usb_iface_transfer_in_callback_t, void *);
-	errno_t (*write)(ddf_fun_t *, usb_endpoint_t, uint64_t, const uint8_t *,
-	    size_t, usb_iface_transfer_out_callback_t, void *);
+	errno_t (*get_my_description)(ddf_fun_t *, usb_device_desc_t *);
 } usb_iface_t;
 
Index: uspace/lib/drv/include/usbdiag_iface.h
===================================================================
--- uspace/lib/drv/include/usbdiag_iface.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/lib/drv/include/usbdiag_iface.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2017 Petr Manek
+ * 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 libdrv
+* @addtogroup usb
+* @{
+*/
+/** @file
+* @brief USB diagnostic device interface definition.
+*/
+
+#ifndef LIBDRV_USBDIAG_IFACE_H_
+#define LIBDRV_USBDIAG_IFACE_H_
+
+#include <async.h>
+#include "ddf/driver.h"
+
+#define USBDIAG_CATEGORY "usbdiag"
+
+/** Milliseconds */
+typedef unsigned long usbdiag_dur_t;
+
+async_sess_t *usbdiag_connect(devman_handle_t);
+void usbdiag_disconnect(async_sess_t*);
+
+int usbdiag_burst_intr_in(async_exch_t*, int, size_t, usbdiag_dur_t*);
+int usbdiag_burst_intr_out(async_exch_t*, int, size_t, usbdiag_dur_t*);
+int usbdiag_burst_bulk_in(async_exch_t*, int, size_t, usbdiag_dur_t*);
+int usbdiag_burst_bulk_out(async_exch_t*, int, size_t, usbdiag_dur_t*);
+int usbdiag_burst_isoch_in(async_exch_t*, int, size_t, usbdiag_dur_t*);
+int usbdiag_burst_isoch_out(async_exch_t*, int, size_t, usbdiag_dur_t*);
+
+int usbdiag_data_intr_in(async_exch_t*, int, size_t, usbdiag_dur_t*);
+int usbdiag_data_intr_out(async_exch_t*, int, size_t, usbdiag_dur_t*);
+int usbdiag_data_bulk_in(async_exch_t*, int, size_t, usbdiag_dur_t*);
+int usbdiag_data_bulk_out(async_exch_t*, int, size_t, usbdiag_dur_t*);
+int usbdiag_data_isoch_in(async_exch_t*, int, size_t, usbdiag_dur_t*);
+int usbdiag_data_isoch_out(async_exch_t*, int, size_t, usbdiag_dur_t*);
+
+/** USB diagnostic device communication interface. */
+typedef struct {
+	int (*burst_intr_in)(ddf_fun_t*, int, size_t, usbdiag_dur_t*);
+	int (*burst_intr_out)(ddf_fun_t*, int, size_t, usbdiag_dur_t*);
+	int (*burst_bulk_in)(ddf_fun_t*, int, size_t, usbdiag_dur_t*);
+	int (*burst_bulk_out)(ddf_fun_t*, int, size_t, usbdiag_dur_t*);
+	int (*burst_isoch_in)(ddf_fun_t*, int, size_t, usbdiag_dur_t*);
+	int (*burst_isoch_out)(ddf_fun_t*, int, size_t, usbdiag_dur_t*);
+	int (*data_intr_in)(ddf_fun_t*, int, size_t, usbdiag_dur_t*);
+	int (*data_intr_out)(ddf_fun_t*, int, size_t, usbdiag_dur_t*);
+	int (*data_bulk_in)(ddf_fun_t*, int, size_t, usbdiag_dur_t*);
+	int (*data_bulk_out)(ddf_fun_t*, int, size_t, usbdiag_dur_t*);
+	int (*data_isoch_in)(ddf_fun_t*, int, size_t, usbdiag_dur_t*);
+	int (*data_isoch_out)(ddf_fun_t*, int, size_t, usbdiag_dur_t*);
+} usbdiag_iface_t;
+
+#endif
+/**
+ * @}
+ */
Index: uspace/lib/drv/include/usbhc_iface.h
===================================================================
--- uspace/lib/drv/include/usbhc_iface.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/drv/include/usbhc_iface.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -1,5 +1,5 @@
 /*
  * Copyright (c) 2010 Vojtech Horky
- * Copyright (c) 2011 Jan Vesely
+ * Copyright (c) 2017 Ondrej Hlavaty
  * All rights reserved.
  *
@@ -32,7 +32,7 @@
  * @{
  */
-
 /** @file
- * @brief USB host controller interface definition.
+ * @brief USB host controler interface definition. This is the interface of
+ * USB host controller function, which can be used by usb device drivers.
  */
 
@@ -41,26 +41,136 @@
 
 #include "ddf/driver.h"
-#include <usb_iface.h>
-#include <stdbool.h>
+#include <async.h>
 
-extern errno_t usbhc_read(async_exch_t *, usb_address_t, usb_endpoint_t,
-    uint64_t, void *, size_t, size_t *);
-extern errno_t usbhc_write(async_exch_t *, usb_address_t, usb_endpoint_t,
-    uint64_t, const void *, size_t);
+/** USB speeds. */
+typedef enum {
+	/** USB 1.1 low speed (1.5Mbits/s). */
+	USB_SPEED_LOW,
+	/** USB 1.1 full speed (12Mbits/s). */
+	USB_SPEED_FULL,
+	/** USB 2.0 high speed (480Mbits/s). */
+	USB_SPEED_HIGH,
+	/** USB 3.0 super speed (5Gbits/s). */
+	USB_SPEED_SUPER,
+	/** Psuedo-speed serving as a boundary. */
+	USB_SPEED_MAX
+} usb_speed_t;
 
-/** Callback for outgoing transfer. */
-typedef void (*usbhc_iface_transfer_out_callback_t)(errno_t, void *);
+/** USB endpoint number type.
+ * Negative values could be used to indicate error.
+ */
+typedef uint16_t usb_endpoint_t;
 
-/** Callback for incoming transfer. */
-typedef void (*usbhc_iface_transfer_in_callback_t)(errno_t, size_t, void *);
+/** USB address type.
+ * Negative values could be used to indicate error.
+ */
+typedef uint16_t usb_address_t;
 
-/** USB host controller communication interface. */
+/**
+ * USB Stream ID type.
+ */
+typedef uint16_t usb_stream_t;
+
+/** USB transfer type. */
+typedef enum {
+	USB_TRANSFER_CONTROL = 0,
+	USB_TRANSFER_ISOCHRONOUS = 1,
+	USB_TRANSFER_BULK = 2,
+	USB_TRANSFER_INTERRUPT = 3,
+} usb_transfer_type_t;
+
+#define USB_TRANSFER_COUNT  (USB_TRANSFER_INTERRUPT + 1)
+
+/** USB data transfer direction. */
+typedef enum {
+	USB_DIRECTION_IN,
+	USB_DIRECTION_OUT,
+	USB_DIRECTION_BOTH,
+} usb_direction_t;
+
+#define USB_DIRECTION_COUNT  (USB_DIRECTION_BOTH + 1)
+
+/** USB complete address type.
+ * Pair address + endpoint is identification of transaction recipient.
+ */
+typedef union {
+	struct {
+		usb_address_t address;
+		usb_endpoint_t endpoint;
+		usb_stream_t stream;
+	} __attribute__((packed));
+	uint64_t packed;
+} usb_target_t;
+
+typedef struct usb_pipe_desc {
+	/** Endpoint number. */
+	usb_endpoint_t endpoint_no;
+
+	/** Endpoint transfer type. */
+	usb_transfer_type_t transfer_type;
+
+	/** Endpoint direction. */
+	usb_direction_t direction;
+
+	/** Maximum size of one transfer */
+	size_t max_transfer_size;
+} usb_pipe_desc_t;
+
+/** This structure follows standard endpoint descriptor + superspeed companion
+ * descriptor, and exists to avoid dependency of libdrv on libusb. Keep the
+ * internal fields named exactly like their source (because we want to use the
+ * same macros to access them).
+ * Callers shall fill it with bare contents of respective descriptors (in usb endianity).
+ */
+typedef struct usb_endpoint_descriptors {
+	struct {
+		uint8_t endpoint_address;
+		uint8_t attributes;
+		uint16_t max_packet_size;
+		uint8_t poll_interval;
+	} endpoint;
+
+	/* Superspeed companion descriptor */
+	struct companion_desc_t {
+		uint8_t max_burst;
+		uint8_t attributes;
+		uint16_t bytes_per_interval;
+	} companion;
+} usb_endpoint_descriptors_t;
+
+extern errno_t usbhc_reserve_default_address(async_exch_t *);
+extern errno_t usbhc_release_default_address(async_exch_t *);
+
+extern errno_t usbhc_device_enumerate(async_exch_t *, unsigned, usb_speed_t);
+extern errno_t usbhc_device_remove(async_exch_t *, unsigned);
+
+extern errno_t usbhc_register_endpoint(async_exch_t *, usb_pipe_desc_t *, const usb_endpoint_descriptors_t *);
+extern errno_t usbhc_unregister_endpoint(async_exch_t *, const usb_pipe_desc_t *);
+
+extern errno_t usbhc_read(async_exch_t *, usb_endpoint_t, uint64_t, void *, size_t,
+    size_t *);
+extern errno_t usbhc_write(async_exch_t *, usb_endpoint_t, uint64_t, const void *,
+    size_t);
+
+/** Callback for outgoing transfer */
+typedef errno_t (*usbhc_iface_transfer_callback_t)(void *, int, size_t);
+
+/** USB device communication interface. */
 typedef struct {
-	errno_t (*read)(ddf_fun_t *, usb_target_t, uint64_t, uint8_t *, size_t,
-	    usbhc_iface_transfer_in_callback_t, void *);
-	errno_t (*write)(ddf_fun_t *, usb_target_t, uint64_t, const uint8_t *,
-	    size_t, usbhc_iface_transfer_out_callback_t, void *);
+	int (*default_address_reservation)(ddf_fun_t *, bool);
+
+	int (*device_enumerate)(ddf_fun_t *, unsigned, usb_speed_t);
+	int (*device_remove)(ddf_fun_t *, unsigned);
+
+	int (*register_endpoint)(ddf_fun_t *, usb_pipe_desc_t *, const usb_endpoint_descriptors_t *);
+	int (*unregister_endpoint)(ddf_fun_t *, const usb_pipe_desc_t *);
+
+	errno_t (*read)(ddf_fun_t *, usb_target_t,
+		uint64_t, char *, size_t,
+		usbhc_iface_transfer_callback_t, void *);
+	errno_t (*write)(ddf_fun_t *, usb_target_t,
+		uint64_t, const char *, size_t,
+		usbhc_iface_transfer_callback_t, void *);
 } usbhc_iface_t;
-
 
 #endif
Index: uspace/lib/pcm/src/format.c
===================================================================
--- uspace/lib/pcm/src/format.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/pcm/src/format.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -41,9 +41,4 @@
 
 #include "format.h"
-
-#define uint8_t_le2host(x) (x)
-#define host2uint8_t_le(x) (x)
-#define uint8_t_be2host(x) (x)
-#define host2uint8_t_be(x) (x)
 
 #define int8_t_le2host(x) (x)
Index: uspace/lib/usb/Makefile
===================================================================
--- uspace/lib/usb/Makefile	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usb/Makefile	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -36,4 +36,5 @@
 	src/debug.c \
 	src/dump.c \
+	src/port.c \
 	src/usb.c
 
Index: uspace/lib/usb/include/usb/classes/hub.h
===================================================================
--- uspace/lib/usb/include/usb/classes/hub.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usb/include/usb/classes/hub.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -48,21 +48,58 @@
 	USB_HUB_FEATURE_HUB_OVER_CURRENT = 1,
 	USB_HUB_FEATURE_PORT_CONNECTION = 0,
-	USB_HUB_FEATURE_PORT_ENABLE = 1,
-	USB_HUB_FEATURE_PORT_SUSPEND = 2,
+	USB2_HUB_FEATURE_PORT_ENABLE = 1,
+	USB2_HUB_FEATURE_PORT_SUSPEND = 2,
 	USB_HUB_FEATURE_PORT_OVER_CURRENT = 3,
 	USB_HUB_FEATURE_PORT_RESET = 4,
+	USB3_HUB_FEATURE_PORT_LINK_STATE = 5,
 	USB_HUB_FEATURE_PORT_POWER = 8,
-	USB_HUB_FEATURE_PORT_LOW_SPEED = 9,
-	USB_HUB_FEATURE_PORT_HIGH_SPEED = 10,
+	USB2_HUB_FEATURE_PORT_LOW_SPEED = 9,
 	USB_HUB_FEATURE_C_PORT_CONNECTION = 16,
-	USB_HUB_FEATURE_C_PORT_ENABLE = 17,
-	USB_HUB_FEATURE_C_PORT_SUSPEND = 18,
+	USB2_HUB_FEATURE_C_PORT_ENABLE = 17,
+	USB2_HUB_FEATURE_C_PORT_SUSPEND = 18,
 	USB_HUB_FEATURE_C_PORT_OVER_CURRENT = 19,
 	USB_HUB_FEATURE_C_PORT_RESET = 20,
-	USB_HUB_FEATURE_PORT_TEST = 21,
-	USB_HUB_FEATURE_PORT_INDICATOR = 22
+	USB2_HUB_FEATURE_PORT_TEST = 21,
+	USB2_HUB_FEATURE_PORT_INDICATOR = 22,
+	USB3_HUB_FEATURE_C_PORT_LINK_STATE = 25,
+	USB3_HUB_FEATURE_BH_PORT_RESET = 28,
+	USB3_HUB_FEATURE_C_BH_PORT_RESET = 29,
 	/* USB_HUB_FEATURE_ = , */
 } usb_hub_class_feature_t;
 
+/**
+ * Dword holding port status and changes flags.
+ *
+ * For more information refer to tables 11-15 and 11-16 in
+ * "Universal Serial Bus Specification Revision 1.1" pages 274 and 277
+ * (290 and 293 in pdf)
+ *
+ * Beware that definition of bits changed between USB 2 and 3,
+ * so some fields are prefixed with USB2 or USB3 instead.
+ */
+typedef uint32_t usb_port_status_t;
+
+#define USB_HUB_PORT_STATUS_BIT(bit)  (uint32_usb2host(1 << (bit)))
+#define USB_HUB_PORT_STATUS_CONNECTION		USB_HUB_PORT_STATUS_BIT(0)
+#define USB_HUB_PORT_STATUS_ENABLE		USB_HUB_PORT_STATUS_BIT(1)
+#define USB2_HUB_PORT_STATUS_SUSPEND		USB_HUB_PORT_STATUS_BIT(2)
+#define USB_HUB_PORT_STATUS_OC			USB_HUB_PORT_STATUS_BIT(3)
+#define USB_HUB_PORT_STATUS_RESET		USB_HUB_PORT_STATUS_BIT(4)
+
+#define USB2_HUB_PORT_STATUS_POWER		USB_HUB_PORT_STATUS_BIT(8)
+#define USB2_HUB_PORT_STATUS_LOW_SPEED		USB_HUB_PORT_STATUS_BIT(9)
+#define USB3_HUB_PORT_STATUS_POWER		USB_HUB_PORT_STATUS_BIT(9)
+#define USB2_HUB_PORT_STATUS_HIGH_SPEED		USB_HUB_PORT_STATUS_BIT(10)
+#define USB2_HUB_PORT_STATUS_TEST		USB_HUB_PORT_STATUS_BIT(11)
+#define USB2_HUB_PORT_STATUS_INDICATOR		USB_HUB_PORT_STATUS_BIT(12)
+
+#define USB_HUB_PORT_STATUS_C_CONNECTION	USB_HUB_PORT_STATUS_BIT(16)
+#define USB2_HUB_PORT_STATUS_C_ENABLE		USB_HUB_PORT_STATUS_BIT(17)
+#define USB2_HUB_PORT_STATUS_C_SUSPEND		USB_HUB_PORT_STATUS_BIT(18)
+#define USB_HUB_PORT_STATUS_C_OC		USB_HUB_PORT_STATUS_BIT(19)
+#define USB_HUB_PORT_STATUS_C_RESET		USB_HUB_PORT_STATUS_BIT(20)
+#define USB3_HUB_PORT_STATUS_C_BH_RESET		USB_HUB_PORT_STATUS_BIT(21)
+#define USB3_HUB_PORT_STATUS_C_LINK_STATE	USB_HUB_PORT_STATUS_BIT(22)
+#define USB3_HUB_PORT_STATUS_C_CONFIG_ERROR	USB_HUB_PORT_STATUS_BIT(23)
 
 /** Header of standard hub descriptor without the "variadic" part. */
@@ -71,5 +108,5 @@
 	uint8_t length;
 
-	/** Descriptor type (0x29). */
+	/** Descriptor type (0x29 or 0x2a for superspeed hub). */
 	uint8_t descriptor_type;
 
@@ -116,4 +153,6 @@
 #define HUB_CHAR_OC_PER_PORT_FLAG       (1 << 3)
 #define HUB_CHAR_NO_OC_FLAG             (1 << 4)
+
+/* These are invalid for superspeed hub */
 #define HUB_CHAR_TT_THINK_16            (1 << 5)
 #define HUB_CHAR_TT_THINK_8             (1 << 6)
@@ -143,20 +182,28 @@
 #define STATUS_BYTES(ports) ((1 + ports + 7) / 8)
 
-/**	@brief usb hub specific request types.
- *
- *	For more information see Universal Serial Bus Specification Revision 1.1 chapter 11.16.2
+/**
+ * @brief usb hub specific request types.
  */
 typedef enum {
-    /**	This request resets a value reported in the hub status.	*/
+    /** This request resets a value reported in the hub status. */
     USB_HUB_REQ_TYPE_CLEAR_HUB_FEATURE = 0x20,
     /** This request resets a value reported in the port status. */
     USB_HUB_REQ_TYPE_CLEAR_PORT_FEATURE = 0x23,
-    /** This is an optional per-port diagnostic request that returns the bus state value, as sampled at the last EOF2 point. */
+    /**
+     * This is an optional per-port diagnostic request that returns the bus
+     * state value, as sampled at the last EOF2 point.
+     */
     USB_HUB_REQ_TYPE_GET_STATE = 0xA3,
     /** This request returns the hub descriptor. */
     USB_HUB_REQ_TYPE_GET_DESCRIPTOR = 0xA0,
-    /** This request returns the current hub status and the states that have changed since the previous acknowledgment. */
+    /**
+     * This request returns the current hub status and the states that have
+     * changed since the previous acknowledgment.
+     */
     USB_HUB_REQ_TYPE_GET_HUB_STATUS = 0xA0,
-    /** This request returns the current port status and the current value of the port status change bits. */
+    /**
+     * This request returns the current port status and the current value of the
+     * port status change bits.
+     */
     USB_HUB_REQ_TYPE_GET_PORT_STATUS = 0xA3,
     /** This request overwrites the hub descriptor. */
@@ -164,6 +211,11 @@
     /** This request sets a value reported in the hub status. */
     USB_HUB_REQ_TYPE_SET_HUB_FEATURE = 0x20,
+    /**
+     * This request sets the value that the hub uses to determine the index
+     * into the Route String Index for the hub.
+     */
+    USB_HUB_REQ_TYPE_SET_HUB_DEPTH = 0x20,
     /** This request sets a value reported in the port status. */
-    USB_HUB_REQ_TYPE_SET_PORT_FEATURE = 0x23
+    USB_HUB_REQ_TYPE_SET_PORT_FEATURE = 0x23,
 } usb_hub_bm_request_type_t;
 
@@ -191,8 +243,10 @@
     /** */
     USB_HUB_STOP_TT = 11,
+    /** USB 3+ only */
+    USB_HUB_REQUEST_SET_HUB_DEPTH = 12,
 } usb_hub_request_t;
 
 /**
- *	Maximum size of usb hub descriptor in bytes
+ * Maximum size of usb hub descriptor in bytes
  */
 /* 7 (basic size) + 2*32 (port bitmasks) */
Index: uspace/lib/usb/include/usb/descriptor.h
===================================================================
--- uspace/lib/usb/include/usb/descriptor.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usb/include/usb/descriptor.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -49,4 +49,10 @@
 	USB_DESCTYPE_OTHER_SPEED_CONFIGURATION = 7,
 	USB_DESCTYPE_INTERFACE_POWER = 8,
+	/* USB 3.0 types */
+	USB_DESCTYPE_OTG = 9,
+	USB_DESCTYPE_DEBUG = 0xa,
+	USB_DESCTYPE_IFACE_ASSOC = 0xb,
+	USB_DESCTYPE_BOS = 0xf,
+	USB_DESCTYPE_DEVICE_CAP = 0x10,
 	/* Class specific */
 	USB_DESCTYPE_HID = 0x21,
@@ -54,4 +60,6 @@
 	USB_DESCTYPE_HID_PHYSICAL = 0x23,
 	USB_DESCTYPE_HUB = 0x29,
+	USB_DESCTYPE_SSPEED_HUB = 0x2a,
+	USB_DESCTYPE_SSPEED_EP_COMPANION = 0x30
 	/* USB_DESCTYPE_ = */
 } usb_descriptor_type_t;
@@ -193,8 +201,12 @@
 	/** Endpoint address together with data flow direction. */
 	uint8_t endpoint_address;
+#define USB_ED_GET_EP(ed)	((ed).endpoint_address & 0xf)
+#define USB_ED_GET_DIR(ed)	(!(((ed).endpoint_address >> 7) & 0x1))
+
 	/** Endpoint attributes.
 	 * Includes transfer type (usb_transfer_type_t).
 	 */
 	uint8_t attributes;
+#define USB_ED_GET_TRANSFER_TYPE(ed)	((ed).attributes & 0x3)
 	/** Maximum packet size.
 	 * Lower 10 bits represent the actuall size
@@ -202,18 +214,44 @@
 	 * HS INT and ISO transfers. */
 	uint16_t max_packet_size;
-
-#define ED_MPS_PACKET_SIZE_MASK  0x3ff
-#define ED_MPS_PACKET_SIZE_GET(value) \
-	((value) & ED_MPS_PACKET_SIZE_MASK)
-#define ED_MPS_TRANS_OPPORTUNITIES_GET(value) \
-	((((value) >> 10) & 0x3) + 1)
-
-	/** Polling interval in milliseconds.
-	 * Ignored for bulk and control endpoints.
-	 * Isochronous endpoints must use value 1.
-	 * Interrupt endpoints any value from 1 to 255.
+#define USB_ED_GET_MPS(ed) \
+	(uint16_usb2host((ed).max_packet_size) & 0x7ff)
+#define USB_ED_GET_ADD_OPPS(ed) \
+	((uint16_usb2host((ed).max_packet_size) >> 11) & 0x3)
+	/** Polling interval. Different semantics for various (speed, type)
+	 * pairs.
 	 */
 	uint8_t poll_interval;
 } __attribute__ ((packed)) usb_standard_endpoint_descriptor_t;
+
+/** Superspeed USB endpoint companion descriptor.
+ * See USB 3 specification, section 9.6.7.
+ */
+typedef struct {
+	/** Size of this descriptor in bytes */
+	uint8_t length;
+	/** Descriptor type (USB_DESCTYPE_SSPEED_EP_COMPANION). */
+	uint8_t descriptor_type;
+	/** The maximum number of packets the endpoint can send
+	 * or receive as part of a burst. Valid values are from 0 to 15.
+	 * The endpoint can only burst max_burst + 1 packets at a time.
+	 */
+	uint8_t max_burst;
+	/** Valid only for bulk and isochronous endpoints.
+	 * For bulk endpoints, this field contains the amount of streams
+	 * supported by the endpoint.
+	 * For isochronous endpoints, this field contains maximum
+	 * number of packets supported within a service interval.
+	 * Warning: the values returned by macros may not make any sense
+	 * for specific endpoint types.
+	 */
+	uint8_t attributes;
+#define USB_SSC_MAX_STREAMS(sscd) ((sscd).attributes & 0x1f)
+#define USB_SSC_MULT(sscd) ((sscd).attributes & 0x3)
+	/** The total number of bytes this endpoint will transfer
+	 * every service interval (SI).
+	 * This field is only valid for periodic endpoints.
+	 */
+	uint16_t bytes_per_interval;
+} __attribute__ ((packed)) usb_superspeed_endpoint_companion_descriptor_t;
 
 /** Part of standard USB HID descriptor specifying one class descriptor.
Index: uspace/lib/usb/include/usb/port.h
===================================================================
--- uspace/lib/usb/include/usb/port.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/lib/usb/include/usb/port.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2018 HelenOS xHCI 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 libusb
+ * @{
+ */
+/** @file
+ * An USB hub port state machine.
+ *
+ * This helper structure solves a repeated problem in USB world: management of
+ * USB ports. A port is an object which receives events (connect, disconnect,
+ * reset) which are to be handled in an asynchronous way. The tricky part is
+ * that response to events has to wait for different events - the most notable
+ * being USB 2 port requiring port reset to be enabled. This problem is solved
+ * by launching separate fibril for taking the port up.
+ *
+ * This subsystem abstracts the rather complicated state machine, and offers
+ * a simple interface to announce events and leave the fibril management on the
+ * library.
+ */
+
+#ifndef LIB_USB_PORT_H
+#define LIB_USB_PORT_H
+
+#include <fibril_synch.h>
+#include <usb/usb.h>
+#include <errno.h>
+
+typedef enum {
+	PORT_DISABLED,	/* No device connected. Fibril not running. */
+	PORT_ENUMERATED,/* Device enumerated. Fibril finished succesfully. */
+	PORT_CONNECTING,/* A connected event received, fibril running. */
+	PORT_DISCONNECTING,/* A disconnected event received, fibril running. */
+	PORT_ERROR,	/* An error "in-progress". Fibril still running. */
+} usb_port_state_t;
+
+typedef struct usb_port {
+	/** Guarding all fields. Is locked in the connected op. */
+	fibril_mutex_t guard;
+	/** Current state of the port */
+	usb_port_state_t state;
+	/** CV signalled on fibril exit. */
+	fibril_condvar_t finished_cv;
+	/** CV signalled on enabled event. */
+	fibril_condvar_t enabled_cv;
+} usb_port_t;
+
+/**
+ * Callback to run the enumeration routine.
+ * Called in separate fibril with guard locked.
+ */
+typedef int (*usb_port_enumerate_t)(usb_port_t *);
+
+/**
+ * Callback to run the enumeration routine. Called in the caller fibril,
+ */
+typedef void (*usb_port_remove_t)(usb_port_t *);
+
+/* Following methods are intended to be called "from outside". */
+void usb_port_init(usb_port_t *);
+int usb_port_connected(usb_port_t *, usb_port_enumerate_t);
+void usb_port_enabled(usb_port_t *);
+void usb_port_disabled(usb_port_t *, usb_port_remove_t);
+void usb_port_fini(usb_port_t *);
+
+/* And these are to be called from the connected handler. */
+int usb_port_condvar_wait_timeout(usb_port_t *port, fibril_condvar_t *, suseconds_t);
+
+/**
+ * Wait for the enabled event to come.
+ *
+ * @return Error code:
+ *	EINTR if the device was disconnected in the meantime.
+ *	ETIMEOUT if the enabled event didn't come in 2 seconds
+ */
+static inline int usb_port_wait_for_enabled(usb_port_t *port)
+{
+	return usb_port_condvar_wait_timeout(port, &port->enabled_cv, 2000000);
+}
+
+#endif
+
+/**
+ * @}
+ */
Index: uspace/lib/usb/include/usb/request.h
===================================================================
--- uspace/lib/usb/include/usb/request.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usb/include/usb/request.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -71,4 +71,7 @@
 #define USB_ENDPOINT_STATUS_HALTED ((uint16_t)(1 << 0))
 
+/** Size of the USB setup packet */
+#define USB_SETUP_PACKET_SIZE 8
+
 /** Device request setup packet.
  * The setup packet describes the request.
@@ -108,8 +111,29 @@
 } __attribute__ ((packed)) usb_device_request_setup_packet_t;
 
-static_assert(sizeof(usb_device_request_setup_packet_t) == 8);
+static_assert(sizeof(usb_device_request_setup_packet_t) == USB_SETUP_PACKET_SIZE);
 
-int usb_request_needs_toggle_reset(
-    const usb_device_request_setup_packet_t *request);
+#define GET_DEVICE_DESC(size) \
+{ \
+	.request_type = SETUP_REQUEST_TYPE_DEVICE_TO_HOST \
+	    | (USB_REQUEST_TYPE_STANDARD << 5) \
+	    | USB_REQUEST_RECIPIENT_DEVICE, \
+	.request = USB_DEVREQ_GET_DESCRIPTOR, \
+	.value = uint16_host2usb(USB_DESCTYPE_DEVICE << 8), \
+	.index = uint16_host2usb(0), \
+	.length = uint16_host2usb(size), \
+};
+
+#define SET_ADDRESS(address) \
+{ \
+	.request_type = SETUP_REQUEST_TYPE_HOST_TO_DEVICE \
+	    | (USB_REQUEST_TYPE_STANDARD << 5) \
+	    | USB_REQUEST_RECIPIENT_DEVICE, \
+	.request = USB_DEVREQ_SET_ADDRESS, \
+	.value = uint16_host2usb(address), \
+	.index = uint16_host2usb(0), \
+	.length = uint16_host2usb(0), \
+};
+
+#define CTRL_PIPE_MIN_PACKET_SIZE 8
 
 #endif
Index: uspace/lib/usb/include/usb/usb.h
===================================================================
--- uspace/lib/usb/include/usb/usb.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usb/include/usb/usb.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -39,5 +39,5 @@
 #include <stdint.h>
 #include <types/common.h>
-#include <usb_iface.h>
+#include <usbhc_iface.h>
 
 /** Convert 16bit value from native (host) endianness to USB endianness. */
@@ -61,4 +61,9 @@
 {
 	return (s == USB_SPEED_FULL) || (s == USB_SPEED_LOW);
+}
+
+static inline bool usb_speed_is_valid(const usb_speed_t s)
+{
+	return (s >= USB_SPEED_LOW) && (s < USB_SPEED_MAX);
 }
 
@@ -97,5 +102,5 @@
 static inline bool usb_address_is_valid(usb_address_t a)
 {
-	return (a >= USB_ADDRESS_DEFAULT) && (a <= USB11_ADDRESS_MAX);
+	return a <= USB11_ADDRESS_MAX;
 }
 
@@ -103,6 +108,9 @@
 #define USB_ENDPOINT_DEFAULT_CONTROL 0
 
-/** Maximum endpoint number in USB 1.1. */
-#define USB11_ENDPOINT_MAX 16
+/** Maximum endpoint number in USB */
+#define USB_ENDPOINT_MAX 16
+
+/** There might be two directions for every endpoint number (except 0) */
+#define USB_ENDPOINT_COUNT (2 * USB_ENDPOINT_MAX)
 
 /** Check USB endpoint for allowed values.
@@ -115,17 +123,19 @@
 static inline bool usb_endpoint_is_valid(usb_endpoint_t ep)
 {
-	return (ep >= USB_ENDPOINT_DEFAULT_CONTROL) &&
-	    (ep < USB11_ENDPOINT_MAX);
+	return ep < USB_ENDPOINT_MAX;
 }
 
-/** Check USB target for allowed values (address and endpoint).
+/**
+ * Check USB target for allowed values (address, endpoint, stream).
  *
  * @param target.
  * @return True, if values are wihtin limits, false otherwise.
  */
-static inline bool usb_target_is_valid(usb_target_t target)
+static inline bool usb_target_is_valid(usb_target_t *target)
 {
-	return usb_address_is_valid(target.address) &&
-	    usb_endpoint_is_valid(target.endpoint);
+	return usb_address_is_valid(target->address) &&
+	    usb_endpoint_is_valid(target->endpoint);
+
+	// A 16-bit Stream ID is always valid.
 }
 
@@ -136,14 +146,9 @@
  * @return Whether @p a and @p b points to the same pipe on the same device.
  */
-static inline int usb_target_same(usb_target_t a, usb_target_t b)
+static inline bool usb_target_same(usb_target_t a, usb_target_t b)
 {
 	return (a.address == b.address)
 	    && (a.endpoint == b.endpoint);
 }
-
-/** General handle type.
- * Used by various USB functions as opaque handle.
- */
-typedef sysarg_t usb_handle_t;
 
 /** USB packet identifier. */
Index: uspace/lib/usb/src/dump.c
===================================================================
--- uspace/lib/usb/src/dump.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usb/src/dump.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -61,4 +61,6 @@
 static void usb_dump_descriptor_endpoint(FILE *, const char *, const char *,
     const uint8_t *, size_t);
+static void usb_dump_descriptor_superspeed_endpoint_companion(FILE *, const char *, const char *,
+    const uint8_t *, size_t);
 static void usb_dump_descriptor_hid(FILE *, const char *, const char *,
     const uint8_t *, size_t);
@@ -75,4 +77,5 @@
 	{ USB_DESCTYPE_INTERFACE, usb_dump_descriptor_interface },
 	{ USB_DESCTYPE_ENDPOINT, usb_dump_descriptor_endpoint },
+	{ USB_DESCTYPE_SSPEED_EP_COMPANION, usb_dump_descriptor_superspeed_endpoint_companion },
 	{ USB_DESCTYPE_HID, usb_dump_descriptor_hid },
 	{ USB_DESCTYPE_HUB, usb_dump_descriptor_hub },
@@ -238,4 +241,21 @@
 	PRINTLINE("wMaxPacketSize = %d", d->max_packet_size);
 	PRINTLINE("bInterval = %dms", d->poll_interval);
+}
+
+static void usb_dump_descriptor_superspeed_endpoint_companion(FILE *output,
+    const char *line_prefix, const char *line_suffix,
+    const uint8_t *descriptor, size_t descriptor_length)
+{
+	usb_superspeed_endpoint_companion_descriptor_t *d
+	   = (usb_superspeed_endpoint_companion_descriptor_t *) descriptor;
+	if (descriptor_length < sizeof(*d)) {
+		return;
+	}
+
+	PRINTLINE("bLength = %u", d->length);
+	PRINTLINE("bDescriptorType = 0x%02X", d->descriptor_type);
+	PRINTLINE("bMaxBurst = %u", d->max_burst);
+	PRINTLINE("bmAttributes = %d", d->attributes);
+	PRINTLINE("wBytesPerInterval = %u", d->bytes_per_interval);
 }
 
Index: uspace/lib/usb/src/port.c
===================================================================
--- uspace/lib/usb/src/port.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/lib/usb/src/port.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2018 HelenOS xHCI 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 libusb
+ * @{
+ */
+/** @file
+ * An USB hub port state machine.
+ *
+ * This helper structure solves a repeated problem in USB world: management of
+ * USB ports. A port is an object which receives events (connect, disconnect,
+ * reset) which are to be handled in an asynchronous way. The tricky part is
+ * that response to events has to wait for different events - the most notable
+ * being USB 2 port requiring port reset to be enabled. This problem is solved
+ * by launching separate fibril for taking the port up.
+ *
+ * This subsystem abstracts the rather complicated state machine, and offers
+ * a simple interface to announce events and leave the fibril management on the
+ * library.
+ */
+
+#include <stdlib.h>
+#include <fibril.h>
+#include <assert.h>
+#include <usb/debug.h>
+
+#include <usb/port.h>
+
+void usb_port_init(usb_port_t *port)
+{
+	fibril_mutex_initialize(&port->guard);
+	fibril_condvar_initialize(&port->finished_cv);
+	fibril_condvar_initialize(&port->enabled_cv);
+}
+
+struct enumerate_worker_args {
+	usb_port_t *port;
+	usb_port_enumerate_t handler;
+};
+
+static int enumerate_worker(void *arg)
+{
+	struct enumerate_worker_args * const args = arg;
+	usb_port_t *port = args->port;
+	usb_port_enumerate_t handler = args->handler;
+	free(args);
+
+	fibril_mutex_lock(&port->guard);
+
+	if (port->state == PORT_ERROR) {
+		/* 
+		 * The device was removed faster than this fibril acquired the
+		 * mutex.
+		 */
+		port->state = PORT_DISABLED;
+		goto out;
+	}
+
+	assert(port->state == PORT_CONNECTING);
+
+	port->state = handler(port)
+		? PORT_DISABLED
+		: PORT_ENUMERATED;
+
+out:
+	fibril_condvar_broadcast(&port->finished_cv);
+	fibril_mutex_unlock(&port->guard);
+	return EOK; // This is a fibril worker. No one will read the value.
+}
+
+int usb_port_connected(usb_port_t *port, usb_port_enumerate_t handler)
+{
+	assert(port);
+	int ret = ENOMEM;
+
+	fibril_mutex_lock(&port->guard);
+
+	if (port->state != PORT_DISABLED) {
+		usb_log_warning("a connected event come for port that is not disabled.");
+		ret = EINVAL;
+		goto out;
+	}
+
+	struct enumerate_worker_args *args = malloc(sizeof(*args));
+	if (!args)
+		goto out;
+
+	fid_t fibril = fibril_create(&enumerate_worker, args);
+	if (!fibril) {
+		free(args);
+		goto out;
+	}
+
+	args->port = port;
+	args->handler = handler;
+
+	port->state = PORT_CONNECTING;
+	fibril_add_ready(fibril);
+out:
+	fibril_mutex_unlock(&port->guard);
+	return ret;
+}
+
+void usb_port_enabled(usb_port_t *port)
+{
+	assert(port);
+
+	fibril_mutex_lock(&port->guard);
+	fibril_condvar_broadcast(&port->enabled_cv);
+	fibril_mutex_unlock(&port->guard);
+}
+
+struct remove_worker_args {
+	usb_port_t *port;
+	usb_port_remove_t handler;
+};
+
+static int remove_worker(void *arg)
+{
+	struct remove_worker_args * const args = arg;
+	usb_port_t *port = args->port;
+	usb_port_remove_t handler = args->handler;
+	free(args);
+
+	fibril_mutex_lock(&port->guard);
+	assert(port->state == PORT_DISCONNECTING);
+
+	handler(port);
+
+	port->state = PORT_DISABLED;
+	fibril_condvar_broadcast(&port->finished_cv);
+	fibril_mutex_unlock(&port->guard);
+	return EOK;
+}
+
+static void fork_remove_worker(usb_port_t *port, usb_port_remove_t handler)
+{
+	struct remove_worker_args *args = malloc(sizeof(*args));
+	if (!args)
+		return;
+
+	fid_t fibril = fibril_create(&remove_worker, args);
+	if (!fibril) {
+		free(args);
+		return;
+	}
+
+	args->port = port;
+	args->handler = handler;
+
+	port->state = PORT_DISCONNECTING;
+	fibril_add_ready(fibril);
+}
+
+void usb_port_disabled(usb_port_t *port, usb_port_remove_t handler)
+{
+	assert(port);
+	fibril_mutex_lock(&port->guard);
+
+	switch (port->state) {
+	case PORT_ENUMERATED:
+		fork_remove_worker(port, handler);
+		break;
+
+	case PORT_CONNECTING:
+		port->state = PORT_ERROR;
+		fibril_condvar_broadcast(&port->enabled_cv);
+		/* fallthrough */
+	case PORT_ERROR:
+		fibril_condvar_wait(&port->finished_cv, &port->guard);
+		/* fallthrough */
+	case PORT_DISCONNECTING:
+	case PORT_DISABLED:
+		break;
+	}
+
+	fibril_mutex_unlock(&port->guard);
+}
+
+void usb_port_fini(usb_port_t *port)
+{
+	assert(port);
+
+	fibril_mutex_lock(&port->guard);
+	switch (port->state) {
+	case PORT_ENUMERATED:
+		/*
+		 * We shall inform the HC that the device is gone.
+		 * However, we can't wait for it, because if the device is hub,
+		 * it would have to use the same IPC handling fibril as we do.
+		 * But we cannot even defer it to another fibril, because then
+		 * the HC would assume our driver didn't cleanup properly, and
+		 * will remove those devices by himself.
+		 *
+		 * So the solutions seems to be to behave like a bad driver and
+		 * leave the work for HC.
+		 */
+		port->state = PORT_DISABLED;
+		/* fallthrough */
+	case PORT_DISABLED:
+		break;
+
+	/* We first have to stop the fibril in progress. */
+	case PORT_CONNECTING:
+		port->state = PORT_ERROR;
+		fibril_condvar_broadcast(&port->enabled_cv);
+		/* fallthrough */
+	case PORT_ERROR:
+	case PORT_DISCONNECTING:
+		fibril_condvar_wait(&port->finished_cv, &port->guard);
+		break;
+	}
+	fibril_mutex_unlock(&port->guard);
+}
+
+int usb_port_condvar_wait_timeout(usb_port_t *port, fibril_condvar_t *cv, suseconds_t timeout)
+{
+	assert(port);
+	assert(port->state == PORT_CONNECTING);
+	assert(fibril_mutex_is_locked(&port->guard));
+
+	if (fibril_condvar_wait_timeout(cv, &port->guard, timeout))
+		return ETIMEOUT;
+
+	return port->state == PORT_CONNECTING ? EOK : EINTR;
+}
+
+/**
+ * @}
+ */
Index: uspace/lib/usb/src/usb.c
===================================================================
--- uspace/lib/usb/src/usb.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usb/src/usb.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -44,4 +44,5 @@
 	[USB_SPEED_FULL] = "full",
 	[USB_SPEED_HIGH] = "high",
+	[USB_SPEED_SUPER] = "super",
 };
 
@@ -118,42 +119,4 @@
 }
 
-/** Check setup packet data for signs of toggle reset.
- *
- * @param[in] requst Setup requst data.
- *
- * @retval -1 No endpoints need reset.
- * @retval 0 All endpoints need reset.
- * @retval >0 Specified endpoint needs reset.
- *
- */
-int usb_request_needs_toggle_reset(
-    const usb_device_request_setup_packet_t *request)
-{
-	assert(request);
-	switch (request->request)
-	{
-	/* Clear Feature ENPOINT_STALL */
-	case USB_DEVREQ_CLEAR_FEATURE: /*resets only cleared ep */
-		/* 0x2 ( HOST to device | STANDART | TO ENPOINT) */
-		if ((request->request_type == 0x2) &&
-		    (request->value == USB_FEATURE_ENDPOINT_HALT))
-			return uint16_usb2host(request->index);
-		break;
-	case USB_DEVREQ_SET_CONFIGURATION:
-	case USB_DEVREQ_SET_INTERFACE:
-		/* Recipient must be device, this resets all endpoints,
-		 * In fact there should be no endpoints but EP 0 registered
-		 * as different interfaces use different endpoints,
-		 * unless you're changing configuration or alternative
-		 * interface of an already setup device. */
-		if (!(request->request_type & SETUP_REQUEST_TYPE_DEVICE_TO_HOST))
-			return 0;
-		break;
-	default:
-		break;
-	}
-	return -1;
-}
-
 /**
  * @}
Index: uspace/lib/usbdev/include/usb/dev/device.h
===================================================================
--- uspace/lib/usbdev/include/usb/dev/device.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbdev/include/usb/dev/device.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -42,4 +42,5 @@
 #include <usb/dev/alternate_ifaces.h>
 #include <usb/dev/pipes.h>
+#include <usbhc_iface.h>
 
 #include <assert.h>
@@ -86,9 +87,11 @@
 usb_endpoint_mapping_t * usb_device_get_mapped_ep_desc(usb_device_t *,
     const usb_endpoint_description_t *);
-usb_endpoint_mapping_t * usb_device_get_mapped_ep(usb_device_t *,
-    usb_endpoint_t);
+int usb_device_unmap_ep(usb_endpoint_mapping_t *);
 
-int usb_device_get_iface_number(usb_device_t *);
-devman_handle_t usb_device_get_devman_handle(usb_device_t *);
+usb_address_t usb_device_get_address(const usb_device_t *);
+usb_speed_t usb_device_get_depth(const usb_device_t *);
+usb_speed_t usb_device_get_speed(const usb_device_t *);
+int usb_device_get_iface_number(const usb_device_t *);
+devman_handle_t usb_device_get_devman_handle(const usb_device_t *);
 
 const usb_device_descriptors_t * usb_device_descriptors(usb_device_t *);
Index: uspace/lib/usbdev/include/usb/dev/driver.h
===================================================================
--- uspace/lib/usbdev/include/usb/dev/driver.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbdev/include/usb/dev/driver.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -45,7 +45,11 @@
 	errno_t (*device_add)(usb_device_t *);
 	/** Callback when a device is about to be removed from the system. */
-	errno_t (*device_rem)(usb_device_t *);
+	errno_t (*device_remove)(usb_device_t *);
 	/** Callback when a device was removed from the system. */
 	errno_t (*device_gone)(usb_device_t *);
+	/** Callback asking the driver to online a specific function. */
+	errno_t (*function_online)(ddf_fun_t *);
+	/** Callback asking the driver to offline a specific function. */
+	errno_t (*function_offline)(ddf_fun_t *);
 } usb_driver_ops_t;
 
Index: uspace/lib/usbdev/include/usb/dev/pipes.h
===================================================================
--- uspace/lib/usbdev/include/usb/dev/pipes.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbdev/include/usb/dev/pipes.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -44,25 +44,13 @@
 
 #define CTRL_PIPE_MIN_PACKET_SIZE 8
+
 /** Abstraction of a logical connection to USB device endpoint.
- * It encapsulates endpoint attributes (transfer type etc.).
+ * It contains some vital information about the pipe.
  * This endpoint must be bound with existing usb_device_connection_t
  * (i.e. the wire to send data over).
  */
 typedef struct {
-	/** Endpoint number. */
-	usb_endpoint_t endpoint_no;
-
-	/** Endpoint transfer type. */
-	usb_transfer_type_t transfer_type;
-
-	/** Endpoint direction. */
-	usb_direction_t direction;
-
-	/** Maximum packet size for the endpoint. */
-	size_t max_packet_size;
-
-	/** Number of packets per frame/uframe.
-	 * Only valid for HS INT and ISO transfers. All others should set to 1*/
-	unsigned packets;
+	/** Pipe description received from HC */
+	usb_pipe_desc_t desc;
 
 	/** Whether to automatically reset halt on the endpoint.
@@ -70,5 +58,4 @@
 	 */
 	bool auto_reset_halt;
-
 	/** The connection used for sending the data. */
 	usb_dev_session_t *bus_session;
@@ -103,4 +90,6 @@
 	/** Found descriptor fitting the description. */
 	const usb_standard_endpoint_descriptor_t *descriptor;
+	/** Relevant superspeed companion descriptor. */
+	const usb_superspeed_endpoint_companion_descriptor_t *companion_descriptor;
 	/** Interface descriptor the endpoint belongs to. */
 	const usb_standard_interface_descriptor_t *interface;
@@ -109,13 +98,11 @@
 } usb_endpoint_mapping_t;
 
-errno_t usb_pipe_initialize(usb_pipe_t *, usb_endpoint_t, usb_transfer_type_t,
-    size_t, usb_direction_t, unsigned, usb_dev_session_t *);
+errno_t usb_pipe_initialize(usb_pipe_t *, usb_dev_session_t *);
 errno_t usb_pipe_initialize_default_control(usb_pipe_t *, usb_dev_session_t *);
 
-errno_t usb_pipe_probe_default_control(usb_pipe_t *);
 errno_t usb_pipe_initialize_from_configuration(usb_endpoint_mapping_t *,
     size_t, const uint8_t *, size_t, usb_dev_session_t *);
 
-errno_t usb_pipe_register(usb_pipe_t *, unsigned);
+errno_t usb_pipe_register(usb_pipe_t *, const usb_standard_endpoint_descriptor_t *, const usb_superspeed_endpoint_companion_descriptor_t *);
 errno_t usb_pipe_unregister(usb_pipe_t *);
 
Index: uspace/lib/usbdev/include/usb/dev/poll.h
===================================================================
--- uspace/lib/usbdev/include/usb/dev/poll.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbdev/include/usb/dev/poll.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -1,4 +1,5 @@
 /*
  * Copyright (c) 2011 Vojtech Horky
+ * Copyright (c) 2017 Petr Manek
  * All rights reserved.
  *
@@ -43,23 +44,23 @@
 #include <stddef.h>
 #include <stdint.h>
+#include <fibril_synch.h>
 
-/** Parameters and callbacks for automated polling. */
-typedef struct {
-	/** Level of debugging messages from auto polling.
-	 * 0 - nothing
-	 * 1 - inform about errors and polling start/end
-	 * 2 - also dump every retrieved buffer
-	 */
-	int debug;
-	/** Maximum number of consecutive errors before polling termination. */
-	size_t max_failures;
-	/** Delay between poll requests in milliseconds.
-	 * Set to negative value to use value from endpoint descriptor.
-	 */
-	int delay;
-	/** Whether to automatically try to clear the HALT feature after
-	 * the endpoint stalls.
-	 */
-	bool auto_clear_halt;
+
+/** USB automated polling. */
+typedef struct usb_polling {
+	/** Mandatory parameters - user is expected to configure these. */
+
+	/** USB device to poll. */
+	usb_device_t *device;
+
+	/** Device enpoint mapping to use for polling. */
+	usb_endpoint_mapping_t *ep_mapping;
+
+	/** Size of the recieved data. */
+	size_t request_size;
+
+	/** Data buffer of at least `request_size`. User is responsible for its allocation. */
+	uint8_t *buffer;
+
 	/** Callback when data arrives.
 	 *
@@ -72,4 +73,33 @@
 	bool (*on_data)(usb_device_t *dev, uint8_t *data, size_t data_size,
 	    void *arg);
+
+
+	/** Optional parameters - user can customize them, but they are defaulted to
+	 *  some reasonable values.
+	 */
+
+	/** Level of debugging messages from auto polling.
+	 * 0 - nothing (default)
+	 * 1 - inform about errors and polling start/end
+	 * 2 - also dump every retrieved buffer
+	 */
+	int debug;
+
+	/** Maximum number of consecutive errors before polling termination (default 3). */
+	size_t max_failures;
+
+	/** Delay between poll requests in milliseconds.
+	 * By default, value from endpoint descriptor used.
+	 */
+	int delay;
+
+	/** Whether to automatically try to clear the HALT feature after
+	 * the endpoint stalls (true by default).
+	 */
+	bool auto_clear_halt;
+
+	/** Argument to pass to callbacks (default NULL). */
+	void *arg;
+
 	/** Callback when polling is terminated.
 	 *
@@ -80,4 +110,5 @@
 	void (*on_polling_end)(usb_device_t *dev, bool due_to_errors,
 	    void *arg);
+
 	/** Callback when error occurs.
 	 *
@@ -88,24 +119,29 @@
 	 */
 	bool (*on_error)(usb_device_t *dev, errno_t err_code, void *arg);
-	/** Argument to pass to callbacks. */
-	void *arg;
-} usb_device_auto_polling_t;
 
-typedef bool (*usb_polling_callback_t)(usb_device_t *, uint8_t *, size_t, void *);
-typedef void (*usb_polling_terminted_callback_t)(usb_device_t *, bool, void *);
 
-extern errno_t usb_device_auto_polling(usb_device_t *, usb_endpoint_t,
-    const usb_device_auto_polling_t *, size_t);
+	/** Internal parameters - user is not expected to set them. Messing with them
+	 *  can result in unexpected behavior if you do not know what you are doing.
+	 */
 
-extern errno_t usb_device_auto_poll(usb_device_t *, usb_endpoint_t,
-    usb_polling_callback_t, size_t, int, usb_polling_terminted_callback_t, void *);
+	/** Fibril used for polling. */
+	fid_t fibril;
 
-extern errno_t usb_device_auto_polling_desc(usb_device_t *,
-    const usb_endpoint_description_t *, const usb_device_auto_polling_t *,
-    size_t);
+	/** True if polling is currently in operation. */
+	volatile bool running;
 
-extern errno_t usb_device_auto_poll_desc(usb_device_t *,
-    const usb_endpoint_description_t *, usb_polling_callback_t, size_t, int,
-    usb_polling_terminted_callback_t, void *);
+	/** True if polling should terminate as soon as possible. */
+	volatile bool joining;
+
+	/** Synchronization primitives for joining polling end. */
+	fibril_mutex_t guard;
+	fibril_condvar_t cv;
+} usb_polling_t;
+
+errno_t usb_polling_init(usb_polling_t *);
+void usb_polling_fini(usb_polling_t *);
+
+errno_t usb_polling_start(usb_polling_t *);
+errno_t usb_polling_join(usb_polling_t *);
 
 #endif
Index: uspace/lib/usbdev/include/usb/dev/request.h
===================================================================
--- uspace/lib/usbdev/include/usb/dev/request.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbdev/include/usb/dev/request.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -85,5 +85,4 @@
     char **);
 
-errno_t usb_request_clear_endpoint_halt(usb_pipe_t *, uint16_t);
 errno_t usb_pipe_clear_halt(usb_pipe_t *, usb_pipe_t *);
 errno_t usb_request_get_endpoint_status(usb_pipe_t *, usb_pipe_t *, uint16_t *);
Index: uspace/lib/usbdev/src/devdrv.c
===================================================================
--- uspace/lib/usbdev/src/devdrv.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbdev/src/devdrv.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -48,4 +48,5 @@
 #include <devman.h>
 #include <errno.h>
+#include <str_error.h>
 #include <stdlib.h>
 
@@ -56,11 +57,11 @@
 	/** Connection to device on USB bus */
 	usb_dev_session_t *bus_session;
-	
+
 	/** devman handle */
 	devman_handle_t handle;
-	
+
 	/** The default control pipe. */
 	usb_pipe_t ctrl_pipe;
-	
+
 	/** Other endpoint pipes.
 	 *
@@ -69,8 +70,17 @@
 	 */
 	usb_endpoint_mapping_t *pipes;
-	
+
 	/** Number of other endpoint pipes. */
 	size_t pipes_count;
-	
+
+	/** USB address of this device */
+	usb_address_t address;
+
+	/** Depth in the USB hub hiearchy */
+	unsigned depth;
+
+	/** USB speed of this device */
+	usb_speed_t speed;
+
 	/** Current interface.
 	 *
@@ -79,14 +89,14 @@
 	 */
 	int interface_no;
-	
+
 	/** Alternative interfaces. */
 	usb_alternate_interfaces_t alternate_interfaces;
-	
+
 	/** Some useful descriptors for USB device. */
 	usb_device_descriptors_t descriptors;
-	
+
 	/** Generic DDF device backing this one. DO NOT TOUCH! */
 	ddf_dev_t *ddf_dev;
-	
+
 	/** Custom driver data.
 	 *
@@ -146,5 +156,5 @@
 		return rc;
 	}
-	
+
 	/* Change current alternative */
 	usb_dev->alternate_interfaces.current = alternate_setting;
@@ -255,12 +265,13 @@
 
 	/* Register created pipes. */
+	unsigned pipes_registered = 0;
 	for (size_t i = 0; i < pipe_count; i++) {
 		if (pipes[i].present) {
-			rc = usb_pipe_register(&pipes[i].pipe,
-			    pipes[i].descriptor->poll_interval);
+			rc = usb_pipe_register(&pipes[i].pipe, pipes[i].descriptor, pipes[i].companion_descriptor);
 			if (rc != EOK) {
 				goto rollback_unregister_endpoints;
 			}
 		}
+		pipes_registered++;
 	}
 
@@ -277,5 +288,5 @@
 	 */
 rollback_unregister_endpoints:
-	for (size_t i = 0; i < pipe_count; i++) {
+	for (size_t i = 0; i < pipes_registered; i++) {
 		if (pipes[i].present) {
 			usb_pipe_unregister(&pipes[i].pipe);
@@ -296,13 +307,16 @@
 	assert(usb_dev);
 	assert(usb_dev->pipes || usb_dev->pipes_count == 0);
-	
+
 	/* Destroy the pipes. */
+	int rc;
 	for (size_t i = 0; i < usb_dev->pipes_count; ++i) {
-		usb_log_debug2("Unregistering pipe %zu: %spresent.\n",
+		usb_log_debug2("Unregistering pipe %zu: %spresent.",
 		    i, usb_dev->pipes[i].present ? "" : "not ");
-		if (usb_dev->pipes[i].present)
-			usb_pipe_unregister(&usb_dev->pipes[i].pipe);
-	}
-	
+
+		rc = usb_device_unmap_ep(usb_dev->pipes + i);
+		if (rc != EOK && rc != ENOENT)
+			usb_log_warning("Unregistering pipe %zu failed: %s", i, str_error(rc));
+	}
+
 	free(usb_dev->pipes);
 	usb_dev->pipes = NULL;
@@ -327,16 +341,38 @@
 }
 
-usb_endpoint_mapping_t * usb_device_get_mapped_ep(
-    usb_device_t *usb_dev, usb_endpoint_t ep)
-{
-	assert(usb_dev);
-	for (unsigned i = 0; i < usb_dev->pipes_count; ++i) {
-		if (usb_dev->pipes[i].pipe.endpoint_no == ep)
-			return &usb_dev->pipes[i];
-	}
-	return NULL;
-}
-
-int usb_device_get_iface_number(usb_device_t *usb_dev)
+int usb_device_unmap_ep(usb_endpoint_mapping_t *epm)
+{
+	assert(epm);
+
+	if (!epm->present)
+		return ENOENT;
+
+	const int rc = usb_pipe_unregister(&epm->pipe);
+	if (rc != EOK)
+		return rc;
+
+	epm->present = false;
+	return EOK;
+}
+
+usb_address_t usb_device_get_address(const usb_device_t *usb_dev)
+{
+	assert(usb_dev);
+	return usb_dev->depth;
+}
+
+unsigned usb_device_get_depth(const usb_device_t *usb_dev)
+{
+	assert(usb_dev);
+	return usb_dev->depth;
+}
+
+usb_speed_t usb_device_get_speed(const usb_device_t *usb_dev)
+{
+	assert(usb_dev);
+	return usb_dev->speed;
+}
+
+int usb_device_get_iface_number(const usb_device_t *usb_dev)
 {
 	assert(usb_dev);
@@ -344,5 +380,5 @@
 }
 
-devman_handle_t usb_device_get_devman_handle(usb_device_t *usb_dev)
+devman_handle_t usb_device_get_devman_handle(const usb_device_t *usb_dev)
 {
 	assert(usb_dev);
@@ -394,6 +430,5 @@
  */
 static errno_t usb_device_init(usb_device_t *usb_dev, ddf_dev_t *ddf_dev,
-    const usb_endpoint_description_t **endpoints, const char **errstr_ptr,
-    devman_handle_t handle, int interface_no)
+    const usb_endpoint_description_t **endpoints, const char **errstr_ptr)
 {
 	assert(usb_dev != NULL);
@@ -403,6 +438,4 @@
 
 	usb_dev->ddf_dev = ddf_dev;
-	usb_dev->handle = handle;
-	usb_dev->interface_no = interface_no;
 	usb_dev->driver_data = NULL;
 	usb_dev->descriptors.full_config = NULL;
@@ -411,5 +444,5 @@
 	usb_dev->pipes = NULL;
 
-	usb_dev->bus_session = usb_dev_connect(handle);
+	usb_dev->bus_session = usb_dev_connect(usb_dev->handle);
 
 	if (!usb_dev->bus_session) {
@@ -420,6 +453,5 @@
 	/* This pipe was registered by the hub driver,
 	 * during device initialization. */
-	errno_t rc = usb_pipe_initialize_default_control(
-	    &usb_dev->ctrl_pipe, usb_dev->bus_session);
+	errno_t rc = usb_pipe_initialize_default_control(&usb_dev->ctrl_pipe, usb_dev->bus_session);
 	if (rc != EOK) {
 		usb_dev_disconnect(usb_dev->bus_session);
@@ -440,5 +472,5 @@
 	 * it makes no sense to speak about alternate interfaces when
 	 * controlling a device. */
-	rc = usb_alternate_interfaces_init(&usb_dev->alternate_interfaces,
+	usb_alternate_interfaces_init(&usb_dev->alternate_interfaces,
 	    usb_dev->descriptors.full_config,
 	    usb_dev->descriptors.full_config_size, usb_dev->interface_no);
@@ -457,23 +489,23 @@
 }
 
-static errno_t usb_device_get_info(async_sess_t *sess, devman_handle_t *handle,
-	int *iface_no)
-{
-	assert(handle);
-	assert(iface_no);
-	
+static errno_t usb_device_get_info(async_sess_t *sess, usb_device_t *dev)
+{
+	assert(dev);
+
 	async_exch_t *exch = async_exchange_begin(sess);
 	if (!exch)
 		return EPARTY;
-	
-	errno_t ret = usb_get_my_device_handle(exch, handle);
+
+	usb_device_desc_t dev_desc;
+	const errno_t ret = usb_get_my_description(exch, &dev_desc);
+
 	if (ret == EOK) {
-		ret = usb_get_my_interface(exch, iface_no);
-		if (ret == ENOTSUP) {
-			*iface_no = -1;
-			ret = EOK;
-		}
-	}
-	
+		dev->address = dev_desc.address;
+		dev->depth = dev_desc.depth;
+		dev->speed = dev_desc.speed;
+		dev->handle = dev_desc.handle;
+		dev->interface_no = dev_desc.iface;
+	}
+
 	async_exchange_end(exch);
 	return ret;
@@ -485,14 +517,8 @@
 	assert(ddf_dev);
 	assert(err);
-
-	devman_handle_t h = 0;
-	int iface_no = -1;
 
 	async_sess_t *sess = ddf_dev_parent_sess_get(ddf_dev);
 	if (sess == NULL)
 		return ENOMEM;
-	const errno_t ret = usb_device_get_info(sess, &h, &iface_no);
-	if (ret != EOK)
-		return ret;
 
 	usb_device_t *usb_dev =
@@ -502,6 +528,10 @@
 		return ENOMEM;
 	}
-	
-	return usb_device_init(usb_dev, ddf_dev, desc, err, h, iface_no);
+
+	const errno_t ret = usb_device_get_info(sess, usb_dev);
+	if (ret != EOK)
+		return ret;
+
+	return usb_device_init(usb_dev, ddf_dev, desc, err);
 }
 
@@ -517,20 +547,19 @@
 usb_device_t * usb_device_create(devman_handle_t handle)
 {
-	devman_handle_t h = 0;
-	int iface_no = -1;
-
-	async_sess_t *sess = devman_device_connect(handle, IPC_FLAG_BLOCKING);
-	errno_t ret = usb_device_get_info(sess, &h, &iface_no);
-	if (sess)
-		async_hangup(sess);
-	if (ret != EOK)
-		return NULL;
-
 	usb_device_t *usb_dev = malloc(sizeof(usb_device_t));
 	if (!usb_dev)
 		return NULL;
 
+	async_sess_t *sess = devman_device_connect(handle, IPC_FLAG_BLOCKING);
+	errno_t ret = usb_device_get_info(sess, usb_dev);
+	if (sess)
+		async_hangup(sess);
+	if (ret != EOK) {
+		free(usb_dev);
+		return NULL;
+	}
+
 	const char* dummy = NULL;
-	ret = usb_device_init(usb_dev, NULL, NULL, &dummy, handle, iface_no);
+	ret = usb_device_init(usb_dev, NULL, NULL, &dummy);
 	if (ret != EOK) {
 		free(usb_dev);
Index: uspace/lib/usbdev/src/devpoll.c
===================================================================
--- uspace/lib/usbdev/src/devpoll.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbdev/src/devpoll.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -1,4 +1,5 @@
 /*
  * Copyright (c) 2011 Vojtech Horky
+ * Copyright (c) 2017 Petr Manek
  * All rights reserved.
  *
@@ -47,4 +48,5 @@
 #include <errno.h>
 #include <fibril.h>
+#include <fibril_synch.h>
 #include <stdbool.h>
 #include <stdlib.h>
@@ -53,26 +55,50 @@
 #include <stdint.h>
 
-/** Maximum number of failed consecutive requests before announcing failure. */
-#define MAX_FAILED_ATTEMPTS 3
-
-/** Data needed for polling. */
-typedef struct {
-	/** Parameters for automated polling. */
-	usb_device_auto_polling_t auto_polling;
-
-	/** USB device to poll. */
-	usb_device_t *dev;
-	/** Device enpoint mapping to use for polling. */
-	usb_endpoint_mapping_t *polling_mapping;
-	/** Size of the recieved data. */
-	size_t request_size;
-	/** Data buffer. */
-	uint8_t *buffer;
-} polling_data_t;
+
+/** Initialize the polling data structure, its internals and configuration
+ *  with default values.
+ *
+ * @param polling Valid polling data structure.
+ * @return Error code.
+ * @retval EOK Polling data structure is ready to be used.
+ */
+int usb_polling_init(usb_polling_t *polling)
+{
+	if (!polling)
+		return EBADMEM;
+
+	/* Zero out everything */
+	memset(polling, 0, sizeof(usb_polling_t));
+
+	/* Internal initializers. */
+	fibril_mutex_initialize(&polling->guard);
+	fibril_condvar_initialize(&polling->cv);
+
+	/* Default configuration. */
+	polling->auto_clear_halt = true;
+	polling->delay = -1;
+	polling->max_failures = 3;
+
+	return EOK;
+}
+
+
+/** Destroy the polling data structure.
+ *  This function does nothing but a safety check whether the polling
+ *  was joined successfully.
+ *
+ * @param polling Polling data structure.
+ */
+void usb_polling_fini(usb_polling_t *polling)
+{
+	/* Nothing done at the moment. */
+	assert(polling);
+	assert(!polling->running);
+}
 
 
 /** Polling fibril.
  *
- * @param arg Pointer to polling_data_t.
+ * @param arg Pointer to usb_polling_t.
  * @return Always EOK.
  */
@@ -80,35 +106,37 @@
 {
 	assert(arg);
-	polling_data_t *data = arg;
-	/* Helper to reduce typing. */
-	const usb_device_auto_polling_t *params = &data->auto_polling;
-
-	usb_pipe_t *pipe = &data->polling_mapping->pipe;
-
-	if (params->debug > 0) {
+	usb_polling_t *polling = arg;
+
+	fibril_mutex_lock(&polling->guard);
+	polling->running = true;
+	fibril_mutex_unlock(&polling->guard);
+
+	usb_pipe_t *pipe = &polling->ep_mapping->pipe;
+
+	if (polling->debug > 0) {
 		const usb_endpoint_mapping_t *mapping =
-		    data->polling_mapping;
+		    polling->ep_mapping;
 		usb_log_debug("Poll (%p): started polling of `%s' - " \
 		    "interface %d (%s,%d,%d), %zuB/%zu.\n",
-		    data, usb_device_get_name(data->dev),
+		    polling, usb_device_get_name(polling->device),
 		    (int) mapping->interface->interface_number,
 		    usb_str_class(mapping->interface->interface_class),
 		    (int) mapping->interface->interface_subclass,
 		    (int) mapping->interface->interface_protocol,
-		    data->request_size, pipe->max_packet_size);
+		    polling->request_size, pipe->desc.max_transfer_size);
 	}
 
 	size_t failed_attempts = 0;
-	while (failed_attempts <= params->max_failures) {
+	while (failed_attempts <= polling->max_failures) {
 		size_t actual_size;
-		const errno_t rc = usb_pipe_read(pipe, data->buffer,
-		    data->request_size, &actual_size);
+		const errno_t rc = usb_pipe_read(pipe, polling->buffer,
+		    polling->request_size, &actual_size);
 
 		if (rc == EOK) {
-			if (params->debug > 1) {
+			if (polling->debug > 1) {
 				usb_log_debug(
 				    "Poll%p: received: '%s' (%zuB).\n",
-				    data,
-				    usb_debug_str_buffer(data->buffer,
+				    polling,
+				    usb_debug_str_buffer(polling->buffer,
 				        actual_size, 16),
 				    actual_size);
@@ -117,24 +145,26 @@
 				usb_log_debug(
 				    "Poll%p: polling failed: %s.\n",
-				    data, str_error(rc));
+				    polling, str_error(rc));
 		}
 
 		/* If the pipe stalled, we can try to reset the stall. */
-		if ((rc == ESTALL) && (params->auto_clear_halt)) {
+		if (rc == ESTALL && polling->auto_clear_halt) {
 			/*
 			 * We ignore error here as this is usually a futile
 			 * attempt anyway.
 			 */
-			usb_request_clear_endpoint_halt(
-			    usb_device_get_default_pipe(data->dev),
-			    pipe->endpoint_no);
+			usb_pipe_clear_halt(
+			    usb_device_get_default_pipe(polling->device), pipe);
 		}
 
 		if (rc != EOK) {
 			++failed_attempts;
-			const bool cont = (params->on_error == NULL) ? true :
-			    params->on_error(data->dev, rc, params->arg);
-			if (!cont) {
-				failed_attempts = params->max_failures;
+			const bool carry_on = !polling->on_error ? true :
+			    polling->on_error(polling->device, rc, polling->arg);
+
+			if (!carry_on || polling->joining) {
+				/* This is user requested abort, erases failures. */
+				failed_attempts = 0;
+				break;
 			}
 			continue;
@@ -142,7 +172,7 @@
 
 		/* We have the data, execute the callback now. */
-		assert(params->on_data);
-		const bool carry_on = params->on_data(
-		    data->dev, data->buffer, actual_size, params->arg);
+		assert(polling->on_data);
+		const bool carry_on = polling->on_data(polling->device,
+		    polling->buffer, actual_size, polling->arg);
 
 		if (!carry_on) {
@@ -156,33 +186,34 @@
 
 		/* Take a rest before next request. */
-		
+
 		// FIXME TODO: This is broken, the time is in ms not us.
 		// but first we need to fix drivers to actually stop using this,
 		// since polling delay should be implemented in HC schedule
-		async_usleep(params->delay);
+		async_usleep(polling->delay);
 	}
 
 	const bool failed = failed_attempts > 0;
 
-	if (params->on_polling_end != NULL) {
-		params->on_polling_end(data->dev, failed, params->arg);
-	}
-
-	if (params->debug > 0) {
+	if (polling->on_polling_end)
+		polling->on_polling_end(polling->device, failed, polling->arg);
+
+	if (polling->debug > 0) {
 		if (failed) {
 			usb_log_error("Polling of device `%s' terminated: "
 			    "recurring failures.\n",
-			    usb_device_get_name(data->dev));
+			    usb_device_get_name(polling->device));
 		} else {
 			usb_log_debug("Polling of device `%s' terminated: "
 			    "driver request.\n",
-			    usb_device_get_name(data->dev));
+			    usb_device_get_name(polling->device));
 		}
 	}
 
-	/* Free the allocated memory. */
-	free(data->buffer);
-	free(data);
-
+	fibril_mutex_lock(&polling->guard);
+	polling->running = false;
+	fibril_mutex_unlock(&polling->guard);
+
+	/* Notify joiners, if any. */
+	fibril_condvar_broadcast(&polling->cv);
 	return EOK;
 }
@@ -198,156 +229,72 @@
  * first request would be executed prior to return from this function).
  *
- * @param dev Device to be periodically polled.
- * @param epm Endpoint mapping to use.
- * @param polling Polling settings.
- * @param request_size How many bytes to ask for in each request.
- * @param arg Custom argument (passed as is to the callbacks).
+ * @param polling Polling data structure.
  * @return Error code.
  * @retval EOK New fibril polling the device was already started.
  */
-static errno_t usb_device_auto_polling_internal(usb_device_t *dev,
-    usb_endpoint_mapping_t *epm, const usb_device_auto_polling_t *polling,
-    size_t request_size)
-{
-	if ((dev == NULL) || (polling == NULL) || (polling->on_data == NULL)) {
+errno_t usb_polling_start(usb_polling_t *polling)
+{
+	if (!polling || !polling->device || !polling->ep_mapping || !polling->on_data)
 		return EBADMEM;
-	}
-
-	if (request_size == 0)
+
+	if (!polling->request_size)
 		return EINVAL;
-	
-	if (!epm || (epm->pipe.transfer_type != USB_TRANSFER_INTERRUPT) ||
-	    (epm->pipe.direction != USB_DIRECTION_IN))
+
+	if (!polling->ep_mapping || (polling->ep_mapping->pipe.desc.transfer_type != USB_TRANSFER_INTERRUPT)
+	    || (polling->ep_mapping->pipe.desc.direction != USB_DIRECTION_IN))
 		return EINVAL;
-	
-
-	polling_data_t *polling_data = malloc(sizeof(polling_data_t));
-	if (polling_data == NULL) {
+
+	/* Negative value means use descriptor provided value. */
+	if (polling->delay < 0)
+		polling->delay = polling->ep_mapping->descriptor->poll_interval;
+
+	polling->fibril = fibril_create(polling_fibril, polling);
+	if (!polling->fibril)
 		return ENOMEM;
-	}
-
-	/* Fill-in the data. */
-	polling_data->buffer = malloc(sizeof(request_size));
-	if (polling_data->buffer == NULL) {
-		free(polling_data);
-		return ENOMEM;
-	}
-	polling_data->request_size = request_size;
-	polling_data->dev = dev;
-	polling_data->polling_mapping = epm;
-
-	/* Copy provided settings. */
-	polling_data->auto_polling = *polling;
-
-	/* Negative value means use descriptor provided value. */
-	if (polling->delay < 0) {
-		polling_data->auto_polling.delay =
-		    epm->descriptor->poll_interval;
-	}
-
-	fid_t fibril = fibril_create(polling_fibril, polling_data);
-	if (fibril == 0) {
-		free(polling_data->buffer);
-		free(polling_data);
-		return ENOMEM;
-	}
-	fibril_add_ready(fibril);
+
+	fibril_add_ready(polling->fibril);
 
 	/* Fibril launched. That fibril will free the allocated data. */
-
 	return EOK;
 }
-/** Start automatic device polling over interrupt in pipe.
- *
- * The polling settings is copied thus it is okay to destroy the structure
- * after this function returns.
- *
- * @warning There is no guarantee when the request to the device
- * will be sent for the first time (it is possible that this
- * first request would be executed prior to return from this function).
- *
- * @param dev Device to be periodically polled.
- * @param pipe_index Index of the endpoint pipe used for polling.
- * @param polling Polling settings.
- * @param req_size How many bytes to ask for in each request.
- * @param arg Custom argument (passed as is to the callbacks).
- * @return Error code.
- * @retval EOK New fibril polling the device was already started.
- */
-errno_t usb_device_auto_polling(usb_device_t *usb_dev, usb_endpoint_t ep,
-    const usb_device_auto_polling_t *polling, size_t req_size)
-{
-	usb_endpoint_mapping_t *epm = usb_device_get_mapped_ep(usb_dev, ep);
-	return usb_device_auto_polling_internal(usb_dev, epm, polling, req_size);
-}
-
-/** Start automatic device polling over interrupt in pipe.
- *
- * @warning It is up to the callback to produce delays between individual
- * requests.
- *
- * @warning There is no guarantee when the request to the device
- * will be sent for the first time (it is possible that this
- * first request would be executed prior to return from this function).
- *
- * @param dev Device to be periodically polled.
- * @param ep Endpoint  used for polling.
- * @param callback Callback when data are available.
- * @param request_size How many bytes to ask for in each request.
- * @param delay NUmber of ms to wait between queries, -1 to use descriptor val.
- * @param terminated_callback Callback when polling is terminated.
- * @param arg Custom argument (passed as is to the callbacks).
- * @return Error code.
- * @retval EOK New fibril polling the device was already started.
- */
-errno_t usb_device_auto_poll(usb_device_t *dev, usb_endpoint_t ep,
-    usb_polling_callback_t callback, size_t request_size, int delay,
-    usb_polling_terminted_callback_t terminated_callback, void *arg)
-{
-	const usb_device_auto_polling_t auto_polling = {
-		.debug = 1,
-		.auto_clear_halt = true,
-		.delay = delay,
-		.max_failures = MAX_FAILED_ATTEMPTS,
-		.on_data = callback,
-		.on_polling_end = terminated_callback,
-		.on_error = NULL,
-		.arg = arg,
-	};
-
-	usb_endpoint_mapping_t *epm = usb_device_get_mapped_ep(dev, ep);
-	return usb_device_auto_polling_internal(
-	    dev, epm, &auto_polling, request_size);
-}
-
-errno_t usb_device_auto_polling_desc(usb_device_t *usb_dev,
-    const usb_endpoint_description_t *desc,
-    const usb_device_auto_polling_t *polling, size_t req_size)
-{
-	usb_endpoint_mapping_t *epm =
-	    usb_device_get_mapped_ep_desc(usb_dev, desc);
-	return usb_device_auto_polling_internal(usb_dev, epm, polling, req_size);
-}
-
-errno_t usb_device_auto_poll_desc(usb_device_t * usb_dev,
-    const usb_endpoint_description_t *desc, usb_polling_callback_t callback,
-    size_t req_size, int delay,
-    usb_polling_terminted_callback_t terminated_callback, void *arg)
-{
-	const usb_device_auto_polling_t auto_polling = {
-		.debug = 1,
-		.auto_clear_halt = true,
-		.delay = delay,
-		.max_failures = MAX_FAILED_ATTEMPTS,
-		.on_data = callback,
-		.on_polling_end = terminated_callback,
-		.on_error = NULL,
-		.arg = arg,
-	};
-
-	usb_endpoint_mapping_t *epm =
-	    usb_device_get_mapped_ep_desc(usb_dev, desc);
-	return usb_device_auto_polling_internal(
-	    usb_dev, epm, &auto_polling, req_size);
+
+/** Close the polling pipe permanently and synchronously wait
+ *  until the automatic polling fibril terminates.
+ *
+ *  It is safe to deallocate the polling data structure (and its
+ *  data buffer) only after a successful call to this function.
+ *
+ *  @warning Call to this function will trigger execution of the
+ *  on_error() callback with EINTR error code.
+ *
+ *  @parram polling Polling data structure.
+ *  @return Error code.
+ *  @retval EOK Polling fibril has been successfully terminated.
+ */
+errno_t usb_polling_join(usb_polling_t *polling)
+{
+	errno_t rc;
+	if (!polling)
+		return EBADMEM;
+
+	/* Check if the fibril already terminated. */
+	if (!polling->running)
+		return EOK;
+
+	/* Set the flag */
+	polling->joining = true;
+
+	/* Unregister the pipe. */
+	rc = usb_device_unmap_ep(polling->ep_mapping);
+	if (rc != EOK && rc != ENOENT && rc != EHANGUP)
+		return rc;
+
+	/* Wait for the fibril to terminate. */
+	fibril_mutex_lock(&polling->guard);
+	while (polling->running)
+		fibril_condvar_wait(&polling->cv, &polling->guard);
+	fibril_mutex_unlock(&polling->guard);
+
+	return EOK;
 }
 
Index: uspace/lib/usbdev/src/dp.c
===================================================================
--- uspace/lib/usbdev/src/dp.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbdev/src/dp.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -62,4 +62,5 @@
 	NESTING(CONFIGURATION, INTERFACE),
 	NESTING(INTERFACE, ENDPOINT),
+	NESTING(ENDPOINT, SSPEED_EP_COMPANION),
 	NESTING(INTERFACE, HUB),
 	NESTING(INTERFACE, HID),
Index: uspace/lib/usbdev/src/driver.c
===================================================================
--- uspace/lib/usbdev/src/driver.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbdev/src/driver.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -62,5 +62,5 @@
 	errno_t rc = usb_device_create_ddf(gen_dev, driver->endpoints, &err_msg);
 	if (rc != EOK) {
-		usb_log_error("USB device `%s' init failed (%s): %s.\n",
+		usb_log_error("USB device `%s' init failed (%s): %s.",
 		    ddf_dev_get_name(gen_dev), err_msg, str_error(rc));
 		return rc;
@@ -85,11 +85,13 @@
 	assert(driver);
 	assert(driver->ops);
-	if (driver->ops->device_rem == NULL)
+	if (driver->ops->device_remove == NULL)
 		return ENOTSUP;
+
 	/* Just tell the driver to stop whatever it is doing */
 	usb_device_t *usb_dev = ddf_dev_data_get(gen_dev);
-	const errno_t ret = driver->ops->device_rem(usb_dev);
+	const errno_t ret = driver->ops->device_remove(usb_dev);
 	if (ret != EOK)
 		return ret;
+
 	usb_device_destroy_ddf(gen_dev);
 	return EOK;
@@ -117,8 +119,42 @@
 }
 
+/** Callback when the driver is asked to online a specific function.
+ *
+ * This callback is a wrapper for USB specific version of @c fun_online.
+ *
+ * @param gen_dev Device function structure as prepared by DDF.
+ * @return Error code.
+ */
+static int generic_function_online(ddf_fun_t *fun)
+{
+	assert(driver);
+	assert(driver->ops);
+	if (driver->ops->function_online == NULL)
+		return ENOTSUP;
+	return driver->ops->function_online(fun);
+}
+
+/** Callback when the driver is asked to offline a specific function.
+ *
+ * This callback is a wrapper for USB specific version of @c fun_offline.
+ *
+ * @param gen_dev Device function structure as prepared by DDF.
+ * @return Error code.
+ */
+static int generic_function_offline(ddf_fun_t *fun)
+{
+	assert(driver);
+	assert(driver->ops);
+	if (driver->ops->function_offline == NULL)
+		return ENOTSUP;
+	return driver->ops->function_offline(fun);
+}
+
 static driver_ops_t generic_driver_ops = {
 	.dev_add = generic_device_add,
 	.dev_remove = generic_device_remove,
 	.dev_gone = generic_device_gone,
+	.fun_online = generic_function_online,
+	.fun_offline = generic_function_offline,
 };
 static driver_t generic_driver = {
Index: uspace/lib/usbdev/src/pipes.c
===================================================================
--- uspace/lib/usbdev/src/pipes.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbdev/src/pipes.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -36,5 +36,4 @@
 #include <usb/dev/request.h>
 #include <usb/usb.h>
-#include <usb_iface.h>
 
 #include <assert.h>
@@ -51,5 +50,5 @@
 	assert(pipe != NULL);
 
-	if (!pipe->auto_reset_halt || (pipe->endpoint_no != 0)) {
+	if (!pipe->auto_reset_halt || (pipe->desc.endpoint_no != 0)) {
 		return;
 	}
@@ -57,5 +56,5 @@
 	/* Prevent infinite recursion. */
 	pipe->auto_reset_halt = false;
-	usb_request_clear_endpoint_halt(pipe, 0);
+	usb_pipe_clear_halt(pipe, pipe);
 	pipe->auto_reset_halt = true;
 }
@@ -70,11 +69,11 @@
  * @param[out] data_buffer Buffer for incoming data.
  * @param[in] data_buffer_size Size of the buffer for incoming data (in bytes).
- * @param[out] data_transfered_size Number of bytes that were actually
- *                                  transfered during the DATA stage.
+ * @param[out] data_transferred_size Number of bytes that were actually
+ *                                  transferred during the DATA stage.
  * @return Error code.
  */
 errno_t usb_pipe_control_read(usb_pipe_t *pipe,
     const void *setup_buffer, size_t setup_buffer_size,
-    void *buffer, size_t buffer_size, size_t *transfered_size)
+    void *buffer, size_t buffer_size, size_t *transferred_size)
 {
 	assert(pipe);
@@ -88,6 +87,6 @@
 	}
 
-	if ((pipe->direction != USB_DIRECTION_BOTH)
-	    || (pipe->transfer_type != USB_TRANSFER_CONTROL)) {
+	if ((pipe->desc.direction != USB_DIRECTION_BOTH)
+	    || (pipe->desc.transfer_type != USB_TRANSFER_CONTROL)) {
 		return EBADF;
 	}
@@ -98,5 +97,5 @@
 	async_exch_t *exch = async_exchange_begin(pipe->bus_session);
 	size_t act_size = 0;
-	const errno_t rc = usb_read(exch, pipe->endpoint_no, setup_packet, buffer,
+	const errno_t rc = usbhc_read(exch, pipe->desc.endpoint_no, setup_packet, buffer,
 	    buffer_size, &act_size);
 	async_exchange_end(exch);
@@ -106,6 +105,6 @@
 	}
 
-	if (rc == EOK && transfered_size != NULL) {
-		*transfered_size = act_size;
+	if (rc == EOK && transferred_size != NULL) {
+		*transferred_size = act_size;
 	}
 
@@ -142,6 +141,6 @@
 	}
 
-	if ((pipe->direction != USB_DIRECTION_BOTH)
-	    || (pipe->transfer_type != USB_TRANSFER_CONTROL)) {
+	if ((pipe->desc.direction != USB_DIRECTION_BOTH)
+	    || (pipe->desc.transfer_type != USB_TRANSFER_CONTROL)) {
 		return EBADF;
 	}
@@ -151,6 +150,6 @@
 
 	async_exch_t *exch = async_exchange_begin(pipe->bus_session);
-	const errno_t rc = usb_write(exch,
-	    pipe->endpoint_no, setup_packet, buffer, buffer_size);
+	const errno_t rc = usbhc_write(exch,
+	    pipe->desc.endpoint_no, setup_packet, buffer, buffer_size);
 	async_exchange_end(exch);
 
@@ -167,9 +166,9 @@
  * @param[out] buffer Buffer where to store the data.
  * @param[in] size Size of the buffer (in bytes).
- * @param[out] size_transfered Number of bytes that were actually transfered.
+ * @param[out] size_transferred Number of bytes that were actually transferred.
  * @return Error code.
  */
 errno_t usb_pipe_read(usb_pipe_t *pipe,
-    void *buffer, size_t size, size_t *size_transfered)
+    void *buffer, size_t size, size_t *size_transferred)
 {
 	assert(pipe);
@@ -183,25 +182,20 @@
 	}
 
-	if (pipe->direction != USB_DIRECTION_IN) {
-		return EBADF;
-	}
-
-	if (pipe->transfer_type == USB_TRANSFER_CONTROL) {
-		return EBADF;
-	}
-
-	/* Isochronous transfer are not supported (yet) */
-	if (pipe->transfer_type != USB_TRANSFER_INTERRUPT &&
-	    pipe->transfer_type != USB_TRANSFER_BULK)
-	    return ENOTSUP;
+	if (pipe->desc.direction != USB_DIRECTION_IN) {
+		return EBADF;
+	}
+
+	if (pipe->desc.transfer_type == USB_TRANSFER_CONTROL) {
+		return EBADF;
+	}
 
 	async_exch_t *exch = async_exchange_begin(pipe->bus_session);
 	size_t act_size = 0;
 	const errno_t rc =
-	    usb_read(exch, pipe->endpoint_no, 0, buffer, size, &act_size);
-	async_exchange_end(exch);
-
-	if (rc == EOK && size_transfered != NULL) {
-		*size_transfered = act_size;
+	    usbhc_read(exch, pipe->desc.endpoint_no, 0, buffer, size, &act_size);
+	async_exchange_end(exch);
+
+	if (rc == EOK && size_transferred != NULL) {
+		*size_transferred = act_size;
 	}
 
@@ -224,19 +218,14 @@
 	}
 
-	if (pipe->direction != USB_DIRECTION_OUT) {
-		return EBADF;
-	}
-
-	if (pipe->transfer_type == USB_TRANSFER_CONTROL) {
-		return EBADF;
-	}
-
-	/* Isochronous transfer are not supported (yet) */
-	if (pipe->transfer_type != USB_TRANSFER_INTERRUPT &&
-	    pipe->transfer_type != USB_TRANSFER_BULK)
-	    return ENOTSUP;
-
-	async_exch_t *exch = async_exchange_begin(pipe->bus_session);
-	const errno_t rc = usb_write(exch, pipe->endpoint_no, 0, buffer, size);
+	if (pipe->desc.direction != USB_DIRECTION_OUT) {
+		return EBADF;
+	}
+
+	if (pipe->desc.transfer_type == USB_TRANSFER_CONTROL) {
+		return EBADF;
+	}
+
+	async_exch_t *exch = async_exchange_begin(pipe->bus_session);
+	const errno_t rc = usbhc_write(exch, pipe->desc.endpoint_no, 0, buffer, size);
 	async_exchange_end(exch);
 	return rc;
@@ -246,21 +235,11 @@
  *
  * @param pipe Endpoint pipe to be initialized.
- * @param endpoint_no Endpoint number (in USB 1.1 in range 0 to 15).
- * @param transfer_type Transfer type (e.g. interrupt or bulk).
- * @param max_packet_size Maximum packet size in bytes.
- * @param direction Endpoint direction (in/out).
- * @return Error code.
- */
-errno_t usb_pipe_initialize(usb_pipe_t *pipe, usb_endpoint_t endpoint_no,
-    usb_transfer_type_t transfer_type, size_t max_packet_size,
-    usb_direction_t direction, unsigned packets, usb_dev_session_t *bus_session)
-{
-	assert(pipe);
-
-	pipe->endpoint_no = endpoint_no;
-	pipe->transfer_type = transfer_type;
-	pipe->packets = packets;
-	pipe->max_packet_size = max_packet_size;
-	pipe->direction = direction;
+ * @param bus_session Endpoint pipe to be initialized.
+ * @return Error code.
+ */
+errno_t usb_pipe_initialize(usb_pipe_t *pipe, usb_dev_session_t *bus_session)
+{
+	assert(pipe);
+
 	pipe->auto_reset_halt = false;
 	pipe->bus_session = bus_session;
@@ -269,20 +248,29 @@
 }
 
-/** Initialize USB endpoint pipe as the default zero control pipe.
+static const usb_pipe_desc_t default_control_pipe = {
+	.endpoint_no = 0,
+	.transfer_type = USB_TRANSFER_CONTROL,
+	.direction = USB_DIRECTION_BOTH,
+	.max_transfer_size = CTRL_PIPE_MIN_PACKET_SIZE,
+};
+
+/** Initialize USB default control pipe.
+ *
+ * This one is special because it must not be registered, it is registered automatically.
  *
  * @param pipe Endpoint pipe to be initialized.
- * @return Error code.
- */
-errno_t usb_pipe_initialize_default_control(usb_pipe_t *pipe,
-    usb_dev_session_t *bus_session)
-{
-	assert(pipe);
-
-	const errno_t rc = usb_pipe_initialize(pipe, 0, USB_TRANSFER_CONTROL,
-	    CTRL_PIPE_MIN_PACKET_SIZE, USB_DIRECTION_BOTH, 1, bus_session);
-
+ * @param bus_session Endpoint pipe to be initialized.
+ * @return Error code.
+ */
+errno_t usb_pipe_initialize_default_control(usb_pipe_t *pipe, usb_dev_session_t *bus_session)
+{
+	const errno_t ret = usb_pipe_initialize(pipe, bus_session);
+	if (ret)
+		return ret;
+
+	pipe->desc = default_control_pipe;
 	pipe->auto_reset_halt = true;
 
-	return rc;
+	return EOK;
 }
 
@@ -290,17 +278,36 @@
  *
  * @param pipe Pipe to be registered.
- * @param interval Polling interval.
- * @return Error code.
- */
-errno_t usb_pipe_register(usb_pipe_t *pipe, unsigned interval)
+ * @param ep_desc Matched endpoint descriptor
+ * @param comp_desc Matched superspeed companion descriptro, if any
+ * @return Error code.
+ */
+errno_t usb_pipe_register(usb_pipe_t *pipe, const usb_standard_endpoint_descriptor_t *ep_desc, const usb_superspeed_endpoint_companion_descriptor_t *comp_desc)
 {
 	assert(pipe);
 	assert(pipe->bus_session);
+	assert(ep_desc);
+
 	async_exch_t *exch = async_exchange_begin(pipe->bus_session);
 	if (!exch)
 		return ENOMEM;
-	const errno_t ret = usb_register_endpoint(exch, pipe->endpoint_no,
-	    pipe->transfer_type, pipe->direction, pipe->max_packet_size,
-	    pipe->packets, interval);
+
+	usb_endpoint_descriptors_t descriptors;
+
+#define COPY(field) descriptors.endpoint.field = ep_desc->field
+	COPY(endpoint_address);
+	COPY(attributes);
+	COPY(max_packet_size);
+	COPY(poll_interval);
+#undef COPY
+
+#define COPY(field) descriptors.companion.field = comp_desc->field
+	if (comp_desc) {
+		COPY(max_burst);
+		COPY(attributes);
+		COPY(bytes_per_interval);
+	}
+#undef COPY
+
+	const errno_t ret = usbhc_register_endpoint(exch, &pipe->desc, &descriptors);
 	async_exchange_end(exch);
 	return ret;
@@ -319,6 +326,7 @@
 	if (!exch)
 		return ENOMEM;
-	const errno_t ret = usb_unregister_endpoint(exch, pipe->endpoint_no,
-	    pipe->direction);
+
+	const errno_t ret = usbhc_unregister_endpoint(exch, &pipe->desc);
+
 	async_exchange_end(exch);
 	return ret;
Index: uspace/lib/usbdev/src/pipesinit.c
===================================================================
--- uspace/lib/usbdev/src/pipesinit.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbdev/src/pipesinit.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -38,4 +38,5 @@
 #include <usb/dev/request.h>
 #include <usb/usb.h>
+#include <usb/debug.h>
 #include <usb/descriptor.h>
 
@@ -59,4 +60,5 @@
 	NESTING(INTERFACE, HID),
 	NESTING(HID, HID_REPORT),
+	NESTING(ENDPOINT, SSPEED_EP_COMPANION),
 	LAST_NESTING
 };
@@ -70,4 +72,14 @@
 {
 	return descriptor[1] == USB_DESCTYPE_ENDPOINT;
+}
+
+/** Tells whether given descriptor is of superspeed companion type.
+ *
+ * @param descriptor Descriptor in question.
+ * @return Whether the given descriptor is superspeed companion descriptor.
+ */
+static inline bool is_superspeed_companion_descriptor(const uint8_t *descriptor)
+{
+	return descriptor[1] == USB_DESCTYPE_SSPEED_EP_COMPANION;
 }
 
@@ -150,4 +162,5 @@
  * @param interface Interface descriptor under which belongs the @p endpoint.
  * @param endpoint Endpoint descriptor.
+ * @param companion Superspeed companion descriptor.
  * @return Error code.
  */
@@ -156,4 +169,5 @@
     usb_standard_interface_descriptor_t *interface,
     usb_standard_endpoint_descriptor_t *endpoint_desc,
+    usb_superspeed_endpoint_companion_descriptor_t *companion_desc,
     usb_dev_session_t *bus_session)
 {
@@ -162,15 +176,7 @@
 	 * Get endpoint characteristics.
 	 */
-
-	/* Actual endpoint number is in bits 0..3 */
-	const usb_endpoint_t ep_no = endpoint_desc->endpoint_address & 0x0F;
-
 	const usb_endpoint_description_t description = {
-		/* Endpoint direction is set by bit 7 */
-		.direction = (endpoint_desc->endpoint_address & 128)
-		    ? USB_DIRECTION_IN : USB_DIRECTION_OUT,
-		/* Transfer type is in bits 0..2 and
-		 * the enum values corresponds 1:1 */
-		.transfer_type = endpoint_desc->attributes & 3,
+		.transfer_type = USB_ED_GET_TRANSFER_TYPE(*endpoint_desc),
+		.direction = USB_ED_GET_DIR(*endpoint_desc),
 
 		/* Get interface characteristics. */
@@ -194,17 +200,11 @@
 	}
 
-	errno_t rc = usb_pipe_initialize(&ep_mapping->pipe,
-	    ep_no, description.transfer_type,
-	    ED_MPS_PACKET_SIZE_GET(
-	        uint16_usb2host(endpoint_desc->max_packet_size)),
-	    description.direction,
-	    ED_MPS_TRANS_OPPORTUNITIES_GET(
-	        uint16_usb2host(endpoint_desc->max_packet_size)), bus_session);
-	if (rc != EOK) {
-		return rc;
-	}
+	errno_t err = usb_pipe_initialize(&ep_mapping->pipe, bus_session);
+	if (err)
+		return err;
 
 	ep_mapping->present = true;
 	ep_mapping->descriptor = endpoint_desc;
+	ep_mapping->companion_descriptor = companion_desc;
 	ep_mapping->interface = interface;
 
@@ -235,4 +235,12 @@
 	do {
 		if (is_endpoint_descriptor(descriptor)) {
+			/* Check if companion descriptor is present too, it should immediatelly follow. */
+			const uint8_t *companion_desc = usb_dp_get_nested_descriptor(parser,
+				parser_data, descriptor);
+			if (companion_desc && !is_superspeed_companion_descriptor(companion_desc)) {
+				/* Not what we wanted, don't pass it further. */
+				companion_desc = NULL;
+			}
+
 			(void) process_endpoint(mapping, mapping_count,
 			    (usb_standard_interface_descriptor_t *)
@@ -240,4 +248,6 @@
 			    (usb_standard_endpoint_descriptor_t *)
 			        descriptor,
+			    (usb_superspeed_endpoint_companion_descriptor_t *)
+			        companion_desc,
 			    bus_session);
 		}
@@ -288,5 +298,5 @@
 	if (config_descriptor == NULL)
 		return EBADMEM;
-	
+
 	if (config_descriptor_size <
 	    sizeof(usb_standard_configuration_descriptor_t)) {
@@ -328,51 +338,4 @@
 }
 
-/** Probe default control pipe for max packet size.
- *
- * The function tries to get the correct value of max packet size several
- * time before giving up.
- *
- * The session on the pipe shall not be started.
- *
- * @param pipe Default control pipe.
- * @return Error code.
- */
-errno_t usb_pipe_probe_default_control(usb_pipe_t *pipe)
-{
-	assert(pipe);
-	static_assert(DEV_DESCR_MAX_PACKET_SIZE_OFFSET < CTRL_PIPE_MIN_PACKET_SIZE);
-
-	if ((pipe->direction != USB_DIRECTION_BOTH) ||
-	    (pipe->transfer_type != USB_TRANSFER_CONTROL) ||
-	    (pipe->endpoint_no != 0)) {
-		return EINVAL;
-	}
-
-	uint8_t dev_descr_start[CTRL_PIPE_MIN_PACKET_SIZE];
-	size_t transferred_size;
-	errno_t rc;
-	for (size_t attempt_var = 0; attempt_var < 3; ++attempt_var) {
-		rc = usb_request_get_descriptor(pipe, USB_REQUEST_TYPE_STANDARD,
-		    USB_REQUEST_RECIPIENT_DEVICE, USB_DESCTYPE_DEVICE,
-		    0, 0, dev_descr_start, CTRL_PIPE_MIN_PACKET_SIZE,
-		    &transferred_size);
-		if (rc == EOK) {
-			if (transferred_size != CTRL_PIPE_MIN_PACKET_SIZE) {
-				rc = ELIMIT;
-				continue;
-			}
-			break;
-		}
-	}
-	if (rc != EOK) {
-		return rc;
-	}
-
-	pipe->max_packet_size
-	    = dev_descr_start[DEV_DESCR_MAX_PACKET_SIZE_OFFSET];
-
-	return EOK;
-}
-
 /**
  * @}
Index: uspace/lib/usbdev/src/request.c
===================================================================
--- uspace/lib/usbdev/src/request.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbdev/src/request.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -118,5 +118,5 @@
  * @param data_size        Size of the @p data buffer
  *                         (in native endianness).
- * @param actual_data_size Actual size of transfered data
+ * @param actual_data_size Actual size of transferred data
  *                         (in native endianness).
  *
@@ -183,12 +183,12 @@
 
 	uint16_t status_usb_endianess;
-	size_t data_transfered_size;
+	size_t data_transferred_size;
 	errno_t rc = usb_control_request_get(pipe, USB_REQUEST_TYPE_STANDARD,
 	    recipient, USB_DEVREQ_GET_STATUS, 0, uint16_host2usb(index),
-	    &status_usb_endianess, 2, &data_transfered_size);
-	if (rc != EOK) {
-		return rc;
-	}
-	if (data_transfered_size != 2) {
+	    &status_usb_endianess, 2, &data_transferred_size);
+	if (rc != EOK) {
+		return rc;
+	}
+	if (data_transferred_size != 2) {
 		return ELIMIT;
 	}
@@ -314,12 +314,12 @@
 	 */
 	uint8_t tmp_buffer;
-	size_t bytes_transfered;
+	size_t bytes_transferred;
 	rc = usb_request_get_descriptor(pipe, request_type, recipient,
 	    descriptor_type, descriptor_index, language,
-	    &tmp_buffer, sizeof(tmp_buffer), &bytes_transfered);
-	if (rc != EOK) {
-		return rc;
-	}
-	if (bytes_transfered != 1) {
+	    &tmp_buffer, sizeof(tmp_buffer), &bytes_transferred);
+	if (rc != EOK) {
+		return rc;
+	}
+	if (bytes_transferred != 1) {
 		return ELIMIT;
 	}
@@ -340,10 +340,10 @@
 	rc = usb_request_get_descriptor(pipe, request_type, recipient,
 	    descriptor_type, descriptor_index, language,
-	    buffer, size, &bytes_transfered);
+	    buffer, size, &bytes_transferred);
 	if (rc != EOK) {
 		free(buffer);
 		return rc;
 	}
-	if (bytes_transfered != size) {
+	if (bytes_transferred != size) {
 		free(buffer);
 		return ELIMIT;
@@ -824,5 +824,5 @@
  * @return Error code.
  */
-errno_t usb_request_clear_endpoint_halt(usb_pipe_t *pipe, uint16_t ep_index)
+static errno_t usb_request_clear_endpoint_halt(usb_pipe_t *pipe, uint16_t ep_index)
 {
 	return usb_request_clear_feature(pipe,
@@ -843,6 +843,8 @@
 		return EINVAL;
 	}
-	return usb_request_clear_endpoint_halt(ctrl_pipe,
-	    target_pipe->endpoint_no);
+
+	uint16_t index = target_pipe->desc.endpoint_no;
+	index |= (target_pipe->desc.direction == USB_DIRECTION_IN) << 7;
+	return usb_request_clear_endpoint_halt(ctrl_pipe, index);
 }
 
@@ -858,5 +860,5 @@
 {
 	uint16_t status_tmp;
-	uint16_t pipe_index = (uint16_t) pipe->endpoint_no;
+	uint16_t pipe_index = (uint16_t) pipe->desc.endpoint_no;
 	errno_t rc = usb_request_get_status(ctrl_pipe,
 	    USB_REQUEST_RECIPIENT_ENDPOINT, uint16_host2usb(pipe_index),
Index: uspace/lib/usbhid/src/hiddescriptor.c
===================================================================
--- uspace/lib/usbhid/src/hiddescriptor.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbhid/src/hiddescriptor.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -176,5 +176,5 @@
 
 	if(report_item->usages_count > 0){
-		usages = malloc(sizeof(int32_t) * report_item->usages_count);
+		usages = malloc(sizeof(uint32_t) * report_item->usages_count);
 		memcpy(usages, report_item->usages, sizeof(int32_t) *
 				report_item->usages_count); 
@@ -247,19 +247,5 @@
 		field->size = report_item->size;
 
-		if(report_item->type == USB_HID_REPORT_TYPE_INPUT) {
-			int offset = report_item->offset + report_item->size * i;
-			int field_offset = (offset/8)*8 + (offset/8 + 1) * 8 - 
-				offset - report_item->size;
-			if(field_offset < 0) {
-				field->offset = 0;
-			}
-			else {
-				field->offset = field_offset;
-			}
-		}
-		else {
-			field->offset = report_item->offset + (i * report_item->size);
-		}
-
+		field->offset = report_item->offset + (i * report_item->size);
 
 		if(report->use_report_ids != 0) {
@@ -896,5 +882,5 @@
 {
 	if(list == NULL || list_empty(list)) {
-	    usb_log_debug("\tempty\n");
+	    usb_log_debug("\tempty");
 	    return;
 	}
@@ -902,28 +888,26 @@
         list_foreach(*list, ritems_link, usb_hid_report_field_t,
     	    report_item) {
-		usb_log_debug("\t\tOFFSET: %X\n", report_item->offset);
-		usb_log_debug("\t\tSIZE: %zu\n", report_item->size);
-		usb_log_debug("\t\tLOGMIN: %d\n",
+		usb_log_debug("\t\tOFFSET: %u", report_item->offset);
+		usb_log_debug("\t\tSIZE: %zu", report_item->size);
+		usb_log_debug("\t\tLOGMIN: %d",
 			report_item->logical_minimum);
-		usb_log_debug("\t\tLOGMAX: %d\n",
+		usb_log_debug("\t\tLOGMAX: %d",
 			report_item->logical_maximum);
-		usb_log_debug("\t\tPHYMIN: %d\n",
+		usb_log_debug("\t\tPHYMIN: %d",
 			report_item->physical_minimum);
-		usb_log_debug("\t\tPHYMAX: %d\n",
+		usb_log_debug("\t\tPHYMAX: %d",
 			report_item->physical_maximum);
-		usb_log_debug("\t\ttUSAGEMIN: %X\n",
+		usb_log_debug("\t\ttUSAGEMIN: %X",
 			report_item->usage_minimum);
-		usb_log_debug("\t\tUSAGEMAX: %X\n",
+		usb_log_debug("\t\tUSAGEMAX: %X",
 			       report_item->usage_maximum);
-		usb_log_debug("\t\tUSAGES COUNT: %zu\n",
+		usb_log_debug("\t\tUSAGES COUNT: %zu",
 			report_item->usages_count);
 
-		usb_log_debug("\t\tVALUE: %X\n", report_item->value);
-		usb_log_debug("\t\ttUSAGE: %X\n", report_item->usage);
-		usb_log_debug("\t\tUSAGE PAGE: %X\n", report_item->usage_page);
+		usb_log_debug("\t\tVALUE: %X", report_item->value);
+		usb_log_debug("\t\ttUSAGE: %X", report_item->usage);
+		usb_log_debug("\t\tUSAGE PAGE: %X", report_item->usage_page);
 
 		usb_hid_print_usage_path(report_item->collection_path);
-
-		usb_log_debug("\n");
 	}
 }
@@ -943,12 +927,12 @@
 	list_foreach(report->reports, reports_link,
 	    usb_hid_report_description_t, report_des) {
-		usb_log_debug("Report ID: %d\n", report_des->report_id);
-		usb_log_debug("\tType: %d\n", report_des->type);
-		usb_log_debug("\tLength: %zu\n", report_des->bit_length);
-		usb_log_debug("\tB Size: %zu\n",
+		usb_log_debug("Report ID: %d", report_des->report_id);
+		usb_log_debug("\tType: %d", report_des->type);
+		usb_log_debug("\tLength: %zu", report_des->bit_length);
+		usb_log_debug("\tB Size: %zu",
 			usb_hid_report_byte_size(report,
 				report_des->report_id,
 				report_des->type));
-		usb_log_debug("\tItems: %zu\n", report_des->item_length);
+		usb_log_debug("\tItems: %zu", report_des->item_length);
 
 		usb_hid_descriptor_print_list(&report_des->report_items);
Index: uspace/lib/usbhid/src/hidparser.c
===================================================================
--- uspace/lib/usbhid/src/hidparser.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbhid/src/hidparser.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -40,4 +40,6 @@
 #include <usb/debug.h>
 #include <assert.h>
+#include <bitops.h>
+#include <macros.h>
 
 
@@ -199,12 +201,4 @@
 int usb_hid_translate_data(usb_hid_report_field_t *item, const uint8_t *data)
 {
-	int resolution;
-	int offset;
-	int part_size;
-	
-	int32_t value = 0;
-	int32_t mask = 0;
-	const uint8_t *foo = 0;
-
 	/* now only short tags are allowed */
 	if (item->size > 32) {
@@ -214,51 +208,44 @@
 	if ((item->physical_minimum == 0) && (item->physical_maximum == 0)) {
 		item->physical_minimum = item->logical_minimum;
-		item->physical_maximum = item->logical_maximum;			
-	}
-	
-
+		item->physical_maximum = item->logical_maximum;
+	}
+
+	int resolution;
 	if (item->physical_maximum == item->physical_minimum) {
 	    resolution = 1;
 	} else {
-	    resolution = (item->logical_maximum - item->logical_minimum) / 
-		((item->physical_maximum - item->physical_minimum) * 
+	    resolution = (item->logical_maximum - item->logical_minimum) /
+		((item->physical_maximum - item->physical_minimum) *
 		(usb_pow(10, (item->unit_exponent))));
 	}
 
-	offset = item->offset;
-	// FIXME
-	if ((size_t) (offset / 8) != (size_t) ((offset+item->size - 1) / 8)) {
-		
-		part_size = 0;
-
-		size_t i = 0;
-		for (i = (size_t) (offset / 8);
-		    i <= (size_t) (offset + item->size - 1) / 8; i++) {
-			if (i == (size_t) (offset / 8)) {
-				/* the higher one */
-				part_size = 8 - (offset % 8);
-				foo = data + i;
-				mask =  ((1 << (item->size - part_size)) - 1);
-				value = (*foo & mask);
-			} else if (i == ((offset + item->size - 1) / 8)) {
-				/* the lower one */
-				foo = data + i;
-				mask = ((1 << (item->size - part_size)) - 1) <<
-				    (8 - (item->size - part_size));
-
-				value = (((*foo & mask) >> (8 - 
-				    (item->size - part_size))) << part_size) + 
-				    value;
-			} else {
-				value = (*(data + 1) << (part_size + 8)) +
-				    value;
-				part_size += 8;
-			}
-		}
-	} else {		
-		foo = data + (offset / 8);
-		mask = ((1 << item->size) - 1) <<
-		    (8 - ((offset % 8) + item->size));
-		value = (*foo & mask) >> (8 - ((offset % 8) + item->size));
+	int32_t value = 0;
+
+	/* First, skip all bytes we don't care */
+	data += item->offset / 8;
+
+	int bits = item->size;
+	int taken = 0;
+
+	/* Than we take the higher bits from the LSB */
+	const unsigned bit_offset = item->offset % 8;
+	const int lsb_bits = min(bits, 8);
+
+	value |= (*data >> bit_offset) & BIT_RRANGE(uint8_t, lsb_bits);
+	bits -= lsb_bits;
+	taken += lsb_bits;
+	data++;
+
+	/* Then there may be bytes, which we take as a whole. */
+	while (bits > 8) {
+		value |= *data << taken;
+		taken += 8;
+		bits -= 8;
+		data++;
+	}
+
+	/* And, finally, lower bits from HSB. */
+	if (bits > 0) {
+		value |= (*data & BIT_RRANGE(uint8_t, bits)) << taken;
 	}
 
@@ -366,5 +353,5 @@
 		length = report_item->size;
 		
-		usb_log_debug("\ttranslated value: %x\n", value);
+		usb_log_debug("\ttranslated value: %x", value);
 
 		if ((offset / 8) == ((offset + length - 1) / 8)) {
@@ -437,5 +424,5 @@
 
 	if (USB_HID_ITEM_FLAG_CONSTANT(item->item_flags)) {
-		ret = item->logical_minimum;
+		return item->logical_minimum;
 	}
 
Index: uspace/lib/usbhid/src/hidpath.c
===================================================================
--- uspace/lib/usbhid/src/hidpath.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbhid/src/hidpath.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -173,13 +173,13 @@
 void usb_hid_print_usage_path(usb_hid_report_path_t *path)
 {
-	usb_log_debug("USAGE_PATH FOR RId(%d):\n", path->report_id);
-	usb_log_debug("\tLENGTH: %d\n", path->depth);
+	usb_log_debug("USAGE_PATH FOR RId(%d):", path->report_id);
+	usb_log_debug("\tLENGTH: %d", path->depth);
 
 	list_foreach(path->items, rpath_items_link,
 	    usb_hid_report_usage_path_t, path_item) {
 
-		usb_log_debug("\tUSAGE_PAGE: %X\n", path_item->usage_page);
-		usb_log_debug("\tUSAGE: %X\n", path_item->usage);
-		usb_log_debug("\tFLAGS: %d\n", path_item->flags);
+		usb_log_debug("\tUSAGE_PAGE: %X", path_item->usage_page);
+		usb_log_debug("\tUSAGE: %X", path_item->usage);
+		usb_log_debug("\tFLAGS: %d", path_item->flags);
 	}
 }
Index: uspace/lib/usbhid/src/hidreport.c
===================================================================
--- uspace/lib/usbhid/src/hidreport.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbhid/src/hidreport.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -84,5 +84,5 @@
 	
 	if (d == NULL) {
-		usb_log_error("The %d. interface descriptor not found!\n",
+		usb_log_error("The %d. interface descriptor not found!",
 		    usb_device_get_iface_number(dev));
 		return ENOENT;
@@ -104,5 +104,5 @@
 	
 	if (d == NULL) {
-		usb_log_fatal("No HID descriptor found!\n");
+		usb_log_fatal("No HID descriptor found!");
 		return ENOENT;
 	}
@@ -130,5 +130,5 @@
 	}
 	
-	usb_log_debug("Getting Report descriptor, expected size: %u\n", length);
+	usb_log_debug("Getting Report descriptor, expected size: %u", length);
 	
 	/*
@@ -156,5 +156,5 @@
 	*size = length;
 	
-	usb_log_debug("Done.\n");
+	usb_log_debug("Done.");
 	
 	return EOK;
@@ -178,5 +178,5 @@
 	
 	if (rc != EOK) {
-		usb_log_error("Problem with getting Report descriptor: %s.\n",
+		usb_log_error("Problem with getting Report descriptor: %s.",
 		    str_error(rc));
 		if (*report_desc != NULL) {
@@ -191,5 +191,5 @@
 	rc = usb_hid_parse_report_descriptor(report, *report_desc, *report_size);
 	if (rc != EOK) {
-		usb_log_error("Problem parsing Report descriptor: %s.\n",
+		usb_log_error("Problem parsing Report descriptor: %s.",
 		    str_error(rc));
 		free(*report_desc);
Index: uspace/lib/usbhid/src/hidreq.c
===================================================================
--- uspace/lib/usbhid/src/hidreq.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbhid/src/hidreq.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -62,5 +62,5 @@
 {
 	if (ctrl_pipe == NULL) {
-		usb_log_warning("usbhid_req_set_report(): no pipe given.\n");
+		usb_log_warning("usbhid_req_set_report(): no pipe given.");
 		return EINVAL;
 	}
@@ -82,5 +82,5 @@
 	value |= (type << 8);
 
-	usb_log_debug("Sending Set Report request to the device.\n");
+	usb_log_debug("Sending Set Report request to the device.");
 	
 	rc = usb_control_request_set(ctrl_pipe,
@@ -112,5 +112,5 @@
 {
 	if (ctrl_pipe == NULL) {
-		usb_log_warning("usbhid_req_set_report(): no pipe given.\n");
+		usb_log_warning("usbhid_req_set_report(): no pipe given.");
 		return EINVAL;
 	}
@@ -160,5 +160,5 @@
 {
 	if (ctrl_pipe == NULL) {
-		usb_log_warning("usbhid_req_set_report(): no pipe given.\n");
+		usb_log_warning("usbhid_req_set_report(): no pipe given.");
 		return EINVAL;
 	}
@@ -215,5 +215,5 @@
 {
 	if (ctrl_pipe == NULL) {
-		usb_log_warning("usbhid_req_set_report(): no pipe given.\n");
+		usb_log_warning("usbhid_req_set_report(): no pipe given.");
 		return EINVAL;
 	}
@@ -235,5 +235,5 @@
 	value |= (type << 8);
 	
-	usb_log_debug("Sending Get Report request to the device.\n");
+	usb_log_debug("Sending Get Report request to the device.");
 	
 	rc = usb_control_request_get(ctrl_pipe, 
@@ -266,5 +266,5 @@
 {
 	if (ctrl_pipe == NULL) {
-		usb_log_warning("usbhid_req_set_report(): no pipe given.\n");
+		usb_log_warning("usbhid_req_set_report(): no pipe given.");
 		return EINVAL;
 	}
@@ -300,5 +300,5 @@
 	
 	if (actual_size != 1) {
-		usb_log_warning("Wrong data size: %zu, expected: 1.\n",
+		usb_log_warning("Wrong data size: %zu, expected: 1.",
 			actual_size);
 		return ELIMIT;
@@ -327,5 +327,5 @@
 {
 	if (ctrl_pipe == NULL) {
-		usb_log_warning("usbhid_req_set_report(): no pipe given.\n");
+		usb_log_warning("usbhid_req_set_report(): no pipe given.");
 		return EINVAL;
 	}
@@ -363,5 +363,5 @@
 	
 	if (actual_size != 1) {
-		usb_log_warning("Wrong data size: %zu, expected: 1.\n",
+		usb_log_warning("Wrong data size: %zu, expected: 1.",
 			actual_size);
 		return ELIMIT;
Index: uspace/lib/usbhost/Makefile
===================================================================
--- uspace/lib/usbhost/Makefile	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbhost/Makefile	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -37,5 +37,9 @@
 	src/endpoint.c \
 	src/hcd.c \
-	src/usb_bus.c \
+	src/bus.c \
+	src/usb2_bus.c \
+	src/bandwidth.c \
+	src/dma_buffer.c \
+	src/utility.c \
 	src/usb_transfer_batch.c
 
Index: uspace/lib/usbhost/include/usb/host/bandwidth.h
===================================================================
--- uspace/lib/usbhost/include/usb/host/bandwidth.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/lib/usbhost/include/usb/host/bandwidth.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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 libusbhost
+ * @{
+ */
+/** @file
+ *
+ * Bandwidth calculation functions. Shared among uhci, ohci and ehci drivers.
+ */
+
+#ifndef LIBUSBHOST_HOST_BANDWIDTH_H
+#define LIBUSBHOST_HOST_BANDWIDTH_H
+
+#include <usb/usb.h>
+
+#include <stddef.h>
+
+typedef struct endpoint endpoint_t;
+
+typedef size_t (*endpoint_count_bw_t)(endpoint_t *);
+
+typedef struct {
+	size_t available_bandwidth;
+	endpoint_count_bw_t count_bw;
+} bandwidth_accounting_t;
+
+extern const bandwidth_accounting_t bandwidth_accounting_usb11;
+extern const bandwidth_accounting_t bandwidth_accounting_usb2;
+
+#endif
+/**
+ * @}
+ */
Index: uspace/lib/usbhost/include/usb/host/bus.h
===================================================================
--- uspace/lib/usbhost/include/usb/host/bus.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/lib/usbhost/include/usb/host/bus.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2017 Ondrej Hlavaty <aearsis@eideo.cz>
+ * 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 libusbhost
+ * @{
+ */
+/** @file
+ * Virtual base for usb bus implementations.
+ *
+ * The purpose of this structure is to keep information about connected devices
+ * and endpoints, manage available bandwidth and the toggle bit flipping.
+ *
+ * The generic implementation is provided for USB 1 and 2 in usb2_bus.c. Some
+ * details in [OUE]HCI are solved through overriding some functions. XHCI does
+ * not need the bookkeeping functionality, because addresses are managed by HC
+ * itself.
+ */
+#ifndef LIBUSBHOST_HOST_BUS_H
+#define LIBUSBHOST_HOST_BUS_H
+
+#include <assert.h>
+#include <fibril_synch.h>
+#include <stdbool.h>
+#include <usb/host/hcd.h>
+#include <usb/request.h>
+#include <usb/usb.h>
+#include <usbhc_iface.h>
+
+typedef struct hcd hcd_t;
+typedef struct endpoint endpoint_t;
+typedef struct bus bus_t;
+typedef struct ddf_fun ddf_fun_t;
+typedef struct usb_transfer_batch usb_transfer_batch_t;
+
+typedef struct device {
+	/* Device tree keeping */
+	link_t link;
+	list_t devices;
+	fibril_mutex_t guard;
+
+	/* Associated DDF function, if any */
+	ddf_fun_t *fun;
+
+	/* Invalid for the roothub device */
+	unsigned port;
+
+	/** Hub under which this device is connected */
+	struct device *hub;
+
+	/** USB Tier of the device */
+	uint8_t tier;
+
+	/* Transaction translator */
+	struct {
+		device_t *dev;
+		unsigned port;
+	} tt;
+
+	/* The following are not set by the library */
+	usb_speed_t speed;
+	usb_address_t address;
+	endpoint_t *endpoints [USB_ENDPOINT_COUNT];
+
+	/* Managing bus */
+	bus_t *bus;
+
+	/** True if the device can add new endpoints and schedule transfers. */
+	volatile bool online;
+
+	/* This structure is meant to be extended by overriding. */
+} device_t;
+
+typedef struct bus_ops bus_ops_t;
+
+/**
+ * Operations structure serving as an interface of hc driver for the library
+ * (and the rest of the system).
+ */
+struct bus_ops {
+	/* Global operations on the bus */
+	void (*interrupt)(bus_t *, uint32_t);
+	int (*status)(bus_t *, uint32_t *);
+
+	/* Operations on device */
+	int (*device_enumerate)(device_t *);
+	void (*device_gone)(device_t *);
+	int (*device_online)(device_t *);			/**< Optional */
+	void (*device_offline)(device_t *);			/**< Optional */
+	endpoint_t *(*endpoint_create)(device_t *, const usb_endpoint_descriptors_t *);
+
+	/* Operations on endpoint */
+	int (*endpoint_register)(endpoint_t *);
+	void (*endpoint_unregister)(endpoint_t *);
+	void (*endpoint_destroy)(endpoint_t *);			/**< Optional */
+	usb_transfer_batch_t *(*batch_create)(endpoint_t *);	/**< Optional */
+
+	/* Operations on batch */
+	int (*batch_schedule)(usb_transfer_batch_t *);
+	void (*batch_destroy)(usb_transfer_batch_t *);		/**< Optional */
+};
+
+/** Endpoint management structure */
+typedef struct bus {
+	/* Synchronization of ops */
+	fibril_mutex_t guard;
+
+	/* Size of the device_t extended structure */
+	size_t device_size;
+
+	/* Do not call directly, ops are synchronized. */
+	const bus_ops_t *ops;
+
+	/* Reserving default address */
+	device_t *default_address_owner;
+	fibril_condvar_t default_address_cv;
+
+	/* This structure is meant to be extended by overriding. */
+} bus_t;
+
+void bus_init(bus_t *, size_t);
+int bus_device_init(device_t *, bus_t *);
+
+int bus_device_set_default_name(device_t *);
+
+int bus_device_enumerate(device_t *);
+void bus_device_gone(device_t *);
+
+int bus_device_online(device_t *);
+int bus_device_offline(device_t *);
+
+int bus_device_send_batch(device_t *, usb_target_t,
+    usb_direction_t direction, char *, size_t, uint64_t,
+    usbhc_iface_transfer_callback_t, void *, const char *);
+
+ssize_t bus_device_send_batch_sync(device_t *, usb_target_t,
+    usb_direction_t direction, char *, size_t, uint64_t,
+    const char *);
+
+int bus_endpoint_add(device_t *, const usb_endpoint_descriptors_t *, endpoint_t **);
+endpoint_t *bus_find_endpoint(device_t *, usb_endpoint_t, usb_direction_t);
+int bus_endpoint_remove(endpoint_t *);
+
+int bus_reserve_default_address(bus_t *, device_t *);
+void bus_release_default_address(bus_t *, device_t *);
+
+#endif
+/**
+ * @}
+ */
Index: uspace/lib/usbhost/include/usb/host/ddf_helpers.h
===================================================================
--- uspace/lib/usbhost/include/usb/host/ddf_helpers.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbhost/include/usb/host/ddf_helpers.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -37,45 +37,25 @@
 #define LIBUSBHOST_HOST_DDF_HELPERS_H
 
-#include <usb/host/hcd.h>
-#include <usb/host/usb_bus.h>
+#include <ddf/driver.h>
+#include <ddf/interrupt.h>
 #include <usb/usb.h>
 
-#include <ddf/driver.h>
-#include <ddf/interrupt.h>
-#include <device/hw_res_parsed.h>
+#include <usb/host/hcd.h>
+#include <usb/descriptor.h>
 
-typedef errno_t (*driver_init_t)(hcd_t *, const hw_res_list_parsed_t *, bool);
-typedef void (*driver_fini_t)(hcd_t *);
-typedef errno_t (*claim_t)(ddf_dev_t *);
-typedef errno_t (*irq_code_gen_t)(irq_code_t *, const hw_res_list_parsed_t *, int *);
 
-typedef struct {
-	hcd_ops_t ops;
-	claim_t claim;
-	usb_speed_t hc_speed;
-	driver_init_t init;
-	driver_fini_t fini;
-	interrupt_handler_t *irq_handler;
-	irq_code_gen_t irq_code_gen;
-	const char *name;
-} ddf_hc_driver_t;
+errno_t hcd_ddf_setup_hc(ddf_dev_t *, size_t);
+void hcd_ddf_clean_hc(hc_device_t *);
 
-errno_t hcd_ddf_add_hc(ddf_dev_t *device, const ddf_hc_driver_t *driver);
 
-errno_t hcd_ddf_setup_hc(ddf_dev_t *device, usb_speed_t max_speed,
-    size_t bw, bw_count_func_t bw_count);
-void hcd_ddf_clean_hc(ddf_dev_t *device);
-errno_t hcd_ddf_setup_root_hub(ddf_dev_t *device);
+device_t *hcd_ddf_fun_create(hc_device_t *, usb_speed_t);
+void hcd_ddf_fun_destroy(device_t *);
 
-hcd_t *dev_to_hcd(ddf_dev_t *dev);
+errno_t hcd_ddf_setup_match_ids(device_t *, usb_standard_device_descriptor_t *);
 
-errno_t hcd_ddf_enable_interrupt(ddf_dev_t *device, int);
-errno_t hcd_ddf_get_registers(ddf_dev_t *device, hw_res_list_parsed_t *hw_res);
-errno_t hcd_ddf_setup_interrupts(ddf_dev_t *device,
-    const hw_res_list_parsed_t *hw_res,
-    interrupt_handler_t handler,
-    errno_t (*gen_irq_code)(irq_code_t *, const hw_res_list_parsed_t *, int *),
-    cap_handle_t *handle);
-void ddf_hcd_gen_irq_handler(ipc_call_t *call, ddf_dev_t *dev);
+errno_t hcd_ddf_enable_interrupt(hc_device_t *hcd, int);
+errno_t hcd_ddf_get_registers(hc_device_t *hcd, hw_res_list_parsed_t *hw_res);
+
+void hcd_ddf_gen_irq_handler(ipc_callid_t iid, ipc_call_t *call, ddf_dev_t *dev);
 
 #endif
Index: uspace/lib/usbhost/include/usb/host/dma_buffer.h
===================================================================
--- uspace/lib/usbhost/include/usb/host/dma_buffer.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/lib/usbhost/include/usb/host/dma_buffer.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2017 Ondrej Hlavaty <aearsis@eideo.cz>
+ * 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 drvusbehci
+ * @{
+ */
+/** @file
+ * @brief USB host controller library: DMA buffer helpers
+ *
+ * Simplifies usage of bounce buffers.
+ *
+ * Currently the minimum size allocated is a page, which is wasteful. Could be
+ * extended to support memory pools, which will enable smaller units of
+ * allocation.
+ */
+#ifndef LIB_USBHOST_DMA_BUFFER
+#define LIB_USBHOST_DMA_BUFFER
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <errno.h>
+
+typedef const struct dma_policy {
+	bool use64;		/**< Whether to use more than initial 4GiB of memory */
+	size_t alignment;	/**< What alignment is needed. At most PAGE_SIZE. */
+} dma_policy_t;
+
+typedef struct dma_buffer {
+	void *virt;
+	uintptr_t phys;
+} dma_buffer_t;
+
+extern dma_policy_t dma_policy_default;
+
+extern int dma_buffer_alloc(dma_buffer_t *db, size_t size);
+extern int dma_buffer_alloc_policy(dma_buffer_t *, size_t, dma_policy_t);
+extern void dma_buffer_free(dma_buffer_t *);
+extern uintptr_t dma_buffer_phys(const dma_buffer_t *, void *);
+
+static inline int dma_buffer_is_set(dma_buffer_t *db)
+{
+	return !!db->virt;
+}
+
+#endif
+/**
+ * @}
+ */
Index: uspace/lib/usbhost/include/usb/host/endpoint.h
===================================================================
--- uspace/lib/usbhost/include/usb/host/endpoint.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbhost/include/usb/host/endpoint.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -32,23 +32,60 @@
 /** @file
  *
+ * Endpoint structure is tightly coupled to the bus. The bus controls the
+ * life-cycle of endpoint. In order to keep endpoints lightweight, operations
+ * on endpoints are part of the bus structure.
+ *
  */
 #ifndef LIBUSBHOST_HOST_ENDPOINT_H
 #define LIBUSBHOST_HOST_ENDPOINT_H
 
+#include <adt/list.h>
+#include <atomic.h>
+#include <fibril_synch.h>
 #include <stdbool.h>
-#include <adt/list.h>
-#include <fibril_synch.h>
+#include <sys/time.h>
 #include <usb/usb.h>
-#include <atomic.h>
+#include <usb/host/bus.h>
+#include <usbhc_iface.h>
 
-/** Host controller side endpoint structure. */
+typedef struct bus bus_t;
+typedef struct device device_t;
+typedef struct usb_transfer_batch usb_transfer_batch_t;
+
+/**
+ * Host controller side endpoint structure.
+ *
+ * This structure, though reference-counted, is very fragile. It is responsible
+ * for synchronizing transfer batch scheduling and completion.
+ *
+ * To avoid situations, in which two locks must be obtained to schedule/finish
+ * a transfer, the endpoint inherits a lock from the outside. Because the
+ * concrete instance of mutex can be unknown at the time of initialization,
+ * the HC shall pass the right lock at the time of onlining the endpoint.
+ *
+ * The fields used for scheduling (online, active_batch) are to be used only
+ * under that guard and by functions designed for this purpose. The driver can
+ * also completely avoid using this mechanism, in which case it is on its own in
+ * question of transfer aborting.
+ *
+ * Relevant information can be found in the documentation of HelenOS xHCI
+ * project.
+ */
 typedef struct endpoint {
+	/** USB device */
+	device_t *device;
 	/** Reference count. */
-	atomic_t refcnt;	
-	/** Part of linked list. */
-	link_t link;
-	/** USB address. */
-	usb_address_t address;
-	/** USB endpoint number. */
+	atomic_t refcnt;
+
+	/** An inherited guard */
+	fibril_mutex_t *guard;
+	/** Whether it's allowed to schedule on this endpoint */
+	bool online;
+	/** The currently active transfer batch. */
+	usb_transfer_batch_t *active_batch;
+	/** Signals change of active status. */
+	fibril_condvar_t avail;
+
+	/** Endpoint number */
 	usb_endpoint_t endpoint;
 	/** Communication direction. */
@@ -56,65 +93,40 @@
 	/** USB transfer type. */
 	usb_transfer_type_t transfer_type;
-	/** Communication speed. */
-	usb_speed_t speed;
-	/** Maximum size of data packets. */
+	/** Maximum size of one packet */
 	size_t max_packet_size;
-	/** Additional opportunities per uframe */
-	unsigned packets;
-	/** Necessary bandwidth. */
-	size_t bandwidth;
-	/** Value of the toggle bit. */
-	unsigned toggle:1;
-	/** True if there is a batch using this scheduled for this endpoint. */
-	volatile bool active;
-	/** Protects resources and active status changes. */
-	fibril_mutex_t guard;
-	/** Signals change of active status. */
-	fibril_condvar_t avail;
-	/** High speed TT data */
-	struct {
-		usb_address_t address;
-		unsigned port;
-	} tt;
-	/** Optional device specific data. */
-	struct {
-		/** Device specific data. */
-		void *data;
-		/** Callback to get the value of toggle bit. */
-		int (*toggle_get)(void *);
-		/** Callback to set the value of toggle bit. */
-		void (*toggle_set)(void *, int);
-	} hc_data;
+
+	/** Maximum size of one transfer */
+	size_t max_transfer_size;
+	/**
+	 * Number of packets that can be sent in one service interval
+	 * (not necessarily uframe, despite its name)
+	 */
+	unsigned packets_per_uframe;
+
+	/* This structure is meant to be extended by overriding. */
 } endpoint_t;
 
-extern endpoint_t *endpoint_create(usb_address_t, usb_endpoint_t,
-    usb_direction_t, usb_transfer_type_t, usb_speed_t, size_t, unsigned int,
-    size_t, usb_address_t, unsigned int);
-extern void endpoint_destroy(endpoint_t *);
+extern void endpoint_init(endpoint_t *, device_t *, const usb_endpoint_descriptors_t *);
 
 extern void endpoint_add_ref(endpoint_t *);
 extern void endpoint_del_ref(endpoint_t *);
 
-extern void endpoint_set_hc_data(endpoint_t *, void *, int (*)(void *),
-    void (*)(void *, int));
-extern void endpoint_clear_hc_data(endpoint_t *);
+extern void endpoint_set_online(endpoint_t *, fibril_mutex_t *);
+extern void endpoint_set_offline_locked(endpoint_t *);
 
-extern void endpoint_use(endpoint_t *);
-extern void endpoint_release(endpoint_t *);
+extern void endpoint_wait_timeout_locked(endpoint_t *ep, suseconds_t);
+extern int endpoint_activate_locked(endpoint_t *, usb_transfer_batch_t *);
+extern void endpoint_deactivate_locked(endpoint_t *);
 
-extern int endpoint_toggle_get(endpoint_t *);
-extern void endpoint_toggle_set(endpoint_t *, int);
+int endpoint_send_batch(endpoint_t *, usb_target_t, usb_direction_t,
+    char *, size_t, uint64_t, usbhc_iface_transfer_callback_t, void *,
+    const char *);
 
-/** list_get_instance wrapper.
- *
- * @param item Pointer to link member.
- *
- * @return Pointer to endpoint_t structure.
- *
- */
-static inline endpoint_t * endpoint_get_instance(link_t *item)
+static inline bus_t *endpoint_get_bus(endpoint_t *ep)
 {
-	return item ? list_get_instance(item, endpoint_t, link) : NULL;
+	device_t * const device = ep->device;
+	return device ? device->bus : NULL;
 }
+
 #endif
 
Index: uspace/lib/usbhost/include/usb/host/hcd.h
===================================================================
--- uspace/lib/usbhost/include/usb/host/hcd.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbhost/include/usb/host/hcd.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -37,92 +37,76 @@
 #define LIBUSBHOST_HOST_HCD_H
 
-#include <usb/host/endpoint.h>
-#include <usb/host/usb_bus.h>
-#include <usb/host/usb_transfer_batch.h>
-#include <usb/usb.h>
+#include <ddf/driver.h>
+#include <usb/request.h>
 
-#include <assert.h>
-#include <usbhc_iface.h>
-#include <stddef.h>
-#include <stdint.h>
+typedef struct hw_resource_list_parsed hw_res_list_parsed_t;
+typedef struct bus bus_t;
+typedef struct device device_t;
 
-typedef struct hcd hcd_t;
+/* Treat this header as read-only in driver code.
+ * It could be opaque, but why to complicate matters.
+ */
+typedef struct hc_device {
+	/* Bus instance */
+	bus_t *bus;
 
-typedef errno_t (*schedule_hook_t)(hcd_t *, usb_transfer_batch_t *);
-typedef errno_t (*ep_add_hook_t)(hcd_t *, endpoint_t *);
-typedef void (*ep_remove_hook_t)(hcd_t *, endpoint_t *);
-typedef void (*interrupt_hook_t)(hcd_t *, uint32_t);
-typedef errno_t (*status_hook_t)(hcd_t *, uint32_t *);
+	/* Managed DDF device */
+	ddf_dev_t *ddf_dev;
 
-typedef struct {
-	/** Transfer scheduling, implement in device driver. */
-	schedule_hook_t schedule;
-	/** Hook called upon registering new endpoint. */
-	ep_add_hook_t ep_add_hook;
-	/** Hook called upon removing of an endpoint. */
-	ep_remove_hook_t ep_remove_hook;
-	/** Hook to be called on device interrupt, passes ARG1 */
-	interrupt_hook_t irq_hook;
-	/** Periodic polling hook */
-	status_hook_t status_hook;
-} hcd_ops_t;
+	/* Control function */
+	ddf_fun_t *ctl_fun;
 
-/** Generic host controller driver structure. */
-struct hcd {
-	/** Endpoint manager. */
-	usb_bus_t bus;
+	/* Result of enabling HW IRQs */
+	int irq_cap;
 
 	/** Interrupt replacement fibril */
 	fid_t polling_fibril;
 
-	/** Driver implementation */
-	hcd_ops_t ops;
-	/** Device specific driver data. */
-	void * driver_data;
-};
+	/* This structure is meant to be extended by driver code. */
+} hc_device_t;
 
-extern void hcd_init(hcd_t *, usb_speed_t, size_t, bw_count_func_t);
+typedef struct hc_driver {
+	const char *name;
 
-static inline void hcd_set_implementation(hcd_t *hcd, void *data,
-    const hcd_ops_t *ops)
-{
-	assert(hcd);
-	if (ops) {
-		hcd->driver_data = data;
-		hcd->ops = *ops;
-	} else {
-		memset(&hcd->ops, 0, sizeof(hcd->ops));
-	}
+	/** Size of the device data to be allocated, and passed as the
+	 * hc_device_t. */
+	size_t hc_device_size;
+
+	/** Initialize device structures. */
+	int (*hc_add)(hc_device_t *, const hw_res_list_parsed_t *);
+
+	/** Generate IRQ code to handle interrupts. */
+	int (*irq_code_gen)(irq_code_t *, hc_device_t *, const hw_res_list_parsed_t *, int *);
+
+	/** Claim device from BIOS. */
+	int (*claim)(hc_device_t *);
+
+	/** Start the host controller. */
+	int (*start)(hc_device_t *);
+
+	/** Setup the virtual roothub. */
+	int (*setup_root_hub)(hc_device_t *);
+
+	/** Stop the host controller (after start has been called) */
+	int (*stop)(hc_device_t *);
+
+	/** HC was asked to be removed (after hc_add has been called) */
+	int (*hc_remove)(hc_device_t *);
+
+	/** HC is gone. */
+	int (*hc_gone)(hc_device_t *);
+} hc_driver_t;
+
+/* Drivers should call this before leaving hc_add */
+static inline void hc_device_setup(hc_device_t *hcd, bus_t *bus) {
+	hcd->bus = bus;
 }
 
-static inline void * hcd_get_driver_data(hcd_t *hcd)
+static inline hc_device_t *dev_to_hcd(ddf_dev_t *dev)
 {
-	assert(hcd);
-	return hcd->driver_data;
+	return ddf_dev_data_get(dev);
 }
 
-extern errno_t hcd_request_address(hcd_t *, usb_speed_t, usb_address_t *);
-
-extern errno_t hcd_release_address(hcd_t *, usb_address_t);
-
-extern errno_t hcd_reserve_default_address(hcd_t *, usb_speed_t);
-
-static inline errno_t hcd_release_default_address(hcd_t *hcd)
-{
-	return hcd_release_address(hcd, USB_ADDRESS_DEFAULT);
-}
-
-extern errno_t hcd_add_ep(hcd_t *, usb_target_t, usb_direction_t,
-    usb_transfer_type_t, size_t, unsigned int, size_t, usb_address_t,
-    unsigned int);
-
-extern errno_t hcd_remove_ep(hcd_t *, usb_target_t, usb_direction_t);
-
-extern errno_t hcd_send_batch(hcd_t *, usb_target_t, usb_direction_t, void *,
-    size_t, uint64_t, usbhc_iface_transfer_in_callback_t,
-    usbhc_iface_transfer_out_callback_t, void *, const char *);
-
-extern errno_t hcd_send_batch_sync(hcd_t *, usb_target_t, usb_direction_t,
-    void *, size_t, uint64_t, const char *, size_t *);
+extern errno_t hc_driver_main(const hc_driver_t *);
 
 #endif
Index: uspace/lib/usbhost/include/usb/host/usb2_bus.h
===================================================================
--- uspace/lib/usbhost/include/usb/host/usb2_bus.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/lib/usbhost/include/usb/host/usb2_bus.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * Copyright (c) 2017 Ondrej Hlavaty <aearsis@eideo.cz>
+ * 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 libusbhost
+ * @{
+ */
+/** @file
+ *
+ * Bus implementation common for OHCI, UHCI and EHCI.
+ */
+#ifndef LIBUSBHOST_HOST_USB2_BUS_H
+#define LIBUSBHOST_HOST_USB2_BUS_H
+
+#include <adt/list.h>
+#include <stdbool.h>
+#include <usb/usb.h>
+
+#include <usb/host/bus.h>
+#include <usb/host/bandwidth.h>
+
+typedef struct usb2_bus usb2_bus_t;
+typedef struct endpoint endpoint_t;
+
+/** Endpoint and bandwidth management structure */
+typedef struct usb2_bus_helper {
+	/** Map of occupied addresses */
+	bool address_occupied [USB_ADDRESS_COUNT];
+	/** The last reserved address */
+	usb_address_t last_address;
+
+	/** Size of the bandwidth pool */
+	size_t free_bw;
+
+	/* Configured bandwidth accounting */
+	const bandwidth_accounting_t *bw_accounting;
+} usb2_bus_helper_t;
+
+extern void usb2_bus_helper_init(usb2_bus_helper_t *, const bandwidth_accounting_t *);
+
+extern int usb2_bus_device_enumerate(usb2_bus_helper_t *, device_t *);
+extern void usb2_bus_device_gone(usb2_bus_helper_t *, device_t *);
+
+extern int usb2_bus_endpoint_register(usb2_bus_helper_t *, endpoint_t *);
+extern void usb2_bus_endpoint_unregister(usb2_bus_helper_t *, endpoint_t *);
+
+#endif
+/**
+ * @}
+ */
Index: uspace/lib/usbhost/include/usb/host/usb_bus.h
===================================================================
--- uspace/lib/usbhost/include/usb/host/usb_bus.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ 	(revision )
@@ -1,120 +1,0 @@
-/*
- * Copyright (c) 2011 Jan Vesely
- * 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 libusbhost
- * @{
- */
-/** @file
- * Device keeper structure and functions.
- *
- * Typical USB host controller needs to keep track of various settings for
- * each device that is connected to it.
- * State of toggle bit, device speed etc. etc.
- * This structure shall simplify the management.
- */
-#ifndef LIBUSBHOST_HOST_USB_ENDPOINT_MANAGER_H
-#define LIBUSBHOST_HOST_USB_ENDPOINT_MANAGER_H
-
-#include <usb/host/endpoint.h>
-#include <usb/usb.h>
-
-#include <adt/list.h>
-#include <fibril_synch.h>
-#include <stdbool.h>
-
-
-/** Bytes per second in FULL SPEED */
-#define BANDWIDTH_TOTAL_USB11 (12000000 / 8)
-/** 90% of total bandwidth is available for periodic transfers */
-#define BANDWIDTH_AVAILABLE_USB11 ((BANDWIDTH_TOTAL_USB11 / 10) * 9)
-
-//TODO: Implement
-#define BANDWIDTH_AVAILABLE_USB20  1
-
-typedef size_t (*bw_count_func_t)(usb_speed_t, usb_transfer_type_t, size_t, size_t);
-typedef void (*ep_remove_callback_t)(endpoint_t *, void *);
-typedef errno_t (*ep_add_callback_t)(endpoint_t *, void *);
-
-/** Endpoint management structure */
-typedef struct usb_bus {
-	struct {
-		usb_speed_t speed;      /**< Device speed */
-		bool occupied;          /**< The address is in use. */
-		list_t endpoint_list;   /**< Store endpoint_t instances */
-	} devices[USB_ADDRESS_COUNT];
-	/** Prevents races accessing lists */
-	fibril_mutex_t guard;
-	/** Size of the bandwidth pool */
-	size_t free_bw;
-	/** Use this function to count bw required by EP */
-	bw_count_func_t bw_count;
-	/** Maximum speed allowed. */
-	usb_speed_t max_speed;
-	/** The last reserved address */
-	usb_address_t last_address;
-} usb_bus_t;
-
-
-size_t bandwidth_count_usb11(usb_speed_t speed, usb_transfer_type_t type,
-    size_t size, size_t max_packet_size);
-size_t bandwidth_count_usb20(usb_speed_t speed, usb_transfer_type_t type,
-    size_t size, size_t max_packet_size);
-
-errno_t usb_bus_init(usb_bus_t *instance,
-    size_t available_bandwidth, bw_count_func_t bw_count, usb_speed_t max_speed);
-
-errno_t usb_bus_register_ep(usb_bus_t *instance, endpoint_t *ep, size_t data_size);
-
-errno_t usb_bus_unregister_ep(usb_bus_t *instance, endpoint_t *ep);
-
-endpoint_t *usb_bus_find_ep(usb_bus_t *instance,
-    usb_address_t address, usb_endpoint_t ep, usb_direction_t direction);
-
-errno_t usb_bus_add_ep(usb_bus_t *instance,
-    usb_address_t address, usb_endpoint_t endpoint, usb_direction_t direction,
-    usb_transfer_type_t type, size_t max_packet_size, unsigned packets,
-    size_t data_size, ep_add_callback_t callback, void *arg,
-    usb_address_t tt_address, unsigned tt_port);
-
-errno_t usb_bus_remove_ep(usb_bus_t *instance,
-    usb_address_t address, usb_endpoint_t endpoint, usb_direction_t direction,
-    ep_remove_callback_t callback, void *arg);
-
-errno_t usb_bus_reset_toggle(usb_bus_t *instance, usb_target_t target, bool all);
-
-errno_t usb_bus_remove_address(usb_bus_t *instance,
-    usb_address_t address, ep_remove_callback_t callback, void *arg);
-
-errno_t usb_bus_request_address(usb_bus_t *instance,
-    usb_address_t *address, bool strict, usb_speed_t speed);
-
-errno_t usb_bus_get_speed(usb_bus_t *instance,
-    usb_address_t address, usb_speed_t *speed);
-#endif
-/**
- * @}
- */
Index: uspace/lib/usbhost/include/usb/host/usb_transfer_batch.h
===================================================================
--- uspace/lib/usbhost/include/usb/host/usb_transfer_batch.h	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbhost/include/usb/host/usb_transfer_batch.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -37,50 +37,57 @@
 #define LIBUSBHOST_HOST_USB_TRANSFER_BATCH_H
 
-#include <usb/host/endpoint.h>
-#include <usb/usb.h>
-
-#include <assert.h>
+#include <atomic.h>
+#include <errno.h>
 #include <stddef.h>
 #include <stdint.h>
+#include <usb/request.h>
+#include <usb/usb.h>
 #include <usbhc_iface.h>
 
-#define USB_SETUP_PACKET_SIZE 8
+#include <usb/host/hcd.h>
+#include <usb/host/endpoint.h>
+#include <usb/host/bus.h>
+
+typedef struct endpoint endpoint_t;
+typedef struct bus bus_t;
 
 /** Structure stores additional data needed for communication with EP */
 typedef struct usb_transfer_batch {
+	/** Target for communication */
+	usb_target_t target;
+
 	/** Endpoint used for communication */
 	endpoint_t *ep;
-	/** Function called on completion (IN version) */
-	usbhc_iface_transfer_in_callback_t callback_in;
-	/** Function called on completion (OUT version) */
-	usbhc_iface_transfer_out_callback_t callback_out;
-	/** Argument to pass to the completion function */
-	void *arg;
+
+	/** Direction of the transfer */
+	usb_direction_t dir;
+
+	/** Function called on completion */
+	usbhc_iface_transfer_callback_t on_complete;
+	/** Arbitrary data for the handler */
+	void *on_complete_data;
+
+	/** Place to store SETUP data needed by control transfers */
+	union {
+		char buffer [USB_SETUP_PACKET_SIZE];
+		usb_device_request_setup_packet_t packet;
+		uint64_t packed;
+	} setup;
+
 	/** Place for data to send/receive */
 	char *buffer;
 	/** Size of memory pointed to by buffer member */
 	size_t buffer_size;
-	/** Place to store SETUP data needed by control transfers */
-	char setup_buffer[USB_SETUP_PACKET_SIZE];
-	/** Used portion of setup_buffer member
-	 *
-	 * SETUP buffer must be 8 bytes for control transfers and is left
-	 * unused for all other transfers. Thus, this field is either 0 or 8.
-	 */
-	size_t setup_size;
+	/** Actually used portion of the buffer */
+	size_t transferred_size;
 
-	/** Actually used portion of the buffer
-	 * This member is never accessed by functions provided in this header,
-	 * with the exception of usb_transfer_batch_finish. For external use.
-	 */
-	size_t transfered_size;
-	/** Indicates success/failure of the communication
-	 * This member is never accessed by functions provided in this header,
-	 * with the exception of usb_transfer_batch_finish. For external use.
-	 */
+	/** Indicates success/failure of the communication */
 	errno_t error;
 } usb_transfer_batch_t;
 
-/** Printf formatting string for dumping usb_transfer_batch_t. */
+/**
+ * Printf formatting string for dumping usb_transfer_batch_t.
+ *  [address:endpoint speed transfer_type-direction buffer_sizeB/max_packet_size]
+ * */
 #define USB_TRANSFER_BATCH_FMT "[%d:%d %s %s-%s %zuB/%zu]"
 
@@ -89,62 +96,23 @@
  */
 #define USB_TRANSFER_BATCH_ARGS(batch) \
-	(batch).ep->address, (batch).ep->endpoint, \
-	usb_str_speed((batch).ep->speed), \
+	((batch).ep->device->address), ((batch).ep->endpoint), \
+	usb_str_speed((batch).ep->device->speed), \
 	usb_str_transfer_type_short((batch).ep->transfer_type), \
-	usb_str_direction((batch).ep->direction), \
+	usb_str_direction((batch).dir), \
 	(batch).buffer_size, (batch).ep->max_packet_size
 
+/** Wrapper for bus operation. */
+usb_transfer_batch_t *usb_transfer_batch_create(endpoint_t *);
 
-usb_transfer_batch_t * usb_transfer_batch_create(
-    endpoint_t *ep,
-    char *buffer,
-    size_t buffer_size,
-    uint64_t setup_buffer,
-    usbhc_iface_transfer_in_callback_t func_in,
-    usbhc_iface_transfer_out_callback_t func_out,
-    void *arg
-);
-void usb_transfer_batch_destroy(usb_transfer_batch_t *instance);
+/** Batch initializer. */
+void usb_transfer_batch_init(usb_transfer_batch_t *, endpoint_t *);
 
-void usb_transfer_batch_finish_error(const usb_transfer_batch_t *instance,
-    const void* data, size_t size, errno_t error);
+/** Batch finalization. */
+void usb_transfer_batch_finish(usb_transfer_batch_t *);
 
-/** Finish batch using stored error value and transferred size.
- *
- * @param[in] instance Batch structure to use.
- * @param[in] data Data to copy to the output buffer.
+/** To be called from outside only when the transfer is not going to be finished
+ * (i.o.w. until successfuly scheduling)
  */
-static inline void usb_transfer_batch_finish(
-    const usb_transfer_batch_t *instance, const void* data)
-{
-	assert(instance);
-	usb_transfer_batch_finish_error(
-	    instance, data, instance->transfered_size, instance->error);
-}
-
-/** Determine batch direction based on the callbacks present
- * @param[in] instance Batch structure to use, non-null.
- * @return USB_DIRECTION_IN, or USB_DIRECTION_OUT.
- */
-static inline usb_direction_t usb_transfer_batch_direction(
-    const usb_transfer_batch_t *instance)
-{
-	assert(instance);
-	if (instance->callback_in) {
-		assert(instance->callback_out == NULL);
-		assert(instance->ep == NULL
-		    || instance->ep->transfer_type == USB_TRANSFER_CONTROL
-		    || instance->ep->direction == USB_DIRECTION_IN);
-		return USB_DIRECTION_IN;
-	}
-	if (instance->callback_out) {
-		assert(instance->callback_in == NULL);
-		assert(instance->ep == NULL
-		    || instance->ep->transfer_type == USB_TRANSFER_CONTROL
-		    || instance->ep->direction == USB_DIRECTION_OUT);
-		return USB_DIRECTION_OUT;
-	}
-	assert(false);
-}
+void usb_transfer_batch_destroy(usb_transfer_batch_t *);
 
 #endif
Index: uspace/lib/usbhost/include/usb/host/utility.h
===================================================================
--- uspace/lib/usbhost/include/usb/host/utility.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/lib/usbhost/include/usb/host/utility.h	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2017 Ondrej Hlavaty <aearsis@eideo.cz>
+ * 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 drvusbehci
+ * @{
+ */
+/** @file
+ * @brief USB host controller library: Utility functions.
+ *
+ * A mix of various functions that makes life of USB HC driver developers
+ * easier.
+ */
+#ifndef LIB_USBHOST_UTILITY
+#define LIB_USBHOST_UTILITY
+
+#include <usb/host/bus.h>
+#include <usb/host/usb_transfer_batch.h>
+#include <usb/descriptor.h>
+#include <usb/classes/hub.h>
+#include <usb/request.h>
+
+typedef void (*endpoint_reset_toggle_t)(endpoint_t *);
+
+uint16_t hc_get_ep0_initial_mps(usb_speed_t);
+int hc_get_ep0_max_packet_size(uint16_t *, device_t *);
+void hc_reset_toggles(const usb_transfer_batch_t *batch, endpoint_reset_toggle_t);
+int hc_setup_virtual_root_hub(hc_device_t *, usb_speed_t);
+int hc_get_device_desc(device_t *, usb_standard_device_descriptor_t *);
+int hc_get_hub_desc(device_t *, usb_hub_descriptor_header_t *);
+int hc_device_explore(device_t *);
+
+#endif
+/**
+ * @}
+ */
Index: uspace/lib/usbhost/src/bandwidth.c
===================================================================
--- uspace/lib/usbhost/src/bandwidth.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/lib/usbhost/src/bandwidth.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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 libusbhost
+ * @{
+ */
+/** @file
+ *
+ * Bandwidth calculation functions. Shared among uhci, ohci and ehci drivers.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "endpoint.h"
+#include "bus.h"
+
+#include "bandwidth.h"
+
+/** Bytes per second in FULL SPEED */
+#define BANDWIDTH_TOTAL_USB11 (12000000 / 8)
+/** 90% of total bandwidth is available for periodic transfers */
+#define BANDWIDTH_AVAILABLE_USB11 ((BANDWIDTH_TOTAL_USB11 * 9) / 10)
+
+/**
+ * Calculate bandwidth that needs to be reserved for communication with EP.
+ * Calculation follows USB 1.1 specification.
+ *
+ * @param ep An endpoint for which the bandwidth is to be counted
+ */
+static size_t bandwidth_count_usb11(endpoint_t *ep)
+{
+	assert(ep);
+	assert(ep->device);
+
+	const usb_transfer_type_t type = ep->transfer_type;
+
+	/* We care about bandwidth only for interrupt and isochronous. */
+	if ((type != USB_TRANSFER_INTERRUPT)
+	    && (type != USB_TRANSFER_ISOCHRONOUS)) {
+		return 0;
+	}
+
+	const size_t max_packet_size = ep->max_packet_size;
+	const size_t packet_count = ep->packets_per_uframe;
+
+	/* TODO: It may be that ISO and INT transfers use only one packet per
+	 * transaction, but I did not find text in USB spec to confirm this */
+	/* NOTE: All data packets will be considered to be max_packet_size */
+	switch (ep->device->speed)
+	{
+	case USB_SPEED_LOW:
+		assert(type == USB_TRANSFER_INTERRUPT);
+		/* Protocol overhead 13B
+		 * (3 SYNC bytes, 3 PID bytes, 2 Endpoint + CRC bytes, 2
+		 * CRC bytes, and a 3-byte interpacket delay)
+		 * see USB spec page 45-46. */
+		/* Speed penalty 8: low speed is 8-times slower*/
+		return packet_count * (13 + max_packet_size) * 8;
+	case USB_SPEED_FULL:
+		/* Interrupt transfer overhead see above
+		 * or page 45 of USB spec */
+		if (type == USB_TRANSFER_INTERRUPT)
+			return packet_count * (13 + max_packet_size);
+
+		assert(type == USB_TRANSFER_ISOCHRONOUS);
+		/* Protocol overhead 9B
+		 * (2 SYNC bytes, 2 PID bytes, 2 Endpoint + CRC bytes, 2 CRC
+		 * bytes, and a 1-byte interpacket delay)
+		 * see USB spec page 42 */
+		return packet_count * (9 + max_packet_size);
+	default:
+		return 0;
+	}
+}
+
+const bandwidth_accounting_t bandwidth_accounting_usb11 = {
+	.available_bandwidth = BANDWIDTH_AVAILABLE_USB11,
+	.count_bw = &bandwidth_count_usb11,
+};
+
+/** Number of nanoseconds in one microframe */
+#define BANDWIDTH_TOTAL_USB2 (125000)
+/** 90% of total bandwidth is available for periodic transfers */
+#define BANDWIDTH_AVAILABLE_USB2  ((BANDWIDTH_TOTAL_USB2 * 9) / 10)
+
+/**
+ * Calculate bandwidth that needs to be reserved for communication with EP.
+ * Calculation follows USB 2.0 specification, chapter 5.11.3.
+ *
+ * FIXME: Interrupt transfers shall be probably divided by their polling interval.
+ *
+ * @param ep An endpoint for which the bandwidth is to be counted
+ * @return Number of nanoseconds transaction with @c size bytes payload will
+ *         take.
+ */
+static size_t bandwidth_count_usb2(endpoint_t *ep)
+{
+	assert(ep);
+	assert(ep->device);
+
+	const usb_transfer_type_t type = ep->transfer_type;
+
+	/* We care about bandwidth only for interrupt and isochronous. */
+	if ((type != USB_TRANSFER_INTERRUPT)
+	    && (type != USB_TRANSFER_ISOCHRONOUS)) {
+		return 0;
+	}
+
+	// FIXME: Come up with some upper bound for these (in ns).
+	const size_t host_delay = 0;
+	const size_t hub_ls_setup = 0;
+
+	// Approx. Floor(3.167 + BitStuffTime(Data_bc))
+	const size_t base_time = (ep->max_transfer_size * 8 + 19) / 6;
+
+	switch (ep->device->speed) {
+	case USB_SPEED_LOW:
+		if (ep->direction == USB_DIRECTION_IN)
+			return 64060 + (2 * hub_ls_setup) + (677 * base_time) + host_delay;
+		else
+			return 64107 + (2 * hub_ls_setup) + (667 * base_time) + host_delay;
+
+	case USB_SPEED_FULL:
+		if (ep->transfer_type == USB_TRANSFER_INTERRUPT)
+			return 9107 + 84 * base_time + host_delay;
+
+		if (ep->direction == USB_DIRECTION_IN)
+			return 7268 + 84 * base_time + host_delay;
+		else
+			return 6265 + 84 * base_time + host_delay;
+
+	case USB_SPEED_HIGH:
+		if (ep->transfer_type == USB_TRANSFER_INTERRUPT)
+			return (3648 + 25 * base_time + 11) / 12 + host_delay;
+		else
+			return (5280 + 25 * base_time + 11) / 12 + host_delay;
+
+	default:
+		return 0;
+	}
+}
+
+const bandwidth_accounting_t bandwidth_accounting_usb2 = {
+	.available_bandwidth = BANDWIDTH_AVAILABLE_USB2,
+	.count_bw = &bandwidth_count_usb2,
+};
Index: uspace/lib/usbhost/src/bus.c
===================================================================
--- uspace/lib/usbhost/src/bus.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/lib/usbhost/src/bus.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,677 @@
+/*
+ * Copyright (c) 2017 Ondrej Hlavaty <aearsis@eideo.cz>
+ * 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 libusbhost
+ * @{
+ */
+/** @file
+ *
+ * The Bus is a structure that serves as an interface of the HC driver
+ * implementation for the usbhost library. Every HC driver that uses libusbhost
+ * must use a bus_t (or its child), fill it with bus_ops and present it to the
+ * library. The library then handles the DDF interface and translates it to the
+ * bus callbacks.
+ */
+
+#include <ddf/driver.h>
+#include <errno.h>
+#include <mem.h>
+#include <macros.h>
+#include <stdio.h>
+#include <str_error.h>
+#include <usb/debug.h>
+
+#include "endpoint.h"
+#include "bus.h"
+
+/**
+ * Initializes the base bus structure.
+ */
+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->device_size = device_size;
+}
+
+/**
+ * Initialize the device_t structure belonging to a bus.
+ */
+int bus_device_init(device_t *dev, bus_t *bus)
+{
+	assert(bus);
+
+	memset(dev, 0, sizeof(*dev));
+
+	dev->bus = bus;
+
+	link_initialize(&dev->link);
+	list_initialize(&dev->devices);
+	fibril_mutex_initialize(&dev->guard);
+
+	return EOK;
+}
+
+/**
+ * Create a name of the ddf function node.
+ */
+int bus_device_set_default_name(device_t *dev)
+{
+	assert(dev);
+	assert(dev->fun);
+
+	char buf[10] = { 0 }; /* usbxyz-ss */
+	snprintf(buf, sizeof(buf), "usb%u-%cs",
+	    dev->address, usb_str_speed(dev->speed)[0]);
+
+	return ddf_fun_set_name(dev->fun, buf);
+}
+
+/**
+ * Setup devices Transaction Translation.
+ *
+ * This applies for Low/Full speed devices under High speed hub only. Other
+ * devices just inherit TT from the hub.
+ *
+ * Roothub must be handled specially.
+ */
+static void device_setup_tt(device_t *dev)
+{
+	if (!dev->hub)
+		return;
+
+	if (dev->hub->speed == USB_SPEED_HIGH && usb_speed_is_11(dev->speed)) {
+		/* For LS devices under HS hub */
+		dev->tt.dev = dev->hub;
+		dev->tt.port = dev->port;
+	}
+	else {
+		/* Inherit hub's TT */
+		dev->tt = dev->hub->tt;
+	}
+}
+
+/**
+ * Invoke the device_enumerate bus operation.
+ *
+ * There's no need to synchronize here, because no one knows the device yet.
+ */
+int bus_device_enumerate(device_t *dev)
+{
+	assert(dev);
+
+	if (!dev->bus->ops->device_enumerate)
+		return ENOTSUP;
+
+	if (dev->online)
+		return EINVAL;
+
+	device_setup_tt(dev);
+
+	const int r = dev->bus->ops->device_enumerate(dev);
+	if (r)
+		return r;
+
+	dev->online = true;
+
+	if (dev->hub) {
+		fibril_mutex_lock(&dev->hub->guard);
+		list_append(&dev->link, &dev->hub->devices);
+		fibril_mutex_unlock(&dev->hub->guard);
+	}
+
+	return EOK;
+}
+
+/**
+ * Clean endpoints and children that could have been left behind after
+ * asking the driver of device to offline/remove a device.
+ *
+ * Note that EP0's lifetime is shared with the device, and as such is not
+ * touched.
+ */
+static void device_clean_ep_children(device_t *dev, const char *op)
+{
+	assert(fibril_mutex_is_locked(&dev->guard));
+
+	/* Unregister endpoints left behind. */
+	for (usb_endpoint_t i = 1; i < USB_ENDPOINT_MAX; ++i) {
+		if (!dev->endpoints[i])
+			continue;
+
+		usb_log_warning("USB device '%s' driver left endpoint %u registered after %s.",
+		    ddf_fun_get_name(dev->fun), i, op);
+
+		endpoint_t * const ep = dev->endpoints[i];
+		endpoint_add_ref(ep);
+
+		fibril_mutex_unlock(&dev->guard);
+		const int err = bus_endpoint_remove(ep);
+		if (err)
+			usb_log_warning("Endpoint %u cannot be removed. "
+			    "Some deffered cleanup was faster?", ep->endpoint);
+
+		endpoint_del_ref(ep);
+		fibril_mutex_lock(&dev->guard);
+	}
+
+	for (usb_endpoint_t i = 1; i < USB_ENDPOINT_MAX; ++i)
+		assert(dev->endpoints[i] == NULL);
+
+	/* Remove also orphaned children. */
+	while (!list_empty(&dev->devices)) {
+		device_t * const child = list_get_instance(list_first(&dev->devices), device_t, link);
+
+		/*
+		 * This is not an error condition, as devices cannot remove
+		 * their children devices while they are removed, because for
+		 * DDF, they are siblings.
+		 */
+		usb_log_debug("USB device '%s' driver left device '%s' behind after %s.",
+		    ddf_fun_get_name(dev->fun), ddf_fun_get_name(child->fun), op);
+
+		/*
+		 * The child node won't disappear, because its parent's driver
+		 * is already dead. And the child will need the guard to remove
+		 * itself from the list.
+		 */
+		fibril_mutex_unlock(&dev->guard);
+		bus_device_gone(child);
+		fibril_mutex_lock(&dev->guard);
+	}
+	assert(list_empty(&dev->devices));
+}
+
+/**
+ * Resolve a USB device that is gone.
+ */
+void bus_device_gone(device_t *dev)
+{
+	assert(dev);
+	assert(dev->fun != NULL);
+
+	const bus_ops_t *ops = dev->bus->ops;
+
+	/* First, block new transfers and operations. */
+	fibril_mutex_lock(&dev->guard);
+	dev->online = false;
+
+	/* Unbinding will need guard unlocked. */
+	fibril_mutex_unlock(&dev->guard);
+
+	/* Remove our device from our hub's children. */
+	if (dev->hub) {
+		fibril_mutex_lock(&dev->hub->guard);
+		list_remove(&dev->link);
+		fibril_mutex_unlock(&dev->hub->guard);
+	}
+
+	/*
+	 * Unbind the DDF function. That will result in dev_gone called in
+	 * driver, which shall destroy its pipes and remove its children.
+	 */
+	const int err = ddf_fun_unbind(dev->fun);
+	if (err) {
+		usb_log_error("Failed to unbind USB device '%s': %s",
+		    ddf_fun_get_name(dev->fun), str_error(err));
+		return;
+	}
+
+	/* Remove what driver left behind */
+	fibril_mutex_lock(&dev->guard);
+	device_clean_ep_children(dev, "removing");
+
+	/* Tell the HC to release its resources. */
+	if (ops->device_gone)
+		ops->device_gone(dev);
+
+	/* Check whether the driver didn't forgot EP0 */
+	if (dev->endpoints[0]) {
+		if (ops->endpoint_unregister)
+			ops->endpoint_unregister(dev->endpoints[0]);
+		/* Release the EP0 bus reference */
+		endpoint_del_ref(dev->endpoints[0]);
+	}
+
+	/* Destroy the function, freeing also the device, unlocking mutex. */
+	ddf_fun_destroy(dev->fun);
+}
+
+/**
+ * The user wants this device back online.
+ */
+int bus_device_online(device_t *dev)
+{
+	int rc;
+	assert(dev);
+
+	fibril_mutex_lock(&dev->guard);
+	if (dev->online) {
+		rc = EINVAL;
+		goto err_lock;
+	}
+
+	/* First, tell the HC driver. */
+	const bus_ops_t *ops = dev->bus->ops;
+	if (ops->device_online && (rc = ops->device_online(dev))) {
+		usb_log_warning("Host controller failed to make device '%s' online: %s",
+		    ddf_fun_get_name(dev->fun), str_error(rc));
+		goto err_lock;
+	}
+
+	/* Allow creation of new endpoints and communication with the device. */
+	dev->online = true;
+
+	/* Onlining will need the guard */
+	fibril_mutex_unlock(&dev->guard);
+
+	if ((rc = ddf_fun_online(dev->fun))) {
+		usb_log_warning("Failed to take device '%s' online: %s",
+		    ddf_fun_get_name(dev->fun), str_error(rc));
+		goto err;
+	}
+
+	usb_log_info("USB Device '%s' is now online.", ddf_fun_get_name(dev->fun));
+	return EOK;
+
+err_lock:
+	fibril_mutex_unlock(&dev->guard);
+err:
+	return rc;
+}
+
+/**
+ * The user requested to take this device offline.
+ */
+int bus_device_offline(device_t *dev)
+{
+	int rc;
+	assert(dev);
+
+	/* Make sure we're the one who offlines this device */
+	if (!dev->online) {
+		rc = ENOENT;
+		goto err;
+	}
+
+	/*
+	 * XXX: If the device is removed/offlined just now, this can fail on
+	 * assertion. We most probably need some kind of enum status field to
+	 * make the synchronization work.
+	 */
+	
+	/* Tear down all drivers working with the device. */
+	if ((rc = ddf_fun_offline(dev->fun))) {
+		goto err;
+	}
+
+	fibril_mutex_lock(&dev->guard);
+	dev->online = false;
+	device_clean_ep_children(dev, "offlining");
+
+	/* Tell also the HC driver. */
+	const bus_ops_t *ops = dev->bus->ops;
+	if (ops->device_offline)
+		ops->device_offline(dev);
+
+	fibril_mutex_unlock(&dev->guard);
+	usb_log_info("USB Device '%s' is now offline.", ddf_fun_get_name(dev->fun));
+	return EOK;
+
+err:
+	return rc;
+}
+
+/**
+ * Calculate an index to the endpoint array.
+ * For the default control endpoint 0, it must return 0.
+ * For different arguments, the result is stable but not defined.
+ */
+static size_t bus_endpoint_index(usb_endpoint_t ep, usb_direction_t dir)
+{
+	return 2 * ep + (dir == USB_DIRECTION_OUT);
+}
+
+/**
+ * Create and register new endpoint to the bus.
+ *
+ * @param[in] device The device of which the endpoint shall be created
+ * @param[in] desc Endpoint descriptors as reported by the device
+ * @param[out] out_ep The resulting new endpoint reference, if any. Can be NULL.
+ */
+int bus_endpoint_add(device_t *device, const usb_endpoint_descriptors_t *desc, endpoint_t **out_ep)
+{
+	int err;
+	assert(device);
+
+	bus_t *bus = device->bus;
+
+	if (!bus->ops->endpoint_register)
+		return ENOTSUP;
+
+	endpoint_t *ep;
+	if (bus->ops->endpoint_create) {
+		ep = bus->ops->endpoint_create(device, desc);
+		if (!ep)
+			return ENOMEM;
+	} else {
+		ep = calloc(1, sizeof(endpoint_t));
+		if (!ep)
+			return ENOMEM;
+		endpoint_init(ep, device, desc);
+	}
+
+	/* Bus reference */
+	endpoint_add_ref(ep);
+
+	const size_t idx = bus_endpoint_index(ep->endpoint, ep->direction);
+	if (idx >= ARRAY_SIZE(device->endpoints)) {
+		usb_log_warning("Invalid endpoint description (ep no %u out of "
+		    "bounds)", ep->endpoint);
+		goto drop;
+	}
+
+	if (ep->max_transfer_size == 0) {
+		usb_log_warning("Invalid endpoint description (mps %zu, "
+			"%u packets)", ep->max_packet_size, ep->packets_per_uframe);
+		goto drop;
+	}
+
+	usb_log_debug("Register endpoint %d:%d %s-%s %zuB.",
+	    device->address, ep->endpoint,
+	    usb_str_transfer_type(ep->transfer_type),
+	    usb_str_direction(ep->direction),
+	    ep->max_transfer_size);
+
+	fibril_mutex_lock(&device->guard);
+	if (!device->online && ep->endpoint != 0) {
+		err = EAGAIN;
+	} else if (device->endpoints[idx] != NULL) {
+		err = EEXIST;
+	} else {
+		err = bus->ops->endpoint_register(ep);
+		if (!err)
+			device->endpoints[idx] = ep;
+	}
+	fibril_mutex_unlock(&device->guard);
+	if (err) {
+		endpoint_del_ref(ep);
+		return err;
+	}
+
+	if (out_ep) {
+		/* Exporting reference */
+		endpoint_add_ref(ep);
+		*out_ep = ep;
+	}
+
+	return EOK;
+drop:
+	/* Bus reference */
+	endpoint_del_ref(ep);
+	return EINVAL;
+}
+
+/**
+ * Search for an endpoint. Returns a reference.
+ */
+endpoint_t *bus_find_endpoint(device_t *device, usb_endpoint_t endpoint,
+    usb_direction_t dir)
+{
+	assert(device);
+
+	const size_t idx = bus_endpoint_index(endpoint, dir);
+	const size_t ctrl_idx = bus_endpoint_index(endpoint, USB_DIRECTION_BOTH);
+
+	endpoint_t *ep = NULL;
+
+	fibril_mutex_lock(&device->guard);
+	if (idx < ARRAY_SIZE(device->endpoints))
+		ep = device->endpoints[idx];
+	/*
+	 * If the endpoint was not found, it's still possible it is a control
+	 * endpoint having direction BOTH.
+	 */
+	if (!ep && ctrl_idx < ARRAY_SIZE(device->endpoints)) {
+		ep = device->endpoints[ctrl_idx];
+		if (ep && ep->transfer_type != USB_TRANSFER_CONTROL)
+			ep = NULL;
+	}
+	if (ep) {
+		/* Exporting reference */
+		endpoint_add_ref(ep);
+	}
+	fibril_mutex_unlock(&device->guard);
+	return ep;
+}
+
+/**
+ * Remove an endpoint from the device.
+ */
+int bus_endpoint_remove(endpoint_t *ep)
+{
+	assert(ep);
+	assert(ep->device);
+
+	device_t *device = ep->device;
+	if (!device)
+		return ENOENT;
+
+	bus_t *bus = device->bus;
+
+	if (!bus->ops->endpoint_unregister)
+		return ENOTSUP;
+
+	usb_log_debug("Unregister endpoint %d:%d %s-%s %zuB.",
+	    device->address, ep->endpoint,
+	    usb_str_transfer_type(ep->transfer_type),
+	    usb_str_direction(ep->direction),
+	    ep->max_transfer_size);
+
+	const size_t idx = bus_endpoint_index(ep->endpoint, ep->direction);
+
+	if (idx >= ARRAY_SIZE(device->endpoints))
+		return EINVAL;
+
+	fibril_mutex_lock(&device->guard);
+
+	/* Check whether the endpoint is registered */
+	if (device->endpoints[idx] != ep) {
+		fibril_mutex_unlock(&device->guard);
+		return EINVAL;
+	}
+
+	bus->ops->endpoint_unregister(ep);
+	device->endpoints[idx] = NULL;
+	fibril_mutex_unlock(&device->guard);
+
+	/* Bus reference */
+	endpoint_del_ref(ep);
+
+	return EOK;
+}
+
+/**
+ * Reserve the default address on the bus for the specified device (hub).
+ */
+int bus_reserve_default_address(bus_t *bus, device_t *dev)
+{
+	assert(bus);
+
+	int err;
+	fibril_mutex_lock(&bus->guard);
+	if (bus->default_address_owner != NULL) {
+		err = (bus->default_address_owner == dev) ? EINVAL : EAGAIN;
+	} else {
+		bus->default_address_owner = dev;
+		err = EOK;
+	}
+	fibril_mutex_unlock(&bus->guard);
+	return err;
+}
+
+/**
+ * Release the default address.
+ */
+void bus_release_default_address(bus_t *bus, device_t *dev)
+{
+	assert(bus);
+
+	fibril_mutex_lock(&bus->guard);
+	if (bus->default_address_owner != dev) {
+		usb_log_error("Device %d tried to release default address, "
+		    "which is not reserved for it.", dev->address);
+	} else {
+		bus->default_address_owner = NULL;
+	}
+	fibril_mutex_unlock(&bus->guard);
+}
+
+/**
+ * Initiate a transfer on the bus. Finds the target endpoint, creates
+ * a transfer batch and schedules it.
+ *
+ * @param device Device for which to send the batch
+ * @param target The target of the transfer.
+ * @param direction A direction of the transfer.
+ * @param data A pointer to the data buffer.
+ * @param size Size of the data buffer.
+ * @param setup_data Data to use in the setup stage (Control communication type)
+ * @param on_complete Callback which is called after the batch is complete
+ * @param arg Callback parameter.
+ * @param name Communication identifier (for nicer output).
+ * @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.endpoint, direction);
+	if (ep == NULL) {
+		usb_log_error("Endpoint(%d:%d) not registered for %s.",
+		    device->address, target.endpoint, name);
+		return ENOENT;
+	}
+
+	assert(ep->device == device);
+
+	/*
+	 * This method is already callable from HC only, so we can force these
+	 * conditions harder.
+	 * Invalid values from devices shall be caught on DDF interface already.
+	 */
+	assert(usb_target_is_valid(&target));
+	assert(direction != USB_DIRECTION_BOTH);
+	assert(size == 0 || data != NULL);
+	assert(arg == NULL || on_complete != NULL);
+	assert(name);
+
+	const int err = endpoint_send_batch(ep, target, direction,
+	    data, size, setup_data, on_complete, arg, name);
+
+	/* Temporary reference */
+	endpoint_del_ref(ep);
+
+	return err;
+}
+
+/**
+ * A structure to pass data from the completion callback to the caller.
+ */
+typedef struct {
+	fibril_mutex_t done_mtx;
+	fibril_condvar_t done_cv;
+	bool done;
+
+	size_t transferred_size;
+	int error;
+} sync_data_t;
+
+/**
+ * Callback for finishing the transfer. Wake the issuing thread.
+ */
+static int sync_transfer_complete(void *arg, int error, size_t transferred_size)
+{
+	sync_data_t *d = arg;
+	assert(d);
+	d->transferred_size = transferred_size;
+	d->error = error;
+	fibril_mutex_lock(&d->done_mtx);
+	d->done = true;
+	fibril_condvar_broadcast(&d->done_cv);
+	fibril_mutex_unlock(&d->done_mtx);
+	return EOK;
+}
+
+/**
+ * Issue a transfer on the bus, wait for the result.
+ *
+ * @param device Device for which to send the batch
+ * @param target The target of the transfer.
+ * @param direction A direction of the transfer.
+ * @param data A pointer to the data buffer.
+ * @param size Size of the data buffer.
+ * @param setup_data Data to use in the setup stage (Control communication type)
+ * @param name Communication identifier (for nicer output).
+ */
+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 = false };
+	fibril_mutex_initialize(&sd.done_mtx);
+	fibril_condvar_initialize(&sd.done_cv);
+
+	const int ret = bus_device_send_batch(device, target, direction,
+	    data, size, setup_data, sync_transfer_complete, &sd, name);
+	if (ret != EOK)
+		return ret;
+
+	/*
+	 * Note: There are requests that are completed synchronously. It is not
+	 *       therefore possible to just lock the mutex before and wait.
+	 */
+	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.transferred_size
+		: (ssize_t) sd.error;
+}
+
+/**
+ * @}
+ */
Index: uspace/lib/usbhost/src/ddf_helpers.c
===================================================================
--- uspace/lib/usbhost/src/ddf_helpers.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbhost/src/ddf_helpers.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -31,12 +31,6 @@
  */
 /** @file
- *
- */
-
-#include <usb/classes/classes.h>
-#include <usb/debug.h>
-#include <usb/descriptor.h>
-#include <usb/request.h>
-#include <usb/usb.h>
+ * Helpers to work with the DDF interface.
+ */
 
 #include <adt/list.h>
@@ -47,168 +41,214 @@
 #include <device/hw_res_parsed.h>
 #include <errno.h>
-#include <fibril_synch.h>
-#include <macros.h>
-#include <stdio.h>
-#include <stdlib.h>
 #include <str_error.h>
+#include <usb/classes/classes.h>
+#include <usb/debug.h>
+#include <usb/descriptor.h>
+#include <usb/usb.h>
 #include <usb_iface.h>
+#include <usbhc_iface.h>
+
+#include "bus.h"
+#include "endpoint.h"
 
 #include "ddf_helpers.h"
 
-#define CTRL_PIPE_MIN_PACKET_SIZE 8
-
-typedef struct usb_dev {
-	link_t link;
-	list_t devices;
-	fibril_mutex_t guard;
-	ddf_fun_t *fun;
-	usb_address_t address;
-	usb_speed_t speed;
-	usb_address_t tt_address;
-	unsigned port;
-} usb_dev_t;
-
-typedef struct hc_dev {
-	ddf_fun_t *ctl_fun;
-	hcd_t hcd;
-	usb_dev_t *root_hub;
-} 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 errno_t hcd_ddf_new_device(ddf_dev_t *device, usb_dev_t *hub, unsigned port);
-static errno_t hcd_ddf_remove_device(ddf_dev_t *device, usb_dev_t *hub, unsigned port);
-
-
-/* DDF INTERFACE */
-
-/** Register endpoint interface function.
- * @param fun DDF function.
- * @param address USB address of the device.
- * @param endpoint USB endpoint number to be registered.
- * @param transfer_type Endpoint's transfer type.
- * @param direction USB communication direction the endpoint is capable of.
- * @param max_packet_size Maximu size of packets the endpoint accepts.
- * @param interval Preferred timeout between communication.
+/**
+ * DDF usbhc_iface callback. Passes the endpoint descriptors, fills the pipe
+ * descriptor according to the contents of the endpoint.
+ *
+ * @param[in] fun DDF function of the device in question.
+ * @param[out] pipe_desc The pipe descriptor to be filled.
+ * @param[in] endpoint_desc Endpoint descriptors from the device.
  * @return Error code.
  */
-static errno_t register_endpoint(
-    ddf_fun_t *fun, usb_endpoint_t endpoint,
-    usb_transfer_type_t transfer_type, usb_direction_t direction,
-    size_t max_packet_size, unsigned packets, unsigned interval)
-{
-	assert(fun);
-	hcd_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
-	usb_dev_t *dev = ddf_fun_data_get(fun);
+static errno_t register_endpoint(ddf_fun_t *fun, usb_pipe_desc_t *pipe_desc,
+     const usb_endpoint_descriptors_t *ep_desc)
+{
+	assert(fun);
+	hc_device_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
+	device_t *dev = ddf_fun_data_get(fun);
 	assert(hcd);
-	assert(dev);
-	const size_t size = max_packet_size;
-	const usb_target_t target =
-	    {{.address = dev->address, .endpoint = endpoint}};
-
-	usb_log_debug("Register endpoint %d:%d %s-%s %zuB %ums.\n",
-	    dev->address, endpoint, usb_str_transfer_type(transfer_type),
-	    usb_str_direction(direction), max_packet_size, interval);
-
-	return hcd_add_ep(hcd, target, direction, transfer_type,
-	    max_packet_size, packets, size, dev->tt_address, dev->port);
-}
-
-/** Unregister endpoint interface function.
- * @param fun DDF function.
- * @param address USB address of the endpoint.
- * @param endpoint USB endpoint number.
- * @param direction Communication direction of the enpdoint to unregister.
+	assert(hcd->bus);
+	assert(dev);
+
+	endpoint_t *ep;
+	const int err = bus_endpoint_add(dev, ep_desc, &ep);
+	if (err)
+		return err;
+
+	if (pipe_desc) {
+		pipe_desc->endpoint_no = ep->endpoint;
+		pipe_desc->direction = ep->direction;
+		pipe_desc->transfer_type = ep->transfer_type;
+		pipe_desc->max_transfer_size = ep->max_transfer_size;
+	}
+	endpoint_del_ref(ep);
+
+	return EOK;
+}
+
+ /**
+  * DDF usbhc_iface callback. Unregister endpoint that makes the other end of
+  * the pipe described.
+  *
+  * @param fun DDF function of the device in question.
+  * @param pipe_desc Pipe description.
+  * @return Error code.
+  */
+static errno_t unregister_endpoint(ddf_fun_t *fun, const usb_pipe_desc_t *pipe_desc)
+{
+	assert(fun);
+	hc_device_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
+	device_t *dev = ddf_fun_data_get(fun);
+	assert(hcd);
+	assert(hcd->bus);
+	assert(dev);
+
+	endpoint_t *ep = bus_find_endpoint(dev, pipe_desc->endpoint_no, pipe_desc->direction);
+	if (!ep)
+		return ENOENT;
+
+	return bus_endpoint_remove(ep);
+}
+
+/**
+ * DDF usbhc_iface callback. Calls the respective bus operation directly.
+ *
+ * @param fun DDF function of the device (hub) requesting the address.
+ */
+static errno_t default_address_reservation(ddf_fun_t *fun, bool reserve)
+{
+	assert(fun);
+	hc_device_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
+	device_t *dev = ddf_fun_data_get(fun);
+	assert(hcd);
+	assert(hcd->bus);
+	assert(dev);
+
+	usb_log_debug("Device %d %s default address", dev->address, reserve ? "requested" : "releasing");
+	if (reserve) {
+		return bus_reserve_default_address(hcd->bus, dev);
+	} else {
+		bus_release_default_address(hcd->bus, dev);
+		return EOK;
+	}
+}
+
+/**
+ * DDF usbhc_iface callback. Calls the bus operation directly.
+ *
+ * @param fun DDF function of the device (hub) requesting the address.
+ * @param speed USB speed of the new device
+ */
+static errno_t device_enumerate(ddf_fun_t *fun, unsigned port, usb_speed_t speed)
+{
+	assert(fun);
+	ddf_dev_t *hc = ddf_fun_get_dev(fun);
+	assert(hc);
+	hc_device_t *hcd = dev_to_hcd(hc);
+	assert(hcd);
+	device_t *hub = ddf_fun_data_get(fun);
+	assert(hub);
+
+	errno_t err;
+
+	if (!usb_speed_is_valid(speed))
+		return EINVAL;
+
+	usb_log_debug("Hub %d reported a new %s speed device on port: %u",
+	    hub->address, usb_str_speed(speed), port);
+
+	device_t *dev = hcd_ddf_fun_create(hcd, speed);
+	if (!dev) {
+		usb_log_error("Failed to create USB device function.");
+		return ENOMEM;
+	}
+
+	dev->hub = hub;
+	dev->tier = hub->tier + 1;
+	dev->port = port;
+	dev->speed = speed;
+
+	if ((err = bus_device_enumerate(dev))) {
+		usb_log_error("Failed to initialize USB dev memory structures.");
+		goto err_usb_dev;
+	}
+
+	/* If the driver didn't name the dev when enumerating,
+	 * do it in some generic way.
+	 */
+	if (!ddf_fun_get_name(dev->fun)) {
+		bus_device_set_default_name(dev);
+	}
+
+	if ((err = ddf_fun_bind(dev->fun))) {
+		usb_log_error("Device(%d): Failed to register: %s.", dev->address, str_error(err));
+		goto err_usb_dev;
+	}
+
+	return EOK;
+
+err_usb_dev:
+	hcd_ddf_fun_destroy(dev);
+	return err;
+}
+
+static errno_t device_remove(ddf_fun_t *fun, unsigned port)
+{
+	assert(fun);
+	device_t *hub = ddf_fun_data_get(fun);
+	assert(hub);
+	usb_log_debug("Hub `%s' reported removal of device on port %u",
+	    ddf_fun_get_name(fun), port);
+
+	device_t *victim = NULL;
+
+	fibril_mutex_lock(&hub->guard);
+	list_foreach(hub->devices, link, device_t, it) {
+		if (it->port == port) {
+			victim = it;
+			break;
+		}
+	}
+	fibril_mutex_unlock(&hub->guard);
+
+	if (!victim) {
+		usb_log_warning("Hub '%s' tried to remove non-existent"
+		    " device.", ddf_fun_get_name(fun));
+		return ENOENT;
+	}
+
+	assert(victim->fun);
+	assert(victim->port == port);
+	assert(victim->hub == hub);
+
+	bus_device_gone(victim);
+	return EOK;
+}
+
+/**
+ * Gets description of the device that is calling.
+ *
+ * @param[in] fun Device function.
+ * @param[out] desc Device descriptor to be filled.
  * @return Error code.
  */
-static errno_t unregister_endpoint(
-    ddf_fun_t *fun, usb_endpoint_t endpoint, usb_direction_t direction)
-{
-	assert(fun);
-	hcd_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
-	usb_dev_t *dev = ddf_fun_data_get(fun);
-	assert(hcd);
-	assert(dev);
-	const usb_target_t target =
-	    {{.address = dev->address, .endpoint = endpoint}};
-	usb_log_debug("Unregister endpoint %d:%d %s.\n",
-	    dev->address, endpoint, usb_str_direction(direction));
-	return hcd_remove_ep(hcd, target, direction);
-}
-
-static errno_t reserve_default_address(ddf_fun_t *fun, usb_speed_t speed)
-{
-	assert(fun);
-	hcd_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
-	usb_dev_t *dev = ddf_fun_data_get(fun);
-	assert(hcd);
-	assert(dev);
-
-	usb_log_debug("Device %d requested default address at %s speed\n",
-	    dev->address, usb_str_speed(speed));
-	return hcd_reserve_default_address(hcd, speed);
-}
-
-static errno_t release_default_address(ddf_fun_t *fun)
-{
-	assert(fun);
-	hcd_t *hcd = dev_to_hcd(ddf_fun_get_dev(fun));
-	usb_dev_t *dev = ddf_fun_data_get(fun);
-	assert(hcd);
-	assert(dev);
-
-	usb_log_debug("Device %d released default address\n", dev->address);
-	return hcd_release_default_address(hcd);
-}
-
-static errno_t device_enumerate(ddf_fun_t *fun, unsigned port)
-{
-	assert(fun);
-	ddf_dev_t *ddf_dev = ddf_fun_get_dev(fun);
-	usb_dev_t *dev = ddf_fun_data_get(fun);
-	assert(ddf_dev);
-	assert(dev);
-	usb_log_debug("Hub %d reported a new USB device on port: %u\n",
-	    dev->address, port);
-	return hcd_ddf_new_device(ddf_dev, dev, port);
-}
-
-static errno_t device_remove(ddf_fun_t *fun, unsigned port)
-{
-	assert(fun);
-	ddf_dev_t *ddf_dev = ddf_fun_get_dev(fun);
-	usb_dev_t *dev = ddf_fun_data_get(fun);
-	assert(ddf_dev);
-	assert(dev);
-	usb_log_debug("Hub `%s' reported removal of device on port %u\n",
-	    ddf_fun_get_name(fun), port);
-	return hcd_ddf_remove_device(ddf_dev, dev, port);
-}
-
-/** Gets handle of the respective device.
- *
- * @param[in] fun Device function.
- * @param[out] handle Place to write the handle.
- * @return Error code.
- */
-static errno_t get_my_device_handle(ddf_fun_t *fun, devman_handle_t *handle)
-{
-	assert(fun);
-	if (handle)
-		*handle = ddf_fun_get_handle(fun);
+static errno_t get_device_description(ddf_fun_t *fun, usb_device_desc_t *desc)
+{
+	assert(fun);
+	device_t *dev = ddf_fun_data_get(fun);
+	assert(dev);
+
+	if (!desc)
+		return EOK;
+
+	*desc = (usb_device_desc_t) {
+		.address = dev->address,
+		.depth = dev->tier,
+		.speed = dev->speed,
+		.handle = ddf_fun_get_handle(fun),
+		.iface = -1,
+	};
 	return EOK;
 }
@@ -224,18 +264,26 @@
  * @return Error code.
  */
-static errno_t dev_read(ddf_fun_t *fun, usb_endpoint_t endpoint,
-    uint64_t setup_data, uint8_t *data, size_t size,
-    usbhc_iface_transfer_in_callback_t callback, void *arg)
-{
-	assert(fun);
-	usb_dev_t *usb_dev = ddf_fun_data_get(fun);
-	assert(usb_dev);
-	const usb_target_t target = {{
-	    .address =  usb_dev->address,
-	    .endpoint = endpoint,
-	}};
-	return hcd_send_batch(dev_to_hcd(ddf_fun_get_dev(fun)), target,
-	    USB_DIRECTION_IN, data, size, setup_data, callback, NULL, arg,
-	    "READ");
+static errno_t dev_read(ddf_fun_t *fun, usb_target_t target,
+    uint64_t setup_data, char *data, size_t size,
+    usbhc_iface_transfer_callback_t callback, void *arg)
+{
+	assert(fun);
+	device_t *dev = ddf_fun_data_get(fun);
+	assert(dev);
+
+	target.address = dev->address;
+
+	if (!usb_target_is_valid(&target))
+		return EINVAL;
+
+	if (size > 0 && data == NULL)
+		return EBADMEM;
+
+	if (!callback && arg)
+		return EBADMEM;
+
+	return bus_device_send_batch(dev, target, USB_DIRECTION_IN,
+	    data, size, setup_data,
+	    callback, arg, "READ");
 }
 
@@ -250,17 +298,25 @@
  * @return Error code.
  */
-static errno_t dev_write(ddf_fun_t *fun, usb_endpoint_t endpoint,
-    uint64_t setup_data, const uint8_t *data, size_t size,
-    usbhc_iface_transfer_out_callback_t callback, void *arg)
-{
-	assert(fun);
-	usb_dev_t *usb_dev = ddf_fun_data_get(fun);
-	assert(usb_dev);
-	const usb_target_t target = {{
-	    .address =  usb_dev->address,
-	    .endpoint = endpoint,
-	}};
-	return hcd_send_batch(dev_to_hcd(ddf_fun_get_dev(fun)),
-	    target, USB_DIRECTION_OUT, (uint8_t*)data, size, setup_data, NULL,
+static errno_t dev_write(ddf_fun_t *fun, usb_target_t target,
+    uint64_t setup_data, const char *data, size_t size,
+    usbhc_iface_transfer_callback_t callback, void *arg)
+{
+	assert(fun);
+	device_t *dev = ddf_fun_data_get(fun);
+	assert(dev);
+
+	target.address = dev->address;
+
+	if (!usb_target_is_valid(&target))
+		return EINVAL;
+
+	if (size > 0 && data == NULL)
+		return EBADMEM;
+
+	if (!callback && arg)
+		return EBADMEM;
+
+	return bus_device_send_batch(dev, target, USB_DIRECTION_OUT,
+	    (char *) data, size, setup_data,
 	    callback, arg, "WRITE");
 }
@@ -268,8 +324,10 @@
 /** USB device interface */
 static usb_iface_t usb_iface = {
-	.get_my_device_handle = get_my_device_handle,
-
-	.reserve_default_address = reserve_default_address,
-	.release_default_address = release_default_address,
+	.get_my_description = get_device_description,
+};
+
+/** USB host controller interface */
+static usbhc_iface_t usbhc_iface = {
+	.default_address_reservation = default_address_reservation,
 
 	.device_enumerate = device_enumerate,
@@ -286,86 +344,9 @@
 static ddf_dev_ops_t usb_ops = {
 	.interfaces[USB_DEV_IFACE] = &usb_iface,
+	.interfaces[USBHC_DEV_IFACE] = &usbhc_iface,
 };
 
 
 /* DDF HELPERS */
-
-#define GET_DEVICE_DESC(size) \
-{ \
-	.request_type = SETUP_REQUEST_TYPE_DEVICE_TO_HOST \
-	    | (USB_REQUEST_TYPE_STANDARD << 5) \
-	    | USB_REQUEST_RECIPIENT_DEVICE, \
-	.request = USB_DEVREQ_GET_DESCRIPTOR, \
-	.value = uint16_host2usb(USB_DESCTYPE_DEVICE << 8), \
-	.index = uint16_host2usb(0), \
-	.length = uint16_host2usb(size), \
-};
-
-#define SET_ADDRESS(address) \
-{ \
-	.request_type = SETUP_REQUEST_TYPE_HOST_TO_DEVICE \
-	    | (USB_REQUEST_TYPE_STANDARD << 5) \
-	    | USB_REQUEST_RECIPIENT_DEVICE, \
-	.request = USB_DEVREQ_SET_ADDRESS, \
-	.value = uint16_host2usb(address), \
-	.index = uint16_host2usb(0), \
-	.length = uint16_host2usb(0), \
-};
-
-static errno_t hcd_ddf_add_device(ddf_dev_t *parent, usb_dev_t *hub_dev,
-    unsigned port, usb_address_t address, usb_speed_t speed, const char *name,
-    const match_id_list_t *mids)
-{
-	assert(parent);
-
-	char default_name[10] = { 0 }; /* usbxyz-ss */
-	if (!name) {
-		snprintf(default_name, sizeof(default_name) - 1,
-		    "usb%u-%cs", address, usb_str_speed(speed)[0]);
-		name = default_name;
-	}
-
-	ddf_fun_t *fun = ddf_fun_create(parent, fun_inner, name);
-	if (!fun)
-		return ENOMEM;
-	usb_dev_t *info = ddf_fun_data_alloc(fun, sizeof(usb_dev_t));
-	if (!info) {
-		ddf_fun_destroy(fun);
-		return ENOMEM;
-	}
-	info->address = address;
-	info->speed = speed;
-	info->fun = fun;
-	info->port = port;
-	info->tt_address = hub_dev ? hub_dev->tt_address : -1;
-	link_initialize(&info->link);
-	list_initialize(&info->devices);
-	fibril_mutex_initialize(&info->guard);
-
-	if (hub_dev && hub_dev->speed == USB_SPEED_HIGH && usb_speed_is_11(speed))
-		info->tt_address = hub_dev->address;
-
-	ddf_fun_set_ops(fun, &usb_ops);
-	list_foreach(mids->ids, link, const match_id_t, mid) {
-		ddf_fun_add_match_id(fun, mid->id, mid->score);
-	}
-
-	errno_t ret = ddf_fun_bind(fun);
-	if (ret != EOK) {
-		ddf_fun_destroy(fun);
-		return ret;
-	}
-
-	if (hub_dev) {
-		fibril_mutex_lock(&hub_dev->guard);
-		list_append(&info->link, &hub_dev->devices);
-		fibril_mutex_unlock(&hub_dev->guard);
-	} else {
-		hc_dev_t *hc_dev = dev_to_hc_dev(parent);
-		assert(hc_dev->root_hub == NULL);
-		hc_dev->root_hub = info;
-	}
-	return EOK;
-}
 
 #define ADD_MATCHID_OR_RETURN(list, sc, str, ...) \
@@ -394,5 +375,5 @@
 	assert(l);
 	assert(d);
-	
+
 	if (d->vendor_id != 0) {
 		/* First, with release number. */
@@ -401,5 +382,5 @@
 		    d->vendor_id, d->product_id, (d->device_version >> 8),
 		    (d->device_version & 0xff));
-	
+
 		/* Next, without release number. */
 		ADD_MATCHID_OR_RETURN(l, 90, "usb&vendor=%#04x&product=%#04x",
@@ -415,220 +396,53 @@
 
 	return EOK;
-
-}
-
-static errno_t hcd_ddf_remove_device(ddf_dev_t *device, usb_dev_t *hub,
-    unsigned port)
-{
-	assert(device);
-
-	hcd_t *hcd = dev_to_hcd(device);
-	assert(hcd);
-
-	hc_dev_t *hc_dev = dev_to_hc_dev(device);
-	assert(hc_dev);
-
-	fibril_mutex_lock(&hub->guard);
-
-	usb_dev_t *victim = NULL;
-
-	list_foreach(hub->devices, link, usb_dev_t, it) {
-		if (it->port == port) {
-			victim = it;
-			break;
-		}
-	}
-	if (victim) {
-		assert(victim->port == port);
-		list_remove(&victim->link);
-		fibril_mutex_unlock(&hub->guard);
-		const errno_t ret = ddf_fun_unbind(victim->fun);
-		if (ret == EOK) {
-			usb_address_t address = victim->address;
-			ddf_fun_destroy(victim->fun);
-			hcd_release_address(hcd, address);
-		} else {
-			usb_log_warning("Failed to unbind device `%s': %s\n",
-			    ddf_fun_get_name(victim->fun), str_error(ret));
-		}
-		return EOK;
-	}
-	fibril_mutex_unlock(&hub->guard);
-	return ENOENT;
-}
-
-static errno_t hcd_ddf_new_device(ddf_dev_t *device, usb_dev_t *hub, unsigned port)
-{
-	assert(device);
-
-	hcd_t *hcd = dev_to_hcd(device);
-	assert(hcd);
-
-	usb_speed_t speed = USB_SPEED_MAX;
-
-	/* This checks whether the default address is reserved and gets speed */
-	errno_t ret = usb_bus_get_speed(&hcd->bus, USB_ADDRESS_DEFAULT, &speed);
-	if (ret != EOK) {
-		usb_log_error("Failed to verify speed: %s.", str_error(ret));
-		return ret;
-	}
-
-	usb_log_debug("Found new %s speed USB device.", usb_str_speed(speed));
-
-	static const usb_target_t default_target = {{
-		.address = USB_ADDRESS_DEFAULT,
-		.endpoint = 0,
-	}};
-
-	usb_address_t address;
-	ret = hcd_request_address(hcd, speed, &address);
-	if (ret != EOK) {
-		usb_log_error("Failed to reserve new address: %s.",
-		    str_error(ret));
-		return ret;
-	}
-
-	usb_log_debug("Reserved new address: %d\n", address);
-
-	const usb_target_t target = {{
-		.address = address,
-		.endpoint = 0,
-	}};
-
-	const usb_address_t tt_address = hub ? hub->tt_address : -1;
-
-	/* Add default pipe on default address */
-	usb_log_debug("Device(%d): Adding default target(0:0)\n", address);
-	ret = hcd_add_ep(hcd,
-	    default_target, USB_DIRECTION_BOTH, USB_TRANSFER_CONTROL,
-	    CTRL_PIPE_MIN_PACKET_SIZE, CTRL_PIPE_MIN_PACKET_SIZE, 1,
-	    tt_address, port);
-	if (ret != EOK) {
-		usb_log_error("Device(%d): Failed to add default target: %s.",
-		    address, str_error(ret));
-		hcd_release_address(hcd, address);
-		return ret;
-	}
-
-	/* Get max packet size for default pipe */
-	usb_standard_device_descriptor_t desc = { 0 };
-	const usb_device_request_setup_packet_t get_device_desc_8 =
-	    GET_DEVICE_DESC(CTRL_PIPE_MIN_PACKET_SIZE);
-
-	// TODO CALLBACKS
-	usb_log_debug("Device(%d): Requesting first 8B of device descriptor.",
-	    address);
-	size_t got;
-	ret = hcd_send_batch_sync(hcd, default_target, USB_DIRECTION_IN,
-	    &desc, CTRL_PIPE_MIN_PACKET_SIZE, *(uint64_t *)&get_device_desc_8,
-	    "read first 8 bytes of dev descriptor", &got);
-
-	if (ret == EOK && got != CTRL_PIPE_MIN_PACKET_SIZE) {
-		ret = EOVERFLOW;
-	}
-
-	if (ret != EOK) {
-		usb_log_error("Device(%d): Failed to get 8B of dev descr: %s.",
-		    address, str_error(ret));
-		hcd_remove_ep(hcd, default_target, USB_DIRECTION_BOTH);
-		hcd_release_address(hcd, address);
-		return ret;
-	}
-
-	/* Register EP on the new address */
-	usb_log_debug("Device(%d): Registering control EP.", address);
-	ret = hcd_add_ep(hcd, target, USB_DIRECTION_BOTH, USB_TRANSFER_CONTROL,
-	    ED_MPS_PACKET_SIZE_GET(uint16_usb2host(desc.max_packet_size)),
-	    ED_MPS_TRANS_OPPORTUNITIES_GET(uint16_usb2host(desc.max_packet_size)),
-	    ED_MPS_PACKET_SIZE_GET(uint16_usb2host(desc.max_packet_size)),
-	    tt_address, port);
-	if (ret != EOK) {
-		usb_log_error("Device(%d): Failed to register EP0: %s",
-		    address, str_error(ret));
-		hcd_remove_ep(hcd, default_target, USB_DIRECTION_BOTH);
-		hcd_remove_ep(hcd, target, USB_DIRECTION_BOTH);
-		hcd_release_address(hcd, address);
-		return ret;
-	}
-
-	/* Set new address */
-	const usb_device_request_setup_packet_t set_address =
-	    SET_ADDRESS(target.address);
-
-	usb_log_debug("Device(%d): Setting USB address.", address);
-	ret = hcd_send_batch_sync(hcd, default_target, USB_DIRECTION_OUT,
-	    NULL, 0, *(uint64_t *)&set_address, "set address", &got);
-
-	usb_log_debug("Device(%d): Removing default (0:0) EP.", address);
-	hcd_remove_ep(hcd, default_target, USB_DIRECTION_BOTH);
-
-	if (ret != EOK) {
-		usb_log_error("Device(%d): Failed to set new address: %s.",
-		    address, str_error(ret));
-		hcd_remove_ep(hcd, target, USB_DIRECTION_BOTH);
-		hcd_release_address(hcd, address);
-		return ret;
-	}
-
-	/* Get std device descriptor */
-	const usb_device_request_setup_packet_t get_device_desc =
-	    GET_DEVICE_DESC(sizeof(desc));
-
-	usb_log_debug("Device(%d): Requesting full device descriptor.",
-	    address);
-	ret = hcd_send_batch_sync(hcd, target, USB_DIRECTION_IN,
-	    &desc, sizeof(desc), *(uint64_t *)&get_device_desc,
-	    "read device descriptor", &got);
-	if (ret != EOK) {
-		usb_log_error("Device(%d): Failed to set get dev descriptor: %s",
-		    address, str_error(ret));
-		hcd_remove_ep(hcd, target, USB_DIRECTION_BOTH);
-		hcd_release_address(hcd, target.address);
-		return ret;
-	}
+}
+
+device_t *hcd_ddf_fun_create(hc_device_t *hc, usb_speed_t speed)
+{
+	/* Create DDF function for the new device */
+	ddf_fun_t *fun = ddf_fun_create(hc->ddf_dev, fun_inner, NULL);
+	if (!fun)
+		return NULL;
+
+	ddf_fun_set_ops(fun, &usb_ops);
+
+	/* Create USB device node for the new device */
+	device_t *dev = ddf_fun_data_alloc(fun, hc->bus->device_size);
+	if (!dev) {
+		ddf_fun_destroy(fun);
+		return NULL;
+	}
+
+	bus_device_init(dev, hc->bus);
+	dev->fun = fun;
+	dev->speed = speed;
+	return dev;
+}
+
+void hcd_ddf_fun_destroy(device_t *dev)
+{
+	assert(dev);
+	assert(dev->fun);
+	ddf_fun_destroy(dev->fun);
+}
+
+errno_t hcd_ddf_setup_match_ids(device_t *device, usb_standard_device_descriptor_t *desc)
+{
+	errno_t err;
+	match_id_list_t mids;
+
+	init_match_ids(&mids);
 
 	/* Create match ids from the device descriptor */
-	match_id_list_t mids;
-	init_match_ids(&mids);
-
-	usb_log_debug("Device(%d): Creating match IDs.", address);
-	ret = create_match_ids(&mids, &desc);
-	if (ret != EOK) {
-		usb_log_error("Device(%d): Failed to create match ids: %s",
-		    address, str_error(ret));
-		hcd_remove_ep(hcd, target, USB_DIRECTION_BOTH);
-		hcd_release_address(hcd, target.address);
-		return ret;
-	}
-
-	/* Register device */
-	usb_log_debug("Device(%d): Registering DDF device.", address);
-	ret = hcd_ddf_add_device(device, hub, port, address, speed, NULL, &mids);
-	clean_match_ids(&mids);
-	if (ret != EOK) {
-		usb_log_error("Device(%d): Failed to register: %s.",
-		    address, str_error(ret));
-		hcd_remove_ep(hcd, target, USB_DIRECTION_BOTH);
-		hcd_release_address(hcd, target.address);
-	}
-
-	return ret;
-}
-
-/** Announce root hub to the DDF
- *
- * @param[in] device Host controller ddf device
- * @return Error code
- */
-errno_t hcd_ddf_setup_root_hub(ddf_dev_t *device)
-{
-	assert(device);
-	hcd_t *hcd = dev_to_hcd(device);
-	assert(hcd);
-
-	hcd_reserve_default_address(hcd, hcd->bus.max_speed);
-	const errno_t ret = hcd_ddf_new_device(device, NULL, 0);
-	hcd_release_default_address(hcd);
-	return ret;
+	usb_log_debug("Device(%d): Creating match IDs.", device->address);
+	if ((err = create_match_ids(&mids, desc))) {
+		return err;
+	}
+
+	list_foreach(mids.ids, link, const match_id_t, mid) {
+		ddf_fun_add_match_id(device->fun, mid->id, mid->score);
+	}
+
+	return EOK;
 }
 
@@ -643,21 +457,19 @@
  * This function does all the ddf work for hc driver.
  */
-errno_t hcd_ddf_setup_hc(ddf_dev_t *device, usb_speed_t max_speed,
-    size_t bw, bw_count_func_t bw_count)
+errno_t 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");
+		usb_log_error("Failed to allocate HCD ddf structure.");
 		return ENOMEM;
 	}
-	instance->root_hub = NULL;
-	hcd_init(&instance->hcd, max_speed, bw, bw_count);
+	instance->ddf_dev = device;
 
 	errno_t ret = ENOMEM;
 	instance->ctl_fun = ddf_fun_create(device, fun_exposed, "ctl");
 	if (!instance->ctl_fun) {
-		usb_log_error("Failed to create HCD ddf fun.\n");
+		usb_log_error("Failed to create HCD ddf fun.");
 		goto err_destroy_fun;
 	}
@@ -665,5 +477,5 @@
 	ret = ddf_fun_bind(instance->ctl_fun);
 	if (ret != EOK) {
-		usb_log_error("Failed to bind ctl_fun: %s.\n", str_error(ret));
+		usb_log_error("Failed to bind ctl_fun: %s.", str_error(ret));
 		goto err_destroy_fun;
 	}
@@ -671,5 +483,5 @@
 	ret = ddf_fun_add_to_category(instance->ctl_fun, USB_HC_CATEGORY);
 	if (ret != EOK) {
-		usb_log_error("Failed to add fun to category: %s.\n",
+		usb_log_error("Failed to add fun to category: %s.",
 		    str_error(ret));
 		ddf_fun_unbind(instance->ctl_fun);
@@ -686,15 +498,10 @@
 }
 
-void hcd_ddf_clean_hc(ddf_dev_t *device)
-{
-	assert(device);
-	hc_dev_t *hc = dev_to_hc_dev(device);
-	assert(hc);
-	const errno_t 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
  *
@@ -703,7 +510,7 @@
  * @return Error code.
  */
-errno_t hcd_ddf_enable_interrupt(ddf_dev_t *device, int inum)
-{
-	async_sess_t *parent_sess = ddf_dev_parent_sess_get(device);
+errno_t 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;
@@ -712,8 +519,7 @@
 }
 
-//TODO: Cache parent session in HCD
-errno_t 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);
+errno_t 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;
@@ -726,221 +532,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.
- *
- * @param[out] handle  IRQ capability handle on success.
- *
- * @return Error code.
- */
-errno_t hcd_ddf_setup_interrupts(ddf_dev_t *device,
-    const hw_res_list_parsed_t *hw_res,
-    interrupt_handler_t handler,
-    errno_t (*gen_irq_code)(irq_code_t *, const hw_res_list_parsed_t *, int *),
-    cap_handle_t *handle)
-{
-
-	assert(device);
-	if (!handler || !gen_irq_code)
-		return ENOTSUP;
-
-	irq_code_t irq_code = {0};
-
-	int irq;
-	errno_t ret = gen_irq_code(&irq_code, hw_res, &irq);
-	if (ret != EOK) {
-		usb_log_error("Failed to generate IRQ code: %s.\n",
-		    str_error(ret));
-		return ret;
-	}
-
-	/* Register handler to avoid interrupt lockup */
-	ret = register_interrupt_handler(device, irq, handler,
-	    &irq_code, handle);
-	irq_code_clean(&irq_code);
-	if (ret != EOK) {
-		usb_log_error("Failed to register interrupt handler: %s.\n",
-		    str_error(ret));
-		return ret;
-	}
-
-	/* Enable interrupts */
-	ret = hcd_ddf_enable_interrupt(device, irq);
-	if (ret != EOK) {
-		usb_log_error("Failed to register interrupt handler: %s.\n",
-		    str_error(ret));
-		unregister_interrupt_handler(device, *handle);
-	}
-	return ret;
-}
-
-/** IRQ handling callback, forward status from call to diver structure.
- *
- * @param[in] dev DDF instance of the device to use.
- * @param[in] call Pointer to the call from kernel.
- */
-void ddf_hcd_gen_irq_handler(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 errno_t 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
- */
-errno_t hcd_ddf_add_hc(ddf_dev_t *device, const ddf_hc_driver_t *driver)
-{
-	assert(driver);
-	static const struct { size_t bw; bw_count_func_t bw_count; }bw[] = {
-	    [USB_SPEED_FULL] = { .bw = BANDWIDTH_AVAILABLE_USB11,
-	                         .bw_count = bandwidth_count_usb11 },
-	    [USB_SPEED_HIGH] = { .bw = BANDWIDTH_AVAILABLE_USB11,
-	                         .bw_count = bandwidth_count_usb11 },
-	};
-
-	errno_t ret = EOK;
-	const usb_speed_t speed = driver->hc_speed;
-	if (speed >= ARRAY_SIZE(bw) || bw[speed].bw == 0) {
-		usb_log_error("Driver `%s' reported unsupported speed: %s",
-		    driver->name, usb_str_speed(speed));
-		return ENOTSUP;
-	}
-
-	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, speed, bw[speed].bw, bw[speed].bw_count);
-	if (ret != EOK) {
-		usb_log_error("Failed to setup generic HCD.\n");
-		hw_res_list_parsed_clean(&hw_res);
-		return ret;
-	}
-
-	interrupt_handler_t *irq_handler =
-	    driver->irq_handler ? driver->irq_handler : ddf_hcd_gen_irq_handler;
-	int irq_cap;
-	errno_t irq_ret = hcd_ddf_setup_interrupts(device, &hw_res,
-	    irq_handler, driver->irq_code_gen, &irq_cap);
-	bool irqs_enabled = (irq_ret == EOK);
-	if (irqs_enabled) {
-		usb_log_debug("Hw interrupts enabled.\n");
-	}
-
-	if (driver->claim) {
-		ret = driver->claim(device);
-		if (ret != EOK) {
-			usb_log_error("Failed to claim `%s' for driver `%s'",
-			    ddf_dev_get_name(device), driver->name);
-			return ret;
-		}
-	}
-
-
-	/* Init hw driver */
-	hcd_t *hcd = dev_to_hcd(device);
-	ret = driver->init(hcd, &hw_res, irqs_enabled);
-	hw_res_list_parsed_clean(&hw_res);
-	if (ret != EOK) {
-		usb_log_error("Failed to init HCD: %s.\n", str_error(ret));
-		goto irq_unregister;
-	}
-
-	/* 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 irq_unregister;
-		}
-		fibril_add_ready(hcd->polling_fibril);
-		usb_log_warning("Failed to enable interrupts: %s."
-		    " Falling back to polling.\n", str_error(irq_ret));
-	}
-
-	/*
-	 * Creating root hub registers a new USB device so HC
-	 * needs to be ready at this time.
-	 */
-	ret = hcd_ddf_setup_root_hub(device);
-	if (ret != EOK) {
-		usb_log_error("Failed to setup HC root hub: %s.\n",
-		    str_error(ret));
-		driver->fini(dev_to_hcd(device));
-irq_unregister:
-		/* Unregistering non-existent should be ok */
-		unregister_interrupt_handler(device, irq_cap);
-		hcd_ddf_clean_hc(device);
-		return ret;
-	}
-
-	usb_log_info("Controlling new `%s' device `%s'.\n",
-	    driver->name, ddf_dev_get_name(device));
-	return EOK;
-}
 /**
  * @}
Index: uspace/lib/usbhost/src/dma_buffer.c
===================================================================
--- uspace/lib/usbhost/src/dma_buffer.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/lib/usbhost/src/dma_buffer.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2013 Jan Vesely
+ * Copyright (c) 2017 Ondrej Hlavaty <aearsis@eideo.cz>
+ * 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 libusbhost
+ * @{
+ */
+/** @file
+ */
+
+#include <align.h>
+#include <as.h>
+#include <ddi.h>
+#include <stddef.h>
+
+#include "dma_buffer.h"
+
+dma_policy_t dma_policy_default = {
+	.use64 = false,
+	.alignment = PAGE_SIZE,
+};
+
+/**
+ * Allocate a DMA buffer.
+ *
+ * @param[in] db dma_buffer_t structure to fill
+ * @param[in] policy dma_policy_t structure to guide
+ * @param[in] size Size of the required memory space
+ * @return Error code.
+ */
+int dma_buffer_alloc_policy(dma_buffer_t *db, size_t size, dma_policy_t policy)
+{
+	assert(db);
+
+	if (policy.alignment > PAGE_SIZE)
+		return EINVAL;
+
+	const size_t aligned_size = ALIGN_UP(size, policy.alignment);
+	const size_t real_size = ALIGN_UP(aligned_size, PAGE_SIZE);
+	const uintptr_t flags = policy.use64 ? 0 : DMAMEM_4GiB;
+
+	uintptr_t phys;
+	void *address = AS_AREA_ANY;
+
+	const int ret = dmamem_map_anonymous(real_size,
+	    flags, AS_AREA_READ | AS_AREA_WRITE, 0,
+	    &phys, &address);
+
+	if (ret == EOK) {
+		/* Poison, accessing it should be enough to make sure
+		 * the location is mapped, but poison works better */
+		memset(address, 0x5, real_size);
+		db->virt = address;
+		db->phys = phys;
+	}
+	return ret;
+}
+
+/**
+ * Allocate a DMA buffer using the default policy.
+ *
+ * @param[in] db dma_buffer_t structure to fill
+ * @param[in] size Size of the required memory space
+ * @return Error code.
+ */
+int dma_buffer_alloc(dma_buffer_t *db, size_t size)
+{
+	return dma_buffer_alloc_policy(db, size, dma_policy_default);
+}
+
+
+/**
+ * Free a DMA buffer.
+ *
+ * @param[in] db dma_buffer_t structure buffer of which will be freed
+ */
+void dma_buffer_free(dma_buffer_t *db)
+{
+	if (db->virt) {
+		dmamem_unmap_anonymous(db->virt);
+		db->virt = NULL;
+		db->phys = 0;
+	}
+}
+
+/**
+ * Convert a pointer inside a buffer to physical address.
+ *
+ * @param[in] db Buffer at which virt is pointing
+ * @param[in] virt Pointer somewhere inside db
+ */
+uintptr_t dma_buffer_phys(const dma_buffer_t *db, void *virt)
+{
+	return db->phys + (virt - db->virt);
+}
+
+/**
+ * @}
+ */
Index: uspace/lib/usbhost/src/endpoint.c
===================================================================
--- uspace/lib/usbhost/src/endpoint.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbhost/src/endpoint.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -1,4 +1,5 @@
 /*
  * Copyright (c) 2011 Jan Vesely
+ * Copyright (c) 2017 Ondrej Hlavaty <aearsis@eideo.cz>
  * All rights reserved.
  *
@@ -34,159 +35,245 @@
  */
 
-#include <usb/host/endpoint.h>
-
 #include <assert.h>
+#include <atomic.h>
+#include <mem.h>
 #include <stdlib.h>
-#include <atomic.h>
-
-/** Allocate ad initialize endpoint_t structure.
- * @param address USB address.
- * @param endpoint USB endpoint number.
- * @param direction Communication direction.
- * @param type USB transfer type.
- * @param speed Communication speed.
- * @param max_packet_size Maximum size of data packets.
- * @param bw Required bandwidth.
- * @return Pointer to initialized endpoint_t structure, NULL on failure.
- */
-endpoint_t * endpoint_create(usb_address_t address, usb_endpoint_t endpoint,
-    usb_direction_t direction, usb_transfer_type_t type, usb_speed_t speed,
-    size_t max_packet_size, unsigned packets, size_t bw,
-    usb_address_t tt_address, unsigned tt_p)
-{
-	endpoint_t *instance = malloc(sizeof(endpoint_t));
-	if (instance) {
-		atomic_set(&instance->refcnt, 0);
-		instance->address = address;
-		instance->endpoint = endpoint;
-		instance->direction = direction;
-		instance->transfer_type = type;
-		instance->speed = speed;
-		instance->max_packet_size = max_packet_size;
-		instance->packets = packets;
-		instance->bandwidth = bw;
-		instance->toggle = 0;
-		instance->active = false;
-		instance->tt.address = tt_address;
-		instance->tt.port = tt_p;
-		instance->hc_data.data = NULL;
-		instance->hc_data.toggle_get = NULL;
-		instance->hc_data.toggle_set = NULL;
-		link_initialize(&instance->link);
-		fibril_mutex_initialize(&instance->guard);
-		fibril_condvar_initialize(&instance->avail);
-	}
-	return instance;
-}
-
-/** Properly dispose of endpoint_t structure.
- * @param instance endpoint_t structure.
- */
-void endpoint_destroy(endpoint_t *instance)
-{
-	assert(instance);
-	assert(!instance->active);
-	assert(instance->hc_data.data == NULL);
-	free(instance);
-}
-
-void endpoint_add_ref(endpoint_t *instance)
-{
-	atomic_inc(&instance->refcnt);
-}
-
-void endpoint_del_ref(endpoint_t *instance)
-{
-	if (atomic_predec(&instance->refcnt) == 0)
-		endpoint_destroy(instance);
-}
-
-/** Set device specific data and hooks.
- * @param instance endpoint_t structure.
- * @param data device specific data.
- * @param toggle_get Hook to call when retrieving value of toggle bit.
- * @param toggle_set Hook to call when setting the value of toggle bit.
- */
-void endpoint_set_hc_data(endpoint_t *instance,
-    void *data, int (*toggle_get)(void *), void (*toggle_set)(void *, int))
-{
-	assert(instance);
-	fibril_mutex_lock(&instance->guard);
-	instance->hc_data.data = data;
-	instance->hc_data.toggle_get = toggle_get;
-	instance->hc_data.toggle_set = toggle_set;
-	fibril_mutex_unlock(&instance->guard);
-}
-
-/** Clear device specific data and hooks.
- * @param instance endpoint_t structure.
- * @note This function does not free memory pointed to by data pointer.
- */
-void endpoint_clear_hc_data(endpoint_t *instance)
-{
-	assert(instance);
-	endpoint_set_hc_data(instance, NULL, NULL, NULL);
-}
-
-/** Mark the endpoint as active and block access for further fibrils.
- * @param instance endpoint_t structure.
- */
-void endpoint_use(endpoint_t *instance)
-{
-	assert(instance);
-	/* Add reference for active endpoint. */
-	endpoint_add_ref(instance);
-	fibril_mutex_lock(&instance->guard);
-	while (instance->active)
-		fibril_condvar_wait(&instance->avail, &instance->guard);
-	instance->active = true;
-	fibril_mutex_unlock(&instance->guard);
-}
-
-/** Mark the endpoint as inactive and allow access for further fibrils.
- * @param instance endpoint_t structure.
- */
-void endpoint_release(endpoint_t *instance)
-{
-	assert(instance);
-	fibril_mutex_lock(&instance->guard);
-	instance->active = false;
-	fibril_mutex_unlock(&instance->guard);
-	fibril_condvar_signal(&instance->avail);
-	/* Drop reference for active endpoint. */
-	endpoint_del_ref(instance);
-}
-
-/** Get the value of toggle bit.
- * @param instance endpoint_t structure.
- * @note Will use provided hook.
- */
-int endpoint_toggle_get(endpoint_t *instance)
-{
-	assert(instance);
-	fibril_mutex_lock(&instance->guard);
-	if (instance->hc_data.toggle_get)
-		instance->toggle =
-		    instance->hc_data.toggle_get(instance->hc_data.data);
-	const int ret = instance->toggle;
-	fibril_mutex_unlock(&instance->guard);
+#include <str_error.h>
+#include <usb/debug.h>
+#include <usb/descriptor.h>
+#include <usb/host/hcd.h>
+#include <usb/host/utility.h>
+
+#include "usb_transfer_batch.h"
+#include "bus.h"
+
+#include "endpoint.h"
+
+/**
+ * Initialize provided endpoint structure.
+ */
+void endpoint_init(endpoint_t *ep, device_t *dev, const usb_endpoint_descriptors_t *desc)
+{
+	memset(ep, 0, sizeof(endpoint_t));
+
+	assert(dev);
+	ep->device = dev;
+
+	atomic_set(&ep->refcnt, 0);
+	fibril_condvar_initialize(&ep->avail);
+
+	ep->endpoint = USB_ED_GET_EP(desc->endpoint);
+	ep->direction = USB_ED_GET_DIR(desc->endpoint);
+	ep->transfer_type = USB_ED_GET_TRANSFER_TYPE(desc->endpoint);
+	ep->max_packet_size = USB_ED_GET_MPS(desc->endpoint);
+	ep->packets_per_uframe = USB_ED_GET_ADD_OPPS(desc->endpoint) + 1;
+
+	/** Direction both is our construct never present in descriptors */
+	if (ep->transfer_type == USB_TRANSFER_CONTROL)
+		ep->direction = USB_DIRECTION_BOTH;
+
+	ep->max_transfer_size = ep->max_packet_size * ep->packets_per_uframe;
+}
+
+/**
+ * Get the bus endpoint belongs to.
+ */
+static inline const bus_ops_t *get_bus_ops(endpoint_t *ep)
+{
+	return ep->device->bus->ops;
+}
+
+/**
+ * Increase the reference count on endpoint.
+ */
+void endpoint_add_ref(endpoint_t *ep)
+{
+	atomic_inc(&ep->refcnt);
+}
+
+/**
+ * Call the desctruction callback. Default behavior is to free the memory directly.
+ */
+static inline void endpoint_destroy(endpoint_t *ep)
+{
+	const bus_ops_t *ops = get_bus_ops(ep);
+	if (ops->endpoint_destroy) {
+		ops->endpoint_destroy(ep);
+	} else {
+		assert(ep->active_batch == NULL);
+
+		/* Assume mostly the eps will be allocated by malloc. */
+		free(ep);
+	}
+}
+
+/**
+ * Decrease the reference count.
+ */
+void endpoint_del_ref(endpoint_t *ep)
+{
+	if (atomic_predec(&ep->refcnt) == 0) {
+		endpoint_destroy(ep);
+	}
+}
+
+/**
+ * Mark the endpoint as online. Supply a guard to be used for this endpoint
+ * synchronization.
+ */
+void endpoint_set_online(endpoint_t *ep, fibril_mutex_t *guard)
+{
+	ep->guard = guard;
+	ep->online = true;
+}
+
+/**
+ * Mark the endpoint as offline. All other fibrils waiting to activate this
+ * endpoint will be interrupted.
+ */
+void endpoint_set_offline_locked(endpoint_t *ep)
+{
+	assert(ep);
+	assert(fibril_mutex_is_locked(ep->guard));
+
+	ep->online = false;
+	fibril_condvar_broadcast(&ep->avail);
+}
+
+/**
+ * Wait until a transfer finishes. Can be used even when the endpoint is
+ * offline (and is interrupted by the endpoint going offline).
+ */
+void endpoint_wait_timeout_locked(endpoint_t *ep, suseconds_t timeout)
+{
+	assert(ep);
+	assert(fibril_mutex_is_locked(ep->guard));
+
+	if (ep->active_batch == NULL)
+		return;
+
+	fibril_condvar_wait_timeout(&ep->avail, ep->guard, timeout);
+}
+
+/**
+ * Mark the endpoint as active and block access for further fibrils. If the
+ * endpoint is already active, it will block on ep->avail condvar.
+ *
+ * Call only under endpoint guard. After you activate the endpoint and release
+ * the guard, you must assume that particular transfer is already
+ * finished/aborted.
+ *
+ * Activation and deactivation is not done by the library to maximize
+ * performance. The HC might want to prepare some memory buffers prior to
+ * interfering with other world.
+ *
+ * @param batch Transfer batch this endpoint is blocked by.
+ */
+int endpoint_activate_locked(endpoint_t *ep, usb_transfer_batch_t *batch)
+{
+	assert(ep);
+	assert(batch);
+	assert(batch->ep == ep);
+	assert(ep->guard);
+	assert(fibril_mutex_is_locked(ep->guard));
+
+	while (ep->online && ep->active_batch != NULL)
+		fibril_condvar_wait(&ep->avail, ep->guard);
+
+	if (!ep->online)
+		return EINTR;
+
+	assert(ep->active_batch == NULL);
+	ep->active_batch = batch;
+	return EOK;
+}
+
+/**
+ * Mark the endpoint as inactive and allow access for further fibrils.
+ */
+void endpoint_deactivate_locked(endpoint_t *ep)
+{
+	assert(ep);
+	assert(fibril_mutex_is_locked(ep->guard));
+
+	ep->active_batch = NULL;
+	fibril_condvar_signal(&ep->avail);
+}
+
+/**
+ * Initiate a transfer on an endpoint. Creates a transfer batch, checks the
+ * bandwidth requirements and schedules the batch.
+ *
+ * @param endpoint Endpoint for which to send the batch
+ * @param target The target of the transfer.
+ * @param direction A direction of the transfer.
+ * @param data A pointer to the data buffer.
+ * @param size Size of the data buffer.
+ * @param setup_data Data to use in the setup stage (Control communication type)
+ * @param on_complete Callback which is called after the batch is complete
+ * @param arg Callback parameter.
+ * @param name Communication identifier (for nicer output).
+ */
+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)
+{
+	if (!ep)
+		return EBADMEM;
+
+	if (ep->transfer_type == USB_TRANSFER_CONTROL) {
+		usb_log_debug("%s %d:%d %zu/%zuB, setup %#016" PRIx64, name,
+		    target.address, target.endpoint, size, ep->max_packet_size,
+		    setup_data);
+	} else {
+		usb_log_debug("%s %d:%d %zu/%zuB", name, target.address,
+		    target.endpoint, size, ep->max_packet_size);
+	}
+
+	device_t * const device = ep->device;
+	if (!device) {
+		usb_log_warning("Endpoint detached");
+		return EAGAIN;
+	}
+
+	const bus_ops_t *ops = device->bus->ops;
+	if (!ops->batch_schedule) {
+		usb_log_error("HCD does not implement scheduler.");
+		return ENOTSUP;
+	}
+
+	/** Limit transfers with reserved bandwidth to the amount reserved */
+	if ((ep->transfer_type == USB_TRANSFER_INTERRUPT
+	    || ep->transfer_type == USB_TRANSFER_ISOCHRONOUS)
+	    && size > ep->max_transfer_size)
+		return ENOSPC;
+
+	/* Offline devices don't schedule transfers other than on EP0. */
+	if (!device->online && ep->endpoint > 0)
+		return EAGAIN;
+
+	usb_transfer_batch_t *batch = usb_transfer_batch_create(ep);
+	if (!batch) {
+		usb_log_error("Failed to create transfer batch.");
+		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;
+
+	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;
 }
 
-/** Set the value of toggle bit.
- * @param instance endpoint_t structure.
- * @note Will use provided hook.
- */
-void endpoint_toggle_set(endpoint_t *instance, int toggle)
-{
-	assert(instance);
-	assert(toggle == 0 || toggle == 1);
-	fibril_mutex_lock(&instance->guard);
-	instance->toggle = toggle;
-	if (instance->hc_data.toggle_set)
-		instance->hc_data.toggle_set(instance->hc_data.data, toggle);
-	fibril_mutex_unlock(&instance->guard);
-}
-
 /**
  * @}
Index: uspace/lib/usbhost/src/hcd.c
===================================================================
--- uspace/lib/usbhost/src/hcd.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbhost/src/hcd.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -32,258 +32,338 @@
 /** @file
  *
- */
-
-#include <usb/debug.h>
-#include <usb/request.h>
+ * Host controller driver framework. Encapsulates DDF device of HC to an
+ * hc_device_t, which is passed to driver implementing hc_driver_t.
+ */
 
 #include <assert.h>
 #include <async.h>
+#include <ddf/interrupt.h>
 #include <errno.h>
+#include <macros.h>
+#include <str_error.h>
+#include <usb/debug.h>
+#include <usb/descriptor.h>
+#include <usb/request.h>
 #include <usb_iface.h>
 
+#include "bus.h"
+#include "ddf_helpers.h"
+#include "endpoint.h"
+#include "usb_transfer_batch.h"
+
 #include "hcd.h"
 
-/** Calls ep_add_hook upon endpoint registration.
- * @param ep Endpoint to be registered.
- * @param arg hcd_t in disguise.
- * @return Error code.
- */
-static errno_t register_helper(endpoint_t *ep, void *arg)
-{
-	hcd_t *hcd = arg;
-	assert(ep);
+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_online,
+	.fun_offline = hc_fun_offline,
+};
+
+static const hc_driver_t *hc_driver;
+
+/**
+ * The main HC driver routine.
+ */
+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. Call the bus operation.
+ *
+ * Currently, there is a bus ops lookup to find the interrupt handler. So far,
+ * the mechanism is too flexible, as it allows different instances of HC to
+ * have different IRQ handlers, disallowing us to optimize the lookup here.
+ * TODO: Make the bus mechanism less flexible in irq handling and remove the
+ * lookup.
+ */
+static void irq_handler(ipc_call_t *call, ddf_dev_t *dev)
+{
+	assert(dev);
+	hc_device_t *hcd = dev_to_hcd(dev);
+
+	const uint32_t status = IPC_GET_ARG1(*call);
+	hcd->bus->ops->interrupt(hcd->bus, status);
+}
+
+/**
+ * Worker for the HW interrupt replacement fibril.
+ */
+static errno_t interrupt_polling(void *arg)
+{
+	bus_t *bus = arg;
+	assert(bus);
+
+	if (!bus->ops->interrupt || !bus->ops->status)
+		return ENOTSUP;
+
+	uint32_t status = 0;
+	while (bus->ops->status(bus, &status) == EOK) {
+		bus->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;
+}
+
+/**
+ * Clean the IRQ code bottom-half.
+ */
+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 an interrupt handler. If there is a callback to setup the bottom half,
+ * invoke it and register it. Register for notifications.
+ *
+ * If this method fails, a polling fibril is started instead.
+ *
+ * @param[in] hcd Host controller device.
+ * @param[in] hw_res Resources to be used.
+ *
+ * @return IRQ capability handle on success.
+ * @return Negative error code.
+ */
+static errno_t hcd_ddf_setup_interrupts(hc_device_t *hcd, const hw_res_list_parsed_t *hw_res)
+{
 	assert(hcd);
-	if (hcd->ops.ep_add_hook)
-		return hcd->ops.ep_add_hook(hcd, ep);
+	irq_code_t irq_code = {0};
+
+	if (!hc_driver->irq_code_gen)
+		return ENOTSUP;
+
+	int irq;
+	errno_t ret;
+	ret = hc_driver->irq_code_gen(&irq_code, hcd, hw_res, &irq);
+	if (ret != EOK) {
+		usb_log_error("Failed to generate IRQ code: %s.",
+		    str_error(irq));
+		return irq;
+	}
+
+	/* Register handler to avoid interrupt lockup */
+	int irq_cap;
+	ret = register_interrupt_handler(hcd->ddf_dev, irq, irq_handler, &irq_code, &irq_cap);
+	irq_code_clean(&irq_code);
+	if (ret != EOK) {
+		usb_log_error("Failed to register interrupt handler: %s.",
+		    str_error(irq_cap));
+		return irq_cap;
+	}
+
+	/* Enable interrupts */
+	ret = hcd_ddf_enable_interrupt(hcd, irq);
+	if (ret != EOK) {
+		usb_log_error("Failed to enable interrupts: %s.",
+		    str_error(ret));
+		unregister_interrupt_handler(hcd->ddf_dev, irq_cap);
+		return ret;
+	}
+	return irq_cap;
+}
+
+/**
+ * Initialize HC in memory of the driver.
+ *
+ * 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
+ *
+ * @param device DDF instance of the device to use
+ * @return Error code
+ */
+errno_t hc_dev_add(ddf_dev_t *device)
+{
+	errno_t 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.");
+		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.", 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.");
+		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.");
+	}
+
+	/* 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.", str_error(ret));
+		goto err_irq;
+	}
+
+	const bus_ops_t *ops = hcd->bus->ops;
+
+	/* Need working irq replacement to setup root hub */
+	if (hcd->irq_cap < 0 && ops->status) {
+		hcd->polling_fibril = fibril_create(interrupt_polling, hcd->bus);
+		if (!hcd->polling_fibril) {
+			usb_log_error("Failed to create polling fibril");
+			ret = ENOMEM;
+			goto err_started;
+		}
+		fibril_add_ready(hcd->polling_fibril);
+		usb_log_warning("Failed to enable interrupts: %s."
+		    " Falling back to polling.", 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.",
+		    str_error(ret));
+		goto err_polling;
+	}
+
+	usb_log_info("Controlling new `%s' device `%s'.",
+	   hc_driver->name, ddf_dev_get_name(device));
 	return EOK;
-}
-
-/** Calls ep_remove_hook upon endpoint removal.
- * @param ep Endpoint to be unregistered.
- * @param arg hcd_t in disguise.
- */
-static void unregister_helper(endpoint_t *ep, void *arg)
-{
-	hcd_t *hcd = arg;
-	assert(ep);
-	assert(hcd);
-	if (hcd->ops.ep_remove_hook)
-		hcd->ops.ep_remove_hook(hcd, ep);
-}
-
-/** Calls ep_remove_hook upon endpoint removal. Prints warning.
- *  * @param ep Endpoint to be unregistered.
- *   * @param arg hcd_t in disguise.
- *    */
-static void unregister_helper_warn(endpoint_t *ep, void *arg)
-{
-        assert(ep);
-        usb_log_warning("Endpoint %d:%d %s was left behind, removing.\n",
-            ep->address, ep->endpoint, usb_str_direction(ep->direction));
-	unregister_helper(ep, arg);
-}
-
-
-/** 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, usb_speed_t max_speed, size_t bandwidth,
-    bw_count_func_t bw_count)
-{
-	assert(hcd);
-	usb_bus_init(&hcd->bus, bandwidth, bw_count, max_speed);
-
-	hcd_set_implementation(hcd, NULL, NULL);
-}
-
-errno_t hcd_request_address(hcd_t *hcd, usb_speed_t speed, usb_address_t *address)
-{
-	assert(hcd);
-	return usb_bus_request_address(&hcd->bus, address, false, speed);
-}
-
-errno_t hcd_release_address(hcd_t *hcd, usb_address_t address)
-{
-	assert(hcd);
-	return usb_bus_remove_address(&hcd->bus, address,
-	    unregister_helper_warn, hcd);
-}
-
-errno_t hcd_reserve_default_address(hcd_t *hcd, usb_speed_t speed)
-{
-	assert(hcd);
-	usb_address_t address = 0;
-	return usb_bus_request_address(&hcd->bus, &address, true, speed);
-}
-
-errno_t hcd_add_ep(hcd_t *hcd, usb_target_t target, usb_direction_t dir,
-    usb_transfer_type_t type, size_t max_packet_size, unsigned packets,
-    size_t size, usb_address_t tt_address, unsigned tt_port)
-{
-	assert(hcd);
-	return usb_bus_add_ep(&hcd->bus, target.address,
-	    target.endpoint, dir, type, max_packet_size, packets, size,
-	    register_helper, hcd, tt_address, tt_port);
-}
-
-errno_t hcd_remove_ep(hcd_t *hcd, usb_target_t target, usb_direction_t dir)
-{
-	assert(hcd);
-	return usb_bus_remove_ep(&hcd->bus, target.address,
-	    target.endpoint, dir, unregister_helper, hcd);
-}
-
-
-typedef struct {
-	void *original_data;
-	usbhc_iface_transfer_out_callback_t original_callback;
-	usb_target_t target;
-	hcd_t *hcd;
-} toggle_t;
-
-static void toggle_reset_callback(errno_t retval, void *arg)
-{
-	assert(arg);
-	toggle_t *toggle = arg;
-	if (retval == EOK) {
-		usb_log_debug2("Reseting toggle on %d:%d.\n",
-		    toggle->target.address, toggle->target.endpoint);
-		usb_bus_reset_toggle(&toggle->hcd->bus,
-		    toggle->target, toggle->target.endpoint == 0);
-	}
-
-	toggle->original_callback(retval, toggle->original_data);
-}
-
-/** Prepare generic usb_transfer_batch and schedule it.
- * @param hcd Host controller driver.
- * @param fun DDF fun
- * @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.
- */
-errno_t hcd_send_batch(
-    hcd_t *hcd, usb_target_t target, usb_direction_t direction,
-    void *data, size_t size, uint64_t setup_data,
-    usbhc_iface_transfer_in_callback_t in,
-    usbhc_iface_transfer_out_callback_t out, void *arg, const char* name)
-{
-	assert(hcd);
-
-	endpoint_t *ep = usb_bus_find_ep(&hcd->bus,
-	    target.address, target.endpoint, direction);
-	if (ep == NULL) {
-		usb_log_error("Endpoint(%d:%d) not registered for %s.\n",
-		    target.address, target.endpoint, name);
-		return ENOENT;
-	}
-
-	usb_log_debug2("%s %d:%d %zu(%zu).\n",
-	    name, target.address, target.endpoint, size, ep->max_packet_size);
-
-	const size_t bw = bandwidth_count_usb11(
-	    ep->speed, ep->transfer_type, size, ep->max_packet_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->address, ep->endpoint, name, bw, ep->bandwidth);
-		return ENOSPC;
-	}
-	if (!hcd->ops.schedule) {
-		usb_log_error("HCD does not implement scheduler.\n");
-		return ENOTSUP;
-	}
-
-	/* Check for commands that reset toggle bit */
-	if (ep->transfer_type == USB_TRANSFER_CONTROL) {
-		const int reset_toggle = usb_request_needs_toggle_reset(
-		    (usb_device_request_setup_packet_t *) &setup_data);
-		if (reset_toggle >= 0) {
-			assert(out);
-			toggle_t *toggle = malloc(sizeof(toggle_t));
-			if (!toggle)
-				return ENOMEM;
-			toggle->target.address = target.address;
-			toggle->target.endpoint = reset_toggle;
-			toggle->original_callback = out;
-			toggle->original_data = arg;
-			toggle->hcd = hcd;
-
-			arg = toggle;
-			out = toggle_reset_callback;
-		}
-	}
-
-	usb_transfer_batch_t *batch = usb_transfer_batch_create(
-	    ep, data, size, setup_data, in, out, arg);
-	if (!batch) {
-		usb_log_error("Failed to create transfer batch.\n");
-		return ENOMEM;
-	}
-
-	const errno_t ret = hcd->ops.schedule(hcd, batch);
-	if (ret != EOK)
-		usb_transfer_batch_destroy(batch);
-
-	/* Drop our own reference to ep. */
-	endpoint_del_ref(ep);
-
+
+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;
 }
 
-typedef struct {
-	volatile unsigned done;
-	errno_t ret;
-	size_t size;
-} sync_data_t;
-
-static void transfer_in_cb(errno_t ret, size_t size, void* data)
-{
-	sync_data_t *d = data;
-	assert(d);
-	d->ret = ret;
-	d->done = 1;
-	d->size = size;
-}
-
-static void transfer_out_cb(errno_t ret, void* data)
-{
-	sync_data_t *d = data;
-	assert(data);
-	d->ret = ret;
-	d->done = 1;
-}
-
-/** this is really ugly version of sync usb communication */
-errno_t hcd_send_batch_sync(
-    hcd_t *hcd, usb_target_t target, usb_direction_t dir,
-    void *data, size_t size, uint64_t setup_data, const char* name, size_t *out_size)
-{
-	assert(hcd);
-	sync_data_t sd = { .done = 0, .ret = EBUSY, .size = size };
-
-	const errno_t ret = hcd_send_batch(hcd, target, dir, data, size, setup_data,
-	    dir == USB_DIRECTION_IN ? transfer_in_cb : NULL,
-	    dir == USB_DIRECTION_OUT ? transfer_out_cb : NULL, &sd, name);
-	if (ret != EOK)
-		return ret;
-
-	while (!sd.done) {
-		async_usleep(1000);
-	}
-
-	if (sd.ret == EOK)
-		*out_size = sd.size;
-	return sd.ret;
+errno_t hc_dev_remove(ddf_dev_t *dev)
+{
+	errno_t 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;
+}
+
+errno_t hc_dev_gone(ddf_dev_t *dev)
+{
+	errno_t 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;
+}
+
+errno_t 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);
 }
 
Index: uspace/lib/usbhost/src/usb2_bus.c
===================================================================
--- uspace/lib/usbhost/src/usb2_bus.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/lib/usbhost/src/usb2_bus.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * Copyright (c) 2017 Ondrej Hlavaty <aearsis@eideo.cz>
+ * 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 libusbhost
+ * @{
+ */
+/** @file
+ *
+ * A bus_t implementation for USB 2 and lower. Implements USB 2 enumeration and
+ * configurable bandwidth counting.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <macros.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <str_error.h>
+#include <usb/debug.h>
+#include <usb/descriptor.h>
+#include <usb/request.h>
+#include <usb/host/utility.h>
+#include <usb/usb.h>
+
+#include "endpoint.h"
+#include "ddf_helpers.h"
+
+#include "usb2_bus.h"
+
+/**
+ * Request a new address. A free address is found and marked as occupied.
+ *
+ * There's no need to synchronize this method, because it is called only with
+ * default address reserved.
+ *
+ * @param bus usb_device_manager
+ * @param addr Pointer to requested address value, place to store new address
+ */
+static int request_address(usb2_bus_helper_t *helper, usb_address_t *addr)
+{
+	// Find a free address
+	usb_address_t new_address = helper->last_address;
+	do {
+		new_address = (new_address + 1) % USB_ADDRESS_COUNT;
+		if (new_address == USB_ADDRESS_DEFAULT)
+			new_address = 1;
+		if (new_address == helper->last_address)
+			return ENOSPC;
+	} while (helper->address_occupied[new_address]);
+	helper->last_address = new_address;
+
+	*addr = new_address;
+	helper->address_occupied[*addr] = true;
+
+	return EOK;
+}
+
+/**
+ * Mark address as free.
+ */
+static void release_address(usb2_bus_helper_t *helper, usb_address_t address)
+{
+	helper->address_occupied[address] = false;
+}
+
+static const usb_target_t usb2_default_target = {{
+	.address = USB_ADDRESS_DEFAULT,
+	.endpoint = 0,
+}};
+
+/**
+ * Transition the device to the addressed state.
+ *
+ * Reserve address, configure the control EP, issue a SET_ADDRESS command.
+ * Configure the device with the new address,
+ */
+static int address_device(usb2_bus_helper_t *helper, device_t *dev)
+{
+	int err;
+
+	/* The default address is currently reserved for this device */
+	dev->address = USB_ADDRESS_DEFAULT;
+
+	/** Reserve address early, we want pretty log messages */
+	usb_address_t address = USB_ADDRESS_DEFAULT;
+	if ((err = request_address(helper, &address))) {
+		usb_log_error("Failed to reserve new address: %s.",
+		    str_error(err));
+		return err;
+	}
+	usb_log_debug("Device(%d): Reserved new address.", address);
+
+	/* Add default pipe on default address */
+	usb_log_debug("Device(%d): Adding default target (0:0)", address);
+
+	usb_endpoint_descriptors_t ep0_desc = {
+	    .endpoint.max_packet_size = CTRL_PIPE_MIN_PACKET_SIZE,
+	};
+
+	/* Temporary reference */
+	endpoint_t *default_ep;
+	err = bus_endpoint_add(dev, &ep0_desc, &default_ep);
+	if (err != EOK) {
+		usb_log_error("Device(%d): Failed to add default target: %s.",
+		    address, str_error(err));
+		goto err_address;
+	}
+
+	if ((err = hc_get_ep0_max_packet_size(&ep0_desc.endpoint.max_packet_size, dev)))
+		goto err_address;
+
+	/* Set new address */
+	const usb_device_request_setup_packet_t set_address = SET_ADDRESS(address);
+
+	usb_log_debug("Device(%d): Setting USB address.", address);
+	err = bus_device_send_batch_sync(dev, usb2_default_target, USB_DIRECTION_OUT,
+	    NULL, 0, *(uint64_t *)&set_address, "set address");
+	if (err != 0) {
+		usb_log_error("Device(%d): Failed to set new address: %s.",
+		    address, str_error(err));
+		goto err_default_control_ep;
+	}
+
+	/* We need to remove ep before we change the address */
+	if ((err = bus_endpoint_remove(default_ep))) {
+		usb_log_error("Device(%d): Failed to unregister default target: %s", address, str_error(err));
+		goto err_address;
+	}
+
+	/* Temporary reference */
+	endpoint_del_ref(default_ep);
+
+	dev->address = address;
+
+	/* Register EP on the new address */
+	usb_log_debug("Device(%d): Registering control EP.", address);
+	err = bus_endpoint_add(dev, &ep0_desc, NULL);
+	if (err != EOK) {
+		usb_log_error("Device(%d): Failed to register EP0: %s",
+		    address, str_error(err));
+		goto err_address;
+	}
+
+	return EOK;
+
+err_default_control_ep:
+	bus_endpoint_remove(default_ep);
+	/* Temporary reference */
+	endpoint_del_ref(default_ep);
+err_address:
+	release_address(helper, address);
+	return err;
+}
+
+/**
+ * Enumerate a USB device. Move it to the addressed state, then explore it
+ * to create a DDF function node with proper characteristics.
+ */
+int usb2_bus_device_enumerate(usb2_bus_helper_t *helper, device_t *dev)
+{
+	int err;
+	usb_log_debug("Found new %s speed USB device.", usb_str_speed(dev->speed));
+
+	/* Assign an address to the device */
+	if ((err = address_device(helper, dev))) {
+		usb_log_error("Failed to setup address of the new device: %s", str_error(err));
+		return err;
+	}
+
+	/* Read the device descriptor, derive the match ids */
+	if ((err = hc_device_explore(dev))) {
+		usb_log_error("Device(%d): Failed to explore device: %s", dev->address, str_error(err));
+		release_address(helper, dev->address);
+		return err;
+	}
+
+	return EOK;
+}
+
+void usb2_bus_device_gone(usb2_bus_helper_t *helper, device_t *dev)
+{
+	release_address(helper, dev->address);
+}
+
+/**
+ * Register an endpoint to the bus. Reserves bandwidth.
+ */
+int usb2_bus_endpoint_register(usb2_bus_helper_t *helper, endpoint_t *ep)
+{
+	assert(ep);
+	assert(fibril_mutex_is_locked(&ep->device->guard));
+
+	size_t bw = helper->bw_accounting->count_bw(ep);
+
+	/* Check for available bandwidth */
+	if (bw > helper->free_bw)
+		return ENOSPC;
+
+	helper->free_bw -= bw;
+
+	return EOK;
+}
+
+/**
+ * Release bandwidth reserved by the given endpoint.
+ */
+void usb2_bus_endpoint_unregister(usb2_bus_helper_t *helper, endpoint_t *ep)
+{
+	assert(helper);
+	assert(ep);
+
+	helper->free_bw += helper->bw_accounting->count_bw(ep);
+}
+
+/**
+ * Initialize to default state.
+ *
+ * @param helper usb_bus_helper structure, non-null.
+ * @param bw_accounting a structure defining bandwidth accounting
+ */
+void usb2_bus_helper_init(usb2_bus_helper_t *helper, const bandwidth_accounting_t *bw_accounting)
+{
+	assert(helper);
+	assert(bw_accounting);
+
+	helper->bw_accounting = bw_accounting;
+	helper->free_bw = bw_accounting->available_bandwidth;
+
+	/*
+	 * The first address allocated is for the roothub. This way, its
+	 * address will be 127, and the first connected USB device will have
+	 * address 1.
+	 */
+	helper->last_address = USB_ADDRESS_COUNT - 2;
+}
+
+/**
+ * @}
+ */
Index: uspace/lib/usbhost/src/usb_bus.c
===================================================================
--- uspace/lib/usbhost/src/usb_bus.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ 	(revision )
@@ -1,561 +1,0 @@
-/*
- * Copyright (c) 2011 Jan Vesely
- * All rights eps.
- *
- * 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 libusbhost
- * @{
- */
-/** @file
- * HC Endpoint management.
- */
-
-#include <usb/host/usb_bus.h>
-#include <usb/debug.h>
-
-#include <assert.h>
-#include <errno.h>
-#include <macros.h>
-#include <stdbool.h>
-
-
-/** Endpoint compare helper function.
- *
- * USB_DIRECTION_BOTH matches both IN and OUT.
- * @param ep Endpoint to compare, non-null.
- * @param address Tested address.
- * @param endpoint Tested endpoint number.
- * @param direction Tested direction.
- * @return True if ep can be used to communicate with given device,
- * false otherwise.
- */
-static inline bool ep_match(const endpoint_t *ep,
-    usb_address_t address, usb_endpoint_t endpoint, usb_direction_t direction)
-{
-	assert(ep);
-	return
-	    ((direction == ep->direction)
-	        || (ep->direction == USB_DIRECTION_BOTH)
-	        || (direction == USB_DIRECTION_BOTH))
-	    && (endpoint == ep->endpoint)
-	    && (address == ep->address);
-}
-
-/** Get list that holds endpoints for given address.
- * @param instance usb_bus structure, non-null.
- * @param addr USB address, must be >= 0.
- * @return Pointer to the appropriate list.
- */
-static list_t * get_list(usb_bus_t *instance, usb_address_t addr)
-{
-	assert(instance);
-	assert(addr >= 0);
-	return &instance->devices[addr % ARRAY_SIZE(instance->devices)].endpoint_list;
-}
-
-/** Internal search function, works on locked structure.
- * @param instance usb_bus structure, non-null.
- * @param address USB address, must be valid.
- * @param endpoint USB endpoint number.
- * @param direction Communication direction.
- * @return Pointer to endpoint_t structure representing given communication
- * target, NULL if there is no such endpoint registered.
- * @note Assumes that the internal mutex is locked.
- */
-static endpoint_t * find_locked(usb_bus_t *instance,
-    usb_address_t address, usb_endpoint_t endpoint, usb_direction_t direction)
-{
-	assert(instance);
-	assert(fibril_mutex_is_locked(&instance->guard));
-	if (address < 0)
-		return NULL;
-	list_foreach(*get_list(instance, address), link, endpoint_t, ep) {
-		if (ep_match(ep, address, endpoint, direction))
-			return ep;
-	}
-	return NULL;
-}
-
-/** Get a free USB address
- *
- * @param[in] instance Device manager structure to use.
- * @param[out] address Free address.
- * @return Error code.
- */
-static errno_t usb_bus_get_free_address(usb_bus_t *instance, usb_address_t *address)
-{
-
-	usb_address_t new_address = instance->last_address;
-	do {
-		new_address = (new_address + 1) % USB_ADDRESS_COUNT;
-		if (new_address == USB_ADDRESS_DEFAULT)
-			new_address = 1;
-		if (new_address == instance->last_address)
-			return ENOSPC;
-	} while (instance->devices[new_address].occupied);
-
-	assert(new_address != USB_ADDRESS_DEFAULT);
-	instance->last_address = new_address;
-
-	*address = new_address;
-	return EOK;
-}
-
-/** Calculate bandwidth that needs to be reserved for communication with EP.
- * Calculation follows USB 1.1 specification.
- * @param speed Device's speed.
- * @param type Type of the transfer.
- * @param size Number of byte to transfer.
- * @param max_packet_size Maximum bytes in one packet.
- */
-size_t bandwidth_count_usb11(usb_speed_t speed, usb_transfer_type_t type,
-    size_t size, size_t max_packet_size)
-{
-	/* We care about bandwidth only for interrupt and isochronous. */
-	if ((type != USB_TRANSFER_INTERRUPT)
-	    && (type != USB_TRANSFER_ISOCHRONOUS)) {
-		return 0;
-	}
-
-	const unsigned packet_count =
-	    (size + max_packet_size - 1) / max_packet_size;
-	/* TODO: It may be that ISO and INT transfers use only one packet per
-	 * transaction, but I did not find text in USB spec to confirm this */
-	/* NOTE: All data packets will be considered to be max_packet_size */
-	switch (speed)
-	{
-	case USB_SPEED_LOW:
-		assert(type == USB_TRANSFER_INTERRUPT);
-		/* Protocol overhead 13B
-		 * (3 SYNC bytes, 3 PID bytes, 2 Endpoint + CRC bytes, 2
-		 * CRC bytes, and a 3-byte interpacket delay)
-		 * see USB spec page 45-46. */
-		/* Speed penalty 8: low speed is 8-times slower*/
-		return packet_count * (13 + max_packet_size) * 8;
-	case USB_SPEED_FULL:
-		/* Interrupt transfer overhead see above
-		 * or page 45 of USB spec */
-		if (type == USB_TRANSFER_INTERRUPT)
-			return packet_count * (13 + max_packet_size);
-
-		assert(type == USB_TRANSFER_ISOCHRONOUS);
-		/* Protocol overhead 9B
-		 * (2 SYNC bytes, 2 PID bytes, 2 Endpoint + CRC bytes, 2 CRC
-		 * bytes, and a 1-byte interpacket delay)
-		 * see USB spec page 42 */
-		return packet_count * (9 + max_packet_size);
-	default:
-		return 0;
-	}
-}
-
-/** Calculate bandwidth that needs to be reserved for communication with EP.
- * Calculation follows USB 2.0 specification.
- * @param speed Device's speed.
- * @param type Type of the transfer.
- * @param size Number of byte to transfer.
- * @param max_packet_size Maximum bytes in one packet.
- */
-size_t bandwidth_count_usb20(usb_speed_t speed, usb_transfer_type_t type,
-    size_t size, size_t max_packet_size)
-{
-	/* We care about bandwidth only for interrupt and isochronous. */
-	if ((type != USB_TRANSFER_INTERRUPT)
-	    && (type != USB_TRANSFER_ISOCHRONOUS)) {
-		return 0;
-	}
-	//TODO Implement
-	return 0;
-}
-
-/** Initialize to default state.
- * You need to provide valid bw_count function if you plan to use
- * add_endpoint/remove_endpoint pair.
- *
- * @param instance usb_bus structure, non-null.
- * @param available_bandwidth Size of the bandwidth pool.
- * @param bw_count function to use to calculate endpoint bw requirements.
- * @return Error code.
- */
-errno_t usb_bus_init(usb_bus_t *instance,
-    size_t available_bandwidth, bw_count_func_t bw_count, usb_speed_t max_speed)
-{
-	assert(instance);
-	fibril_mutex_initialize(&instance->guard);
-	instance->free_bw = available_bandwidth;
-	instance->bw_count = bw_count;
-	instance->last_address = 0;
-	instance->max_speed = max_speed;
-	for (unsigned i = 0; i < ARRAY_SIZE(instance->devices); ++i) {
-		list_initialize(&instance->devices[i].endpoint_list);
-		instance->devices[i].speed = USB_SPEED_MAX;
-		instance->devices[i].occupied = false;
-	}
-	return EOK;
-}
-
-/** Register endpoint structure.
- * Checks for duplicates.
- * @param instance usb_bus, non-null.
- * @param ep endpoint_t to register.
- * @param data_size Size of data to transfer.
- * @return Error code.
- */
-errno_t usb_bus_register_ep(usb_bus_t *instance, endpoint_t *ep, size_t data_size)
-{
-	assert(instance);
-	if (ep == NULL || ep->address < 0)
-		return EINVAL;
-
-	fibril_mutex_lock(&instance->guard);
-	/* Check for available bandwidth */
-	if (ep->bandwidth > instance->free_bw) {
-		fibril_mutex_unlock(&instance->guard);
-		return ENOSPC;
-	}
-
-	/* Check for existence */
-	const endpoint_t *endpoint =
-	    find_locked(instance, ep->address, ep->endpoint, ep->direction);
-	if (endpoint != NULL) {
-		fibril_mutex_unlock(&instance->guard);
-		return EEXIST;
-	}
-	/* Add endpoint list's reference to ep. */
-	endpoint_add_ref(ep);
-	list_append(&ep->link, get_list(instance, ep->address));
-
-	instance->free_bw -= ep->bandwidth;
-	usb_log_debug("Registered EP(%d:%d:%s:%s)\n", ep->address, ep->endpoint,
-	    usb_str_transfer_type_short(ep->transfer_type),
-	    usb_str_direction(ep->direction));
-	fibril_mutex_unlock(&instance->guard);
-	return EOK;
-}
-
-/** Unregister endpoint structure.
- * Checks for duplicates.
- * @param instance usb_bus, non-null.
- * @param ep endpoint_t to unregister.
- * @return Error code.
- */
-errno_t usb_bus_unregister_ep(usb_bus_t *instance, endpoint_t *ep)
-{
-	assert(instance);
-	if (ep == NULL || ep->address < 0)
-		return EINVAL;
-
-	fibril_mutex_lock(&instance->guard);
-	if (!list_member(&ep->link, get_list(instance, ep->address))) {
-		fibril_mutex_unlock(&instance->guard);
-		return ENOENT;
-	}
-	list_remove(&ep->link);
-	instance->free_bw += ep->bandwidth;
-	usb_log_debug("Unregistered EP(%d:%d:%s:%s)\n", ep->address,
-	    ep->endpoint, usb_str_transfer_type_short(ep->transfer_type),
-	    usb_str_direction(ep->direction));
-	/* Drop endpoint list's reference to ep. */
-	endpoint_del_ref(ep);
-	fibril_mutex_unlock(&instance->guard);
-	return EOK;
-}
-
-/** Find endpoint_t representing the given communication route.
- * @param instance usb_bus, non-null.
- * @param address
- */
-endpoint_t * usb_bus_find_ep(usb_bus_t *instance,
-    usb_address_t address, usb_endpoint_t endpoint, usb_direction_t direction)
-{
-	assert(instance);
-
-	fibril_mutex_lock(&instance->guard);
-	endpoint_t *ep = find_locked(instance, address, endpoint, direction);
-	if (ep) {
-		/* We are exporting ep to the outside world, add reference. */
-		endpoint_add_ref(ep);
-	}
-	fibril_mutex_unlock(&instance->guard);
-	return ep;
-}
-
-/** Create and register new endpoint_t structure.
- * @param instance usb_bus structure, non-null.
- * @param address USB address.
- * @param endpoint USB endpoint number.
- * @param direction Communication direction.
- * @param type USB transfer type.
- * @param speed USB Communication speed.
- * @param max_packet_size Maximum size of data packets.
- * @param data_size Expected communication size.
- * @param callback function to call just after registering.
- * @param arg Argument to pass to the callback function.
- * @return Error code.
- */
-errno_t usb_bus_add_ep(usb_bus_t *instance,
-    usb_address_t address, usb_endpoint_t endpoint, usb_direction_t direction,
-    usb_transfer_type_t type, size_t max_packet_size, unsigned packets,
-    size_t data_size, ep_add_callback_t callback, void *arg,
-    usb_address_t tt_address, unsigned tt_port)
-{
-	assert(instance);
-	if (instance->bw_count == NULL)
-		return ENOTSUP;
-	if (!usb_address_is_valid(address))
-		return EINVAL;
-
-
-	fibril_mutex_lock(&instance->guard);
-	/* Check for speed and address */
-	if (!instance->devices[address].occupied) {
-		fibril_mutex_unlock(&instance->guard);
-		return ENOENT;
-	}
-
-	/* Check for existence */
-	endpoint_t *ep = find_locked(instance, address, endpoint, direction);
-	if (ep != NULL) {
-		fibril_mutex_unlock(&instance->guard);
-		return EEXIST;
-	}
-
-	const usb_speed_t speed = instance->devices[address].speed;
-	const size_t bw =
-	    instance->bw_count(speed, type, data_size, max_packet_size);
-
-	/* Check for available bandwidth */
-	if (bw > instance->free_bw) {
-		fibril_mutex_unlock(&instance->guard);
-		return ENOSPC;
-	}
-
-	ep = endpoint_create(address, endpoint, direction, type, speed,
-	    max_packet_size, packets, bw, tt_address, tt_port);
-	if (!ep) {
-		fibril_mutex_unlock(&instance->guard);
-		return ENOMEM;
-	}
-
-	/* Add our reference to ep. */
-	endpoint_add_ref(ep);
-
-	if (callback) {
-		const errno_t ret = callback(ep, arg);
-		if (ret != EOK) {
-			fibril_mutex_unlock(&instance->guard);
-			endpoint_del_ref(ep);
-			return ret;
-		}
-	}
-	
-	/* Add endpoint list's reference to ep. */
-	endpoint_add_ref(ep);
-	list_append(&ep->link, get_list(instance, ep->address));
-
-	instance->free_bw -= ep->bandwidth;
-	fibril_mutex_unlock(&instance->guard);
-
-	/* Drop our reference to ep. */
-	endpoint_del_ref(ep);
-
-	return EOK;
-}
-
-/** Unregister and destroy endpoint_t structure representing given route.
- * @param instance usb_bus structure, non-null.
- * @param address USB address.
- * @param endpoint USB endpoint number.
- * @param direction Communication direction.
- * @param callback Function to call after unregister, before destruction.
- * @arg Argument to pass to the callback function.
- * @return Error code.
- */
-errno_t usb_bus_remove_ep(usb_bus_t *instance,
-    usb_address_t address, usb_endpoint_t endpoint, usb_direction_t direction,
-    ep_remove_callback_t callback, void *arg)
-{
-	assert(instance);
-	fibril_mutex_lock(&instance->guard);
-	endpoint_t *ep = find_locked(instance, address, endpoint, direction);
-	if (ep != NULL) {
-		list_remove(&ep->link);
-		instance->free_bw += ep->bandwidth;
-	}
-	fibril_mutex_unlock(&instance->guard);
-	if (ep == NULL)
-		return ENOENT;
-
-	if (callback) {
-		callback(ep, arg);
-	}
-	/* Drop endpoint list's reference to ep. */
-	endpoint_del_ref(ep);
-	return EOK;
-}
-
-errno_t usb_bus_reset_toggle(usb_bus_t *instance, usb_target_t target, bool all)
-{
-	assert(instance);
-	if (!usb_target_is_valid(target))
-		return EINVAL;
-
-	errno_t ret = ENOENT;
-
-	fibril_mutex_lock(&instance->guard);
-	list_foreach(*get_list(instance, target.address), link, endpoint_t, ep) {
-		if ((ep->address == target.address)
-		    && (all || ep->endpoint == target.endpoint)) {
-			endpoint_toggle_set(ep, 0);
-			ret = EOK;
-		}
-	}
-	fibril_mutex_unlock(&instance->guard);
-	return ret;
-}
-
-/** Unregister and destroy all endpoints using given address.
- * @param instance usb_bus structure, non-null.
- * @param address USB address.
- * @param endpoint USB endpoint number.
- * @param direction Communication direction.
- * @param callback Function to call after unregister, before destruction.
- * @arg Argument to pass to the callback function.
- * @return Error code.
- */
-errno_t usb_bus_remove_address(usb_bus_t *instance,
-    usb_address_t address, ep_remove_callback_t callback, void *arg)
-{
-	assert(instance);
-	if (!usb_address_is_valid(address))
-		return EINVAL;
-
-	fibril_mutex_lock(&instance->guard);
-
-	const errno_t ret = instance->devices[address].occupied ? EOK : ENOENT;
-	instance->devices[address].occupied = false;
-
-	list_t *list = get_list(instance, address);
-	for (link_t *link = list_first(list); link != NULL; ) {
-		endpoint_t *ep = list_get_instance(link, endpoint_t, link);
-		link = list_next(link, list);
-		if (ep->address == address) {
-			list_remove(&ep->link);
-			if (callback)
-				callback(ep, arg);
-			/* Drop endpoint list's reference to ep. */
-			endpoint_del_ref(ep);
-		}
-	}
-	fibril_mutex_unlock(&instance->guard);
-	return ret;
-}
-
-/** Request USB address.
- * @param instance usb_device_manager
- * @param[inout] address Pointer to requested address value, place to store new address
- * @parma strict Fail if the requested address is not available.
- * @return Error code.
- * @note Default address is only available in strict mode.
- */
-errno_t usb_bus_request_address(usb_bus_t *instance,
-    usb_address_t *address, bool strict, usb_speed_t speed)
-{
-	assert(instance);
-	assert(address);
-	if (speed > instance->max_speed)
-		return ENOTSUP;
-
-	if (!usb_address_is_valid(*address))
-		return EINVAL;
-
-	usb_address_t addr = *address;
-	errno_t rc;
-
-	fibril_mutex_lock(&instance->guard);
-	/* Only grant default address to strict requests */
-	if ((addr == USB_ADDRESS_DEFAULT) && !strict) {
-		rc = usb_bus_get_free_address(instance, &addr);
-		if (rc != EOK) {
-			fibril_mutex_unlock(&instance->guard);
-			return rc;
-		}
-	}
-
-	if (instance->devices[addr].occupied) {
-		if (strict) {
-			fibril_mutex_unlock(&instance->guard);
-			return ENOENT;
-		}
-		rc = usb_bus_get_free_address(instance, &addr);
-		if (rc != EOK) {
-			fibril_mutex_unlock(&instance->guard);
-			return rc;
-		}
-	}
-	if (usb_address_is_valid(addr)) {
-		assert(instance->devices[addr].occupied == false);
-		assert(addr != USB_ADDRESS_DEFAULT || strict);
-
-		instance->devices[addr].occupied = true;
-		instance->devices[addr].speed = speed;
-		*address = addr;
-		addr = 0;
-	}
-
-	fibril_mutex_unlock(&instance->guard);
-
-	*address = addr;
-	return EOK;
-}
-
-/** Get speed assigned to USB address.
- *
- * @param[in] instance Device manager structure to use.
- * @param[in] address Address the caller wants to find.
- * @param[out] speed Assigned speed.
- * @return Error code.
- */
-errno_t usb_bus_get_speed(usb_bus_t *instance, usb_address_t address,
-    usb_speed_t *speed)
-{
-	assert(instance);
-	if (!usb_address_is_valid(address)) {
-		return EINVAL;
-	}
-
-	fibril_mutex_lock(&instance->guard);
-
-	const errno_t ret = instance->devices[address].occupied ? EOK : ENOENT;
-	if (speed && instance->devices[address].occupied) {
-		*speed = instance->devices[address].speed;
-	}
-
-	fibril_mutex_unlock(&instance->guard);
-	return ret;
-}
-/**
- * @}
- */
Index: uspace/lib/usbhost/src/usb_transfer_batch.c
===================================================================
--- uspace/lib/usbhost/src/usb_transfer_batch.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbhost/src/usb_transfer_batch.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -33,105 +33,97 @@
  */
 
-#include <usb/host/usb_transfer_batch.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <str_error.h>
 #include <usb/debug.h>
 
-#include <assert.h>
-#include <errno.h>
-#include <macros.h>
-#include <mem.h>
-#include <stdlib.h>
-#include <usbhc_iface.h>
+#include "endpoint.h"
+#include "bus.h"
 
-/** Allocate and initialize usb_transfer_batch structure.
- * @param ep endpoint used by the transfer batch.
- * @param buffer data to send/recieve.
- * @param buffer_size Size of data buffer.
- * @param setup_buffer Data to send in SETUP stage of control transfer.
- * @param func_in callback on IN transfer completion.
- * @param func_out callback on OUT transfer completion.
- * @param fun DDF function (passed to callback function).
- * @param arg Argument to pass to the callback function.
- * @param private_data driver specific per batch data.
- * @param private_data_dtor Function to properly destroy private_data.
- * @return Pointer to valid usb_transfer_batch_t structure, NULL on failure.
+#include "usb_transfer_batch.h"
+
+/**
+ * Create a batch on a given endpoint.
+ *
+ * If the bus callback is not defined, it just creates a default batch.
  */
-usb_transfer_batch_t *usb_transfer_batch_create(endpoint_t *ep, char *buffer,
-    size_t buffer_size,
-    uint64_t setup_buffer,
-    usbhc_iface_transfer_in_callback_t func_in,
-    usbhc_iface_transfer_out_callback_t func_out,
-    void *arg)
+usb_transfer_batch_t *usb_transfer_batch_create(endpoint_t *ep)
 {
-	if (func_in == NULL && func_out == NULL)
-		return NULL;
-	if (func_in != NULL && func_out != NULL)
-		return NULL;
+	assert(ep);
 
-	usb_transfer_batch_t *instance = malloc(sizeof(usb_transfer_batch_t));
-	if (instance) {
-		instance->ep = ep;
-		instance->callback_in = func_in;
-		instance->callback_out = func_out;
-		instance->arg = arg;
-		instance->buffer = buffer;
-		instance->buffer_size = buffer_size;
-		instance->setup_size = 0;
-		instance->transfered_size = 0;
-		instance->error = EOK;
-		if (ep && ep->transfer_type == USB_TRANSFER_CONTROL) {
-			memcpy(instance->setup_buffer, &setup_buffer,
-			    USB_SETUP_PACKET_SIZE);
-			instance->setup_size = USB_SETUP_PACKET_SIZE;
-		}
-		if (instance->ep)
-			endpoint_use(instance->ep);
+	bus_t *bus = endpoint_get_bus(ep);
+
+	if (!bus->ops->batch_create) {
+		usb_transfer_batch_t *batch = calloc(1, sizeof(usb_transfer_batch_t));
+		if (!batch)
+			return NULL;
+		usb_transfer_batch_init(batch, ep);
+		return batch;
 	}
-	return instance;
+
+	return bus->ops->batch_create(ep);
 }
 
-/** Correctly dispose all used data structures.
- *
- * @param[in] instance Batch structure to use.
+/**
+ * Initialize given batch structure.
  */
-void usb_transfer_batch_destroy(usb_transfer_batch_t *instance)
+void usb_transfer_batch_init(usb_transfer_batch_t *batch, endpoint_t *ep)
 {
-	if (!instance)
-		return;
-	usb_log_debug2("Batch %p " USB_TRANSFER_BATCH_FMT " disposing.\n",
-	    instance, USB_TRANSFER_BATCH_ARGS(*instance));
-	if (instance->ep) {
-		endpoint_release(instance->ep);
-	}
-	free(instance);
+	assert(ep);
+	/* Batch reference */
+	endpoint_add_ref(ep);
+	batch->ep = ep;
 }
 
-/** Prepare data and call the right callback.
- *
- * @param[in] instance Batch structure to use.
- * @param[in] data Data to copy to the output buffer.
- * @param[in] size Size of @p data.
- * @param[in] error Error value to use.
+/**
+ * Destroy the batch. If there's no bus callback, just free it.
  */
-void usb_transfer_batch_finish_error(const usb_transfer_batch_t *instance,
-    const void *data, size_t size, errno_t error)
+void usb_transfer_batch_destroy(usb_transfer_batch_t *batch)
 {
-	assert(instance);
-	usb_log_debug2("Batch %p " USB_TRANSFER_BATCH_FMT " finishing.\n",
-	    instance, USB_TRANSFER_BATCH_ARGS(*instance));
+	assert(batch);
+	assert(batch->ep);
 
-	/* NOTE: Only one of these pointers should be set. */
-        if (instance->callback_out) {
-		instance->callback_out(error, instance->arg);
+	bus_t *bus = endpoint_get_bus(batch->ep);
+	endpoint_t *ep = batch->ep;
+
+	if (bus->ops) {
+		usb_log_debug2("Batch %p " USB_TRANSFER_BATCH_FMT " destroying.",
+		    batch, USB_TRANSFER_BATCH_ARGS(*batch));
+		bus->ops->batch_destroy(batch);
+	}
+	else {
+		usb_log_debug2("Batch %p " USB_TRANSFER_BATCH_FMT " disposing.",
+		    batch, USB_TRANSFER_BATCH_ARGS(*batch));
+		free(batch);
 	}
 
-        if (instance->callback_in) {
-		/* We care about the data and there are some to copy */
-		const size_t safe_size = min(size, instance->buffer_size);
-		if (data) {
-	                memcpy(instance->buffer, data, safe_size);
-		}
-		instance->callback_in(error, safe_size, instance->arg);
+	/* Batch reference */
+	endpoint_del_ref(ep);
+}
+
+/**
+ * Finish a transfer batch: call handler, destroy batch, release endpoint.
+ *
+ * Call only after the batch have been scheduled && completed!
+ */
+void usb_transfer_batch_finish(usb_transfer_batch_t *batch)
+{
+	assert(batch);
+	assert(batch->ep);
+
+	usb_log_debug2("Batch %p " USB_TRANSFER_BATCH_FMT " finishing.",
+	    batch, USB_TRANSFER_BATCH_ARGS(*batch));
+
+	if (batch->on_complete) {
+		const int err = batch->on_complete(batch->on_complete_data, batch->error, batch->transferred_size);
+		if (err)
+			usb_log_warning("batch %p failed to complete: %s",
+			    batch, str_error(err));
 	}
+
+	usb_transfer_batch_destroy(batch);
 }
+
 /**
  * @}
Index: uspace/lib/usbhost/src/utility.c
===================================================================
--- uspace/lib/usbhost/src/utility.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
+++ uspace/lib/usbhost/src/utility.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -0,0 +1,288 @@
+/*
+ * Copyright (c) 2013 Jan Vesely
+ * Copyright (c) 2017 Ondrej Hlavaty <aearsis@eideo.cz>
+ * 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 libusbhost
+ * @{
+ */
+/** @file
+ */
+
+#include <macros.h>
+#include <str_error.h>
+#include <usb/debug.h>
+#include <usb/descriptor.h>
+#include <usb/request.h>
+
+#include "ddf_helpers.h"
+#include "utility.h"
+
+/**
+ * Get initial max packet size for the control endpoint 0.
+ *
+ * For LS, HS, and SS devices this value is final and fixed.
+ * For FS devices, the default value of 8 is returned. The caller needs
+ * to fetch the first 8B of the device descriptor later in the initialization
+ * process and determine whether it should be increased.
+ *
+ * @return Max packet size for EP 0 (in bytes)
+ */
+uint16_t hc_get_ep0_initial_mps(usb_speed_t speed)
+{
+	static const uint16_t mps_fixed [] = {
+		[USB_SPEED_LOW] = 8,
+		[USB_SPEED_HIGH] = 64,
+		[USB_SPEED_SUPER] = 512,
+	};
+
+	if (speed < ARRAY_SIZE(mps_fixed) && mps_fixed[speed] != 0) {
+		return mps_fixed[speed];
+	}
+	return 8; // USB_SPEED_FULL default
+}
+
+/**
+ * Get max packet size for the control endpoint 0.
+ *
+ * For LS, HS, and SS devices the corresponding fixed value is obtained.
+ * For FS devices the first 8B of the device descriptor are fetched to
+ * determine it.
+ *
+ * @return Max packet size for EP 0 (in bytes)
+ */
+int hc_get_ep0_max_packet_size(uint16_t *mps, device_t *dev)
+{
+	assert(mps);
+
+	*mps = hc_get_ep0_initial_mps(dev->speed);
+	if (dev->speed != USB_SPEED_FULL) {
+		return EOK;
+	}
+
+	const usb_target_t control_ep = {{
+		.address = dev->address,
+		.endpoint = 0,
+	}};
+
+	usb_standard_device_descriptor_t desc = { 0 };
+	const usb_device_request_setup_packet_t get_device_desc_8 =
+	    GET_DEVICE_DESC(CTRL_PIPE_MIN_PACKET_SIZE);
+
+	usb_log_debug("Requesting first 8B of device descriptor to determine MPS.");
+	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");
+
+	if (got != CTRL_PIPE_MIN_PACKET_SIZE) {
+		const int err = got < 0 ? got : EOVERFLOW;
+		usb_log_error("Failed to get 8B of dev descr: %s.", str_error(err));
+		return err;
+	}
+
+	if (desc.descriptor_type != USB_DESCTYPE_DEVICE) {
+		usb_log_error("The device responded with wrong device descriptor.");
+		return EIO;
+	}
+
+	uint16_t version = uint16_usb2host(desc.usb_spec_version);
+	if (version < 0x0300) {
+		/* USB 2 and below have MPS raw in the field */
+		*mps = desc.max_packet_size;
+	} else {
+		/* USB 3 have MPS as an 2-based exponent */
+		*mps = (1 << desc.max_packet_size);
+	}
+	return EOK;
+}
+
+int hc_get_device_desc(device_t *device, usb_standard_device_descriptor_t *desc)
+{
+	const usb_target_t control_ep = {{
+		.address = device->address,
+		.endpoint = 0,
+	}};
+
+	/* Get std device descriptor */
+	const usb_device_request_setup_packet_t get_device_desc =
+	    GET_DEVICE_DESC(sizeof(*desc));
+
+	usb_log_debug("Device(%d): Requesting full device descriptor.",
+	    device->address);
+	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");
+
+	if (got < 0)
+		return got;
+
+	return got == sizeof(*desc) ? EOK : EOVERFLOW;
+}
+
+int hc_get_hub_desc(device_t *device, usb_hub_descriptor_header_t *desc)
+{
+	const usb_target_t control_ep = {{
+		.address = device->address,
+		.endpoint = 0,
+	}};
+
+	const usb_descriptor_type_t type = device->speed >= USB_SPEED_SUPER
+		? USB_DESCTYPE_SSPEED_HUB : USB_DESCTYPE_HUB;
+
+	const usb_device_request_setup_packet_t get_hub_desc = {
+		.request_type = SETUP_REQUEST_TYPE_DEVICE_TO_HOST
+		    | (USB_REQUEST_TYPE_CLASS << 5)
+		    | USB_REQUEST_RECIPIENT_DEVICE,
+		.request = USB_DEVREQ_GET_DESCRIPTOR, \
+		.value = uint16_host2usb(type << 8), \
+		.length = sizeof(*desc),
+	};
+
+	usb_log_debug("Device(%d): Requesting hub descriptor.",
+	    device->address);
+	ssize_t got = bus_device_send_batch_sync(device, control_ep, USB_DIRECTION_IN,
+	    (char *) desc, sizeof(*desc), *(uint64_t *)&get_hub_desc,
+	    "get hub descriptor");
+
+	if (got < 0)
+		return got;
+
+	return got == sizeof(*desc) ? EOK : EOVERFLOW;
+}
+
+int hc_device_explore(device_t *device)
+{
+	int err;
+	usb_standard_device_descriptor_t desc = { 0 };
+
+	if ((err = hc_get_device_desc(device, &desc))) {
+		usb_log_error("Device(%d): Failed to get dev descriptor: %s",
+		    device->address, str_error(err));
+		return err;
+	}
+
+	if ((err = hcd_ddf_setup_match_ids(device, &desc))) {
+		usb_log_error("Device(%d): Failed to setup match ids: %s", device->address, str_error(err));
+		return err;
+	}
+
+	return EOK;
+}
+
+/** Announce root hub to the DDF
+ *
+ * @param[in] device Host controller ddf device
+ * @return Error code
+ */
+int hc_setup_virtual_root_hub(hc_device_t *hcd, usb_speed_t rh_speed)
+{
+	int err;
+
+	assert(hcd);
+
+	device_t *dev = hcd_ddf_fun_create(hcd, rh_speed);
+	if (!dev) {
+		usb_log_error("Failed to create function for the root hub.");
+		return ENOMEM;
+	}
+
+	ddf_fun_set_name(dev->fun, "roothub");
+
+	/* Assign an address to the device */
+	if ((err = bus_device_enumerate(dev))) {
+		usb_log_error("Failed to enumerate roothub device: %s", str_error(err));
+		goto err_usb_dev;
+	}
+
+	if ((err = ddf_fun_bind(dev->fun))) {
+		usb_log_error("Failed to register roothub: %s.", str_error(err));
+		goto err_enumerated;
+	}
+
+	return EOK;
+
+err_enumerated:
+	bus_device_gone(dev);
+err_usb_dev:
+	hcd_ddf_fun_destroy(dev);
+	return err;
+}
+
+/**
+ * Check setup packet data for signs of toggle reset.
+ *
+ * @param[in] batch USB batch
+ * @param[in] reset_cb Callback to reset an endpoint
+ */
+void hc_reset_toggles(const usb_transfer_batch_t *batch, endpoint_reset_toggle_t reset_cb)
+{
+	if (batch->ep->transfer_type != USB_TRANSFER_CONTROL
+	    || batch->dir != USB_DIRECTION_OUT)
+		return;
+
+	const usb_device_request_setup_packet_t *request = &batch->setup.packet;
+	device_t * const dev = batch->ep->device;
+
+	switch (request->request)
+	{
+	/* Clear Feature ENPOINT_STALL */
+	case USB_DEVREQ_CLEAR_FEATURE: /*resets only cleared ep */
+		/* 0x2 ( HOST to device | STANDART | TO ENPOINT) */
+		if ((request->request_type == 0x2) &&
+		    (request->value == USB_FEATURE_ENDPOINT_HALT)) {
+			const uint16_t index = uint16_usb2host(request->index);
+			const usb_endpoint_t ep_num = index & 0xf;
+			const usb_direction_t dir = (index >> 7) ? USB_DIRECTION_IN : USB_DIRECTION_OUT;
+
+			endpoint_t *ep = bus_find_endpoint(dev, ep_num, dir);
+			if (ep) {
+				reset_cb(ep);
+				endpoint_del_ref(ep);
+			} else {
+				usb_log_warning("Device(%u): Resetting unregistered endpoint %u %s.", dev->address, ep_num, usb_str_direction(dir));
+			}
+		}
+		break;
+	case USB_DEVREQ_SET_CONFIGURATION:
+	case USB_DEVREQ_SET_INTERFACE:
+		/* Recipient must be device, this resets all endpoints,
+		 * In fact there should be no endpoints but EP 0 registered
+		 * as different interfaces use different endpoints,
+		 * unless you're changing configuration or alternative
+		 * interface of an already setup device. */
+		if (!(request->request_type & SETUP_REQUEST_TYPE_DEVICE_TO_HOST))
+			for (usb_endpoint_t i = 0; i < 2 * USB_ENDPOINT_MAX; ++i)
+				if (dev->endpoints[i])
+					reset_cb(dev->endpoints[i]);
+		break;
+	default:
+		break;
+	}
+}
+
+/**
+ * @}
+ */
Index: uspace/lib/usbvirt/src/ctrltransfer.c
===================================================================
--- uspace/lib/usbvirt/src/ctrltransfer.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbvirt/src/ctrltransfer.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -68,5 +68,5 @@
 		}
 
-		usb_log_debug("Control transfer: %s(%s)\n", handler->name,
+		usb_log_debug("Control transfer: %s(%s)", handler->name,
 		    usb_debug_str_buffer((uint8_t*) setup, sizeof(*setup), 0));
 		errno_t rc = handler->callback(dev, setup, data, data_sent_size);
Index: uspace/lib/usbvirt/src/ipc_hc.c
===================================================================
--- uspace/lib/usbvirt/src/ipc_hc.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbvirt/src/ipc_hc.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -50,5 +50,5 @@
  * @param data_buffer Data buffer (DATA stage of control transfer).
  * @param data_buffer_size Size of data buffer in bytes.
- * @param data_transfered_size Number of actually transferred bytes.
+ * @param data_transferred_size Number of actually transferred bytes.
  *
  * @return Error code.
@@ -57,5 +57,5 @@
 errno_t usbvirt_ipc_send_control_read(async_sess_t *sess, void *setup_buffer,
     size_t setup_buffer_size, void *data_buffer, size_t data_buffer_size,
-    size_t *data_transfered_size)
+    size_t *data_transferred_size)
 {
 	if (!sess)
@@ -111,6 +111,6 @@
 		return (errno_t) opening_request_rc;
 	
-	if (data_transfered_size != NULL)
-		*data_transfered_size = IPC_GET_ARG2(data_request_call);
+	if (data_transferred_size != NULL)
+		*data_transferred_size = IPC_GET_ARG2(data_request_call);
 	
 	return EOK;
Index: uspace/lib/usbvirt/src/transfer.c
===================================================================
--- uspace/lib/usbvirt/src/transfer.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/lib/usbvirt/src/transfer.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -79,5 +79,5 @@
 
 	if (rc == EFORWARD) {
-		usb_log_warning("Control transfer {%s (%s)} not handled.\n",
+		usb_log_warning("Control transfer {%s (%s)} not handled.",
 		    usb_debug_str_buffer(setup, setup_size, 10),
 		    setup_packet->request_type & 0x80
Index: uspace/srv/locsrv/locsrv.c
===================================================================
--- uspace/srv/locsrv/locsrv.c	(revision 2752620b090f5bf0ecc3baace21dc1fa5da89d73)
+++ uspace/srv/locsrv/locsrv.c	(revision 2b3dd78e52704a51e22b71513327373cf8f6a614)
@@ -1357,4 +1357,7 @@
 	categ_dir_add_cat(&cdir, cat);
 
+	cat = category_new("usbdiag");
+	categ_dir_add_cat(&cdir, cat);
+
 	cat = category_new("usbhc");
 	categ_dir_add_cat(&cdir, cat);
