/*!
    \file  usbh_std.c
    \brief USB 2.0 standard function definition

    \version 2017-02-10, V1.0.0, firmware for GD32F30x
    \version 2018-10-10, V1.1.0, firmware for GD32F30x
    \version 2018-12-25, V2.0.0, firmware for GD32F30x
*/

/*
    Copyright (c) 2018, GigaDevice 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:

    1. Redistributions of source code must retain the above copyright notice, this 
       list of conditions and the following disclaimer.
    2. 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.
    3. Neither the name of the copyright holder 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 "usbh_core.h"
#include "usbh_usr.h"
#include "usbh_std.h"
#include "usbh_ctrl.h"

uint8_t local_buffer[64];
uint8_t usbh_cfg_desc[512];
uint8_t enum_polling_handle_flag = 0U;

static void enum_idle_handle                       (usb_core_handle_struct *pudev, usbh_host_struct *puhost, usbh_state_handle_struct *pustate);
static void enum_get_full_dev_desc_handle          (usb_core_handle_struct *pudev, usbh_host_struct *puhost, usbh_state_handle_struct *pustate);
static void enum_set_addr_handle                   (usb_core_handle_struct *pudev, usbh_host_struct *puhost, usbh_state_handle_struct *pustate);
static void enum_get_cfg_desc_handle               (usb_core_handle_struct *pudev, usbh_host_struct *puhost, usbh_state_handle_struct *pustate);
static void enum_get_full_cfg_desc_handle          (usb_core_handle_struct *pudev, usbh_host_struct *puhost, usbh_state_handle_struct *pustate);
static void enum_get_mfc_string_desc_handle        (usb_core_handle_struct *pudev, usbh_host_struct *puhost, usbh_state_handle_struct *pustate);
static void enum_get_product_string_desc_handle    (usb_core_handle_struct *pudev, usbh_host_struct *puhost, usbh_state_handle_struct *pustate);
static void enum_get_serialnum_string_desc_handle  (usb_core_handle_struct *pudev, usbh_host_struct *puhost, usbh_state_handle_struct *pustate);
static void enum_set_configuration_handle          (usb_core_handle_struct *pudev, usbh_host_struct *puhost, usbh_state_handle_struct *pustate);
static void enum_dev_configured_handle             (usb_core_handle_struct *pudev, usbh_host_struct *puhost, usbh_state_handle_struct *pustate);

/* the enumeration state handle function array */
void (*enum_state_handle[]) (usb_core_handle_struct *pudev, usbh_host_struct *puhost, usbh_state_handle_struct *pustate) =
{
    enum_idle_handle,
    enum_set_addr_handle,
    enum_get_full_dev_desc_handle,
    enum_get_cfg_desc_handle,
    enum_get_full_cfg_desc_handle,
    enum_get_mfc_string_desc_handle,
    enum_get_product_string_desc_handle,
    enum_get_serialnum_string_desc_handle,
    enum_set_configuration_handle,
    enum_dev_configured_handle,
};

/* the enumeration state handle table */
state_table_struct enum_handle_table[ENUM_HANDLE_TABLE_SIZE] = 
{
    /* the current state             the current event                     the next state                   the event function */
    {ENUM_IDLE,                      ENUM_EVENT_SET_ADDR,                  ENUM_SET_ADDR,                   only_state_move     },
    {ENUM_SET_ADDR,                  ENUN_EVENT_GET_FULL_DEV_DESC,         ENUM_GET_FULL_DEV_DESC,          only_state_move     },
    {ENUM_GET_FULL_DEV_DESC,         ENUN_EVENT_GET_CFG_DESC,              ENUM_GET_CFG_DESC,               only_state_move     },
    {ENUM_GET_CFG_DESC,              ENUN_EVENT_GET_FULL_CFG_DESC,         ENUM_GET_FULL_CFG_DESC,          only_state_move     },
    {ENUM_GET_FULL_CFG_DESC,         ENUN_EVENT_GET_MFC_STRING_DESC,       ENUM_GET_MFC_STRING_DESC,        only_state_move     },
    {ENUM_GET_MFC_STRING_DESC,       ENUN_EVENT_GET_PRODUCT_STRING_DESC,   ENUM_GET_PRODUCT_STRING_DESC,    only_state_move     },
    {ENUM_GET_PRODUCT_STRING_DESC,   ENUN_EVENT_GET_SERIALNUM_STRING_DESC, ENUM_GET_SERIALNUM_STRING_DESC,  only_state_move     },
    {ENUM_GET_SERIALNUM_STRING_DESC, ENUN_EVENT_SET_CONFIGURATION,         ENUM_SET_CONFIGURATION,          only_state_move     },
    {ENUM_SET_CONFIGURATION,         ENUN_EVENT_DEV_CONFIGURED,            ENUM_DEV_CONFIGURED,             only_state_move     },
    {ENUM_DEV_CONFIGURED,            GO_TO_UP_STATE_EVENT,                 UP_STATE,                        goto_up_state_fun   },
};

