/*
 * Copyright (c) 2015, Freescale Semiconductor, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * o 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.
 *
 * o Neither the name of Freescale Semiconductor, Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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.
 */

#include "usb_host_config.h"
#include "usb_host.h"
#include "usb_host_hci.h"
#include "usb_host_devices.h"

#if ((defined USB_HOST_CONFIG_HUB) && (USB_HOST_CONFIG_HUB))
#include "usb_host_hub.h"
#endif /* USB_HOST_CONFIG_HUB */

/*******************************************************************************
 * Definitions
 ******************************************************************************/

/*******************************************************************************
 * Prototypes
 ******************************************************************************/

/*!
 * @brief enumeration transfer callback function.
 *
 * @param param      callback parameter.
 * @param transfer   the transfer.
 * @param status     transfer result status.
 */
static void USB_HostEnumerationTransferCallback(void *param, usb_host_transfer_t *transfer, usb_status_t status);

/*!
 * @brief process the new step state.
 *
 * @param deviceInstance    device instance pointer.
 *
 * @return kStatus_USB_Success or error codes
 */
static usb_status_t USB_HostProcessState(usb_host_device_instance_t *deviceInstance);

/*!
 * @brief process the previous step transfer result.
 *
 * @param deviceInstance    device instance pointer.
 *
 * @return kStatus_USB_Success or error codes
 */
static usb_status_t USB_HostProcessCallback(usb_host_device_instance_t *deviceInstance);

/*!
 * @brief notify the application event, the callback is registered when initializing host.
 *
 * @param deviceInstance   device instance pointer.
 * @param eventCode        event code.
 *
 * @return kStatus_USB_Success or error codes
 */
static usb_status_t USB_HostNotifyDevice(usb_host_device_instance_t *deviceInstance, uint32_t eventCode);

/*!
 * @brief allocate one address.
 *
 * @param hostInstance    host instance pointer.
 *
 * @return address, 0 is invalid.
 */
static uint8_t USB_HostAllocateDeviceAddress(usb_host_instance_t *hostInstance);

/*!
 * @brief release one address.
 *
 * @param hostInstance    host instance pointer.
 * @param address     releasing address.
 */
static void USB_HostReleaseDeviceAddress(usb_host_instance_t *hostInstance, uint8_t address);

/*!
 * @brief release device resource.
 *
 * @param hostInstance   host instance pointer.
 * @param deviceInstance    device instance pointer.
 */
static void USB_HostReleaseDeviceResource(usb_host_instance_t *hostInstance,
                                          usb_host_device_instance_t *deviceInstance);

/*!
 * @brief parse device configuration descriptor.
 *
 * @param deviceHandle    device handle.
 *
 * @return kStatus_USB_Success or error codes.
 */
static usb_status_t USB_HostParseDeviceConfigurationDescriptor(usb_device_handle deviceHandle);

/*!
 * @brief remove device instance from host device list.
 *
 * @param hostHandle   host instance handle.
 * @param deviceHandle    device handle.
 *
 * @return kStatus_USB_Success or error codes.
 */
static usb_status_t USB_HostRemoveDeviceInstance(usb_host_handle hostHandle, usb_device_handle deviceHandle);

/*!
 * @brief control the bus.
 *
 * This function control the host bus.
 *
 * @param[in] hostHandle     the host handle.
 * @param[in] controlType    the control code, please reference to bus_event_t.
 *
 * @retval kStatus_USB_Success              control successfully.
 * @retval kStatus_USB_InvalidHandle        The hostHandle is a NULL pointer.
 */
static usb_status_t USB_HostControlBus(usb_host_handle hostHandle, uint8_t controlType);

extern usb_status_t USB_HostStandardSetGetDescriptor(usb_host_device_instance_t *deviceInstance,
                                                     usb_host_transfer_t *transfer,
                                                     void *param);
extern usb_status_t USB_HostStandardSetAddress(usb_host_device_instance_t *deviceInstance,
                                               usb_host_transfer_t *transfer,
                                               void *param);
extern usb_status_t USB_HostCh9RequestCommon(usb_host_device_instance_t *deviceInstance,
                                             usb_host_transfer_t *transfer,
                                             uint8_t *buffer,
                                             uint32_t bufferLen);

#if ((defined USB_HOST_CONFIG_HUB) && (USB_HOST_CONFIG_HUB))

extern usb_status_t USB_HostHubDeviceEvent(usb_device_handle deviceHandle,
                                           usb_host_configuration_handle configurationHandle,
                                           uint32_t eventCode);

extern uint32_t USB_HostHubGetHsHubNumber(uint8_t parentHubNo);

extern uint32_t USB_HostHubGetHsHubPort(uint8_t parentHubNo, uint8_t parentPortNo);

extern usb_status_t USB_HostHubRemovePort(uint8_t hubNumber, uint8_t portNumber);

#endif

/*******************************************************************************
 * Variables
 ******************************************************************************/

/*! @brief enumeration step process array */
static const usb_host_enum_process_entry_t s_EnumEntries[] = \
{
    /* kStatus_dev_initial */
    {
        0, 0, NULL,
    },
    /* kStatus_DEV_GetDes8 */
    {
        kStatus_DEV_SetAddress, kStatus_DEV_GetDes8, USB_HostProcessCallback,
    },
    /* kStatus_DEV_SetAddress */
    {
        kStatus_DEV_GetDes, kStatus_DEV_SetAddress, USB_HostProcessCallback,
    },
    /* kStatus_DEV_GetDes */
    {
        kStatus_DEV_GetCfg9, kStatus_DEV_GetDes, NULL,
    },
    /* kStatus_DEV_GetCfg9 */
    {
        kStatus_DEV_GetCfg, kStatus_DEV_GetCfg9, USB_HostProcessCallback,
    },
    /* kStatus_DEV_GetCfg */
    {
        kStatus_DEV_SetCfg, kStatus_DEV_GetCfg9, USB_HostProcessCallback,
    },
    /* kStatus_DEV_SetCfg */
    {
        kStatus_DEV_EnumDone, kStatus_DEV_SetCfg, NULL,
    },
};

