root/usr/src/uts/common/io/usb/hcd/uhci/uhcihub.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


/*
 * Universal Serial BUS  Host Controller Driver (UHCI)
 *
 * The UHCI driver is a driver which interfaces to the Universal
 * Serial Bus Architecture (USBA) and the Host Controller (HC). The interface to
 * the Host Controller is defined by the Universal Host Controller Interface.
 * This file contains the code for root hub related functions.
 */
#include <sys/usb/hcd/uhci/uhcid.h>
#include <sys/usb/hcd/uhci/uhci.h>
#include <sys/usb/hcd/uhci/uhcihub.h>

/*
 *  Function Prototypes
 */
static int      uhci_handle_set_clear_port_feature(
                        uhci_state_t            *uhcip,
                        uchar_t                 bRequest,
                        uint16_t                wValue,
                        usb_port_t              port);
static  void    uhci_handle_port_power(
                        uhci_state_t            *uhcip,
                        usb_port_t              port,
                        uint_t                  on);
static  void    uhci_handle_port_suspend(
                        uhci_state_t            *uhcip,
                        usb_port_t              port,
                        uint_t                  on);
static  void    uhci_handle_port_enable_disable(
                        uhci_state_t            *uhcip,
                        usb_port_t              port,
                        uint_t                  on);
static  void    uhci_handle_port_reset(
                        uhci_state_t            *uhcip,
                        usb_port_t              port);
static  void    uhci_handle_complete_port_reset(
                        uhci_state_t            *uhcip,
                        usb_port_t              port);
static  void    uhci_handle_clear_port_connection(
                        uhci_state_t            *uhcip,
                        usb_port_t              port);
static  void    uhci_handle_get_port_status(
                        uhci_state_t            *uhcip,
                        usb_ctrl_req_t          *req,
                        usb_port_t              port);
static  void    uhci_handle_get_hub_descriptor(
                        uhci_state_t            *uhcip,
                        usb_ctrl_req_t          *req);
static void     uhci_handle_get_hub_status(
                        uhci_state_t            *uhcip,
                        usb_ctrl_req_t          *req);
static void     uhci_handle_get_device_status(
                        uhci_state_t            *uhcip,
                        usb_ctrl_req_t          *req);
static uint_t   uhci_get_port_status(
                        uhci_state_t            *uhcip,
                        usb_port_t              port);
static  void    uhci_rh_hcdi_callback(
                        uhci_state_t            *uhcip,
                        usba_pipe_handle_data_t *ph,
                        usb_opaque_t            req,
                        usb_cr_t                cr);

/*
 * root hub device descriptor
 */
static usb_dev_descr_t uhci_rh_dev_descr = {
        0x12,   /* Length */
        1,      /* Type */
        0x110,  /* BCD - v1.1 */
        9,      /* Class */
        0,      /* Sub class */
        0,      /* Protocol */
        8,      /* Max pkt size */
        0,      /* Vendor */
        0,      /* Product id */
        0,      /* Device release */
        0,      /* Manufacturer */
        0,      /* Product */
        0,      /* Sn */
        1       /* No of configs */
};

/*
 * root hub config descriptor
 */
static uchar_t uhci_rh_config_descr[] = {
        /* config descriptor */
        0x09,           /* bLength */
        0x02,           /* bDescriptorType, Configuration */
        0x19, 0x00,     /* wTotalLength */
        0x01,           /* bNumInterfaces */
        0x01,           /* bConfigurationValue */
        0x00,           /* iConfiguration */
        0x40,           /* bmAttributes */
        0x00,           /* MaxPower */

        /* interface descriptor */
        0x09,           /* bLength */
        0x04,           /* bDescriptorType, Interface */
        0x00,           /* bInterfaceNumber */
        0x00,           /* bAlternateSetting */
        0x01,           /* bNumEndpoints */
        0x09,           /* bInterfaceClass */
        0x01,           /* bInterfaceSubClass */
        0x00,           /* bInterfaceProtocol */
        0x00,           /* iInterface */

        /* endpoint descriptor */
        0x07,           /* bLength */
        0x05,           /* bDescriptorType, Endpoint */
        0x81,           /* bEndpointAddress */
        0x03,           /* bmAttributes */
        0x01, 0x00,     /* wMaxPacketSize, 1 +  (OHCI_MAX_RH_PORTS / 8) */
        0x20            /* bInterval */
};


