/**
  ******************************************************************************
  * @file    usbd_core.c
  * @author  MCD Application Team
  * @version V2.4.2
  * @date    11-December-2015
  * @brief   This file provides all the USBD core functions.
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; COPYRIGHT 2015 STMicroelectronics</center></h2>
  *
  * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License");
  * You may not use this file except in compliance with the License.
  * You may obtain a copy of the License at:
  *
  *        http://www.st.com/software_license_agreement_liberty_v2
  *
  * Unless required by applicable law or agreed to in writing, software 
  * distributed under the License is distributed on an "AS IS" BASIS, 
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *
  ******************************************************************************
  */ 

/* Includes ------------------------------------------------------------------*/
#include "usbd_core.h"

/** @addtogroup STM32_USBD_DEVICE_LIBRARY
* @{
*/


/** @defgroup USBD_CORE 
* @brief usbd core module
* @{
*/ 

/** @defgroup USBD_CORE_Private_TypesDefinitions
* @{
*/ 
/**
* @}
*/ 


/** @defgroup USBD_CORE_Private_Defines
* @{
*/ 

/**
* @}
*/ 


/** @defgroup USBD_CORE_Private_Macros
* @{
*/ 
/**
* @}
*/ 




/** @defgroup USBD_CORE_Private_FunctionPrototypes
* @{
*/ 

/**
* @}
*/ 

/** @defgroup USBD_CORE_Private_Variables
* @{
*/ 

/**
* @}
*/ 

/** @defgroup USBD_CORE_Private_Functions
* @{
*/ 


#if USBD_GETDESCRIPTORS_ALTMETHOD == 0
// @USBD_CallGetDescriptorHandler
// The function calls the apropriate GetDescriptor-handler depending on the
// following conditions:
// - wheither the class-defined descriptors are supported or not;
// - wheither the class implements it's own get-descriptor handlers set or not;
// - wheither the class implements the specific handler in the set or not;
// - wheither the common GetDescriptor-hander set is implemented or not;
// - wheither the common specific GetDescriptor-hander set is implemented or not;
// If the function finds the valid function after the checking all the conditions,
// it calls it and returns the pointer @pbuf (by pointer @ppbuf) and @len (by
// pointer @plen).
// @ppbuf, @plen, @pdev must be NON-NULL
// Returns: USBD_OK if the specific handler have called, otherwise USBD_FAIL
USBD_StatusTypeDef USBD_CallGetDescriptorHandler(
   USBD_HandleTypeDef *pdev,
   eGetDescriptorHandlerType_t desc_type,
   const uint8_t * * ppbuf,
   uint16_t * plen )
{
    USBD_StatusTypeDef ret = USBD_FAIL;                             
    //-----------------------------------------------------------------
    // Class-defined descriptors are supported
    // if the class implements it's own get-descriptor handlers set,
    // and the class implements the specific handler, this class-specific 
    // will be returned. Otherwise, if the common get-descriptor handlers set
    // is implemented and the specified handler is implemented, this common
    // handler will be returned.
    fGetDescriptorRoutine_t fLocal_GetDescriptor 
       = ChooseDescriptorHandler(
            pdev->pClass->pDesc,
            pdev->pDesc,
            desc_type
       );    

    if( NULL != fLocal_GetDescriptor )
    {
       *ppbuf = fLocal_GetDescriptor( pdev->dev_speed, plen ); 
       ret = USBD_OK;
    }
    
    return ret;
}
#endif

