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