Repository for import to local machine

Dependencies:   DMBasicGUI DMSupport

USBHostGC.cpp

Committer:
jmitc91516
Date:
2017-07-31
Revision:
8:26e49e6955bd
Parent:
1:a5258871b33d

File content as of revision 8:26e49e6955bd:

/**
 * A class to communicate with a USB GC.
 *
 * *** Important note - when reading data from the USB device, you must do this into memory allocated using       ***
 * *** the 'getSafeMem' function of the USBHost class. If you read it into 'normal' memory, your code will crash. ***
 * *** This is my experience so far, at least. This applies, e.g. to all the 'Get...' functions below.            ***
 * *** (There may, of course, be a simple explanation for this, but if so, I am not currently aware of it.)       ***
 * *** This is why (currently) all the 'Get...' functions allocate a buffer from safe memory,                     ***
 * *** read the data into it, then copy the data into the buffer provided by the caller.                          ***
 */

#include "USBHostGC.h"

extern void EasyGUIDebugPrint(char *stuffToPrint, short X, short Y);

/*
    Displays the specified text string at the specified location in the currently-displayed easyGUI page.
    
    Defined in main.cpp - and omits the 'ALLOW_DEBUG_PRINTS' #define
*/
extern void SpecialDebugPrint(char *stuffToPrint, short X, short Y);

USBHostGC::USBHostGC()
{
    debugWindow = NULL;
    
    usbHost = USBHost::getHostInst();
    
    reportBuffer = NULL;
    
    NoDeviceConnected(); // So far...
    
    alreadyInSetDeviceReport = false;
}

USBHostGC::~USBHostGC()
{
    NoDeviceConnected();
}

void USBHostGC::NoDeviceConnected(void)
{
    deviceIsGC = false;
    intfGC = -1;
    intInEndpointGC = NULL;
    usbGCDeviceConnected = NULL;
    
    if (reportBuffer != NULL) {
        usbHost->returnSafeMem(reportBuffer);
        reportBuffer = NULL;
    }
}

void USBHostGC::DebugPrint(const char* debugText)
{
    if(debugWindow != NULL) {
        swim_put_text(debugWindow, debugText);
    }
}

/*
    Data sent to/from the GC over the USB link has to be in 'safe' memory - 
    it will crash if ordinary memory is used. This function allocates 
    a buffer in safe memory
*/
void USBHostGC::AllocateReportBuffer(void)
{
    int reportSize = GC_MESSAGE_LENGTH;

    if (intInEndpointGC != NULL) {
        int intInEndpointSize = intInEndpointGC->getSize();
        if (intInEndpointSize > reportSize) {
            reportSize = intInEndpointSize;
        }
    }

    reportBuffer = usbHost->getSafeMem(reportSize);
}

/*
    Attach the specified USB device to this instance
*/
bool USBHostGC::AttachGCDevice(USBDeviceConnected* usbDeviceConnected)
{
    intInEndpointGC = usbDeviceConnected->getEndpoint(intfGC, INTERRUPT_ENDPOINT, IN);
    if (!intInEndpointGC) {
        DebugPrint("AttachGCDevice - interrupt endpoint in not found - returning false\n");
        NoDeviceConnected();
        return false;
    }

    usbDeviceConnected->setName("GC", intfGC);

    usbHost->registerDriver(usbDeviceConnected, intfGC, this, &USBHostGC::NoDeviceConnected);

    intInEndpointGC->attach(this, &USBHostGC::RXHandler);

    if (reportBuffer == NULL) {
        AllocateReportBuffer();
    }

    usbHost->interruptRead(usbDeviceConnected, intInEndpointGC, reportBuffer, intInEndpointGC->getSize(), false);

    usbGCDeviceConnected = usbDeviceConnected;
    
    return true;
}

/*
    Invoked by the OS when a USB read (from the GC) has completed
*/
void USBHostGC::RXHandler(void)
{
    responseReceived = true;
    
    if (usbGCDeviceConnected) {
        usbHost->interruptRead(usbGCDeviceConnected, intInEndpointGC, reportBuffer, intInEndpointGC->getSize(), false);
    }
}

/*
    The SetDeviceReport function is not re-entrant (and even if it were, the GC is not, so simultaneous calls 
    to SetDeviceReport would not work anyway).
    
    This function tells the caller whether or not a 'SetDeviceReport' call is in progress.
    It returns true if so, false if not.
    
    The expectation is that the caller will wait until this function returns false
    before calling SetDeviceReport.
*/
bool USBHostGC::ExecutingSetDeviceReport(void)
{
    return alreadyInSetDeviceReport;
}

