/* LPCUSB, an USB device driver for LPC microcontrollers Copyright (C) 2006 Bertrik Sikken (bertrik@sikken.nl) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file Control transfer handler. In case of a control-write (host-to-device), this module collects the full control message in a local buffer, then sends it off to an installed request handler. In case of a control-read (device-to-host), an installed request handler is asked to handle the request and provide return data. The handler can either put the data in the control data buffer through the supplied pointer, or it can supply a new data pointer. In both cases, the handler is required to update the data length in *piLen; Currently, control transfers are handled in a very simple way, keeping almost no state about the control transfer progress (setup stage, data stage, status stage). We simply follow the host: if it sends data, we store it in the control data buffer and if it asks for data, we just send the next block. */ #include "type.h" #include "usbdebug.h" #include "usbstruct.h" #include "usbapi.h" #define MAX_CONTROL_SIZE 128 /**< maximum total size of control transfer data */ #define MAX_REQ_HANDLERS 4 /**< standard, class, vendor, reserved */ static TSetupPacket Setup; /**< setup packet */ static U8 *pbData; /**< pointer to data buffer */ static int iResidue; /**< remaining bytes in buffer */ static int iLen; /**< total length of control transfer */ /** Array of installed request handler callbacks */ static TFnHandleRequest *apfnReqHandlers[4] = {NULL, NULL, NULL, NULL}; /** Array of installed request data pointers */ static U8 *apbDataStore[4] = {NULL, NULL, NULL, NULL}; /** Local function to handle a request by calling one of the installed request handlers. In case of data going from host to device, the data is at *ppbData. In case of data going from device to host, the handler can either choose to write its data at *ppbData or update the data pointer. @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 handles successfully */ static BOOL _HandleRequest(TSetupPacket *pSetup, int *piLen, U8 **ppbData) { TFnHandleRequest *pfnHandler; int iType; iType = REQTYPE_GET_TYPE(pSetup->bmRequestType); pfnHandler = apfnReqHandlers[iType]; if (pfnHandler == NULL) { DBG("No handler for reqtype %d\n", iType); return FALSE; } return pfnHandler(pSetup, piLen, ppbData); } /** Local function to stall the control endpoint @param [in] bEPStat Endpoint status */ static void StallControlPipe(U8 bEPStat) { U8 *pb; int i; USBHwEPStall(0x80, TRUE); // dump setup packet DBG("STALL on ["); pb = (U8 *)&Setup; for (i = 0; i < 8; i++) { DBG(" %02x", *pb++); } DBG("] stat=%x\n", bEPStat); } /** Sends next chunk of data (possibly 0 bytes) to host */ static void DataIn(void) { int iChunk; iChunk = MIN(MAX_PACKET_SIZE0, iResidue); USBHwEPWrite(0x80, pbData, iChunk); pbData += iChunk; iResidue -= iChunk; } /** * Handles IN/OUT transfers on EP0 * * @param [in] bEP Endpoint address * @param [in] bEPStat Endpoint status */ void USBHandleControlTransfer(U8 bEP, U8 bEPStat) { int iChunk, iType; if (bEP == 0x00) { // OUT transfer if (bEPStat & EP_STATUS_SETUP) { // setup packet, reset request message state machine USBHwEPRead(0x00, (U8 *)&Setup, sizeof(Setup)); DBG("S%x", Setup.bRequest); // defaults for data pointer and residue iType = REQTYPE_GET_TYPE(Setup.bmRequestType); pbData = apbDataStore[iType]; iResidue = Setup.wLength; iLen = Setup.wLength; if ((Setup.wLength == 0) || (REQTYPE_GET_DIR(Setup.bmRequestType) == REQTYPE_DIR_TO_HOST)) { // ask installed handler to process request if (!_HandleRequest(&Setup, &iLen, &pbData)) { DBG("_HandleRequest1 failed\n"); StallControlPipe(bEPStat); return; } // send smallest of requested and offered length iResidue = MIN(iLen, Setup.wLength); // send first part (possibly a zero-length status message) DataIn(); } } else { if (iResidue > 0) { // store data iChunk = USBHwEPRead(0x00, pbData, iResidue); if (iChunk < 0) { StallControlPipe(bEPStat); return; } pbData += iChunk; iResidue -= iChunk; if (iResidue == 0) { // received all, send data to handler iType = REQTYPE_GET_TYPE(Setup.bmRequestType); pbData = apbDataStore[iType]; if (!_HandleRequest(&Setup, &iLen, &pbData)) { DBG("_HandleRequest2 failed\n"); StallControlPipe(bEPStat); return; } // send status to host DataIn(); } } else { // absorb zero-length status message iChunk = USBHwEPRead(0x00, NULL, 0); DBG(iChunk > 0 ? "?" : ""); } } } else if (bEP == 0x80) { // IN transfer // send more data if available (possibly a 0-length packet) DataIn(); } else { ASSERT(FALSE); } } /** Registers a callback for handling requests @param [in] iType Type of request, e.g. REQTYPE_TYPE_STANDARD @param [in] *pfnHandler Callback function pointer @param [in] *pbDataStore Data storage area for this type of request */ void USBRegisterRequestHandler(int iType, TFnHandleRequest *pfnHandler, U8 *pbDataStore) { ASSERT(iType >= 0); ASSERT(iType < 4); apfnReqHandlers[iType] = pfnHandler; apbDataStore[iType] = pbDataStore; }