UsbHost.cpp

Committer:
RichardUK
Date:
2012-09-19
Revision:
2:33e7ce63dd6d
Parent:
1:4461071ed964

File content as of revision 2:33e7ce63dd6d:

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