/*******************************************************************************
 * Code
 ******************************************************************************/

static void USB_HostEnumerationTransferCallback(void *param, usb_host_transfer_t *transfer, usb_status_t status)
{
    uint8_t nextStep = 0;
    usb_host_device_instance_t *deviceInstance = (usb_host_device_instance_t *)param;

    USB_HostFreeTransfer(deviceInstance->hostHandle, transfer); /* free transfer */

    if (status == kStatus_USB_Success)
    {
        nextStep = 1;
    }
    else if (status == kStatus_USB_TransferStall)
    {
#if ((defined USB_HOST_CONFIG_COMPLIANCE_TEST) && (USB_HOST_CONFIG_COMPLIANCE_TEST))
        usb_echo("no response from device\r\n");
#endif                                        /* USB_HOST_CONFIG_COMPLIANCE_TEST */
        if (deviceInstance->stallRetries > 0) /* retry same transfer when stall */
        {
            deviceInstance->stallRetries--;
        }
        else /* process next state when all retries stall */
        {
            nextStep = 1;
        }
    }
    else if (status == kStatus_USB_TransferCancel)
    {
        return;
    }
    else
    {
        if (deviceInstance->enumRetries > 0) /* next whole retry */
        {
            deviceInstance->enumRetries--;
            deviceInstance->stallRetries = USB_HOST_CONFIG_ENUMERATION_MAX_STALL_RETRIES;
            deviceInstance->configurationValue = 0;
            deviceInstance->state = kStatus_DEV_GetDes8;
        }
        else
        {
#ifdef HOST_ECHO
            usb_echo("enumeration fail\r\n");
#endif
            return;
        }
    }

    if (nextStep == 1)
    {
        deviceInstance->stallRetries = USB_HOST_CONFIG_ENUMERATION_MAX_STALL_RETRIES;
        if (s_EnumEntries[deviceInstance->state - 1].process == NULL)
        {
            deviceInstance->state = s_EnumEntries[deviceInstance->state - 1].successState; /* next state */
        }
        else
        {
            status = s_EnumEntries[deviceInstance->state - 1].process(
                deviceInstance);               /* process the previous state result */
            if (status == kStatus_USB_Success) /* process success */
            {
                deviceInstance->state = s_EnumEntries[deviceInstance->state - 1].successState;
            }
            else if (status == kStatus_USB_Retry) /* need retry */
            {
                deviceInstance->state = s_EnumEntries[deviceInstance->state - 1].retryState;
            }
            else if (status == kStatus_USB_NotSupported) /* device don't suport by the application */
            {
                return; /* unrecoverable fail */
            }
            else /* process error, next retry */
            {
                if (deviceInstance->enumRetries > 0) /* next whole retry */
                {
                    deviceInstance->enumRetries--;
                    deviceInstance->stallRetries = USB_HOST_CONFIG_ENUMERATION_MAX_STALL_RETRIES;
                    deviceInstance->configurationValue = 0;
                    deviceInstance->state = kStatus_DEV_GetDes8;
                }
                else
                {
#ifdef HOST_ECHO
                    usb_echo("enumeration fail\r\n");
#endif
                    return; /* unrecoverable fail */
                }
            }
        }
    }

    if (USB_HostProcessState(deviceInstance) != kStatus_USB_Success) /* process the new state */
    {
#ifdef HOST_ECHO
        usb_echo("enumation setup error\r\n");
#endif
        return;
    }
}

static usb_status_t USB_HostProcessState(usb_host_device_instance_t *deviceInstance)
{
    usb_status_t status = kStatus_USB_Success;
    usb_host_process_descriptor_param_t getDescriptorParam;
    usb_host_transfer_t *transfer;

    /* malloc transfer */
    if (deviceInstance->state != kStatus_DEV_EnumDone)
    {
        if (USB_HostMallocTransfer(deviceInstance->hostHandle, &transfer) != kStatus_USB_Success)
        {
#ifdef HOST_ECHO
            usb_echo("error to get transfer\r\n");
#endif
            return kStatus_USB_Error;
        }
        transfer->callbackFn = USB_HostEnumerationTransferCallback;
        transfer->callbackParam = deviceInstance;

        /* reset transfer fields */
        transfer->setupPacket.bmRequestType = 0x00;
        transfer->setupPacket.wIndex = 0;
        transfer->setupPacket.wLength = 0;
        transfer->setupPacket.wValue = 0;
    }

    switch (deviceInstance->state)
    {
        case kStatus_DEV_GetDes8:
        case kStatus_DEV_GetDes: /* get descriptor state */
            getDescriptorParam.descriptorLength = sizeof(usb_descriptor_device_t);
            if (deviceInstance->state == kStatus_DEV_GetDes8)
            {
                getDescriptorParam.descriptorLength = 8;
            }
            getDescriptorParam.descriptorBuffer = (uint8_t *)&deviceInstance->deviceDescriptor;
            getDescriptorParam.descriptorType = USB_DESCRIPTOR_TYPE_DEVICE;
            getDescriptorParam.descriptorIndex = 0;
            getDescriptorParam.languageId = 0;

            transfer->setupPacket.bmRequestType |= USB_REQUEST_TYPE_DIR_IN;
            transfer->setupPacket.bRequest = USB_REQUSET_STANDARD_GET_DESCRIPTOR;
            status = USB_HostStandardSetGetDescriptor(deviceInstance, transfer, &getDescriptorParam);
            break;
        case kStatus_DEV_SetAddress: /* set address state */
            transfer->setupPacket.bRequest = USB_REQUSET_STANDARD_SET_ADDRESS;
            status = USB_HostStandardSetAddress(deviceInstance, transfer, &deviceInstance->allocatedAddress);
            break;

        case kStatus_DEV_GetCfg9: /* get 9 bytes configuration state */
            getDescriptorParam.descriptorBuffer = deviceInstance->enumBuffer;
            getDescriptorParam.descriptorType = USB_DESCRIPTOR_TYPE_CONFIGURE;
            getDescriptorParam.descriptorIndex = deviceInstance->configurationValue;
            getDescriptorParam.descriptorLength = 9;
            getDescriptorParam.languageId = 0;

            transfer->setupPacket.bmRequestType |= USB_REQUEST_TYPE_DIR_IN;
            transfer->setupPacket.bRequest = USB_REQUSET_STANDARD_GET_DESCRIPTOR;
            status = USB_HostStandardSetGetDescriptor(deviceInstance, transfer, &getDescriptorParam);
            break;

        case kStatus_DEV_GetCfg: /* get configuration state */
            getDescriptorParam.descriptorBuffer = deviceInstance->configurationDesc;
            getDescriptorParam.descriptorType = USB_DESCRIPTOR_TYPE_CONFIGURE;
            getDescriptorParam.descriptorIndex = deviceInstance->configurationValue;
            getDescriptorParam.descriptorLength = deviceInstance->configurationLen;
            getDescriptorParam.languageId = 0;

            transfer->setupPacket.bmRequestType |= USB_REQUEST_TYPE_DIR_IN;
            transfer->setupPacket.bRequest = USB_REQUSET_STANDARD_GET_DESCRIPTOR;
            status = USB_HostStandardSetGetDescriptor(deviceInstance, transfer, &getDescriptorParam);
            break;

        case kStatus_DEV_SetCfg: /* set configuration state */
            transfer->setupPacket.wValue =
                USB_SHORT_TO_LITTLE_ENDIAN(deviceInstance->configuration.configurationDesc->bConfigurationValue);
            transfer->setupPacket.bRequest = USB_REQUSET_STANDARD_SET_CONFIGURATION;
            status = USB_HostCh9RequestCommon(deviceInstance, transfer, NULL, 0);
            break;

        case kStatus_DEV_EnumDone: /* enumeration done state */
            status = USB_HostNotifyDevice(deviceInstance,
                                          kUSB_HostEventEnumerationDone); /* notify device enumeration done */
            if (status == kStatus_USB_Success)
            {
                deviceInstance->state = kStatus_DEV_AppUsed;
            }
            break;

        default:
            break;
    }

    return status;
}