/**
* @brief  USBD_Init
*         Initializes the device stack and load the class driver
* @param  pdev: device instance
* @param  pdesc: Descriptor structure address
* @param  id: Low level core index
* @retval None
*/
#if USBD_GETDESCRIPTORS_ALTMETHOD == 0
USBD_StatusTypeDef USBD_Init(USBD_HandleTypeDef *pdev, USBD_DescriptorsTypeDef *pdesc, uint8_t id)
#else
USBD_StatusTypeDef USBD_Init(USBD_HandleTypeDef *pdev, void * unused, uint8_t id)
#endif
{
  /* Check whether the USB Host handle is valid */
  if(pdev == NULL)
  {    
    return USBD_FAIL; 
  }
  
  /* Unlink previous class*/
  if(pdev->pClass != NULL)
  {
    pdev->pClass = NULL;
  }
  
  #if USBD_GETDESCRIPTORS_ALTMETHOD == 0
  /* Assign USBD Descriptors */
  if(pdesc != NULL)
  {
    pdev->pDesc = pdesc;
  }
  #else
  (void)unused;
  // Class-specific descriptors are used.
  // No field @pDesc is implemented in @pdev
  #endif
  
  /* Set Device initial State */
  pdev->dev_state  = USBD_STATE_DEFAULT;
  pdev->id = id;
  /* Initialize low level driver */
  USBD_LL_Init(pdev);
  
  return USBD_OK; 
}

/**
* @brief  USBD_DeInit 
*         Re-Initialize th device library
* @param  pdev: device instance
* @retval status: status
*/
USBD_StatusTypeDef USBD_DeInit(USBD_HandleTypeDef *pdev)
{
  /* Set Default State */
  pdev->dev_state  = USBD_STATE_DEFAULT;
  
  /* Free Class Resources */
  pdev->pClass->DeInit(pdev, pdev->dev_config);  
  
    /* Stop the low level driver  */
  USBD_LL_Stop(pdev); 
  
  /* Initialize low level driver */
  USBD_LL_DeInit(pdev);
  
  return USBD_OK;
}


/**
  * @brief  USBD_RegisterClass 
  *         Link class driver to Device Core.
  * @param  pDevice : Device Handle
  * @param  pclass: Class handle
  * @retval USBD Status
  */
#if USBD_CONSTCLASS_IFACE
USBD_StatusTypeDef  USBD_RegisterClass(USBD_HandleTypeDef *pdev, const USBD_ClassTypeDef *pclass)
#else
USBD_StatusTypeDef  USBD_RegisterClass(USBD_HandleTypeDef *pdev, USBD_ClassTypeDef *pclass)
#endif
{
  USBD_StatusTypeDef   status = USBD_OK;
  if(pclass != 0)
  {
    /* link the class to the USB Device handle */
    pdev->pClass = pclass;
    status = USBD_OK;
  }
  else
  {    
    status = USBD_FAIL; 
  }
  
  return status;
}

/**
  * @brief  USBD_Start 
  *         Start the USB Device Core.
  * @param  pdev: Device Handle
  * @retval USBD Status
  */
USBD_StatusTypeDef  USBD_Start  (USBD_HandleTypeDef *pdev)
{
  
  /* Start the low level driver  */
  USBD_LL_Start(pdev); 
  
  return USBD_OK;  
}

/**
  * @brief  USBD_Stop 
  *         Stop the USB Device Core.
  * @param  pdev: Device Handle
  * @retval USBD Status
  */
USBD_StatusTypeDef  USBD_Stop   (USBD_HandleTypeDef *pdev)
{
  /* Free Class Resources */
  pdev->pClass->DeInit(pdev, pdev->dev_config);  

  /* Stop the low level driver  */
  USBD_LL_Stop(pdev); 
  
  return USBD_OK;  
}

/**
* @brief  USBD_RunTestMode 
*         Launch test mode process
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef  USBD_RunTestMode (USBD_HandleTypeDef  *pdev) 
{
  return USBD_OK;
}


/**
* @brief  USBD_SetClassConfig 
*        Configure device and start the interface
* @param  pdev: device instance
* @param  cfgidx: configuration index
* @retval status
*/

USBD_StatusTypeDef USBD_SetClassConfig(USBD_HandleTypeDef  *pdev, uint8_t cfgidx)
{
  USBD_StatusTypeDef   ret = USBD_FAIL;
  
  if(pdev->pClass != NULL)
  {
      /* Set configuration  and Start the Class*/
      if( USBD_OK == pdev->pClass->Init( pdev, cfgidx ) )
      {
          ret = USBD_OK;
      }
      else
      {
          // Clean up the resource the class has already acquired
          pdev->pClass->DeInit( pdev, cfgidx );    
      }
  }
  return ret; 
}

