/** ****************************************************************************** * @file usbh_ctlreq.c * @author MCD Application Team * @version V3.2.1 * @date 26-June-2015 * @brief This file implements the control requests for device enumeration ****************************************************************************** * @attention * *

© COPYRIGHT 2015 STMicroelectronics

* * 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. * ****************************************************************************** * * Modifications by Robert Fisk */ /* Includes ------------------------------------------------------------------*/ #include "usbh_ctlreq.h" /** @addtogroup USBH_LIB * @{ */ /** @addtogroup USBH_LIB_CORE * @{ */ /** @defgroup USBH_CTLREQ * @brief This file implements the standard requests for device enumeration * @{ */ /** @defgroup USBH_CTLREQ_Private_Defines * @{ */ /** * @} */ /** @defgroup USBH_CTLREQ_Private_TypesDefinitions * @{ */ /** * @} */ /** @defgroup USBH_CTLREQ_Private_Macros * @{ */ /** * @} */ /** @defgroup USBH_CTLREQ_Private_Variables * @{ */ /** * @} */ /** @defgroup USBH_CTLREQ_Private_FunctionPrototypes * @{ */ static USBH_StatusTypeDef USBH_HandleControl (USBH_HandleTypeDef *phost); static void USBH_ParseDevDesc (USBH_DevDescTypeDef* , uint8_t *buf, uint16_t length); static void USBH_ParseCfgDesc (USBH_CfgDescTypeDef* cfg_desc, uint8_t *buf, uint16_t length); static void USBH_ParseEPDesc (USBH_EpDescTypeDef *ep_descriptor, uint8_t *buf); static void USBH_ParseStringDesc (uint8_t* psrc, uint8_t* pdest, uint16_t length); static void USBH_ParseInterfaceDesc (USBH_InterfaceDescTypeDef *if_descriptor, uint8_t *buf); /** * @} */ /** @defgroup USBH_CTLREQ_Private_Functions * @{ */ /** * @brief USBH_Get_DevDesc * Issue Get Device Descriptor command to the device. Once the response * received, it parses the device descriptor and updates the status. * @param phost: Host Handle * @param length: Length of the descriptor * @retval USBH Status */ USBH_StatusTypeDef USBH_Get_DevDesc(USBH_HandleTypeDef *phost, uint16_t length) { USBH_StatusTypeDef status; if (length > USBH_MAX_DATA_BUFFER) { length = USBH_MAX_DATA_BUFFER; } if((status = USBH_GetDescriptor(phost, USB_REQ_RECIPIENT_DEVICE | USB_REQ_TYPE_STANDARD, USB_DESC_DEVICE, 0, phost->device.Data, length)) == USBH_OK) { /* Commands successfully sent and Response Received */ USBH_ParseDevDesc(&phost->device.DevDesc, phost->device.Data, length); } return status; } /** * @brief USBH_Get_CfgDesc * Issues Configuration Descriptor to the device. Once the response * received, it parses the configuration descriptor and updates the * status. * @param phost: Host Handle * @param length: Length of the descriptor * @retval USBH Status */ USBH_StatusTypeDef USBH_Get_CfgDesc(USBH_HandleTypeDef *phost, uint16_t length) { USBH_StatusTypeDef status; uint8_t *pData; #if (USBH_KEEP_CFG_DESCRIPTOR == 1) pData = phost->device.CfgDesc_Raw; if (length > USBH_MAX_SIZE_CONFIGURATION) { length = USBH_MAX_SIZE_CONFIGURATION; } #else pData = phost->device.Data; if (length > USBH_MAX_DATA_BUFFER) { length = USBH_MAX_DATA_BUFFER; } #endif if((status = USBH_GetDescriptor(phost, USB_REQ_RECIPIENT_DEVICE | USB_REQ_TYPE_STANDARD, USB_DESC_CONFIGURATION, 0, pData, length)) == USBH_OK) { /* Commands successfully sent and Response Received */ USBH_ParseCfgDesc (&phost->device.CfgDesc, pData, length); } return status; } /** * @brief USBH_Get_StringDesc * Issues string Descriptor command to the device. Once the response * received, it parses the string descriptor and updates the status. * @param phost: Host Handle * @param string_index: String index for the descriptor * @param buff: Buffer address for the descriptor * @param length: Length of the descriptor * @retval USBH Status */ USBH_StatusTypeDef USBH_Get_StringDesc(USBH_HandleTypeDef *phost, uint8_t string_index, uint8_t *buff, uint16_t length) { USBH_StatusTypeDef status; if (length > USBH_MAX_DATA_BUFFER) { length = USBH_MAX_DATA_BUFFER; } if((status = USBH_GetDescriptor(phost, USB_REQ_RECIPIENT_DEVICE | USB_REQ_TYPE_STANDARD, USB_DESC_STRING | string_index, 0x0409, phost->device.Data, length)) == USBH_OK) { /* Commands successfully sent and Response Received */ USBH_ParseStringDesc(phost->device.Data,buff, length); } return status; } /** * @brief USBH_GetDescriptor * Issues Descriptor command to the device. Once the response received, * it parses the descriptor and updates the status. * @param phost: Host Handle * @param req_type: Descriptor type * @param value_idx: Value for the GetDescriptr request * @param buff: Buffer to store the descriptor * @param length: Length of the descriptor * @retval USBH Status */ USBH_StatusTypeDef USBH_GetDescriptor(USBH_HandleTypeDef *phost, uint8_t req_type, uint16_t type_idx, uint16_t value, uint8_t* buff, uint16_t length ) { if(phost->RequestState == CMD_SEND) { phost->Control.setup.b.bmRequestType = USB_D2H | req_type; phost->Control.setup.b.bRequest = USB_REQ_GET_DESCRIPTOR; phost->Control.setup.b.wValue.w = type_idx; phost->Control.setup.b.wIndex.w = value; phost->Control.setup.b.wLength.w = length; } return USBH_CtlReq(phost, buff , length ); } /** * @brief USBH_SetAddress * This command sets the address to the connected device * @param phost: Host Handle * @param DeviceAddress: Device address to assign * @retval USBH Status */ USBH_StatusTypeDef USBH_SetAddress(USBH_HandleTypeDef *phost, uint8_t DeviceAddress) { if(phost->RequestState == CMD_SEND) { phost->Control.setup.b.bmRequestType = USB_H2D | USB_REQ_RECIPIENT_DEVICE | \ USB_REQ_TYPE_STANDARD; phost->Control.setup.b.bRequest = USB_REQ_SET_ADDRESS; phost->Control.setup.b.wValue.w = (uint16_t)DeviceAddress; phost->Control.setup.b.wIndex.w = 0; phost->Control.setup.b.wLength.w = 0; } return USBH_CtlReq(phost, 0 , 0 ); } /** * @brief USBH_SetCfg * The command sets the configuration value to the connected device * @param phost: Host Handle * @param cfg_idx: Configuration value * @retval USBH Status */ USBH_StatusTypeDef USBH_SetCfg(USBH_HandleTypeDef *phost, uint16_t cfg_idx) { if(phost->RequestState == CMD_SEND) { phost->Control.setup.b.bmRequestType = USB_H2D | USB_REQ_RECIPIENT_DEVICE |\ USB_REQ_TYPE_STANDARD; phost->Control.setup.b.bRequest = USB_REQ_SET_CONFIGURATION; phost->Control.setup.b.wValue.w = cfg_idx; phost->Control.setup.b.wIndex.w = 0; phost->Control.setup.b.wLength.w = 0; } return USBH_CtlReq(phost, 0 , 0 ); } /** * @brief USBH_SetInterface * The command sets the Interface value to the connected device * @param phost: Host Handle * @param altSetting: Interface value * @retval USBH Status */ USBH_StatusTypeDef USBH_SetInterface(USBH_HandleTypeDef *phost, uint8_t ep_num, uint8_t altSetting) { if(phost->RequestState == CMD_SEND) { phost->Control.setup.b.bmRequestType = USB_H2D | USB_REQ_RECIPIENT_INTERFACE | \ USB_REQ_TYPE_STANDARD; phost->Control.setup.b.bRequest = USB_REQ_SET_INTERFACE; phost->Control.setup.b.wValue.w = altSetting; phost->Control.setup.b.wIndex.w = ep_num; phost->Control.setup.b.wLength.w = 0; } return USBH_CtlReq(phost, 0 , 0 ); } /** * @brief USBH_ClrFeature * This request is used to clear or disable a specific feature. * @param phost: Host Handle * @param ep_num: endpoint number * @param hc_num: Host channel number * @retval USBH Status */ USBH_StatusTypeDef USBH_ClrFeature(USBH_HandleTypeDef *phost, uint8_t ep_num) { if(phost->RequestState == CMD_SEND) { phost->Control.setup.b.bmRequestType = USB_H2D | USB_REQ_RECIPIENT_ENDPOINT | USB_REQ_TYPE_STANDARD; phost->Control.setup.b.bRequest = USB_REQ_CLEAR_FEATURE; phost->Control.setup.b.wValue.w = FEATURE_SELECTOR_ENDPOINT; phost->Control.setup.b.wIndex.w = ep_num; phost->Control.setup.b.wLength.w = 0; } return USBH_CtlReq(phost, 0 , 0 ); } /** * @brief USBH_ParseDevDesc * This function Parses the device descriptor * @param dev_desc: device_descriptor destination address * @param buf: Buffer where the source descriptor is available * @param length: Length of the descriptor * @retval None */ static void USBH_ParseDevDesc (USBH_DevDescTypeDef* dev_desc, uint8_t *buf, uint16_t length) { dev_desc->bLength = *(uint8_t *) (buf + 0); dev_desc->bDescriptorType = *(uint8_t *) (buf + 1); dev_desc->bcdUSB = LE16 (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->bMaxPacketSize = *(uint8_t *) (buf + 7); if (length > 8) { /* For 1st time after device connection, Host may issue only 8 bytes for Device Descriptor Length */ dev_desc->idVendor = LE16 (buf + 8); dev_desc->idProduct = LE16 (buf + 10); dev_desc->bcdDevice = LE16 (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->bNumConfigurations = *(uint8_t *) (buf + 17); } } /** * @brief USBH_ParseCfgDesc * This function Parses the configuration descriptor * @param cfg_desc: Configuration Descriptor address * @param buf: Buffer where the source descriptor is available * @param length: Length of the descriptor * @retval None */ static void USBH_ParseCfgDesc (USBH_CfgDescTypeDef* cfg_desc, uint8_t *buf, uint16_t length) { USBH_InterfaceDescTypeDef *pif ; USBH_EpDescTypeDef *pep; USBH_DescHeader_t *pdesc = (USBH_DescHeader_t *)buf; uint16_t ptr; int8_t if_ix = 0; int8_t ep_ix = 0; pdesc = (USBH_DescHeader_t *)buf; /* Parse configuration descriptor */ cfg_desc->bLength = *(uint8_t *) (buf + 0); cfg_desc->bDescriptorType = *(uint8_t *) (buf + 1); cfg_desc->wTotalLength = LE16 (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 (length > USB_CONFIGURATION_DESC_SIZE) { ptr = USB_LEN_CFG_DESC; pif = (USBH_InterfaceDescTypeDef *)0; //*************** //Todo: This does not check for malformed descriptors. Needs hardening! //*************** while ((if_ix < USBH_MAX_NUM_INTERFACES ) && (ptr < cfg_desc->wTotalLength)) { pdesc = USBH_GetNextDesc((uint8_t *)pdesc, &ptr); if (pdesc->bDescriptorType == USB_DESC_TYPE_INTERFACE) { pif = &cfg_desc->Itf_Desc[if_ix]; USBH_ParseInterfaceDesc (pif, (uint8_t *)pdesc); ep_ix = 0; pep = (USBH_EpDescTypeDef *)0; while ((ep_ix < pif->bNumEndpoints) && (ptr < cfg_desc->wTotalLength)) { pdesc = USBH_GetNextDesc((uint8_t*) pdesc, &ptr); if (pdesc->bDescriptorType == USB_DESC_TYPE_ENDPOINT) { pep = &cfg_desc->Itf_Desc[if_ix].Ep_Desc[ep_ix]; USBH_ParseEPDesc (pep, (uint8_t *)pdesc); ep_ix++; } } if_ix++; } } } } /** * @brief USBH_ParseInterfaceDesc * This function Parses the interface descriptor * @param if_descriptor : Interface descriptor destination * @param buf: Buffer where the descriptor data is available * @retval None */ static void USBH_ParseInterfaceDesc (USBH_InterfaceDescTypeDef *if_descriptor, uint8_t *buf) { if_descriptor->bLength = *(uint8_t *) (buf + 0); if_descriptor->bDescriptorType = *(uint8_t *) (buf + 1); if_descriptor->bInterfaceNumber = *(uint8_t *) (buf + 2); if_descriptor->bAlternateSetting = *(uint8_t *) (buf + 3); if_descriptor->bNumEndpoints = *(uint8_t *) (buf + 4); if_descriptor->bInterfaceClass = *(uint8_t *) (buf + 5); if_descriptor->bInterfaceSubClass = *(uint8_t *) (buf + 6); if_descriptor->bInterfaceProtocol = *(uint8_t *) (buf + 7); if_descriptor->iInterface = *(uint8_t *) (buf + 8); } /** * @brief USBH_ParseEPDesc * This function Parses the endpoint descriptor * @param ep_descriptor: Endpoint descriptor destination address * @param buf: Buffer where the parsed descriptor stored * @retval None */ static void USBH_ParseEPDesc (USBH_EpDescTypeDef *ep_descriptor, uint8_t *buf) { ep_descriptor->bLength = *(uint8_t *) (buf + 0); ep_descriptor->bDescriptorType = *(uint8_t *) (buf + 1); ep_descriptor->bEndpointAddress = *(uint8_t *) (buf + 2); ep_descriptor->bmAttributes = *(uint8_t *) (buf + 3); ep_descriptor->wMaxPacketSize = LE16 (buf + 4); ep_descriptor->bInterval = *(uint8_t *) (buf + 6); } /** * @brief USBH_ParseStringDesc * This function Parses the string descriptor * @param psrc: Source pointer containing the descriptor data * @param pdest: Destination address pointer * @param length: Length of the descriptor * @retval None */ static void USBH_ParseStringDesc (uint8_t* psrc, uint8_t* pdest, uint16_t length) { 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 ( psrc[1] == USB_DESC_TYPE_STRING) { /* Make sure the Descriptor is String Type */ /* psrc[0] contains Size of Descriptor, subtract 2 to get the length of string */ strlength = ( ( (psrc[0]-2) <= length) ? (psrc[0]-2) :length); psrc += 2; /* Adjust the offset ignoring the String Len and Descriptor type */ for (idx = 0; idx < strlength; idx+=2 ) {/* Copy Only the string and ignore the UNICODE ID, hence add the src */ *pdest = psrc[idx]; pdest++; } *pdest = 0; /* mark end of string */ } } /** * @brief USBH_GetNextDesc * This function return the next descriptor header * @param buf: Buffer where the cfg descriptor is available * @param ptr: data pointer inside the cfg descriptor * @retval next header */ USBH_DescHeader_t *USBH_GetNextDesc (uint8_t *pbuf, uint16_t *ptr) { USBH_DescHeader_t *pnext; *ptr += ((USBH_DescHeader_t *)pbuf)->bLength; pnext = (USBH_DescHeader_t *)((uint8_t *)pbuf + \ ((USBH_DescHeader_t *)pbuf)->bLength); return(pnext); } /** * @brief USBH_CtlReq * USBH_CtlReq sends a control request and provide the status after * completion of the request * @param phost: Host Handle * @param req: Setup Request Structure * @param buff: data buffer address to store the response * @param length: length of the response * @retval USBH Status */ USBH_StatusTypeDef USBH_CtlReq (USBH_HandleTypeDef *phost, uint8_t *buff, uint16_t length) { USBH_StatusTypeDef status; status = USBH_BUSY; switch (phost->RequestState) { case CMD_SEND: /* Start a SETUP transfer */ phost->Control.buff = buff; phost->Control.length = length; phost->Control.state = CTRL_SETUP; phost->RequestState = CMD_WAIT; status = USBH_BUSY; #if (USBH_USE_OS == 1) osMessagePut ( phost->os_event, USBH_CONTROL_EVENT, 0); #endif break; case CMD_WAIT: status = USBH_HandleControl(phost); if (status != USBH_BUSY) { phost->RequestState = CMD_SEND; phost->Control.state = CTRL_IDLE; } break; default: break; } return status; } /** * @brief USBH_HandleControl * Handles the USB control transfer state machine * @param phost: Host Handle * @retval USBH Status */ static USBH_StatusTypeDef USBH_HandleControl (USBH_HandleTypeDef *phost) { uint8_t direction; USBH_StatusTypeDef status = USBH_BUSY; USBH_URBStateTypeDef URB_Status = USBH_URB_IDLE; switch (phost->Control.state) { case CTRL_SETUP: /* send a SETUP packet */ phost->Control.timer = phost->Timer; USBH_CtlSendSetup(phost, (uint8_t *)phost->Control.setup.d8 , phost->Control.pipe_out); phost->Control.state = CTRL_SETUP_WAIT; break; case CTRL_SETUP_WAIT: URB_Status = USBH_LL_GetURBState(phost, phost->Control.pipe_out); /* case SETUP packet sent successfully */ if(URB_Status == USBH_URB_DONE) { direction = (phost->Control.setup.b.bmRequestType & USB_REQ_DIR_MASK); /* check if there is a data stage */ if (phost->Control.setup.b.wLength.w != 0 ) { if (direction == USB_D2H) { /* Data Direction is IN */ phost->Control.state = CTRL_DATA_IN; } else { /* Data Direction is OUT */ phost->Control.state = CTRL_DATA_OUT; } } /* No DATA stage */ else { /* If there is No Data Transfer Stage */ if (direction == USB_D2H) { /* Data Direction is IN */ phost->Control.state = CTRL_STATUS_OUT; } else { /* Data Direction is OUT */ phost->Control.state = CTRL_STATUS_IN; } } } else if(URB_Status == USBH_URB_ERROR) { phost->Control.state = CTRL_ERROR; } else if (URB_Status == USBH_URB_NOTREADY) { //Some mice cause a transaction error interrupt (HCINT_TXERR) at this point. //Other mice just NAK our transaction. //Either way we need to retry, but keep our original timeout so we don't wait forever. USBH_CtlSendSetup(phost, (uint8_t *)phost->Control.setup.d8 , phost->Control.pipe_out); } if ((int32_t)(phost->Timer - phost->Control.timer) >= USBH_CTRL_TRANSACTION_TIMEOUT_MS) { phost->Control.state = CTRL_ERROR; } break; case CTRL_DATA_IN: /* Issue an IN token */ phost->Control.timer = phost->Timer; USBH_CtlReceiveData(phost, phost->Control.buff, phost->Control.length, phost->Control.pipe_in); phost->Control.state = CTRL_DATA_IN_WAIT; break; case CTRL_DATA_IN_WAIT: URB_Status = USBH_LL_GetURBState(phost , phost->Control.pipe_in); /* check is DATA packet transferred successfully */ if (URB_Status == USBH_URB_DONE) { phost->Control.state = CTRL_STATUS_OUT; } /* manage error cases*/ if (URB_Status == USBH_URB_STALL) { //Command not supported, report to callee status = USBH_NOT_SUPPORTED; } else if (URB_Status == USBH_URB_ERROR) { /* Device error */ phost->Control.state = CTRL_ERROR; } if ((int32_t)(phost->Timer - phost->Control.timer) >= USBH_CTRL_TRANSACTION_TIMEOUT_MS) { phost->Control.state = CTRL_ERROR; } break; case CTRL_DATA_OUT: phost->Control.timer = phost->Timer; USBH_CtlSendData (phost, phost->Control.buff, phost->Control.length , phost->Control.pipe_out, 1); phost->Control.state = CTRL_DATA_OUT_WAIT; break; case CTRL_DATA_OUT_WAIT: URB_Status = USBH_LL_GetURBState(phost, phost->Control.pipe_out); if (URB_Status == USBH_URB_DONE) { /* If the Setup Pkt is sent successful, then change the state */ phost->Control.state = CTRL_STATUS_IN; } /* handle error cases */ else if (URB_Status == USBH_URB_STALL) { //Command not supported, report to callee status = USBH_NOT_SUPPORTED; } else if (URB_Status == USBH_URB_NOTREADY) { /* Nack received from device */ phost->Control.state = CTRL_DATA_OUT; } else if (URB_Status == USBH_URB_ERROR) { /* device error */ phost->Control.state = CTRL_ERROR; status = USBH_FAIL; } if ((int32_t)(phost->Timer - phost->Control.timer) >= USBH_CTRL_TRANSACTION_TIMEOUT_MS) { phost->Control.state = CTRL_ERROR; } break; case CTRL_STATUS_IN: /* Send 0 bytes out packet */ phost->Control.timer = phost->Timer; USBH_CtlReceiveData (phost, 0, 0, phost->Control.pipe_in); phost->Control.state = CTRL_STATUS_IN_WAIT; break; case CTRL_STATUS_IN_WAIT: URB_Status = USBH_LL_GetURBState(phost , phost->Control.pipe_in); if ( URB_Status == USBH_URB_DONE) { /* Control transfers completed, Exit the State Machine */ phost->Control.state = CTRL_COMPLETE; status = USBH_OK; } else if (URB_Status == USBH_URB_ERROR) { phost->Control.state = CTRL_ERROR; } else if(URB_Status == USBH_URB_STALL) { //Command not supported, report to callee status = USBH_NOT_SUPPORTED; } if ((int32_t)(phost->Timer - phost->Control.timer) >= USBH_CTRL_TRANSACTION_TIMEOUT_MS) { phost->Control.state = CTRL_ERROR; } break; case CTRL_STATUS_OUT: phost->Control.timer = phost->Timer; USBH_CtlSendData (phost, 0, 0, phost->Control.pipe_out, 1); phost->Control.state = CTRL_STATUS_OUT_WAIT; break; case CTRL_STATUS_OUT_WAIT: URB_Status = USBH_LL_GetURBState(phost , phost->Control.pipe_out); if (URB_Status == USBH_URB_DONE) { status = USBH_OK; phost->Control.state = CTRL_COMPLETE; } else if (URB_Status == USBH_URB_NOTREADY) { phost->Control.state = CTRL_STATUS_OUT; } else if (URB_Status == USBH_URB_ERROR) { phost->Control.state = CTRL_ERROR; } if ((int32_t)(phost->Timer - phost->Control.timer) >= USBH_CTRL_TRANSACTION_TIMEOUT_MS) { phost->Control.state = CTRL_ERROR; } break; case CTRL_ERROR: /* After a halt condition is encountered or an error is detected by the host, a control endpoint is allowed to recover by accepting the next Setup PID; i.e., recovery actions via some other pipe are not required for control endpoints. For the Default Control Pipe, a device reset will ultimately be required to clear the halt or error condition if the next Setup PID is not accepted. */ if (++ phost->Control.errorcount <= USBH_MAX_ERROR_COUNT) { /* try to recover control */ // USBH_LL_Stop(phost); /* Do the transmission again, starting from SETUP Packet */ phost->Control.state = CTRL_SETUP; phost->RequestState = CMD_SEND; } else { phost->pUser(phost, HOST_USER_UNRECOVERED_ERROR); phost->Control.errorcount = 0; USBH_ErrLog("Control error"); status = USBH_FAIL; } break; default: break; } return status; } /** * @} */ /** * @} */ /** * @} */ /** * @} */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/