/*
    Sends a command to the GC, gets a response back. In this context, what we would think of
    as a 'command' is called a 'report' in the GC code - hence the use of the word 'report' here.
    
    Args: a pointer to the USB device corresponding to the GC
          a pointer to the command/report to be sent to the GC
          a pointer to the buffer to contain the GC's response
          
    Returns the USB_TYPE code giving the status of the transaction.
*/
USB_TYPE USBHostGC::SetDeviceReport(USBDeviceConnected * dev, const char* report, char* response)
{
    // Neither this function, nor the GC itself, are re-entrant...
    
    if(alreadyInSetDeviceReport) {
        return USB_TYPE_PROCESSING;
    }
    
    alreadyInSetDeviceReport = true;
    
    if (reportBuffer == NULL) {
        AllocateReportBuffer();
    }
    
    int i;
    for (i = 0; (i < GC_MESSAGE_LENGTH) && (report[i]); ++i) {
        reportBuffer[i] = (uint8_t) report[i];
    }
    //reportBuffer[i++] = (uint8_t) GC_CR; // Append <CR> - make *sure* it is the same code the GC expects
    // Fill remainder of buffer with nulls, to pad out to 10 bytes
    for ( ; i < GC_MESSAGE_LENGTH; ++i) {
        reportBuffer[i] = 0;
    }
    //reportBuffer[GC_MESSAGE_LENGTH - 1] = (uint8_t) GC_CR;
    
    responseReceived = false; // Synchronise with RXHandler
    
//#define ALLOW_DEBUG_PRINTS_HERE
#ifdef ALLOW_DEBUG_PRINTS_HERE
    SpecialDebugPrint("SetDeviceReport - before controlWrite", 20,80);
#endif
    USB_TYPE t = usbHost->controlWrite( dev,
                         1, // Non-zero - tells GC - set report, not set configuration
                         SET_CONFIGURATION,
                         0,
                         0, reportBuffer, GC_MESSAGE_LENGTH);
    //EasyGUIDebugPrint("SetDeviceReport - after controlWrite", 20,100);
    
#ifdef ALLOW_DEBUG_PRINTS_HERE
    SpecialDebugPrint("SetDeviceReport - waiting for response", 20,120);
#endif    

    while (!responseReceived) {
        // wait...
        Thread::wait(10); // *** With this, we can successfully call this function from the Ethernet thread ***
                          // (without it, this function enters an infinite loop at this point when called 
                          // from the Ethernet thread, although it is OK from the main thread)
    }

#ifdef ALLOW_DEBUG_PRINTS_HERE
    SpecialDebugPrint("SetDeviceReport - after wait for response", 20,120);
#endif    

    // RXHandler has now received the response - return it to the caller
    
    for (i = 0; i < GC_MESSAGE_LENGTH; ++i) {
        response[i] = reportBuffer[i];
    }
    response[i] = '\0';  
    
    alreadyInSetDeviceReport = false;
    
    return t;
}

// Part of IUSBEnumerator interface
/* virtual */ void USBHostGC::setVidPid(uint16_t vid, uint16_t pid)
{
    char buff[100];
    sprintf(buff, "setVidPid(%d %X, %d %X)\n", vid, vid, pid, pid);
    DebugPrint(buff);

    deviceIsGC = ((vid == GC_VENDOR_ID) && ((pid == GC_PRODUCT_ID) || (pid == GC_PRODUCT_ID_2)));

    DebugPrint(deviceIsGC ? "Device is a GC\n" : "Device is *not* a GC\n");
}

// Part of IUSBEnumerator interface - must return true if the interface should be parsed
/* virtual */ bool USBHostGC::parseInterface(uint8_t intf_nb, uint8_t intf_class, uint8_t intf_subclass, uint8_t intf_protocol)
{
    char buff[100];
    sprintf(buff, "parseInterface(%d %X, %d %X, %d %X, %d %X)\n", intf_nb, intf_nb, intf_class, intf_class, intf_subclass, intf_subclass, intf_protocol, intf_protocol);
    DebugPrint(buff);

    // setVidPid should have been called before this - 
    // assume it will have identified whether or not the device is a GC
    if (deviceIsGC) {
        if ((intfGC == -1) &&
            (intf_class == HID_CLASS)) {
            // Don't care about subclass and protocol in this case

            intfGC = intf_nb;
            DebugPrint("  **** GC found ****  \n");
        }
        DebugPrint("GC found - parseInterface returning true\n");
        return true;
    }

    DebugPrint("No GC found - parseInterface returning false\n");
    return false;
}

// Part of IUSBEnumerator interface - must return true if the endpoint will be used
/* virtual */ bool USBHostGC::useEndpoint(uint8_t intf_nb, ENDPOINT_TYPE type, ENDPOINT_DIRECTION dir)
{
    char buff[100];
    sprintf(buff, "useEndPoint(%d %X, %d %X, %d %X)\n", intf_nb, intf_nb, type, type, dir, dir);
    DebugPrint(buff);

    if (deviceIsGC) {
        if ((intfGC == intf_nb) &&
            (type == INTERRUPT_ENDPOINT) &&
            (dir == IN)) {
            DebugPrint("GC endpoint found - useEndPoint returning true\n");
            return true;
        }
    }

    // 'else'
    DebugPrint("GC endpoint not found - useEndpoint returning false\n");
    return false;
}