/**
 * usb.c - USB driver stack project for Windows NT 4.0
 *
 * Copyright (c) 2002-2004 Zhiming  mypublic99@yahoo.com
 *
 * This program/include file is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published
 * by the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program/include file is distributed in the hope that it will be
 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program (in the main directory of the distribution, the file
 * COPYING); if not, write to the Free Software Foundation,Inc., 59 Temple
 * Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "usbdriver.h"

LONG g_alloc_cnt = 0;
ULONG cpu_clock_freq = 0;

NTSTATUS usb_get_descriptor(PUSB_DEV pdev, PURB purb);
VOID usb_set_interface_completion(PURB purb, PVOID context);
NTSTATUS usb_set_interface(PURB purb);

PVOID
usb_alloc_mem(POOL_TYPE pool_type, LONG size)
{
    PVOID ret;
    g_alloc_cnt++;
    ret = ExAllocatePool(pool_type, size);
    usb_dbg_print(DBGLVL_ULTRA, ("usb_alloc_mem(): alloced=0x%x\n", g_alloc_cnt));
    return ret;
}

VOID
usb_free_mem(PVOID pbuf)
{
    g_alloc_cnt--;
    usb_dbg_print(DBGLVL_ULTRA, ("usb_free_mem(): alloced=0x%x\n", g_alloc_cnt));
    ExFreePool(pbuf);
}

VOID usb_config_dev_completion(PURB purb, PVOID context);

//shamelessly pasted from linux's usb.c
LONG
usb_calc_bus_time(LONG speed, LONG input_dir, LONG is_iso, LONG byte_count)
{
    LONG tmp;

    switch (speed & 0x3)        /* no isoc. here */
    {
        case USB_SPEED_LOW:
        {
            if (input_dir)
            {
                tmp = (67667L * (31L + 10L * bit_time(byte_count))) / 1000L;
                return (64060L + (2 * BW_HUB_LS_SETUP) + BW_HOST_DELAY + tmp);
            }
            else
            {
                tmp = (66700L * (31L + 10L * bit_time(byte_count))) / 1000L;
                return (64107L + (2 * BW_HUB_LS_SETUP) + BW_HOST_DELAY + tmp);
            }
            break;
        }
        /* for full-speed: */
        case USB_SPEED_FULL:
        {
            if (!is_iso)        /* Input or Output */
            {
                tmp = (8354L * (31L + 10L * bit_time(byte_count))) / 1000L;
                return (9107L + BW_HOST_DELAY + tmp);
            }                   /* end not Isoc */

            /* for isoc: */

            tmp = (8354L * (31L + 10L * bit_time(byte_count))) / 1000L;
            return (((input_dir) ? 7268L : 6265L) + BW_HOST_DELAY + tmp);
        }
        case USB_SPEED_HIGH:
        {
            if (!is_iso)
            {
                tmp = (999 + 926520 + 2083 * ((LONG) ((19 + 7 * 8 * byte_count) / 6))) / 1000;
            }
            else
            {
                tmp = (999 + 633232 + 2083 * ((LONG) ((19 + 7 * 8 * byte_count) / 6))) / 1000;
            }
            return tmp + USB2_HOST_DELAY;
        }
        default:
        {
            break;
        }
    }
    return 125001;
}

//
// if the dev is not in the list, return value is not success and the pointer is nulled
// if the dev is in the list but zomb, return value is error code and the pointer is the dev( no ref_count guarded )
// if the dev is alive and in the list, return is success and the pointer is the dev.
// one must be aware of what his doing before he uses the ppdev
//
NTSTATUS
usb_query_and_lock_dev(PUSB_DEV_MANAGER dev_mgr, DEV_HANDLE dev_handle, PUSB_DEV * ppdev)
{
    int i;
    PLIST_ENTRY pthis, pnext;
    PUSB_DEV pdev = NULL;
    BOOLEAN valid_dev;

    USE_NON_PENDING_IRQL;

    *ppdev = NULL;

    if (dev_mgr == NULL || dev_handle == 0)
        return STATUS_INVALID_PARAMETER;

    i = dev_id_from_handle(dev_handle);

    KeAcquireSpinLock(&dev_mgr->dev_list_lock, &old_irql);
    ListFirst(&dev_mgr->dev_list, pthis);

    while (pthis)
    {
        pdev = (PUSB_DEV) pthis;
        if (pdev->dev_id != (ULONG) i)
        {
            ListNext(&dev_mgr->dev_list, pthis, pnext);
            pthis = pnext;
            continue;
        }
        else
            break;
    }
    if (pthis == NULL)
    {
        //no such device
        KeReleaseSpinLock(&dev_mgr->dev_list_lock, old_irql);
        return STATUS_INVALID_PARAMETER;
    }

    valid_dev = TRUE;

    lock_dev(pdev, TRUE);

    if (dev_state(pdev) == USB_DEV_STATE_ZOMB)
    {
        valid_dev = FALSE;
    }
    else
        pdev->ref_count++;      //guard the dev by increasing the ref count

    unlock_dev(pdev, TRUE);

    KeReleaseSpinLock(&dev_mgr->dev_list_lock, old_irql);

    *ppdev = pdev;

    if (!valid_dev)
        return STATUS_DEVICE_DOES_NOT_EXIST;

    return STATUS_SUCCESS;

}

NTSTATUS
usb_unlock_dev(PUSB_DEV dev)
{
    USE_BASIC_NON_PENDING_IRQL;

    if (dev == NULL)
        return STATUS_INVALID_PARAMETER;

    lock_dev(dev, FALSE);
    dev->ref_count--;
    if (dev->ref_count < 0)
        dev->ref_count = 0;
    unlock_dev(dev, FALSE);
    return STATUS_SUCCESS;
}