static usb_status_t USB_HostProcessCallback(usb_host_device_instance_t *deviceInstance)
{
    usb_host_pipe_t *pipe = (usb_host_pipe_t *)deviceInstance->controlPipe;
    usb_status_t status = kStatus_USB_Success;
    usb_descriptor_configuration_t *configureDesc;
    usb_host_instance_t *hostInstance = (usb_host_instance_t *)deviceInstance->hostHandle;

    switch (deviceInstance->state)
    {
        case kStatus_DEV_GetDes8: /* process get 8 bytes descriptor result */
            pipe->maxPacketSize = deviceInstance->deviceDescriptor.bMaxPacketSize0;
            hostInstance->controllerTable->controllerIoctl(
                hostInstance->controllerHandle, kUSB_HostUpdateControlPacketSize, deviceInstance->controlPipe);
            break;

        case kStatus_DEV_SetAddress: /* process set address result */
            deviceInstance->setAddress = deviceInstance->allocatedAddress;
            hostInstance->controllerTable->controllerIoctl(
                hostInstance->controllerHandle, kUSB_HostUpdateControlEndpointAddress, deviceInstance->controlPipe);
            break;

        case kStatus_DEV_GetDes: /* process set address result */
            /* NULL */
            break;

        case kStatus_DEV_GetCfg9: /* process get 9 bytes configuration result */
            configureDesc = (usb_descriptor_configuration_t *)&deviceInstance->enumBuffer[0];

            deviceInstance->configurationLen = USB_SHORT_FROM_LITTLE_ENDIAN_ADDRESS(configureDesc->wTotalLength);
            if (deviceInstance->configurationDesc != NULL)
            {
                USB_OsaMemoryFree(deviceInstance->configurationDesc);
                deviceInstance->configurationDesc = NULL;
            }
            /* for KHCI, the start address and the length should be 4 byte align */
            if ((deviceInstance->configurationLen & 0x03) != 0)
            {
                deviceInstance->configurationDesc =
                    (uint8_t *)USB_OsaMemoryAllocate((deviceInstance->configurationLen & 0xFFFFFFFCu) + 4);
            }
            else
            {
                deviceInstance->configurationDesc = (uint8_t *)USB_OsaMemoryAllocate(deviceInstance->configurationLen);
            }
            if (deviceInstance->configurationDesc == NULL)
            {
                return kStatus_USB_Error;
            }
            break;

        case kStatus_DEV_GetCfg: /* process get cofiguration result */
            if (((usb_descriptor_configuration_t *)deviceInstance->configurationDesc)->bMaxPower >
                USB_HOST_CONFIG_MAX_POWER)
            {
                return kStatus_USB_Error;
            }
            deviceInstance->configurationValue++;
            if (USB_HostParseDeviceConfigurationDescriptor(deviceInstance) !=
                kStatus_USB_Success) /* parse configuration descriptor */
            {
                return kStatus_USB_Error;
            }

            status = USB_HostNotifyDevice(deviceInstance, kUSB_HostEventAttach);
            break;

        case kStatus_DEV_SetCfg:
            /* NULL */
            break;

        default:
            break;
    }

    return status;
}