/*
 * uhci_init_root_hub:
 *      Initialize the root hub
 */
int
uhci_init_root_hub(uhci_state_t *uhcip)
{
        int             i, length;
        usb_hub_descr_t *root_hub_descr = &uhcip->uhci_root_hub.rh_descr;

        USB_DPRINTF_L4(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
            "uhci_init_root_hub:");

        uhcip->uhci_root_hub.rh_num_ports = MAX_RH_PORTS;

        /*
         * Build the hub descriptor
         */
        root_hub_descr->bDescriptorType = ROOT_HUB_DESCRIPTOR_TYPE;
        root_hub_descr->bNbrPorts       = MAX_RH_PORTS;

        length = root_hub_descr->bNbrPorts / 8;
        if (length) {
                root_hub_descr->bDescLength = 7 + (2 * (length + 1));
        } else {
                root_hub_descr->bDescLength = ROOT_HUB_DESCRIPTOR_LENGTH;
        }

        /* Determine the Power Switching Mode */
        root_hub_descr->bPwrOn2PwrGood = 10; /* arbitrary number */
        root_hub_descr->wHubCharacteristics =
            HUB_CHARS_NO_POWER_SWITCHING|HUB_CHARS_NO_OVER_CURRENT;

        /* Indicate if the device is removable */
        root_hub_descr->DeviceRemovable = 0x0;

        /* Fill in the port power control mask */
        root_hub_descr->PortPwrCtrlMask = 0xff;

        for (i = 0; i < uhcip->uhci_root_hub.rh_num_ports; i++) {
                uhcip->uhci_root_hub.rh_port_state[i]  = DISCONNECTED;
                uhcip->uhci_root_hub.rh_port_status[i] = 0;
                uhcip->uhci_root_hub.rh_port_changes[i] = 0;
        }

        /* Finally load the root hub driver */
        return (usba_hubdi_bind_root_hub(uhcip->uhci_dip, uhci_rh_config_descr,
            sizeof (uhci_rh_config_descr), &uhci_rh_dev_descr));
}


/*
 * uhci_handle_root_hub_request:
 *      Intercept a root hub request.
 *      Handle the  root hub request through the registers
 */
int
uhci_handle_root_hub_request(
        uhci_state_t            *uhcip,
        usba_pipe_handle_data_t  *pipe_handle,
        usb_ctrl_req_t          *req)
{
        int             error = USB_SUCCESS;
        uint16_t        port = req->ctrl_wIndex - 1;
        usb_cr_t        completion_reason;

        USB_DPRINTF_L4(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
            "uhci_handle_root_hub_request: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%p",
            req->ctrl_bmRequestType, req->ctrl_bRequest, req->ctrl_wValue,
            req->ctrl_wIndex, req->ctrl_wLength, (void *)req->ctrl_data);

        ASSERT(mutex_owned(&uhcip->uhci_int_mutex));

        switch (req->ctrl_bmRequestType) {
        case HUB_GET_DEVICE_STATUS_TYPE:
                uhci_handle_get_device_status(uhcip, req);

                break;
        case HUB_HANDLE_PORT_FEATURE_TYPE:
                error = uhci_handle_set_clear_port_feature(uhcip,
                    req->ctrl_bRequest, req->ctrl_wValue, port);

                break;
        case HUB_GET_PORT_STATUS_TYPE:
                uhci_handle_get_port_status(uhcip, req, port);

                break;
        case HUB_CLASS_REQ_TYPE:
                switch (req->ctrl_bRequest) {
                case USB_REQ_GET_DESCR:
                        uhci_handle_get_hub_descriptor(uhcip, req);

                        break;
                case USB_REQ_GET_STATUS:
                        uhci_handle_get_hub_status(uhcip, req);

                        break;
                default:
                        USB_DPRINTF_L2(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
                            "uhci_handle_root_hub_request: Unsupported "
                            "request 0x%x", req->ctrl_bmRequestType);

                        break;
                }

                break;
        default:
                USB_DPRINTF_L2(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
                    "uhci_handle_root_hub_request: Unsupported request 0x%x",
                    req->ctrl_bmRequestType);

                break;
        }

        completion_reason = (error != USB_SUCCESS) ?
            USB_CR_NOT_SUPPORTED : USB_CR_OK;

        USB_DPRINTF_L4(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
            "uhci_handle_root_hub_request: error = %d", error);

        uhci_rh_hcdi_callback(uhcip, pipe_handle, (usb_opaque_t)req,
            completion_reason);

        return (USB_SUCCESS);
}