NTSTATUS
usb_reset_pipe_ex(PUSB_DEV_MANAGER dev_mgr,
                  DEV_HANDLE endp_handle,          //endp handle to reset
                  PURBCOMPLETION reset_completion, //note: this reset completion has no right to delete the urb, that is only for reference
                  PVOID param)
{
    NTSTATUS status;
    PUSB_DEV pdev;
    LONG if_idx, endp_idx;
    PUSB_ENDPOINT pendp;
    USE_BASIC_NON_PENDING_IRQL;

    if (dev_mgr == NULL)
        return STATUS_INVALID_PARAMETER;

    status = usb_query_and_lock_dev(dev_mgr, (endp_handle & 0xffff0000), &pdev);
    if (status != STATUS_SUCCESS)
        return STATUS_UNSUCCESSFUL;

    lock_dev(pdev, FALSE);
    if (dev_state(pdev) == USB_DEV_STATE_ZOMB)
    {
        status = STATUS_UNSUCCESSFUL;
        goto LBL_OUT;
    }

    if_idx = if_idx_from_handle(endp_handle);
    endp_idx = endp_idx_from_handle(endp_handle);

    if (default_endp_handle(endp_handle))
    {
        status = STATUS_UNSUCCESSFUL;
        goto LBL_OUT;
    }

    if (dev_state(pdev) < USB_DEV_STATE_CONFIGURED)
    {
        status = STATUS_DEVICE_NOT_READY;
        goto LBL_OUT;
    }

    pendp = &pdev->usb_config->interf[if_idx].endp[endp_idx];
    unlock_dev(pdev, FALSE) status = usb_reset_pipe(pdev, pendp, reset_completion, param);
    usb_unlock_dev(pdev);
    return status;

LBL_OUT:
    unlock_dev(pdev, FALSE);
    usb_unlock_dev(pdev);

    return status;
}

// caller must guarantee the pdev exist before the routine exit
NTSTATUS
usb_reset_pipe(PUSB_DEV pdev, PUSB_ENDPOINT pendp, PURBCOMPLETION client_reset_pipe_completion, PVOID param)
{

    PHCD hcd;
    PURB purb;
    BYTE endp_addr;
    NTSTATUS status;
    DEV_HANDLE dev_handle;

    USE_BASIC_NON_PENDING_IRQL;

    if (pdev == NULL || pendp == NULL)
        return STATUS_INVALID_PARAMETER;

    lock_dev(pdev, FALSE);
    if (dev_state(pdev) == USB_DEV_STATE_ZOMB)
    {
        unlock_dev(pdev, FALSE);
        return STATUS_DEVICE_DOES_NOT_EXIST;
    }

    hcd = pdev->hcd;
    endp_addr = pendp->pusb_endp_desc->bEndpointAddress;
    dev_handle = usb_make_handle(pdev->dev_id, 0, 0);
    unlock_dev(pdev, FALSE);

    purb = (PURB) usb_alloc_mem(NonPagedPool, sizeof(URB) + sizeof(PIRP));

    if (purb == NULL)
        return STATUS_NO_MEMORY;

    UsbBuildResetPipeRequest(purb,
                             dev_handle,
                             endp_addr,
                             usb_reset_pipe_completion,
                             pendp,
                             (LONG)client_reset_pipe_completion);

    *((PULONG)&purb[1]) = (ULONG)param;

    if ((status = hcd->hcd_submit_urb(hcd, pdev, &pdev->default_endp, purb)) != STATUS_PENDING)
    {
        usb_free_mem(purb);
        purb = NULL;
    }
    return status;
}