/*!
    \brief      the polling function of enumeration state
    \param[in]  pudev: pointer to USB device
    \param[in]  puhost: pointer to USB host
    \param[in]  pustate: pointer to USB state driver
    \param[out] none
    \retval     usb host status
*/
usbh_status_enum enum_state_polling_fun (usb_core_handle_struct *pudev, usbh_host_struct *puhost, void *pustate)
{
    usbh_status_enum exe_state = USBH_BUSY;
    usbh_state_handle_struct *p_state;
    p_state = (usbh_state_handle_struct *)pustate;

    if (0U == enum_polling_handle_flag) {
        enum_polling_handle_flag = 1U;
        scd_table_push(p_state);
        scd_state_move(p_state, ENUM_IDLE);
    }
    
    /* start the enumeration state handle */
    scd_begin(p_state,ENUM_FSM_ID);
    
    if (0 == p_state->usbh_current_state_stack_top) {
        enum_state_handle[p_state->usbh_current_state](pudev, puhost, p_state);
    } else {
        enum_state_handle[p_state->stack[1].state](pudev, puhost, p_state);
    }

    /* determine the enumeration whether to complete  */
    if (ENUM_DEV_CONFIGURED == puhost->usbh_backup_state.enum_backup_state) {
        puhost->usbh_backup_state.enum_backup_state = ENUM_IDLE;
        enum_polling_handle_flag = 0U;
        exe_state = USBH_OK;
    }

    return exe_state;
}

/*!
    \brief      the handle function of ENUM_IDLE state
    \param[in]  pudev: pointer to USB device
    \param[in]  puhost: pointer to USB host
    \param[in]  pustate: pointer to USB state driver
    \param[out] none
    \retval     none
*/
static void enum_idle_handle (usb_core_handle_struct *pudev, usbh_host_struct *puhost, usbh_state_handle_struct *pustate)
{
    puhost->usbh_backup_state.enum_backup_state = ENUM_IDLE;

    if (CTRL_IDLE == puhost->usbh_backup_state.ctrl_backup_state) {
        usbh_enum_desc_get(pudev, 
                           puhost, 
                           pudev->host.rx_buffer, 
                           USB_REQTYPE_DEVICE | USB_STANDARD_REQ, 
                           USB_DEVDESC, 
                           8U);
        if ((void *)0 != pudev->mdelay) {
            pudev->mdelay(100U);
        }
    }

    if (USBH_OK == ctrl_state_polling_fun(pudev, puhost, pustate)) {
        usbh_device_desc_parse(&puhost->device.dev_desc, pudev->host.rx_buffer, 8U);
        puhost->control.ep0_size = puhost->device.dev_desc.bMaxPacketSize0;

        /* issue reset */
        usb_port_reset(pudev);

        /* modify control channels configuration for maxpacket size */
        usbh_channel_modify (pudev,
                             puhost->control.hc_out_num,
                             0U,
                             0U,
                             0U,
                             (uint16_t)puhost->control.ep0_size);

        usbh_channel_modify (pudev,
                             puhost->control.hc_in_num,
                             0U,
                             0U,
                             0U,
                             (uint16_t)puhost->control.ep0_size);

        scd_event_handle(pudev, 
                         puhost, 
                         pustate, 
                         ENUM_EVENT_SET_ADDR, 
                         pustate->usbh_current_state);
    }
}

