File content as of revision 1:4461071ed964:
/*
A host controller driver from the mBed device.
Copyright (C) 2012 Richard e Collins - richard.collins@linux.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <mbed.h>
#include "UsbHost.h"
#include "UsbHostController.h"
#include "HardwareDefines.h"
#include "Debug.h"
#include "UsbStructures.h"
#include "UsbEnums.h"
#if 0
#define DEBUGV DEBUG
#else
#define DEBUGV(...) do {} while(0)
#endif
namespace USB
{
uint32_t controlTransferDataLength = 0;
Host::Host()
{
for( int n = 0 ; n < MAX_DEVICES ; n++ )
{
devices[n] = NULL;
}
driver = HostController::get();
driver->Init(this,deviceZero);
}
Host::~Host()
{
}
Device* Host::AllocateDevice(int endpointZeroMaxPacketSize,int hubPortStatus)
{
for( int n = 0 ; n < MAX_DEVICES ; n++ )
{
if( devices[n] == NULL )
{
devices[n] = (Device *)driver->AllocateMemoryPoolItem();
memset(devices[n],0,sizeof(Device));
devices[n]->id = (uint8_t)(n+1);//The device is +1 as device 0 is the host controller.
devices[n]->endpointZeroMaxPacketSize = endpointZeroMaxPacketSize;
devices[n]->lowspeed = (hubPortStatus&ROOTHUB_LOW_SPEED_DEVICE_ATTACHED) ? 1 : 0;
devices[n]->blockOnEP = -1;
return devices[n];
}
}
return NULL;
}
void Host::SendControlTransferData(Device* device,int dataToggle)
{
driver->QueueControlTransfer(
device->id,
device->controlTransferDirToHost == 1 ? TDD_IN : TDD_OUT,
0,
device->endpointZeroMaxPacketSize,
device->lowspeed,
dataToggle,
device->controlTransferData,
device->controlTransferDataLength);
device->controlTransferState = CTS_DATA;
}
void Host::SendControlTransferAcknowledge(Device* device,int dataToggle)
{
//Send empty packet to ACK.
driver->QueueControlTransfer(
device->id,
device->controlTransferDirToHost == 0 ? TDD_IN : TDD_OUT,
0,
device->endpointZeroMaxPacketSize,
device->lowspeed,
dataToggle,
NULL,0);
device->controlTransferState = CTS_ACK;
}
Device* Host::getDevice(int deviceID)
{
if( deviceID == 0 )
{
return deviceZero;
}
deviceID--;
if( deviceID > -1 && deviceID < MAX_DEVICES )
{
return devices[deviceID];
}
return NULL;
}
void Host::Update()
{
driver->Update();
//Now the host has been updated service any outstanding transfers.
for(int n = 0 ; n < MAX_DEVICES ; n++ )
{
if( devices[n] != NULL && devices[n]->pendingCallbacks != NULL )
{
TransferDescriptor *transfer = devices[n]->pendingCallbacks;
devices[n]->pendingCallbacks = NULL;
while( transfer )
{
TransferDescriptor *next = transfer->nextTD;
// DEBUG("onReceive (0x%08x) (0x%08x)",transfer,next);
int deviceEP = transfer->ep | (transfer->direction == TDD_IN ? 0x80 : 0);
int reciveLength = transfer->CurrentBufferPointer - transfer->data;
transfer->transferCallback->onReceive(devices[n]->id,deviceEP,0,transfer->data,reciveLength);
driver->FreeMemoryPoolItem(transfer);
transfer = next;
}
}
}
}
int Host::getDeviceDescriptor(int deviceID,DeviceDescription &description)
{
return ControlTransfer(deviceID,LIBUSB_ENDPOINT_IN, LIBUSB_REQUEST_GET_DESCRIPTOR,(LIBUSB_DT_DEVICE << 8), 0, (uint8_t*)&description, sizeof(description),DEFAULT_TIMEOUT);
}
const uint8_t* Host::getConfigurationDescriptor(int deviceID,int index)
{
uint8_t* buf = driver->getScratchRam();
ControlTransfer(deviceID,LIBUSB_ENDPOINT_IN, LIBUSB_REQUEST_GET_DESCRIPTOR,(LIBUSB_DT_CONFIG << 8)|index, 0,buf,255,DEFAULT_TIMEOUT);
return (const uint8_t*)buf;
}
const char* Host::getStringDescriptor(int deviceID,int index)
{
Device* dev = getDevice(deviceID);
if( dev == NULL )
{
return "error";
}
uint8_t* buf = driver->getScratchRam();
ControlTransfer(deviceID,LIBUSB_ENDPOINT_IN, LIBUSB_REQUEST_GET_DESCRIPTOR,(LIBUSB_DT_STRING << 8)|index, dev->languageID,buf,255,DEFAULT_TIMEOUT);
uint8_t* string = buf;
int len = buf[0];
for( int n = 2 ; n < len ; n += 2 )
{
*string++ = buf[n];
}
*string = 0;
return (char*)buf;
}
int Host::setConfiguration(int deviceID,int configuration)
{
return ControlTransfer(deviceID,LIBUSB_ENDPOINT_OUT, LIBUSB_REQUEST_SET_CONFIGURATION,configuration, 0,NULL,0,DEFAULT_TIMEOUT);
}
/*
* Control transfer uses three stages, the setup stage where the request is sent.
* The optional Data Stage consists of one or multiple IN or OUT transfers.
* The setup request indicates the amount of data to be transmitted in this stage.
* If it exceeds the maximum packet size, data will be sent in multiple transfers each being the maximum packet length except for the last packet.
* Status Stage reports the status of the overall request and this once again varies due to direction of transfer. Status reporting is always performed by the function.
*/
int Host::ControlTransfer(int deviceID,uint8_t requestType, uint8_t request, uint16_t value, uint16_t index,const uint8_t *data, uint16_t length, uint32_t timeout)
{
DEBUGV("ControlTransfer");
Device* device = getDevice(deviceID);
//Remeber the packet we are sending so that when we get the responce from the setup packet we can then ask for or send the data.
device->controlTransferData = data;
device->controlTransferDataLength = length;
device->controlTransferDirToHost = (requestType & LIBUSB_ENDPOINT_IN) ? 1 : 0;
device->controlTransferState = CTS_SETUP;
//First do the setup packet.
SetupPacket* sp = (SetupPacket*)driver->AllocateMemoryPoolItem();
sp->requestType = requestType;
sp->request = request;
sp->value = value;
sp->index = index;
sp->length = length;
DEBUGV("Setup %d %d %d %d %d",sp->requestType,sp->request,sp->value,sp->index,sp->length);
driver->QueueControlTransfer(deviceID,TDD_SETUP,0,device->endpointZeroMaxPacketSize,device->lowspeed,TOGGLE_DATA0,(const uint8_t*)sp,sizeof(SetupPacket));
while(device->controlTransferState != CTS_IDLE)
{
wait_ms(50);
};
//Free this up.
driver->FreeMemoryPoolItem(sp);
DEBUGV("Done, recived %d",controlTransferDataLength);
return 0;
}
int Host::BulkTransfer(int deviceID,uint8_t endpoint,const uint8_t* data,int length,TransferCallback* callback)
{
DEBUGV("BulkTransfer Device(%d) EP(%d) Len(%d) CB(0x%08x)",deviceID,endpoint,length,callback);
Device* device = getDevice(deviceID);
device->blockOnEP = endpoint;
driver->QueueBulkTransfer(deviceID,(endpoint&LIBUSB_ENDPOINT_IN) ? TDD_IN : TDD_OUT,endpoint&0x7f,device->endpointZeroMaxPacketSize,device->lowspeed,callback,data,length);
if( callback == NULL )
{
while( device->blockOnEP == endpoint )
{
driver->Update();
wait_ms(100);
}
}
return 0;
}
void Host::AddDevice(int hub,int port,int hubPortStatus)
{
DEBUG("AddDevice(%d,%d,%08x)",hub,port,hubPortStatus);
DeviceDescription description;
description.length = sizeof(DeviceDescription);
//Setup device zero.
deviceZero->endpointZeroMaxPacketSize = 8;
deviceZero->lowspeed = (hubPortStatus&ROOTHUB_LOW_SPEED_DEVICE_ATTACHED) ? 1 : 0;
//For the enumeration we'll use device zero, once done will set the device address and get the rest of the details.
//Now we add the first endpoint, endpoint zero by default.
//Once added we'll read the device's config and add endpoints for them.
//I add my endpoints to the host controller and leave them there.
//I set the direction code so that the direction is read from the transfer descriptor.
//This means that I need less endpoint objects.
//I leave them attached with not transfers to do. That is ok and as per the spec.
//Old devices may only have a max packet size of 8 bytes.
//Using device 0 for the setup and enum phase of the newley connected device.
//We do this just to get some info on the device and then send a 'set address' command.
//At that point we'll start using it's real ID.
DEBUGV("Getting endpoint zero maxPacketSize");
ControlTransfer(0,LIBUSB_ENDPOINT_IN, LIBUSB_REQUEST_GET_DESCRIPTOR,(LIBUSB_DT_DEVICE << 8), 0, (uint8_t*)&description,8,DEFAULT_TIMEOUT);
//Now allocate the device and it's ID.
DEBUGV("Allocating device");
Device* device = AllocateDevice(description.maxPacketSize,hubPortStatus);
//Now correct the endpoint's max packet size. Needs this for when we setup the endpoint descriptor for a transfer.
DEBUGV("endpointZeroMaxPacketSize(%d)",device->endpointZeroMaxPacketSize);
//Set the devices new address.
DEBUGV("Setting device address to %d",device->id);
ControlTransfer(0,LIBUSB_ENDPOINT_OUT | LIBUSB_RECIPIENT_DEVICE, LIBUSB_REQUEST_SET_ADDRESS, device->id,0,NULL,0,DEFAULT_TIMEOUT);
//New ID is now set, so we can free the object used when it was called device zero.
driver->DetachDevice(0);
//Now fetch the full device description.
DEBUGV("Getting device description");
getDeviceDescriptor(device->id,description);
DEBUGV("idVendor(0x%04x) idProduct(0x%04x)",description.idVendor,description.idProduct);
//Get the language ID.
uint16_t* lang = (uint16_t*)driver->AllocateMemoryPoolItem();
ControlTransfer(device->id,LIBUSB_ENDPOINT_IN, LIBUSB_REQUEST_GET_DESCRIPTOR,LIBUSB_DT_STRING<<8,0,(uint8_t*)lang,16,DEFAULT_TIMEOUT);
device->languageID = lang[1];
driver->FreeMemoryPoolItem(lang);
//Set the first config by default, will get most things up and running.
const USB::ConfigurationDescription* config = (const USB::ConfigurationDescription*)getConfigurationDescriptor(device->id,0);
wait_ms(100);
setConfiguration(device->id,config->configID);
wait_ms(100);
//Tell the app's inherted code about this new device.
onConnected(device->id,description);
}
void Host::TransferDone(TransferDescriptor *transfer)
{
//This is the endpoint with the dir encoded into it, the way it is done to the applicaition coder.
//Needed when we are blocking for a non interput transfer to finish.
int epDir = transfer->ep | (transfer->direction==TDD_IN?0x80:0);
DEBUGV("TransferDone, device(%d) ep(%d) errorCount(%d) status(%d) direction(%d) dataToggle(%d)",transfer->deviceID,epDir,transfer->errorCount,transfer->conditionCode,transfer->direction,transfer->dataToggle);
//Get the device this TD that has just been done for.
Device* device = getDevice(transfer->deviceID);
if( transfer->transferType == TT_CONTROL )
{
//See if this was a setup packet, if so do we have data to ask for, if not then just ack the transaction.
//TODO: Error checking!
switch( device->controlTransferState )
{
case CTS_SETUP:
if( device->controlTransferData != NULL )
{
SendControlTransferData(device,transfer->dataToggle);
}
else
{
SendControlTransferAcknowledge(device,transfer->dataToggle);
}
break;
case CTS_DATA:
controlTransferDataLength = (transfer->bufferEnd - device->controlTransferData) + 1;
SendControlTransferAcknowledge(device,transfer->dataToggle);
break;
case CTS_ACK:
device->controlTransferState = CTS_IDLE;
break;
}
}
if( transfer->transferCallback != NULL )
{
transfer->nextTD = device->pendingCallbacks;
device->pendingCallbacks = transfer;
}
if( device->blockOnEP == epDir )
{
device->blockOnEP = -1;
}
}
};//namespace USB