/***********************************************************
/        Title: usbserial.c
/       Author: David Corcoran
/      Purpose: Abstracts usb API to serial like calls
/      License: See file COPYING
/         $Id: usbserial_mosx.c,v 1.18 2012/04/08 19:09:46 rousseau Exp $
************************************************************/


#include <stdio.h>
#include <unistd.h>
#include <mach/mach.h>
#include <mach/mach_error.h>

#include <IOKit/IOKitLib.h>
#include <IOKit/usb/IOUSBLib.h>

#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFURL.h>
#include <CoreFoundation/CFPlugin.h>

#include <IOKit/IOCFPlugIn.h>

#include <assert.h>
#include "Config.h"
#include "gempc_ifdhandler.h"
#include "GemCore.h"
#include "GCdebug.h"
#include "usbserial.h"

#define USBMAX_READERS  16

// Read time out in milliseconds
unsigned long ReadTimeOut = 60000;

/* Change the following to uniquely match your reader. */

/*enum {
    kMyDeviceClass		= kIOUSBAnyClass,
    kMyDeviceSubClass		= kIOUSBAnySubClass,
    kMyDeviceProtocol		= kIOUSBAnyProtocol
};
  */

static int                      iInitialized = FALSE;

typedef struct _intFace {
  IOUSBInterfaceInterface182  **iface;
  IOUSBDeviceInterface182	**dev;
  UInt32			usbAddr;
  UInt8				inPipeRef;
  UInt8				outPipeRef;

} intrFace, *pIntrFace;

static pIntrFace intFace[USBMAX_READERS];

void FreeintFace();


void FreeintFace()
{
    int i;
    for (i=0; i < USBMAX_READERS; i++) {
        free(intFace[i]);

    }
}

