Index: uspace/drv/bus/usb/usbhub/port.c
===================================================================
--- uspace/drv/bus/usb/usbhub/port.c	(revision 8ad2b0a99f1326c29aad2873ef275fc1d343f087)
+++ uspace/drv/bus/usb/usbhub/port.c	(revision 94f8c363ee71e2fb5993b37d877a1cb451f25950)
@@ -58,37 +58,13 @@
 	assert(port);
 	memset(port, 0, sizeof(*port));
-	fibril_mutex_initialize(&port->guard);
-	fibril_condvar_initialize(&port->state_cv);
 	port->hub = hub;
 	port->port_number = port_number;
-}
-
-/**
- * Utility method to change current port state and notify waiters.
- */
-static void port_change_state(usb_hub_port_t *port, port_state_t state)
-{
-	assert(fibril_mutex_is_locked(&port->guard));
-#define S(STATE) [PORT_##STATE] = #STATE
-	static const char *state_names [] = {
-	    S(DISABLED), S(CONNECTED), S(ERROR), S(IN_RESET), S(ENABLED),
-	};
-#undef S
-	port_log(debug, port, "%s ->%s", state_names[port->state], state_names[state]);
-	port->state = state;
-	fibril_condvar_broadcast(&port->state_cv);
-}
-
-/**
- * Utility method to wait for a particular state.
- *
- * @warning Some states might not be reached because of an external error
- * condition (PORT_ENABLED).
- */
-static void port_wait_state(usb_hub_port_t *port, port_state_t state)
-{
-	assert(fibril_mutex_is_locked(&port->guard));
-	while (port->state != state)
-		fibril_condvar_wait(&port->state_cv, &port->guard);
+	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;
 }
 
@@ -96,223 +72,80 @@
  * Inform the HC that the device on port is gone.
  */
-static int usb_hub_port_device_gone(usb_hub_port_t *port)
-{
-	assert(port);
-	assert(fibril_mutex_is_locked(&port->guard));
-	assert(port->state == PORT_ENABLED);
+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)
+	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);
+}
+
+/**
+ * Routine for adding a new device.
+ *
+ * Separate fibril is needed because the operation blocks on waiting for
+ * requesting default address and resetting port, and we must not block the
+ * control pipe.
+ */
+static int enumerate_device(usb_port_t *port_base)
+{
+	int err;
+	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 int rc = usbhc_device_remove(exch, port->port_number);
-	usb_device_bus_exchange_end(exch);
-	return rc;
-}
-
-/**
- * Teardown a device on port, no matter which state it was in.
- */
-static void port_make_disabled(usb_hub_port_t *port)
-{
-	assert(fibril_mutex_is_locked(&port->guard));
-
-	port_log(debug, port, "Making device offline.");
-
-	switch (port->state) {
-	case PORT_ENABLED:
-		port_log(debug, port, "Enabled ->");
-		if (usb_hub_port_device_gone(port))
-			port_log(error, port, "Failed to remove the device node from HC. Continuing anyway.");
-		port_change_state(port, PORT_DISABLED);
-		break;
-
-	case PORT_CONNECTED:
-		port_log(debug, port, "Connected ->");
-		/* fallthrough */
-	case PORT_IN_RESET:
-		port_log(debug, port, "In reset ->");
-		port_change_state(port, PORT_ERROR);
-		/* fallthrough */
-	case PORT_ERROR:
-		port_log(debug, port, "Error ->");
-		port_wait_state(port, PORT_DISABLED);
-		/* fallthrough */
-	case PORT_DISABLED:
-		port_log(debug, port, "Disabled.");
-		break;
-	}
-
-	assert(port->state == PORT_DISABLED);
-}
-
-/**
- * Finalize a port. Make sure no fibril is managing its state,
- * and that the HC is aware the device is no longer there.
- */
-void usb_hub_port_fini(usb_hub_port_t *port)
-{
-	assert(port);
-
-	fibril_mutex_lock(&port->guard);
-	switch (port->state) {
-	case PORT_ENABLED:
-		/*
-		 * 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 behave like a bad driver and leave
-		 * the work for HC.
-		 */
-		port_change_state(port, PORT_DISABLED);
-		break;
-
-	case PORT_CONNECTED:
-	case PORT_IN_RESET:
-		port_change_state(port, PORT_ERROR);
-		/* fallthrough */
-	case PORT_ERROR:
-		port_wait_state(port, PORT_DISABLED);
-		/* fallthrough */
-	case PORT_DISABLED:
-		break;
-	}
-	port_log(debug, port, "Finalized.");
-	fibril_mutex_unlock(&port->guard);
-}
-
-static int port_reset_sync(usb_hub_port_t *port)
-{
-	assert(fibril_mutex_is_locked(&port->guard));
-	assert(port->state == PORT_CONNECTED);
-
-	port_change_state(port, PORT_IN_RESET);
-	port_log(debug2, port, "Issuing reset.");
+	}
+
+	/* Reserve 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));
+		goto out_exch;
+	}
+
+	/* Reservation of default address could have blocked */
+	if (port->base.state != PORT_CONNECTING)
+		goto out_address;
+
+	port_log(debug, port, "Got default address. Resetting port.");
 	int rc = usb_hub_set_port_feature(port->hub, port->port_number, USB_HUB_FEATURE_PORT_RESET);
 	if (rc != EOK) {
 		port_log(warning, port, "Port reset request failed: %s", str_error(rc));
-		return rc;
-	}
-
-	fibril_condvar_wait_timeout(&port->state_cv, &port->guard, 2000000);
-	return port->state == PORT_ENABLED ? EOK : ESTALL;
-}
-
-/**
- * Routine for adding a new device.
- *
- * Separate fibril is needed because the operation blocks on waiting for
- * requesting default address and resetting port, and we must not block the
- * control pipe.
- */
-static void setup_device(usb_hub_port_t *port)
-{
-	int err;
-
-	fibril_mutex_lock(&port->guard);
-
-	if (port->state == PORT_ERROR) {
-		/* 
-		 * The device was removed faster than this fibril acquired the
-		 * mutex.
-		 */
-		port_change_state(port, PORT_DISABLED);
-		goto out;
-	}
-
-	if (port->state != PORT_CONNECTED) {
-		/*
-		 * Another fibril already took care of the device.
-		 * This may happen for example when the connection is unstable
-		 * and a sequence of connect, disconnect and connect come
-		 * faster the first fibril manages to request the default
-		 * address.
-		 */
-		goto out;
-	}
-
-	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.");
-		goto out;
-	}
-
-	/* Reserve default address */
-	err = usb_hub_reserve_default_address(port->hub, exch, &port->guard);
-	if (err != EOK) {
-		port_log(error, port, "Failed to reserve default address: %s", str_error(err));
-		port_change_state(port, PORT_DISABLED);
-		goto out_exch;
-	}
-
-	/* Reservation of default address could have blocked */
-	if (port->state != PORT_CONNECTED) {
-		assert(port->state == PORT_ERROR);
-		port_change_state(port, PORT_DISABLED);
-		goto out_exch;
-	}
-
-	port_log(debug, port, "Got default address. Resetting port.");
-
-	if ((err = port_reset_sync(port))) {
-		port_log(error, port, "Failed to reset port.");
-		port_change_state(port, PORT_DISABLED);
-		goto out_address;
-	}
-
-	assert(port->state == PORT_ENABLED);
+		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, "Port reset, enumerating device.");
 
