Index: uspace/drv/bus/usb/xhci/rh.c
===================================================================
--- uspace/drv/bus/usb/xhci/rh.c	(revision eeca8a6576e862d2b94e31531749315d4d938507)
+++ uspace/drv/bus/usb/xhci/rh.c	(revision a9fcd73c9a825936be7838244fd03da4f23bf1e7)
@@ -42,4 +42,5 @@
 #include <usb/host/dma_buffer.h>
 #include <usb/host/hcd.h>
+#include <usb/port.h>
 
 #include "debug.h"
@@ -61,4 +62,12 @@
 	XHCI_REG_MASK(XHCI_PORT_CEC);
 
+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;
+
 /**
  * Initialize the roothub subsystem.
@@ -71,9 +80,19 @@
 	rh->hc = hc;
 	rh->max_ports = XHCI_REG_RD(hc->cap_regs, XHCI_CAP_MAX_PORTS);
-	rh->devices_by_port = (xhci_device_t **) calloc(rh->max_ports, sizeof(xhci_device_t *));
+	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)
+	if (err) {
+		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 */
@@ -81,8 +100,4 @@
 	rh->device.tier = 0;
 
-	fibril_mutex_initialize(&rh->event_guard);
-	fibril_condvar_initialize(&rh->event_ready);
-	fibril_condvar_initialize(&rh->event_handled);
-
 	return EOK;
 }
@@ -94,62 +109,13 @@
 {
 	assert(rh);
-	free(rh->devices_by_port);
+	for (unsigned i = 0; i < rh->max_ports; i++)
+		usb_port_fini(&rh->ports[i].base);
 	return EOK;
 }
 
-typedef struct rh_event {
-	uint8_t port_id;
-	uint32_t events;
-	unsigned readers_to_go;
-} rh_event_t;
-
-static int rh_event_wait_timeout(xhci_rh_t *rh, uint8_t port_id, uint32_t mask, suseconds_t timeout)
-{
-	int r;
-	assert(fibril_mutex_is_locked(&rh->event_guard));
-
-	++rh->event_readers_waiting;
-
-	do {
-		r = fibril_condvar_wait_timeout(&rh->event_ready, &rh->event_guard, timeout);
-		if (r != EOK)
-			break;
-
-		assert(rh->event);
-		if (--rh->event->readers_to_go == 0)
-			fibril_condvar_broadcast(&rh->event_handled);
-	} while (rh->event->port_id != port_id || (rh->event->events & mask) != mask);
-
-	if (r == EOK)
-		rh->event->events &= ~mask;
-
-	--rh->event_readers_waiting;
-
-	return r;
-}
-
-static void rh_event_run_handlers(xhci_rh_t *rh, uint8_t port_id, uint32_t *events)
-{
-	assert(fibril_mutex_is_locked(&rh->event_guard));
-
-	/* There can be different event running already */
-	while (rh->event)
-		fibril_condvar_wait(&rh->event_handled, &rh->event_guard);
-
-	rh_event_t event = {
-		.port_id = port_id,
-		.events = *events,
-		.readers_to_go = rh->event_readers_waiting,
-	};
-
-	rh->event = &event;
-	fibril_condvar_broadcast(&rh->event_ready);
-	while (event.readers_to_go)
-		fibril_condvar_wait(&rh->event_handled, &rh->event_guard);
-	*events = event.events;
-	rh->event = NULL;
-
-	/* Wake other threads potentially waiting to post their event */
-	fibril_condvar_broadcast(&rh->event_handled);
+static rh_port_t *get_rh_port(usb_port_t *port)
+{
+	assert(port);
+	return (rh_port_t *) port;
 }
 
@@ -158,18 +124,17 @@
  * a virtual usbhub device for RH, this routine is called for devices directly.
  */
-static int rh_setup_device(xhci_rh_t *rh, uint8_t port_id)
+static int rh_enumerate_device(usb_port_t *usb_port)
 {
 	int err;
-	assert(rh);
-	assert(rh->devices_by_port[port_id - 1] == NULL);
-
-	if (!XHCI_REG_RD(&rh->hc->op_regs->portrs[port_id - 1], XHCI_PORT_PED)) {
-		usb_log_error("Cannot setup RH device: port is disabled.");
-		return EIO;
-	}
-
-	const xhci_port_speed_t *port_speed = xhci_rh_get_port_speed(rh, port_id);
-
-	device_t *dev = hcd_ddf_fun_create(&rh->hc->base, port_speed->usb_speed);
+	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;
+	}
+
+	device_t *dev = hcd_ddf_fun_create(&port->rh->hc->base, port->base.speed);
 	if (!dev) {
 		usb_log_error("Failed to create USB device function.");
@@ -177,10 +142,9 @@
 	}
 
-	dev->hub = &rh->device.base;
-	dev->port = port_id;
-
-	xhci_device_t *xhci_dev = xhci_device_get(dev);
-	xhci_dev->usb3 = port_speed->major == 3;
-	xhci_dev->rh_port = port_id;
+	dev->hub = &port->rh->device.base;
+	dev->port = port - port->rh->ports + 1;
+
+	port->device = xhci_device_get(dev);
+	port->device->rh_port = dev->port;
 
 	if ((err = bus_device_enumerate(dev))) {
@@ -195,9 +159,7 @@
 	if ((err = ddf_fun_bind(dev->fun))) {
 		usb_log_error("Failed to register device " XHCI_DEV_FMT " DDF function: %s.",
-		    XHCI_DEV_ARGS(*xhci_dev), str_error(err));
+		    XHCI_DEV_ARGS(*port->device), str_error(err));
 		goto err_usb_dev;
 	}
-
-	rh->devices_by_port[port_id - 1] = xhci_dev;
 
 	return EOK;
@@ -205,115 +167,24 @@
 err_usb_dev:
 	hcd_ddf_fun_destroy(dev);
+	port->device = NULL;
 	return err;
 }
 