/**
* @brief  USBD_ClrClassConfig 
*         Clear current configuration
* @param  pdev: device instance
* @param  cfgidx: configuration index
* @retval status: USBD_StatusTypeDef
*/
USBD_StatusTypeDef USBD_ClrClassConfig(USBD_HandleTypeDef  *pdev, uint8_t cfgidx)
{
  /* Clear configuration  and De-initialize the Class process*/
  pdev->pClass->DeInit(pdev, cfgidx);  
  return USBD_OK;
}


/**
* @brief  USBD_SetupStage 
*         Handle the setup stage
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_LL_SetupStage(USBD_HandleTypeDef *pdev, uint8_t *psetup)
{

  USBD_ParseSetupRequest(&pdev->request, psetup);
  USBD_StatusTypeDef ret = USBD_OK;

  pdev->ep0_state = USBD_EP0_SETUP;
  pdev->ep0_data_len = pdev->request.wLength;
  
  switch (pdev->request.bmRequest & USB_REQ_RECIPIENT_MASK)   
  {
      case USB_REQ_RECIPIENT_DEVICE:   
      {
          ret = USBD_StdDevReq (pdev, &pdev->request);

          if( USBD_OK != ret ) USBD_CtlError(pdev, &pdev->request);
      }
      break;
        
      case USB_REQ_RECIPIENT_INTERFACE:     
      {
          ret = USBD_StdItfReq(pdev, &pdev->request);

          if( USBD_OK != ret ) USBD_CtlError(pdev, &pdev->request);
      }
      break;
        
      case USB_REQ_RECIPIENT_ENDPOINT:        
      {
          ret = USBD_StdEPReq(pdev, &pdev->request);   

          if( USBD_OK != ret ) USBD_CtlError(pdev, &pdev->request);
      }
      break;
        
      default:
      {
          ret = USBD_FAIL;

          USBD_LL_StallEP(pdev , pdev->request.bmRequest & 0x80);
      }
      break;
  }  

  #warning Need to call Setup Packet Complete Stage (Success)  after processing

  if( ret != USBD_OK )
  {
      if( NULL != pdev->pClass->Error )
      {
          pdev->pClass->Error( pdev, 0 ); 
      }
  }

  return ret;  
}

/**
* @brief  USBD_DataOutStage 
*         Handle data OUT stage
* @param  pdev: device instance
* @param  epnum: endpoint index
* @retval status
*/
USBD_StatusTypeDef USBD_LL_DataOutStage(USBD_HandleTypeDef *pdev , uint8_t epnum, uint8_t *pdata )
{
  USBD_EndpointTypeDef    *pep;
  USBD_StatusTypeDef ret = USBD_OK;
  USBD_StatusTypeDef ret_user = USBD_OK; 
  
  if(epnum == 0) 
  {
      pep = &pdev->ep_out[0];
      
      if ( pdev->ep0_state == USBD_EP0_STATUS_OUT)
      {           
           
      }

      if ( pdev->ep0_state == USBD_EP0_DATA_OUT)
      {   
          if(pep->rem_length > pep->maxpacket)
          {
              // @nBytesRollback
              // Number bytes to rollback: @pdata must be shifted back by @nBytesRollback bytes
              size_t nBytesRollback = 0;
              // ~~~~~~~~~~~~~~~~~
              if((pdev->pClass->EP0_RxReady != NULL)&&
                 (pdev->dev_state == USBD_STATE_CONFIGURED))
              {
                  ret_user = (USBD_StatusTypeDef)pdev->pClass->EP0_RxReady(pdev, &nBytesRollback ); 
              }
              // ~~~~~~~~~~~~~~~~~

              if( (pdev->request.bmRequest & USB_REQ_TYPE_MASK) != USB_REQ_TYPE_STANDARD )
              {
                  // ~~~~~~~~~~~~~~~~~
                  if( USBD_OK == ret_user )
                  {
                      // use the same buffer for next packet
                      // roll the pointer back :)

                      if( 0 == nBytesRollback  )
                      {
                          pdata -= pep->maxpacket; 
                      }
                      else
                      {
                          pdata -= nBytesRollback;
                      }
                  } 
                  else if ( USBD_CONTINUE == ret_user )
                  {
                      ret_user = USBD_OK;
                  }
                  // ~~~~~~~~~~~~~~~~~
              }

              pep->rem_length -=  pep->maxpacket;
              if( USBD_OK == ret_user )
              {
                  USBD_CtlContinueRx (pdev, 
                                      pdata,
                                      MIN(pep->rem_length,pep->maxpacket));

                  ret = USBD_OK;
              }              
          }
          else
          {
              if( (pdev->request.bmRequest & USB_REQ_TYPE_MASK) != USB_REQ_TYPE_STANDARD )
              {
                  if((pdev->pClass->EP0_RxReady != NULL)&&
                     (pdev->dev_state == USBD_STATE_CONFIGURED))
                  {
                      ret_user = (USBD_StatusTypeDef)pdev->pClass->EP0_RxReady(pdev, NULL); 
                  }
              }
              pep->rem_length = 0;

              if( USBD_OK == ret_user )
              {
                  USBD_CtlSendStatus(pdev);

                  ret = USBD_OK;
              }
          }
      }
  }
  else if((pdev->pClass->DataOut != NULL)&&
          (pdev->dev_state == USBD_STATE_CONFIGURED))
  {
     pep = &pdev->ep_out[ epnum & 0x7F ];

     uint32_t xfer_count = USBD_LL_GetRxDataSize( pdev, epnum ); 
     bool bShortPacket = false;

     // @rem_length and @total_length is set by USBD_EpPrepareRx() in the beginning of the transfer
     // This event is called only if the whole transfer is completed,
     // So, the first variant: all @total_length is received
     //     the second variant: short packet received
     if( xfer_count < pep->total_length )
     {
         // second variant: short packet received
         pep->rem_length = pep->total_length - xfer_count;
         bShortPacket = true;
     }
     else
     {
         // first variant: all @total_length is received
         pep->rem_length = 0;
     }

     ret_user = (USBD_StatusTypeDef)pdev->pClass->DataOut(pdev, epnum, bShortPacket ); 
  }  

  if( USBD_OK != ret || USBD_OK != ret_user )
  {
      pdev->pClass->Error( pdev, epnum );
  }

  if( USBD_OK != ret_user )
      return ret_user;

  return ret;
}