static usb_status_t USB_HostNotifyDevice(usb_host_device_instance_t *deviceInstance, uint32_t eventCode)
{
    usb_host_instance_t *hostInstance;
    usb_status_t status1 = kStatus_USB_Error;
#if ((defined USB_HOST_CONFIG_HUB) && (USB_HOST_CONFIG_HUB))
    usb_status_t status2 = kStatus_USB_Error;
    uint8_t haveHub;
    uint8_t haveNoHub;
    uint8_t interfaceIndex;
#endif /* USB_HOST_CONFIG_HUB */

    if (deviceInstance == NULL)
    {
        return kStatus_USB_InvalidHandle;
    }
    hostInstance = (usb_host_instance_t *)deviceInstance->hostHandle;

#if ((defined USB_HOST_CONFIG_HUB) && (USB_HOST_CONFIG_HUB))
    haveHub = 0;
    haveNoHub = 0;
    for (interfaceIndex = 0; interfaceIndex < deviceInstance->configuration.interfaceCount; ++interfaceIndex)
    {
        if (((usb_descriptor_interface_t *)deviceInstance->configuration.interfaceList[interfaceIndex].interfaceDesc)
                ->bInterfaceClass == USB_HOST_HUB_CLASS_CODE)
        {
            haveHub = 1;
        }
        else
        {
            haveNoHub = 1;
        }
    }

    if ((haveNoHub == 1) && (hostInstance->deviceCallback != NULL))
    {
        status1 = hostInstance->deviceCallback(deviceInstance, &deviceInstance->configuration,
                                               eventCode); /* notify application event */
    }
    if (haveHub)
    {
        status2 =
            USB_HostHubDeviceEvent(deviceInstance, &deviceInstance->configuration, eventCode); /* notify hub event */
    }

    if ((status1 == kStatus_USB_Success) || (status2 == kStatus_USB_Success)) /* the device is supported */
    {
        return kStatus_USB_Success;
    }
    else if (eventCode == kUSB_HostEventAttach) /* attach event */
    {
        status1 = kStatus_USB_NotSupported;
    }
    else
    {
        status1 = kStatus_USB_Error;
    }
#else
    if (hostInstance->deviceCallback != NULL)
    {
        status1 = hostInstance->deviceCallback(deviceInstance, &deviceInstance->configuration,
                                               eventCode); /* call host callback function */
    }
#endif
    return status1;
}

static uint8_t USB_HostAllocateDeviceAddress(usb_host_instance_t *hostInstance)
{
    uint8_t address = 0;
    uint8_t addressIndex;
    uint8_t addressBitIndex;
    for (addressIndex = 0; addressIndex < 8; ++addressIndex) /* find the idle address postion byte */
    {
        if (hostInstance->addressBitMap[addressIndex] != 0xFF)
        {
            break;
        }
    }
    if (addressIndex < 8)
    {
        for (addressBitIndex = 0; addressBitIndex < 8; ++addressBitIndex) /* find the idle address position bit */
        {
            if (!(hostInstance->addressBitMap[addressIndex] & (0x01u << addressBitIndex)))
            {
                hostInstance->addressBitMap[addressIndex] |= (0x01u << addressBitIndex); /* set the allocated bit */
                address = addressIndex * 8 + addressBitIndex + 1;                        /* the address minimum is 1 */
                break;
            }
        }
    }
    return address;
}

static void USB_HostReleaseDeviceAddress(usb_host_instance_t *hostInstance, uint8_t address)
{
    USB_HostLock();
    hostInstance->addressBitMap[(uint32_t)(address - 1) >> 3] &=
        (~(0x01u << (((uint32_t)address - 1) & 0x07U))); /* reset the allocated bit */
    USB_HostUnlock();
}

static usb_status_t USB_HostRemoveDeviceInstance(usb_host_handle hostHandle, usb_device_handle deviceHandle)
{
    usb_host_instance_t *hostInstance = (usb_host_instance_t *)hostHandle;
    usb_host_device_instance_t *currentInstance;
    usb_host_device_instance_t *prevInstance;
    if ((hostHandle == NULL) || (deviceHandle == NULL))
    {
        return kStatus_USB_InvalidHandle;
    }

    /* search and remove device instance */
    USB_HostLock();
    prevInstance = (usb_host_device_instance_t *)hostInstance->deviceList;
    if (prevInstance == deviceHandle)
    {
        hostInstance->deviceList = prevInstance->next;
        USB_HostUnlock();
        return kStatus_USB_Success;
    }
    else
    {
        currentInstance = prevInstance->next;
    }

    while (currentInstance != NULL)
    {
        if (currentInstance == deviceHandle)
        {
            prevInstance->next = currentInstance->next;
            USB_HostUnlock();
            return kStatus_USB_Success;
        }
        prevInstance = currentInstance;
        currentInstance = currentInstance->next;
    }

    USB_HostUnlock();
    return kStatus_USB_Success;
}

static void USB_HostReleaseDeviceResource(usb_host_instance_t *hostInstance, usb_host_device_instance_t *deviceInstance)
{
    /* remove device instance from host */
    USB_HostRemoveDeviceInstance(hostInstance, deviceInstance);

    /* release device's address */
    if (deviceInstance->setAddress != 0)
    {
        USB_HostReleaseDeviceAddress(hostInstance, deviceInstance->setAddress);
    }
    else
    {
        if (deviceInstance->allocatedAddress != 0)
        {
            USB_HostReleaseDeviceAddress(hostInstance, deviceInstance->allocatedAddress);
        }
    }

    /* close control pipe */
    if (deviceInstance->controlPipe != NULL)
    {
        USB_HostCancelTransfer(hostInstance, deviceInstance->controlPipe, NULL);
        if (USB_HostClosePipe(hostInstance, deviceInstance->controlPipe) != kStatus_USB_Success)
        {
#ifdef HOST_ECHO
            usb_echo("error when close pipe\r\n");
#endif
        }
        deviceInstance->controlPipe = NULL;
    }

    /* free configuration buffer */
    if (deviceInstance->configurationDesc != NULL)
    {
        USB_OsaMemoryFree(deviceInstance->configurationDesc);
    }

    /* free device instance buffer */
    USB_OsaMemoryFree(deviceInstance);

    /* enable controller attach */
    USB_HostControlBus(hostInstance, kUSB_HostBusEnableAttach);
}

