Index: uspace/drv/bus/usb/uhcirh/port.c
===================================================================
--- uspace/drv/bus/usb/uhcirh/port.c	(revision 26e7d6da331e8d35b5e83880a9740de5e9e4bd99)
+++ uspace/drv/bus/usb/uhcirh/port.c	(revision 26e7d6da331e8d35b5e83880a9740de5e9e4bd99)
@@ -0,0 +1,355 @@
+/*
+ * 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 drvusbuhcirh
+ * @{
+ */
+/** @file
+ * @brief UHCI root hub port routines
+ */
+#include <libarch/ddi.h>  /* pio_read and pio_write */
+#include <fibril_synch.h> /* async_usleep */
+#include <errno.h>
+#include <str_error.h>
+#include <async.h>
+
+#include <usb/usb.h>    /* usb_address_t */
+#include <usb/dev/hub.h>    /* usb_hc_new_device_wrapper */
+#include <usb/debug.h>
+
+#include "port.h"
+
+static int uhci_port_check(void *port);
+static int uhci_port_reset_enable(int portno, void *arg);
+static int uhci_port_new_device(uhci_port_t *port, usb_speed_t speed);
+static int uhci_port_remove_device(uhci_port_t *port);
+static int uhci_port_set_enabled(uhci_port_t *port, bool enabled);
+static void uhci_port_print_status(
+    uhci_port_t *port, const port_status_t value);
+
+/** Register reading helper function.
+ *
+ * @param[in] port Structure to use.
+ * @return Error code. (Always EOK)
+ */
+static inline port_status_t uhci_port_read_status(uhci_port_t *port)
+{
+	assert(port);
+	return pio_read_16(port->address);
+}
+/*----------------------------------------------------------------------------*/
+/** Register writing helper function.
+ *
+ * @param[in] port Structure to use.
+ * @param[in] val New register value.
+ * @return Error code. (Always EOK)
+ */
+static inline void uhci_port_write_status(uhci_port_t *port, port_status_t val)
+{
+	assert(port);
+	pio_write_16(port->address, val);
+}
+/*----------------------------------------------------------------------------*/
+/** Initialize UHCI root hub port instance.
+ *
+ * @param[in] port Memory structure to use.
+ * @param[in] address Address of I/O register.
+ * @param[in] number Port number.
+ * @param[in] usec Polling interval.
+ * @param[in] rh Pointer to ddf instance of the root hub driver.
+ * @return Error code.
+ *
+ * Creates and starts the polling fibril.
+ */
+int uhci_port_init(uhci_port_t *port,
+    port_status_t *address, unsigned number, unsigned usec, ddf_dev_t *rh)
+{
+	assert(port);
+	char *id_string;
+	asprintf(&id_string, "Port (%p - %u)", port, number);
+	if (id_string == NULL) {
+		return ENOMEM;
+	}
+
+	port->id_string = id_string;
+	port->address = address;
+	port->number = number;
+	port->wait_period_usec = usec;
+	port->attached_device = 0;
+	port->rh = rh;
+
+	int ret =
+	    usb_hc_connection_initialize_from_device(&port->hc_connection, rh);
+	if (ret != EOK) {
+		usb_log_error("%s: failed to initialize connection to HC.",
+		    port->id_string);
+		free(id_string);
+		return ret;
+	}
+
+	port->checker = fibril_create(uhci_port_check, port);
+	if (port->checker == 0) {
+		usb_log_error("%s: failed to create polling fibril.",
+		    port->id_string);
+		free(id_string);
+		return ENOMEM;
+	}
+
+	fibril_add_ready(port->checker);
+	usb_log_debug("%s: Started polling fibril (%" PRIun ").\n",
+	    port->id_string, port->checker);
+	return EOK;
+}
+/*----------------------------------------------------------------------------*/
+/** Cleanup UHCI root hub port instance.
+ *
+ * @param[in] port Memory structure to use.
+ *
+ * Stops the polling fibril.
+ */
+void uhci_port_fini(uhci_port_t *port)
+{
+	assert(port);
+	free(port->id_string);
+	// TODO: Kill fibril here
+	return;
+}
+/*----------------------------------------------------------------------------*/
+/** Periodically checks port status and reports new devices.
+ *
+ * @param[in] port Port structure to use.
+ * @return Error code.
+ */
+int uhci_port_check(void *port)
+{
+	uhci_port_t *instance = port;
+	assert(instance);
+
+	while (1) {
+		async_usleep(instance->wait_period_usec);
+
+		/* Read register value */
+		const port_status_t port_status =
+		    uhci_port_read_status(instance);
+
+		/* Print the value if it's interesting */
+		if (port_status & ~STATUS_ALWAYS_ONE)
+			uhci_port_print_status(instance, port_status);
+
+		if ((port_status & STATUS_CONNECTED_CHANGED) == 0)
+			continue;
+
+		usb_log_debug("%s: Connected change detected: %x.\n",
+		    instance->id_string, port_status);
+
+		/* Remove any old device */
+		if (instance->attached_device) {
+			usb_log_debug2("%s: Removing device.\n",
+			    instance->id_string);
+			uhci_port_remove_device(instance);
+		}
+
+		int ret =
+		    usb_hc_connection_open(&instance->hc_connection);
+		if (ret != EOK) {
+			usb_log_error("%s: Failed to connect to HC.",
+			    instance->id_string);
+			continue;
+		}
+
+		if ((port_status & STATUS_CONNECTED) != 0) {
+			/* New device */
+			const usb_speed_t speed =
+			    ((port_status & STATUS_LOW_SPEED) != 0) ?
+			    USB_SPEED_LOW : USB_SPEED_FULL;
+			uhci_port_new_device(instance, speed);
+		} else {
+			/* Write one to WC bits, to ack changes */
+			uhci_port_write_status(instance, port_status);
+			usb_log_debug("%s: status change ACK.\n",
+			    instance->id_string);
+		}
+
+		ret = usb_hc_connection_close(&instance->hc_connection);
+		if (ret != EOK) {
+			usb_log_error("%s: Failed to disconnect.",
+			    instance->id_string);
+		}
+	}
+	return EOK;
+}
+/*----------------------------------------------------------------------------*/
+/** Callback for enabling port during adding a new device.
+ *
+ * @param portno Port number (unused).
+ * @param arg Pointer to uhci_port_t of port with the new device.
+ * @return Error code.
+ *
+ * Resets and enables the ub port.
+ */
+int uhci_port_reset_enable(int portno, void *arg)
+{
+	uhci_port_t *port = arg;
+	assert(port);
+
+	usb_log_debug2("%s: new_device_enable_port.\n", port->id_string);
+
+	/*
+	 * Resets from root ports should be nominally 50ms (USB spec 7.1.7.3)
+	 */
+	{
+		usb_log_debug("%s: Reset Signal start.\n", port->id_string);
+		port_status_t port_status = uhci_port_read_status(port);
+		port_status |= STATUS_IN_RESET;
+		uhci_port_write_status(port, port_status);
+		async_usleep(50000);
+		port_status = uhci_port_read_status(port);
+		port_status &= ~STATUS_IN_RESET;
+		uhci_port_write_status(port, port_status);
+		while (uhci_port_read_status(port) & STATUS_IN_RESET);
+	}
+	/* PIO delay, should not be longer than 3ms as the device might
+	 * enter suspend state. */
+	udelay(10);
+	/* Enable the port. */
+	uhci_port_set_enabled(port, true);
+	return EOK;
+}
+/*----------------------------------------------------------------------------*/
+/** Initialize and report connected device.
+ *
+ * @param[in] port Port structure to use.
+ * @param[in] speed Detected speed.
+ * @return Error code.
+ *
+ * Uses libUSB function to do the actual work.
+ */
+int uhci_port_new_device(uhci_port_t *port, usb_speed_t speed)
+{
+	assert(port);
+	assert(usb_hc_connection_is_opened(&port->hc_connection));
+
+	usb_log_debug("%s: Detected new device.\n", port->id_string);
+
+	int ret, count = 0;
+	usb_address_t dev_addr;
+	do {
+		ret = usb_hc_new_device_wrapper(port->rh, &port->hc_connection,
+		    speed, uhci_port_reset_enable, port->number, port,
+		    &dev_addr, &port->attached_device, NULL, NULL, NULL);
+	} while (ret != EOK && ++count < 4);
+
+	if (ret != EOK) {
+		usb_log_error("%s: Failed(%d) to add device: %s.\n",
+		    port->id_string, ret, str_error(ret));
+		uhci_port_set_enabled(port, false);
+		return ret;
+	}
+
+	usb_log_info("New device at port %u, address %d (handle %" PRIun ").\n",
+	    port->number, dev_addr, port->attached_device);
+	return EOK;
+}
+/*----------------------------------------------------------------------------*/
+/** Remove device.
+ *
+ * @param[in] port Memory structure to use.
+ * @return Error code.
+ *
+ * Does not work, DDF does not support device removal.
+ * Does not even free used USB address (it would be dangerous if tis driver
+ * is still running).
+ */
+int uhci_port_remove_device(uhci_port_t *port)
+{
+	usb_log_error("%s: Don't know how to remove device %" PRIun ".\n",
+	    port->id_string, port->attached_device);
+	port->attached_device = 0;
+	return ENOTSUP;
+}
+/*----------------------------------------------------------------------------*/
+/** Enable or disable root hub port.
+ *
+ * @param[in] port Port structure to use.
+ * @param[in] enabled Port status to set.
+ * @return Error code. (Always EOK)
+ */
+int uhci_port_set_enabled(uhci_port_t *port, bool enabled)
+{
+	assert(port);
+
+	/* Read register value */
+	port_status_t port_status = uhci_port_read_status(port);
+
+	/* Set enabled bit */
+	if (enabled) {
+		port_status |= STATUS_ENABLED;
+	} else {
+		port_status &= ~STATUS_ENABLED;
+	}
+
+	/* Write new value. */
+	uhci_port_write_status(port, port_status);
+
+	/* Wait for port to become enabled */
+	do {
+		port_status = uhci_port_read_status(port);
+	} while ((port_status & STATUS_CONNECTED) &&
+	    !(port_status & STATUS_ENABLED));
+
+	usb_log_debug("%s: %sabled port.\n",
+		port->id_string, enabled ? "En" : "Dis");
+	return EOK;
+}
+/*----------------------------------------------------------------------------*/
+/** Print the port status value in a human friendly way
+ *
+ * @param[in] port Port structure to use.
+ * @param[in] value Port register value to print.
+ * @return Error code. (Always EOK)
+ */
+void uhci_port_print_status(uhci_port_t *port, const port_status_t value)
+{
+	assert(port);
+	usb_log_debug2("%s Port status(%#x):%s%s%s%s%s%s%s%s%s%s%s.\n",
+	    port->id_string, value,
+	    (value & STATUS_SUSPEND) ? " SUSPENDED," : "",
+	    (value & STATUS_RESUME) ? " IN RESUME," : "",
+	    (value & STATUS_IN_RESET) ? " IN RESET," : "",
+	    (value & STATUS_LINE_D_MINUS) ? " VD-," : "",
+	    (value & STATUS_LINE_D_PLUS) ? " VD+," : "",
+	    (value & STATUS_LOW_SPEED) ? " LOWSPEED," : "",
+	    (value & STATUS_ENABLED_CHANGED) ? " ENABLED-CHANGE," : "",
+	    (value & STATUS_ENABLED) ? " ENABLED," : "",
+	    (value & STATUS_CONNECTED_CHANGED) ? " CONNECTED-CHANGE," : "",
+	    (value & STATUS_CONNECTED) ? " CONNECTED," : "",
+	    (value & STATUS_ALWAYS_ONE) ? " ALWAYS ONE" : " ERR: NO ALWAYS ONE"
+	);
+}
+/**
+ * @}
+ */