-
-static int rh_port_reset_sync(xhci_rh_t *rh, uint8_t port_id)
-{
-	xhci_port_regs_t *regs = &rh->hc->op_regs->portrs[port_id - 1];
-
-	fibril_mutex_lock(&rh->event_guard);
-	XHCI_REG_SET(regs, XHCI_PORT_PR, 1);
-	const int r = rh_event_wait_timeout(rh, port_id, XHCI_REG_MASK(XHCI_PORT_PRC), 0);
-	fibril_mutex_unlock(&rh->event_guard);
-	return r;
-}
-
-/**
- * Handle a device connection. USB 3+ devices are set up directly, USB 2 and
- * below first need to have their port reset.
- */
-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 rh_setup_device(rh, port_id);
-		}
-		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.");
-		const int err = rh_port_reset_sync(rh, port_id);
-		if (err)
-			return err;
-
-		rh_setup_device(rh, port_id);
-		return EOK;
-	}
-}
-
 /**
  * Deal with a detached device.
  */
-static int handle_disconnected_device(xhci_rh_t *rh, uint8_t port_id)
-{
-	assert(rh);
-
-	/* Find XHCI device by the port. */
-	xhci_device_t *dev = rh->devices_by_port[port_id - 1];
-	if (!dev) {
-		/* Must be extraneous call. */
-		return EOK;
-	}
-
-	usb_log_info("Device " XHCI_DEV_FMT " at port %u has been disconnected.",
-	    XHCI_DEV_ARGS(*dev), port_id);
+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. */
-	rh->devices_by_port[port_id - 1] = NULL;
-
-	/* Remove device from XHCI bus. */
-	bus_device_gone(&dev->base);
-
-	return EOK;
-}
-
-typedef int (*rh_event_handler_t)(xhci_rh_t *, uint8_t);
-
-typedef struct rh_event_args {
-	xhci_rh_t *rh;
-	uint8_t port_id;
-	rh_event_handler_t handler;
-} rh_event_args_t;
-
-static int rh_event_handler_fibril(void *arg) {
-	rh_event_args_t *rh_args = arg;
-	xhci_rh_t *rh = rh_args->rh;
-	uint8_t port_id = rh_args->port_id;
-	rh_event_handler_t handler = rh_args->handler;
-
-	free(rh_args);
-
-	return handler(rh, port_id);
-}
-
-static fid_t handle_in_fibril(xhci_rh_t *rh, uint8_t port_id, rh_event_handler_t handler)
-{
-	rh_event_args_t *args = malloc(sizeof(*args));
-	*args = (rh_event_args_t) {
-		.rh = rh,
-		.port_id = port_id,
-		.handler = handler,
-	};
-
-	const fid_t fid = fibril_create(rh_event_handler_fibril, args);
-	fibril_add_ready(fid);
-	return fid;
+	port->device = NULL;
 }
 