/*
 * uhci_handle_set_clear_port_feature:
 */
static int
uhci_handle_set_clear_port_feature(
        uhci_state_t            *uhcip,
        uchar_t                 bRequest,
        uint16_t                wValue,
        usb_port_t              port)
{
        int    error = USB_SUCCESS;

        USB_DPRINTF_L4(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
            "uhci_handle_set_clear_port_feature: 0x%x 0x%x 0x%x",
            bRequest, wValue, port);

        switch (bRequest) {
        case USB_REQ_SET_FEATURE:
                switch (wValue) {
                case CFS_PORT_ENABLE:
                        uhci_handle_port_enable_disable(uhcip,
                            port, UHCI_ENABLE_PORT);
                        break;
                case CFS_PORT_SUSPEND:
                        uhci_handle_port_suspend(uhcip, port, 1);

                        break;
                case CFS_PORT_RESET:
                        uhci_handle_port_reset(uhcip, port);

                        break;
                case CFS_PORT_POWER:
                        uhci_handle_port_power(uhcip, port,
                            UHCI_ENABLE_PORT_PWR);
                        break;

                default:
                        USB_DPRINTF_L2(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
                            "uhci_handle_set_clear_port_feature: "
                            "Unsupported request 0x%x 0x%x", bRequest, wValue);
                        error = USB_FAILURE;

                        break;
                }

                break;
        case USB_REQ_CLEAR_FEATURE:
                switch (wValue) {
                case CFS_PORT_ENABLE:
                        uhci_handle_port_enable_disable(uhcip,
                            port, UHCI_DISABLE_PORT);

                        break;
                case CFS_C_PORT_ENABLE:
                        uhci_handle_port_enable_disable(uhcip,
                            port, UHCI_CLEAR_ENDIS_BIT);

                        break;
                case CFS_PORT_SUSPEND:
                        uhci_handle_port_suspend(uhcip, port, 0);

                        break;
                case CFS_C_PORT_RESET:
                        uhci_handle_complete_port_reset(uhcip, port);

                        break;
                case CFS_PORT_POWER:
                        uhci_handle_port_power(uhcip, port,
                            UHCI_DISABLE_PORT_PWR);

                        break;
                case CFS_C_PORT_CONNECTION:
                        uhci_handle_clear_port_connection(uhcip, port);

                        break;
                default:
                        USB_DPRINTF_L2(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
                            "uhci_handle_set_clear_port_feature: "
                            "Unsupported request 0x%x 0x%x", bRequest, wValue);
                        error = USB_FAILURE;

                        break;
                }

                break;
        default:
                USB_DPRINTF_L2(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
                    "uhci_handle_set_clear_port_feature: "
                    "Unsupported request 0x%x 0x%x", bRequest, wValue);
                error = USB_FAILURE;
        }


        return (error);
}


/*
 * uhci_handle_port_suspend:
 */
static void
uhci_handle_port_suspend(
        uhci_state_t            *uhcip,
        usb_port_t              port,
        uint_t                  on)
{
        uint_t  port_status = Get_OpReg16(PORTSC[port]);

        USB_DPRINTF_L4(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
            "uhci_handle_port_suspend: port=%d on=%d",
            port, on);

        if (on) {
                /* See if the port suspend is already on */
                if (!(port_status & HCR_PORT_SUSPEND)) {
                        /* suspend the port */
                        Set_OpReg16(PORTSC[port],
                            (port_status | HCR_PORT_SUSPEND));
                }
        } else {
                /* See if the port suspend is already off */
                if ((port_status & HCR_PORT_SUSPEND)) {
                        /* resume the port */
                        Set_OpReg16(PORTSC[port],
                            (port_status & ~HCR_PORT_SUSPEND));
                }
        }
}