/**
* @brief  USBD_DataInStage 
*         Handle data in stage
* @param  pdev: device instance
* @param  epnum: endpoint index
* @retval status
*/
USBD_StatusTypeDef USBD_LL_DataInStage(USBD_HandleTypeDef *pdev ,uint8_t epnum, uint8_t *pdata)
{
  USBD_EndpointTypeDef    *pep;
  USBD_StatusTypeDef ret = USBD_OK;
  USBD_StatusTypeDef ret_user = USBD_OK; 
  
  if(epnum == 0) 
  {
      pep = &pdev->ep_in[0];
    
      if ( pdev->ep0_state == USBD_EP0_DATA_IN)
      {          
          if(pep->rem_length > pep->maxpacket)
          {
              pep->rem_length -=  pep->maxpacket;

              // Call user handler only for user requests (skip STD requests for descriptors)
              if( (pdev->request.bmRequest & USB_REQ_TYPE_MASK) != USB_REQ_TYPE_STANDARD )
              // wrong! ~~~Call user handler only for EP recipient (skip STD requests for descriptors)~~~
              // wrong! if( (pdev->request.bmRequest & USB_REQ_RECIPIENT_MASK) ==  USB_REQ_RECIPIENT_ENDPOINT )
              {
                  // ~~~~~~~~~~~~~~~~~
                  if((pdev->pClass->EP0_TxSent != NULL)&&
                     (pdev->dev_state == USBD_STATE_CONFIGURED))
                  {
                      // @nBytesRollback
                      // Number bytes to rollback: @pdata must be shifted back by @nBytesRollback bytes
                      size_t nBytesRollback = 0;

                      ret_user = (USBD_StatusTypeDef)pdev->pClass->EP0_TxSent(pdev, &nBytesRollback ); 

                      if( USBD_OK == ret_user )
                      {
                          if( 0 == nBytesRollback )
                          {
                              pdata -= pep->maxpacket;
                          }
                          else
                          {
                              pdata -= nBytesRollback;
                          }
                      }
                      else if( USBD_CONTINUE == ret_user )  // continue transmitting without rollbacking the pointer
                      {
                          ret_user = USBD_OK;
                      }
                  }
                  else
                  {
                      ret = USBD_FAIL;
                      ret_user = USBD_FAIL;
                  }
                  // ~~~~~~~~~~~~~~~~~
              }

              if( USBD_OK == ret_user )
              {
                  USBD_CtlContinueSendData (pdev, 
                                            pdata, 
                                            pep->rem_length);
              }
              #if 0
              #warning 08/05/19 - removed due to data exchange error possibility
              /* Prepare endpoint for premature end of transfer
              // #error ~~~~ USBD_LL_PrepareReceive (pdev,
              //                         0,
              //                         NULL,
              //                         0); ~~~~ it causes errors */ 
              #endif
          }
          else
          { /* last packet is MPS multiple, so send ZLP packet */
                if((pep->total_length % pep->maxpacket == 0) &&
                   (pep->total_length >= pep->maxpacket) &&
                     (pep->total_length < pdev->ep0_data_len ))
                {
                    // Sychov: useless code?? due to "pep->total_length < pdev->ep0_data_len" is always FALSE.
                    // send ZERO packet
                    USBD_CtlContinueSendData( pdev, NULL, 0 );

                    pdev->ep0_data_len = 0;
          
                    /* Prepare endpoint for premature end of transfer */                   
                    USBD_LL_PrepareReceive (pdev,
                                                  0,
                                                  NULL,
                                                  0);
                    ret = USBD_OK;
                    
                }
                else
                {
                    pep->rem_length = 0;
                    // Call user handler only for user requests (skip STD requests for descriptors)                    
                    if( (pdev->request.bmRequest & USB_REQ_TYPE_MASK) != USB_REQ_TYPE_STANDARD )
                    // wrong! ~~~Call user handler only for EP recipient (skip STD requests for descriptors)~~~
                    // wrong! if( (pdev->request.bmRequest & USB_REQ_RECIPIENT_MASK) ==  USB_REQ_RECIPIENT_ENDPOINT )
                    {
                        if((pdev->pClass->EP0_TxSent != NULL)&&
                           (pdev->dev_state == USBD_STATE_CONFIGURED))
                        {
                            // last call - "finalizer" ( remaining length is zero )
                            ret_user = (USBD_StatusTypeDef)pdev->pClass->EP0_TxSent(pdev, NULL);
                        } 
                    }

                    if( USBD_OK ==  ret_user )
                    {
                        /* 
                        08/05/19 Trace Information:
                        HAL_PCD_DataInStageCallback ->
                         USBD_CtlReceiveStatus (this place) -> 
                           USBD_LL_PrepareReceive(...NUL,0) ->
                             leave PCD_EP_ISR_Handler ->
                               enter PCD_EP_ISR_Handler ->
                                 HAL_PCD_DataOutStageCallback (receive status!) ->
                                  PCD_SET_EP_RX_CNT => setup PMA RX-buffer => OK
                        */
                        USBD_CtlReceiveStatus(pdev);                        
                    }                    
                }
          }
    }

    if (pdev->dev_test_mode == 1)
    {
        USBD_RunTestMode(pdev); 
        pdev->dev_test_mode = 0;
    }

  }
  else if((pdev->pClass->DataIn != NULL)&& 
          (pdev->dev_state == USBD_STATE_CONFIGURED))
  {
      ret_user = (USBD_StatusTypeDef)pdev->pClass->DataIn(pdev, epnum); 
  } 

  if( USBD_OK != ret || USBD_OK != ret_user )
  {
      pdev->pClass->Error( pdev, epnum );
  }
 
  if( USBD_OK != ret_user )
      return ret_user;

  return ret;
}