@@ -323,10 +194,9 @@
 void xhci_rh_handle_port_change(xhci_rh_t *rh, uint8_t port_id)
 {
-	fibril_mutex_lock(&rh->event_guard);
-	xhci_port_regs_t * const regs = &rh->hc->op_regs->portrs[port_id - 1];
-
-	uint32_t events = XHCI_REG_RD_FIELD(&regs->portsc, 32) & port_events_mask;
-
-	while (events) {
+	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
@@ -334,43 +204,44 @@
 		 * standard mechanisms of register handling fails here.
 		 */
-		uint32_t portsc = XHCI_REG_RD_FIELD(&regs->portsc, 32);
-		portsc &= ~(port_events_mask | XHCI_REG_MASK(XHCI_PORT_PED)); // Clear events + PED
-		portsc |= events; // Add back events to assert them
-		XHCI_REG_WR_FIELD(&regs->portsc, portsc, 32);
-
-		if (events & XHCI_REG_MASK(XHCI_PORT_CSC)) {
+		XHCI_REG_WR_FIELD(&port->regs->portsc, status & ~XHCI_REG_MASK(XHCI_PORT_PED), 32);
+
+		if (status & XHCI_REG_MASK(XHCI_PORT_CSC)) {
 			usb_log_info("Connected state changed on port %u.", port_id);
-			events &= ~XHCI_REG_MASK(XHCI_PORT_CSC);
-
-			bool connected = XHCI_REG_RD(regs, XHCI_PORT_CCS);
+			status &= ~XHCI_REG_MASK(XHCI_PORT_CSC);
+
+			bool connected = !!(status & XHCI_REG_MASK(XHCI_PORT_CCS));
 			if (connected) {
-				handle_in_fibril(rh, port_id, handle_connected_device);
+				usb_port_connected(&port->base, &rh_enumerate_device);
 			} else {
-				handle_in_fibril(rh, port_id, handle_disconnected_device);
+				usb_port_disabled(&port->base, &rh_remove_device);
 			}
 		}
 
-		if (events != 0)
-			rh_event_run_handlers(rh, port_id, &events);
-
-		if (events != 0)
-			usb_log_debug("RH port %u change not handled: 0x%x", port_id, events);
+		if (status & XHCI_REG_MASK(XHCI_PORT_PRC)) {
+			status &= ~XHCI_REG_MASK(XHCI_PORT_PRC);
+			bool enabled = !!(status & XHCI_REG_MASK(XHCI_PORT_PED));
+
+			if (enabled) {
+				unsigned psiv = (status & XHCI_REG_MASK(XHCI_PORT_PS)) >> XHCI_REG_SHIFT(XHCI_PORT_PS);
+				const usb_speed_t speed = rh->hc->speeds[psiv].usb_speed;
+				usb_port_enabled(&port->base, speed);
+			} 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. */
-		events = XHCI_REG_RD_FIELD(&regs->portsc, 32) & port_events_mask;
-	}
-
-	fibril_mutex_unlock(&rh->event_guard);
-}
-
-/**
- * Get a port speed for a given port id.
- */
-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];
-
-	unsigned psiv = XHCI_REG_RD(port_regs, XHCI_PORT_PS);
-	return &rh->hc->speeds[psiv];
+		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;
 }
 