VOID
usb_reset_pipe_completion(PURB purb, PVOID context)
{
    PUSB_DEV pdev;
    PUSB_ENDPOINT pendp;

    USE_BASIC_NON_PENDING_IRQL;

    if (purb == NULL || context == NULL)
        return;

    pdev = purb->pdev;
    pendp = (PUSB_ENDPOINT) context;

    lock_dev(pdev, TRUE);
    if (dev_state(pdev) == USB_DEV_STATE_ZOMB)
    {
        goto LBL_OUT;
    }

    if (usb_error(purb->status))
    {
        goto LBL_OUT;
    }
    //clear stall
    pendp->flags &= ~USB_ENDP_FLAG_STAT_MASK;

    //reset toggle endp_type
    if ((pendp->pusb_endp_desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_BULK ||
        (pendp->pusb_endp_desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT)
    {
        pendp->flags &= ~USB_ENDP_FLAG_DATATOGGLE;
    }

LBL_OUT:
    unlock_dev(pdev, TRUE);

    if (purb->reference)
        ((PURBCOMPLETION) purb->reference) (purb, (PVOID) (*((PULONG) & purb[1])));

    usb_free_mem(purb);
    purb = NULL;
    return;

}

void
usb_reset_pipe_from_dispatch_completion(PURB purb, PVOID param)
{
    PURB pclient_urb;
    if (purb == NULL || param == NULL)
        TRAP();
    pclient_urb = (PURB) param;
    pclient_urb->status = purb->status;

    if (pclient_urb->completion)
    {
        pclient_urb->completion(pclient_urb, pclient_urb->context);
    }
    // the urb can not be freed here because it is owned by the reset
    // pipe completion
    return;
}

//used to check descriptor validity
BOOLEAN
is_header_match(PUCHAR pbuf, ULONG type)
{
    BOOLEAN ret;
    PUSB_DESC_HEADER phdr;
    phdr = (PUSB_DESC_HEADER) pbuf;

    switch (type)
    {
        case USB_DT_DEVICE:
        {
            ret = (phdr->bLength == sizeof(USB_DEVICE_DESC) && phdr->bDescriptorType == USB_DT_DEVICE);
            break;
        }
        case USB_DT_CONFIG:
        {
            ret = (phdr->bLength == sizeof(USB_CONFIGURATION_DESC) && phdr->bDescriptorType == USB_DT_CONFIG);
            break;
        }
        case USB_DT_INTERFACE:
        {
            ret = (phdr->bLength == sizeof(USB_INTERFACE_DESC) && phdr->bDescriptorType == USB_DT_INTERFACE);
            break;
        }
        case USB_DT_ENDPOINT:
        {
            ret = (phdr->bLength == sizeof(USB_ENDPOINT_DESC) && phdr->bDescriptorType == USB_DT_ENDPOINT);
            break;
        }
        default:
            ret = FALSE;
    }
    return ret;
}

BOOLEAN
usb_skip_endp_desc(PBYTE * pbUF, LONG n)
{
    if (is_header_match(*pbUF, USB_DT_ENDPOINT))
    {
        (*pbUF) += sizeof(USB_ENDPOINT_DESC) * n;
        return TRUE;
    }
    return FALSE;
}

BOOLEAN
usb_skip_if_desc(PBYTE * pBUF)
{
    BOOLEAN ret;
    PUSB_INTERFACE_DESC pif_desc = (PUSB_INTERFACE_DESC) * pBUF;
    LONG endp_count;
    ret = is_header_match((PBYTE) * pBUF, USB_DT_INTERFACE);
    if (ret == TRUE)
    {
        endp_count = pif_desc->bNumEndpoints;
        if (endp_count < MAX_ENDPS_PER_IF)
        {
            pif_desc++;
            ret = usb_skip_endp_desc((PBYTE *) & pif_desc, endp_count);
            if (ret)
                *(pBUF) = (PBYTE) pif_desc;
        }
        else
            ret = FALSE;
    }
    return ret;
}

BOOLEAN
usb_skip_if_and_altif(PUCHAR * pdesc_BUF)
{
    BOOLEAN ret;
    PUSB_INTERFACE_DESC pif_desc1 = (PUSB_INTERFACE_DESC) * pdesc_BUF;
    ret = is_header_match(*pdesc_BUF, USB_DT_INTERFACE);
    if (ret == TRUE)
    {
        if (pif_desc1->bAlternateSetting == 0)
            ret = usb_skip_if_desc((PUCHAR *) & pif_desc1);
        else
            //no default interface
            ret = FALSE;

        while (ret && pif_desc1->bAlternateSetting != 0)
            ret = usb_skip_if_desc((PUCHAR *) & pif_desc1);
    }
    if (ret)
        *pdesc_BUF = (PUCHAR) pif_desc1;

    return ret;
}

BOOLEAN
usb_skip_one_config(PUCHAR *pconfig_desc_BUF)
{
    LONG if_count, i;
    BOOLEAN ret;
    PUSB_CONFIGURATION_DESC pcfg_DESC = (PUSB_CONFIGURATION_DESC) * pconfig_desc_BUF;
    PUSB_INTERFACE_DESC pif_desc2 = (PUSB_INTERFACE_DESC) & pcfg_DESC[1];

    ret = is_header_match((PUCHAR) pcfg_DESC, USB_DT_CONFIG);
    if (ret)
        *pconfig_desc_BUF = &((BYTE *) pcfg_DESC)[pcfg_DESC->wTotalLength];
    return ret;

    ret = is_header_match((PUCHAR) pcfg_DESC, USB_DT_CONFIG)
        && is_header_match((PUCHAR) pif_desc2, USB_DT_INTERFACE);

    if (ret)
    {
        if_count = pcfg_DESC->bNumInterfaces;
        if (if_count < MAX_INTERFACES_PER_CONFIG)
        {
            for(i = 0; i < if_count; i++)
            {
                ret = usb_skip_if_and_altif((PUCHAR *) & pif_desc2);
                if (ret == FALSE)
                    break;
            }
            if (ret)
                *pconfig_desc_BUF = (PUCHAR) pif_desc2;
        }
    }
    return ret;
}

PUSB_CONFIGURATION_DESC
usb_find_config_desc_by_idx(PUCHAR pbuf, LONG idx, LONG cfg_count)
{
    LONG i;
    BOOLEAN ret;
    PUSB_CONFIGURATION_DESC pcfg_desc = (PUSB_CONFIGURATION_DESC) pbuf;
    if (pcfg_desc == NULL)
        return NULL;

    if (cfg_count > MAX_CONFIGS_PER_DEV)
        return NULL;

    if (idx > cfg_count)
        return NULL;

    if (idx == 0)
        return pcfg_desc;

    for(i = 0; i < idx - 1; i++)
    {
        ret = usb_skip_one_config((PBYTE *) & pcfg_desc);
        if (ret == FALSE)
            return NULL;
    }
    return pcfg_desc;
}

PUSB_CONFIGURATION_DESC
usb_find_config_desc_by_val(PBYTE pbuf, LONG val, LONG cfg_count)
{
    LONG i;
    BOOLEAN ret;
    PUSB_CONFIGURATION_DESC pcfg_desc = (PUSB_CONFIGURATION_DESC) pbuf;
    if (pcfg_desc == NULL)
        return NULL;

    if (cfg_count > MAX_CONFIGS_PER_DEV)
        return NULL;

    for(i = 0; i < cfg_count; i++)
    {
        if (pcfg_desc->bConfigurationValue == val)
            return pcfg_desc;

        ret = usb_skip_one_config((PBYTE *) & pcfg_desc);
        if (ret == FALSE)
            return NULL;
    }

    return NULL;
}

#define if_from_handle( handle ) ( ( handle & 0xff00 ) >> 8 )

NTSTATUS
usb_submit_config_urb(PURB purb)
{
    PUSB_DEV pdev;
    PUSB_DEV_MANAGER dev_mgr;
    PUSB_ENDPOINT pendp;
    PURB purb1;
    PUSB_CTRL_SETUP_PACKET psetup;
    NTSTATUS status;
    PHCD hcd;

    USE_BASIC_NON_PENDING_IRQL;

    if (purb == NULL)
        return STATUS_INVALID_PARAMETER;

    pdev = purb->pdev;
    pendp = purb->pendp;

    lock_dev(pdev, FALSE);

    dev_mgr = dev_mgr_from_dev(pdev);
    hcd = pdev->hcd;

    if (dev_state(pdev) == USB_DEV_STATE_ZOMB)
    {
        status = STATUS_DEVICE_DOES_NOT_EXIST;
        goto LBL_OUT;
    }

    if (dev_state(pdev) == USB_DEV_STATE_FIRST_CONFIG || dev_state(pdev) == USB_DEV_STATE_RECONFIG)
    {
        //outstanding request of set configuration exists in process
        status = STATUS_UNSUCCESSFUL;
        goto LBL_OUT;
    }

    psetup = (PUSB_CTRL_SETUP_PACKET) purb->setup_packet;

    if (dev_state(pdev) == USB_DEV_STATE_CONFIGURED
        && pdev->usb_config->pusb_config_desc->bConfigurationValue == (BYTE) psetup->wValue)
    {
        //already the current config
        status = STATUS_SUCCESS;
        goto LBL_OUT;
    }


    if (dev_state(pdev) == USB_DEV_STATE_CONFIGURED)
    {
        // not support re-configuration yet
        status = STATUS_NOT_SUPPORTED;
        goto LBL_OUT;
    }

    psetup = (PUSB_CTRL_SETUP_PACKET) purb->setup_packet;
    purb1 = usb_alloc_mem(NonPagedPool, sizeof(URB));
    if (purb1 == NULL)
    {
        status = STATUS_NO_MEMORY;
        goto LBL_OUT;
    }

    UsbBuildSelectConfigurationRequest(purb1,
                                       usb_make_handle(pdev->dev_id, 0, 0) | 0xffff,
                                       psetup->wValue, usb_config_dev_completion, 0, ((ULONG) purb));
    purb1->pdev = pdev;
    purb1->pendp = pendp;

    //change the dev state
    pdev->flags &= ~USB_DEV_STATE_MASK;
    pdev->flags |= USB_DEV_STATE_FIRST_CONFIG;

    unlock_dev(pdev, FALSE);

    status = hcd->hcd_submit_urb(hcd, pdev, pendp, purb1);
    if (status != STATUS_PENDING)
    {
        usb_free_mem(purb1);
        purb1 = NULL;
    }
    return status;

  LBL_OUT:
    unlock_dev(pdev, FALSE);
    return status;
}


NTSTATUS
usb_submit_urb(PUSB_DEV_MANAGER dev_mgr, PURB purb)
{
    NTSTATUS status;
    PUSB_DEV pdev;
    LONG if_idx, endp_idx;
    DEV_HANDLE endp_handle;
    PUSB_CTRL_SETUP_PACKET psetup;
    PUSB_ENDPOINT pendp;

    PHCD hcd;
    USE_BASIC_NON_PENDING_IRQL;

    if (purb == NULL || dev_mgr == NULL)
        return STATUS_INVALID_PARAMETER;

    endp_handle = purb->endp_handle;

    if (endp_handle == 0)
        return STATUS_INVALID_PARAMETER;

    status = usb_query_and_lock_dev(dev_mgr, endp_handle, &pdev);
    if (status != STATUS_SUCCESS)
    {
        return status;
    }

    if_idx = if_idx_from_handle(endp_handle);
    endp_idx = endp_idx_from_handle(endp_handle);

    lock_dev(pdev, FALSE);
    if (dev_state(pdev) == USB_DEV_STATE_ZOMB)
    {
        unlock_dev(pdev, FALSE);
        status = STATUS_DEVICE_DOES_NOT_EXIST;
        goto LBL_OUT;
    }

    if (dev_state(pdev) < USB_DEV_STATE_ADDRESSED)
    {
        unlock_dev(pdev, FALSE);
        status = STATUS_DEVICE_NOT_READY;
        goto LBL_OUT;
    }

    dev_mgr = dev_mgr_from_dev(pdev);
    hcd = pdev->hcd;

    if (default_endp_handle(endp_handle))
    {
        //default endp
        pendp = &pdev->default_endp;
    }
    else if (if_idx >= MAX_INTERFACES_PER_CONFIG || endp_idx >= MAX_ENDPS_PER_IF)
    {
        status = STATUS_INVALID_PARAMETER;
        unlock_dev(pdev, FALSE);
        goto LBL_OUT;
    }
    else
    {
        if (dev_state(pdev) < USB_DEV_STATE_CONFIGURED)
        {
            status = STATUS_DEVICE_NOT_READY;
            unlock_dev(pdev, FALSE);
            goto LBL_OUT;
        }
        pendp = &pdev->usb_config->interf[if_idx].endp[endp_idx];

    }

    purb->pdev = pdev;
    purb->pendp = pendp;

    //for default endpoint we have some special process
    if (default_endp_handle(endp_handle))
    {
        psetup = (PUSB_CTRL_SETUP_PACKET) purb->setup_packet;
        if (psetup->bmRequestType == 0 && psetup->bRequest == USB_REQ_SET_CONFIGURATION)
        {
            unlock_dev(pdev, FALSE);
            status = usb_submit_config_urb(purb);
            goto LBL_OUT;
        }
        else if (psetup->bmRequestType == 1 && psetup->bRequest == USB_REQ_SET_INTERFACE)
        {
            unlock_dev(pdev, FALSE);
            // status = STATUS_NOT_SUPPORTED;
            status = usb_set_interface(purb);
            goto LBL_OUT;
        }
        else if (psetup->bmRequestType == 0x80 && psetup->bRequest == USB_REQ_GET_DESCRIPTOR)
        {
            if ((psetup->wValue >> 8) == USB_DT_CONFIG || (psetup->wValue >> 8) == USB_DT_DEVICE)
            {
                unlock_dev(pdev, FALSE);
                status = usb_get_descriptor(pdev, purb);
                goto LBL_OUT;

                //get the descriptor directly
                //status = hcd->hcd_submit_urb( hcd, pdev, purb->pendp, purb );
                //goto LBL_OUT;
            }
        }
        else if (psetup->bmRequestType == 0x02 && psetup->bRequest == USB_REQ_CLEAR_FEATURE && psetup->wValue == 0)     //reset pipe
        {
            ULONG endp_addr;
            BOOLEAN found;
            endp_addr = psetup->wIndex;
            if ((endp_addr & 0xf) == 0)
            {
                unlock_dev(pdev, FALSE);
                status = STATUS_INVALID_PARAMETER;
                goto LBL_OUT;
            }

            // search for the endp by the endp addr in the wIndex
            found = FALSE;
            for(if_idx = 0; if_idx < pdev->usb_config->if_count; if_idx++)
            {
                for(endp_idx = 0; endp_idx < pdev->usb_config->interf[if_idx].endp_count; endp_idx++)
                {
                    pendp = &pdev->usb_config->interf[if_idx].endp[endp_idx];
                    if (pendp->pusb_endp_desc->bEndpointAddress == endp_addr)
                    {
                        found = TRUE;
                        break;
                    }
                }
                if (found == TRUE)
                    break;
            }
            if (found)
                endp_handle = usb_make_handle(pdev->dev_id, if_idx, endp_idx);
            else
            {
                unlock_dev(pdev, FALSE);
                status = STATUS_INVALID_PARAMETER;
                goto LBL_OUT;
            }
            unlock_dev(pdev, FALSE);
            status = usb_reset_pipe_ex(dev_mgr, endp_handle, usb_reset_pipe_from_dispatch_completion, purb);

            goto LBL_OUT;
        }
    }

    unlock_dev(pdev, FALSE);
    status = hcd->hcd_submit_urb(hcd, pdev, purb->pendp, purb);

LBL_OUT:
    usb_unlock_dev(pdev);
    return status;
}

void
usb_config_dev_completion(PURB purb, PVOID context)
{
    PURB puser_urb;
    PUSB_DEV pdev;
    PUSB_ENDPOINT pendp;
    PUSB_CTRL_SETUP_PACKET psetup;
    ULONG config_val;
    NTSTATUS status;

    USE_BASIC_NON_PENDING_IRQL;

    UNREFERENCED_PARAMETER(context);

    if (purb == NULL)
    {
        return;
    }
    pdev = purb->pdev;
    pendp = purb->pendp;

    if (pdev == NULL)
        return;

    if (purb->reference != 0)
        puser_urb = (PURB) purb->reference;
    else
        puser_urb = NULL;

    lock_dev(pdev, TRUE);

    if (puser_urb)
        puser_urb->status = purb->status;

    if (purb->status != STATUS_SUCCESS)
    {
        if (dev_state(pdev) == USB_DEV_STATE_ZOMB)
        {
            goto LBL_OUT;
        }

        if (dev_state(pdev) == USB_DEV_STATE_FIRST_CONFIG)
        {
            pdev->flags &= ~USB_DEV_STATE_MASK;
            pdev->flags |= USB_DEV_STATE_ADDRESSED;
        }
        else if (dev_state(pdev) == USB_DEV_STATE_RECONFIG)
        {
            pdev->flags &= ~USB_DEV_STATE_MASK;
            pdev->flags |= USB_DEV_STATE_CONFIGURED;

        }
        goto LBL_OUT;
    }
    // now let's construct usb_config
    if (!pdev->usb_config)
    {
        psetup = (PUSB_CTRL_SETUP_PACKET) purb->setup_packet;
        config_val = psetup->wValue;
        status = dev_mgr_build_usb_config(pdev,
                                          &pdev->desc_buf[sizeof(USB_DEVICE_DESC)],
                                          config_val, pdev->pusb_dev_desc->bNumConfigurations);
        if (status != STATUS_SUCCESS)
        {
            pdev->flags &= ~USB_DEV_STATE_MASK;
            pdev->flags |= USB_DEV_STATE_ADDRESSED;
            goto LBL_OUT;
        }
        pdev->flags &= ~USB_DEV_STATE_MASK;
        pdev->flags |= USB_DEV_STATE_CONFIGURED;
        //this usb dev represents physical dev
        if (pdev->pusb_dev_desc->bDeviceClass == USB_CLASS_HUB && pdev->pusb_dev_desc->bDeviceSubClass == 0)
        {
            pdev->flags &= ~USB_DEV_CLASS_MASK;
            pdev->flags |= USB_DEV_CLASS_HUB;
        }
        else if (pdev->pusb_dev_desc->bDeviceClass == USB_CLASS_MASS_STORAGE
                 && pdev->pusb_dev_desc->bDeviceSubClass == 0)
        {
            pdev->flags &= ~USB_DEV_CLASS_MASK;
            pdev->flags |= USB_DEV_CLASS_MASSSTOR;
        }
        else
        {
            pdev->flags &= ~USB_DEV_CLASS_MASK;
            pdev->flags |= USB_DEV_CLASS_SCANNER;
        }
    }
    else
    {
        //not supported
        puser_urb->status = STATUS_NOT_SUPPORTED;
        pdev->flags &= ~USB_DEV_STATE_MASK;
        pdev->flags |= USB_DEV_STATE_CONFIGURED;
    }

LBL_OUT:
    unlock_dev(pdev, TRUE);
    usb_free_mem(purb);
    if (puser_urb && puser_urb->completion)
        puser_urb->completion(puser_urb, puser_urb->context);

    return;
}

NTSTATUS
usb_get_descriptor(PUSB_DEV pdev, PURB purb)
{
    PUSB_CTRL_SETUP_PACKET psetup;
    LONG idx, size, count, i;
    PBYTE buf;
    PUSB_CONFIGURATION_DESC pcfg_desc1;

    USE_BASIC_NON_PENDING_IRQL;

    if (pdev == NULL || purb == NULL)
        return STATUS_INVALID_PARAMETER;

    if (purb->data_buffer == NULL || purb->data_length == 0)
    {
        return purb->status = STATUS_INVALID_PARAMETER;
    }

    lock_dev(pdev, FALSE);
    psetup = (PUSB_CTRL_SETUP_PACKET) purb->setup_packet;
    if (pdev->desc_buf == NULL)
    {
        purb->status = STATUS_DEVICE_NOT_READY;
        goto LBL_OUT;
    }

    if ((psetup->wValue >> 8) == USB_DT_CONFIG)
    {
        idx = (psetup->wValue & 0xff);

        count = pdev->pusb_dev_desc->bNumConfigurations;

        if (idx >= count)
        {
            purb->status = STATUS_INVALID_PARAMETER;
            goto LBL_OUT;
        }
        buf = &pdev->desc_buf[sizeof(USB_DEVICE_DESC)];
        pcfg_desc1 = usb_find_config_desc_by_idx(buf, idx, count);
        if (pcfg_desc1 == NULL)
        {
            purb->status = STATUS_UNSUCCESSFUL;
            goto LBL_OUT;
        }

        size = pcfg_desc1->wTotalLength;
        size = size > purb->data_length ? purb->data_length : size;
        for(i = 0; i < size; i++)
        {
            purb->data_buffer[i] = ((PBYTE) pcfg_desc1)[i];
        }
        purb->status = STATUS_SUCCESS;
        goto LBL_OUT;

    }
    else if ((psetup->wValue >> 8) == USB_DT_DEVICE)
    {
        size = purb->data_length > sizeof(USB_DEVICE_DESC) ? sizeof(USB_DEVICE_DESC) : purb->data_length;

        for(i = 0; i < size; i++)
        {
            purb->data_buffer[i] = ((PBYTE) pdev->pusb_dev_desc)[i];
        }
        purb->status = STATUS_SUCCESS;
    }

LBL_OUT:
    unlock_dev(pdev, FALSE);
    return purb->status;
}

LONG
usb_count_list(PLIST_HEAD list_head)
{
    LONG count;
    PLIST_ENTRY pthis, pnext;

    if (list_head == NULL)
        return 0;

    count = 0;
    ListFirst(list_head, pthis);

    while (pthis)
    {
        ListNext(list_head, pthis, pnext);
        pthis = pnext;
        count++;
    }
    return count;
}

// checks if processor supports Time Stamp Counter
__inline BOOLEAN
usb_query_clicks(PLARGE_INTEGER clicks)
{
    BOOLEAN ret_val;
    //so we have to use intel's cpu???
    ret_val = FALSE;

#ifdef _MSC_VER
    __asm
    {
        push ebx;
        push eax;
        mov eax, 1;             //read version
        cpuid;
        test edx, 0x10;         //timer stamp
        jz LBL_OUT;
        // cpuid                                //serialization
        rdtsc;
        mov ebx, dword ptr[clicks];
        mov dword ptr[ebx], eax;
        mov dword ptr[ebx + 4], edx;
        mov dword ptr[ret_val], TRUE;
LBL_OUT:
        pop eax;
        pop ebx;
    }
#else
    ret_val = FALSE;
#endif
    return ret_val;
}

VOID
usb_wait_ms_dpc(ULONG ms)
{
    LARGE_INTEGER start;
    LARGE_INTEGER ticker;
    LARGE_INTEGER freq;
    ULONG interval;
    ULONG expire_count;

    KeQueryPerformanceCounter(&freq);

    expire_count = 2000000;
    if (cpu_clock_freq)
        expire_count = (cpu_clock_freq / 1000) * ms;

    if (usb_query_clicks(&start) == FALSE)
    {
        ticker.QuadPart = 0;
        while (TRUE)
        {
            KeQuerySystemTime(&ticker);
            interval = ticker.LowPart - start.LowPart;
            if (interval >= ms * 10000)
                break;
        }
    }
    else
    {
        ticker.QuadPart = 0;
        while (TRUE)
        {
            usb_query_clicks(&ticker);
            interval = ticker.LowPart - start.LowPart;
            if (interval >= expire_count)
                break;
        }
    }
}


VOID
usb_wait_us_dpc(ULONG us)
{
    LARGE_INTEGER start;
    LARGE_INTEGER ticker;
    LARGE_INTEGER freq;
    ULONG interval;
    ULONG expire_count;

    KeQueryPerformanceCounter(&freq);

    expire_count = 2000000;
    if (cpu_clock_freq)
        expire_count = (cpu_clock_freq / 1000000) * us;

    if (usb_query_clicks(&start) == FALSE)
    {
        ticker.QuadPart = 0;
        while (TRUE)
        {
            KeQuerySystemTime(&ticker);
            interval = ticker.LowPart - start.LowPart;
            if (interval >= us * 10)
                break;
        }
    }
    else
    {
        ticker.QuadPart = 0;
        while (TRUE)
        {
            usb_query_clicks(&ticker);
            interval = ticker.LowPart - start.LowPart;
            if (interval >= expire_count)
                break;
        }
    }
}

VOID
usb_cal_cpu_freq()
{
    LARGE_INTEGER tick1, tick2;
    LONG i;
    // interval.QuadPart = -40 * 1000 * 1000;

    if (cpu_clock_freq >= 100 * 1000 * 1000)    // assume it is valid
        return;

    if (usb_query_clicks(&tick1))
    {
        for(i = 0; i < 25; i++)
        {
            usb_query_clicks(&tick1);
            KeStallExecutionProcessor(40 * 1000);
            usb_query_clicks(&tick2);
            cpu_clock_freq += (ULONG) (tick2.QuadPart - tick1.QuadPart);
        }
        // cpu_clock_freq *= 1000;
        usb_dbg_print(DBGLVL_MAXIMUM, ("usb_cal_cpu_freq(): cpu frequency = %d Hz\n", cpu_clock_freq));
    }
}

NTSTATUS
usb_set_interface(PURB purb)
{
    ULONG u;
    PURB purb1;
    PCTRL_REQ_STACK pstack;
    PUSB_DEV pdev;
    PUSB_CTRL_SETUP_PACKET psetup;
    PUSB_ENDPOINT pendp;
    NTSTATUS status;

    PHCD hcd;
    USE_BASIC_NON_PENDING_IRQL;

    purb1 = purb;
    pdev = purb->pdev;
    psetup = (PUSB_CTRL_SETUP_PACKET) purb->setup_packet;

    lock_dev(pdev, FALSE);
    if (dev_state(pdev) == USB_DEV_STATE_ZOMB)
    {
        unlock_dev(pdev, FALSE);
        return STATUS_DEVICE_NOT_CONNECTED;
    }
    if (dev_state(pdev) < USB_DEV_STATE_CONFIGURED)
    {
        unlock_dev(pdev, FALSE);
        return STATUS_DEVICE_NOT_READY;
    }

    hcd = pdev->hcd;

    if (psetup->wIndex >= pdev->usb_config->if_count)
    {
        unlock_dev(pdev, FALSE);
        return STATUS_INVALID_PARAMETER;
    }
    if (psetup->wValue >= pdev->usb_config->interf[psetup->wIndex].altif_count + 1)
    {
        unlock_dev(pdev, FALSE);
        return STATUS_INVALID_PARAMETER;
    }
    if (pdev->usb_config->interf[psetup->wIndex].pusb_if_desc->bAlternateSetting == psetup->wValue)
    {
        // already the current interface
        unlock_dev(pdev, FALSE);
        return STATUS_SUCCESS;
    }
    // check to see if the endp is busy
    for(u = 0; u < pdev->usb_config->interf[psetup->wIndex].endp_count; u++)
    {
        // This check is not adquate. Since we do not have mechanism to block the new coming
        // request during this request. the caller must guarantee no active or pending
        // usb request on these endpoint.
        pendp = &pdev->usb_config->interf[psetup->wIndex].endp[u];
        if (usb_endp_busy_count(pendp))
        {
            // active urb on that endp
            unlock_dev(pdev, FALSE);
            return STATUS_DEVICE_NOT_READY;
        }
        if (IsListEmpty(&pendp->urb_list))
        {
            // pending urb on that endp
            unlock_dev(pdev, FALSE);
            return STATUS_DEVICE_NOT_READY;
        }
    }
    unlock_dev(pdev, FALSE);

    if (purb1->ctrl_req_context.ctrl_stack_count == 0)
    {
        // ok, we have one stack cell for our use
        if (purb1->completion != NULL)
        {
            purb1->ctrl_req_context.ctrl_stack_count = 1;
            purb1->ctrl_req_context.ctrl_cur_stack = 0;
        }
        else
        {
            // use urb's completion and context
            purb1->completion = usb_set_interface_completion;
            purb1->context = pdev;
        }
    }
    else
    {
        if (purb->ctrl_req_context.ctrl_cur_stack + 1 >= purb->ctrl_req_context.ctrl_stack_count)
        {
            // stack full, let's allocate one new urb, we need stack size one
            purb1 = usb_alloc_mem(NonPagedPool, sizeof(URB));
            if (purb1 == NULL)
                return STATUS_NO_MEMORY;

            RtlCopyMemory(purb1, purb, sizeof(URB));

            // we do not use stack
            RtlZeroMemory(purb1->ctrl_req_stack, sizeof(CTRL_REQ_STACK));
            purb1->context = pdev;
            purb1->completion = usb_set_interface_completion;
            purb1->ctrl_parent_urb = purb;
            purb1->ctrl_req_context.ctrl_req_flags = CTRL_PARENT_URB_VALID;

            goto LBL_SEND_URB;
        }
        else
            purb->ctrl_req_context.ctrl_cur_stack++;
    }

    u = purb1->ctrl_req_context.ctrl_cur_stack;
    RtlZeroMemory(&purb1->ctrl_req_stack[u], sizeof(CTRL_REQ_STACK));
    pstack = &purb1->ctrl_req_stack[u];
    pstack->context = pdev;
    pstack->urb_completion = usb_set_interface_completion;

LBL_SEND_URB:
    if (hcd == NULL)
        return STATUS_INVALID_PARAMETER;

    status = hcd->hcd_submit_urb(hcd, purb->pdev, purb->pendp, purb);
    return status;
}

#define usb_complete_and_free_ctrl_urb( pURB ) \
{\
    UCHAR i, j;\
    i = pURB->ctrl_req_context.ctrl_cur_stack;\
    j = pURB->ctrl_req_context.ctrl_stack_count;\
    usb_call_ctrl_completion( pURB );\
    if( i == 0xff || j == 0 )\
        usb_free_mem( pURB );\
}

VOID
usb_set_interface_completion(PURB purb, PVOID context)
{
    PUSB_CTRL_SETUP_PACKET psetup;
    PUSB_INTERFACE pif, palt_if;
    USB_INTERFACE temp_if;
    UCHAR if_idx, if_alt_idx;
    PUSB_DEV pdev;
    PUSB_ENDPOINT pendp;
    ULONG i;
    PLIST_ENTRY pthis, pnext;

    USE_BASIC_NON_PENDING_IRQL;

    UNREFERENCED_PARAMETER(context);

    if (purb == NULL)
        return;

    if (purb->status == STATUS_SUCCESS)
    {
        psetup = (PUSB_CTRL_SETUP_PACKET) purb->setup_packet;
        if_idx = (UCHAR) psetup->wIndex;
        if_alt_idx = (UCHAR) psetup->wValue;
        pdev = purb->pdev;
        RtlZeroMemory(&temp_if, sizeof(USB_INTERFACE));

        lock_dev(pdev, TRUE);
        if (dev_state(pdev) == USB_DEV_STATE_ZOMB)
        {
            unlock_dev(pdev, TRUE);
            purb->status = STATUS_DEVICE_NOT_CONNECTED;
            purb->data_length = 0;
        }
        else
        {
            // let's swap the interface
            pif = &pdev->usb_config->interf[if_idx];
            ListFirst(&pif->altif_list, pthis);
            pnext = pthis;
            do
            {
                palt_if = struct_ptr(pthis, USB_INTERFACE, altif_list);
                if (palt_if->pusb_if_desc->bAlternateSetting == if_alt_idx)
                {
                    break;
                }
                palt_if = NULL;
                ListNext(&pif->altif_list, pthis, pnext);
                pthis = pnext;

            } while (pthis);

            if (palt_if != NULL)
            {
                RtlCopyMemory(&temp_if, palt_if, sizeof(USB_INTERFACE));

                palt_if->endp_count = pif->endp_count;
                RtlCopyMemory(palt_if->endp, pif->endp, sizeof(pif->endp));
                palt_if->pif_drv = pif->pif_drv;
                palt_if->pusb_if_desc = pif->pusb_if_desc;
                for(i = 0; i < palt_if->endp_count; i++)
                {
                    pendp = &palt_if->endp[i];
                    InitializeListHead(&pendp->urb_list);
                    pendp->flags = 0;
                }

                RtlCopyMemory(pif->endp, temp_if.endp, sizeof(temp_if.endp));
                pif->endp_count = temp_if.endp_count;
                pif->pusb_if_desc = temp_if.pusb_if_desc;
                for(i = 0; i < pif->endp_count; i++)
                {
                    pendp = &pif->endp[i];
                    InitializeListHead(&pendp->urb_list);
                    pendp->flags = 0;
                }
            }
            else
            {
                TRAP();
                purb->status = STATUS_UNSUCCESSFUL;
            }
        }
        unlock_dev(pdev, TRUE);
    }

    // for recursive reason, we have to store the parameter ahead
    usb_complete_and_free_ctrl_urb(purb);
}

// can only be called when current completion finished and called only in
// urb completion. And this func may be called recursively, if this routine
// is called, the urb must be treated as released.
VOID
usb_call_ctrl_completion(PURB purb)
{
    PURB parent_urb;
    PCTRL_REQ_STACK pstack;
    ULONG i;


    if (purb == NULL)
        return;

    if (purb->ctrl_req_context.ctrl_stack_count != 0)
    {
        i = purb->ctrl_req_context.ctrl_cur_stack;
        if (i > 0 && i < 0x10)
        {
            i--;
            purb->ctrl_req_context.ctrl_cur_stack = (UCHAR) i;
            pstack = &purb->ctrl_req_stack[i];
            if (pstack->urb_completion)
            {
                pstack->urb_completion(purb, pstack->context);
            }
            else
                TRAP();
        }
        else if (i == 0)
        {
            i = purb->ctrl_req_context.ctrl_cur_stack = 0xff;
            if (purb->completion)
            {
                purb->completion(purb, purb->context);
            }
            else
                TRAP();
        }
        else if (i == 0xff)
        {
            // only parent urb's completion, if parent urb exists, can be called
            if (purb->ctrl_req_context.ctrl_req_flags & CTRL_PARENT_URB_VALID)
            {
                parent_urb = purb->ctrl_parent_urb;
                if (parent_urb)
                {
                    pstack = &parent_urb->ctrl_req_stack[parent_urb->ctrl_req_context.ctrl_cur_stack];
                    pstack->urb_completion(parent_urb, pstack->context);
                }
                else
                    TRAP();
            }
        }
        else
            TRAP();
    }
    else if (purb->ctrl_req_context.ctrl_req_flags & CTRL_PARENT_URB_VALID)
    {
        // this is the case when the child urb won't use the stack
        parent_urb = purb->ctrl_parent_urb;
        if (parent_urb)
        {
            // pstack = &parent_urb->ctrl_req_stack[ parent_urb->ctrl_req_context.ctrl_cur_stack ];
            // pstack->urb_completion( parent_urb, pstack->context );
            usb_call_ctrl_completion(parent_urb);
        }
        else
            TRAP();
    }
    else
        return;
}