-	if ((err = usbhc_device_enumerate(exch, port->port_number, port->speed))) {
+	if ((err = usbhc_device_enumerate(exch, port->port_number, port->base.speed))) {
 		port_log(error, port, "Failed to enumerate device: %s", str_error(err));
-		port_change_state(port, PORT_DISABLED);
-		goto out_port;
+		/* Disable the port */
+		usb_hub_clear_port_feature(port->hub, port->port_number, USB_HUB_FEATURE_PORT_ENABLE);
+		goto out_address;
 	}
 
 	port_log(debug, port, "Device enumerated");
 
-out_port:
-	if (port->state != PORT_ENABLED)
-		usb_hub_clear_port_feature(port->hub, port->port_number, USB_HUB_FEATURE_C_PORT_ENABLE);
 out_address:
 	usb_hub_release_default_address(port->hub, exch);
 out_exch:
 	usb_device_bus_exchange_end(exch);
-out:
-	assert(port->state == PORT_ENABLED || port->state == PORT_DISABLED);
-	fibril_mutex_unlock(&port->guard);
-}
-
-static int setup_device_worker(void *arg)
-{
-	setup_device(arg);
-	return EOK;
-}
-
-/** 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 int create_setup_device_fibril(usb_hub_port_t *port)
-{
-	assert(port);
-
-	fid_t fibril = fibril_create(setup_device_worker, port);
-	if (!fibril)
-		return ENOMEM;
-
-	fibril_add_ready(fibril);
-	return EOK;
+	return err;
 }
 
@@ -323,13 +156,7 @@
 
 	if (connected) {
-		if (port->state == PORT_ENABLED)
-			port_log(warning, port, "Connection detected on port that is currently enabled. Resetting.");
-
-		port_make_disabled(port);
-		port_change_state(port, PORT_CONNECTED);
-		port->speed = usb_port_speed(status);
-		create_setup_device_fibril(port);
+		usb_port_connected(&port->base, &enumerate_device);
 	} else {
-		port_make_disabled(port);
+		usb_port_disabled(&port->base, &remove_device);
 	}
 }
@@ -341,5 +168,5 @@
 		port_log(warning, port, "Port unexpectedly changed to enabled.");
 	} else {
-		port_make_disabled(port);
+		usb_port_disabled(&port->base, &remove_device);
 	}
 }
@@ -360,5 +187,5 @@
 	 * back on when the over-current condition is gone */
 