/*!
    \brief      the handle function of ENUM_GET_FULL_DEV_DESC state
    \param[in]  pudev: pointer to USB device
    \param[in]  puhost: pointer to USB host
    \param[in]  pustate: pointer to USB state driver
    \param[out] none
    \retval     none
*/
static void enum_get_full_dev_desc_handle (usb_core_handle_struct *pudev, 
                                           usbh_host_struct *puhost, 
                                           usbh_state_handle_struct *pustate)
{
    puhost->usbh_backup_state.enum_backup_state = ENUM_GET_FULL_DEV_DESC;

    if (CTRL_IDLE == puhost->usbh_backup_state.ctrl_backup_state) {
        usbh_enum_desc_get(pudev, 
                           puhost, 
                           pudev->host.rx_buffer, 
                           USB_REQTYPE_DEVICE | USB_STANDARD_REQ, 
                           USB_DEVDESC, 
                           USB_DEVDESC_SIZE);
    }
    
    if(USBH_OK == ctrl_state_polling_fun(pudev, puhost, pustate)){
        usbh_device_desc_parse(&puhost->device.dev_desc, pudev->host.rx_buffer, USB_DEVDESC_SIZE);
        puhost->usr_cb->device_desc_available(&puhost->device.dev_desc);

        scd_event_handle(pudev, 
                         puhost, 
                         pustate, 
                         ENUN_EVENT_GET_CFG_DESC, 
                         pustate->usbh_current_state);
    }
}

/*!
    \brief      the handle function of ENUM_SET_ADDR state
    \param[in]  pudev: pointer to USB device
    \param[in]  puhost: pointer to USB host
    \param[in]  pustate: pointer to USB state driver
    \param[out] none
    \retval     none
*/
static void enum_set_addr_handle (usb_core_handle_struct *pudev, 
                                  usbh_host_struct *puhost, 
                                  usbh_state_handle_struct *pustate)
{
    puhost->usbh_backup_state.enum_backup_state = ENUM_SET_ADDR;

    if (CTRL_IDLE == puhost->usbh_backup_state.ctrl_backup_state) {
        usbh_enum_addr_set(pudev, puhost,USBH_DEVICE_ADDRESS);
        if ((void *)0 != pudev->mdelay) {
            pudev->mdelay(100U);
        }
    }
    
    if (USBH_OK == ctrl_state_polling_fun(pudev, puhost, pustate)) {
        if ((void *)0 != pudev->mdelay) {
            pudev->mdelay(2U);
        }
        puhost->device.address = USBH_DEVICE_ADDRESS;

        /* user callback for device address assigned */
        puhost->usr_cb->device_address_set();

        /* modify control channels to update device address */
        usbh_channel_modify (pudev,
                             puhost->control.hc_in_num,
                             puhost->device.address,
                             0U,
                             0U,
                             0U);

        usbh_channel_modify (pudev,
                             puhost->control.hc_out_num,
                             puhost->device.address,
                             0U,
                             0U,
                             0U);

        scd_event_handle(pudev, 
                         puhost, 
                         pustate, 
                         ENUN_EVENT_GET_FULL_DEV_DESC, 
                         pustate->usbh_current_state);
    }
}

/*!
    \brief      the handle function of ENUM_GET_CFG_DESC state
    \param[in]  pudev: pointer to USB device
    \param[in]  puhost: pointer to USB host
    \param[in]  pustate: pointer to USB state driver
    \param[out] none
    \retval     none
*/
static void enum_get_cfg_desc_handle (usb_core_handle_struct *pudev, 
                                      usbh_host_struct *puhost, 
                                      usbh_state_handle_struct *pustate)
{
    uint16_t index = 0U;

    puhost->usbh_backup_state.enum_backup_state = ENUM_GET_CFG_DESC;

    if (CTRL_IDLE == puhost->usbh_backup_state.ctrl_backup_state) {
        usbh_enum_desc_get(pudev, 
                           puhost, 
                           pudev->host.rx_buffer, 
                           USB_REQTYPE_DEVICE | USB_STANDARD_REQ, 
                           USB_CFGDESC, 
                           USB_CFGDESC_SIZE);
    }
    
    if (USBH_OK == ctrl_state_polling_fun(pudev, puhost, pustate)) {
        /* save configuration descriptor for class parsing usage */
        for (; index < USB_CFGDESC_SIZE; index ++) {
            usbh_cfg_desc[index] = pudev->host.rx_buffer[index];
        }

        /* commands successfully sent and response received */
        usbh_cfg_desc_parse (&puhost->device.cfg_desc,
                              puhost->device.itf_desc,
                              puhost->device.ep_desc, 
                              pudev->host.rx_buffer,
                              USB_CFGDESC_SIZE);

        scd_event_handle(pudev, 
                         puhost, 
                         pustate, 
                         ENUN_EVENT_GET_FULL_CFG_DESC, 
                         pustate->usbh_current_state);
    }
}