/*
 * uhci_handle_port_power:
 *      Turn on a root hub port.  NOTE: Driver does not have any control
 *      over the power status.
 */
/* ARGSUSED */
static void
uhci_handle_port_power(
        uhci_state_t            *uhcip,
        usb_port_t              port,
        uint_t                  on)
{
        USB_DPRINTF_L4(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
            "uhci_handle_port_power: nothing to do");
}


/*
 * uhci_handle_port_enable_disable:
 *      Handle port enable request.
 */
static void
uhci_handle_port_enable_disable(
        uhci_state_t            *uhcip,
        usb_port_t              port,
        uint_t                  action)
{
        uint_t  port_status = Get_OpReg16(PORTSC[port]);

        USB_DPRINTF_L4(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
            "uhci_handle_port_enable: port = 0x%x, status = 0x%x",
            port, port_status);

        if (action == UHCI_ENABLE_PORT) {
                /* See if the port enable is already on */
                if (!(port_status & HCR_PORT_ENABLE)) {
                        /* Enable the port */
                        Set_OpReg16(PORTSC[port],
                            (port_status | HCR_PORT_ENABLE));
                }
        } else if (action == UHCI_DISABLE_PORT) {
                /* See if the port enable is already off */
                if ((port_status & HCR_PORT_ENABLE)) {
                        /* Disable the port */
                        Set_OpReg16(PORTSC[port],
                            (port_status & ~HCR_PORT_ENABLE));
                }
        } else {
                /* Clear the Enable/Disable change bit */
                Set_OpReg16(PORTSC[port], (port_status | HCR_PORT_ENDIS_CHG));

                /* Update software port_changes register */
                uhcip->uhci_root_hub.rh_port_changes[port] &= ~PORT_CHANGE_PESC;
        }
}


/*
 * uhci_root_hub_reset_occurred:
 *      Inform the upper layer that reset has occured on the port.
 *      This is required because the upper layer is expecting an
 *      event immediately after doing a reset. In case of OHCI
 *      the HC gets an interrupt for the change in the root hub
 *      status, but in case of UHCI we don't. So, we send an
 *      event to the upper layer as soon as we complete the reset
 *      as long as the root hub pipe is polling.
 */
void
uhci_root_hub_reset_occurred(
        uhci_state_t    *uhcip,
        uint16_t        port)
{
        usb_intr_req_t  *intr_reqp = uhcip->uhci_root_hub.rh_curr_intr_reqp;

        USB_DPRINTF_L4(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
            "uhci_root_hub_reset_occurred: intr_reqp = 0x%p data = 0x%p",
            (void *)intr_reqp, (void *)intr_reqp->intr_data);

        *intr_reqp->intr_data->b_wptr++ = (1 << (port+1));

        uhci_rh_hcdi_callback(uhcip, uhcip->uhci_root_hub.rh_intr_pipe_handle,
            (usb_opaque_t)intr_reqp, USB_CR_OK);
}


/*
 * uhci_handle_port_reset:
 *      Perform a port reset.
 */
static void
uhci_handle_port_reset(
        uhci_state_t            *uhcip,
        usb_port_t              port)
{
        uint_t  port_status = Get_OpReg16(PORTSC[port]);

        USB_DPRINTF_L4(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
            "uhci_handle_port_reset: port = 0x%x, status = 0x%x",
            port, port_status);

        if (!(port_status & HCR_PORT_CCS)) {
                USB_DPRINTF_L3(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
                    "port_status & HCR_PORT_CCS == 0: "
                    "port = 0x%x, status = 0x%x", port, port_status);
        }

        Set_OpReg16(PORTSC[port], (port_status| HCR_PORT_RESET));

        drv_usecwait(UHCI_RESET_DELAY);

        Set_OpReg16(PORTSC[port], (port_status & ~HCR_PORT_RESET));

        drv_usecwait(UHCI_RESET_DELAY/100);

        Set_OpReg16(PORTSC[port], (port_status| HCR_PORT_ENABLE));

        /*
         * The next function is only called if the interrupt pipe
         * is polling and the USBA is ready to receive the
         * data. If not, we could panic.
         */
        if (uhcip->uhci_root_hub.rh_pipe_state != UHCI_PIPE_STATE_ACTIVE) {
                /* make a note that we need to send status back */
                uhcip->uhci_root_hub.rh_status = port + 1;
        } else {
                uhci_root_hub_reset_occurred(uhcip, port);
        }
}