-	port_make_disabled(port);
+	usb_port_disabled(&port->base, &remove_device);
 
 	if (!overcurrent) {
@@ -373,9 +200,8 @@
 	const bool enabled = !!(status & USB_HUB_PORT_STATUS_ENABLED);
 
-	/* Check if someone is waiting for the result */
-	if (port->state != PORT_IN_RESET)
-		return;
-
-	port_change_state(port, enabled ? PORT_ENABLED : PORT_ERROR);
+	if (enabled)
+		usb_port_enabled(&port->base, usb_port_speed(status));
+	else
+		usb_port_disabled(&port->base, &remove_device);
 }
 
@@ -398,8 +224,7 @@
  * @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);
 	port_log(debug2, port, "Interrupt.");
 
@@ -411,6 +236,4 @@
 	}
 
-	fibril_mutex_lock(&port->guard);
-
 	for (uint32_t feature = 0; feature < sizeof(usb_port_status_t) * 8; ++feature) {
 		uint32_t mask = 1 << feature;
@@ -429,6 +252,4 @@
 	}
 
-	fibril_mutex_unlock(&port->guard);
-
 	port_log(debug2, port, "Port status after handling: %#08" PRIx32, status);
 }
Index: uspace/drv/bus/usb/usbhub/port.h
===================================================================
--- uspace/drv/bus/usb/usbhub/port.h	(revision 8ad2b0a99f1326c29aad2873ef275fc1d343f087)
+++ uspace/drv/bus/usb/usbhub/port.h	(revision 94f8c363ee71e2fb5993b37d877a1cb451f25950)
@@ -33,5 +33,5 @@
  */
 /** @file
- * Hub port state machine.
+ * Hub port handling.
  */
 
@@ -41,35 +41,20 @@
 #include <usb/dev/driver.h>
 #include <usb/classes/hub.h>
+#include <usb/port.h>
 
 typedef struct usb_hub_dev usb_hub_dev_t;
 
-typedef enum {
-	PORT_DISABLED,	/* No device connected. */
-	PORT_CONNECTED,	/* A device connected, not yet initialized. */
-	PORT_IN_RESET,	/* An initial port reset in progress. */
-	PORT_ENABLED,	/* Port reset complete, port enabled. Device announced to the HC. */
-	PORT_ERROR,	/* An error occured. There is still a fibril that needs to know it. */
-} port_state_t;
-
 /** Information about single port on a hub. */
 typedef struct {
+	usb_port_t base;
 	/* Parenting hub */
 	usb_hub_dev_t *hub;
-	/** Guarding all fields */
-	fibril_mutex_t guard;
-	/** Current state of the port */
-	port_state_t state;
-	/** A speed of the device connected (if any). Valid unless state == PORT_DISABLED. */
-	usb_speed_t speed;
 	/** Port number as reported in descriptors. */
 	unsigned int port_number;
-	/** CV for waiting to port reset completion. */
-	fibril_condvar_t state_cv;
 } usb_hub_port_t;
 
 void usb_hub_port_init(usb_hub_port_t *, usb_hub_dev_t *, unsigned int);
-void usb_hub_port_fini(usb_hub_port_t *);
 
-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/usbhub.c
===================================================================
--- uspace/drv/bus/usb/usbhub/usbhub.c	(revision 8ad2b0a99f1326c29aad2873ef275fc1d343f087)
+++ uspace/drv/bus/usb/usbhub/usbhub.c	(revision 94f8c363ee71e2fb5993b37d877a1cb451f25950)
@@ -200,5 +200,5 @@
 
 	for (size_t port = 0; port < hub->port_count; ++port) {
-		usb_hub_port_fini(&hub->ports[port]);
+		usb_port_fini(&hub->ports[port].base);
 	}
 	free(hub->ports);
@@ -291,5 +291,5 @@
 		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]);
 		}
 	}
@@ -605,4 +605,6 @@
 }
 
+static FIBRIL_CONDVAR_INITIALIZE(global_hub_default_address_cv);
+
 /**
  * Reserve a default address for a port across all other devices connected to
@@ -611,10 +613,10 @@
  * is connected with already attached devices.
  */