status_t OpenUSB( DWORD lun, DWORD Channel)
{
    CFMutableDictionaryRef		USBMatch = 0;
    io_iterator_t			iter = 0;
    io_service_t			USBDevice = 0;
    io_service_t			USBIface = 0;
    kern_return_t			kr;
    io_iterator_t			iterB = 0;
    SInt32				score;
    IOUSBFindInterfaceRequest		findInterface;
    IOCFPlugInInterface			**iodev=NULL;
    IOCFPlugInInterface			**iodevB=NULL;
    HRESULT				res;
    DWORD				rdrLun;
    SInt32				hpManu_id, hpProd_id;
    UInt32				usbAddr,usbAddrtest;
    short				iFound;
    int					i;
    IOUSBConfigurationDescriptorPtr     confDesc;
    UInt8				intfNumEndpoints;
    UInt8				direction, number, transferType, interval;
    UInt16				maxPacketSize;
    mach_port_t			masterPort;

    CFBundleRef myBundle;
    CFStringRef propertyString;
    CFDictionaryRef bundleInfoDict;
    const char *cStringValue;

    DEBUG_INFO("");

    hpManu_id = 0;
    hpProd_id = 0;



    // Look for a bundle using its identifier
    if (!(myBundle = CFBundleGetBundleWithIdentifier(CFSTR("free.pcsc.GemPC430") )))
    {
        DEBUG_CRITICAL("Bundle Identifier not found");
        return STATUS_UNSUCCESSFUL;
    }
    bundleInfoDict = CFBundleGetInfoDictionary(myBundle);
    if (bundleInfoDict == NULL)
    {
        DEBUG_CRITICAL("Bundle InfoDic error");
        return STATUS_UNSUCCESSFUL;
    }


    propertyString = CFDictionaryGetValue(bundleInfoDict,
                                          CFSTR("ifdVendorID"));
    if (propertyString == NULL)
    {
        DEBUG_CRITICAL("Bundle InfoDic error: ifdVendorID not found");
        return STATUS_UNSUCCESSFUL;
    }

    cStringValue = CFStringGetCStringPtr(propertyString,
                                         CFStringGetSystemEncoding());
    hpManu_id = strtoul(cStringValue, 0, 16);


    propertyString = CFDictionaryGetValue(bundleInfoDict,
                                          CFSTR("ifdProductID"));
    if (propertyString == 0)
    {
        DEBUG_CRITICAL("Bundle InfoDic error: ifdProductID not found");
        return STATUS_UNSUCCESSFUL;
    }

    cStringValue = CFStringGetCStringPtr(propertyString,
                                         CFStringGetSystemEncoding());
    hpProd_id = strtoul(cStringValue, 0, 16);

    propertyString = CFDictionaryGetValue(bundleInfoDict,
                                          CFSTR("ifdReadTimeOut"));
    if (propertyString == 0)
    {
        DEBUG_CRITICAL("Bundle InfoDic error: could find ifdReadTimeOut, keep default");
    }
    else
    {
        cStringValue = CFStringGetCStringPtr(propertyString,
                                         CFStringGetSystemEncoding());
       ReadTimeOut = strtoul(cStringValue, 0, 10);
    }

    DEBUG_CRITICAL2("Driver configured to detect Vendor ID = %04X", hpManu_id);
    DEBUG_CRITICAL2("Driver configured to detect Product ID = %04X", hpProd_id);

    iFound = FALSE;
    rdrLun = lun >> 16;

    if ( iInitialized == FALSE ) {

        for (i=0; i < USBMAX_READERS; i++) {
            intFace[i] = (pIntrFace)malloc(sizeof(intrFace));
            if (!intFace[i])
            {
                /* Malloc failed! */
                DEBUG_CRITICAL("Couldn't malloc structure: ");
                return STATUS_UNSUCCESSFUL;
            }
            (intFace[i])->usbAddr = 0;
            (intFace[i])->dev = NULL;
            (intFace[i])->iface = NULL;
            (intFace[i])->inPipeRef = 0;
            (intFace[i])->outPipeRef = 0;
        }

        iInitialized = TRUE;

    }

    /* first create a master_port for my task */
    kr = IOMasterPort(bootstrap_port, &masterPort);
    if (kr || !masterPort)
    {
        DEBUG_CRITICAL2("Couldn't create a master IOKit Port (0x%08X)", kr);
        FreeintFace();
        return STATUS_UNSUCCESSFUL;
    }



    USBMatch = IOServiceMatching(kIOUSBDeviceClassName);
    if (!USBMatch)
    {
        DEBUG_CRITICAL("Can't create a USB matching dictionary");
        mach_port_deallocate(mach_task_self(), masterPort);
        FreeintFace();
        return STATUS_UNSUCCESSFUL;
    }



    CFDictionarySetValue(USBMatch,
                         CFSTR(kUSBVendorName),
                         CFNumberCreate(kCFAllocatorDefault,
                                        kCFNumberSInt32Type,
                                        &hpManu_id));
    CFDictionarySetValue(USBMatch,
                         CFSTR(kUSBProductName),
                         CFNumberCreate(kCFAllocatorDefault,
                                        kCFNumberSInt32Type,
                                        &hpProd_id));




    /* create an iterator over all matching IOService nubs */
    kr = IOServiceGetMatchingServices(masterPort, USBMatch, &iter);
    if (kr)
    {
        DEBUG_CRITICAL2("Can't create a USB Service iterator (0x%08X)", kr);
//?
        CFRelease(USBMatch);
        mach_port_deallocate(mach_task_self(), masterPort);
        return STATUS_UNSUCCESSFUL;
    }

    // masterPort not used any more
    mach_port_deallocate(mach_task_self(), masterPort);

    while ( (USBDevice = IOIteratorNext(iter)) )
    {

        kr = IOCreatePlugInInterfaceForService(USBDevice, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &iodev, &score);
        IOObjectRelease(USBDevice);	/* done with the device object now */
        if (kr || !iodev)
        {
            DEBUG_CRITICAL2("unable to create a plugin (0x%08X)", kr);
            continue;
        }

        /* i have the device plugin. I need the device interface */
        res = (*iodev)->QueryInterface(iodev, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), (LPVOID)&(intFace[rdrLun])->dev);
        //(*iodev)->Release(iodev);
        if (res || !(intFace[rdrLun])->dev)
        {
            DEBUG_CRITICAL2("Couldn't create a device interface (0x%08X)", (int)res);
            continue;
        }

        kr = (*(intFace[rdrLun])->dev)->GetLocationID(((intFace[rdrLun])->dev), &usbAddr);


        for (i=0; i < USBMAX_READERS; i++)
        {
            if ( usbAddr == (intFace[i])->usbAddr )
            {
                iFound = TRUE;
                break;
            }
        }

        if ( iFound ) {
            iFound = FALSE;
            continue;
        }

        /* We found it now lets break out of the loop */
        break;
    }


    if ((intFace[rdrLun])->dev == NULL)
    {
        /* Device not found */
        CFRelease(USBMatch);
        return STATUS_UNSUCCESSFUL;
    }

    kr = (*(intFace[rdrLun])->dev)->GetLocationID(((intFace[rdrLun])->dev), &usbAddrtest);


    kr = (*(intFace[rdrLun])->dev)->USBDeviceOpen(((intFace[rdrLun])->dev));
    if (kr == kIOReturnExclusiveAccess)
    {
        sleep(1);
        // Try to re-open in case Classic is using the device
       if( kr = (*(intFace[rdrLun])->dev)->USBDeviceOpenSeize(((intFace[rdrLun])->dev)) )
       {
            DEBUG_CRITICAL2("unable to open device: 0x%08X", kr);
           (*(intFace[rdrLun])->dev)->Release(((intFace[rdrLun])->dev));
           return STATUS_UNSUCCESSFUL;
        }
    }
    else
    {
        if ( kr != kIOReturnSuccess)
        {
            DEBUG_CRITICAL2("unable to open device, not kIOReturnExclusiveAccess: 0x%08X", kr);
            (*(intFace[rdrLun])->dev)->Release(((intFace[rdrLun])->dev));
            return STATUS_UNSUCCESSFUL;
        }
    }


    kr = (*(intFace[rdrLun])->dev)->GetConfigurationDescriptorPtr(((intFace[rdrLun])->dev), 0, &confDesc);
    if (kr)
    {
        DEBUG_CRITICAL("ERR: unable to get the configuration");
        (*(intFace[rdrLun])->dev)->USBDeviceClose(((intFace[rdrLun])->dev));
        (*(intFace[rdrLun])->dev)->Release(((intFace[rdrLun])->dev));
        return STATUS_UNSUCCESSFUL;
    }

    (*(intFace[rdrLun])->dev)->ResetDevice(((intFace[rdrLun])->dev));

    kr = (*(intFace[rdrLun])->dev)->SetConfiguration(((intFace[rdrLun])->dev), confDesc->bConfigurationValue);

    if (kr)
    {
        DEBUG_CRITICAL("ERR: unable to set the configuration");
        (*(intFace[rdrLun])->dev)->USBDeviceClose(((intFace[rdrLun])->dev));
        (*(intFace[rdrLun])->dev)->Release(((intFace[rdrLun])->dev));
        return STATUS_UNSUCCESSFUL;
    }



    /* Time to find the first interface */
    findInterface.bInterfaceClass    = kIOUSBFindInterfaceDontCare;
    findInterface.bInterfaceSubClass = kIOUSBFindInterfaceDontCare;
    findInterface.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
    findInterface.bAlternateSetting  = kIOUSBFindInterfaceDontCare;

    kr = (*(intFace[rdrLun])->dev)->CreateInterfaceIterator(
                                                            ((intFace[rdrLun])->dev), &findInterface, &iterB );
    USBIface = IOIteratorNext(iterB);

    if ( USBIface == 0 ) {
        DEBUG_CRITICAL("No interface found");
        return STATUS_UNSUCCESSFUL;
    }

    score = 0;


    /* Create the plugin for the interface service */
    kr  = IOCreatePlugInInterfaceForService(USBIface, kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID, &iodevB, &score);
    res = (*iodev)->QueryInterface(iodevB, CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID),
                                  (LPVOID)&(intFace[rdrLun])->iface);

    (intFace[rdrLun])->usbAddr = usbAddr;


    /* Open the interface to open all the pipes */
    kr = (*(intFace[rdrLun])->iface)->USBInterfaceOpen((intFace[rdrLun])->iface);
    if (kr != kIOReturnSuccess )
    {
        DEBUG_CRITICAL2("unable to open device: 0x%08X", kr);
        return STATUS_UNSUCCESSFUL;
    }

    if (iter) {
        IOObjectRelease(iter);
        iter = 0;
    }

    // Get nb of end points
    kr = (*(intFace[rdrLun])->iface)->GetNumEndpoints((intFace[rdrLun])->iface, &intfNumEndpoints);
    if (kr != kIOReturnSuccess )
    {
        DEBUG_CRITICAL2("unable to get number of end points: 0x%08X", kr);
        return STATUS_UNSUCCESSFUL;
    }
    // pipes are one based, since zero is the default control pipe
    for (i=1; i <= intfNumEndpoints; i++)
    {
        kr = (*(intFace[rdrLun])->iface)->GetPipeProperties((intFace[rdrLun])->iface, i, &direction, &number, &transferType, &maxPacketSize, &interval);
        if (kr != kIOReturnSuccess )
        {
            DEBUG_CRITICAL2("unable to get pipe properties: 0x%08X", kr);
            return STATUS_UNSUCCESSFUL;
        }
        if (transferType != kUSBBulk)
        {
            continue;
        }
        if ((direction == kUSBIn) && !((intFace[rdrLun])->inPipeRef))
        {
            (intFace[rdrLun])->inPipeRef = i;
        }
        if ((direction == kUSBOut) && !((intFace[rdrLun])->outPipeRef))
        {
            (intFace[rdrLun])->outPipeRef = i;
        }

    }


    if ( !((intFace[rdrLun])->outPipeRef) )
    {
        DEBUG_CRITICAL2("unable to get outPipe: 0x%08X", kr);
        CloseUSB(lun);
        return STATUS_UNSUCCESSFUL;
    }

    if (!( (intFace[rdrLun])->inPipeRef))
    {
        DEBUG_CRITICAL2("unable to get inPipe: 0x%08X", kr);
        CloseUSB(lun);
        return STATUS_UNSUCCESSFUL;
    }

    DEBUG_CRITICAL2("New reader found at USB address: %08X", (intFace[rdrLun])->usbAddr);

    return STATUS_SUCCESS;
}