/**
* @brief  USBD_LL_Reset 
*         Handle Reset event
* @param  pdev: device instance
* @retval status
*/

USBD_StatusTypeDef USBD_LL_Reset(USBD_HandleTypeDef  *pdev)
{
  /* Open EP0 OUT */
  USBD_LL_OpenEP(pdev,
              0x00,
              USBD_EP_TYPE_CTRL,
              USB_MAX_EP0_SIZE);
  
  pdev->ep_out[0].maxpacket = USB_MAX_EP0_SIZE;
  
  /* Open EP0 IN */
  USBD_LL_OpenEP(pdev,
              0x80,
              USBD_EP_TYPE_CTRL,
              USB_MAX_EP0_SIZE);
  
  pdev->ep_in[0].maxpacket = USB_MAX_EP0_SIZE;

  // Update PMA addresses for endpoints
  USBD_LL_UpdatePMA( pdev );

  /* Upon Reset call user call back */
  pdev->dev_state = USBD_STATE_DEFAULT;
  
  if ( NULL != pdev->pClassData) // is initialized?
    pdev->pClass->DeInit(pdev, pdev->dev_config);  
 
  
  return USBD_OK;
}




/**
* @brief  USBD_LL_Reset 
*         Handle Reset event
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_LL_SetSpeed(USBD_HandleTypeDef  *pdev, USBD_SpeedTypeDef speed)
{
  pdev->dev_speed = speed;
  return USBD_OK;
}

/**
* @brief  USBD_Suspend 
*         Handle Suspend event
* @param  pdev: device instance
* @retval status
*/

