| /* |
| LPCUSB, an USB device driver for LPC microcontrollers |
| Copyright (C) 2006 Bertrik Sikken (bertrik@sikken.nl) |
| |
| 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. The name of the author may not be used to endorse or promote products |
| derived from this software without specific prior written permission. |
| |
| THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. |
| */ |
| |
| |
| /** @file |
| Standard request handler. |
| |
| This modules handles the 'chapter 9' processing, specifically the |
| standard device requests in table 9-3 from the universal serial bus |
| specification revision 2.0 |
| |
| Specific types of devices may specify additional requests (for example |
| HID devices add a GET_DESCRIPTOR request for interfaces), but they |
| will not be part of this module. |
| |
| @todo some requests have to return a request error if device not configured: |
| @todo GET_INTERFACE, GET_STATUS, SET_INTERFACE, SYNCH_FRAME |
| @todo this applies to the following if endpoint != 0: |
| @todo SET_FEATURE, GET_FEATURE |
| */ |
| |
| #include "usbdebug.h" |
| #include "usbstruct.h" |
| #include "usbapi.h" |
| |
| #define MAX_DESC_HANDLERS 4 /**< device, interface, endpoint, other */ |
| |
| |
| /* general descriptor field offsets */ |
| #define DESC_bLength 0 /**< length offset */ |
| #define DESC_bDescriptorType 1 /**< descriptor type offset */ |
| |
| /* config descriptor field offsets */ |
| #define CONF_DESC_wTotalLength 2 /**< total length offset */ |
| #define CONF_DESC_bConfigurationValue 5 /**< configuration value offset */ |
| #define CONF_DESC_bmAttributes 7 /**< configuration characteristics */ |
| |
| /* interface descriptor field offsets */ |
| #define INTF_DESC_bAlternateSetting 3 /**< alternate setting offset */ |
| |
| /* endpoint descriptor field offsets */ |
| #define ENDP_DESC_bEndpointAddress 2 /**< endpoint address offset */ |
| #define ENDP_DESC_wMaxPacketSize 4 /**< maximum packet size offset */ |
| |
| |
| /** Currently selected configuration */ |
| static unsigned char bConfiguration = 0; |
| /** Installed custom request handler */ |
| static TFnHandleRequest *pfnHandleCustomReq = NULL; |
| /** Pointer to registered descriptors */ |
| static const unsigned char *pabDescrip = NULL; |
| |
| |
| /** |
| Registers a pointer to a descriptor block containing all descriptors |
| for the device. |
| |
| @param [in] pabDescriptors The descriptor byte array |
| */ |
| void USBRegisterDescriptors(const unsigned char *pabDescriptors) |
| { |
| pabDescrip = pabDescriptors; |
| } |
| |
| |
| /** |
| Parses the list of installed USB descriptors and attempts to find |
| the specified USB descriptor. |
| |
| @param [in] wTypeIndex Type and index of the descriptor |
| @param [in] wLangID Language ID of the descriptor (currently unused) |
| @param [out] *piLen Descriptor length |
| @param [out] *ppbData Descriptor data |
| |
| @return TRUE if the descriptor was found, FALSE otherwise |
| */ |
| BOOL USBGetDescriptor(unsigned short wTypeIndex, unsigned short wLangID, int *piLen, unsigned char **ppbData) |
| { |
| unsigned char bType, bIndex; |
| unsigned char *pab; |
| int iCurIndex; |
| |
| ASSERT(pabDescrip != NULL); |
| |
| bType = GET_DESC_TYPE(wTypeIndex); |
| bIndex = GET_DESC_INDEX(wTypeIndex); |
| |
| pab = (unsigned char *)pabDescrip; |
| iCurIndex = 0; |
| |
| while (pab[DESC_bLength] != 0) { |
| if (pab[DESC_bDescriptorType] == bType) { |
| if (iCurIndex == bIndex) { |
| // set data pointer |
| *ppbData = pab; |
| // get length from structure |
| if (bType == DESC_CONFIGURATION) { |
| // configuration descriptor is an exception, length is at offset 2 and 3 |
| *piLen = (pab[CONF_DESC_wTotalLength]) | |
| (pab[CONF_DESC_wTotalLength + 1] << 8); |
| } else { |
| // normally length is at offset 0 |
| *piLen = pab[DESC_bLength]; |
| } |
| return TRUE; |
| } |
| iCurIndex++; |
| } |
| // skip to next descriptor |
| pab += pab[DESC_bLength]; |
| } |
| // nothing found |
| DBG("Desc %x not found!\n", wTypeIndex); |
| return FALSE; |
| } |
| |
| |
| /** |
| Configures the device according to the specified configuration index and |
| alternate setting by parsing the installed USB descriptor list. |
| A configuration index of 0 unconfigures the device. |
| |
| @param [in] bConfigIndex Configuration index |
| @param [in] bAltSetting Alternate setting number |
| |
| @todo function always returns TRUE, add stricter checking? |
| |
| @return TRUE if successfully configured, FALSE otherwise |
| */ |
| static BOOL USBSetConfiguration(unsigned char bConfigIndex, unsigned char bAltSetting) |
| { |
| unsigned char *pab; |
| unsigned char bCurConfig, bCurAltSetting; |
| unsigned char bEP; |
| unsigned short wMaxPktSize; |
| |
| ASSERT(pabDescrip != NULL); |
| |
| if (bConfigIndex == 0) { |
| // unconfigure device |
| USBHwConfigDevice(FALSE); |
| } else { |
| // configure endpoints for this configuration/altsetting |
| pab = (unsigned char *)pabDescrip; |
| bCurConfig = 0xFF; |
| bCurAltSetting = 0xFF; |
| |
| while (pab[DESC_bLength] != 0) { |
| |
| switch (pab[DESC_bDescriptorType]) { |
| |
| case DESC_CONFIGURATION: |
| // remember current configuration index |
| bCurConfig = pab[CONF_DESC_bConfigurationValue]; |
| break; |
| |
| case DESC_INTERFACE: |
| // remember current alternate setting |
| bCurAltSetting = pab[INTF_DESC_bAlternateSetting]; |
| break; |
| |
| case DESC_ENDPOINT: |
| if ((bCurConfig == bConfigIndex) && |
| (bCurAltSetting == bAltSetting)) { |
| // endpoint found for desired config and alternate setting |
| bEP = pab[ENDP_DESC_bEndpointAddress]; |
| wMaxPktSize = (pab[ENDP_DESC_wMaxPacketSize]) | |
| (pab[ENDP_DESC_wMaxPacketSize + 1] << 8); |
| // configure endpoint |
| USBHwEPConfig(bEP, wMaxPktSize); |
| } |
| break; |
| |
| default: |
| break; |
| } |
| // skip to next descriptor |
| pab += pab[DESC_bLength]; |
| } |
| |
| // configure device |
| USBHwConfigDevice(TRUE); |
| } |
| |
| return TRUE; |
| } |
| SetConfigHandler *SetConfig_CallBack = NULL; |
| void USBHwRegisterSetConfigHandler(SetConfigHandler *handler){ |
| SetConfig_CallBack = handler; |
| } |
| |
| |
| /** |
| Local function to handle a standard device request |
| |
| @param [in] pSetup The setup packet |
| @param [in,out] *piLen Pointer to data length |
| @param [in,out] ppbData Data buffer. |
| |
| @return TRUE if the request was handled successfully |
| */ |
| static BOOL HandleStdDeviceReq(TSetupPacket *pSetup, int *piLen, unsigned char **ppbData) |
| { |
| unsigned char *pbData = *ppbData; |
| |
| switch (pSetup->bRequest) { |
| |
| case REQ_GET_STATUS: |
| // bit 0: self-powered |
| // bit 1: remote wakeup = not supported |
| pbData[0] = 0; |
| pbData[1] = 0; |
| *piLen = 2; |
| break; |
| |
| case REQ_SET_ADDRESS: |
| USBHwSetAddress(pSetup->wValue); |
| break; |
| |
| case REQ_GET_DESCRIPTOR: |
| DBG("D%x", pSetup->wValue); |
| return USBGetDescriptor(pSetup->wValue, pSetup->wIndex, piLen, ppbData); |
| |
| case REQ_GET_CONFIGURATION: |
| // indicate if we are configured |
| pbData[0] = bConfiguration; |
| *piLen = 1; |
| break; |
| |
| case REQ_SET_CONFIGURATION: |
| if (!USBSetConfiguration(pSetup->wValue & 0xFF, 0)) { |
| DBG("USBSetConfiguration failed!\n"); |
| return FALSE; |
| } |
| // configuration successful, update current configuration |
| bConfiguration = pSetup->wValue & 0xFF; |
| if(SetConfig_CallBack != NULL) |
| SetConfig_CallBack(); |
| break; |
| |
| case REQ_CLEAR_FEATURE: |
| case REQ_SET_FEATURE: |
| if (pSetup->wValue == FEA_REMOTE_WAKEUP) { |
| // put DEVICE_REMOTE_WAKEUP code here |
| } |
| if (pSetup->wValue == FEA_TEST_MODE) { |
| // put TEST_MODE code here |
| } |
| return FALSE; |
| |
| case REQ_SET_DESCRIPTOR: |
| DBG("Device req %d not implemented\n", pSetup->bRequest); |
| return FALSE; |
| |
| default: |
| DBG("Illegal device req %d\n", pSetup->bRequest); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| |
| /** |
| Local function to handle a standard interface request |
| |
| @param [in] pSetup The setup packet |
| @param [in,out] *piLen Pointer to data length |
| @param [in] ppbData Data buffer. |
| |
| @return TRUE if the request was handled successfully |
| */ |
| static BOOL HandleStdInterfaceReq(TSetupPacket *pSetup, int *piLen, unsigned char **ppbData) |
| { |
| unsigned char *pbData = *ppbData; |
| |
| switch (pSetup->bRequest) { |
| |
| case REQ_GET_STATUS: |
| // no bits specified |
| pbData[0] = 0; |
| pbData[1] = 0; |
| *piLen = 2; |
| break; |
| |
| case REQ_CLEAR_FEATURE: |
| case REQ_SET_FEATURE: |
| // not defined for interface |
| return FALSE; |
| |
| case REQ_GET_INTERFACE: // TODO use bNumInterfaces |
| // there is only one interface, return n-1 (= 0) |
| pbData[0] = 0; |
| *piLen = 1; |
| break; |
| |
| case REQ_SET_INTERFACE: // TODO use bNumInterfaces |
| // there is only one interface (= 0) |
| if (pSetup->wValue != 0) { |
| return FALSE; |
| } |
| *piLen = 0; |
| break; |
| |
| default: |
| DBG("Illegal interface req %d\n", pSetup->bRequest); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| |
| /** |
| Local function to handle a standard endpoint request |
| |
| @param [in] pSetup The setup packet |
| @param [in,out] *piLen Pointer to data length |
| @param [in] ppbData Data buffer. |
| |
| @return TRUE if the request was handled successfully |
| */ |
| static BOOL HandleStdEndPointReq(TSetupPacket *pSetup, int *piLen, unsigned char **ppbData) |
| { |
| unsigned char *pbData = *ppbData; |
| |
| switch (pSetup->bRequest) { |
| case REQ_GET_STATUS: |
| // bit 0 = endpointed halted or not |
| pbData[0] = (USBHwEPGetStatus(pSetup->wIndex) & EP_STATUS_STALLED) ? 1 : 0; |
| pbData[1] = 0; |
| *piLen = 2; |
| break; |
| |
| case REQ_CLEAR_FEATURE: |
| if (pSetup->wValue == FEA_ENDPOINT_HALT) { |
| // clear HALT by unstalling |
| USBHwEPStall(pSetup->wIndex, FALSE); |
| break; |
| } |
| // only ENDPOINT_HALT defined for endpoints |
| return FALSE; |
| |
| case REQ_SET_FEATURE: |
| if (pSetup->wValue == FEA_ENDPOINT_HALT) { |
| // set HALT by stalling |
| USBHwEPStall(pSetup->wIndex, TRUE); |
| break; |
| } |
| // only ENDPOINT_HALT defined for endpoints |
| return FALSE; |
| |
| case REQ_SYNCH_FRAME: |
| DBG("EP req %d not implemented\n", pSetup->bRequest); |
| return FALSE; |
| |
| default: |
| DBG("Illegal EP req %d\n", pSetup->bRequest); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| |
| /** |
| Default handler for standard ('chapter 9') requests |
| |
| If a custom request handler was installed, this handler is called first. |
| |
| @param [in] pSetup The setup packet |
| @param [in,out] *piLen Pointer to data length |
| @param [in] ppbData Data buffer. |
| |
| @return TRUE if the request was handled successfully |
| */ |
| BOOL USBHandleStandardRequest(TSetupPacket *pSetup, int *piLen, unsigned char **ppbData) |
| { |
| // try the custom request handler first |
| if ((pfnHandleCustomReq != NULL) && pfnHandleCustomReq(pSetup, piLen, ppbData)) { |
| return TRUE; |
| } |
| |
| switch (REQTYPE_GET_RECIP(pSetup->bmRequestType)) { |
| case REQTYPE_RECIP_DEVICE: |
| return HandleStdDeviceReq(pSetup, piLen, ppbData); |
| case REQTYPE_RECIP_INTERFACE: |
| return HandleStdInterfaceReq(pSetup, piLen, ppbData); |
| case REQTYPE_RECIP_ENDPOINT: |
| return HandleStdEndPointReq(pSetup, piLen, ppbData); |
| default: |
| return FALSE; |
| } |
| } |
| |
| |
| /** |
| Registers a callback for custom device requests |
| |
| In USBHandleStandardRequest, the custom request handler gets a first |
| chance at handling the request before it is handed over to the 'chapter 9' |
| request handler. |
| |
| This can be used for example in HID devices, where a REQ_GET_DESCRIPTOR |
| request is sent to an interface, which is not covered by the 'chapter 9' |
| specification. |
| |
| @param [in] pfnHandler Callback function pointer |
| */ |
| void USBRegisterCustomReqHandler(TFnHandleRequest *pfnHandler) |
| { |
| pfnHandleCustomReq = pfnHandler; |
| } |
| |