Index: uspace/drv/bus/usb/xhci/hc.c
===================================================================
--- uspace/drv/bus/usb/xhci/hc.c	(revision 063dfe863508dd89880d6cd9d69eb9cb5a73e95d)
+++ uspace/drv/bus/usb/xhci/hc.c	(revision dcf0597038009fab95226d17afe1802c9145bebe)
@@ -425,4 +425,9 @@
 	XHCI_REG_SET(hc->op_regs, XHCI_OP_RS, 1);
 
+	/* The reset changed status of all ports, and SW originated reason does
+	 * not cause an interrupt.
+	 */
+	xhci_rh_handle_port_change(&hc->rh);
+
 	return EOK;
 }
@@ -451,10 +456,4 @@
 {
 	assert(batch);
-
-	/* Check for root hub communication */
-	if (batch->ep->target.address == xhci_rh_get_address(&hc->rh)) {
-		usb_log_debug("XHCI root hub request.\n");
-		return xhci_rh_schedule(&hc->rh, batch);
-	}
 
 	usb_log_debug2("EP(%d:%d) started %s transfer of size %lu.",
@@ -488,5 +487,5 @@
 static event_handler event_handlers [] = {
 	[XHCI_TRB_TYPE_COMMAND_COMPLETION_EVENT] = &xhci_handle_command_completion,
-	[XHCI_TRB_TYPE_PORT_STATUS_CHANGE_EVENT] = &xhci_handle_port_status_change_event,
+	[XHCI_TRB_TYPE_PORT_STATUS_CHANGE_EVENT] = &xhci_rh_handle_port_status_change_event,
 	[XHCI_TRB_TYPE_TRANSFER_EVENT] = &xhci_handle_transfer_event,
 };
@@ -559,9 +558,7 @@
 	status = xhci2host(32, status);
 
-	/* TODO: Figure out how root hub interrupts work. */
 	if (status & XHCI_REG_MASK(XHCI_OP_PCD)) {
 		usb_log_debug2("Root hub interrupt.");
-		xhci_rh_interrupt(&hc->rh);
-
+		xhci_rh_handle_port_change(&hc->rh);
 		status &= ~XHCI_REG_MASK(XHCI_OP_PCD);
 	}
Index: uspace/drv/bus/usb/xhci/rh.c
===================================================================
--- uspace/drv/bus/usb/xhci/rh.c	(revision 063dfe863508dd89880d6cd9d69eb9cb5a73e95d)
+++ uspace/drv/bus/usb/xhci/rh.c	(revision dcf0597038009fab95226d17afe1802c9145bebe)
@@ -1,4 +1,4 @@
 /*
-n * Copyright (c) 2017 Michal Staruch
+ * Copyright (c) 2017 Michal Staruch
  * All rights reserved.
  *
@@ -46,13 +46,4 @@
 #include "transfers.h"
 
-#define USB_MAP_VALUE(a, b) [USB_HUB_FEATURE_##a] = b
-#define USB_MAP_XHCI(a, b) USB_MAP_VALUE(a, XHCI_REG_MASK(XHCI_PORT_##b))
-
-enum {
-	HUB_STATUS_CHANGE_PIPE = 1,
-};
-
-static usbvirt_device_ops_t ops;
-
 /* This mask only lists registers, which imply port change. */
 static const uint32_t port_change_mask =
@@ -73,15 +64,5 @@
 	rh->max_ports = XHCI_REG_RD(hc->cap_regs, XHCI_CAP_MAX_PORTS);
 
-	usb_hub_descriptor_header_t *header = &rh->hub_descriptor.header;
-	header->length = sizeof(usb_hub_descriptor_header_t);
-	header->descriptor_type = USB_DESCTYPE_HUB;
-	header->port_count = rh->max_ports;
-	header->characteristics =
-		    HUB_CHAR_NO_POWER_SWITCH_FLAG | HUB_CHAR_NO_OC_FLAG;
-	header->power_good_time = 10; /* XHCI section 5.4.9 says 20ms max */
-	header->max_current = 0;
-
-	return virthub_base_init(&rh->base, "xhci", &ops, rh, NULL,
-	    header, HUB_STATUS_CHANGE_PIPE);
+	return EOK;
 }
 
@@ -186,56 +167,132 @@
 // }
 
-// static int handle_connected_device(xhci_hc_t* hc, xhci_port_regs_t* regs, uint8_t port_id)
-// {
-// 	uint8_t link_state = XHCI_REG_RD(regs, XHCI_PORT_PLS);
-// 	const xhci_port_speed_t *speed = xhci_get_port_speed(&hc->rh, port_id);
-//
-// 	usb_log_info("Detected new %.4s%u.%u device on port %u.", speed->name, speed->major, speed->minor, port_id);
-//
-// 	if (speed->major == 3) {
-// 		if(link_state == 0) {
-// 			/* USB3 is automatically advanced to enabled. */
-// 			return alloc_dev(hc, port_id, 0);
-// 		}
-// 		else if (link_state == 5) {
-// 			/* USB 3 failed to enable. */
-// 			usb_log_error("USB 3 port couldn't be enabled.");
-// 			return EAGAIN;
-// 		}
-// 		else {
-// 			usb_log_error("USB 3 port is in invalid state %u.", link_state);
-// 			return EINVAL;
-// 		}
-// 	}
-// 	else {
-// 		usb_log_debug("USB 2 device attached, issuing reset.");
-// 		xhci_reset_hub_port(hc, port_id);
-// 		/*
-// 			FIXME: we need to wait for the event triggered by the reset
-// 			and then alloc_dev()... can't it be done directly instead of
-// 			going around?
-// 		*/
-// 		return EOK;
-// 	}
-// }
-
-int xhci_handle_port_status_change_event(xhci_hc_t *hc, xhci_trb_t *trb)
-{
-	int err;
-
-	uint8_t port_id = xhci_get_hub_port(trb);
+static int handle_connected_device(xhci_rh_t *rh, uint8_t port_id)
+{
+	xhci_port_regs_t *regs = &rh->hc->op_regs->portrs[port_id - 1];
+
+	uint8_t link_state = XHCI_REG_RD(regs, XHCI_PORT_PLS);
+	const xhci_port_speed_t *speed = xhci_rh_get_port_speed(rh, port_id);
+
+	usb_log_info("Detected new %.4s%u.%u device on port %u.", speed->name, speed->major, speed->minor, port_id);
+
+	if (speed->major == 3) {
+		if (link_state == 0) {
+			/* USB3 is automatically advanced to enabled. */
+			// return alloc_dev(hc, port_id, 0);
+			return ENOTSUP;
+		}
+		else if (link_state == 5) {
+			/* USB 3 failed to enable. */
+			usb_log_error("USB 3 port couldn't be enabled.");
+			return EAGAIN;
+		}
+		else {
+			usb_log_error("USB 3 port is in invalid state %u.", link_state);
+			return EINVAL;
+		}
+	}
+	else {
+		usb_log_debug("USB 2 device attached, issuing reset.");
+		xhci_rh_reset_port(rh, port_id);
+		/*
+			FIXME: we need to wait for the event triggered by the reset
+			and then alloc_dev()... can't it be done directly instead of
+			going around?
+		*/
+		return EOK;
+	}
+}
+
+/** Handle an incoming Port Change Detected Event.
+ */
+int xhci_rh_handle_port_status_change_event(xhci_hc_t *hc, xhci_trb_t *trb)
+{
+	uint8_t port_id = XHCI_QWORD_EXTRACT(trb->parameter, 31, 24);
 	usb_log_debug("Port status change event detected for port %u.", port_id);
 
-	// Interrupt on the virtual hub status change pipe.
-	err = xhci_rh_interrupt(&hc->rh);
-	if (err != EOK) {
-		usb_log_warning("Invoking interrupt on virtual hub failed: %s",
-		    str_error(err));
+	/**
+	 * We can't be sure that the port change this event announces is the
+	 * only port change that happened (see section 4.19.2 of the xHCI
+	 * specification). Therefore, we just check all ports for changes.
+	 */
+	xhci_rh_handle_port_change(&hc->rh);
+
+	return EOK;
+}
+
+void xhci_rh_handle_port_change(xhci_rh_t *rh)
+{
+	for (uint8_t i = 1; i <= rh->max_ports; ++i) {
+		xhci_port_regs_t *regs = &rh->hc->op_regs->portrs[i - 1];
+
+		uint32_t events = XHCI_REG_RD_FIELD(&regs->portsc, 32);
+		XHCI_REG_WR_FIELD(&regs->portsc, events, 32);
+
+		events &= port_change_mask;
+
+		if (events & XHCI_REG_MASK(XHCI_PORT_CSC)) {
+			usb_log_info("Connected state changed on port %u.", i);
+			events &= ~XHCI_REG_MASK(XHCI_PORT_CSC);
+
+			bool connected = XHCI_REG_RD(regs, XHCI_PORT_CCS);
+			if (connected)
+				handle_connected_device(rh, i);
+		}
+
+		if (events & XHCI_REG_MASK(XHCI_PORT_PEC)) {
+			usb_log_info("Port enabled changed on port %u.", i);
+			events &= ~XHCI_REG_MASK(XHCI_PORT_PEC);
+		}
+
+		if (events & XHCI_REG_MASK(XHCI_PORT_WRC)) {
+			usb_log_info("Warm port reset on port %u completed.", i);
+			events &= ~XHCI_REG_MASK(XHCI_PORT_WRC);
+		}
+
+		if (events & XHCI_REG_MASK(XHCI_PORT_OCC)) {
+			usb_log_info("Over-current change on port %u.", i);
+			events &= ~XHCI_REG_MASK(XHCI_PORT_OCC);
+		}
+
+		if (events & XHCI_REG_MASK(XHCI_PORT_PRC)) {
+			usb_log_info("Port reset on port %u completed.", i);
+			events &= ~XHCI_REG_MASK(XHCI_PORT_PRC);
+		}
+
+		if (events & XHCI_REG_MASK(XHCI_PORT_PLC)) {
+			usb_log_info("Port link state changed on port %u.", i);
+			events &= ~XHCI_REG_MASK(XHCI_PORT_PLC);
+		}
+
+		if (events & XHCI_REG_MASK(XHCI_PORT_CEC)) {
+			usb_log_info("Port %u failed to configure link.", i);
+			events &= ~XHCI_REG_MASK(XHCI_PORT_CEC);
+		}
+
+		if (events) {
+			usb_log_warning("Port change (0x%08x) ignored on port %u.", events, i);
+		}
 	}
-
-	return EOK;
-}
-
-const xhci_port_speed_t *xhci_get_port_speed(xhci_rh_t *rh, uint8_t port)
+	
+	/**
+	 * Theory:
+	 *
+	 * Although more events could have happened while processing, the PCD
+	 * bit in USBSTS will be set on every change. Because the PCD is
+	 * cleared even before the interrupt is cleared, it is safe to assume
+	 * that this handler will be called again.
+	 *
+	 * But because we could have handled the event in previous run of this
+	 * handler, it is not an error when no event is detected.
+	 *
+	 * Reality:
+	 *
+	 * The PCD bit is never set. TODO Check why the interrupt never carries
+	 * the PCD flag. Possibly repeat the checking until we're sure the
+	 * PSCEG is 0 - check section 4.19.2 of the xHCI spec.
+	 */
+}
+
+const xhci_port_speed_t *xhci_rh_get_port_speed(xhci_rh_t *rh, uint8_t port)
 {
 	xhci_port_regs_t *port_regs = &rh->hc->op_regs->portrs[port - 1];
@@ -245,257 +302,11 @@
 }
 
-int xhci_get_hub_port(xhci_trb_t *trb)
-{
-	assert(trb);
-	uint8_t port_id = XHCI_QWORD_EXTRACT(trb->parameter, 31, 24);
-
-	return port_id;
-}
-
-int xhci_reset_hub_port(xhci_hc_t* hc, uint8_t port)
+int xhci_rh_reset_port(xhci_rh_t* rh, uint8_t port)
 {
 	usb_log_debug2("Resetting port %u.", port);
-	xhci_port_regs_t *regs = &hc->op_regs->portrs[port-1];
-	XHCI_REG_WR(regs, XHCI_PORT_PR, 1);
+	xhci_port_regs_t *regs = &rh->hc->op_regs->portrs[port-1];
+	XHCI_REG_SET(regs, XHCI_PORT_PR, 1);
 
 	return EOK;
-}
-
-int xhci_rh_schedule(xhci_rh_t *rh, usb_transfer_batch_t *batch)
-{
-	assert(rh);
-	assert(batch);
-	const usb_target_t target = batch->ep->target;
-	batch->error = virthub_base_request(&rh->base, target,
-	    usb_transfer_batch_direction(batch), (void*)batch->setup_buffer,
-	    batch->buffer, batch->buffer_size, &batch->transfered_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(rh->unfinished_interrupt_transfer == NULL);
-		rh->unfinished_interrupt_transfer = batch;
-	} else {
-		usb_transfer_batch_finish(batch, NULL);
-		usb_transfer_batch_destroy(batch);
-	}
-	return EOK;
-}
-
-int xhci_rh_interrupt(xhci_rh_t *rh)
-{
-	usb_log_debug2("Called xhci_rh_interrupt().");
-
-	/* TODO: atomic swap needed */
-	usb_transfer_batch_t *batch = rh->unfinished_interrupt_transfer;
-	rh->unfinished_interrupt_transfer = NULL;
-	if (batch) {
-		const usb_target_t target = batch->ep->target;
-		batch->error = virthub_base_request(&rh->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);
-	}
-	return EOK;
-}
-
-/** Hub set feature request handler.
- * @param device Virtual hub device
- * @param setup_packet USB setup stage data.
- * @param[out] data destination data buffer, size must be at least
- *             setup_packet->length bytes
- * @param[out] act_size Sized of the valid response part of the buffer.
- * @return Error code.
- */
-static int req_clear_hub_feature(usbvirt_device_t *device,
-	const usb_device_request_setup_packet_t *setup_packet,
-	uint8_t *data, size_t *act_size)
-{
-	/* TODO: Implement me! */
-	usb_log_debug2("Called req_clear_hub_feature().");
-	return EOK;
-}
-
-#define XHCI_TO_USB(usb_feat, reg_set, ...) \
-	(((XHCI_REG_RD(reg_set, ##__VA_ARGS__)) ? 1 : 0) << (usb_feat))
-
-/** Port status request handler.
- * @param device Virtual hub device
- * @param setup_packet USB setup stage data.
- * @param[out] data destination data buffer, size must be at least
- *             setup_packet->length bytes
- * @param[out] act_size Sized of the valid response part of the buffer.
- * @return Error code.
- */
-static int req_get_port_status(usbvirt_device_t *device,
-	const usb_device_request_setup_packet_t *setup_packet,
-	uint8_t *data, size_t *act_size)
-{
-	xhci_rh_t *hub = virthub_get_data(device);
-	assert(hub);
-
-	if (!setup_packet->index || setup_packet->index > hub->max_ports) {
-		return ESTALL;
-	}
-
-	/* The index is 1-based. */
-	xhci_port_regs_t* regs = &hub->hc->op_regs->portrs[setup_packet->index - 1];
-
-	const uint32_t status = uint32_host2usb(
-	    XHCI_TO_USB(USB_HUB_FEATURE_C_PORT_CONNECTION, regs, XHCI_PORT_CSC) |
-	    XHCI_TO_USB(USB_HUB_FEATURE_C_PORT_ENABLE, regs, XHCI_PORT_PEC) |
-	    XHCI_TO_USB(USB_HUB_FEATURE_C_PORT_OVER_CURRENT, regs, XHCI_PORT_OCC) |
-	    XHCI_TO_USB(USB_HUB_FEATURE_C_PORT_RESET, regs, XHCI_PORT_PRC) |
-	    XHCI_TO_USB(USB_HUB_FEATURE_PORT_CONNECTION, regs, XHCI_PORT_CCS) |
-	    XHCI_TO_USB(USB_HUB_FEATURE_PORT_ENABLE, regs, XHCI_PORT_PED) |
-	    XHCI_TO_USB(USB_HUB_FEATURE_PORT_OVER_CURRENT, regs, XHCI_PORT_OCA) |
-	    XHCI_TO_USB(USB_HUB_FEATURE_PORT_RESET, regs, XHCI_PORT_PR) |
-	    XHCI_TO_USB(USB_HUB_FEATURE_PORT_POWER, regs, XHCI_PORT_PP)
-	);
-
-	usb_log_debug2("RH: GetPortStatus(%hu) = %u.", setup_packet->index,
-		uint32_usb2host(status));
-
-	memcpy(data, &status, sizeof(status));
-	*act_size = sizeof(status);
-
-	return EOK;
-}
-
-/** Port clear feature request handler.
- * @param device Virtual hub device
- * @param setup_packet USB setup stage data.
- * @param[out] data destination data buffer, size must be at least
- *             setup_packet->length bytes
- * @param[out] act_size Sized of the valid response part of the buffer.
- * @return Error code.
- */
-static int req_clear_port_feature(usbvirt_device_t *device,
-	const usb_device_request_setup_packet_t *setup_packet,
-	uint8_t *data, size_t *act_size)
-{
-	xhci_rh_t *hub = virthub_get_data(device);
-	assert(hub);
-
-	if (!setup_packet->index || setup_packet->index > hub->max_ports) {
-		return ESTALL;
-	}
-
-	/* The index is 1-based. */
-	xhci_port_regs_t* regs = &hub->hc->op_regs->portrs[setup_packet->index - 1];
-
-	const usb_hub_class_feature_t feature = uint16_usb2host(setup_packet->value);
-	static const ioport32_t masks[] = {
-		USB_MAP_XHCI(C_PORT_CONNECTION, CSC),
-		USB_MAP_XHCI(C_PORT_ENABLE, PEC),
-		USB_MAP_XHCI(C_PORT_OVER_CURRENT, OCC),
-		USB_MAP_XHCI(C_PORT_RESET, PRC),
-		USB_MAP_XHCI(PORT_ENABLE, PED),
-		USB_MAP_XHCI(PORT_RESET, PR),
-		USB_MAP_XHCI(PORT_POWER, PP)
-	};
-
-	static const bool is_change[] = {
-		USB_MAP_VALUE(C_PORT_CONNECTION, true),
-		USB_MAP_VALUE(C_PORT_ENABLE, true),
-		USB_MAP_VALUE(C_PORT_OVER_CURRENT, true),
-		USB_MAP_VALUE(C_PORT_RESET, true),
-		USB_MAP_VALUE(PORT_ENABLE, false),
-		USB_MAP_VALUE(PORT_RESET, false),
-		USB_MAP_VALUE(PORT_POWER, false)
-	};
-
-	usb_log_debug2("RH: ClearPortFeature(%hu) = %d.", setup_packet->index,
-		feature);
-
-	if (is_change[feature]) {
-		/* Clear the register by writing 1. */
-		XHCI_REG_SET_FIELD(&regs->portsc, masks[feature], 32);
-	} else {
-		/* Clear the register by writing 0. */
-		XHCI_REG_CLR_FIELD(&regs->portsc, masks[feature], 32);
-	}
-
-	return EOK;
-}
-
-/** Port set feature request handler.
- * @param device Virtual hub device
- * @param setup_packet USB setup stage data.
- * @param[out] data destination data buffer, size must be at least
- *             setup_packet->length bytes
- * @param[out] act_size Sized of the valid response part of the buffer.
- * @return Error code.
- */
-static int req_set_port_feature(usbvirt_device_t *device,
-	const usb_device_request_setup_packet_t *setup_packet,
-	uint8_t *data, size_t *act_size)
-{
-	xhci_rh_t *hub = virthub_get_data(device);
-	assert(hub);
-
-	if (!setup_packet->index || setup_packet->index > hub->max_ports) {
-		return ESTALL;
-	}
-
-	/* The index is 1-based. */
-	xhci_port_regs_t* regs = &hub->hc->op_regs->portrs[setup_packet->index - 1];
-
-	const usb_hub_class_feature_t feature = uint16_usb2host(setup_packet->value);
-	static const ioport32_t masks[] = {
-		USB_MAP_XHCI(PORT_ENABLE, PED),
-		USB_MAP_XHCI(PORT_RESET, PR),
-		USB_MAP_XHCI(PORT_POWER, PP)
-	};
-
-	usb_log_debug2("RH: SetPortFeature(%hu) = %d.", setup_packet->index,
-		feature);
-
-	/* Set the feature in the PIO register. */
-	XHCI_REG_SET_FIELD(&regs->portsc, masks[feature], 32);
-
-	return EOK;
-}
-
-/** Status change handler.
- * @param device Virtual hub device
- * @param endpoint Endpoint number
- * @param tr_type Transfer type
- * @param buffer Response destination
- * @param buffer_size Bytes available in buffer
- * @param actual_size Size us the used part of the dest buffer.
- *
- * Produces status mask. Bit 0 indicates hub status change the other bits
- * represent port status change.
- */
-static int req_status_change_handler(usbvirt_device_t *device,
-	usb_endpoint_t endpoint, usb_transfer_type_t tr_type,
-	void *buffer, size_t buffer_size, size_t *actual_size)
-{
-	xhci_rh_t *hub = virthub_get_data(device);
-	assert(hub);
-
-	uint8_t status[STATUS_BYTES(hub->max_ports)];
-	memset(status, 0, sizeof(status));
-
-	if (buffer_size < sizeof(status))
-		return ESTALL;
-
-	bool change = false;
-	for (size_t i = 1; i <= hub->max_ports; ++i) {
-		xhci_port_regs_t *regs = &hub->hc->op_regs->portrs[i - 1];
-
-		if (XHCI_REG_RD_FIELD(&regs->portsc, 32) & port_change_mask) {
-			status[i / 8] |= (1 << (i % 8));
-			change = true;
-		}
-	}
-
-	usb_log_debug2("RH: Status change %s", (change ? "occurred" : "did not occur"));
-	memcpy(buffer, &status, sizeof(status));
-	*actual_size = sizeof(status);
-	return change ? EOK : ENAK;
 }
 
@@ -507,70 +318,4 @@
 }
 
-/** XHCI root hub request handlers */
-static const usbvirt_control_request_handler_t control_transfer_handlers[] = {
-	{
-		STD_REQ_IN(USB_REQUEST_RECIPIENT_DEVICE, USB_DEVREQ_GET_DESCRIPTOR),
-		.name = "GetDescriptor",
-		.callback = virthub_base_get_hub_descriptor,
-	},
-	{
-		CLASS_REQ_IN(USB_REQUEST_RECIPIENT_DEVICE, USB_DEVREQ_GET_DESCRIPTOR),
-		.name = "GetDescriptor",
-		.callback = virthub_base_get_hub_descriptor,
-	},
-	{
-		CLASS_REQ_IN(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_GET_DESCRIPTOR),
-		.name = "GetHubDescriptor",
-		.callback = virthub_base_get_hub_descriptor,
-	},
-	{
-		CLASS_REQ_IN(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_GET_STATUS),
-		.name = "GetPortStatus",
-		.callback = req_get_port_status,
-	},
-	{
-		CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_CLEAR_FEATURE),
-		.name = "ClearHubFeature",
-		.callback = req_clear_hub_feature,
-	},
-	{
-		CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_CLEAR_FEATURE),
-		.name = "ClearPortFeature",
-		.callback = req_clear_port_feature,
-	},
-	{
-		CLASS_REQ_IN(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_GET_STATUS),
-		.name = "GetHubStatus",
-		/* XHCI root hub has no power source,
-		 * over-current is reported by port */
-		.callback = virthub_base_get_null_status,
-	},
-	{
-		CLASS_REQ_IN(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_GET_STATUS),
-		.name = "GetPortStatus",
-		.callback = req_get_port_status,
-	},
-	{
-		CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_SET_FEATURE),
-		.name = "SetHubFeature",
-		.callback = req_nop,
-	},
-	{
-		CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_SET_FEATURE),
-		.name = "SetPortFeature",
-		.callback = req_set_port_feature,
-	},
-	{
-		.callback = NULL
-	}
-};
-
-/** Virtual XHCI root hub ops */
-static usbvirt_device_ops_t ops = {
-	.control = control_transfer_handlers,
-	.data_in[HUB_STATUS_CHANGE_PIPE] = req_status_change_handler,
-};
-
-
 /**
  * @}
Index: uspace/drv/bus/usb/xhci/rh.h
===================================================================
--- uspace/drv/bus/usb/xhci/rh.h	(revision 063dfe863508dd89880d6cd9d69eb9cb5a73e95d)
+++ uspace/drv/bus/usb/xhci/rh.h	(revision dcf0597038009fab95226d17afe1802c9145bebe)
@@ -38,12 +38,7 @@
 
 #include <usb/host/usb_transfer_batch.h>
-#include <usbvirt/virthub_base.h>
 #include "hw_struct/regs.h"
 
 typedef struct xhci_hc xhci_hc_t;
-
-enum {
-	XHCI_MAX_PORTS = 255,
-};
 
 /**
@@ -58,7 +53,4 @@
 /* XHCI root hub instance */
 typedef struct {
-	/** Virtual hub instance */
-	virthub_base_t base;
-
 	/** Host controller */
 	xhci_hc_t *hc;
@@ -66,10 +58,4 @@
 	/** Port speeds reported from HC */
 	xhci_port_speed_t speeds [16];
-
-	/** USB hub descriptor describing the XHCI root hub */
-	struct {
-		usb_hub_descriptor_header_t header;
-		uint8_t rempow[STATUS_BYTES(XHCI_MAX_PORTS) * 2];
-	} __attribute__((packed)) hub_descriptor;
 
 	/** Interrupt transfer waiting for an actual interrupt to occur */
@@ -82,27 +68,10 @@
 int xhci_rh_init(xhci_rh_t *, xhci_hc_t *);
 int xhci_rh_fini(xhci_rh_t *);
-const xhci_port_speed_t *xhci_get_port_speed(xhci_rh_t *, uint8_t);
-int xhci_handle_port_status_change_event(xhci_hc_t *, xhci_trb_t *);
-int xhci_get_hub_port(xhci_trb_t *);
-int xhci_reset_hub_port(xhci_hc_t *, uint8_t);
-int xhci_rh_schedule(xhci_rh_t *, usb_transfer_batch_t *);
-int xhci_rh_interrupt(xhci_rh_t *);
+const xhci_port_speed_t *xhci_rh_get_port_speed(xhci_rh_t *, uint8_t);
+int xhci_rh_reset_port(xhci_rh_t *, uint8_t);
 
-/** Get XHCI rh address.
- *
- * @param rh XHCI rh instance.
- * @return USB address assigned to the hub.
- * Wrapper for virtual hub address
- */
-static inline usb_address_t xhci_rh_get_address(xhci_rh_t *rh)
-{
-	assert(rh);
-	return virthub_base_get_address(&rh->base);
-}
+int xhci_rh_handle_port_status_change_event(xhci_hc_t *, xhci_trb_t *);
+void xhci_rh_handle_port_change(xhci_rh_t *);
 
-static inline bool xhci_is_usb3_port(xhci_rh_t* rh, uint8_t port)
-{
-	return xhci_get_port_speed(rh, port)->major == 3;
-}
 #endif
 
Index: uspace/drv/bus/usb/xhci/transfers.c
===================================================================
--- uspace/drv/bus/usb/xhci/transfers.c	(revision 063dfe863508dd89880d6cd9d69eb9cb5a73e95d)
+++ uspace/drv/bus/usb/xhci/transfers.c	(revision dcf0597038009fab95226d17afe1802c9145bebe)
@@ -36,4 +36,5 @@
 #include <usb/host/utils/malloc32.h>
 #include <usb/debug.h>
+#include <usb/request.h>
 #include "endpoint.h"
 #include "hc.h"