/*!
    \brief      the handle function of ENUM_GET_FULL_CFG_DESC state
    \param[in]  pudev: pointer to USB device
    \param[in]  puhost: pointer to USB host
    \param[in]  pustate: pointer to USB state driver
    \param[out] none
    \retval     none
*/
static void enum_get_full_cfg_desc_handle (usb_core_handle_struct *pudev, 
                                           usbh_host_struct *puhost, 
                                           usbh_state_handle_struct *pustate)
{

    uint16_t index = 0U;

    puhost->usbh_backup_state.enum_backup_state = ENUM_GET_FULL_CFG_DESC;

    if (CTRL_IDLE == puhost->usbh_backup_state.ctrl_backup_state) {
        usbh_enum_desc_get (pudev, puhost, pudev->host.rx_buffer, 
                            USB_REQTYPE_DEVICE | USB_STANDARD_REQ, 
                            USB_CFGDESC, puhost->device.cfg_desc.wTotalLength);
    }

    
    if (USBH_OK == ctrl_state_polling_fun(pudev, puhost, pustate)) {
        /* save configuration descriptor for class parsing usage */
        for (; index < puhost->device.cfg_desc.wTotalLength; index ++) {
            usbh_cfg_desc[index] = pudev->host.rx_buffer[index];
        }

        /* commands successfully sent and response received */
        usbh_cfg_desc_parse (&puhost->device.cfg_desc, 
                              puhost->device.itf_desc, 
                              puhost->device.ep_desc, 
                              pudev->host.rx_buffer, 
                              puhost->device.cfg_desc.wTotalLength);

        /* User callback for configuration descriptors available */
        puhost->usr_cb->configuration_desc_available(&puhost->device.cfg_desc,
                                                      puhost->device.itf_desc,
                                                      puhost->device.ep_desc[0]);

        scd_event_handle(pudev, 
                         puhost, 
                         pustate, 
                         ENUN_EVENT_GET_MFC_STRING_DESC, 
                         pustate->usbh_current_state);
    }
}

/*!
    \brief      the handle function of ENUM_GET_MFC_STRING_DESC state
    \param[in]  pudev: pointer to USB device
    \param[in]  puhost: pointer to USB host
    \param[in]  pustate: pointer to USB state driver
    \param[out] none
    \retval     none
*/
static void enum_get_mfc_string_desc_handle (usb_core_handle_struct *pudev, 
                                             usbh_host_struct *puhost, 
                                             usbh_state_handle_struct *pustate)
{
    puhost->usbh_backup_state.enum_backup_state = ENUM_GET_MFC_STRING_DESC;

    if (0U != puhost->device.dev_desc.iManufacturer) {
        if(CTRL_IDLE == puhost->usbh_backup_state.ctrl_backup_state) {
            usbh_enum_desc_get(pudev, 
                               puhost, 
                               pudev->host.rx_buffer, 
                               USB_REQTYPE_DEVICE | USB_STANDARD_REQ, 
                               USB_STRDESC | puhost->device.dev_desc.iManufacturer,
                               0xffU);
        }
        
        if (USBH_OK == ctrl_state_polling_fun(pudev, puhost, pustate)) {
            usbh_string_desc_parse(pudev->host.rx_buffer, local_buffer, 0xffU);
            puhost->usr_cb->manufacturer_string(local_buffer);
            
            scd_event_handle(pudev, 
                             puhost, 
                             pustate, 
                             ENUN_EVENT_GET_PRODUCT_STRING_DESC, 
                             pustate->usbh_current_state);
        }
    } else {
        puhost->usr_cb->manufacturer_string("N/A");
        scd_state_move((usbh_state_handle_struct *)pustate, ENUM_GET_PRODUCT_STRING_DESC);
    }
}