/*
 * uhci_handle_complete_port_reset:
 *      Perform a port reset change.
 */
static void
uhci_handle_complete_port_reset(
        uhci_state_t            *uhcip,
        usb_port_t              port)
{
        uint_t port_status = Get_OpReg16(PORTSC[port]);

        USB_DPRINTF_L4(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
            "uhci_handle_complete_port_reset: port = 0x%x status = 0x%x",
            port, port_status);

        if (!(port_status & HCR_PORT_CCS)) {
                USB_DPRINTF_L3(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
                    "port_status & HCR_PORT_CCS == 0: "
                    "port = 0x%x, status = 0x%x", port, port_status);
        }

        Set_OpReg16(PORTSC[port], (port_status & (~ HCR_PORT_RESET)));

        /* Update software port_changes register */
        uhcip->uhci_root_hub.rh_port_changes[port] &= ~PORT_CHANGE_PRSC;
}


/*
 * uhci_handle_clear_port_connection:
 *      Perform a clear port connection.
 */
static void
uhci_handle_clear_port_connection(
        uhci_state_t            *uhcip,
        usb_port_t              port)
{
        uint_t port_status = Get_OpReg16(PORTSC[port]);

        USB_DPRINTF_L4(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
            "uhci_handle_clear_port_connection: port = 0x%x status = 0x%x",
            port, port_status);

        /* Clear CSC bit */
        Set_OpReg16(PORTSC[port], port_status | HCR_PORT_CSC);

        /* Update software port_changes register */
        uhcip->uhci_root_hub.rh_port_changes[port] &= ~PORT_CHANGE_CSC;
}


/*
 * uhci_handle_get_port_status:
 *      Handle a get port status request.
 */
static void
uhci_handle_get_port_status(
        uhci_state_t            *uhcip,
        usb_ctrl_req_t          *req,
        usb_port_t              port)
{
        uint_t          new_port_status;
        uint_t          old_port_status =
            uhcip->uhci_root_hub.rh_port_status[port];
        uint_t          old_port_changes =
            uhcip->uhci_root_hub.rh_port_changes[port];
        uint_t          change_status;
        usb_ctrl_req_t  *ctrl_reqp = (usb_ctrl_req_t *)req;
        uint16_t        wLength = req->ctrl_wLength;

        ASSERT(wLength == 4);
        ASSERT(ctrl_reqp->ctrl_data != NULL);

        /* Read the current port status and return it */
        new_port_status = uhci_get_port_status(uhcip, port);
        change_status   = (old_port_status ^ new_port_status) & 0xff;
        change_status   |= old_port_changes;

        USB_DPRINTF_L4(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
            "uhci_handle_get_port_status:\n\t"
            "port%d: old status = 0x%x  new status = 0x%x change = 0x%x",
            port, old_port_status, new_port_status, change_status);

        *ctrl_reqp->ctrl_data->b_wptr++ = (uchar_t)new_port_status;
        *ctrl_reqp->ctrl_data->b_wptr++ = (uchar_t)(new_port_status >> 8);
        *ctrl_reqp->ctrl_data->b_wptr++ = (uchar_t)change_status;
        *ctrl_reqp->ctrl_data->b_wptr++ = (uchar_t)(change_status >> 8);

        /* Update the status */
        uhcip->uhci_root_hub.rh_port_status[port] = new_port_status;
        uhcip->uhci_root_hub.rh_port_changes[port] = change_status;
}


/*
 * uhci_handle_get_hub_descriptor:
 */