static usb_status_t USB_HostParseDeviceConfigurationDescriptor(usb_device_handle deviceHandle)
{
    usb_host_device_instance_t *deviceInstance = (usb_host_device_instance_t *)deviceHandle;
    uint32_t endPos;
    usb_descriptor_union_t *unionDes;
    usb_host_interface_t *interfaceParse = NULL;
    usb_host_ep_t *epParse;
    uint8_t *buffer;

    if (deviceHandle == NULL)
    {
        return kStatus_USB_InvalidParameter;
    }

    buffer = (uint8_t *)&deviceInstance->configuration;
    /* clear the previous parse result, note: end_pos means buffer index here*/
    for (endPos = 0; endPos < sizeof(usb_host_configuration_t); endPos++)
    {
        buffer[endPos] = 0;
    }
    for (endPos = 0; endPos < USB_HOST_CONFIG_CONFIGURATION_MAX_INTERFACE; ++endPos)
    {
        deviceInstance->interfaceStatus[endPos] = 0;
    }

    /* parse configuration descriptor */
    unionDes = (usb_descriptor_union_t *)deviceInstance->configurationDesc;
    endPos = (uint32_t)(deviceInstance->configurationDesc + deviceInstance->configurationLen);

    if ((unionDes->common.bLength == USB_DESCRIPTOR_LENGTH_CONFIGURE) &&
        (unionDes->common.bDescriptorType == USB_DESCRIPTOR_TYPE_CONFIGURE))
    {
        /* configuration descriptor */
        deviceInstance->configuration.configurationDesc = (usb_descriptor_configuration_t *)unionDes;
        deviceInstance->configuration.configurationExtensionLength = 0;
        deviceInstance->configuration.configurationExtension = NULL;
        deviceInstance->configuration.interfaceCount = 0;
        unionDes = (usb_descriptor_union_t *)((uint32_t)unionDes + unionDes->common.bLength);
        while ((uint32_t)unionDes < endPos)
        {
            if (unionDes->common.bDescriptorType != USB_DESCRIPTOR_TYPE_INTERFACE)
            {
                if (deviceInstance->configuration.configurationExtension == NULL)
                {
                    deviceInstance->configuration.configurationExtension = (uint8_t *)unionDes;
                }
                if ((unionDes->common.bDescriptorType == 0x00) ||
                    (unionDes->common.bLength == 0x00)) /* the descriptor data is wrong */
                {
                    return kStatus_USB_Error;
                }
                deviceInstance->configuration.configurationExtensionLength += unionDes->common.bLength;
                unionDes = (usb_descriptor_union_t *)((uint32_t)unionDes + unionDes->common.bLength);
            }
            else
            {
                break;
            }
        }

        /* interface descriptor */
        deviceInstance->configuration.interfaceCount = 0;
        while ((uint32_t)unionDes < endPos)
        {
            if (unionDes->common.bDescriptorType == USB_DESCRIPTOR_TYPE_INTERFACE)
            {
                if (unionDes->interface.bAlternateSetting == 0x00)
                {
                    if (deviceInstance->configuration.interfaceCount >= USB_HOST_CONFIG_CONFIGURATION_MAX_INTERFACE)
                    {
#ifdef HOST_ECHO
                        usb_echo(
                            "Unsupported Device attached\r\n too many interfaces in one configuration, please increase "
                            "the USB_HOST_CONFIG_CONFIGURATION_MAX_INTERFACE value\n");
#endif
                        return kStatus_USB_Error;
                    }
                    interfaceParse =
                        &deviceInstance->configuration.interfaceList[deviceInstance->configuration.interfaceCount];
                    deviceInstance->configuration.interfaceCount++;
                    interfaceParse->alternateSettingNumber = 0;
                    interfaceParse->epCount = 0;
                    interfaceParse->interfaceDesc = &unionDes->interface;
                    interfaceParse->interfaceExtensionLength = 0;
                    interfaceParse->interfaceExtension = NULL;
                    interfaceParse->interfaceIndex = unionDes->interface.bInterfaceNumber;
                    if (unionDes->common.bLength == 0x00) /* the descriptor data is wrong */
                    {
                        return kStatus_USB_Error;
                    }
                    unionDes = (usb_descriptor_union_t *)((uint32_t)unionDes + unionDes->common.bLength);
                    while ((uint32_t)unionDes < endPos)
                    {
                        if ((unionDes->common.bDescriptorType != USB_DESCRIPTOR_TYPE_INTERFACE) &&
                            (unionDes->common.bDescriptorType != USB_DESCRIPTOR_TYPE_ENDPOINT))
                        {
                            if (interfaceParse->interfaceExtension == NULL)
                            {
                                interfaceParse->interfaceExtension = (uint8_t *)unionDes;
                            }
                            if ((unionDes->common.bDescriptorType == 0x00) ||
                                (unionDes->common.bLength == 0x00)) /* the descriptor data is wrong */
                            {
                                return kStatus_USB_Error;
                            }
                            interfaceParse->interfaceExtensionLength += unionDes->common.bLength;
                            unionDes = (usb_descriptor_union_t *)((uint32_t)unionDes + unionDes->common.bLength);
                        }
                        else
                        {
                            break;
                        }
                    }

                    /* endpoint descriptor */
                    if (interfaceParse->interfaceDesc->bNumEndpoints != 0)
                    {
                        if ((unionDes->common.bDescriptorType != USB_DESCRIPTOR_TYPE_ENDPOINT) ||
                            (interfaceParse->interfaceDesc->bNumEndpoints > USB_HOST_CONFIG_INTERFACE_MAX_EP))
                        {
#ifdef HOST_ECHO
                            usb_echo("interface descriptor error\n");
#endif
                            return kStatus_USB_Error;
                        }
                        for (; interfaceParse->epCount < interfaceParse->interfaceDesc->bNumEndpoints;
                             (interfaceParse->epCount)++)
                        {
                            if (((uint32_t)unionDes >= endPos) ||
                                (unionDes->common.bDescriptorType != USB_DESCRIPTOR_TYPE_ENDPOINT))
                            {
#ifdef HOST_ECHO
                                usb_echo("endpoint descriptor error\n");
#endif
                                return kStatus_USB_Error;
                            }
                            epParse = (usb_host_ep_t *)&interfaceParse->epList[interfaceParse->epCount];
                            epParse->epDesc = (usb_descriptor_endpoint_t *)unionDes;
                            epParse->epExtensionLength = 0;
                            epParse->epExtension = NULL;
                            if (unionDes->common.bLength == 0x00) /* the descriptor data is wrong */
                            {
                                return kStatus_USB_Error;
                            }
                            unionDes = (usb_descriptor_union_t *)((uint32_t)unionDes + unionDes->common.bLength);
                            while ((uint32_t)unionDes < endPos)
                            {
                                if ((unionDes->common.bDescriptorType != USB_DESCRIPTOR_TYPE_ENDPOINT) &&
                                    (unionDes->common.bDescriptorType != USB_DESCRIPTOR_TYPE_INTERFACE))
                                {
                                    if (epParse->epExtension == NULL)
                                    {
                                        epParse->epExtension = (uint8_t *)unionDes;
                                    }
                                    if ((unionDes->common.bDescriptorType == 0x00) ||
                                        (unionDes->common.bLength == 0x00)) /* the descriptor data is wrong */
                                    {
                                        return kStatus_USB_Error;
                                    }
                                    epParse->epExtensionLength += unionDes->common.bLength;
                                    unionDes =
                                        (usb_descriptor_union_t *)((uint32_t)unionDes + unionDes->common.bLength);
                                }
                                else
                                {
                                    break;
                                }
                            }
                        }
                    }
                }
                else
                {
                    if (interfaceParse == NULL)
                    {
                        return kStatus_USB_Error; /* in normal situation this cannot reach */
                    }
                    interfaceParse->alternateSettingNumber++;
                    if (interfaceParse->interfaceExtension == NULL)
                    {
                        interfaceParse->interfaceExtension = (uint8_t *)unionDes;
                    }
                    if (unionDes->common.bLength == 0x00) /* the descriptor data is wrong */
                    {
                        return kStatus_USB_Error;
                    }
                    interfaceParse->interfaceExtensionLength += unionDes->common.bLength;
                    unionDes = (usb_descriptor_union_t *)((uint32_t)unionDes + unionDes->common.bLength);
                    while ((uint32_t)unionDes < endPos)
                    {
                        if (unionDes->common.bDescriptorType != USB_DESCRIPTOR_TYPE_INTERFACE)
                        {
                            if ((unionDes->common.bDescriptorType == 0x00) ||
                                (unionDes->common.bLength == 0x00)) /* the descriptor data is wrong */
                            {
                                return kStatus_USB_Error;
                            }
                            interfaceParse->interfaceExtensionLength += unionDes->common.bLength;
                            unionDes = (usb_descriptor_union_t *)((uint32_t)unionDes + unionDes->common.bLength);
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }
            else
            {
                return kStatus_USB_Error;
            }
        }
    }

    for (endPos = 0; endPos < deviceInstance->configuration.interfaceCount; ++endPos)
    {
        deviceInstance->interfaceStatus[endPos] = kStatus_interface_Attached;
    }

    return kStatus_USB_Success;
}

usb_status_t USB_HostAttachDevice(usb_host_handle hostHandle,
                                  uint8_t speed,
                                  uint8_t hubNumber,
                                  uint8_t portNumber,
                                  uint8_t level,
                                  usb_device_handle *deviceHandle)
{
    usb_host_instance_t *hostInstance = (usb_host_instance_t *)hostHandle;
    usb_host_device_instance_t *newInstance;
#if ((defined USB_HOST_CONFIG_HUB) && (USB_HOST_CONFIG_HUB))
    usb_host_device_instance_t *currentInstance;
#endif
    uint8_t address;
    usb_host_pipe_init_t pipeInit;

    if (hostHandle == NULL)
    {
        return kStatus_USB_InvalidHandle;
    }

/* check whether is the device attached? */
#if ((defined USB_HOST_CONFIG_HUB) && (USB_HOST_CONFIG_HUB))
    currentInstance = (usb_host_device_instance_t *)hostInstance->deviceList;
    while (currentInstance != NULL)
    {
        if ((currentInstance->hubNumber == hubNumber) && (currentInstance->portNumber == portNumber))
        {
            *deviceHandle = NULL;
#ifdef HOST_ECHO
            usb_echo("device has attached\r\n");
#endif
            return kStatus_USB_Busy;
        }
        else
        {
            currentInstance = currentInstance->next;
        }
    }
#else
    if (hostInstance->deviceList != NULL)
    {
        *deviceHandle = NULL;
        usb_echo("device has attached\r\n");
        return kStatus_USB_Busy;
    }
#endif

    /* Allocate new device instance */
    newInstance = (usb_host_device_instance_t *)USB_OsaMemoryAllocate(sizeof(usb_host_device_instance_t));
    if (newInstance == NULL)
    {
#ifdef HOST_ECHO
        usb_echo("allocate dev instance fail\r\n");
#endif
        return kStatus_USB_AllocFail;
    }

    /* new instance fields init */
    newInstance->hostHandle = hostHandle;
    newInstance->speed = speed;
    newInstance->stallRetries = USB_HOST_CONFIG_ENUMERATION_MAX_STALL_RETRIES;
    newInstance->enumRetries = USB_HOST_CONFIG_ENUMERATION_MAX_RETRIES;
    newInstance->setAddress = 0;
    newInstance->deviceAttachState = kStatus_device_Attached;

#if ((defined USB_HOST_CONFIG_HUB) && (USB_HOST_CONFIG_HUB))
    newInstance->hubNumber = hubNumber;
    newInstance->portNumber = portNumber;
    newInstance->level = level;

    if ((speed != USB_SPEED_HIGH) && (level > 1))
    {
        newInstance->hsHubNumber = USB_HostHubGetHsHubNumber(hubNumber);
        newInstance->hsHubPort = USB_HostHubGetHsHubPort(hubNumber, portNumber);
    }
    else
    {
        newInstance->hsHubNumber = hubNumber;
        newInstance->hsHubPort = portNumber;
    }
#endif /* USB_HOST_CONFIG_HUB */

    USB_HostLock();
    /* allocate address && insert to the dev list */
    address = USB_HostAllocateDeviceAddress(hostInstance);
    if (address == 0)
    {
#ifdef HOST_ECHO
        usb_echo("allocate address fail\r\n");
#endif
        USB_HostUnlock();
        USB_OsaMemoryFree(newInstance);
        return kStatus_USB_Error;
    }
    newInstance->allocatedAddress = address;

    newInstance->next = (usb_host_device_instance_t *)hostInstance->deviceList;
    hostInstance->deviceList = newInstance;
    newInstance->state = kStatus_DEV_Initial;
    USB_HostUnlock();

    /* open control pipe */
    pipeInit.devInstance = newInstance;
    pipeInit.pipeType = USB_ENDPOINT_CONTROL;
    pipeInit.direction = 0;
    pipeInit.endpointAddress = 0;
    pipeInit.interval = 0;
    pipeInit.maxPacketSize = 8;
    pipeInit.numberPerUframe = 0;
    pipeInit.nakCount = USB_HOST_CONFIG_MAX_NAK;
    if (USB_HostOpenPipe(hostHandle, &newInstance->controlPipe, &pipeInit) != kStatus_USB_Success)
    {
        /* don't need release resource, resource is released when detach */
        *deviceHandle = newInstance;
        return kStatus_USB_Error;
    }

    /* start enumeration */
    newInstance->state = kStatus_DEV_GetDes8;
    USB_HostProcessState(newInstance); /* process enumeration state machine */

    *deviceHandle = newInstance;
    return kStatus_USB_Success;
}

usb_status_t USB_HostDetachDevice(usb_host_handle hostHandle, uint8_t hubNumber, uint8_t portNumber)
{
    usb_host_device_instance_t *deviceInstance;
    usb_host_instance_t *hostInstance = (usb_host_instance_t *)hostHandle;

    if (hostHandle == NULL)
    {
        return kStatus_USB_InvalidHandle;
    }

    USB_HostLock();
/* search for device instance handle */
#if ((defined USB_HOST_CONFIG_HUB) && (USB_HOST_CONFIG_HUB))
    deviceInstance = (usb_host_device_instance_t *)hostInstance->deviceList;
    while (deviceInstance != NULL)
    {
        if ((deviceInstance->hubNumber == hubNumber) && (deviceInstance->portNumber == portNumber))
        {
            break;
        }
        deviceInstance = deviceInstance->next;
    }
#else
    deviceInstance = (usb_host_device_instance_t *)hostInstance->deviceList;
#endif
    USB_HostUnlock();
    if (deviceInstance != NULL)
    {
        return USB_HostDetachDeviceInternal(hostHandle, deviceInstance); /* device instance detach */
    }
    return kStatus_USB_Success;
}

usb_status_t USB_HostDetachDeviceInternal(usb_host_handle hostHandle, usb_device_handle deviceHandle)
{
    usb_host_device_instance_t *deviceInstance = (usb_host_device_instance_t *)deviceHandle;
    usb_host_instance_t *hostInstance = (usb_host_instance_t *)hostHandle;
    if ((hostHandle == NULL) || (deviceHandle == NULL))
    {
        return kStatus_USB_InvalidHandle;
    }

    deviceInstance->deviceAttachState = kStatus_device_Detached; /* mark the device is detached from host */

    if (deviceInstance->state >= kStatus_DEV_Initial) /* device instance is valid */
    {
        /* detach internally */
        if (deviceInstance->state < kStatus_DEV_AppUsed) /* enumeration is not done */
        {
            if (deviceInstance->controlPipe != NULL)
            {
                USB_HostCancelTransfer(hostInstance, deviceInstance->controlPipe, NULL);
            }
            USB_HostReleaseDeviceResource(hostInstance, deviceInstance);
        }
        else /* enumeration has be done and notifed application */
        {
            USB_HostNotifyDevice(deviceInstance, kUSB_HostEventDetach); /* notify application device detach */
        }
    }

    return kStatus_USB_Success;
}

uint8_t USB_HostGetDeviceAttachState(usb_device_handle deviceHandle)
{
    return deviceHandle ? ((usb_host_device_instance_t *)deviceHandle)->deviceAttachState : 0x0;
}

usb_status_t USB_HostValidateDevice(usb_host_handle hostHandle, usb_device_handle deviceHandle)
{
    usb_host_device_instance_t *searchDev;
    usb_host_instance_t *hostInstance = (usb_host_instance_t *)hostHandle;

    if (deviceHandle == NULL)
    {
        return kStatus_USB_InvalidParameter;
    }
    USB_HostLock();
    /* search for the device */
    searchDev = (usb_host_device_instance_t *)((usb_host_instance_t *)hostHandle)->deviceList;
    while ((searchDev != NULL) && ((usb_device_handle)searchDev != deviceHandle))
    {
        searchDev = searchDev->next;
    }

    if (searchDev)
    {
        USB_HostUnlock();
        return kStatus_USB_Success;
    }
    USB_HostUnlock();
    return kStatus_USB_Error;
}

static usb_status_t USB_HostControlBus(usb_host_handle hostHandle, uint8_t controlType)
{
    usb_status_t status = kStatus_USB_Success;
    usb_host_instance_t *hostInstance = (usb_host_instance_t *)hostHandle;

    if (hostHandle == NULL)
    {
        return kStatus_USB_InvalidHandle;
    }

    status = hostInstance->controllerTable->controllerIoctl(hostInstance->controllerHandle, kUSB_HostBusControl,
                                                            &controlType);

    return status;
}

usb_status_t USB_HostOpenDeviceInterface(usb_device_handle deviceHandle, usb_host_interface_handle interfaceHandle)
{
    usb_host_device_instance_t *deviceInstance = (usb_host_device_instance_t *)deviceHandle;
    usb_host_instance_t *hostInstance = NULL;
    uint8_t interfaceIndex;

    if ((deviceHandle == NULL) || (interfaceHandle == NULL))
    {
        return kStatus_USB_InvalidHandle;
    }

    hostInstance = (usb_host_instance_t *)deviceInstance->hostHandle;
    USB_HostLock();

    /* search interface and set the interface as opened */
    for (interfaceIndex = 0; interfaceIndex < deviceInstance->configuration.interfaceCount; ++interfaceIndex)
    {
        if (&deviceInstance->configuration.interfaceList[interfaceIndex] == interfaceHandle)
        {
            deviceInstance->interfaceStatus[interfaceIndex] = kStatus_interface_Opened;
            break;
        }
    }
    USB_HostUnlock();

    return kStatus_USB_Success;
}

usb_status_t USB_HostCloseDeviceInterface(usb_device_handle deviceHandle, usb_host_interface_handle interfaceHandle)
{
    usb_host_device_instance_t *deviceInstance = (usb_host_device_instance_t *)deviceHandle;
    usb_host_instance_t *hostInstance = NULL;
    uint8_t interfaceIndex;
    uint8_t removeLabel = 1;

    if (deviceHandle == NULL)
    {
        return kStatus_USB_InvalidHandle;
    }

    hostInstance = (usb_host_instance_t *)deviceInstance->hostHandle;
    USB_HostLock();
    if (interfaceHandle != NULL)
    {
        /* search interface and set the interface as detached */
        for (interfaceIndex = 0; interfaceIndex < deviceInstance->configuration.interfaceCount; ++interfaceIndex)
        {
            if (&deviceInstance->configuration.interfaceList[interfaceIndex] == interfaceHandle)
            {
                deviceInstance->interfaceStatus[interfaceIndex] = kStatus_interface_Detached;
                break;
            }
        }
    }

    if (deviceInstance->deviceAttachState == kStatus_device_Detached) /* device is removed from host */
    {
        removeLabel = 1;
        /* check all the interfaces of the device are not opened */
        for (interfaceIndex = 0; interfaceIndex < deviceInstance->configuration.interfaceCount; ++interfaceIndex)
        {
            if (deviceInstance->interfaceStatus[interfaceIndex] == kStatus_interface_Opened)
            {
                removeLabel = 0;
                break;
            }
        }
        if ((removeLabel == 1) && (deviceInstance->deviceAttachState != kStatus_device_Released))
        {
            deviceInstance->deviceAttachState = kStatus_device_Released;
            removeLabel = 2;
        }
        USB_HostUnlock();

        if (removeLabel == 2)
        {
            USB_HostReleaseDeviceResource((usb_host_instance_t *)deviceInstance->hostHandle,
                                          deviceInstance); /* release device resource */
        }
    }
    else
    {
        USB_HostUnlock();
    }

    return kStatus_USB_Success;
}

usb_status_t USB_HostRemoveDevice(usb_host_handle hostHandle, usb_device_handle deviceHandle)
{
    usb_host_instance_t *hostInstance = (usb_host_instance_t *)hostHandle;
    usb_host_device_instance_t *deviceInstance = (usb_host_device_instance_t *)deviceHandle;
    uint8_t interfaceIndex = 0;
#if ((defined USB_HOST_CONFIG_HUB) && (USB_HOST_CONFIG_HUB))
    uint8_t level = 0;
    uint8_t devHubNo;
    uint8_t devPortNo;
#endif

    if ((hostHandle == NULL) || (deviceHandle == NULL))
    {
        return kStatus_USB_InvalidHandle;
    }
    if (deviceInstance->hostHandle != hostHandle)
    {
        return kStatus_USB_InvalidParameter;
    }

    if (USB_HostValidateDevice(hostInstance, deviceInstance) == kStatus_USB_Success) /* device is valid */
    {
#if ((defined USB_HOST_CONFIG_HUB) && (USB_HOST_CONFIG_HUB))
        devHubNo = deviceInstance->hubNumber;
        devPortNo = deviceInstance->portNumber;
        level = deviceInstance->level;
#endif

        deviceInstance->deviceAttachState = kStatus_device_Detached;
        if (deviceInstance->state >= kStatus_DEV_Initial) /* device is valid */
        {
            if (deviceInstance->state < kStatus_DEV_AppUsed) /* enumeraion is not done or application don't use */
            {
                /* detach internally */
                USB_HostDetachDeviceInternal(hostHandle, deviceHandle);
            }
            else /* application use the device */
            {
                for (interfaceIndex = 0; interfaceIndex < deviceInstance->configuration.interfaceCount;
                     ++interfaceIndex)
                {
                    if (deviceInstance->interfaceStatus[interfaceIndex] == kStatus_interface_Opened)
                    {
#ifdef HOST_ECHO
                        usb_echo("error: there is class instance that is not deinited\r\n");
#endif
                        break;
                    }
                }
                USB_HostReleaseDeviceResource(hostInstance, deviceInstance); /* release resource */
            }
        }

#if ((defined USB_HOST_CONFIG_HUB) && (USB_HOST_CONFIG_HUB))
        if (level == 1)
        {
            USB_HostControlBus(hostHandle, kUSB_HostBusReset);   /* reset controller port */
            USB_HostControlBus(hostHandle, kUSB_HostBusRestart); /* restart controller port */
        }
        else
        {
            USB_HostHubRemovePort(devHubNo, devPortNo); /* reset hub port */
        }
#else
        USB_HostControlBus(hostHandle, kUSB_HostBusReset);   /* reset controller port */
        USB_HostControlBus(hostHandle, kUSB_HostBusRestart); /* restart controller port */
#endif /* USB_HOST_CONFIG_HUB */
    }

    return kStatus_USB_Success;
}