/*!
    \brief      the handle function of ENUM_GET_PRODUCT_STRING_DESC state
    \param[in]  pudev: pointer to USB device
    \param[in]  puhost: pointer to USB host
    \param[in]  pustate: pointer to USB state driver
    \param[out] none
    \retval     none
*/
static void enum_get_product_string_desc_handle (usb_core_handle_struct *pudev, 
                                                 usbh_host_struct *puhost, 
                                                 usbh_state_handle_struct *pustate)
{
    puhost->usbh_backup_state.enum_backup_state = ENUM_GET_PRODUCT_STRING_DESC;

    if (0U != puhost->device.dev_desc.iProduct) {
        if (CTRL_IDLE == puhost->usbh_backup_state.ctrl_backup_state) {
            usbh_enum_desc_get(pudev, 
                               puhost, 
                               pudev->host.rx_buffer,
                               USB_REQTYPE_DEVICE | USB_STANDARD_REQ, 
                               USB_STRDESC | puhost->device.dev_desc.iProduct,
                               0xffU);
        }
        
        if (USBH_OK == ctrl_state_polling_fun(pudev, puhost, pustate)) {
            usbh_string_desc_parse(pudev->host.rx_buffer, local_buffer, 0xffU);
          
            /* user callback for product string */
            puhost->usr_cb->product_string(local_buffer);
          
            scd_event_handle(pudev, 
                             puhost, 
                             pustate, 
                             ENUN_EVENT_GET_SERIALNUM_STRING_DESC, 
                             pustate->usbh_current_state);
        }
    } else {
        puhost->usr_cb->product_string("N/A");
        scd_event_handle(pudev, 
                         puhost, 
                         pustate, 
                         ENUN_EVENT_GET_SERIALNUM_STRING_DESC, 
                         pustate->usbh_current_state);
    }
}

/*!
    \brief      the handle function of ENUM_GET_SERIALNUM_STRING_DESC state
    \param[in]  pudev: pointer to USB device
    \param[in]  puhost: pointer to USB host
    \param[in]  pustate: pointer to USB state driver
    \param[out] none
    \retval     none
*/
static void enum_get_serialnum_string_desc_handle (usb_core_handle_struct *pudev, 
                                                   usbh_host_struct *puhost, 
                                                   usbh_state_handle_struct *pustate)
{
    puhost->usbh_backup_state.enum_backup_state = ENUM_GET_SERIALNUM_STRING_DESC;

    if (0U != puhost->device.dev_desc.iSerialNumber) {
        if (CTRL_IDLE == puhost->usbh_backup_state.ctrl_backup_state) {
            usbh_enum_desc_get(pudev, 
                               puhost, 
                               pudev->host.rx_buffer, 
                               USB_REQTYPE_DEVICE | USB_STANDARD_REQ, 
                               USB_STRDESC | puhost->device.dev_desc.iSerialNumber,
                               0xffU);
        }
        
        if (USBH_OK == ctrl_state_polling_fun(pudev, puhost, pustate)){
            usbh_string_desc_parse(pudev->host.rx_buffer, local_buffer, 0xffU);
          
            /* user callback for product string */
            puhost->usr_cb->serial_num_string(local_buffer);
            scd_event_handle(pudev, 
                             puhost, 
                             pustate, 
                             ENUN_EVENT_SET_CONFIGURATION, 
                             pustate->usbh_current_state);
        }
    } else {
        puhost->usr_cb->serial_num_string("N/A");
        scd_event_handle(pudev, 
                         puhost, 
                         pustate, 
                         ENUN_EVENT_SET_CONFIGURATION, 
                         pustate->usbh_current_state);
    }
}

/*!
    \brief      the handle function of ENUM_SET_CONFIGURATION state
    \param[in]  pudev: pointer to USB device
    \param[in]  puhost: pointer to USB host
    \param[in]  pustate: pointer to USB state driver
    \param[out] none
    \retval     none
*/
static void enum_set_configuration_handle (usb_core_handle_struct *pudev, 
                                           usbh_host_struct *puhost, 
                                           usbh_state_handle_struct *pustate)
{
    puhost->usbh_backup_state.enum_backup_state = ENUM_SET_CONFIGURATION;

    if (CTRL_IDLE == puhost->usbh_backup_state.ctrl_backup_state ) {
        usbh_enum_cfg_set(pudev, puhost, (uint16_t)puhost->device.cfg_desc.bConfigurationValue);
    }
    
    if (USBH_OK == ctrl_state_polling_fun(pudev, puhost, pustate)) {
        scd_event_handle(pudev, 
                         puhost, 
                         pustate, 
                         ENUN_EVENT_DEV_CONFIGURED, 
                         pustate->usbh_current_state);
    }
}

