| /* | |
| 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; | |
| } | |
| /** | |
| 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; | |
| 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; | |
| } | |