status_t WriteUSB( DWORD lun, DWORD length, unsigned char *buffer )
{
    IOReturn		iorv;
    DWORD		rdrLun;

    rdrLun = lun >> 16;

    DEBUG_XXD("Attempt to write: ", buffer, length);

    /* Make sure the pipe is OK */
    iorv = (*(intFace[rdrLun])->iface)->GetPipeStatus( (intFace[rdrLun])->iface,
                                                            (intFace[rdrLun])->outPipeRef );
    if ( iorv != kIOReturnSuccess ) {
        return STATUS_UNSUCCESSFUL;
    }

    /* Write the data */
    iorv = (*(intFace[rdrLun])->iface)->WritePipe((intFace[rdrLun])->iface,
                                                  (intFace[rdrLun])->outPipeRef,
                                                  buffer, length);

    if ( iorv != kIOReturnSuccess ) {
        return STATUS_UNSUCCESSFUL;
    }

    return STATUS_SUCCESS;
}

status_t ReadUSB( DWORD lun, DWORD *length, unsigned char *buffer )
{
    IOReturn		iorv;
    UInt32		recvLen;
    UInt32              noDataTO, completeTO;
    DWORD		rdrLun;

    rdrLun = lun >> 16;

    DEBUG_COMM2("Attempt to read %ld bytes", *length);

    /* Make sure the pipe is OK */
    iorv = (*(intFace[rdrLun])->iface)->GetPipeStatus( (intFace[rdrLun])->iface,
                                                       (intFace[rdrLun])->inPipeRef
                                                       );
    if ( iorv != kIOReturnSuccess ) {
        return STATUS_UNSUCCESSFUL;
    }

    /* Try to read up to 256 bytes */
    recvLen = 256;
    completeTO = ReadTimeOut;
    noDataTO   = ReadTimeOut;

    iorv = (*(intFace[rdrLun])->iface)->ReadPipeTO( (intFace[rdrLun])->iface,
                                                    (intFace[rdrLun])->inPipeRef,
                                                    buffer, &recvLen, noDataTO, completeTO);
    if ( iorv != 0 ) {
        (*(intFace[rdrLun])->dev)->ResetDevice(((intFace[rdrLun])->dev));
        return STATUS_UNSUCCESSFUL;
    }

    DEBUG_XXD("received: ", buffer, recvLen);

    *length = recvLen;
    return STATUS_SUCCESS;
}

status_t CloseUSB( DWORD lun )
{
    IOReturn iorv;
    DWORD rdrLun;

    rdrLun = lun >> 16;

    // Reset struct
    (intFace[rdrLun])->usbAddr = 0;
    (intFace[rdrLun])->outPipeRef = 0;
    (intFace[rdrLun])->inPipeRef = 0;


    /* Close the interface and rid the iterator */
    iorv = (*(intFace[rdrLun])->iface)->USBInterfaceClose( (intFace[rdrLun])->iface );
    if (iorv) {
        DEBUG_CRITICAL2("ERR: Couldn't close interface (%08x)", (int)iorv);
    }

    /* Release the interface */
    (*(intFace[rdrLun])->iface)->Release((intFace[rdrLun])->iface);

    iorv = (*(intFace[rdrLun])->dev)->USBDeviceClose((intFace[rdrLun])->dev);
    if (iorv) {
        DEBUG_CRITICAL2("ERR: Couldn't close device (%08x)", (int)iorv);
    }

    (*(intFace[rdrLun])->dev)->Release((intFace[rdrLun])->dev);



    if ( iorv != kIOReturnSuccess ) {
        return STATUS_UNSUCCESSFUL;
    }

    return STATUS_SUCCESS;
}