/*!
    \brief      the handle function of ENUM_DEV_CONFIGURED state
    \param[in]  pudev: pointer to USB device
    \param[in]  puhost: pointer to USB host
    \param[in]  pustate: pointer to USB state driver
    \param[out] none
    \retval     none
*/
static void enum_dev_configured_handle (usb_core_handle_struct *pudev, 
                                        usbh_host_struct *puhost, 
                                        usbh_state_handle_struct *pustate)
{
    puhost->usbh_backup_state.enum_backup_state = ENUM_DEV_CONFIGURED;
    scd_event_handle(pudev, puhost, pustate, GO_TO_UP_STATE_EVENT, pustate->usbh_current_state);
}

/*!
    \brief      get descriptor in usb host enumeration stage
    \param[in]  pudev: pointer to USB device
    \param[in]  puhost: pointer to USB host
    \param[in]  buf: buffer to store the descriptor
    \param[in]  ReqType: descriptor type
    \param[in]  ValueIdx: wValue for the GetDescriptr request
    \param[in]  Len: length of the descriptor
    \param[out] none
    \retval     none
*/
void usbh_enum_desc_get(usb_core_handle_struct *pudev, 
                        usbh_host_struct *puhost, 
                        uint8_t *buf, 
                        uint8_t  req_type, 
                        uint16_t value_idx, 
                        uint16_t len)
{
    usb_setup_union *pSetup = &(puhost->control.setup);

    pSetup->b.bmRequestType = USB_DIR_IN | req_type;
    pSetup->b.bRequest = USBREQ_GET_DESCRIPTOR;
    pSetup->b.wValue = value_idx;

    if (USB_STRDESC == (value_idx & 0xff00U)){
        pSetup->b.wIndex = 0x0409U;
    } else {
        pSetup->b.wIndex = 0U;
    }

    pSetup->b.wLength = len;
    
    puhost->control.buff = buf;
    puhost->control.length = len;
    
}

/*!
    \brief      set address in usb host enumeration stage
    \param[in]  pudev: pointer to USB device
    \param[in]  puhost: pointer to USB host
    \param[in]  device_address: the device address
    \param[out] none
    \retval     none
*/
void usbh_enum_addr_set(usb_core_handle_struct *pudev, 
                        usbh_host_struct *puhost,
                        uint8_t device_address)
{
    usb_setup_union *p_setup = &(puhost->control.setup);

    p_setup->b.bmRequestType = USB_DIR_OUT | USB_REQTYPE_DEVICE | USB_STANDARD_REQ;
    p_setup->b.bRequest = USBREQ_SET_ADDRESS;
    p_setup->b.wValue = (uint16_t)device_address;
    p_setup->b.wIndex = 0U;
    p_setup->b.wLength = 0U;
    puhost->control.buff = 0U;
    puhost->control.length = 0U;
}

/*!
    \brief      set configuration in usb host enumeration stage
    \param[in]  pudev: pointer to USB device
    \param[in]  puhost: pointer to USB host
    \param[in]  cfg_idx: the index of the configuration
    \param[out] none
    \retval     none
*/
void usbh_enum_cfg_set(usb_core_handle_struct *pudev, 
                       usbh_host_struct *puhost,
                       uint16_t cfg_idx)
{
    usb_setup_union *p_setup = &(puhost->control.setup);

    p_setup->b.bmRequestType = USB_DIR_OUT | USB_REQTYPE_DEVICE | USB_STANDARD_REQ;
    p_setup->b.bRequest = USBREQ_SET_CONFIGURATION;
    p_setup->b.wValue = cfg_idx;
    p_setup->b.wIndex = 0U;
    p_setup->b.wLength = 0U;
    puhost->control.buff = 0;
    puhost->control.length = 0U;
}