static void
uhci_handle_get_hub_descriptor(
        uhci_state_t            *uhcip,
        usb_ctrl_req_t          *req)
{
        uchar_t         raw_descr[ROOT_HUB_DESCRIPTOR_LENGTH];
        usb_hub_descr_t *root_hub_descr = &uhcip->uhci_root_hub.rh_descr;

        USB_DPRINTF_L4(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
            "uhci_handle_get_hub_descriptor: wLength = 0x%x",
            req->ctrl_wLength);

        ASSERT(req->ctrl_wLength != 0);
        ASSERT(req->ctrl_data != NULL);

        bzero(&raw_descr, ROOT_HUB_DESCRIPTOR_LENGTH);

        raw_descr[0] = root_hub_descr->bDescLength;
        raw_descr[1] = root_hub_descr->bDescriptorType;
        raw_descr[2] = root_hub_descr->bNbrPorts;
        raw_descr[3] = root_hub_descr->wHubCharacteristics & 0x00ff;
        raw_descr[4] = (root_hub_descr->wHubCharacteristics & 0xff00) >> 8;
        raw_descr[5] = root_hub_descr->bPwrOn2PwrGood;
        raw_descr[6] = root_hub_descr->bHubContrCurrent;
        raw_descr[7] = root_hub_descr->DeviceRemovable;
        raw_descr[8] = root_hub_descr->PortPwrCtrlMask;

        bcopy(raw_descr, req->ctrl_data->b_wptr, req->ctrl_wLength);
        req->ctrl_data->b_wptr += req->ctrl_wLength;
}


/*
 * uhci_handle_get_hub_status:
 */
static void
uhci_handle_get_hub_status(
        uhci_state_t            *uhcip,
        usb_ctrl_req_t          *req)
{

        USB_DPRINTF_L4(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
            "uhci_handle_get_hub_status: wLength = 0x%x",
            req->ctrl_wLength);
        ASSERT(req->ctrl_wLength != 0);
        ASSERT(req->ctrl_data != NULL);

        /*
         * A good status is always sent because there is no way that
         * the driver can get to know about the status change of the
         * over current or power failure of the root hub from the HC.
         */
        bzero(req->ctrl_data->b_wptr, req->ctrl_wLength);
        req->ctrl_data->b_wptr += req->ctrl_wLength;
}


/*
 * uhci_handle_get_device_status:
 */
static void
uhci_handle_get_device_status(
        uhci_state_t            *uhcip,
        usb_ctrl_req_t          *req)
{
        uint16_t        dev_status;

        USB_DPRINTF_L4(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
            "uhci_handle_get_device_status: wLength = 0x%x",
            req->ctrl_wLength);

        ASSERT(req->ctrl_wLength != 0);
        ASSERT(req->ctrl_data != NULL);

        /*
         * UHCI doesn't have device status information.
         * Simply return what is desired for the request.
         */
        dev_status = USB_DEV_SLF_PWRD_STATUS;

        *req->ctrl_data->b_wptr++ = (uchar_t)dev_status;
        *req->ctrl_data->b_wptr++ = (uchar_t)(dev_status >> 8);
}


/*
 * uhci_handle_root_hub_status_change:
 *      This function is called every 256 ms from the time out handler.
 *      It checks for the status change of the root hub and its ports.
 */