USBD_StatusTypeDef USBD_LL_Suspend(USBD_HandleTypeDef  *pdev)
{
  pdev->dev_old_state =  pdev->dev_state;
  pdev->dev_state  = USBD_STATE_SUSPENDED;
  return USBD_OK;
}

/**
* @brief  USBD_Resume 
*         Handle Resume event
* @param  pdev: device instance
* @retval status
*/

USBD_StatusTypeDef USBD_LL_Resume(USBD_HandleTypeDef  *pdev)
{
  pdev->dev_state = pdev->dev_old_state;  
  return USBD_OK;
}

/**
* @brief  USBD_SOF 
*         Handle SOF event
* @param  pdev: device instance
* @retval status
*/

USBD_StatusTypeDef USBD_LL_SOF(USBD_HandleTypeDef  *pdev)
{
  if(pdev->dev_state == USBD_STATE_CONFIGURED)
  {
    if(pdev->pClass->SOF != NULL)
    {
      pdev->pClass->SOF(pdev);
    }
  }
  return USBD_OK;
}

/**
* @brief  USBD_IsoINIncomplete 
*         Handle iso in incomplete event
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_LL_IsoINIncomplete(USBD_HandleTypeDef  *pdev, uint8_t epnum)
{
  return USBD_OK;
}

/**
* @brief  USBD_IsoOUTIncomplete 
*         Handle iso out incomplete event
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_LL_IsoOUTIncomplete(USBD_HandleTypeDef  *pdev, uint8_t epnum)
{
  return USBD_OK;
}

/**
* @brief  USBD_DevConnected 
*         Handle device connection event
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_LL_DevConnected(USBD_HandleTypeDef  *pdev)
{
  return USBD_OK;
}

/**
* @brief  USBD_DevDisconnected 
*         Handle device disconnection event
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_LL_DevDisconnected(USBD_HandleTypeDef  *pdev)
{
  /* Free Class Resources */
  pdev->dev_state = USBD_STATE_DEFAULT;
  pdev->pClass->DeInit(pdev, pdev->dev_config);  
   
  return USBD_OK;
}
/**
* @}
*/ 


/**
* @}
*/ 


/**
* @}
*/ 

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