-int usb_hub_reserve_default_address(usb_hub_dev_t *hub, async_exch_t *exch, fibril_mutex_t *guard)
+int usb_hub_reserve_default_address(usb_hub_dev_t *hub, async_exch_t *exch, usb_port_t *port)
 {
 	assert(hub);
 	assert(exch);
-	assert(guard);
-	assert(fibril_mutex_is_locked(guard));
+	assert(port);
+	assert(fibril_mutex_is_locked(&port->guard));
 
 	fibril_mutex_lock(&hub->default_address_guard);
@@ -624,12 +626,11 @@
 		int err;
 		while ((err = usbhc_reserve_default_address(exch)) == EAGAIN) {
-			fibril_mutex_unlock(guard);
-			async_usleep(500000);
-			fibril_mutex_lock(guard);
+			// We ignore the return value here, as we cannot give up now.
+			usb_port_condvar_wait_timeout(port, &global_hub_default_address_cv, 500000);
 		}
 		return err;
 	} else {
 		/* Drop the port guard, we're going to wait */
-		fibril_mutex_unlock(guard);
+		fibril_mutex_unlock(&port->guard);
 
 		/* Wait for a signal */
@@ -638,5 +639,5 @@
 		/* Remember ABBA, first drop the hub guard */
 		fibril_mutex_unlock(&hub->default_address_guard);
-		fibril_mutex_lock(guard);
+		fibril_mutex_lock(&port->guard);
 		return EOK;
 	}
@@ -655,4 +656,6 @@
 		// from requesting the address before we release
 		ret = usbhc_release_default_address(exch);
+		// This is optimistic optimization - it may wake one hub from polling sleep
+		fibril_condvar_signal(&global_hub_default_address_cv);
 	} else {
 		fibril_condvar_signal(&hub->default_address_cv);
Index: uspace/drv/bus/usb/usbhub/usbhub.h
===================================================================
--- uspace/drv/bus/usb/usbhub/usbhub.h	(revision 8ad2b0a99f1326c29aad2873ef275fc1d343f087)
+++ uspace/drv/bus/usb/usbhub/usbhub.h	(revision 94f8c363ee71e2fb5993b37d877a1cb451f25950)
@@ -89,5 +89,5 @@
 bool hub_port_changes_callback(usb_device_t *, uint8_t *, size_t, void *);
 
-int usb_hub_reserve_default_address(usb_hub_dev_t *, async_exch_t *, fibril_mutex_t *);
+int usb_hub_reserve_default_address(usb_hub_dev_t *, async_exch_t *, usb_port_t *);
 int usb_hub_release_default_address(usb_hub_dev_t *, async_exch_t *);
 
Index: uspace/lib/usb/Makefile
===================================================================
--- uspace/lib/usb/Makefile	(revision 8ad2b0a99f1326c29aad2873ef275fc1d343f087)
+++ uspace/lib/usb/Makefile	(revision 94f8c363ee71e2fb5993b37d877a1cb451f25950)
@@ -36,4 +36,5 @@
 	src/debug.c \
 	src/dump.c \
+	src/port.c \
 	src/usb.c
 
Index: uspace/lib/usb/include/usb/port.h
===================================================================
--- uspace/lib/usb/include/usb/port.h	(revision 94f8c363ee71e2fb5993b37d877a1cb451f25950)
+++ uspace/lib/usb/include/usb/port.h	(revision 94f8c363ee71e2fb5993b37d877a1cb451f25950)
@@ -0,0 +1,111 @@
+/*
+ * 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 call interface to announce events, and a callback structure for
+ * implementations to supply the hardware-dependent part.
+ */
+
+#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_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;
+	/** A speed of the device connected (if any). Valid unless state == PORT_DISABLED. */
+	usb_speed_t speed;
+	/** 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 *, usb_speed_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/src/port.c
===================================================================
--- uspace/lib/usb/src/port.c	(revision 94f8c363ee71e2fb5993b37d877a1cb451f25950)
+++ uspace/lib/usb/src/port.c	(revision 94f8c363ee71e2fb5993b37d877a1cb451f25950)
@@ -0,0 +1,212 @@
+/*
+ * 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 call interface to announce events, and a callback structure for
+ * implementations to supply the hardware-dependent part.
+ */
+
+#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, usb_speed_t speed)
+{
+	assert(port);
+
+	fibril_mutex_lock(&port->guard);
+	port->speed = speed;
+	fibril_condvar_broadcast(&port->enabled_cv);
+	fibril_mutex_unlock(&port->guard);
+}
+
+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:
+		handler(port);
+		port->state = PORT_DISABLED;
+		break;
+
+	case PORT_CONNECTING:
+		port->state = PORT_ERROR;
+		/* fallthrough */
+	case PORT_ERROR:
+		fibril_condvar_wait(&port->finished_cv, &port->guard);
+		/* fallthrough */
+	case PORT_DISABLED:
+		break;
+	}
+
+	assert(port->state == PORT_DISABLED);
+	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;
+		/* fallthrough */
+	case PORT_ERROR:
+		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;
+}
+
+/**
+ * @}
+ */