/*!
    \brief      parse the device descriptor
    \param[in]  dev_desc: device_descriptor destinaton address 
    \param[in]  buf: buffer where the source descriptor is available
    \param[in]  len: length of the descriptor
    \param[out] none
    \retval     none
*/
void usbh_device_desc_parse (usb_descriptor_device_struct *dev_desc, uint8_t *buf, uint16_t len)
{
    dev_desc->Header.bLength  = *(uint8_t *)(buf + 0);
    dev_desc->Header.bDescriptorType = *(uint8_t *)(buf + 1);
    dev_desc->bcdUSB          = SWAPBYTE(buf + 2);
    dev_desc->bDeviceClass    = *(uint8_t *)(buf + 4);
    dev_desc->bDeviceSubClass = *(uint8_t *)(buf + 5);
    dev_desc->bDeviceProtocol = *(uint8_t *)(buf + 6);
    dev_desc->bMaxPacketSize0 = *(uint8_t *)(buf + 7);

    if (len > 8U){
        /* for 1st time after device connection, host may issue only 8 bytes for device descriptor length  */
        dev_desc->idVendor      = SWAPBYTE(buf + 8);
        dev_desc->idProduct     = SWAPBYTE(buf + 10);
        dev_desc->bcdDevice     = SWAPBYTE(buf + 12);
        dev_desc->iManufacturer = *(uint8_t *)(buf + 14);
        dev_desc->iProduct      = *(uint8_t *)(buf + 15);
        dev_desc->iSerialNumber = *(uint8_t *)(buf + 16);
        dev_desc->bNumberConfigurations = *(uint8_t *)(buf + 17);
    }
}

/*!
    \brief      parse the configuration descriptor
    \param[in]  cfg_desc: configuration descriptor address
    \param[in]  itf_desc: interface descriptor address
    \param[in]  ep_desc: endpoint descriptor address
    \param[in]  buf: buffer where the source descriptor is available
    \param[in]  len: length of the descriptor
    \param[out] none
    \retval     none
*/
void  usbh_cfg_desc_parse (usb_descriptor_configuration_struct *cfg_desc,
                           usb_descriptor_interface_struct *itf_desc,
                           usb_descriptor_endpoint_struct ep_desc[][USBH_MAX_EP_NUM],
                           uint8_t *buf,
                           uint16_t len)
{  
    usb_descriptor_interface_struct *pitf = NULL;
    usb_descriptor_interface_struct temp_pitf;
    usb_descriptor_endpoint_struct *pep = NULL;
    usb_descriptor_header_struct *pdesc = (usb_descriptor_header_struct *)buf;

    uint8_t itf_ix = 0U;
    uint8_t ep_ix = 0U;
    uint16_t ptr = 0U;
    static uint8_t prev_itf = 0U;
    static uint16_t prev_ep_size = 0U;

    /* parse configuration descriptor */
    cfg_desc->Header.bLength         = *(uint8_t *)(buf + 0);
    cfg_desc->Header.bDescriptorType = *(uint8_t *)(buf + 1);
    cfg_desc->wTotalLength           = SWAPBYTE(buf + 2);
    cfg_desc->bNumInterfaces         = *(uint8_t *)(buf + 4);
    cfg_desc->bConfigurationValue    = *(uint8_t *)(buf + 5);
    cfg_desc->iConfiguration         = *(uint8_t *)(buf + 6);
    cfg_desc->bmAttributes           = *(uint8_t *)(buf + 7);
    cfg_desc->bMaxPower              = *(uint8_t *)(buf + 8);

    if (len > USB_CFGDESC_SIZE) {
        ptr = USB_CFG_DESC_LEN;

        if (cfg_desc->bNumInterfaces <= USBH_MAX_INTERFACES_NUM) {
            pitf = (usb_descriptor_interface_struct *)0;

            for (; ptr < cfg_desc->wTotalLength; ) {
                pdesc = usbh_next_desc_get((uint8_t *)pdesc, &ptr);

                if (USB_DESCTYPE_INTERFACE == pdesc->bDescriptorType) {
                    itf_ix = *((uint8_t *)pdesc + 2U);
                    pitf = &itf_desc[itf_ix];

                    if (*((uint8_t *)pdesc + 3U) < 3U) {
                        usbh_interface_desc_parse (&temp_pitf, (uint8_t *)pdesc);

                        /* parse endpoint descriptors relative to the current interface */
                        if (temp_pitf.bNumEndpoints <= USBH_MAX_EP_NUM) {
                            for (ep_ix = 0U; ep_ix < temp_pitf.bNumEndpoints;) {
                                pdesc = usbh_next_desc_get((void* )pdesc, &ptr);

                                if (USB_DESCTYPE_ENDPOINT == pdesc->bDescriptorType) {
                                    pep = &ep_desc[itf_ix][ep_ix];

                                    if (prev_itf != itf_ix) {
                                        prev_itf = itf_ix;
                                        usbh_interface_desc_parse (pitf, (uint8_t *)&temp_pitf);
                                    } else {
                                        if (prev_ep_size > SWAPBYTE((uint8_t *)pdesc + 4)) {
                                            break;
                                        } else {
                                            usbh_interface_desc_parse (pitf, (uint8_t *)&temp_pitf);
                                        }
                                    }

                                    usbh_endpoint_desc_parse (pep, (uint8_t *)pdesc);
                                    prev_ep_size = SWAPBYTE((uint8_t *)pdesc + 4);
                                    ep_ix++;
                                }
                            }
                        }
                    }
                }
            }
        }

        prev_ep_size = 0U;
        prev_itf = 0U; 
    }
}