void
uhci_handle_root_hub_status_change(void *arg)
{
        usb_port_t      port;
        uint_t          old_port_status;
        uint_t          new_port_status;
        ushort_t        port_status;
        uint_t          change_status;
        uchar_t         all_ports_status = 0;
        uhci_state_t    *uhcip = (uhci_state_t *)arg;
        usb_intr_req_t  *curr_intr_reqp;

        mutex_enter(&uhcip->uhci_int_mutex);

        /* reset the timeout id */
        uhcip->uhci_timeout_id = 0;

        /* Get the current interrupt request pointer */
        curr_intr_reqp = uhcip->uhci_root_hub.rh_curr_intr_reqp;

        /* Check each port */
        for (port = 0; port < uhcip->uhci_root_hub.rh_num_ports; port++) {
                new_port_status = uhci_get_port_status(uhcip, port);
                old_port_status = uhcip->uhci_root_hub.rh_port_status[port];

                change_status = (old_port_status ^ new_port_status) & 0xff;
                change_status |= uhcip->uhci_root_hub.rh_port_changes[port];

                /* See if a device was attached/detached */
                if (change_status & PORT_STATUS_CCS) {
                        all_ports_status |= 1 << (port + 1);
                }

                port_status = Get_OpReg16(PORTSC[port]);
                Set_OpReg16(PORTSC[port], port_status | HCR_PORT_ENDIS_CHG);

                uhcip->uhci_root_hub.rh_port_status[port] = new_port_status;
                uhcip->uhci_root_hub.rh_port_changes[port] = change_status;

                USB_DPRINTF_L4(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
                    "port %d old status 0x%x new status 0x%x change 0x%x\n\t"
                    "all_ports_status = 0x%x", port, old_port_status,
                    new_port_status, change_status, all_ports_status);
        }

        if (uhcip->uhci_root_hub.rh_intr_pipe_handle &&
            all_ports_status && curr_intr_reqp &&
            (uhcip->uhci_root_hub.rh_pipe_state == UHCI_PIPE_STATE_ACTIVE)) {

                ASSERT(curr_intr_reqp->intr_data != NULL);

                *curr_intr_reqp->intr_data->b_wptr++ = all_ports_status;

                uhci_rh_hcdi_callback(uhcip,
                    uhcip->uhci_root_hub.rh_intr_pipe_handle,
                    (usb_opaque_t)curr_intr_reqp, USB_CR_OK);
        }

        if (uhcip->uhci_root_hub.rh_pipe_state == UHCI_PIPE_STATE_ACTIVE) {
                /*
                 * If needed, allocate new interrupt request. Also
                 * start the timer for the root hub interrupt polling.
                 */
                if (uhci_root_hub_allocate_intr_pipe_resource(uhcip, 0) !=
                    USB_SUCCESS) {

                        /* Do interrupt pipe cleanup */
                        uhci_root_hub_intr_pipe_cleanup(uhcip,
                            USB_CR_NO_RESOURCES);
                }
        }

        mutex_exit(&uhcip->uhci_int_mutex);
}


static uint_t
uhci_get_port_status(
        uhci_state_t    *uhcip,
        usb_port_t      port)
{
        uint_t          new_port_status = PORT_STATUS_PPS;
        ushort_t        port_status = Get_OpReg16(PORTSC[port]);

        if (port_status & HCR_PORT_CCS) {
                new_port_status |= PORT_STATUS_CCS;
        }

        if (port_status & HCR_PORT_LSDA) {
                new_port_status |= PORT_STATUS_LSDA;
        }

        if (port_status & HCR_PORT_ENABLE) {
                new_port_status |= PORT_STATUS_PES;
        }

        if (port_status & HCR_PORT_SUSPEND) {
                new_port_status |= PORT_STATUS_PSS;
        }

        if (port_status & HCR_PORT_RESET) {
                new_port_status |= PORT_STATUS_PRS;
        }

        return (new_port_status);
}


/*
 * uhci_root_hub_allocate_intr_pipe_resource:
 *      Allocate interrupt requests and initialize them.
 */
int
uhci_root_hub_allocate_intr_pipe_resource(
        uhci_state_t    *uhcip,
        usb_flags_t     flags)
{
        usb_intr_req_t          *curr_intr_reqp;
        usba_pipe_handle_data_t *ph;

        USB_DPRINTF_L4(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
            "uhci_root_hub_allocate_intr_pipe_resource:");

        ASSERT(mutex_owned(&uhcip->uhci_int_mutex));

        /* Get the interrupt pipe handle */
        ph = uhcip->uhci_root_hub.rh_intr_pipe_handle;

        /* Get the current interrupt request pointer */
        curr_intr_reqp = uhcip->uhci_root_hub.rh_curr_intr_reqp;

        /*
         * If current interrupt request pointer is null,
         * allocate new interrupt request.
         */
        if (curr_intr_reqp == NULL) {
                ASSERT(uhcip->uhci_root_hub.rh_client_intr_req);

                if ((curr_intr_reqp = usba_hcdi_dup_intr_req(ph->p_dip,
                    uhcip->uhci_root_hub.rh_client_intr_req,
                    uhcip->uhci_root_hub.rh_client_intr_req->intr_len,
                    flags)) == NULL) {
                        USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
                            "uhci_root_hub_allocate_intr_pipe_resource:"
                            "Interrupt request structure allocation failed");

                        return (USB_NO_RESOURCES);
                }

                uhcip->uhci_root_hub.rh_curr_intr_reqp = curr_intr_reqp;

                mutex_enter(&ph->p_mutex);
                ph->p_req_count++;
                mutex_exit(&ph->p_mutex);
        }

        if (uhcip->uhci_timeout_id == 0) {
                uhcip->uhci_timeout_id = timeout(
                    uhci_handle_root_hub_status_change,
                    (void *)uhcip, UHCI_256_MS);
                uhcip->uhci_root_hub.rh_pipe_state =
                    UHCI_PIPE_STATE_ACTIVE;
        }

        return (USB_SUCCESS);
}


