Index: uspace/lib/usb/Makefile
===================================================================
--- uspace/lib/usb/Makefile	(revision 17c5e62d0dbb1b04123131491f4743240d75deb7)
+++ uspace/lib/usb/Makefile	(revision a9fcd73c9a825936be7838244fd03da4f23bf1e7)
@@ -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 a9fcd73c9a825936be7838244fd03da4f23bf1e7)
+++ uspace/lib/usb/include/usb/port.h	(revision a9fcd73c9a825936be7838244fd03da4f23bf1e7)
@@ -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 a9fcd73c9a825936be7838244fd03da4f23bf1e7)
+++ uspace/lib/usb/src/port.c	(revision a9fcd73c9a825936be7838244fd03da4f23bf1e7)
@@ -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;
+}
+
+/**
+ * @}
+ */
