2009-02-17 09:36:51 +01:00

223 lines
6.2 KiB
C

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