/*
 * uhci_root_hub_intr_pipe_cleanup:
 *      Deallocate all interrupt requests and do callback
 *      the original client interrupt request.
 */
void
uhci_root_hub_intr_pipe_cleanup(uhci_state_t *uhcip, usb_cr_t cr)
{
        usb_intr_req_t          *curr_intr_reqp;
        usb_opaque_t            client_intr_reqp;
        usba_pipe_handle_data_t *ph;
        timeout_id_t            timer_id;

        USB_DPRINTF_L4(PRINT_MASK_ROOT_HUB, uhcip->uhci_log_hdl,
            "uhci_root_hub_intr_pipe_cleanup:");

        ASSERT(mutex_owned(&uhcip->uhci_int_mutex));

        /* Get the interrupt pipe handle */
        ph = uhcip->uhci_root_hub.rh_intr_pipe_handle;

        /* Get the interrupt timerid */
        timer_id = uhcip->uhci_timeout_id;

        /* Stop the root hub interrupt timer */
        if (timer_id) {

                /* Reset the timer id to zero */
                uhcip->uhci_timeout_id = 0;
                uhcip->uhci_root_hub.rh_pipe_state =
                    UHCI_PIPE_STATE_IDLE;

                mutex_exit(&uhcip->uhci_int_mutex);
                (void) untimeout(timer_id);
                mutex_enter(&uhcip->uhci_int_mutex);
        }

        /* Reset the current interrupt request pointer */
        curr_intr_reqp = uhcip->uhci_root_hub.rh_curr_intr_reqp;

        /* Deallocate uncompleted interrupt request */
        if (curr_intr_reqp) {
                uhcip->uhci_root_hub.rh_curr_intr_reqp = NULL;
                usb_free_intr_req(curr_intr_reqp);

                mutex_enter(&ph->p_mutex);
                ph->p_req_count--;
                mutex_exit(&ph->p_mutex);
        }

        client_intr_reqp = (usb_opaque_t)
            uhcip->uhci_root_hub.rh_client_intr_req;

        /* Callback for original client interrupt request */
        if (client_intr_reqp) {
                uhcip->uhci_root_hub.rh_client_intr_req = NULL;
                uhci_rh_hcdi_callback(uhcip, ph,
                    (usb_opaque_t)client_intr_reqp, cr);
        }
}


/*
 * uhci_rh_hcdi_callback:
 *      Convenience wrapper around usba_hcdi_cb() for the root hub.
 */
static void
uhci_rh_hcdi_callback(
        uhci_state_t            *uhcip,
        usba_pipe_handle_data_t *ph,
        usb_opaque_t            req,
        usb_cr_t                cr)
{
        USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
            "uhci_rh_hcdi_callback: ph=0x%p cr=0x%x req=0x%p",
            (void *)ph, cr, (void *)req);

        ASSERT(mutex_owned(&uhcip->uhci_int_mutex));

        switch (UHCI_XFER_TYPE(&ph->p_ep)) {
        case USB_EP_ATTR_CONTROL:

                break;
        case USB_EP_ATTR_INTR:
                if ((usb_intr_req_t *)req ==
                    uhcip->uhci_root_hub.rh_curr_intr_reqp) {
                        uhcip->uhci_root_hub.rh_curr_intr_reqp = NULL;

                        break;
                } else if ((usb_intr_req_t *)req ==
                    uhcip->uhci_root_hub.rh_client_intr_req) {
                        uhcip->uhci_root_hub.rh_client_intr_req = NULL;

                        break;
                }
                /*FALLTHRU*/
        default:
                ASSERT(req);
                break;
        }

        mutex_exit(&uhcip->uhci_int_mutex);
        usba_hcdi_cb(ph, req, cr);
        mutex_enter(&uhcip->uhci_int_mutex);
}