/*!
    \brief      parse the interface descriptor
    \param[in]  itf_desc: interface descriptor destination
    \param[in]  buf: buffer where the descriptor data is available
    \param[out] none
    \retval     none
*/
void  usbh_interface_desc_parse (usb_descriptor_interface_struct *itf_desc, uint8_t *buf)
{
    itf_desc->Header.bLength         = *(uint8_t *)(buf + 0);
    itf_desc->Header.bDescriptorType = *(uint8_t *)(buf + 1);
    itf_desc->bInterfaceNumber       = *(uint8_t *)(buf + 2);
    itf_desc->bAlternateSetting      = *(uint8_t *)(buf + 3);
    itf_desc->bNumEndpoints          = *(uint8_t *)(buf + 4);
    itf_desc->bInterfaceClass        = *(uint8_t *)(buf + 5);
    itf_desc->bInterfaceSubClass     = *(uint8_t *)(buf + 6);
    itf_desc->bInterfaceProtocol     = *(uint8_t *)(buf + 7);
    itf_desc->iInterface             = *(uint8_t *)(buf + 8);
}

/*!
    \brief      parse the endpoint descriptor
    \param[in]  ep_desc: endpoint descriptor destination address
    \param[in]  buf: buffer where the parsed descriptor stored
    \param[out] none
    \retval     none
*/
void  usbh_endpoint_desc_parse (usb_descriptor_endpoint_struct *ep_desc, uint8_t *buf)
{
    ep_desc->Header.bLength          = *(uint8_t *)(buf + 0);
    ep_desc->Header.bDescriptorType  = *(uint8_t *)(buf + 1);
    ep_desc->bEndpointAddress = *(uint8_t *)(buf + 2);
    ep_desc->bmAttributes     = *(uint8_t *)(buf + 3);
    ep_desc->wMaxPacketSize   = SWAPBYTE(buf + 4);
    ep_desc->bInterval        = *(uint8_t *)(buf + 6);
}

/*!
    \brief      parse the string descriptor
    \param[in]  psrc: source pointer containing the descriptor data
    \param[in]  pdest: destination address pointer
    \param[in]  len: length of the descriptor
    \param[out] none
    \retval     none
*/
void usbh_string_desc_parse (uint8_t* psrc, uint8_t* pdest, uint16_t len)
{
    uint16_t strlength;
    uint16_t idx;

    /* the unicode string descriptor is not null-terminated. the string length is
        computed by substracting two from the value of the first byte of the descriptor.
    */

    /* check which is lower size, the size of string or the length of bytes read from the device */

    if (USB_DESCTYPE_STRING == psrc[1]){
        /* make sure the descriptor is string type */

        /* psrc[0] contains size of descriptor, subtract 2 to get the length of string */      
        strlength = ((((uint16_t)psrc[0] - 2U) <= len) ? ((uint16_t)psrc[0] - 2U) : len);
        psrc += 2; /* adjust the offset ignoring the string len and descriptor type */

        for (idx = 0U; idx < strlength; idx += 2U) {
            /* copy only the string and ignore the unicode id, hence add the src */
            *pdest = psrc[idx];
            pdest++;
        }

        *pdest = 0U; /* mark end of string */  
    }
}

/*!
    \brief      get the next descriptor header
    \param[in]  pbuf: pointer to buffer where the cfg descriptor is available
    \param[in]  ptr: data popinter inside the configuration descriptor
    \param[out] none
    \retval     next descriptor header
*/
usb_descriptor_header_struct *usbh_next_desc_get (uint8_t *pbuf, uint16_t *ptr)
{
    uint8_t len = ((usb_descriptor_header_struct *)pbuf)->bLength;

    usb_descriptor_header_struct *pnext;

    *ptr += len;

    pnext = (usb_descriptor_header_struct *)((uint8_t *)pbuf + len);

    return(pnext);
}

